initialize generic rms-software repository

Add the reusable RMS core application (server, web UI, plugins, tests, tools) with generic defaults, GPL licensing, and maintainer context documentation so deployments can consume this repo as software source independent of station-specific overlays.
This commit is contained in:
2026-03-16 03:31:08 +01:00
commit e1a4ce0b8b
58 changed files with 20611 additions and 0 deletions

4845
public/app.js Normal file

File diff suppressed because it is too large Load Diff

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

108
public/i18n/de.json Normal file
View File

@@ -0,0 +1,108 @@
{
"meta": {
"code": "de",
"name": "Deutsch"
},
"locales": {
"de": "Deutsch",
"en": "English"
},
"literals": {
"📡 Status": "📡 Status",
"📈 SWR": "📈 SWR",
"⚙ Einstellungen": "⚙ Einstellungen",
"❓ Hilfe": "❓ Hilfe",
"🧩 Plugins": "🧩 Plugins",
"⚙ Plugin Konfig": "⚙ Plugin Konfig",
"🔌 Provider": "🔌 Provider",
"👥 User Admin": "👥 User Admin",
"✅ Freigaben": "✅ Freigaben",
"📜 Aktivitaet": "📜 Aktivitaet",
"🛠 Admin": "🛠 Admin",
"Logout": "Logout",
"Anmeldung": "Anmeldung",
"Anmeldung nur per E-Mail-Adresse. Du bekommst einen Login- oder Bestaetigungslink.": "Anmeldung nur per E-Mail-Adresse. Du bekommst einen Login- oder Bestaetigungslink.",
"E-Mail": "E-Mail",
"Bestaetigungsart": "Bestaetigungsart",
"Link senden": "Link senden",
"OTP-Code": "OTP-Code",
"Code bestaetigen": "Code bestaetigen",
"RMS Status": "RMS Status",
"Bitte anmelden": "Bitte anmelden",
"LOGIN": "LOGIN",
"Einstellungen": "Einstellungen",
"Persoenliche Einstellungen": "Persoenliche Einstellungen",
"RMS / EINSTELLUNGEN": "RMS / EINSTELLUNGEN",
"RMS / HILFE": "RMS / HILFE",
"RMS / STATUS": "RMS / STATUS",
"Station steuern": "Station steuern",
"Stationsstatus": "Stationsstatus",
"Station": "Station",
"Status": "Status",
"Aktiver Benutzer": "Aktiver Benutzer",
"Seit": "Seit",
"Bis": "Bis",
"Restzeit": "Restzeit",
"Station aktivieren": "Station aktivieren",
"Station deaktivieren": "Station deaktivieren",
"Aktualisieren": "Aktualisieren",
"SWR-Check laeuft": "SWR-Check laeuft",
"Geschaetzte Restzeit: -": "Geschaetzte Restzeit: -",
"OpenWebRX": "OpenWebRX",
"OpenWebRX laden": "OpenWebRX laden",
"SDR oeffnen": "SDR oeffnen",
"Session Key (Ticket):": "Session Key (Ticket):",
"Reservierungen": "Reservierungen",
"Naechsten Slot reservieren": "Naechsten Slot reservieren",
"SWR Uebersicht": "SWR Uebersicht",
"Neu laden": "Neu laden",
"Stand: -": "Stand: -",
"Gesamt: -": "Gesamt: -",
"SWR Test starten": "SWR Test starten",
"Benutzer-Einstellungen": "Benutzer-Einstellungen",
"Hier findest du nur deine persoenlichen Einstellungen.": "Hier findest du nur deine persoenlichen Einstellungen.",
"Rolle": "Rolle",
"Praeferierte Authentifizierung": "Praeferierte Authentifizierung",
"Authentifizierung speichern": "Authentifizierung speichern",
"Theme wechseln": "Theme wechseln",
"Status neu laden": "Status neu laden",
"Sprache": "Sprache",
"Sprache speichern": "Sprache speichern",
"Hilfe": "Hilfe",
"Plugin Controls": "Plugin Controls",
"Dynamische Geraetesteuerung": "Dynamische Geraetesteuerung",
"RMS / PLUGIN CONTROLS": "RMS / PLUGIN CONTROLS",
"Plugin Konfiguration": "Plugin Konfiguration",
"Einstellungen und Aktivierung": "Einstellungen und Aktivierung",
"RMS / PLUGIN KONFIG": "RMS / PLUGIN KONFIG",
"Provider": "Provider",
"Capability-Zuordnung": "Capability-Zuordnung",
"RMS / PROVIDER": "RMS / PROVIDER",
"Benutzerverwaltung": "Benutzerverwaltung",
"RMS / BENUTZER": "RMS / BENUTZER",
"Freigaben": "Freigaben",
"RMS / FREIGABEN": "RMS / FREIGABEN",
"Aktivitaet": "Aktivitaet",
"RMS / AKTIVITAET": "RMS / AKTIVITAET",
"Admin": "Admin",
"Systemsteuerung": "Systemsteuerung",
"RMS / ADMIN": "RMS / ADMIN",
"Schnellstart": "Schnellstart",
"Aktiv": "Aktiv",
"Inaktiv": "Inaktiv",
"In Benutzung": "In Benutzung",
"Aktivierung laeuft": "Aktivierung laeuft",
"Frei": "Frei",
"Station freigegeben": "Station freigegeben",
"Reservierung gespeichert": "Reservierung gespeichert",
"Reservierung entfernt": "Reservierung entfernt",
"Noch keine Reservierungen vorhanden.": "Noch keine Reservierungen vorhanden.",
"Meine Reservierung loeschen": "Meine Reservierung loeschen",
"Aktiver Slot": "Aktiver Slot",
"per Mail": "per Mail",
"Keine Methode verfuegbar": "Keine Methode verfuegbar",
"Praeferierte Authentifizierung gespeichert.": "Praeferierte Authentifizierung gespeichert.",
"Praeferierte Sprache gespeichert.": "Praeferierte Sprache gespeichert."
,"Wartungsmodus aktiv": "Wartungsmodus aktiv"
}
}

108
public/i18n/en.json Normal file
View File

