PHP Classes

File: templates/admin/bbs_settings.twig

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   templates/admin/bbs_settings.twig   Download  
File: templates/admin/bbs_settings.twig
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 63,596 bytes
 

Contents

Class file image Download
{% extends "base.twig" %} {% block title %}{{ t('ui.admin.bbs_settings.page_title', {}, locale, ['common']) }}{% endblock %} {% block content %} <div class="container-fluid"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <h1 class="h2">{{ t('ui.admin.bbs_settings.heading', {}, locale, ['common']) }}</h1> </div> <div class="row g-4 settings-loading" id="bbsSettingsCards"> <div class="col-lg-6"> <div class="card shadow h-100"> <div class="card-header"> <h6 class="mb-0">{{ t('ui.admin.bbs_settings.system.title', {}, locale, ['common']) }}</h6> </div> <div class="card-body"> <div id="bbsSystemAlert"></div> <form id="bbsSystemForm"> <div class="mb-3"> <label class="form-label" for="bbsSystemName">{{ t('ui.admin.bbs_settings.system.system_name', {}, locale, ['common']) }}</label> <input type="text" class="form-control" id="bbsSystemName"> </div> <div class="mb-3"> <label class="form-label" for="bbsSystemSysop">{{ t('ui.admin.bbs_settings.system.sysop_name', {}, locale, ['common']) }}</label> <select class="form-select" id="bbsSystemSysop"></select> <small class="text-muted">{{ t('ui.admin.bbs_settings.system.select_admin_user', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsSystemLocation">{{ t('ui.admin.bbs_settings.system.location', {}, locale, ['common']) }}</label> <input type="text" class="form-control" id="bbsSystemLocation"> </div> <div class="mb-3"> <label class="form-label" for="bbsSystemTimezone">{{ t('ui.admin.bbs_settings.system.timezone', {}, locale, ['common']) }}</label> <select class="form-select" id="bbsSystemTimezone"> {% for tz in timezone_list %} <option value="{{ tz }}">{{ tz }}</option> {% endfor %} </select> </div> <div class="mb-3"> <label class="form-label" for="bbsSystemAddress">{{ t('ui.admin.bbs_settings.system.system_address', {}, locale, ['common']) }}</label> <input type="text" class="form-control" id="bbsSystemAddress"> </div> <div class="mb-3"> <label class="form-label" for="bbsSystemOrigin">{{ t('ui.admin.bbs_settings.system.origin_line', {}, locale, ['common']) }}</label> <input type="text" class="form-control" id="bbsSystemOrigin"> </div> <button type="button" class="btn btn-primary" onclick="saveBbsSystem()">{{ t('ui.admin.bbs_settings.system.save', {}, locale, ['common']) }}</button> </form> <hr> <h6 class="mb-3">{{ t('ui.admin.bbs_settings.terminal_server.title', {}, locale, ['common']) }}</h6> <div id="bbsTerminalIdleAlert"></div> <form id="bbsTerminalIdleForm"> <div class="mb-3"> <label class="form-label" for="bbsTerminalDefaultShell">{{ t('ui.admin.bbs_settings.terminal_server.default_shell', {}, locale, ['common']) }}</label> <select class="form-select" id="bbsTerminalDefaultShell"> {% for shell in allowed_terminal_shells %} <option value="{{ shell.id }}"> {% if shell.admin_label_key %} {{ t(shell.admin_label_key, {}, locale, ['common']) }} {% else %} {{ shell.admin_label }} {% endif %} </option> {% endfor %} </select> <small class="text-muted">{{ t('ui.admin.bbs_settings.terminal_server.default_shell_help', {}, locale, ['common']) }}</small> </div> <div class="form-check mb-3"> <input class="form-check-input" type="checkbox" id="bbsTerminalForceShell"> <label class="form-check-label" for="bbsTerminalForceShell">{{ t('ui.admin.bbs_settings.terminal_server.force_shell', {}, locale, ['common']) }}</label> <div><small class="text-muted">{{ t('ui.admin.bbs_settings.terminal_server.force_shell_help', {}, locale, ['common']) }}</small></div> </div> <div class="mb-3"> <label class="form-label" for="bbsIdleWarnMinutes">{{ t('ui.admin.bbs_settings.terminal_idle.warn_minutes', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsIdleWarnMinutes" min="1" step="1" value="5"> <small class="text-muted">{{ t('ui.admin.bbs_settings.terminal_idle.warn_minutes_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsIdleDisconnectMinutes">{{ t('ui.admin.bbs_settings.terminal_idle.disconnect_minutes', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsIdleDisconnectMinutes" min="2" step="1" value="7"> <small class="text-muted">{{ t('ui.admin.bbs_settings.terminal_idle.disconnect_minutes_help', {}, locale, ['common']) }}</small> </div> <button type="button" class="btn btn-primary" onclick="saveTerminalIdle()">{{ t('ui.admin.bbs_settings.terminal_server.save', {}, locale, ['common']) }}</button> </form> <hr> <h6 class="mb-3">{{ t('ui.admin.bbs_settings.packet_bbs.title', {}, locale, ['common']) }}</h6> <div id="bbsPacketBbsAlert"></div> <form id="bbsPacketBbsForm"> <div class="mb-3"> <label class="form-label" for="bbsPacketBbsSessionTimeout">{{ t('ui.admin.bbs_settings.packet_bbs.session_timeout_minutes', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsPacketBbsSessionTimeout" min="1" step="1" value="15"> <small class="text-muted">{{ t('ui.admin.bbs_settings.packet_bbs.session_timeout_help', {}, locale, ['common']) }}</small> </div> <div class="form-check mb-3"> <input class="form-check-input" type="checkbox" id="bbsPacketBbsAllowGuestWho"> <label class="form-check-label" for="bbsPacketBbsAllowGuestWho">{{ t('ui.admin.bbs_settings.packet_bbs.allow_guest_who', {}, locale, ['common']) }}</label> <div><small class="text-muted">{{ t('ui.admin.bbs_settings.packet_bbs.allow_guest_who_help', {}, locale, ['common']) }}</small></div> </div> <button type="button" class="btn btn-primary" onclick="saveBbsPacketBbs()">{{ t('ui.admin.bbs_settings.packet_bbs.save', {}, locale, ['common']) }}</button> </form> </div> </div> </div> <div class="col-lg-6"> <div class="card shadow h-100"> <div class="card-header"> <h6 class="mb-0">{{ t('ui.admin.bbs_settings.features.title', {}, locale, ['common']) }}</h6> </div> <div class="card-body"> <div id="bbsSettingsAlert"></div> <form id="bbsSettingsForm"> <div class="form-check mb-2 d-flex align-items-center gap-2"> <input class="form-check-input" type="checkbox" id="bbsWebdoors"> <label class="form-check-label" for="bbsWebdoors">{{ t('ui.admin.bbs_settings.features.enable_webdoors', {}, locale, ['common']) }}</label> {% if not webdoors_active %} <small class="text-muted"> ({{ t('ui.admin.bbs_settings.features.inactive', {}, locale, ['common']) }}) <a href="/admin/webdoors" class="ms-1">{{ t('ui.admin.bbs_settings.features.activate', {}, locale, ['common']) }}</a> </small> {% endif %} </div> <div class="form-check mb-2"> <input class="form-check-input" type="checkbox" id="bbsShoutbox"> <label class="form-check-label" for="bbsShoutbox">{{ t('ui.admin.bbs_settings.features.enable_shoutbox', {}, locale, ['common']) }}</label> </div> <div class="form-check mb-2"> <input class="form-check-input" type="checkbox" id="bbsAdvertising"> <label class="form-check-label" for="bbsAdvertising">{{ t('ui.admin.bbs_settings.features.enable_advertising', {}, locale, ['common']) }}</label> </div> <div class="form-check mb-2"> <input class="form-check-input" type="checkbox" id="bbsVoting"> <label class="form-check-label" for="bbsVoting">{{ t('ui.admin.bbs_settings.features.enable_voting_booth', {}, locale, ['common']) }}</label> </div> <div class="form-check mb-2"> <input class="form-check-input" type="checkbox" id="bbsChat"> <label class="form-check-label" for="bbsChat">{{ t('ui.admin.bbs_settings.features.enable_chat', {}, locale, ['common']) }}</label> </div> <div class="form-check mb-2"> <input class="form-check-input" type="checkbox" id="bbsFileAreas"> <label class="form-check-label" for="bbsFileAreas">{{ t('ui.admin.bbs_settings.features.enable_file_areas', {}, locale, ['common']) }}</label> </div> <div class="form-check mb-2"> <input class="form-check-input" type="checkbox" id="bbsGuestDoorsPage"> <label class="form-check-label" for="bbsGuestDoorsPage">{{ t('ui.admin.bbs_settings.features.enable_guest_doors_page', {}, locale, ['common']) }}</label> <div><small class="text-muted">{{ t('ui.admin.bbs_settings.features.guest_doors_page_help', {}, locale, ['common']) }}</small></div> </div> <div class="form-check mb-3"> <input class="form-check-input" type="checkbox" id="bbsBbsDirectory"> <label class="form-check-label" for="bbsBbsDirectory">{{ t('ui.admin.bbs_settings.features.enable_bbs_directory', {}, locale, ['common']) }}</label> <div><small class="text-muted">{{ t('ui.admin.bbs_settings.features.bbs_directory_help', {}, locale, ['common']) }}</small></div> </div> <div class="form-check mb-3"> <input class="form-check-input" type="checkbox" id="bbsQwk"> <label class="form-check-label" for="bbsQwk">{{ t('ui.admin.bbs_settings.features.enable_qwk', {}, locale, ['common']) }}</label> <div><small class="text-muted">{{ t('ui.admin.bbs_settings.features.qwk_help', {}, locale, ['common']) }}</small></div> </div> <div class="mb-3"> <label class="form-label" for="bbsQwkBbsId">{{ t('ui.admin.bbs_settings.features.qwk_bbs_id', {}, locale, ['common']) }}</label> <input type="text" class="form-control font-monospace" id="bbsQwkBbsId" maxlength="8" style="max-width:12rem;" placeholder="e.g. MYBBS"> <small class="text-muted">{{ t('ui.admin.bbs_settings.features.qwk_bbs_id_help', {}, locale, ['common']) }}</small> </div> {% if license_valid %} <div class="form-check mb-3"> <input class="form-check-input" type="checkbox" id="bbsPublicFilesIndex"> <label class="form-check-label" for="bbsPublicFilesIndex">{{ t('ui.admin.bbs_settings.features.enable_public_files_index', {}, locale, ['common']) }}</label> <div><small class="text-muted">{{ t('ui.admin.bbs_settings.features.public_files_index_help', {}, locale, ['common']) }}</small></div> </div> {% else %} <div class="alert alert-secondary py-2 small mb-3"> <i class="fas fa-lock me-1"></i> {{ t('ui.admin.bbs_settings.features.public_files_index_requires_license', {}, locale, ['common']) }} </div> {% endif %} <div class="mb-3"> <label class="form-label" for="bbsDefaultEchoInterface">{{ t('ui.admin.bbs_settings.features.default_echo_interface', {}, locale, ['common']) }}</label> <select class="form-select" id="bbsDefaultEchoInterface"> <option value="echolist">{{ t('ui.admin.bbs_settings.features.echo_list_forum', {}, locale, ['common']) }}</option> <option value="reader">{{ t('ui.admin.bbs_settings.features.reader_message_list', {}, locale, ['common']) }}</option> </select> <small class="text-muted">{{ t('ui.admin.bbs_settings.features.default_echo_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsBulletinDisplayMode">{{ t('ui.admin.bbs_settings.features.bulletin_display_mode', {}, locale, ['common']) }}</label> <select class="form-select" id="bbsBulletinDisplayMode"> <option value="once">{{ t('ui.admin.bbs_settings.features.bulletin_display_once', {}, locale, ['common']) }}</option> <option value="always">{{ t('ui.admin.bbs_settings.features.bulletin_display_always', {}, locale, ['common']) }}</option> </select> <small class="text-muted">{{ t('ui.admin.bbs_settings.features.bulletin_display_help', {}, locale, ['common']) }}</small> </div> <hr> <div class="mb-3"> <label class="form-label" for="bbsMaxCrossPost">{{ t('ui.admin.bbs_settings.features.max_cross_post_areas', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsMaxCrossPost" min="2" max="20" step="1" value="5"> <small class="text-muted">{{ t('ui.admin.bbs_settings.features.max_cross_post_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsEchomailModerationThreshold">{{ t('ui.admin.bbs_settings.features.echomail_moderation_threshold', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsEchomailModerationThreshold" min="0" step="1" value="0"> <small class="text-muted">{{ t('ui.admin.bbs_settings.features.echomail_moderation_threshold_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsDashboardAdRotateInterval">{{ t('ui.admin.bbs_settings.features.dashboard_ad_rotate_interval', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsDashboardAdRotateInterval" min="5" max="300" step="1" value="20"> <small class="text-muted">{{ t('ui.admin.bbs_settings.features.dashboard_ad_rotate_interval_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsOutgoingCharset">{{ t('ui.admin.bbs_settings.features.outgoing_charset', {}, locale, ['common']) }}</label> <select class="form-select" id="bbsOutgoingCharset"> {% for cs in supported_charsets %} <option value="{{ cs.value }}">{{ cs.label }}</option> {% endfor %} </select> <small class="text-muted">{{ t('ui.admin.bbs_settings.features.outgoing_charset_help', {}, locale, ['common']) }}</small> </div> <button type="button" class="btn btn-primary" onclick="saveBbsSettings()">{{ t('ui.admin.bbs_settings.features.save', {}, locale, ['common']) }}</button> </form> </div> </div> </div> <div class="col-12"> <div class="card shadow"> <div class="card-header"> <h6 class="mb-0">{{ t('ui.admin.bbs_settings.credits.title', {}, locale, ['common']) }}</h6> </div> <div class="card-body"> <div id="bbsCreditsAlert"></div> <div class="row g-4"> <div class="col-md-6 col-xl-3"> <div class="mb-3 form-check"> <input class="form-check-input" type="checkbox" id="bbsCreditsEnabled"> <label class="form-check-label" for="bbsCreditsEnabled">{{ t('ui.admin.bbs_settings.credits.enabled', {}, locale, ['common']) }}</label> </div> </div> <div class="col-md-6 col-xl-3"> <div class="mb-3"> <label class="form-label" for="bbsCreditsSymbol">{{ t('ui.admin.bbs_settings.credits.currency_symbol', {}, locale, ['common']) }}</label> <input type="text" class="form-control" id="bbsCreditsSymbol" maxlength="5"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.currency_symbol_help', {}, locale, ['common']) }}</small> </div> </div> </div> <div class="row g-4"> <div class="col-lg-6"> <h6 class="mb-3">{{ t('ui.admin.bbs_settings.credits.costs_and_fees', {}, locale, ['common']) }}</h6> <div class="mb-3"> <label class="form-label" for="bbsCreditsNetmailCost">{{ t('ui.admin.bbs_settings.credits.netmail_cost', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsNetmailCost" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.netmail_cost_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsCrashmailCost">{{ t('ui.admin.bbs_settings.credits.crashmail_cost', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsCrashmailCost" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.crashmail_cost_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsPollCreationCost">{{ t('ui.admin.bbs_settings.credits.poll_creation_cost', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsPollCreationCost" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.poll_creation_cost_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsFileUploadCost">{{ t('ui.admin.bbs_settings.credits.file_upload_cost', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsFileUploadCost" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.file_upload_cost_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsFileDownloadCost">{{ t('ui.admin.bbs_settings.credits.file_download_cost', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsFileDownloadCost" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.file_download_cost_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsAiPerMilliUsd">{{ t('ui.admin.bbs_settings.credits.ai_credits_per_milli_usd', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsAiPerMilliUsd" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.ai_credits_per_milli_usd_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsTransferFee">{{ t('ui.admin.bbs_settings.credits.transfer_fee_percentage', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsTransferFee" min="0" max="1" step="0.01"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.transfer_fee_help', {}, locale, ['common']) }}</small> </div> </div> <div class="col-lg-6"> <h6 class="mb-3">{{ t('ui.admin.bbs_settings.credits.rewards_and_bonuses', {}, locale, ['common']) }}</h6> <div class="mb-3"> <label class="form-label" for="bbsCreditsDailyAmount">{{ t('ui.admin.bbs_settings.credits.daily_login_bonus_amount', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsDailyAmount" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.daily_login_bonus_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsDailyDelay">{{ t('ui.admin.bbs_settings.credits.daily_login_delay_minutes', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsDailyDelay" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.daily_login_delay_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsApprovalBonus">{{ t('ui.admin.bbs_settings.credits.new_user_approval_bonus', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsApprovalBonus" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.new_user_approval_bonus_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsNew14Days">{{ t('ui.admin.bbs_settings.credits.new_user_14_day_bonus', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsNew14Days" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.new_user_14_day_bonus_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsEchomailReward">{{ t('ui.admin.bbs_settings.credits.echomail_reward', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsEchomailReward" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.echomail_reward_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsFileUploadReward">{{ t('ui.admin.bbs_settings.credits.file_upload_reward', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsFileUploadReward" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.file_upload_reward_help', {}, locale, ['common']) }}</small> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsFileDownloadReward">{{ t('ui.admin.bbs_settings.credits.file_download_reward', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsFileDownloadReward" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.file_download_reward_help', {}, locale, ['common']) }}</small> </div> <hr> <h6 class="mb-3">{{ t('ui.admin.bbs_settings.credits.referral_system', {}, locale, ['common']) }}</h6> <div class="mb-3 form-check"> <input class="form-check-input" type="checkbox" id="bbsCreditsReferralEnabled"> <label class="form-check-label" for="bbsCreditsReferralEnabled">{{ t('ui.admin.bbs_settings.credits.enable_referral_system', {}, locale, ['common']) }}</label> </div> <div class="mb-3"> <label class="form-label" for="bbsCreditsReferralBonus">{{ t('ui.admin.bbs_settings.credits.referral_bonus', {}, locale, ['common']) }}</label> <input type="number" class="form-control" id="bbsCreditsReferralBonus" min="0" step="1"> <small class="text-muted">{{ t('ui.admin.bbs_settings.credits.referral_bonus_help', {}, locale, ['common']) }}</small> </div> </div> </div> <button type="button" class="btn btn-primary" onclick="saveBbsCredits()">{{ t('ui.admin.bbs_settings.credits.save', {}, locale, ['common']) }}</button> </div> </div> </div> <div class="col-12"> <div class="card shadow"> <div class="card-header"> <h6 class="mb-0">{{ t('ui.admin.bbs_settings.taglines.title', {}, locale, ['common']) }}</h6> </div> <div class="card-body"> <div id="bbsTaglinesAlert"></div> <div class="mb-3"> <label class="form-label" for="bbsTaglinesText">{{ t('ui.admin.bbs_settings.taglines.one_per_line', {}, locale, ['common']) }}</label> <textarea class="form-control" id="bbsTaglinesText" rows="8" placeholder="{{ t('ui.admin.bbs_settings.taglines.placeholder', {}, locale, ['common']) }}"></textarea> <small class="text-muted">{{ t('ui.admin.bbs_settings.taglines.help', {}, locale, ['common']) }}</small> </div> <button type="button" class="btn btn-primary" onclick="saveBbsTaglines()">{{ t('ui.admin.bbs_settings.taglines.save', {}, locale, ['common']) }}</button> </div> </div> </div> </div> </div> {% endblock %} {% block scripts %} <script> function uiT(key, fallback, params = {}) { if (window.t) { return window.t(key, params, fallback); } return fallback; } document.addEventListener('DOMContentLoaded', function() { setSettingsLoading('#bbsSettingsCards', true); loadI18nNamespaces(['common', 'errors']).then(function() { loadBbsSettings(); loadBbsSystem(); loadAdminUsers('bbsSystemSysop'); loadBbsTaglines(); }); }); function loadBbsSettings() { fetch('/admin/api/bbs-settings') .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.bbs_settings.load_failed', 'Failed to load settings'))); } const features = data.config?.features || {}; const credits = data.config?.credits || {}; document.getElementById('bbsWebdoors').checked = !!features.webdoors; document.getElementById('bbsShoutbox').checked = !!features.shoutbox; document.getElementById('bbsAdvertising').checked = !!features.advertising; document.getElementById('bbsVoting').checked = !!features.voting_booth; document.getElementById('bbsChat').checked = !!features.chat; document.getElementById('bbsFileAreas').checked = !!features.file_areas; document.getElementById('bbsGuestDoorsPage').checked = !!features.guest_doors_page; document.getElementById('bbsBbsDirectory').checked = features.bbs_directory !== false; document.getElementById('bbsQwk').checked = !!features.qwk; document.getElementById('bbsQwkBbsId').value = data.config?.qwk?.bbs_id || ''; const bbsPublicFilesIndexEl = document.getElementById('bbsPublicFilesIndex'); if (bbsPublicFilesIndexEl) bbsPublicFilesIndexEl.checked = !!features.public_files_index; document.getElementById('bbsDefaultEchoInterface').value = data.config?.default_echo_interface || 'echolist'; document.getElementById('bbsBulletinDisplayMode').value = data.config?.bulletin_display_mode === 'always' ? 'always' : 'once'; const maxCrossPost = parseInt(data.config?.max_cross_post_areas, 10); document.getElementById('bbsMaxCrossPost').value = Number.isNaN(maxCrossPost) ? 5 : maxCrossPost; const echomailModerationThreshold = parseInt(data.config?.echomail_moderation_threshold, 10); document.getElementById('bbsEchomailModerationThreshold').value = Number.isNaN(echomailModerationThreshold) ? 0 : echomailModerationThreshold; const dashboardAdRotateInterval = parseInt(data.config?.dashboard_ad_rotate_interval_seconds, 10); document.getElementById('bbsDashboardAdRotateInterval').value = Number.isNaN(dashboardAdRotateInterval) ? 20 : dashboardAdRotateInterval; document.getElementById('bbsOutgoingCharset').value = data.config?.outgoing_charset || 'CP437'; const terminalIdle = data.config?.terminal_idle || {}; const idleWarnMinutes = parseInt(terminalIdle.warn_minutes, 10); const idleDisconnectMinutes = parseInt(terminalIdle.disconnect_minutes, 10); document.getElementById('bbsIdleWarnMinutes').value = Number.isNaN(idleWarnMinutes) ? 5 : idleWarnMinutes; document.getElementById('bbsIdleDisconnectMinutes').value = Number.isNaN(idleDisconnectMinutes) ? 7 : idleDisconnectMinutes; const terminalServer = data.config?.terminal_server || {}; const terminalDefaultShellEl = document.getElementById('bbsTerminalDefaultShell'); const desiredDefaultShell = terminalServer.default_shell || 'tui'; const hasDesiredDefaultShell = Array.from(terminalDefaultShellEl.options).some(option => option.value === desiredDefaultShell); terminalDefaultShellEl.value = hasDesiredDefaultShell ? desiredDefaultShell : (terminalDefaultShellEl.options[0]?.value || 'tui'); document.getElementById('bbsTerminalForceShell').checked = !!terminalServer.force_shell; const packetBbs = data.config?.packet_bbs || {}; const sessionTimeout = parseInt(packetBbs.session_timeout_minutes, 10); document.getElementById('bbsPacketBbsSessionTimeout').value = Number.isNaN(sessionTimeout) ? 15 : sessionTimeout; document.getElementById('bbsPacketBbsAllowGuestWho').checked = packetBbs.allow_guest_who !== false; document.getElementById('bbsCreditsEnabled').checked = credits.enabled !== false; document.getElementById('bbsCreditsSymbol').value = (credits.symbol ?? '$'); const dailyAmountValue = parseInt(credits.daily_amount, 10); const dailyDelayValue = parseInt(credits.daily_login_delay_minutes, 10); const approvalBonusValue = parseInt(credits.approval_bonus, 10); const netmailCostValue = parseInt(credits.netmail_cost, 10); const echomailRewardValue = parseInt(credits.echomail_reward, 10); const crashmailCostValue = parseInt(credits.crashmail_cost, 10); const pollCreationCostValue = parseInt(credits.poll_creation_cost, 10); const fileUploadCostValue = parseInt(credits.file_upload_cost, 10); const fileUploadRewardValue = parseInt(credits.file_upload_reward, 10); const fileDownloadCostValue = parseInt(credits.file_download_cost, 10); const fileDownloadRewardValue = parseInt(credits.file_download_reward, 10); const aiPerMilliUsdValue = parseInt(credits.ai_credits_per_milli_usd, 10); const new14DaysValue = parseInt(credits.return_14days, 10); const transferFeeValue = parseFloat(credits.transfer_fee_percent); document.getElementById('bbsCreditsDailyAmount').value = Number.isNaN(dailyAmountValue) ? 25 : dailyAmountValue; document.getElementById('bbsCreditsDailyDelay').value = Number.isNaN(dailyDelayValue) ? 5 : dailyDelayValue; document.getElementById('bbsCreditsApprovalBonus').value = Number.isNaN(approvalBonusValue) ? 100 : approvalBonusValue; document.getElementById('bbsCreditsNetmailCost').value = Number.isNaN(netmailCostValue) ? 1 : netmailCostValue; document.getElementById('bbsCreditsEchomailReward').value = Number.isNaN(echomailRewardValue) ? 3 : echomailRewardValue; document.getElementById('bbsCreditsCrashmailCost').value = Number.isNaN(crashmailCostValue) ? 10 : crashmailCostValue; document.getElementById('bbsCreditsPollCreationCost').value = Number.isNaN(pollCreationCostValue) ? 15 : pollCreationCostValue; document.getElementById('bbsCreditsFileUploadCost').value = Number.isNaN(fileUploadCostValue) ? 0 : fileUploadCostValue; document.getElementById('bbsCreditsFileUploadReward').value = Number.isNaN(fileUploadRewardValue) ? 0 : fileUploadRewardValue; document.getElementById('bbsCreditsFileDownloadCost').value = Number.isNaN(fileDownloadCostValue) ? 0 : fileDownloadCostValue; document.getElementById('bbsCreditsFileDownloadReward').value = Number.isNaN(fileDownloadRewardValue) ? 0 : fileDownloadRewardValue; document.getElementById('bbsCreditsAiPerMilliUsd').value = Number.isNaN(aiPerMilliUsdValue) ? 0 : aiPerMilliUsdValue; document.getElementById('bbsCreditsNew14Days').value = Number.isNaN(new14DaysValue) ? 50 : new14DaysValue; document.getElementById('bbsCreditsTransferFee').value = Number.isNaN(transferFeeValue) ? 0.05 : transferFeeValue; const referralBonusValue = parseInt(credits.referral_bonus, 10); document.getElementById('bbsCreditsReferralEnabled').checked = !!credits.referral_enabled; document.getElementById('bbsCreditsReferralBonus').value = Number.isNaN(referralBonusValue) ? 25 : referralBonusValue; setSettingsLoading('#bbsSettingsCards', false); }) .catch(error => { showBbsSettingsAlert(error.message || uiT('ui.admin.bbs_settings.load_failed', 'Failed to load settings'), 'danger'); }); } function saveBbsSettings() { const maxCrossPost = parseInt(document.getElementById('bbsMaxCrossPost').value, 10); if (Number.isNaN(maxCrossPost) || maxCrossPost < 2 || maxCrossPost > 20) { showBbsSettingsAlert(uiT('ui.admin.bbs_settings.validation.max_cross_post_areas_range', 'Max cross-post areas must be an integer between 2 and 20.'), 'danger'); return; } const echomailModerationThreshold = parseInt(document.getElementById('bbsEchomailModerationThreshold').value, 10); if (Number.isNaN(echomailModerationThreshold) || echomailModerationThreshold < 0) { showBbsSettingsAlert(uiT('ui.admin.bbs_settings.validation.echomail_moderation_threshold_range', 'Echomail moderation threshold must be 0 or higher (0 = disabled).'), 'danger'); return; } const dashboardAdRotateInterval = parseInt(document.getElementById('bbsDashboardAdRotateInterval').value, 10); if (Number.isNaN(dashboardAdRotateInterval) || dashboardAdRotateInterval < 5 || dashboardAdRotateInterval > 300) { showBbsSettingsAlert(uiT('ui.admin.bbs_settings.validation.dashboard_ad_rotate_interval_range', 'Dashboard ad rotation interval must be an integer between 5 and 300 seconds.'), 'danger'); return; } const qwkBbsIdRaw = document.getElementById('bbsQwkBbsId').value.trim().toUpperCase().replace(/[^A-Z0-9]/g, '').substring(0, 8); if (qwkBbsIdRaw.length === 0) { showBbsSettingsAlert(uiT('ui.admin.bbs_settings.validation.qwk_bbs_id_invalid', 'QWK BBS ID must be 1?8 letters or digits.'), 'danger'); return; } document.getElementById('bbsQwkBbsId').value = qwkBbsIdRaw; // Get current credits + qwk settings (don't change them) fetch('/admin/api/bbs-settings') .then(response => response.json()) .then(currentData => { const credits = currentData.config?.credits || {}; const currentQwk = currentData.config?.qwk || {}; const config = { features: { webdoors: document.getElementById('bbsWebdoors').checked, shoutbox: document.getElementById('bbsShoutbox').checked, advertising: document.getElementById('bbsAdvertising').checked, voting_booth: document.getElementById('bbsVoting').checked, chat: document.getElementById('bbsChat').checked, file_areas: document.getElementById('bbsFileAreas').checked, guest_doors_page: document.getElementById('bbsGuestDoorsPage').checked, bbs_directory: document.getElementById('bbsBbsDirectory').checked, public_files_index: document.getElementById('bbsPublicFilesIndex') ? document.getElementById('bbsPublicFilesIndex').checked : false, qwk: document.getElementById('bbsQwk').checked }, credits: credits, qwk: Object.assign({}, currentQwk, { bbs_id: qwkBbsIdRaw }), max_cross_post_areas: maxCrossPost, echomail_moderation_threshold: echomailModerationThreshold, dashboard_ad_rotate_interval_seconds: dashboardAdRotateInterval, default_echo_interface: document.getElementById('bbsDefaultEchoInterface').value, bulletin_display_mode: document.getElementById('bbsBulletinDisplayMode').value, outgoing_charset: document.getElementById('bbsOutgoingCharset').value, }; return fetch('/admin/api/bbs-settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config }) }); }) .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.bbs_settings.save_failed', 'Failed to save settings'))); } const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.bbs_settings.saved_success', 'BBS features saved successfully.')) : uiT('ui.admin.bbs_settings.saved_success', 'BBS features saved successfully.'); showBbsSettingsAlert(successMessage, 'success'); }) .catch(error => { showBbsSettingsAlert(error.message || uiT('ui.admin.bbs_settings.save_failed', 'Failed to save settings'), 'danger'); }); } function saveBbsPacketBbs() { const sessionTimeout = parseInt(document.getElementById('bbsPacketBbsSessionTimeout').value, 10); if (Number.isNaN(sessionTimeout) || sessionTimeout < 1) { showBbsPacketBbsAlert(uiT('ui.admin.bbs_settings.validation.session_timeout_positive', 'Session timeout must be a positive integer.'), 'danger'); return; } fetch('/admin/api/bbs-settings') .then(response => response.json()) .then(currentData => { const config = Object.assign({}, currentData.config || {}, { packet_bbs: { session_timeout_minutes: sessionTimeout, allow_guest_who: document.getElementById('bbsPacketBbsAllowGuestWho').checked } }); return fetch('/admin/api/bbs-settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config }) }); }) .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.bbs_settings.packet_bbs.save_failed', 'Failed to save Packet BBS settings.'))); } const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.bbs_settings.packet_bbs.saved_success', 'Packet BBS settings saved successfully.')) : uiT('ui.admin.bbs_settings.packet_bbs.saved_success', 'Packet BBS settings saved successfully.'); showBbsPacketBbsAlert(successMessage, 'success'); }) .catch(error => { showBbsPacketBbsAlert(error.message || uiT('ui.admin.bbs_settings.packet_bbs.save_failed', 'Failed to save Packet BBS settings.'), 'danger'); }); } function saveTerminalIdle() { const defaultShellSelect = document.getElementById('bbsTerminalDefaultShell'); if (!defaultShellSelect || defaultShellSelect.options.length === 0) { showBbsTerminalIdleAlert(uiT('ui.admin.bbs_settings.terminal_server.save_failed', 'Failed to save terminal server settings.'), 'danger'); return; } const warnMinutes = parseInt(document.getElementById('bbsIdleWarnMinutes').value, 10); if (Number.isNaN(warnMinutes) || warnMinutes < 1) { showBbsTerminalIdleAlert(uiT('ui.admin.bbs_settings.validation.terminal_idle_warn_positive', 'Idle warning timeout must be a positive integer.'), 'danger'); return; } const disconnectMinutes = parseInt(document.getElementById('bbsIdleDisconnectMinutes').value, 10); if (Number.isNaN(disconnectMinutes) || disconnectMinutes < 1) { showBbsTerminalIdleAlert(uiT('ui.admin.bbs_settings.validation.terminal_idle_disconnect_positive', 'Idle disconnect timeout must be a positive integer.'), 'danger'); return; } if (disconnectMinutes <= warnMinutes) { showBbsTerminalIdleAlert(uiT('ui.admin.bbs_settings.validation.terminal_idle_disconnect_gt_warn', 'Idle disconnect timeout must be greater than the warning timeout.'), 'danger'); return; } fetch('/admin/api/bbs-settings') .then(response => response.json()) .then(currentData => { const config = Object.assign({}, currentData.config || {}, { terminal_idle: { warn_minutes: warnMinutes, disconnect_minutes: disconnectMinutes }, terminal_server: { default_shell: defaultShellSelect.value, force_shell: document.getElementById('bbsTerminalForceShell').checked } }); return fetch('/admin/api/bbs-settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config }) }); }) .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.bbs_settings.terminal_server.save_failed', 'Failed to save terminal server settings.'))); } const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.bbs_settings.terminal_server.saved_success', 'Terminal server settings saved.')) : uiT('ui.admin.bbs_settings.terminal_server.saved_success', 'Terminal server settings saved.'); showBbsTerminalIdleAlert(successMessage, 'success'); }) .catch(error => { showBbsTerminalIdleAlert(error.message || uiT('ui.admin.bbs_settings.terminal_server.save_failed', 'Failed to save terminal server settings.'), 'danger'); }); } function loadBbsSystem() { fetch('/admin/api/bbs-system') .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.bbs_settings.load_system_failed', 'Failed to load system settings'))); } const config = data.config || {}; document.getElementById('bbsSystemName').value = config.name || ''; document.getElementById('bbsSystemSysop').dataset.currentValue = config.sysop || ''; document.getElementById('bbsSystemLocation').value = config.location || ''; document.getElementById('bbsSystemTimezone').value = config.timezone || 'UTC'; document.getElementById('bbsSystemAddress').value = config.address || ''; document.getElementById('bbsSystemOrigin').value = config.origin || ''; syncSysopSelect('bbsSystemSysop'); }) .catch(error => { showBbsSystemAlert(error.message || uiT('ui.admin.bbs_settings.load_system_failed', 'Failed to load system settings'), 'danger'); }); } function saveBbsSystem() { const config = { name: document.getElementById('bbsSystemName').value.trim(), sysop: document.getElementById('bbsSystemSysop').value, location: document.getElementById('bbsSystemLocation').value.trim(), timezone: document.getElementById('bbsSystemTimezone').value, address: document.getElementById('bbsSystemAddress').value.trim(), origin: document.getElementById('bbsSystemOrigin').value.trim() }; fetch('/admin/api/bbs-system', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config }) }) .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.bbs_settings.save_system_failed', 'Failed to save system settings'))); } const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.bbs_settings.system_saved_success', 'System settings saved successfully.')) : uiT('ui.admin.bbs_settings.system_saved_success', 'System settings saved successfully.'); showBbsSystemAlert(successMessage, 'success'); }) .catch(error => { showBbsSystemAlert(error.message || uiT('ui.admin.bbs_settings.save_system_failed', 'Failed to save system settings'), 'danger'); }); } function saveBbsCredits() { const symbol = document.getElementById('bbsCreditsSymbol').value.trim(); if (symbol.length > 5) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.currency_symbol_length', 'Currency symbol must be 0-5 characters.'), 'danger'); return; } const dailyAmount = parseInt(document.getElementById('bbsCreditsDailyAmount').value, 10); if (Number.isNaN(dailyAmount) || dailyAmount < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.daily_login_amount_non_negative', 'Daily login amount must be a non-negative integer.'), 'danger'); return; } const dailyDelay = parseInt(document.getElementById('bbsCreditsDailyDelay').value, 10); if (Number.isNaN(dailyDelay) || dailyDelay < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.daily_login_delay_non_negative', 'Daily login delay must be a non-negative integer.'), 'danger'); return; } const approvalBonus = parseInt(document.getElementById('bbsCreditsApprovalBonus').value, 10); if (Number.isNaN(approvalBonus) || approvalBonus < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.approval_bonus_non_negative', 'Approval bonus must be a non-negative integer.'), 'danger'); return; } const netmailCost = parseInt(document.getElementById('bbsCreditsNetmailCost').value, 10); if (Number.isNaN(netmailCost) || netmailCost < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.netmail_cost_non_negative', 'Netmail cost must be a non-negative integer.'), 'danger'); return; } const echomailReward = parseInt(document.getElementById('bbsCreditsEchomailReward').value, 10); if (Number.isNaN(echomailReward) || echomailReward < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.echomail_reward_non_negative', 'Echomail reward must be a non-negative integer.'), 'danger'); return; } const crashmailCost = parseInt(document.getElementById('bbsCreditsCrashmailCost').value, 10); if (Number.isNaN(crashmailCost) || crashmailCost < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.crashmail_cost_non_negative', 'Crashmail cost must be a non-negative integer.'), 'danger'); return; } const pollCreationCost = parseInt(document.getElementById('bbsCreditsPollCreationCost').value, 10); if (Number.isNaN(pollCreationCost) || pollCreationCost < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.poll_creation_cost_non_negative', 'Poll creation cost must be a non-negative integer.'), 'danger'); return; } const fileUploadCost = parseInt(document.getElementById('bbsCreditsFileUploadCost').value, 10); if (Number.isNaN(fileUploadCost) || fileUploadCost < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.file_upload_cost_non_negative', 'File upload cost must be a non-negative integer.'), 'danger'); return; } const fileUploadReward = parseInt(document.getElementById('bbsCreditsFileUploadReward').value, 10); if (Number.isNaN(fileUploadReward) || fileUploadReward < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.file_upload_reward_non_negative', 'File upload reward must be a non-negative integer.'), 'danger'); return; } const fileDownloadCost = parseInt(document.getElementById('bbsCreditsFileDownloadCost').value, 10); if (Number.isNaN(fileDownloadCost) || fileDownloadCost < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.file_download_cost_non_negative', 'File download cost must be a non-negative integer.'), 'danger'); return; } const fileDownloadReward = parseInt(document.getElementById('bbsCreditsFileDownloadReward').value, 10); if (Number.isNaN(fileDownloadReward) || fileDownloadReward < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.file_download_reward_non_negative', 'File download reward must be a non-negative integer.'), 'danger'); return; } const aiPerMilliUsd = parseInt(document.getElementById('bbsCreditsAiPerMilliUsd').value, 10); if (Number.isNaN(aiPerMilliUsd) || aiPerMilliUsd < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.ai_credits_per_milli_usd_non_negative', 'AI credits per $0.001 must be a non-negative integer.'), 'danger'); return; } const new14Days = parseInt(document.getElementById('bbsCreditsNew14Days').value, 10); if (Number.isNaN(new14Days) || new14Days < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.return_14_days_non_negative', '14-day return bonus must be a non-negative integer.'), 'danger'); return; } const transferFee = parseFloat(document.getElementById('bbsCreditsTransferFee').value); if (Number.isNaN(transferFee) || transferFee < 0 || transferFee > 1) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.transfer_fee_range', 'Transfer fee must be between 0 and 1 (0% to 100%).'), 'danger'); return; } const referralBonus = parseInt(document.getElementById('bbsCreditsReferralBonus').value, 10); if (Number.isNaN(referralBonus) || referralBonus < 0) { showBbsCreditsAlert(uiT('ui.admin.bbs_settings.validation.referral_bonus_non_negative', 'Referral bonus must be a non-negative integer.'), 'danger'); return; } // Get current BBS features settings (don't change them) fetch('/admin/api/bbs-settings') .then(response => response.json()) .then(currentData => { const features = currentData.config?.features || {}; const config = { features: features, credits: { enabled: document.getElementById('bbsCreditsEnabled').checked, symbol: symbol, daily_amount: dailyAmount, daily_login_delay_minutes: dailyDelay, approval_bonus: approvalBonus, netmail_cost: netmailCost, echomail_reward: echomailReward, crashmail_cost: crashmailCost, poll_creation_cost: pollCreationCost, file_upload_cost: fileUploadCost, file_upload_reward: fileUploadReward, file_download_cost: fileDownloadCost, file_download_reward: fileDownloadReward, ai_credits_per_milli_usd: aiPerMilliUsd, return_14days: new14Days, transfer_fee_percent: transferFee, referral_enabled: document.getElementById('bbsCreditsReferralEnabled').checked, referral_bonus: referralBonus } }; return fetch('/admin/api/bbs-settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config }) }); }) .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.bbs_settings.save_credits_failed', 'Failed to save credits settings'))); } const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.bbs_settings.credits_saved_success', 'Credits settings saved successfully.')) : uiT('ui.admin.bbs_settings.credits_saved_success', 'Credits settings saved successfully.'); showBbsCreditsAlert(successMessage, 'success'); }) .catch(error => { showBbsCreditsAlert(error.message || uiT('ui.admin.bbs_settings.save_credits_failed', 'Failed to save credits settings'), 'danger'); }); } function loadBbsTaglines() { fetch('/admin/api/taglines') .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.bbs_settings.load_taglines_failed', 'Failed to load taglines'))); } document.getElementById('bbsTaglinesText').value = data.taglines || ''; }) .catch(error => { showBbsTaglinesAlert(error.message || uiT('ui.admin.bbs_settings.load_taglines_failed', 'Failed to load taglines'), 'danger'); }); } function saveBbsTaglines() { const text = document.getElementById('bbsTaglinesText').value || ''; fetch('/admin/api/taglines', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ taglines: text }) }) .then(response => response.json()) .then(data => { if (!data.success) { throw new Error(apiError(data, uiT('ui.admin.bbs_settings.save_taglines_failed', 'Failed to save taglines'))); } const successMessage = window.getApiMessage ? window.getApiMessage(data, uiT('ui.admin.bbs_settings.taglines_saved_success', 'Taglines saved successfully.')) : uiT('ui.admin.bbs_settings.taglines_saved_success', 'Taglines saved successfully.'); showBbsTaglinesAlert(successMessage, 'success'); }) .catch(error => { showBbsTaglinesAlert(error.message || uiT('ui.admin.bbs_settings.save_taglines_failed', 'Failed to save taglines'), 'danger'); }); } function showBbsSettingsAlert(message, type) { const container = document.getElementById('bbsSettingsAlert'); container.innerHTML = ` <div class="alert alert-${type} alert-dismissible fade show" role="alert"> ${message} <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> `; } function showBbsPacketBbsAlert(message, type) { const container = document.getElementById('bbsPacketBbsAlert'); container.innerHTML = ` <div class="alert alert-${type} alert-dismissible fade show" role="alert"> ${message} <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> `; } function showBbsTerminalIdleAlert(message, type) { const container = document.getElementById('bbsTerminalIdleAlert'); container.innerHTML = ` <div class="alert alert-${type} alert-dismissible fade show" role="alert"> ${message} <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> `; } function showBbsCreditsAlert(message, type) { const container = document.getElementById('bbsCreditsAlert'); container.innerHTML = ` <div class="alert alert-${type} alert-dismissible fade show" role="alert"> ${message} <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> `; } function showBbsTaglinesAlert(message, type) { const container = document.getElementById('bbsTaglinesAlert'); container.innerHTML = ` <div class="alert alert-${type} alert-dismissible fade show" role="alert"> ${message} <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> `; } function showBbsSystemAlert(message, type) { const container = document.getElementById('bbsSystemAlert'); container.innerHTML = ` <div class="alert alert-${type} alert-dismissible fade show" role="alert"> ${message} <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> `; } function loadAdminUsers(selectId) { fetch('/admin/api/admin-users') .then(response => response.json()) .then(data => { const list = Array.isArray(data.admins) ? data.admins : []; const select = document.getElementById(selectId); select.innerHTML = ''; list.forEach(name => { const option = document.createElement('option'); option.value = name; option.textContent = name; select.appendChild(option); }); syncSysopSelect(selectId); }) .catch(() => {}); } function syncSysopSelect(selectId) { const select = document.getElementById(selectId); const current = select.dataset.currentValue || ''; if (!current) { return; } const exists = Array.from(select.options).some(opt => opt.value === current); if (!exists) { const option = document.createElement('option'); option.value = current; option.textContent = `${current} (${uiT('ui.admin.bbs_settings.current', 'current')})`; select.insertBefore(option, select.firstChild); } select.value = current; } function apiError(payload, fallback) { if (window.getApiErrorMessage) { return window.getApiErrorMessage(payload, fallback); } if (payload && payload.error) { return String(payload.error); } return fallback; } </script> {% endblock %}