管理キー
オーナー登録やアカウント作成は不要です。Cloudflareに設定した管理キーを入力すると、このブラウザのセッション中だけ管理APIを利用できます。
管理キー
管理画面を開く
日本語
English
中文
한국어
📶
Guest Wi-Fi
ご滞在中は無料でご利用いただけます
Wi-Fi 名(SSID)
パスワード
🔑 パスワードをコピー
✓ コピーしました
📱 接続方法を見る
-->
`;
let adSenseLoaded = false;
function loadAdSense() {
if (adSenseLoaded) return;
if (!ADSENSE_CONFIG.publisherId.startsWith("ca-pub-")) return;
const script = document.createElement("script");
script.async = true;
script.src = `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${encodeURIComponent(ADSENSE_CONFIG.publisherId)}`;
script.crossOrigin = "anonymous";
document.head.appendChild(script);
adSenseLoaded = true;
}
function renderAdSenseSlot(targetId, slotId) {
if (!slotId || /^X+$/.test(slotId)) return;
const target = document.getElementById(targetId);
if (!target) return;
target.innerHTML = `
`;
try {
(window.adsbygoogle = window.adsbygoogle || []).push({});
} catch (e) {
console.warn("AdSense slot error", e);
}
}
function renderIMobile() {
// i-mobileタグは管理画面から取得したものを、対象の広告枠へそのまま貼り付けてください。
// i-mobile側でタグの改変は禁止されています。
const top = document.getElementById("ad-top-slot");
const bottom = document.getElementById("ad-bottom-slot");
if (!top || !bottom || !IMOBILE_TAG.trim()) return;
// i-mobileのタグを動的挿入する場合、外部/inline scriptを再生成します。
// ただし document.write を使う旧式タグは動的挿入に向かないため、
// その場合は管理画面のタグをHTMLへ直接配置する方法を推奨します。
[top, bottom].forEach(target => {
target.innerHTML = "";
const template = document.createElement("template");
template.innerHTML = IMOBILE_TAG;
[...template.content.childNodes].forEach(node => {
if (node.nodeType !== Node.ELEMENT_NODE || node.tagName.toLowerCase() !== "script") {
target.appendChild(node.cloneNode(true));
return;
}
const script = document.createElement("script");
for (const attr of node.attributes) {
script.setAttribute(attr.name, attr.value);
}
script.textContent = node.textContent || "";
target.appendChild(script);
});
});
}
function initAds() {
const top = document.getElementById("ad-top");
const bottom = document.getElementById("ad-bottom");
if (ADS_PROVIDER === "none") {
if (top) top.style.display = "none";
if (bottom) bottom.style.display = "none";
return;
}
if (ADS_PROVIDER === "adsense") {
loadAdSense();
setTimeout(() => {
renderAdSenseSlot("ad-top-slot", ADSENSE_CONFIG.topSlot);
renderAdSenseSlot("ad-bottom-slot", ADSENSE_CONFIG.bottomSlot);
}, 250);
return;
}
if (ADS_PROVIDER === "imobile") {
setTimeout(renderIMobile, 50);
}
}
const translations = {
ja: {
subtitle: "ご滞在中は無料でご利用いただけます",
ssid: "Wi-Fi 名(SSID)",
password: "パスワード",
copy: "🔑 パスワードをコピー",
copied: "✓ コピーしました",
guide: "📱 接続方法を見る",
guideTitle: "📱 Wi-Fi接続方法",
guideText: `① スマートフォンのWi-Fi設定を開きます。
② 表示されているWi-Fi名を選択します。
③ パスワードを入力します。
④ 接続完了です。
`,
close: "閉じる"
},
en: {
subtitle: "Free Wi-Fi is available during your stay.",
ssid: "Wi-Fi Name (SSID)",
password: "Password",
copy: "🔑 Copy Password",
copied: "✓ Copied",
guide: "📱 How to Connect",
guideTitle: "📱 How to Connect to Wi-Fi",
guideText: `① Open the Wi-Fi settings on your smartphone.
② Select the Wi-Fi network shown above.
③ Enter the password.
④ You are connected!
`,
close: "Close"
},
zh: {
subtitle: "入住期间可免费使用 Wi-Fi。",
ssid: "Wi-Fi 名称(SSID)",
password: "密码",
copy: "🔑 复制密码",
copied: "✓ 已复制",
guide: "📱 查看连接方法",
guideTitle: "📱 Wi-Fi连接方法",
guideText: `① 打开手机的 Wi-Fi 设置。
② 选择上方显示的 Wi-Fi 网络。
③ 输入密码。
④ 连接完成!
`,
close: "关闭"
},
ko: {
subtitle: "투숙 기간 동안 무료 Wi-Fi를 이용하실 수 있습니다.",
ssid: "Wi-Fi 이름 (SSID)",
password: "비밀번호",
copy: "🔑 비밀번호 복사",
copied: "✓ 복사되었습니다",
guide: "📱 연결 방법 보기",
guideTitle: "📱 Wi-Fi 연결 방법",
guideText: `① 스마트폰의 Wi-Fi 설정을 엽니다.
② 위에 표시된 Wi-Fi 네트워크를 선택합니다.
③ 비밀번호를 입력합니다.
④ 연결이 완료되었습니다!
`,
close: "닫기"
}
};
function apiHeaders() {
return {
"Content-Type": "application/json",
"Authorization": `Bearer ${adminKey}`
};
}
async function api(url, options = {}) {
return fetch(url, {
...options,
headers: {
...(options.body ? { "Content-Type": "application/json" } : {}),
...(adminKey ? { "Authorization": `Bearer ${adminKey}` } : {}),
...(options.headers || {})
}
});
}
async function authorizeAdmin() {
const key = document.getElementById("adminKey").value.trim();
const error = document.getElementById("authError");
error.textContent = "";
if (!key) {
error.textContent = "管理キーを入力してください。";
return;
}
adminKey = key;
sessionStorage.setItem("guest_wifi_admin_key", adminKey);
const response = await api("/api/properties");
if (!response.ok) {
sessionStorage.removeItem("guest_wifi_admin_key");
adminKey = "";
let data = {};
try { data = await response.json(); } catch {}
error.textContent = data.message || "管理キーを確認できませんでした。";
return;
}
document.getElementById("authCard").style.display = "none";
document.getElementById("adminContent").style.display = "block";
const data = await response.json();
properties = data.properties || [];
renderProperties();
}
async function loadProperties() {
const response = await api("/api/properties");
if (response.status === 401) {
sessionStorage.removeItem("guest_wifi_admin_key");
adminKey = "";
document.getElementById("authCard").style.display = "block";
document.getElementById("adminContent").style.display = "none";
return;
}
const data = await response.json();
if (!response.ok) throw new Error(data.message || "取得に失敗しました。");
properties = data.properties || [];
renderProperties();
}
function createButton(text, className, onClick) {
const button = document.createElement("button");
button.className = className;
button.textContent = text;
button.onclick = onClick;
return button;
}
function renderProperties() {
const list = document.getElementById("propertyList");
list.innerHTML = "";
if (!properties.length) {
list.innerHTML = `まだ民泊が登録されていません。 「+ 民泊を追加」から登録してください。
`;
return;
}
properties.forEach(property => {
const card = document.createElement("div");
card.className = "card";
const propertyWrap = document.createElement("div");
propertyWrap.className = "property";
const info = document.createElement("div");
info.className = "property-info";
const name = document.createElement("div");
name.className = "property-name";
name.textContent = property.property_name;
const wifi = document.createElement("div");
wifi.className = "wifi-name";
wifi.textContent = `📶 SSID:${property.ssid}`;
const pass = document.createElement("div");
pass.className = "wifi-password";
pass.textContent = `🔑 Password:${property.password}`;
const publicUrl = `${location.origin}/wifi/${encodeURIComponent(property.public_id)}`;
const urlBox = document.createElement("div");
urlBox.className = "url-box";
urlBox.textContent = publicUrl;
const stats = document.createElement("div");
stats.className = "stats";
const stat = document.createElement("div");
stat.className = "stat";
stat.textContent = `👁 アクセス数:${Number(property.access_count || 0)}`;
stats.appendChild(stat);
info.append(name, wifi, pass, urlBox, stats);
const actions = document.createElement("div");
actions.className = "actions";
actions.append(
createButton("📱 Wi-Fiページ", "button secondary", () => window.open(publicUrl, "_blank")),
createButton("URLコピー", "button secondary", () => copyText(publicUrl)),
createButton("QRコード", "button secondary", () => openQR(property, publicUrl)),
createButton("編集", "button secondary", () => editProperty(property.public_id)),
createButton("削除", "button danger", () => deleteProperty(property.public_id))
);
propertyWrap.append(info, actions);
card.appendChild(propertyWrap);
list.appendChild(card);
});
}
function showAddForm() {
editingId = null;
document.getElementById("formTitle").textContent = "民泊を追加";
document.getElementById("propertyName").value = "";
document.getElementById("ssidInput").value = "";
document.getElementById("passwordInput").value = "";
document.getElementById("formError").textContent = "";
document.getElementById("propertyForm").style.display = "block";
window.scrollTo({ top: 0, behavior: "smooth" });
}
function cancelForm() {
document.getElementById("propertyForm").style.display = "none";
}
async function saveProperty() {
const propertyName = document.getElementById("propertyName").value.trim();
const ssid = document.getElementById("ssidInput").value.trim();
const password = document.getElementById("passwordInput").value.trim();
const error = document.getElementById("formError");
error.textContent = "";
if (!propertyName || !ssid || !password) {
error.textContent = "民泊名、Wi-Fi名、パスワードをすべて入力してください。";
return;
}
const payload = {
property_name: propertyName,
ssid,
password
};
const url = editingId ? `/api/properties/${encodeURIComponent(editingId)}` : "/api/properties";
const response = await api(url, {
method: editingId ? "PUT" : "POST",
body: JSON.stringify(payload)
});
const data = await response.json();
if (!response.ok) {
error.textContent = data.message || "保存に失敗しました。";
return;
}
cancelForm();
await loadProperties();
}
function editProperty(id) {
const property = properties.find(p => p.public_id === id);
if (!property) return;
editingId = id;
document.getElementById("formTitle").textContent = "民泊を編集";
document.getElementById("propertyName").value = property.property_name;
document.getElementById("ssidInput").value = property.ssid;
document.getElementById("passwordInput").value = property.password;
document.getElementById("formError").textContent = "";
document.getElementById("propertyForm").style.display = "block";
window.scrollTo({ top: 0, behavior: "smooth" });
}
async function deleteProperty(id) {
const property = properties.find(p => p.public_id === id);
if (!property) return;
if (!confirm(`「${property.property_name}」を削除しますか?\n公開URLは使えなくなります。`)) return;
const response = await api(`/api/properties/${encodeURIComponent(id)}`, { method: "DELETE" });
const data = await response.json();
if (!response.ok) {
alert(data.message || "削除に失敗しました。");
return;
}
await loadProperties();
}
async function copyText(value) {
try {
await navigator.clipboard.writeText(value);
alert("コピーしました。");
} catch {
window.prompt("コピーしてください", value);
}
}
function openQR(property, url) {
currentQRUrl = url;
document.getElementById("qrPropertyName").textContent = property.property_name;
document.getElementById("qrUrl").textContent = url;
const qrcode = document.getElementById("qrcode");
qrcode.innerHTML = "";
new QRCode(qrcode, {
text: url,
width: 240,
height: 240,
correctLevel: QRCode.CorrectLevel.M
});
document.getElementById("qrModal").style.display = "flex";
}
function closeQR() {
document.getElementById("qrModal").style.display = "none";
}
function downloadQRCode() {
const img = document.querySelector("#qrcode img");
if (!img) {
alert("QRコードを作成できませんでした。");
return;
}
const link = document.createElement("a");
link.href = img.src;
link.download = "guest-wifi-qrcode.png";
link.click();
}
async function loadWifiPage() {
const match = location.pathname.match(/^\/wifi\/([^/]+)$/);
if (!match) return false;
const publicId = decodeURIComponent(match[1]);
try {
const response = await fetch(`/api/wifi/${encodeURIComponent(publicId)}`);
const data = await response.json();
if (!response.ok) throw new Error(data.message || "Wi-Fi情報が見つかりません。");
currentProperty = data.property;
document.getElementById("managementPage").style.display = "none";
document.getElementById("wifiPage").style.display = "block";
document.getElementById("wifiPropertyName").textContent = currentProperty.property_name;
document.getElementById("wifiSSID").textContent = currentProperty.ssid;
document.getElementById("wifiPassword").textContent = currentProperty.password;
changeLanguage("ja");
initAds();
} catch (error) {
document.body.innerHTML = `Wi-Fi情報が見つかりません ${escapeHtml(error.message)}
`;
}
return true;
}
function escapeHtml(value) {
return String(value).replace(/[&<>'"]/g, ch => ({
"&":"&", "<":"<", ">":">", "'":"'", '"':"""
}[ch]));
}
async function copyWifiPassword() {
if (!currentProperty) return;
const message = document.getElementById("copiedMessage");
try {
await navigator.clipboard.writeText(currentProperty.password);
message.style.display = "block";
setTimeout(() => { message.style.display = "none"; }, 2000);
} catch {
alert(currentProperty.password);
}
}
function changeLanguage(language) {
const t = translations[language];
document.getElementById("wifiSubtitle").textContent = t.subtitle;
document.getElementById("ssidLabel").textContent = t.ssid;
document.getElementById("passwordLabel").textContent = t.password;
document.getElementById("copyButton").textContent = t.copy;
document.getElementById("copiedMessage").textContent = t.copied;
document.getElementById("guideButton").textContent = t.guide;
document.getElementById("guideTitle").textContent = t.guideTitle;
document.getElementById("guideText").innerHTML = t.guideText;
document.getElementById("closeButton").textContent = t.close;
document.querySelectorAll(".language-buttons button").forEach(button => button.classList.remove("active"));
document.getElementById(`lang-${language}`).classList.add("active");
}
function openGuide() { document.getElementById("guideModal").style.display = "flex"; }
function closeGuide() { document.getElementById("guideModal").style.display = "none"; }
async function init() {
if (await loadWifiPage()) return;
document.getElementById("managementPage").style.display = "block";
if (adminKey) {
document.getElementById("adminKey").value = adminKey;
try {
const response = await api("/api/properties");
if (response.ok) {
document.getElementById("authCard").style.display = "none";
document.getElementById("adminContent").style.display = "block";
properties = (await response.json()).properties || [];
renderProperties();
} else {
sessionStorage.removeItem("guest_wifi_admin_key");
adminKey = "";
}
} catch {}
}
}
init();