@@ -0,0 +1,108 @@
{
"meta": {
"code": "en",
"name": "English"
},
"locales": {
"de": "German",
"en": "English"
},
"literals": {
"📡 Status": "📡 Status",
"📈 SWR": "📈 SWR",
"⚙ Einstellungen": "⚙ Settings",
"❓ Hilfe": "❓ Help",
"🧩 Plugins": "🧩 Plugins",
"⚙ Plugin Konfig": "⚙ Plugin Config",
"🔌 Provider": "🔌 Providers",
"👥 User Admin": "👥 User Admin",
"✅ Freigaben": "✅ Approvals",
"📜 Aktivitaet": "📜 Activity",
"🛠 Admin": "🛠 Admin",
"Logout": "Logout",
"Anmeldung": "Sign in",
"Anmeldung nur per E-Mail-Adresse. Du bekommst einen Login- oder Bestaetigungslink.": "Sign in with your email address. You will receive a login or verification link.",
"E-Mail": "Email",
"Bestaetigungsart": "Verification method",
"Link senden": "Send link",
"OTP-Code": "OTP code",
"Code bestaetigen": "Verify code",
"RMS Status": "RMS Status",
"Bitte anmelden": "Please sign in",
"LOGIN": "LOGIN",
"Einstellungen": "Settings",
"Persoenliche Einstellungen": "Personal settings",
"RMS / EINSTELLUNGEN": "RMS / SETTINGS",
"RMS / HILFE": "RMS / HELP",
"RMS / STATUS": "RMS / STATUS",
"Station steuern": "Control station",
"Stationsstatus": "Station status",
"Station": "Station",
"Status": "Status",
"Aktiver Benutzer": "Active user",
"Seit": "Since",
"Bis": "Until",
"Restzeit": "Remaining",
"Station aktivieren": "Activate station",
"Station deaktivieren": "Deactivate station",
"Aktualisieren": "Refresh",
"SWR-Check laeuft": "SWR check running",
"Geschaetzte Restzeit: -": "Estimated remaining time: -",
"OpenWebRX": "OpenWebRX",
"OpenWebRX laden": "Load OpenWebRX",
"SDR oeffnen": "Open SDR",
"Session Key (Ticket):": "Session key (ticket):",
"Reservierungen": "Reservations",
"Naechsten Slot reservieren": "Reserve next slot",
"SWR Uebersicht": "SWR Overview",
"Neu laden": "Reload",
"Stand: -": "Updated: -",
"Gesamt: -": "Overall: -",
"SWR Test starten": "Start SWR test",
"Benutzer-Einstellungen": "User settings",
"Hier findest du nur deine persoenlichen Einstellungen.": "Only your personal settings are shown here.",
"Rolle": "Role",
"Praeferierte Authentifizierung": "Preferred authentication",
"Authentifizierung speichern": "Save authentication",
"Theme wechseln": "Toggle theme",
"Status neu laden": "Reload status",
"Sprache": "Language",
"Sprache speichern": "Save language",
"Hilfe": "Help",
"Plugin Controls": "Plugin controls",
"Dynamische Geraetesteuerung": "Dynamic device controls",
"RMS / PLUGIN CONTROLS": "RMS / PLUGIN CONTROLS",
"Plugin Konfiguration": "Plugin configuration",
"Einstellungen und Aktivierung": "Settings and activation",
"RMS / PLUGIN KONFIG": "RMS / PLUGIN CONFIG",
"Provider": "Providers",
"Capability-Zuordnung": "Capability assignment",
"RMS / PROVIDER": "RMS / PROVIDERS",
"Benutzerverwaltung": "User management",
"RMS / BENUTZER": "RMS / USERS",
"Freigaben": "Approvals",
"RMS / FREIGABEN": "RMS / APPROVALS",
"Aktivitaet": "Activity",
"RMS / AKTIVITAET": "RMS / ACTIVITY",
"Admin": "Admin",
"Systemsteuerung": "System control",
"RMS / ADMIN": "RMS / ADMIN",
"Schnellstart": "Quick start",
"Aktiv": "Active",
"Inaktiv": "Inactive",
"In Benutzung": "In use",
"Aktivierung laeuft": "Activation running",
"Frei": "Free",
"Station freigegeben": "Station released",
"Reservierung gespeichert": "Reservation saved",
"Reservierung entfernt": "Reservation removed",
"Noch keine Reservierungen vorhanden.": "No reservations yet.",
"Meine Reservierung loeschen": "Remove my reservation",
"Aktiver Slot": "Active slot",
"per Mail": "by email",
"Keine Methode verfuegbar": "No method available",
"Praeferierte Authentifizierung gespeichert.": "Preferred authentication saved.",
"Praeferierte Sprache gespeichert.": "Preferred language saved."
,"Wartungsmodus aktiv": "Maintenance mode active"
}
}

519
public/index.html Normal file
View File

