Admin: Einstellungen mit Seitenleiste, Docker-Hosts und Messer-Server in die Einstellungen
This commit is contained in:
1 parent
ea06237846
commit
75b1701d8a
4 files changed
+139
-18
No files matched your search
@@ -271,7 +271,7 @@ Jeder Host braucht eigene Spieldateien (rund 60 bis 90 GB). Die Ports müssen au
|
|||||||
|
|
||||||
### Messer-Server für die Seitenwahl
|
### Messer-Server für die Seitenwahl
|
||||||
|
|
||||||
Im Admin-Bereich (Reiter „Matches“) startet „Messer-Server starten“ einen Gameserver, der keinem Match gehört und dauerhaft läuft. Teams verbinden sich, spielen eine Messerrunde und gehen wieder.
|
Im Admin-Bereich (Einstellungen → Gameserver → Messer-Server) startet „Messer-Server starten“ einen Gameserver, der keinem Match gehört und dauerhaft läuft. Teams verbinden sich, spielen eine Messerrunde und gehen wieder.
|
||||||
|
|
||||||
- Modus wählbar: **Competitive 5v5** (12 Plätze, Maps aus dem Competitive-Pool) oder **Wingman 2v2** (6 Plätze, Maps aus dem Wingman-Pool).
|
- Modus wählbar: **Competitive 5v5** (12 Plätze, Maps aus dem Competitive-Pool) oder **Wingman 2v2** (6 Plätze, Maps aus dem Wingman-Pool).
|
||||||
- Offen, ohne Passwort, sichtbar im LAN-Reiter von Steam (`Maxlan | Messer 1 5v5 (Seitenwahl)`) und auf der Startseite mit „Verbinden“.
|
- Offen, ohne Passwort, sichtbar im LAN-Reiter von Steam (`Maxlan | Messer 1 5v5 (Seitenwahl)`) und auf der Startseite mit „Verbinden“.
|
||||||
@@ -459,8 +459,6 @@ Unter `/admin` mit dem Passwort aus `ADMIN_PASSWORD` (leer = Admin-Bereich aus).
|
|||||||
|
|
||||||
### Reiter „Matches“
|
### Reiter „Matches“
|
||||||
|
|
||||||
- **Docker-Hosts:** pro Host belegte und freie Plätze, freier Speicher dort, wo das Gameserver-Volume liegt, und die Matches bzw. Messer-Server darauf.
|
|
||||||
- **Messer-Server (Seitenwahl):** starten (Modus 5v5 oder 2v2 und Map wählen), Runde starten, zurücksetzen, neu starten, entfernen. Siehe „Messer-Server für die Seitenwahl“.
|
|
||||||
- **Suche und Filter:** Suche nach Team, Spieler oder Match-ID. Filter Alle, Laufend, Fehler (Zahl rot, wenn es Fehler gibt), Abgeschlossen und Archiv. Der Filter bleibt im Browser gespeichert.
|
- **Suche und Filter:** Suche nach Team, Spieler oder Match-ID. Filter Alle, Laufend, Fehler (Zahl rot, wenn es Fehler gibt), Abgeschlossen und Archiv. Der Filter bleibt im Browser gespeichert.
|
||||||
- **Matchkarte:** Teams, Modus, Format, Status, Live-Stand, Server- und CSTV-Adresse (`Server 192.168.178.45:27015 · CSTV …:27115`), Zeitpunkt der Server-Anforderung, Pausen-Abzeichen. Bei einem Serverfehler steht die Ursache rot in der Karte.
|
- **Matchkarte:** Teams, Modus, Format, Status, Live-Stand, Server- und CSTV-Adresse (`Server 192.168.178.45:27015 · CSTV …:27115`), Zeitpunkt der Server-Anforderung, Pausen-Abzeichen. Bei einem Serverfehler steht die Ursache rot in der Karte.
|
||||||
- **Aktionen:** Ansehen, Veto starten, Aufwärmphase beenden, Pausieren/Fortsetzen, Nächste Map, Server neu starten / wiederholen, Server stoppen, Match abschließen, Match abbrechen, Archivieren / Aus dem Archiv holen, Löschen. Destruktive Aktionen fragen nach.
|
- **Aktionen:** Ansehen, Veto starten, Aufwärmphase beenden, Pausieren/Fortsetzen, Nächste Map, Server neu starten / wiederholen, Server stoppen, Match abschließen, Match abbrechen, Archivieren / Aus dem Archiv holen, Löschen. Destruktive Aktionen fragen nach.
|
||||||
@@ -471,9 +469,12 @@ Startet man den Server eines laufenden Matches neu, lädt die Anwendung danach d
|
|||||||
|
|
||||||
### Reiter „Einstellungen“
|
### Reiter „Einstellungen“
|
||||||
|
|
||||||
|
Die Einstellungen sind in eine Seitenleiste gegliedert: **Veto** (Map-Pools), **Gameserver** (Match-Regeln, CSTV, Zeiten, Messer-Server), **Server-Betrieb** (Docker-Hosts und Image-Update) und **Benachrichtigungen** (Discord). Am Handy stehen die Menüpunkte als Knöpfe über dem Inhalt. Der zuletzt gewählte Punkt bleibt im Browser gespeichert.
|
||||||
|
|
||||||
|
- **Messer-Server:** starten (Modus 5v5 oder 2v2 und Map wählen), Runde starten, zurücksetzen, neu starten, entfernen. Siehe „Messer-Server für die Seitenwahl“.
|
||||||
- **Map-Pools:** pro Modus wählen, welche Maps im Veto stehen. Der Veto-Ablauf passt sich der Anzahl an, siehe „Maps im Admin-Bereich wählen“.
|
- **Map-Pools:** pro Modus wählen, welche Maps im Veto stehen. Der Veto-Ablauf passt sich der Anzahl an, siehe „Maps im Admin-Bereich wählen“.
|
||||||
- **Gameserver:** Runden Competitive und Wingman, Verlängerung, zusätzliche Konsolenbefehle, CSTV an/aus und Verzögerung, Zeit pro Veto-Schritt, Leerlauf-Frist, maximale Laufzeit, Startzeit (inklusive Download) und Archiv-Frist. Die Werte aus der `.env` sind die Vorgabe. Was hier geändert wird, steht in der Datenbank und gilt auch nach einem Neustart. Mit „live“ markierte Werte gehen beim Speichern sofort per RCON an alle laufenden Server. Befehle, die Passwörter ändern oder den Server beenden, sind gesperrt.
|
- **Gameserver:** Runden Competitive und Wingman, Verlängerung, zusätzliche Konsolenbefehle, CSTV an/aus und Verzögerung, Zeit pro Veto-Schritt, Leerlauf-Frist, maximale Laufzeit, Startzeit (inklusive Download) und Archiv-Frist. Die Werte aus der `.env` sind die Vorgabe. Was hier geändert wird, steht in der Datenbank und gilt auch nach einem Neustart. Mit „live“ markierte Werte gehen beim Speichern sofort per RCON an alle laufenden Server. Befehle, die Passwörter ändern oder den Server beenden, sind gesperrt.
|
||||||
- **Docker-Hosts:** Hosts hinzufügen, entfernen und bearbeiten (Name, Docker-Ziel, Adresse, Portbereich, LAN-Ports, Volume). Die Liste ersetzt `CS2_DOCKER_HOSTS`. „Liste aus .env verwenden“ schaltet zurück. Hosts mit laufenden Servern lassen sich nicht entfernen und ihre Adresse nicht ändern.
|
- **Docker-Hosts:** oben die Auslastung pro Host (belegte und freie Plätze, freier Speicher dort, wo das Gameserver-Volume liegt, Matches und Messer-Server darauf), darunter Hosts hinzufügen, entfernen und bearbeiten (Name, Docker-Ziel, Adresse, Portbereich, LAN-Ports, Volume). Die Liste ersetzt `CS2_DOCKER_HOSTS`. „Liste aus .env verwenden“ schaltet zurück. Hosts mit laufenden Servern lassen sich nicht entfernen und ihre Adresse nicht ändern.
|
||||||
- **Image aktualisieren:** lädt das CS2-Image auf allen Hosts gleichzeitig neu (per SSH direkt auf dem Host). Laufende Server laufen weiter.
|
- **Image aktualisieren:** lädt das CS2-Image auf allen Hosts gleichzeitig neu (per SSH direkt auf dem Host). Laufende Server laufen weiter.
|
||||||
- **Discord-Meldungen:** welche Ereignisse gemeldet werden, plus Testmeldung.
|
- **Discord-Meldungen:** welche Ereignisse gemeldet werden, plus Testmeldung.
|
||||||
|
|
||||||
|
|||||||
+112
-13
@@ -59,7 +59,6 @@ const storeToken = (t) => {
|
|||||||
export default function AdminPage() {
|
export default function AdminPage() {
|
||||||
const [token, setToken] = useState(getToken);
|
const [token, setToken] = useState(getToken);
|
||||||
const [matches, setMatches] = useState(null);
|
const [matches, setMatches] = useState(null);
|
||||||
const [hosts, setHosts] = useState([]);
|
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [notice, setNotice] = useState(null);
|
const [notice, setNotice] = useState(null);
|
||||||
const [busy, setBusy] = useState(null);
|
const [busy, setBusy] = useState(null);
|
||||||
@@ -90,7 +89,6 @@ export default function AdminPage() {
|
|||||||
const res = await api(`/admin/matches${filter === 'archive' ? '?archived=1' : ''}`, { token });
|
const res = await api(`/admin/matches${filter === 'archive' ? '?archived=1' : ''}`, { token });
|
||||||
setMatches(res.matches);
|
setMatches(res.matches);
|
||||||
setArchivedCount(res.archivedCount ?? 0);
|
setArchivedCount(res.archivedCount ?? 0);
|
||||||
setHosts(res.hosts ?? []);
|
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.status === 401) {
|
if (err.status === 401) {
|
||||||
@@ -147,14 +145,9 @@ export default function AdminPage() {
|
|||||||
<button className={`admin-tab ${tab === 'settings' ? 'is-active' : ''}`} onClick={() => setTab('settings')}>Einstellungen</button>
|
<button className={`admin-tab ${tab === 'settings' ? 'is-active' : ''}`} onClick={() => setTab('settings')}>Einstellungen</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{tab === 'settings' && token && <MapPoolsPanel token={token} setNotice={setNotice} />}
|
{tab === 'settings' && token && <SettingsLayout token={token} setNotice={setNotice} />}
|
||||||
{tab === 'settings' && token && <ServerSettingsPanel token={token} setNotice={setNotice} />}
|
|
||||||
{tab === 'settings' && token && <DockerHostsPanel token={token} setNotice={setNotice} />}
|
|
||||||
{tab === 'settings' && token && <NotificationSettings token={token} setNotice={setNotice} />}
|
|
||||||
{notice && <div className={`toast toast-${notice.tone}`} role="status">{notice.text}</div>}
|
{notice && <div className={`toast toast-${notice.tone}`} role="status">{notice.text}</div>}
|
||||||
{error && <div className="alert alert-error">{error}</div>}
|
{error && <div className="alert alert-error">{error}</div>}
|
||||||
{tab === 'matches' && <HostUsage hosts={hosts} />}
|
|
||||||
{tab === 'matches' && <KnifePanel token={token} setNotice={setNotice} />}
|
|
||||||
{tab === 'matches' && matches === null && !error && <p className="muted">Lade Matches …</p>}
|
{tab === 'matches' && matches === null && !error && <p className="muted">Lade Matches …</p>}
|
||||||
{tab === 'matches' && (
|
{tab === 'matches' && (
|
||||||
<MatchToolbar
|
<MatchToolbar
|
||||||
@@ -479,6 +472,22 @@ function MatchAdminCard({ m, run, busy, token, reload, setNotice }) {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
/** Auslastung der Docker-Hosts, regelmäßig neu geladen (Einstellungen → Docker-Hosts). */
|
||||||
|
function HostUsageLive({ token }) {
|
||||||
|
const [hosts, setHosts] = useState([]);
|
||||||
|
useEffect(() => {
|
||||||
|
let alive = true;
|
||||||
|
const load = () => api('/admin/hosts', { token }).then((res) => alive && setHosts(res.hosts)).catch(() => {});
|
||||||
|
load();
|
||||||
|
const t = setInterval(load, REFRESH_MS);
|
||||||
|
return () => {
|
||||||
|
alive = false;
|
||||||
|
clearInterval(t);
|
||||||
|
};
|
||||||
|
}, [token]);
|
||||||
|
return <HostUsage hosts={hosts} />;
|
||||||
|
}
|
||||||
|
|
||||||
/** Auslastung der Docker-Hosts: wie viele Gameserver läuft auf welchem Host, mit Liste der Matches. */
|
/** Auslastung der Docker-Hosts: wie viele Gameserver läuft auf welchem Host, mit Liste der Matches. */
|
||||||
function HostUsage({ hosts }) {
|
function HostUsage({ hosts }) {
|
||||||
if (hosts.length === 0) return null;
|
if (hosts.length === 0) return null;
|
||||||
@@ -534,7 +543,7 @@ const shownDefault = (field) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** Gameserver-Einstellungen: Vorgaben aus der .env, im Admin-Bereich änderbar. */
|
/** Gameserver-Einstellungen: Vorgaben aus der .env, im Admin-Bereich änderbar. */
|
||||||
function ServerSettingsPanel({ token, setNotice }) {
|
function ServerSettingsPanel({ token, setNotice, groups = SETTING_GROUPS.map((g) => g.key), title = 'Gameserver' }) {
|
||||||
const [fields, setFields] = useState(null);
|
const [fields, setFields] = useState(null);
|
||||||
const [values, setValues] = useState({});
|
const [values, setValues] = useState({});
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
@@ -598,13 +607,13 @@ function ServerSettingsPanel({ token, setNotice }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="admin-settings">
|
<section className="admin-settings">
|
||||||
<h2>Gameserver</h2>
|
<h2>{title}</h2>
|
||||||
<p className="small muted">
|
<p className="small muted">
|
||||||
Markierte Werte (live) gehen beim Speichern sofort an alle laufenden Server. Die übrigen gelten für neue Server und Vetos.
|
Markierte Werte (live) gehen beim Speichern sofort an alle laufenden Server. Die übrigen gelten für neue Server und Vetos.
|
||||||
</p>
|
</p>
|
||||||
{SETTING_GROUPS.map((g) => (
|
{SETTING_GROUPS.filter((g) => groups.includes(g.key)).map((g) => (
|
||||||
<div key={g.key} className="setting-group">
|
<div key={g.key} className="setting-group">
|
||||||
<h3>{g.title}</h3>
|
{groups.length > 1 && <h3>{g.title}</h3>}
|
||||||
<div className="setting-grid">
|
<div className="setting-grid">
|
||||||
{fields.filter((f) => f.group === g.key).map((f) => (
|
{fields.filter((f) => f.group === g.key).map((f) => (
|
||||||
<div key={f.key} className="setting-item">
|
<div key={f.key} className="setting-item">
|
||||||
@@ -667,7 +676,14 @@ function DockerHostsPanel({ token, setNotice }) {
|
|||||||
}, [pulling, load]);
|
}, [pulling, load]);
|
||||||
|
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
if (!data.editable) return null;
|
if (!data.editable) {
|
||||||
|
return (
|
||||||
|
<section className="admin-settings">
|
||||||
|
<h2>Docker-Hosts</h2>
|
||||||
|
<p className="muted">Docker-Hosts und Image-Update gibt es nur mit dem Docker-Adapter (<code>SERVER_ADAPTER=docker</code>).</p>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const original = JSON.stringify(data.hosts.map((h) => ({ ...EMPTY_HOST, ...h, dockerHost: h.dockerHost ?? '' })));
|
const original = JSON.stringify(data.hosts.map((h) => ({ ...EMPTY_HOST, ...h, dockerHost: h.dockerHost ?? '' })));
|
||||||
const dirty = JSON.stringify(rows) !== original;
|
const dirty = JSON.stringify(rows) !== original;
|
||||||
@@ -1000,3 +1016,86 @@ function MapPoolsPanel({ token, setNotice }) {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const SETTINGS_KEY = 'cs2mv:admin:settings';
|
||||||
|
|
||||||
|
/** Menüpunkte der Einstellungen, in Gruppen für die Seitenleiste. */
|
||||||
|
const SETTINGS_MENU = [
|
||||||
|
{
|
||||||
|
title: 'Veto',
|
||||||
|
items: [{ key: 'maps', label: 'Map-Pools', hint: 'Maps im Veto' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Gameserver',
|
||||||
|
items: [
|
||||||
|
{ key: 'match', label: 'Match-Regeln', hint: 'Runden, Verlängerung, Befehle' },
|
||||||
|
{ key: 'tv', label: 'CSTV', hint: 'an/aus, Verzögerung' },
|
||||||
|
{ key: 'times', label: 'Zeiten', hint: 'Veto, Leerlauf, Start, Archiv' },
|
||||||
|
{ key: 'knife', label: 'Messer-Server', hint: 'Seitenwahl, 5v5 und 2v2' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Server-Betrieb',
|
||||||
|
items: [{ key: 'hosts', label: 'Docker-Hosts', hint: 'Hosts und Image-Update' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Benachrichtigungen',
|
||||||
|
items: [{ key: 'discord', label: 'Discord', hint: 'Meldungen und Test' }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const SETTINGS_KEYS = SETTINGS_MENU.flatMap((g) => g.items.map((i) => i.key));
|
||||||
|
|
||||||
|
const readSettingsSection = () => {
|
||||||
|
try {
|
||||||
|
const v = localStorage.getItem(SETTINGS_KEY);
|
||||||
|
return SETTINGS_KEYS.includes(v) ? v : SETTINGS_KEYS[0];
|
||||||
|
} catch {
|
||||||
|
return SETTINGS_KEYS[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Einstellungen mit Seitenleiste: links die Menüpunkte, rechts der gewählte Bereich. */
|
||||||
|
function SettingsLayout({ token, setNotice }) {
|
||||||
|
const [section, setSectionState] = useState(readSettingsSection);
|
||||||
|
const setSection = (key) => {
|
||||||
|
setSectionState(key);
|
||||||
|
try {
|
||||||
|
localStorage.setItem(SETTINGS_KEY, key);
|
||||||
|
} catch {
|
||||||
|
/* ignorieren */
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const current = SETTINGS_MENU.flatMap((g) => g.items).find((i) => i.key === section);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="settings-layout">
|
||||||
|
<nav className="settings-nav" aria-label="Einstellungen">
|
||||||
|
{SETTINGS_MENU.map((g) => (
|
||||||
|
<div key={g.title} className="settings-nav-group">
|
||||||
|
<div className="settings-nav-title">{g.title}</div>
|
||||||
|
{g.items.map((i) => (
|
||||||
|
<button
|
||||||
|
key={i.key}
|
||||||
|
className={`settings-nav-item ${section === i.key ? 'is-active' : ''}`}
|
||||||
|
aria-current={section === i.key ? 'page' : undefined}
|
||||||
|
onClick={() => setSection(i.key)}
|
||||||
|
>
|
||||||
|
<span>{i.label}</span>
|
||||||
|
<small>{i.hint}</small>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
<div className="settings-content">
|
||||||
|
{section === 'maps' && <MapPoolsPanel token={token} setNotice={setNotice} />}
|
||||||
|
{['match', 'tv', 'times'].includes(section) && (
|
||||||
|
<ServerSettingsPanel key={section} token={token} setNotice={setNotice} groups={[section]} title={current.label} />
|
||||||
|
)}
|
||||||
|
{section === 'knife' && <KnifePanel token={token} setNotice={setNotice} />}
|
||||||
|
{section === 'hosts' && <HostUsageLive token={token} />}
|
||||||
|
{section === 'hosts' && <DockerHostsPanel token={token} setNotice={setNotice} />}
|
||||||
|
{section === 'discord' && <NotificationSettings token={token} setNotice={setNotice} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -626,3 +626,22 @@ input:focus, select:focus { border-color: var(--link); outline: none; }
|
|||||||
.flow-steps .flow-ban { color: var(--error); border-color: color-mix(in srgb, var(--error) 50%, transparent); }
|
.flow-steps .flow-ban { color: var(--error); border-color: color-mix(in srgb, var(--error) 50%, transparent); }
|
||||||
.flow-steps .flow-pick { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 50%, transparent); }
|
.flow-steps .flow-pick { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 50%, transparent); }
|
||||||
.flow-steps .flow-decider { color: var(--text); border-color: var(--primary); }
|
.flow-steps .flow-decider { color: var(--text); border-color: var(--primary); }
|
||||||
|
/* Admin-Einstellungen mit Seitenleiste */
|
||||||
|
.settings-layout { display: grid; grid-template-columns: 230px minmax(0, 1fr); gap: 20px; align-items: start; }
|
||||||
|
.settings-nav { position: sticky; top: 16px; display: flex; flex-direction: column; gap: 14px; }
|
||||||
|
.settings-nav-title { font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin: 0 0 4px 10px; }
|
||||||
|
.settings-nav-group { display: flex; flex-direction: column; gap: 2px; }
|
||||||
|
.settings-nav-item { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; width: 100%; text-align: left; font: inherit; color: var(--text); background: transparent; border: 1px solid transparent; border-left: 3px solid transparent; border-radius: 4px; padding: 7px 10px; cursor: pointer; }
|
||||||
|
.settings-nav-item small { font-size: .75rem; color: var(--muted); }
|
||||||
|
.settings-nav-item:hover { background: var(--surface); }
|
||||||
|
.settings-nav-item.is-active { background: var(--surface); border-color: var(--line); border-left-color: var(--primary); }
|
||||||
|
.settings-content > .admin-settings { margin-bottom: 0; }
|
||||||
|
@media (max-width: 760px) {
|
||||||
|
.settings-layout { grid-template-columns: minmax(0, 1fr); gap: 12px; }
|
||||||
|
.settings-nav { position: static; flex-direction: row; flex-wrap: wrap; gap: 6px; }
|
||||||
|
.settings-nav-group { flex-direction: row; flex-wrap: wrap; gap: 6px; }
|
||||||
|
.settings-nav-title { display: none; }
|
||||||
|
.settings-nav-item { width: auto; white-space: nowrap; border: 1px solid var(--line); border-radius: 999px; padding: 5px 12px; }
|
||||||
|
.settings-nav-item small { display: none; }
|
||||||
|
.settings-nav-item.is-active { border-color: var(--primary); }
|
||||||
|
}
|
||||||
@@ -164,6 +164,9 @@ export function createAdminRouter({ service, manager, config, settings, knife, p
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Alle Matches mit allen Details (Codes, Verbindungsdaten, Spieler, Veto, Serie, Ereignisse)
|
// Alle Matches mit allen Details (Codes, Verbindungsdaten, Spieler, Veto, Serie, Ereignisse)
|
||||||
|
// Auslastung und Speicher der Docker-Hosts mit den Servern darauf
|
||||||
|
router.get('/hosts', requireAdmin, wrap(async () => ({ hosts: await manager.hostUsage() })));
|
||||||
|
|
||||||
// ?archived=1 liefert das Archiv, sonst alle nicht archivierten Matches
|
// ?archived=1 liefert das Archiv, sonst alle nicht archivierten Matches
|
||||||
router.get('/matches', requireAdmin, wrap(async (req) => {
|
router.get('/matches', requireAdmin, wrap(async (req) => {
|
||||||
const archived = req.query.archived === '1';
|
const archived = req.query.archived === '1';
|
||||||
@@ -174,7 +177,6 @@ export function createAdminRouter({ service, manager, config, settings, knife, p
|
|||||||
return {
|
return {
|
||||||
matches: ids.map(({ id }) => service.snapshot(id, adminViewer(id))),
|
matches: ids.map(({ id }) => service.snapshot(id, adminViewer(id))),
|
||||||
archivedCount,
|
archivedCount,
|
||||||
hosts: await manager.hostUsage(),
|
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user