feat(ui): settings menu restructure, git branch dropdown, and repo cleanup

This commit is contained in:
2026-02-22 21:29:30 +01:00
parent 831564762b
commit b955e4f464
15 changed files with 160 additions and 63 deletions

15
.gitignore vendored
View File

@@ -69,5 +69,20 @@ PROJECT_SUMMARY.md
QUICKSTART.md
VS_CODE_SETUP.md
# Gemini / Antigravity
.gemini/
# Windows artifacts
nul
# Debug / temp files (generated during development & testing)
out.txt
containers.txt
helper.txt
logs.txt
port.txt
env.txt
network.txt
update_helper.txt
state.txt
hostpath.txt

View File

@@ -334,6 +334,19 @@ async def get_version(
return result
@router.get("/branches")
async def get_branches(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Return a list of available branches from the configured git remote."""
config = get_system_config(db)
if not config or not config.git_repo_url:
return []
branches = await update_service.get_remote_branches(config)
return branches
@router.post("/update")
async def trigger_update(
current_user: User = Depends(get_current_user),

View File

@@ -5,6 +5,7 @@ import logging
import os
import shutil
import subprocess
import httpx
from datetime import datetime
from pathlib import Path
from typing import Any
@@ -130,6 +131,42 @@ async def check_for_updates(config: Any) -> dict:
}
async def get_remote_branches(config: Any) -> list[str]:
"""Query the Gitea API for available branches on the configured repository.
Returns a list of branch names (e.g., ['main', 'unstable', 'development']).
If the repository URL is not configured or an error occurs, returns an empty list.
"""
if not config.git_repo_url:
return []
repo_url = config.git_repo_url.rstrip("/")
parts = repo_url.split("/")
if len(parts) < 5:
return []
base_url = "/".join(parts[:-2])
owner = parts[-2]
repo = parts[-1]
branches_api = f"{base_url}/api/v1/repos/{owner}/{repo}/branches?limit=100"
headers = {}
if config.git_token:
headers["Authorization"] = f"token {config.git_token}"
try:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(branches_api, headers=headers)
if resp.status_code == 200:
data = resp.json()
if isinstance(data, list):
return [branch.get("name") for branch in data if "name" in branch]
except Exception as exc:
logger.error("Error fetching branches: %s", exc)
return []
def backup_database(db_path: str) -> str:
"""Create a timestamped backup of the SQLite database.

View File

@@ -1,9 +0,0 @@
NAMES STATUS IMAGE
netbird-msp-appliance Up 3 minutes (healthy) netbirdmsp-appliance-netbird-msp-appliance
msp-updater Exited (0) 3 minutes ago netbirdmsp-appliance-netbird-msp-appliance:latest
netbird-kunde1-caddy Up 2 hours caddy:2-alpine
netbird-kunde1-signal Up 2 hours netbirdio/signal:latest
netbird-kunde1-dashboard Up 2 hours netbirdio/dashboard:latest
netbird-kunde1-relay Up 2 hours netbirdio/relay:latest
netbird-kunde1-management Up 2 hours netbirdio/management:latest
docker-socket-proxy Up 2 hours tecnativa/docker-socket-proxy:latest

View File

View File

@@ -1 +0,0 @@
Error response from daemon: No such container: msp-updater

View File

@@ -1,9 +0,0 @@
INFO: Started server process [1]
INFO: Waiting for application startup.
2026-02-22 14:53:59,694 [INFO] app.main: Starting NetBird MSP Appliance...
2026-02-22 14:53:59,744 [INFO] app.main: Database initialized.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO: 127.0.0.1:45370 - "GET /api/health HTTP/1.1" 200 OK
INFO: 127.0.0.1:57724 - "GET /api/health HTTP/1.1" 200 OK
INFO: 127.0.0.1:56212 - "GET /api/health HTTP/1.1" 200 OK

View File

View File

@@ -1 +0,0 @@
msp-updater

View File

@@ -1,2 +0,0 @@
8000/tcp -> 0.0.0.0:8000
8000/tcp -> [::]:8000

View File

@@ -363,48 +363,54 @@
<!-- Sidebar -->
<div class="col-md-3 mb-4 mb-md-0 border-end pe-3">
<ul class="nav nav-pills flex-column" id="settings-tabs">
<li class="nav-item"><a class="nav-link active" data-bs-toggle="pill"
href="#settings-system"><i class="bi bi-pc-display me-2"></i><span
data-i18n="settings.tabSystem">System</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-npm"><i
class="bi bi-router me-2"></i><span data-i18n="settings.tabNpm">NPM
Proxy</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-images"><i
class="bi bi-box me-2"></i><span
data-i18n="settings.tabImages">Images</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-branding"><i
class="bi bi-palette me-2"></i><span
data-i18n="settings.tabBranding">Branding</span></a></li>
<hr class="my-3 border-secondary opacity-25">
<div class="text-uppercase text-muted fw-bold mb-2 ps-3"
style="font-size: 0.75rem; letter-spacing: 0.05em;"><i
class="bi bi-shield-check me-1"></i>Auth & Users</div>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-users"
onclick="loadUsers()"><i class="bi bi-people me-2"></i><span
data-i18n="settings.tabUsers">Users</span></a></li>
class="bi bi-shield-check me-1"></i><span
data-i18n="settings.groupUsers">Benutzerverwaltung</span></div>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-azure"><i
class="bi bi-microsoft me-2"></i><span data-i18n="settings.tabAzure">Azure
AD</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-users"
onclick="loadUsers()"><i class="bi bi-people me-2"></i><span
data-i18n="settings.tabUsers">Benutzer</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-ldap"><i
class="bi bi-diagram-3 me-2"></i><span data-i18n="settings.tabLdap">LDAP /
AD</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-security"><i
class="bi bi-shield-lock me-2"></i><span
data-i18n="settings.tabSecurity">Sicherheit</span></a></li>
<hr class="my-3 border-secondary opacity-25">
<div class="text-uppercase text-muted fw-bold mb-2 ps-3"
style="font-size: 0.75rem; letter-spacing: 0.05em;"><i
class="bi bi-gear-wide-connected me-1"></i>System</div>
class="bi bi-gear-wide-connected me-1"></i><span
data-i18n="settings.groupSystem">Systemkonfiguration</span></div>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-branding"><i
class="bi bi-palette me-2"></i><span
data-i18n="settings.tabBranding">Branding</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-images"><i
class="bi bi-box me-2"></i><span data-i18n="settings.tabImages">NetBird Docker
Images</span></a></li>
<li class="nav-item"><a class="nav-link active" data-bs-toggle="pill"
href="#settings-system"><i class="bi bi-pc-display me-2"></i><span
data-i18n="settings.tabSystem">NetBird MSP System</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-update"
onclick="loadVersionInfo()"><i class="bi bi-cloud-arrow-down me-2"></i><span
data-i18n="settings.tabUpdate">NetBird MSP Updates</span></a></li>
<hr class="my-3 border-secondary opacity-25">
<div class="text-uppercase text-muted fw-bold mb-2 ps-3"
style="font-size: 0.75rem; letter-spacing: 0.05em;"><i
class="bi bi-link-45deg me-1"></i><span
data-i18n="settings.groupExternal">Umsysteme</span></div>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-npm"><i
class="bi bi-router me-2"></i><span data-i18n="settings.tabNpm">NPM
Proxy</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-dns"><i
class="bi bi-hdd-network me-2"></i><span data-i18n="settings.tabDns">Windows
DNS</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-update"
onclick="loadVersionInfo()"><i class="bi bi-cloud-arrow-down me-2"></i><span
data-i18n="settings.tabUpdate">Updates</span></a></li>
<li class="nav-item"><a class="nav-link" data-bs-toggle="pill" href="#settings-security"><i
class="bi bi-shield-lock me-2"></i><span
data-i18n="settings.tabSecurity">Security</span></a></li>
</ul>
</div>
@@ -1004,8 +1010,15 @@
<div class="col-md-4">
<label class="form-label"
data-i18n="settings.gitBranch">Branch</label>
<input type="text" class="form-control" id="cfg-git-branch"
placeholder="main">
<div class="input-group">
<select class="form-select" id="cfg-git-branch">
<option value="main">main</option>
</select>
<button class="btn btn-outline-secondary" type="button"
onclick="loadGitBranches()" title="Aktualisieren">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<div class="col-md-8">
<label class="form-label" data-i18n="settings.gitToken">Access Token

View File

@@ -872,6 +872,9 @@ async function loadSettings() {
} catch (err) {
showSettingsAlert('danger', t('errors.failedToLoadSettings', { error: err.message }));
}
// Automatically fetch branches once the base config is populated
await loadGitBranches();
}
function updateLogoPreview(logoPath) {
@@ -1183,6 +1186,42 @@ async function testLdapConnection() {
}
}
async function loadGitBranches() {
const branchSelect = document.getElementById('cfg-git-branch');
const currentVal = branchSelect.value;
// Disable mapping while loading
branchSelect.disabled = true;
branchSelect.innerHTML = `<option value="${currentVal}">${currentVal} (Loading...)</option>`;
try {
const branches = await api('GET', '/settings/branches');
branchSelect.innerHTML = '';
// Always ensure the currently saved branch is an option
if (currentVal && !branches.includes(currentVal)) {
branches.unshift(currentVal);
}
if (branches.length === 0) {
branchSelect.innerHTML = `<option value="main">main</option>`;
} else {
branches.forEach(b => {
const opt = document.createElement('option');
opt.value = b;
opt.textContent = b;
if (b === currentVal) opt.selected = true;
branchSelect.appendChild(opt);
});
}
} catch (err) {
showSettingsAlert('warning', `Failed to load branches: ${err.message}`);
branchSelect.innerHTML = `<option value="${currentVal}">${currentVal}</option>`;
} finally {
branchSelect.disabled = false;
}
}
// ---------------------------------------------------------------------------
// Update / Version Management
// ---------------------------------------------------------------------------

View File

@@ -93,16 +93,19 @@
},
"settings": {
"title": "Systemeinstellungen",
"tabSystem": "Systemkonfiguration",
"tabNpm": "NPM Integration",
"tabImages": "Docker Images",
"tabSystem": "NetBird MSP System",
"tabNpm": "NPM Proxy",
"tabImages": "NetBird Docker Images",
"tabBranding": "Branding",
"tabUsers": "Benutzer",
"tabAzure": "Azure AD",
"tabDns": "Windows DNS",
"tabLdap": "LDAP / AD",
"tabUpdate": "Updates",
"tabUpdate": "NetBird MSP Updates",
"tabSecurity": "Sicherheit",
"groupUsers": "Benutzerverwaltung",
"groupSystem": "Systemkonfiguration",
"groupExternal": "Umsysteme",
"baseDomain": "Basis-Domain",
"baseDomainPlaceholder": "ihredomain.com",
"baseDomainHint": "Kunden erhalten Subdomains: kunde.ihredomain.com",

View File

@@ -114,16 +114,19 @@
},
"settings": {
"title": "System Settings",
"tabSystem": "System Configuration",
"tabNpm": "NPM Integration",
"tabImages": "Docker Images",
"tabSystem": "NetBird MSP System",
"tabNpm": "NPM Proxy",
"tabImages": "NetBird Docker Images",
"tabBranding": "Branding",
"tabUsers": "Users",
"tabAzure": "Azure AD",
"tabDns": "Windows DNS",
"tabLdap": "LDAP / AD",
"tabUpdate": "Updates",
"tabUpdate": "NetBird MSP Updates",
"tabSecurity": "Security",
"groupUsers": "User Management",
"groupSystem": "System Configuration",
"groupExternal": "External Systems",
"baseDomain": "Base Domain",
"baseDomainPlaceholder": "yourdomain.com",
"baseDomainHint": "Customers get subdomains: customer.yourdomain.com",
@@ -370,4 +373,4 @@
"confirmDeleteUser": "Delete user '{username}'? This cannot be undone.",
"confirmResetPassword": "Reset password for '{username}'? A new random password will be generated."
}
}
}

View File

@@ -1,4 +0,0 @@
Container netbird-msp-appliance Recreate
Container netbird-msp-appliance Recreated
Container netbird-msp-appliance Starting
Container netbird-msp-appliance Started