@@ -0,0 +1,519 @@
<!doctype html>
<html lang="de" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ARCG RemoteStation</title>
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@500;700;800&family=Source+Sans+3:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<div class="page-bg"></div>
<main class="app-shell reveal">
<header class="topbar">
<div class="brand">
<a class="brand-home-link" href="/rms" aria-label="Zur RMS-Statusseite">
<div class="brand-mark" id="brandMark">
<img id="brandLogo" alt="ARCG Logo" hidden />
<span id="brandFallback">ARCG</span>
</div>
</a>
<div>
<p class="eyebrow">Amateur Radio Club Graz</p>
<h1>RemoteStation Control</h1>
<p id="pageCrumb" class="eyebrow">LOGIN</p>
</div>
</div>
<div class="topbar-actions">
<a id="currentUserLink" class="ghost-btn" href="/rms/user" hidden>👤 -</a>
<button id="userMenuButton" class="ghost-btn" type="button" aria-expanded="false" aria-label="Menue oeffnen"></button>
<button id="languageMenuButton" class="ghost-btn" type="button" aria-expanded="false" aria-label="Sprache waehlen">🌐</button>
<div id="languageMenu" class="language-menu" hidden>
<select id="menuLanguageSelect" aria-label="Sprache"></select>
</div>
<div id="userMenu" class="user-menu" hidden>
<button id="menuRms" type="button">📡 Status</button>
<button id="menuSwr" type="button">📈 SWR</button>
<button id="menuUser" type="button" hidden>⚙ Einstellungen</button>
<button id="menuHelp" type="button">❓ Hilfe</button>
<button id="menuPlugins" type="button">🧩 Plugins</button>
<button id="menuPluginConfig" type="button" hidden>⚙ Plugin Konfig</button>
<button id="menuProviders" type="button" hidden>🔌 Provider</button>
<button id="menuUsers" type="button" hidden>👥 User Admin</button>
<button id="menuApprovals" type="button" hidden>✅ Freigaben</button>
<button id="menuActivity" type="button" hidden>📜 Aktivitaet</button>
<button id="menuAdmin" type="button" hidden>🛠 Admin</button>
<hr class="menu-separator" />
<button id="logoutBtn" type="button" class="danger">Logout</button>
</div>
<button id="themeToggle" class="ghost-btn" type="button" aria-label="Theme wechseln"></button>
</div>
</header>
<section class="view auth-view" id="authView">
<article class="card login-card stagger" id="authCard">
<h2>Anmeldung</h2>
<p class="muted">Anmeldung nur per E-Mail-Adresse. Du bekommst einen Login- oder Bestaetigungslink.</p>
<p id="maintenanceBanner" class="message" hidden></p>
<form id="authForm" class="stack">
<label class="field">
<span>E-Mail</span>
<input id="email" type="email" autocomplete="email" required />
</label>
<label class="field">
<span>Bestaetigungsart</span>
<select id="authMethodSelect"></select>
</label>
<div class="actions">
<button type="submit" id="loginBtn">Link senden</button>
</div>
<div id="otpWrap" class="stack" hidden>
<label class="field">
<span>OTP-Code</span>
<input id="otpCode" type="text" inputmode="numeric" maxlength="6" placeholder="123456" />
</label>
<div class="actions">
<button type="button" id="verifyOtpBtn">Code bestaetigen</button>
</div>
</div>
<p id="authMessage" class="message"></p>
</form>
</article>
</section>
<section class="view" id="rmsView" hidden>
<article class="card page-overview">
<div class="section-head">
<h2 id="pageTitle">RMS Status</h2>
<span id="pageHint" class="pill">Station steuern</span>
</div>
</article>
<section class="grid-layout" id="pageRms" hidden>
<article class="card status-card stagger" id="statusCard">
<div class="section-head">
<h2>Stationsstatus</h2>
<span class="pill" id="stationOnlinePill">Pruefe...</span>
</div>
<dl class="status-grid">
<div>
<dt>Station</dt>
<dd id="stationName">-</dd>
</div>
<div>
<dt>Status</dt>
<dd id="usageStatus">-</dd>
</div>
<div>
<dt>Aktiver Benutzer</dt>
<dd id="activeBy">-</dd>
</div>
<div>
<dt>Seit</dt>
<dd id="startedAt">-</dd>
</div>
<div>
<dt>Bis</dt>
<dd id="endsAt">-</dd>
</div>
<div>
<dt>Restzeit</dt>
<dd id="remainingUsage">-</dd>
</div>
</dl>
<div class="actions" id="stationActions">
<button id="activateBtn" type="button">Station aktivieren</button>
<button id="deactivateBtn" type="button" class="danger">Station deaktivieren</button>
<button id="refreshBtn" type="button" class="ghost-btn">Aktualisieren</button>
</div>
<section id="activationProgress" class="progress-wrap" hidden>
<div class="progress-head">
<strong>SWR-Check laeuft</strong>
<span id="progressText">0%</span>
</div>
<div class="progress-track" aria-hidden="true">
<div id="progressFill" class="progress-fill"></div>
</div>
<p id="progressEta" class="muted">Geschaetzte Restzeit: -</p>
</section>
<section id="openwebrxPanel" class="links-wrap" hidden>
<div class="section-head">
<h3>OpenWebRX</h3>
</div>
<div class="actions">
<button id="openwebrxOpenBtn" type="button" class="ghost-btn">OpenWebRX laden</button>
<a id="openwebrxSessionLink" class="link-btn primary-btn" target="_blank" rel="noopener noreferrer" hidden>SDR oeffnen</a>
</div>
<p id="openwebrxMessage" class="message"></p>
<div id="openwebrxSessionAccess" class="stack" hidden>
<p class="muted">Session Key (Ticket): <code id="openwebrxSessionTicket">-</code></p>
</div>
</section>
<section id="reservationPanel" class="links-wrap" hidden>
<div class="section-head">
<h3>Reservierungen</h3>
</div>
<div class="actions">
<button id="reserveNextBtn" type="button" class="ghost-btn">Naechsten Slot reservieren</button>
</div>
<div id="reservationList" class="stack"></div>
<p id="reservationMessage" class="message"></p>
</section>
<section id="controlsPanel" class="links-wrap" hidden>
<div class="section-head">
<h3>Controls</h3>
<span id="openwebrxTxStatePill" class="pill">TX: unbekannt</span>
</div>
<div class="controls-grid">
<div class="rotor-layout">
<div class="rotor-main">
<p id="rotorCurrent" class="muted controls-inline">Rotor: -</p>
<div class="actions controls-inline">
<input id="rotorTarget" type="number" min="0" max="360" step="1" placeholder="Azimuth (0-360)" />
<button id="rotorSetBtn" type="button" class="ghost-btn">Rotor setzen</button>
</div>
<div id="rotorPresets" class="actions controls-inline">
<button type="button" class="ghost-btn" data-azimuth="0">N</button>
<button type="button" class="ghost-btn" data-azimuth="45">NE</button>
<button type="button" class="ghost-btn" data-azimuth="90">E</button>
<button type="button" class="ghost-btn" data-azimuth="135">SE</button>
<button type="button" class="ghost-btn" data-azimuth="180">S</button>
<button type="button" class="ghost-btn" data-azimuth="225">SW</button>
<button type="button" class="ghost-btn" data-azimuth="270">W</button>
<button type="button" class="ghost-btn" data-azimuth="315">NW</button>
</div>
</div>
<div id="rotorCompass" class="rotor-compass" aria-label="Rotor Kompass">
<span class="rotor-mark rotor-mark-n">0</span>
<span class="rotor-mark rotor-mark-e">90</span>
<span class="rotor-mark rotor-mark-s">180</span>
<span class="rotor-mark rotor-mark-w">270</span>
<div id="rotorCompassArrow" class="rotor-arrow" aria-hidden="true"></div>
<div class="rotor-center" aria-hidden="true"></div>
</div>
</div>
</div>
<p id="controlsMessage" class="message"></p>
</section>
<p id="statusMessage" class="message"></p>
</article>
<article class="card stagger" id="swrSummaryCard">
<div class="section-head">
<h2>SWR Uebersicht</h2>
<button id="refreshSwrBtn" type="button" class="ghost-btn">Neu laden</button>
</div>
<p class="muted" id="swrSummaryGeneratedAt">Stand: -</p>
<p class="muted" id="swrSummaryOverall">Gesamt: -</p>
<div id="swrSummaryBands" class="stack"></div>
<div class="actions">
<button id="runSwrCheckBtn" type="button">SWR Test starten</button>
</div>
<p id="swrSummaryMessage" class="message"></p>
</article>
</section>
<section class="grid-layout" id="pageSwr" hidden>
<article class="card admin-card stagger">
<div class="section-head swr-page-head">
<h2>SWR Test-Daten</h2>
<div class="swr-page-actions">
<div class="actions">
<button id="runSwrCheckPageBtn" type="button">SWR Test starten</button>
<button id="refreshSwrPageBtn" type="button" class="ghost-btn">Neu laden</button>
</div>
<p id="swrPageMessage" class="message swr-page-head-message"></p>
</div>
</div>
<section id="activationProgressSwr" class="progress-wrap" hidden>
<div class="progress-head">
<strong>SWR-Check laeuft</strong>
<span id="progressTextSwr">0%</span>
</div>
<div class="progress-track" aria-hidden="true">
<div id="progressFillSwr" class="progress-fill"></div>
</div>
<p id="progressEtaSwr" class="muted">Geschaetzte Restzeit: -</p>
</section>
<p id="swrPageGeneratedAt" class="muted">Stand: -</p>
<p id="swrPageOverall" class="muted">Gesamt: -</p>
<div id="swrPageBands" class="stack"></div>
</article>
</section>
<section class="grid-layout" id="pageUser" hidden>
<article class="card stagger" id="userSettingsCard">
<h2>Benutzer-Einstellungen</h2>
<p class="muted">Hier findest du nur deine persoenlichen Einstellungen.</p>
<dl class="status-grid">
<div>
<dt>E-Mail</dt>
<dd id="settingsEmail">-</dd>
</div>
<div>
<dt>Rolle</dt>
<dd id="settingsRole">-</dd>
</div>
</dl>
<label class="field" style="margin-top: 0.6rem">
<span>Praeferierte Authentifizierung</span>
<select id="settingsAuthMethodSelect"></select>
</label>
<label class="field" style="margin-top: 0.6rem">
<span>Sprache</span>
<select id="settingsLanguageSelect"></select>
</label>
<div class="actions">
<button id="settingsSaveAuthMethodBtn" type="button">Authentifizierung speichern</button>
<button id="settingsSaveLanguageBtn" type="button" class="ghost-btn">Sprache speichern</button>
<button id="settingsThemeBtn" type="button">Theme wechseln</button>
<button id="settingsRefreshBtn" type="button" class="ghost-btn">Status neu laden</button>
</div>
</article>
</section>
<section class="grid-layout" id="pageHelp" hidden>
<article class="card admin-card stagger" id="helpCard">
<div class="section-head">
<h2 id="helpTitle">Hilfe</h2>
<button id="refreshHelpBtn" type="button" class="ghost-btn">Neu laden</button>
</div>
<section class="plugin-block">
<h3 id="helpQuickStartTitle">Schnellstart</h3>
<ol id="helpQuickStartSteps" class="stack"></ol>
</section>
<div id="helpSections" class="stack"></div>
<p id="helpMessage" class="message"></p>
</article>
</section>
<section class="grid-layout" id="pagePlugins" hidden>
<article class="card admin-card stagger" id="pluginControlsCard">
<div class="section-head">
<h2>Plugin Controls</h2>
<span class="pill">Dynamisch</span>
</div>
<div id="pluginControls" class="stack"></div>
<p id="pluginMessage" class="message"></p>
</article>
</section>
<section class="grid-layout" id="pagePluginConfig" hidden>
<article class="card admin-card stagger" id="pluginsConfigCard" hidden>
<div class="section-head">
<h2>Plugin Konfiguration</h2>
<button id="refreshPluginsPageBtn" type="button" class="ghost-btn">Neu laden</button>
</div>
<h3>Plugin Verwaltung</h3>
<div id="pluginsAdminConfig" class="stack"></div>
</article>
</section>
<section class="grid-layout" id="pageProviders" hidden>
<article class="card admin-card stagger" id="providersCard">
<div class="section-head">
<h2>Provider Verwaltung</h2>
<button id="refreshProvidersBtn" type="button" class="ghost-btn">Neu laden</button>
</div>
<h3>Capability Matrix</h3>
<div id="providersCapabilityMatrix" class="stack"></div>
<hr class="separator" />
<h3>Provider Zuordnung</h3>
<div id="providersAdminConfig" class="stack"></div>
<p id="providersMessage" class="message"></p>
</article>
</section>
<section class="grid-layout" id="pageAdmin" hidden>
<article class="card admin-card stagger" id="adminCard" hidden>
<div class="section-head">
<h2>Admin</h2>
<span class="pill">Steuerung</span>
</div>
<div class="actions">
<button id="setOnlineBtn" type="button">Online setzen</button>
<button id="setOfflineBtn" type="button" class="danger">Offline setzen</button>
<button id="forceReleaseBtn" type="button" class="ghost-btn">Force Release</button>
<button id="refreshAuditBtn" type="button" class="ghost-btn">Audit laden</button>
</div>
<div class="stack" style="margin-top: 1rem">
<label class="field">
<span>Benutzerrolle aendern</span>
<input id="roleEmail" type="email" placeholder="user@example.com" />
</label>
<div class="actions">
<button id="setRoleAdminBtn" type="button">Als Admin setzen</button>
<button id="setRoleOperatorBtn" type="button" class="ghost-btn">Als Operator setzen</button>
</div>
</div>
<p id="adminMessage" class="message"></p>
<pre id="auditLog" class="audit-log"></pre>
<hr class="separator" />
<div class="section-head">
<h3>Branding</h3>
<span class="pill">Logo Upload</span>
</div>
<div class="stack" style="margin-top: 0.6rem">
<label class="field">
<span>Logo Light Theme</span>
<input id="logoLightFile" type="file" accept=".png,.jpg,.jpeg,.svg,.webp,image/png,image/jpeg,image/svg+xml,image/webp" />
</label>
<div class="actions">
<button id="uploadLogoLightBtn" type="button" class="ghost-btn">Light-Logo hochladen</button>
<button id="removeLogoLightBtn" type="button" class="danger">Light-Logo entfernen</button>
</div>
<label class="field">
<span>Logo Dark Theme</span>
<input id="logoDarkFile" type="file" accept=".png,.jpg,.jpeg,.svg,.webp,image/png,image/jpeg,image/svg+xml,image/webp" />
</label>
<div class="actions">
<button id="uploadLogoDarkBtn" type="button" class="ghost-btn">Dark-Logo hochladen</button>
<button id="removeLogoDarkBtn" type="button" class="danger">Dark-Logo entfernen</button>
</div>
</div>
<hr class="separator" />
<div class="section-head">
<h3>Wartungsmodus</h3>
<span class="pill" id="maintenanceStatePill">Unbekannt</span>
</div>
<label class="field" style="margin-top: 0.6rem">
<span>Hinweistext</span>
<input id="maintenanceMessageInput" type="text" placeholder="Wartungsmodus aktiv. Login ist derzeit deaktiviert." />
</label>
<div class="actions">
<button id="maintenanceEnableBtn" type="button" class="danger">Wartungsmodus aktivieren</button>
<button id="maintenanceDisableBtn" type="button" class="ghost-btn">Wartungsmodus deaktivieren</button>
</div>
</article>
</section>
<section class="grid-layout" id="pageUsers" hidden>
<article class="card admin-card stagger">
<div class="section-head">
<h2>Benutzerverwaltung</h2>
<div class="actions">
<label class="field compact-field">
<span>Suche</span>
<input id="usersFilterQuery" type="text" placeholder="mail@domain" />
</label>
<label class="field compact-field">
<span>Rolle</span>
<select id="usersFilterRole">
<option value="all">Alle</option>
<option value="admin">Admin</option>
<option value="approver">Approver</option>
<option value="operator">Operator</option>
</select>
</label>
<label class="field compact-field">
<span>Status</span>
<select id="usersFilterStatus">
<option value="all">Alle</option>
<option value="active">Aktiv</option>
<option value="pending_approval">Pending Approval</option>
<option value="pending_verification">Pending Verification</option>
<option value="denied">Denied</option>
</select>
</label>
<button id="refreshUsersBtn" type="button" class="ghost-btn">Neu laden</button>
</div>
</div>
<div id="usersAdmin" class="stack"></div>
<p id="usersMessage" class="message"></p>
</article>
</section>
<section class="grid-layout" id="pageApprovals" hidden>
<article class="card admin-card stagger">
<div class="section-head">
<h2>Freigaben</h2>
<div class="actions">
<label class="field compact-field">
<span>Suche</span>
<input id="approvalsFilterQuery" type="text" placeholder="mail@domain" />
</label>
<label class="field compact-field">
<span>Status</span>
<select id="approvalsFilterStatus">
<option value="all">Alle</option>
<option value="pending">Pending</option>
<option value="approved">Approved</option>
<option value="rejected">Rejected</option>
</select>
</label>
<button id="approvalsFilterOpenBtn" type="button" class="ghost-btn">Nur offen</button>
<button id="approvalsFilterAllBtn" type="button" class="ghost-btn">Alle</button>
<button id="refreshApprovalsBtn" type="button" class="ghost-btn">Neu laden</button>
</div>
</div>
<div id="approvalsList" class="stack"></div>
<p id="approvalsMessage" class="message"></p>
</article>
</section>
<section class="grid-layout" id="pageActivity" hidden>
<article class="card admin-card stagger">
<div class="section-head">
<h2>Aktivitaetslog</h2>
<div class="actions">
<label class="field compact-field">
<span>Suche</span>
<input id="activityFilterQuery" type="text" placeholder="mail oder text" />
</label>
<label class="field compact-field">
<span>Typ</span>
<select id="activityFilterType">
<option value="all">Alle</option>
<option value="auth.request_access">Link angefordert</option>
<option value="station.activate.start">Start Aktivierung</option>
<option value="station.activate.done">Aktivierung ok</option>
<option value="station.activate.failed">Aktivierung Fehler</option>
<option value="station.deactivate">Manuell beendet</option>
<option value="station.deactivate.timeout">Automatisch beendet</option>
</select>
</label>
<button id="refreshActivityBtn" type="button" class="ghost-btn">Neu laden</button>
</div>
</div>
<div id="activityLogList" class="stack"></div>
<p id="activityMessage" class="message"></p>
</article>
</section>
</section>
<nav id="mobileNav" class="mobile-nav" hidden>
<button id="mobileNavRms" type="button">📡 RMS</button>
<button id="mobileNavSwr" type="button">📈 SWR</button>
<button id="mobileNavUser" type="button" hidden>⚙ Einst.</button>
<button id="mobileNavHelp" type="button">❓ Hilfe</button>
<button id="mobileNavPlugins" type="button">🧩 Plugins</button>
<button id="mobileNavPluginConfig" type="button" hidden>⚙ PluginCfg</button>
<button id="mobileNavUsers" type="button" hidden>👥 User</button>
<button id="mobileNavApprovals" type="button" hidden>✅ Freigaben</button>
<button id="mobileNavActivity" type="button" hidden>📜 Log</button>
<button id="mobileNavAdmin" type="button" hidden>🛠 Admin</button>
</nav>
</main>
<script src="/app.js" defer></script>
</body>
</html>

