Files
doka2-lobby-list/public/admin.js
T
Aleksandr Bogomiakov c239669932
Docker image / build-and-push (push) Successful in 57s
Added lobby auto creation
2026-08-06 20:33:27 +01:00

788 lines
28 KiB
JavaScript

const els = {
adminStatus: document.querySelector('#adminStatus'),
credentialsSource: document.querySelector('#credentialsSource'),
tokenStatus: document.querySelector('#tokenStatus'),
steamStatus: document.querySelector('#steamStatus'),
gcStatus: document.querySelector('#gcStatus'),
loginPanel: document.querySelector('#loginPanel'),
notificationPanel: document.querySelector('#notificationPanel'),
telegramPanel: document.querySelector('#telegramPanel'),
lobbyPanel: document.querySelector('#lobbyPanel'),
credentialsPanel: document.querySelector('#credentialsPanel'),
guardPanel: document.querySelector('#guardPanel'),
tokenPanel: document.querySelector('#tokenPanel'),
adminPassword: document.querySelector('#adminPassword'),
unlockBtn: document.querySelector('#unlockBtn'),
adminError: document.querySelector('#adminError'),
adminMessage: document.querySelector('#adminMessage'),
steamAccount: document.querySelector('#steamAccount'),
steamPassword: document.querySelector('#steamPassword'),
clearToken: document.querySelector('#clearToken'),
reloadBtn: document.querySelector('#reloadBtn'),
saveBtn: document.querySelector('#saveBtn'),
steamGuardCode: document.querySelector('#steamGuardCode'),
submitGuardBtn: document.querySelector('#submitGuardBtn'),
guardHint: document.querySelector('#guardHint'),
tokenSteamId: document.querySelector('#tokenSteamId'),
tokenAccount: document.querySelector('#tokenAccount'),
tokenUpdated: document.querySelector('#tokenUpdated'),
tokenExpires: document.querySelector('#tokenExpires'),
clearTokenBtn: document.querySelector('#clearTokenBtn'),
enableNotificationsBtn: document.querySelector('#enableNotificationsBtn'),
disableNotificationsBtn: document.querySelector('#disableNotificationsBtn'),
testNotificationBtn: document.querySelector('#testNotificationBtn'),
notificationStatus: document.querySelector('#notificationStatus'),
telegramToken: document.querySelector('#telegramToken'),
telegramRecipientIds: document.querySelector('#telegramRecipientIds'),
saveTelegramBtn: document.querySelector('#saveTelegramBtn'),
testTelegramBtn: document.querySelector('#testTelegramBtn'),
disableTelegramBtn: document.querySelector('#disableTelegramBtn'),
telegramStatus: document.querySelector('#telegramStatus'),
savedSteamAccount: document.querySelector('#savedSteamAccount'),
activateAccountBtn: document.querySelector('#activateAccountBtn'),
lobbyName: document.querySelector('#lobbyName'),
lobbyRegion: document.querySelector('#lobbyRegion'),
customGameId: document.querySelector('#customGameId'),
customGameMode: document.querySelector('#customGameMode'),
customMapName: document.querySelector('#customMapName'),
lobbyPassKey: document.querySelector('#lobbyPassKey'),
customMinPlayers: document.querySelector('#customMinPlayers'),
customMaxPlayers: document.querySelector('#customMaxPlayers'),
notifyAtPlayers: document.querySelector('#notifyAtPlayers'),
leaveAtPlayers: document.querySelector('#leaveAtPlayers'),
adminNames: document.querySelector('#adminNames'),
customGameCrc: document.querySelector('#customGameCrc'),
customGameTimestamp: document.querySelector('#customGameTimestamp'),
allowSpectating: document.querySelector('#allowSpectating'),
saveLobbySettingsBtn: document.querySelector('#saveLobbySettingsBtn'),
createLobbyBtn: document.querySelector('#createLobbyBtn'),
stopLobbyBtn: document.querySelector('#stopLobbyBtn'),
automationPhase: document.querySelector('#automationPhase'),
activeLobbyId: document.querySelector('#activeLobbyId'),
activePlayerCount: document.querySelector('#activePlayerCount'),
publicationStatus: document.querySelector('#publicationStatus'),
arcadePublicationStatus: document.querySelector('#arcadePublicationStatus'),
lobbyProtocolStatus: document.querySelector('#lobbyProtocolStatus'),
dotaClientVersion: document.querySelector('#dotaClientVersion'),
leaveReason: document.querySelector('#leaveReason'),
lobbyMembers: document.querySelector('#lobbyMembers'),
};
let adminPassword = sessionStorage.getItem('dokaAdminPassword') || '';
let credentialsDirty = false;
let lobbySettingsDirty = false;
let telegramDirty = false;
let regionsReady = false;
let lastAdminState = null;
function readPendingLobbyTemplate() {
try {
const saved = sessionStorage.getItem('dokaLobbyTemplate');
if (saved) {
const parsed = JSON.parse(saved);
if (parsed && typeof parsed === 'object') return parsed;
}
} catch {
// Fall through to the query-string compatibility path.
}
const query = new URLSearchParams(window.location.search);
return query.get('template') === 'lobby' ? Object.fromEntries(query.entries()) : null;
}
const pendingLobbyTemplate = readPendingLobbyTemplate();
let lobbyTemplateApplied = false;
const dateTimeFormat = new Intl.DateTimeFormat('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
function formatDate(iso) {
if (!iso) return '-';
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return '-';
return dateTimeFormat.format(date);
}
function setBadge(label, klass) {
els.adminStatus.textContent = label;
els.adminStatus.className = `badge ${klass}`;
}
function showError(message) {
els.adminError.hidden = !message;
els.adminError.textContent = message || '';
}
function showMessage(message) {
els.adminMessage.hidden = !message;
els.adminMessage.textContent = message || '';
}
async function adminFetch(url, options = {}) {
const res = await fetch(url, {
...options,
cache: 'no-store',
headers: {
'Content-Type': 'application/json',
'X-Admin-Password': adminPassword,
...(options.headers || {}),
},
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`);
return data;
}
function unlocked() {
return Boolean(adminPassword);
}
function initRegions(regions) {
if (regionsReady || !regions) return;
for (const [id, name] of Object.entries(regions).sort(([a], [b]) => Number(a) - Number(b))) {
const option = document.createElement('option');
option.value = id;
option.textContent = `${name} (${id})`;
els.lobbyRegion.append(option);
}
regionsReady = true;
}
function setLobbyInputs(settings) {
els.lobbyName.value = settings.lobbyName || '';
els.lobbyRegion.value = String(settings.serverRegion ?? 3);
els.customGameId.value = settings.customGameId || '';
els.customGameMode.value = settings.customGameMode || '';
els.customMapName.value = settings.customMapName || '';
els.lobbyPassKey.value = settings.passKey || '';
els.customMinPlayers.value = String(settings.customMinPlayers ?? 2);
els.customMaxPlayers.value = String(settings.customMaxPlayers ?? 10);
els.notifyAtPlayers.value = String(settings.notifyAtPlayers ?? 8);
els.leaveAtPlayers.value = String(settings.leaveAtPlayers ?? 9);
els.adminNames.value = (settings.adminNames || []).join(', ');
els.customGameCrc.value = settings.customGameCrc || '';
els.customGameTimestamp.value = settings.customGameTimestamp || '';
els.allowSpectating.checked = settings.allowSpectating !== false;
lobbySettingsDirty = false;
}
async function resolveMissingTemplateMetadata() {
if (els.customGameMode.value || !pendingLobbyTemplate?.sourceLobbyId) return;
const controller = new AbortController();
let timedOut = false;
const timeout = setTimeout(() => {
timedOut = true;
controller.abort();
}, 15000);
try {
showMessage('Resolving the internal addon name (local Workshop first, Dota GC fallback; up to 15 seconds)...');
const data = await adminFetch('/api/admin/lobby-template', {
method: 'POST',
signal: controller.signal,
body: JSON.stringify({
lobbyId: pendingLobbyTemplate.sourceLobbyId,
serverRegion: Number(pendingLobbyTemplate.serverRegion || 0),
customGameId: pendingLobbyTemplate.customGameId,
}),
});
els.customGameMode.value = data.templateMetadata?.customGameMode || '';
if (!els.customGameTimestamp.value && data.templateMetadata?.customGameTimestamp) {
els.customGameTimestamp.value = data.templateMetadata.customGameTimestamp;
}
if (!els.customGameMode.value) throw new Error('The source lobby has no internal addon name.');
lobbySettingsDirty = true;
showError('');
showMessage(`Lobby template is complete: Workshop ${els.customGameId.value}, addon ${els.customGameMode.value}, map ${els.customMapName.value}. Review the thresholds, then create it.`);
} catch (err) {
showError(`Could not complete lobby template: ${timedOut ? 'metadata lookup timed out after 15 seconds' : err.message}`);
} finally {
clearTimeout(timeout);
}
}
function applyLobbyTemplate() {
if (!pendingLobbyTemplate || lobbyTemplateApplied) return;
const setFromTemplate = (element, key) => {
const value = pendingLobbyTemplate[key];
if (value !== undefined && value !== null) element.value = String(value);
};
setFromTemplate(els.lobbyName, 'lobbyName');
setFromTemplate(els.lobbyRegion, 'serverRegion');
setFromTemplate(els.customGameId, 'customGameId');
setFromTemplate(els.customGameMode, 'customGameMode');
setFromTemplate(els.customMapName, 'customMapName');
setFromTemplate(els.customMinPlayers, 'customMinPlayers');
setFromTemplate(els.customMaxPlayers, 'customMaxPlayers');
setFromTemplate(els.customGameCrc, 'customGameCrc');
setFromTemplate(els.customGameTimestamp, 'customGameTimestamp');
// The source feed never exposes the lobby password. Clear any stale value.
els.lobbyPassKey.value = '';
const maxPlayers = Number(els.customMaxPlayers.value);
if (Number.isInteger(maxPlayers) && maxPlayers > 0) {
const highestSafeThreshold = Math.max(1, maxPlayers - 1);
const leaveAt = Math.min(Number(els.leaveAtPlayers.value) || highestSafeThreshold, highestSafeThreshold);
els.leaveAtPlayers.value = String(leaveAt);
els.notifyAtPlayers.value = String(Math.min(Number(els.notifyAtPlayers.value) || leaveAt, leaveAt));
}
lobbyTemplateApplied = true;
lobbySettingsDirty = true;
try {
sessionStorage.removeItem('dokaLobbyTemplate');
} catch {
// The query-string fallback is still removed below.
}
window.history.replaceState({}, '', '/admin');
showMessage(`Lobby template loaded: Workshop ${els.customGameId.value}, map ${els.customMapName.value}. Review the notification and leave thresholds, then create it.`);
if (!els.customGameMode.value) {
showError('The GC did not return the internal addon name for this row. Refresh the lobby list and create the template again; an incomplete template cannot be published.');
resolveMissingTemplateMetadata();
}
}
function lobbyPayload() {
return {
lobbyName: els.lobbyName.value,
serverRegion: Number(els.lobbyRegion.value),
customGameId: els.customGameId.value,
customGameMode: els.customGameMode.value,
customMapName: els.customMapName.value,
passKey: els.lobbyPassKey.value,
customMinPlayers: Number(els.customMinPlayers.value),
customMaxPlayers: Number(els.customMaxPlayers.value),
notifyAtPlayers: Number(els.notifyAtPlayers.value),
leaveAtPlayers: Number(els.leaveAtPlayers.value),
adminNames: els.adminNames.value,
customGameCrc: els.customGameCrc.value,
customGameTimestamp: els.customGameTimestamp.value,
allowSpectating: els.allowSpectating.checked,
};
}
function renderLobbyAutomation(automation) {
const status = automation.status || {};
const settings = automation.settings || {};
const busy = ['creating', 'leaving'].includes(status.phase);
const hasLobby = Boolean(status.lobbyId);
if (!lobbySettingsDirty && !busy) setLobbyInputs(settings);
if (unlocked() && regionsReady && automation.settings && !busy) applyLobbyTemplate();
els.automationPhase.textContent = status.phase || 'idle';
els.activeLobbyId.textContent = status.lobbyId || '-';
els.activePlayerCount.textContent = String(status.humanCount ?? 0);
els.publicationStatus.textContent = status.practicePublished === true
? 'listed'
: status.practicePublished === false
? 'not listed'
: status.armed
? 'checking'
: '-';
els.arcadePublicationStatus.textContent = status.arcadePublished === true
? 'listed'
: status.arcadePublished === false
? 'not listed'
: status.armed
? 'checking'
: '-';
const protocol = status.lobbyProtocol;
els.lobbyProtocolStatus.textContent = protocol
? `type=${protocol.lobbyType}, visibility=${protocol.visibility}, auto=${protocol.autoCreated ? 'yes' : 'no'}, bot team=${protocol.botTeam ?? '-'}`
: '-';
els.leaveReason.textContent = status.leaveReason || '-';
els.createLobbyBtn.disabled = busy || hasLobby || Boolean(status.armed);
els.stopLobbyBtn.disabled = busy || (!hasLobby && !status.armed);
els.saveLobbySettingsBtn.disabled = busy;
const members = status.members || [];
if (members.length) {
els.lobbyMembers.textContent = members
.map((member) => member.name ? `${member.name} (${member.steamId})` : member.steamId)
.join(', ');
} else {
els.lobbyMembers.textContent = 'No real players in the active lobby.';
}
if (status.lastError) showError(status.lastError);
}
function pushSupported() {
return 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window;
}
async function currentPushSubscription() {
if (!pushSupported()) return null;
const registration = await navigator.serviceWorker.register('/sw.js');
return registration.pushManager.getSubscription();
}
async function updateNotificationStatus() {
if (!pushSupported()) {
els.notificationStatus.textContent = 'not supported';
els.enableNotificationsBtn.disabled = true;
els.disableNotificationsBtn.disabled = true;
els.testNotificationBtn.disabled = true;
return;
}
try {
const subscription = await currentPushSubscription();
els.notificationStatus.textContent = subscription && Notification.permission === 'granted'
? 'enabled on this browser'
: Notification.permission;
els.disableNotificationsBtn.disabled = !subscription;
els.testNotificationBtn.disabled = !subscription;
} catch (err) {
els.notificationStatus.textContent = err.message;
}
}
function urlBase64ToUint8Array(value) {
const padding = '='.repeat((4 - (value.length % 4)) % 4);
const base64 = (value + padding).replace(/-/g, '+').replace(/_/g, '/');
const raw = atob(base64);
return Uint8Array.from([...raw].map((char) => char.charCodeAt(0)));
}
function renderSavedAccounts(credentials) {
const accounts = Array.isArray(credentials.savedAccounts) ? credentials.savedAccounts : [];
const currentSelection = els.savedSteamAccount.value;
els.savedSteamAccount.replaceChildren();
if (!accounts.length) {
const option = document.createElement('option');
option.value = '';
option.textContent = 'No saved accounts';
els.savedSteamAccount.append(option);
els.savedSteamAccount.disabled = true;
els.activateAccountBtn.disabled = true;
return;
}
for (const account of accounts) {
const option = document.createElement('option');
option.value = account.accountName;
option.textContent = account.accountName === credentials.activeAccountName
? `${account.accountName} (active)`
: account.accountName;
els.savedSteamAccount.append(option);
}
els.savedSteamAccount.disabled = false;
els.activateAccountBtn.disabled = false;
const preferred = accounts.some((account) => account.accountName === currentSelection)
? currentSelection
: credentials.activeAccountName;
els.savedSteamAccount.value = preferred || accounts[0].accountName;
}
function renderTelegram(telegram = {}) {
const bot = telegram.botUsername ? `@${telegram.botUsername}` : 'bot';
if (telegram.configured) {
els.telegramStatus.textContent = `${bot}, ${telegram.recipientCount || 0} recipient(s)${telegram.lastError ? `; error: ${telegram.lastError}` : ''}`;
} else {
els.telegramStatus.textContent = 'not configured';
}
els.testTelegramBtn.disabled = !telegram.configured;
els.disableTelegramBtn.disabled = !telegram.configured;
const editing = document.activeElement === els.telegramToken
|| document.activeElement === els.telegramRecipientIds
|| telegramDirty;
if (!editing) {
els.telegramToken.value = '';
els.telegramToken.placeholder = telegram.configured
? 'configured; leave blank to keep saved token'
: 'paste token from @BotFather';
els.telegramRecipientIds.value = (telegram.recipientIds || []).join(', ');
telegramDirty = false;
}
}
function render(data, options = {}) {
lastAdminState = data;
const preserveEditedInputs = options.preserveEditedInputs !== false;
const connection = data.connection || {};
const credentials = data.credentials || {};
const token = data.refreshToken || {};
const steamGuard = connection.steamGuard || {};
els.loginPanel.hidden = unlocked();
els.notificationPanel.hidden = !unlocked();
els.telegramPanel.hidden = !unlocked();
els.lobbyPanel.hidden = !unlocked();
els.credentialsPanel.hidden = !unlocked();
els.tokenPanel.hidden = !unlocked();
els.guardPanel.hidden = !unlocked() || !steamGuard.required;
setBadge(unlocked() ? 'unlocked' : 'locked', unlocked() ? 'badge-ok' : 'badge-muted');
els.credentialsSource.textContent = credentials.source || '-';
els.tokenStatus.textContent = token.exists ? 'saved' : 'missing';
els.steamStatus.textContent = connection.steam || '-';
els.gcStatus.textContent = connection.gc || '-';
els.dotaClientVersion.textContent = connection.dotaClientVersion
? `${connection.dotaClientVersion}${connection.dotaClientVersionSource ? ` (${connection.dotaClientVersionSource})` : ''}`
: 'not detected; set DOTA_CLIENT_VERSION';
initRegions(data.regions);
renderLobbyAutomation(data.lobbyAutomation || {});
renderTelegram(data.lobbyAutomation?.telegram || {});
renderSavedAccounts(credentials);
const editingCredentials = document.activeElement === els.steamAccount
|| document.activeElement === els.steamPassword
|| credentialsDirty;
if (!preserveEditedInputs || !editingCredentials) {
els.steamAccount.value = credentials.accountName || '';
els.steamPassword.value = '';
els.steamPassword.placeholder = credentials.passwordSaved
? 'saved; leave blank to keep the saved password'
: 'password required for a new account';
credentialsDirty = false;
}
els.tokenSteamId.textContent = token.steamID || '-';
els.tokenAccount.textContent = token.accountName || '-';
els.tokenUpdated.textContent = formatDate(token.updatedAt);
els.tokenExpires.textContent = formatDate(token.expiresAt);
if (steamGuard.required) {
const where = steamGuard.domain ? `Email domain: ${steamGuard.domain}` : 'Mobile authenticator code required';
els.guardHint.textContent = steamGuard.lastCodeWrong ? `Last code was rejected. ${where}.` : where;
} else {
els.guardHint.textContent = '';
}
}
async function loadAuth(options = {}) {
if (!unlocked()) {
render({});
return;
}
try {
showError('');
const data = await adminFetch('/api/admin/auth');
render(data, options);
} catch (err) {
showError(err.message);
if (err.message.includes('Invalid admin password')) {
adminPassword = '';
sessionStorage.removeItem('dokaAdminPassword');
}
render({});
}
}
async function saveCredentials() {
try {
showError('');
showMessage('');
const data = await adminFetch('/api/admin/auth', {
method: 'PUT',
body: JSON.stringify({
accountName: els.steamAccount.value,
password: els.steamPassword.value,
clearRefreshToken: els.clearToken.checked,
}),
});
credentialsDirty = false;
render(data);
showMessage('Credentials saved. Reconnect started.');
} catch (err) {
showError(err.message);
}
}
async function submitSteamGuard() {
try {
showError('');
showMessage('');
const data = await adminFetch('/api/admin/steam-guard', {
method: 'POST',
body: JSON.stringify({ code: els.steamGuardCode.value }),
});
els.steamGuardCode.value = '';
render(data);
showMessage('Steam Guard code submitted.');
} catch (err) {
showError(err.message);
}
}
async function clearRefreshToken() {
try {
showError('');
showMessage('');
const data = await adminFetch('/api/admin/clear-token', { method: 'POST', body: '{}' });
render(data);
showMessage('Refresh token cleared.');
} catch (err) {
showError(err.message);
}
}
async function saveLobbySettings() {
try {
showError('');
showMessage('');
const data = await adminFetch('/api/admin/lobby-settings', {
method: 'PUT',
body: JSON.stringify(lobbyPayload()),
});
lobbySettingsDirty = false;
render(data);
showMessage('Lobby settings saved.');
} catch (err) {
showError(err.message);
}
}
async function createLobby() {
try {
showError('');
showMessage('Creating lobby and waiting for the Dota GC snapshot...');
els.createLobbyBtn.disabled = true;
const data = await adminFetch('/api/admin/lobby', {
method: 'POST',
body: JSON.stringify(lobbyPayload()),
});
lobbySettingsDirty = false;
render(data);
showMessage('Lobby created. Player monitoring and safe auto-leave are active.');
} catch (err) {
showError(err.message);
showMessage('');
} finally {
if (!lastAdminState?.lobbyAutomation?.status?.armed) els.createLobbyBtn.disabled = false;
}
}
async function stopLobby() {
try {
showError('');
showMessage('Leaving lobby...');
els.stopLobbyBtn.disabled = true;
const data = await adminFetch('/api/admin/lobby', {
method: 'DELETE',
body: '{}',
});
render(data);
showMessage('The bot left the lobby.');
} catch (err) {
showError(err.message);
showMessage('');
}
}
async function enableNotifications() {
try {
showError('');
if (!pushSupported()) throw new Error('Push notifications are not supported by this browser.');
const permission = await Notification.requestPermission();
if (permission !== 'granted') throw new Error(`Notification permission is ${permission}.`);
const registration = await navigator.serviceWorker.register('/sw.js');
let subscription = await registration.pushManager.getSubscription();
if (!subscription) {
const publicKey = lastAdminState?.lobbyAutomation?.push?.publicKey;
if (!publicKey) throw new Error('Web Push public key is unavailable.');
subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey),
});
}
const data = await adminFetch('/api/admin/push-subscription', {
method: 'PUT',
body: JSON.stringify({ subscription: subscription.toJSON() }),
});
render(data);
await updateNotificationStatus();
showMessage('Browser push notifications enabled.');
} catch (err) {
showError(err.message);
}
}
async function disableNotifications() {
try {
showError('');
const subscription = await currentPushSubscription();
if (subscription) {
await adminFetch('/api/admin/push-subscription', {
method: 'DELETE',
body: JSON.stringify({ endpoint: subscription.endpoint }),
});
await subscription.unsubscribe();
}
await updateNotificationStatus();
showMessage('Browser push notifications disabled on this browser.');
} catch (err) {
showError(err.message);
}
}
async function testNotification() {
try {
showError('');
const data = await adminFetch('/api/admin/test-notification', { method: 'POST', body: '{}' });
const result = data.notificationResult || {};
showMessage(`Test notification sent: ${result.delivered || 0} delivered, ${result.failed || 0} failed.`);
} catch (err) {
showError(err.message);
}
}
async function saveTelegram() {
try {
showError('');
showMessage('Checking the Telegram bot and saving recipients...');
const data = await adminFetch('/api/admin/telegram', {
method: 'PUT',
body: JSON.stringify({
token: els.telegramToken.value,
recipientIds: els.telegramRecipientIds.value,
}),
});
telegramDirty = false;
render(data);
showMessage('Telegram notifications saved. Use Send test to verify every recipient.');
} catch (err) {
showMessage('');
showError(err.message);
}
}
async function disableTelegram() {
try {
showError('');
const data = await adminFetch('/api/admin/telegram', { method: 'DELETE', body: '{}' });
telegramDirty = false;
render(data);
showMessage('Telegram notifications disabled and the saved bot token removed.');
} catch (err) {
showError(err.message);
}
}
async function testTelegram() {
try {
showError('');
const data = await adminFetch('/api/admin/test-telegram', { method: 'POST', body: '{}' });
const result = data.telegramResult || {};
render(data);
const errors = result.errors?.length ? ` ${result.errors.join('; ')}` : '';
showMessage(`Telegram test: ${result.delivered || 0} delivered, ${result.failed || 0} failed.${errors}`);
} catch (err) {
showError(err.message);
}
}
async function activateSavedAccount() {
try {
showError('');
showMessage('Switching Steam account and reconnecting Dota GC...');
const data = await adminFetch('/api/admin/steam-account/activate', {
method: 'POST',
body: JSON.stringify({ accountName: els.savedSteamAccount.value }),
});
credentialsDirty = false;
render(data);
showMessage('Steam account activated. Reconnection continues in the background.');
} catch (err) {
showMessage('');
showError(err.message);
}
}
els.unlockBtn.addEventListener('click', () => {
adminPassword = els.adminPassword.value;
sessionStorage.setItem('dokaAdminPassword', adminPassword);
loadAuth({ preserveEditedInputs: false });
});
els.adminPassword.addEventListener('keydown', (event) => {
if (event.key === 'Enter') els.unlockBtn.click();
});
els.reloadBtn.addEventListener('click', () => {
credentialsDirty = false;
loadAuth({ preserveEditedInputs: false });
});
els.saveBtn.addEventListener('click', saveCredentials);
els.submitGuardBtn.addEventListener('click', submitSteamGuard);
els.clearTokenBtn.addEventListener('click', clearRefreshToken);
els.saveLobbySettingsBtn.addEventListener('click', saveLobbySettings);
els.createLobbyBtn.addEventListener('click', createLobby);
els.stopLobbyBtn.addEventListener('click', stopLobby);
els.enableNotificationsBtn.addEventListener('click', enableNotifications);
els.disableNotificationsBtn.addEventListener('click', disableNotifications);
els.testNotificationBtn.addEventListener('click', testNotification);
els.saveTelegramBtn.addEventListener('click', saveTelegram);
els.testTelegramBtn.addEventListener('click', testTelegram);
els.disableTelegramBtn.addEventListener('click', disableTelegram);
els.activateAccountBtn.addEventListener('click', activateSavedAccount);
els.steamAccount.addEventListener('input', () => {
credentialsDirty = true;
});
els.steamPassword.addEventListener('input', () => {
credentialsDirty = true;
});
els.savedSteamAccount.addEventListener('change', () => {
if (!els.savedSteamAccount.value) return;
els.steamAccount.value = els.savedSteamAccount.value;
els.steamPassword.value = '';
credentialsDirty = true;
});
[els.telegramToken, els.telegramRecipientIds].forEach((element) => {
element.addEventListener('input', () => {
telegramDirty = true;
});
});
[
els.lobbyName,
els.lobbyRegion,
els.customGameId,
els.customGameMode,
els.customMapName,
els.lobbyPassKey,
els.customMinPlayers,
els.customMaxPlayers,
els.notifyAtPlayers,
els.leaveAtPlayers,
els.adminNames,
els.customGameCrc,
els.customGameTimestamp,
els.allowSpectating,
].forEach((element) => {
element.addEventListener('input', () => {
lobbySettingsDirty = true;
});
element.addEventListener('change', () => {
lobbySettingsDirty = true;
});
});
els.steamGuardCode.addEventListener('keydown', (event) => {
if (event.key === 'Enter') els.submitGuardBtn.click();
});
if (adminPassword) {
els.adminPassword.value = adminPassword;
}
loadAuth({ preserveEditedInputs: false });
updateNotificationStatus();
setInterval(loadAuth, 5000);