sfx 0.1.3

SFX is a streamlined, full-stack Rust framework for building small web services with integrated authentication, localization, and config-driven UI components
Documentation
-[ template "/base/base.html" ]-

-[ block body ]-

-[ insert "/base/path.html" ]-

<div class="container-func">
    <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
        <h2 class="mb-0">Manage Admins</h2>
        <a class="btn btn-outline-secondary btn-sm" href="/admin/panel">Back to users</a>
    </div>

    <form id="addAdminForm" method="POST" action="/admin/admins" class="mb-4">
        <div class="mb-3">
            <label for="adminUid" class="form-label">UID or uid@server</label>
            <input id="adminUid" type="text" name="uid" class="form-control" placeholder="2 or 2@local" required />
        </div>
        <button type="submit" class="btn btn-pink">Add Admin</button>
        <span id="addAdminStatus" class="ms-2"></span>
    </form>

    <table class="table">
        <thead>
            <tr>
                <th>Admin entry</th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody id="adminsTableBody"></tbody>
    </table>

    <script>
    const esc = (s) => String(s ?? '').replace(/[&<>"']/g, c => ({
        '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
    }[c]));

    async function loadAdmins() {
        const tbody = document.getElementById('adminsTableBody');
        try {
            const res = await fetch('/admin/admins/json');
            const data = await res.json();
            const admins = Array.isArray(data.admins) ? data.admins : [];
            tbody.innerHTML = admins.map(admin =>
                '<tr><td>' + esc(admin) + '</td>' +
                '<td><button class="btn btn-sm btn-danger remove-admin" data-entry="' + esc(admin) + '">Remove</button></td></tr>'
            ).join('');
        } catch (e) {
            tbody.innerHTML = '<tr><td colspan="2">Unable to load admins</td></tr>';
        }
    }

    document.addEventListener('DOMContentLoaded', () => {
        loadAdmins();

        document.getElementById('addAdminForm').addEventListener('submit', async (event) => {
            event.preventDefault();
            const status = document.getElementById('addAdminStatus');
            status.textContent = 'Saving...';
            // Send urlencoded so hotaru's form_or_default() can parse it.
            const params = new URLSearchParams();
            for (const [k, v] of new FormData(event.currentTarget)) {
                params.append(k, v);
            }
            try {
                const res = await fetch('/admin/admins', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                    body: params.toString(),
                });
                const data = await res.json();
                if (!res.ok || !data.success) {
                    status.textContent = data.message || 'Add failed';
                    return;
                }
                status.textContent = 'Added';
                event.currentTarget.reset();
                loadAdmins();
            } catch (e) {
                status.textContent = 'Add failed';
            }
        });

        document.getElementById('adminsTableBody').addEventListener('click', async (event) => {
            if (!event.target.classList.contains('remove-admin')) {
                return;
            }
            const entry = event.target.getAttribute('data-entry');
            try {
                const res = await fetch('/admin/admins/' + encodeURIComponent(entry) + '/delete', {
                    method: 'POST'
                });
                const data = await res.json();
                if (!res.ok || !data.success) {
                    alert(data.message || 'Remove failed');
                    return;
                }
                loadAdmins();
            } catch (e) {
                alert('Remove failed');
            }
        });
    });
    </script>
</div>

-[ endblock ]-