865
public/styles.css Normal file
View File

@@ -0,0 +1,865 @@
:root {
--font-display: "Montserrat", "Segoe UI", sans-serif;
--font-body: "Source Sans 3", "Trebuchet MS", sans-serif;
--radius: 20px;
--shadow: 0 18px 40px rgba(0, 0, 0, 0.28);
--transition: 200ms ease;
}
html[data-theme="dark"] {
--bg-0: #071623;
--bg-1: #0d2d45;
--bg-2: #194766;
--surface: rgba(8, 23, 36, 0.76);
--surface-border: rgba(164, 210, 236, 0.24);
--text: #e8f5ff;
--muted: #a4c1d4;
--primary: #30b6f7;
--primary-ink: #042032;
--danger: #f76e6e;
--ok: #69dca7;
}
html[data-theme="light"] {
--bg-0: #f2f8ff;
--bg-1: #d5e8f8;
--bg-2: #bfd9ea;
--surface: rgba(255, 255, 255, 0.85);
--surface-border: rgba(19, 60, 92, 0.16);
--text: #0a2235;
--muted: #4f6a7e;
--primary: #0077b6;
--primary-ink: #ffffff;
--danger: #c43f3f;
--ok: #1f8f63;
--shadow: 0 14px 36px rgba(9, 33, 52, 0.16);
}
* {
box-sizing: border-box;
}
[hidden] {
display: none !important;
}
body {
margin: 0;
min-height: 100vh;
font-family: var(--font-body);
color: var(--text);
background: radial-gradient(circle at 20% 12%, var(--bg-2), var(--bg-0) 42%),
linear-gradient(140deg, var(--bg-1), var(--bg-0));
}
.page-bg {
position: fixed;
inset: 0;
pointer-events: none;
background-image: linear-gradient(120deg, transparent 0 48%, rgba(255, 255, 255, 0.05) 48% 52%, transparent 52% 100%),
radial-gradient(circle at 86% 18%, rgba(255, 255, 255, 0.08), transparent 45%);
}
.app-shell {
position: relative;
max-width: 1100px;
margin: 0 auto;
padding: 2rem 1.2rem 2.8rem;
}
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
margin-bottom: 1.4rem;
}
.topbar-actions {
display: flex;
gap: 0.6rem;
align-items: center;
position: relative;
}
.user-menu {
position: absolute;
top: calc(100% + 0.4rem);
right: 7.2rem;
min-width: 180px;
border: 1px solid var(--surface-border);
border-radius: 12px;
background: var(--surface);
backdrop-filter: blur(10px);
box-shadow: var(--shadow);
padding: 0.45rem;
display: grid;
gap: 0.35rem;
z-index: 20;
}
.language-menu {
position: absolute;
top: calc(100% + 0.4rem);
right: 3.8rem;
min-width: 130px;
border: 1px solid var(--surface-border);
border-radius: 12px;
background: var(--surface);
backdrop-filter: blur(10px);
box-shadow: var(--shadow);
padding: 0.45rem;
z-index: 20;
}
.language-menu select {
width: 100%;
}
.user-menu button {
width: 100%;
text-align: left;
}
.menu-separator {
border: 0;
border-top: 1px solid var(--surface-border);
margin: 0.15rem 0;
}
.user-menu button.active {
border: 1px solid var(--surface-border);
background: color-mix(in srgb, var(--primary), transparent 78%);
}
.brand {
display: flex;
gap: 1rem;
align-items: center;
}
.brand-home-link {
display: inline-flex;
color: inherit;
text-decoration: none;
}
.brand-mark {
font-family: var(--font-display);
font-weight: 800;
letter-spacing: 0.12em;
border: 1px solid var(--surface-border);
border-radius: 12px;
padding: 0.45rem;
background: var(--surface);
backdrop-filter: blur(8px);
width: 72px;
height: 72px;
display: grid;
place-items: center;
overflow: hidden;
}
.brand-mark img {
width: 100%;
height: 100%;
object-fit: contain;
}
.brand-mark span {
font-family: var(--font-display);
font-weight: 800;
letter-spacing: 0.12em;
}
h1,
h2 {
margin: 0;
font-family: var(--font-display);
letter-spacing: 0.01em;
}
h3 {
margin: 0;
font-family: var(--font-display);
font-size: 1rem;
}
h1 {
font-size: clamp(1.2rem, 1.6vw + 1rem, 2rem);
}
h2 {
font-size: 1.4rem;
}
.eyebrow {
margin: 0;
text-transform: uppercase;
letter-spacing: 0.15em;
font-size: 0.7rem;
color: var(--muted);
}
#pageCrumb {
display: inline-block;
margin-top: 0.35rem;
padding: 0.16rem 0.42rem;
border: 1px solid var(--surface-border);
border-radius: 999px;
letter-spacing: 0.09em;
}
.grid-layout {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1.2rem;
}
.view[hidden] {
display: none;
}
#rmsView {
display: grid;
gap: 1rem;
}
.page-enter {
animation: fade-up 260ms ease;
}
.page-overview {
padding: 0.85rem 1rem;
}
.mobile-nav {
position: fixed;
left: 0.7rem;
right: 0.7rem;
bottom: 0.7rem;
display: none;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.45rem;
padding: 0.5rem;
border: 1px solid var(--surface-border);
border-radius: 14px;
background: color-mix(in srgb, var(--surface), transparent 5%);
backdrop-filter: blur(12px);
box-shadow: var(--shadow);
z-index: 30;
}
.mobile-nav.admin-visible {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.mobile-nav.swr-visible {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.mobile-nav.approvals-visible {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.mobile-nav.users-visible {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.mobile-nav.activity-visible {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.mobile-nav.admin-visible.approvals-visible {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.mobile-nav.users-visible.approvals-visible,
.mobile-nav.users-visible.admin-visible,
.mobile-nav.users-visible.activity-visible,
.mobile-nav.users-visible.swr-visible,
.mobile-nav.approvals-visible.activity-visible,
.mobile-nav.admin-visible.activity-visible,
.mobile-nav.swr-visible.activity-visible,
.mobile-nav.swr-visible.admin-visible,
.mobile-nav.swr-visible.approvals-visible {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.mobile-nav.users-visible.admin-visible.approvals-visible {
grid-template-columns: repeat(6, minmax(0, 1fr));
}
.mobile-nav.swr-visible.users-visible.admin-visible.approvals-visible,
.mobile-nav.swr-visible.users-visible.admin-visible.activity-visible,
.mobile-nav.swr-visible.users-visible.approvals-visible.activity-visible,
.mobile-nav.swr-visible.admin-visible.approvals-visible.activity-visible {
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.mobile-nav.users-visible.admin-visible.approvals-visible.activity-visible {
grid-template-columns: repeat(7, minmax(0, 1fr));
}
.mobile-nav.swr-visible.users-visible.admin-visible.approvals-visible.activity-visible {
grid-template-columns: repeat(8, minmax(0, 1fr));
}
.mobile-nav button.active {
border: 1px solid var(--surface-border);
background: color-mix(in srgb, var(--primary), transparent 78%);
}
.actions .ghost-btn.active {
border-color: var(--primary);
background: color-mix(in srgb, var(--primary), transparent 84%);
}
.pill.approval-pending {
color: #5b3d00;
background: #ffe9b8;
border: 1px solid #f0bf63;
}
.pill.approval-approved {
color: #0e4f2f;
background: #d9f5e6;
border: 1px solid #81d3a8;
}
.pill.approval-rejected {
color: #6a1212;
background: #ffdede;
border: 1px solid #ee9696;
}
.swr-band-image {
width: 100%;
max-width: 760px;
border-radius: 10px;
border: 1px solid var(--surface-border);
display: block;
}
.auth-view {
display: grid;
place-items: center;
min-height: min(72vh, 760px);
}
.compact-field {
min-width: 9.5rem;
}
.compact-field input,
.compact-field select {
min-width: 9.5rem;
}
.login-card {
width: min(100%, 560px);
}
.card {
border-radius: var(--radius);
border: 1px solid var(--surface-border);
background: var(--surface);
backdrop-filter: blur(14px);
box-shadow: var(--shadow);
padding: 1.2rem;
}
.muted {
margin-top: 0.45rem;
color: var(--muted);
}
.stack {
margin-top: 1rem;
display: grid;
gap: 0.8rem;
}
.field {
display: grid;
gap: 0.35rem;
}
.field span {
color: var(--muted);
}
input {
border: 1px solid var(--surface-border);
background: rgba(255, 255, 255, 0.06);
color: var(--text);
border-radius: 12px;
padding: 0.7rem 0.8rem;
font: inherit;
}
select {
border: 1px solid var(--surface-border);
background: rgba(255, 255, 255, 0.06);
color: var(--text);
border-radius: 12px;
padding: 0.7rem 0.8rem;
font: inherit;
}
button {
border: none;
border-radius: 12px;
background: var(--primary);
color: var(--primary-ink);
padding: 0.66rem 0.9rem;
font-family: var(--font-display);
font-weight: 700;
cursor: pointer;
transition: transform var(--transition), opacity var(--transition), filter var(--transition);
}
button:hover {
transform: translateY(-1px);
filter: brightness(1.05);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.ghost-btn {
background: transparent;
border: 1px solid var(--surface-border);
color: var(--text);
}
.primary-btn {
background: var(--primary);
border-color: transparent;
color: var(--primary-ink);
}
.danger {
background: var(--danger);
color: #fff;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 0.55rem;
margin-top: 0.5rem;
}
.link-btn {
border: none;
border-radius: 12px;
padding: 0.66rem 0.9rem;
font-family: var(--font-display);
font-weight: 700;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform var(--transition), opacity var(--transition), filter var(--transition);
}
.link-btn:hover {
transform: translateY(-1px);
filter: brightness(1.05);
}
.progress-wrap,
.links-wrap {
margin-top: 0.9rem;
border: 1px solid var(--surface-border);
border-radius: 14px;
padding: 0.75rem;
background: rgba(255, 255, 255, 0.04);
}
.controls-grid {
margin-top: 0.7rem;
display: grid;
gap: 0.45rem;
}
.rotor-layout {
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
align-items: center;
justify-content: space-between;
}
.rotor-main {
flex: 1 1 360px;
min-width: 260px;
}
.rotor-compass {
position: relative;
width: 170px;
height: 170px;
border-radius: 999px;
border: 1px solid var(--surface-border);
background: radial-gradient(circle at 35% 30%, rgba(255,255,255,0.1), rgba(15,23,42,0.88) 65%);
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05);
flex: 0 0 auto;
}
.rotor-mark {
position: absolute;
color: #cbd5e1;
font-size: 12px;
font-weight: 700;
}
.rotor-mark-n {
top: 6px;
left: 50%;
transform: translateX(-50%);
}
.rotor-mark-e {
top: 50%;
right: 6px;
transform: translateY(-50%);
}
.rotor-mark-s {
bottom: 6px;
left: 50%;
transform: translateX(-50%);
}
.rotor-mark-w {
top: 50%;
left: 6px;
transform: translateY(-50%);
}
.rotor-arrow {
position: absolute;
left: 50%;
top: 50%;
width: 5px;
height: 66px;
border-radius: 5px;
transform-origin: 50% 100%;
transform: translate(-50%, -100%) rotate(0deg);
transition: transform 220ms ease-out;
background: linear-gradient(180deg, #ef4444 0%, #ef4444 56%, #334155 56%, #334155 100%);
}
.rotor-center {
position: absolute;
left: 50%;
top: 50%;
width: 10px;
height: 10px;
border-radius: 999px;
transform: translate(-50%, -50%);
background: #e5e7eb;
}
.controls-inline {
margin: 0;
}
#rotorTarget {
width: min(220px, 100%);
}
.progress-head {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.9rem;
}
.progress-track {
margin-top: 0.45rem;
width: 100%;
height: 10px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
overflow: hidden;
}
.progress-fill {
width: 0;
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, color-mix(in srgb, var(--primary), #fff 6%), var(--ok));
transition: width 450ms ease;
}
.section-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
}
.swr-page-head {
align-items: flex-start;
}
.swr-page-actions {
display: grid;
justify-items: end;
gap: 0.35rem;
}
.swr-page-head-message {
margin: 0;
min-height: 1.1rem;
text-align: right;
max-width: 34ch;
}
.pill {
font-size: 0.78rem;
padding: 0.34rem 0.58rem;
border-radius: 999px;
border: 1px solid var(--surface-border);
color: var(--muted);
}
.pill.ok {
color: var(--ok);
border-color: color-mix(in srgb, var(--ok), transparent 55%);
background: color-mix(in srgb, var(--ok), transparent 88%);
}
.pill.offline {
color: var(--danger);
border-color: color-mix(in srgb, var(--danger), transparent 55%);
background: color-mix(in srgb, var(--danger), transparent 88%);
}
.status-grid {
margin: 0.9rem 0 0;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.8rem;
}
.admin-card {
grid-column: 1 / -1;
}
.audit-log {
margin: 0.8rem 0 0;
border-radius: 12px;
border: 1px solid var(--surface-border);
background: rgba(0, 0, 0, 0.12);
color: var(--muted);
padding: 0.7rem;
min-height: 120px;
max-height: 250px;
overflow: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.8rem;
}
.separator {
border: 0;
border-top: 1px solid var(--surface-border);
margin: 1rem 0;
}
.plugin-block {
border: 1px solid var(--surface-border);
border-radius: 12px;
padding: 0.7rem;
background: rgba(255, 255, 255, 0.03);
}
.swr-summary-list {
margin-top: 0.6rem;
display: grid;
gap: 0.4rem;
}
.swr-summary-row {
border: 1px solid var(--surface-border);
border-radius: 10px;
padding: 0.42rem 0.6rem;
background: rgba(255, 255, 255, 0.03);
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
}
.reservation-row {
align-items: flex-start;
}
.reservation-row-active {
border-color: color-mix(in srgb, var(--ok), transparent 55%);
background: color-mix(in srgb, var(--ok), transparent 90%);
}
.swr-summary-link {
width: 100%;
text-align: left;
color: inherit;
background: rgba(255, 255, 255, 0.03);
cursor: pointer;
}
.swr-summary-link:hover {
border-color: color-mix(in srgb, var(--primary), var(--surface-border) 45%);
}
.swr-summary-link:focus-visible {
outline: 2px solid color-mix(in srgb, var(--primary), #fff 20%);
outline-offset: 1px;
}
.matrix-row {
border: 1px solid var(--surface-border);
border-radius: 12px;
padding: 0.6rem;
background: rgba(255, 255, 255, 0.02);
}
.health-badge {
font-size: 0.74rem;
border: 1px solid var(--surface-border);
border-radius: 999px;
padding: 0.2rem 0.5rem;
}
.health-healthy {
color: var(--ok);
}
.health-degraded,
.health-failing {
color: var(--danger);
}
.schema-form {
display: grid;
gap: 0.45rem;
margin-top: 0.5rem;
}
.plugin-eq-builder {
border: 1px solid var(--surface-border);
border-radius: 10px;
padding: 0.65rem;
display: grid;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.02);
}
.plugin-eq-row {
display: grid;
grid-template-columns: 130px 1fr 88px;
gap: 0.5rem;
align-items: center;
}
.plugin-eq-checkbox {
display: flex;
align-items: center;
gap: 0.5rem;
}
dt {
color: var(--muted);
font-size: 0.85rem;
}
dd {
margin: 0.2rem 0 0;
font-size: 1.03rem;
font-weight: 600;
}
.message {
min-height: 1.2rem;
margin: 0.5rem 0 0;
color: var(--muted);
}
.message.error {
color: var(--danger);
}
.message.ok {
color: var(--ok);
}
.reveal {
animation: fade-up 500ms ease;
}
.stagger {
opacity: 0;
animation: fade-up 600ms ease forwards;
}
.stagger:nth-child(2) {
animation-delay: 90ms;
}
@keyframes fade-up {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 840px) {
.grid-layout {
grid-template-columns: 1fr;
}
.topbar {
flex-direction: column;
align-items: flex-start;
}
.topbar-actions {
width: 100%;
}
.actions button {
width: 100%;
}
.matrix-row .actions {
flex-direction: column;
align-items: stretch;
}
.health-badge {
width: 100%;
}
.plugin-block .actions button {
width: 100%;
}
.app-shell {
padding-bottom: 6rem;
}
.mobile-nav {
display: grid;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,204 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="141.31099mm"
height="141.31099mm"
viewBox="0 0 141.31099 141.31099"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="arcg-logo.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.88936526"
inkscape:cx="151.4169"
inkscape:cy="267.04439"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1250"
inkscape:window-height="656"
inkscape:window-x="50"
inkscape:window-y="75"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:measure-start="0,0"
inkscape:measure-end="0,0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-134.59789,-71.587331)">
<g
id="g1271"
inkscape:export-filename="/home/jue/afu/arcg/arcgraute_tmp01.png"
inkscape:export-xdpi="179.75"
inkscape:export-ydpi="179.75"
transform="matrix(0.88421052,0,0,0.88421052,92.29501,17.065215)">
<path
d="m 135.10457,142.23043 30.76174,-69.460881 30.73758,69.460881 -30.73758,69.48603 -30.76174,-69.48603"
style="fill:#008000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.03350952"
id="path1058"
inkscape:connector-curvature="0" />
<path
d="m 134.67039,142.42378 -0.0725,-0.19335 0.0725,-0.19268 0.43418,0.19268 z m 30.7616,69.48536 -30.7616,-69.48536 0.86849,-0.38603 30.76161,69.50946 z m 0.8685,0 -0.43418,0.98918 -0.43432,-0.98918 0.43432,-0.19268 z m 30.73768,-69.48536 -30.73768,69.48536 -0.8685,-0.36193 30.73761,-69.50946 z m 0,-0.38603 0.0965,0.19268 -0.0965,0.19335 -0.43428,-0.19335 z m -30.73768,-69.461216 30.73768,69.461216 -0.86857,0.38603 -30.73761,-69.461217 z m -0.8685,0 0.43432,-0.989201 0.43418,0.989201 -0.43418,0.193015 z m -30.7616,69.461216 30.7616,-69.461216 0.8685,0.386029 -30.76161,69.461217 -0.86849,-0.38603 v 0"
style="fill:#fffffa;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1060"
inkscape:connector-curvature="0" />
<path
d="m 139.42324,142.73743 -0.21714,-0.507 0.21714,-0.506 1.15806,0.506 z m 25.28491,57.10826 -25.28491,-57.10826 2.31621,-1.013 25.28491,57.10787 z m 2.31621,0 -1.15805,2.63013 -1.15816,-2.63013 1.15816,-0.5067 z m 25.26082,-57.10826 -25.26082,57.10826 -2.31621,-1.01339 25.26085,-57.10787 z m 0,-1.013 0.21714,0.506 -0.21714,0.507 -1.15809,-0.507 z m -25.26082,-57.084469 25.26082,57.084469 -2.31618,1.013 -25.26085,-57.084141 z m -2.31621,0 1.15816,-2.6057 1.15805,2.6057 -1.15805,0.506664 z m -25.28491,57.084469 25.28491,-57.084469 2.31621,1.013328 -25.28491,57.084141 z"
style="fill:none;stroke:none;stroke-width:0.3350952;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1088"
inkscape:connector-curvature="0" />
<path
d="m 141.16223,142.10724 24.702,-65.657578 24.68259,65.657578 -24.68259,65.68134 -24.702,-65.68134"
style="fill:#ffcc00;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.61480898;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1058-9"
inkscape:connector-curvature="0" />
<g
id="g1032">
<polyline
points="3070,2834 2125,2834 "
style="fill:#cccccc;stroke:#000000;stroke-width:105.84320831;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:211.68641468, 105.84320734;stroke-dashoffset:270.95861816"
id="polyline4502"
transform="matrix(0.01902033,0,0,0.01520551,116.33422,99.444301)" />
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#000000;stroke-width:1.29999995;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none"
d="m 156.67784,159.25402 a 9.0765778,10.780707 0 1 0 18.15315,0"
id="path4506" />
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#000000;stroke-width:1.29999995;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none"
d="m 156.67784,124.34767 a 9.0765778,10.780707 0 0 1 18.15315,0"
id="path4508" />
<polyline
points="1889,3307 1889,2125 "
style="fill:none;stroke:#000000;stroke-width:102.72575378;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none"
id="polyline4510"
transform="matrix(0.01280194,0,0,0.02960912,132.49496,61.428288)" />
<polyline
points="3307,2125 3307,3307 "
style="fill:none;stroke:#000000;stroke-width:102.72575378;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none"
id="polyline4512"
transform="matrix(0.01280194,0,0,0.02960912,132.49496,61.428288)" />
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#000000;stroke-width:1.29999995;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none"
d="m 158.14208,164.94334 c 5.03095,-6.62459 10.32402,-6.93226 15.35385,-0.20629"
id="path4516"
sodipodi:nodetypes="cc" />
<polyline
points="2598,944 2598,2125 "
style="fill:none;stroke:#000000;stroke-width:112.40422821;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none"
id="polyline4520"
transform="matrix(0.01280194,0,0,0.0104483,132.49496,103.78489)" />
<polyline
points="2125,2125 3070,2125 "
style="fill:none;stroke:#000000;stroke-width:116.47023773;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none"
id="polyline4526"
transform="matrix(0.01280194,0,0,0.01520551,132.49496,94.152631)" />
</g>
<text
id="text960"
y="105.75536"
x="160.85648"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:Times;-inkscape-font-specification:Times;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.69999981px;font-family:Serif;-inkscape-font-specification:'Serif Bold';stroke-width:0.26458332px"
y="105.75536"
x="160.85648"
id="tspan958"
sodipodi:role="line">A</tspan></text>
<text
id="text960-6"
y="145.59193"
x="144.0618"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:Times;-inkscape-font-specification:Times;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.69999981px;font-family:Serif;-inkscape-font-specification:'Serif Bold';stroke-width:0.26458332px"
y="145.59193"
x="144.0618"
id="tspan958-7"
sodipodi:role="line">R</tspan></text>
<text
id="text960-6-5"
y="145.41217"
x="176.67909"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:Times;-inkscape-font-specification:Times;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.69999981px;font-family:Serif;-inkscape-font-specification:'Serif Bold';stroke-width:0.26458332px"
y="145.41217"
x="176.67909"
id="tspan958-7-3"
sodipodi:role="line">C</tspan></text>
<text
id="text960-6-5-5"
y="185.46437"
x="160.06544"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:Times;-inkscape-font-specification:Times;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.69999981px;font-family:Serif;-inkscape-font-specification:'Serif Bold';stroke-width:0.26458332px"
y="185.46437"
x="160.06544"
id="tspan958-7-3-6"
sodipodi:role="line">G</tspan></text>
</g>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:15.91838646px;line-height:125%;font-family:Serif;-inkscape-font-specification:'Serif Bold';text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.39795968px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="141.77927"
y="116.03462"
id="text839"><tspan
sodipodi:role="line"
id="tspan837"
x="141.77927"
y="116.03462"
style="stroke-width:0.39795968px">Amateur</tspan><tspan
sodipodi:role="line"
x="141.77927"
y="135.9326"
style="stroke-width:0.39795968px"
id="tspan841">Radio</tspan><tspan
sodipodi:role="line"
x="141.77927"
y="155.83058"
style="stroke-width:0.39795968px"
id="tspan843">Club</tspan><tspan
sodipodi:role="line"
x="141.77927"
y="175.72858"
style="stroke-width:0.39795968px"
id="tspan845">Graz</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,204 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="141.31099mm"
height="141.31099mm"
viewBox="0 0 141.31099 141.31099"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="arcg-logo.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.88936526"
inkscape:cx="151.4169"
inkscape:cy="267.04439"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1250"
inkscape:window-height="656"
inkscape:window-x="50"
inkscape:window-y="75"
inkscape:window-maximized="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:measure-start="0,0"
inkscape:measure-end="0,0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-134.59789,-71.587331)">
<g
id="g1271"
inkscape:export-filename="/home/jue/afu/arcg/arcgraute_tmp01.png"
inkscape:export-xdpi="179.75"
inkscape:export-ydpi="179.75"
transform="matrix(0.88421052,0,0,0.88421052,92.29501,17.065215)">
<path
d="m 135.10457,142.23043 30.76174,-69.460881 30.73758,69.460881 -30.73758,69.48603 -30.76174,-69.48603"
style="fill:#008000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.03350952"
id="path1058"
inkscape:connector-curvature="0" />
<path
d="m 134.67039,142.42378 -0.0725,-0.19335 0.0725,-0.19268 0.43418,0.19268 z m 30.7616,69.48536 -30.7616,-69.48536 0.86849,-0.38603 30.76161,69.50946 z m 0.8685,0 -0.43418,0.98918 -0.43432,-0.98918 0.43432,-0.19268 z m 30.73768,-69.48536 -30.73768,69.48536 -0.8685,-0.36193 30.73761,-69.50946 z m 0,-0.38603 0.0965,0.19268 -0.0965,0.19335 -0.43428,-0.19335 z m -30.73768,-69.461216 30.73768,69.461216 -0.86857,0.38603 -30.73761,-69.461217 z m -0.8685,0 0.43432,-0.989201 0.43418,0.989201 -0.43418,0.193015 z m -30.7616,69.461216 30.7616,-69.461216 0.8685,0.386029 -30.76161,69.461217 -0.86849,-0.38603 v 0"
style="fill:#00000a;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1060"
inkscape:connector-curvature="0" />
<path
d="m 139.42324,142.73743 -0.21714,-0.507 0.21714,-0.506 1.15806,0.506 z m 25.28491,57.10826 -25.28491,-57.10826 2.31621,-1.013 25.28491,57.10787 z m 2.31621,0 -1.15805,2.63013 -1.15816,-2.63013 1.15816,-0.5067 z m 25.26082,-57.10826 -25.26082,57.10826 -2.31621,-1.01339 25.26085,-57.10787 z m 0,-1.013 0.21714,0.506 -0.21714,0.507 -1.15809,-0.507 z m -25.26082,-57.084469 25.26082,57.084469 -2.31618,1.013 -25.26085,-57.084141 z m -2.31621,0 1.15816,-2.6057 1.15805,2.6057 -1.15805,0.506664 z m -25.28491,57.084469 25.28491,-57.084469 2.31621,1.013328 -25.28491,57.084141 z"
style="fill:none;stroke:none;stroke-width:0.3350952;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1088"
inkscape:connector-curvature="0" />
<path
d="m 141.16223,142.10724 24.702,-65.657578 24.68259,65.657578 -24.68259,65.68134 -24.702,-65.68134"
style="fill:#ffcc00;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.61480898;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path1058-9"
inkscape:connector-curvature="0" />
<g
id="g1032">
<polyline
points="3070,2834 2125,2834 "
style="fill:#cccccc;stroke:#000000;stroke-width:105.84320831;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:211.68641468, 105.84320734;stroke-dashoffset:270.95861816"
id="polyline4502"
transform="matrix(0.01902033,0,0,0.01520551,116.33422,99.444301)" />
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#000000;stroke-width:1.29999995;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none"
d="m 156.67784,159.25402 a 9.0765778,10.780707 0 1 0 18.15315,0"
id="path4506" />
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#000000;stroke-width:1.29999995;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none"
d="m 156.67784,124.34767 a 9.0765778,10.780707 0 0 1 18.15315,0"
id="path4508" />
<polyline
points="1889,3307 1889,2125 "
style="fill:none;stroke:#000000;stroke-width:102.72575378;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none"
id="polyline4510"
transform="matrix(0.01280194,0,0,0.02960912,132.49496,61.428288)" />
<polyline
points="3307,2125 3307,3307 "
style="fill:none;stroke:#000000;stroke-width:102.72575378;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none"
id="polyline4512"
transform="matrix(0.01280194,0,0,0.02960912,132.49496,61.428288)" />
<path
inkscape:connector-curvature="0"
style="fill:none;stroke:#000000;stroke-width:1.29999995;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none"
d="m 158.14208,164.94334 c 5.03095,-6.62459 10.32402,-6.93226 15.35385,-0.20629"
id="path4516"
sodipodi:nodetypes="cc" />
<polyline
points="2598,944 2598,2125 "
style="fill:none;stroke:#000000;stroke-width:112.40422821;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none"
id="polyline4520"
transform="matrix(0.01280194,0,0,0.0104483,132.49496,103.78489)" />
<polyline
points="2125,2125 3070,2125 "
style="fill:none;stroke:#000000;stroke-width:116.47023773;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none"
id="polyline4526"
transform="matrix(0.01280194,0,0,0.01520551,132.49496,94.152631)" />
</g>
<text
id="text960"
y="105.75536"
x="160.85648"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:Times;-inkscape-font-specification:Times;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.69999981px;font-family:Serif;-inkscape-font-specification:'Serif Bold';stroke-width:0.26458332px"
y="105.75536"
x="160.85648"
id="tspan958"
sodipodi:role="line">A</tspan></text>
<text
id="text960-6"
y="145.59193"
x="144.0618"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:Times;-inkscape-font-specification:Times;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.69999981px;font-family:Serif;-inkscape-font-specification:'Serif Bold';stroke-width:0.26458332px"
y="145.59193"
x="144.0618"
id="tspan958-7"
sodipodi:role="line">R</tspan></text>
<text
id="text960-6-5"
y="145.41217"
x="176.67909"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:Times;-inkscape-font-specification:Times;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.69999981px;font-family:Serif;-inkscape-font-specification:'Serif Bold';stroke-width:0.26458332px"
y="145.41217"
x="176.67909"
id="tspan958-7-3"
sodipodi:role="line">C</tspan></text>
<text
id="text960-6-5-5"
y="185.46437"
x="160.06544"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.58333302px;line-height:125%;font-family:Times;-inkscape-font-specification:Times;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:12.69999981px;font-family:Serif;-inkscape-font-specification:'Serif Bold';stroke-width:0.26458332px"
y="185.46437"
x="160.06544"
id="tspan958-7-3-6"
sodipodi:role="line">G</tspan></text>
</g>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:15.91838646px;line-height:125%;font-family:Serif;-inkscape-font-specification:'Serif Bold';text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.39795968px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="141.77927"
y="116.03462"
id="text839"><tspan
sodipodi:role="line"
id="tspan837"
x="141.77927"
y="116.03462"
style="stroke-width:0.39795968px">Amateur</tspan><tspan
sodipodi:role="line"
x="141.77927"
y="135.9326"
style="stroke-width:0.39795968px"
id="tspan841">Radio</tspan><tspan
sodipodi:role="line"
x="141.77927"
y="155.83058"
style="stroke-width:0.39795968px"
id="tspan843">Club</tspan><tspan
sodipodi:role="line"
x="141.77927"
y="175.72858"
style="stroke-width:0.39795968px"
id="tspan845">Graz</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB