remove unused stuff, refactor frontend folder structure

This commit is contained in:
Eduard Gert
2026-05-28 16:26:13 +02:00
parent e09bc8894d
commit 51b243bdfa
92 changed files with 953 additions and 1629 deletions
@@ -0,0 +1,41 @@
import { useLayoutEffect, useRef } from "react";
import { Window } from "@wailsio/runtime";
// useAutoSizeWindow resizes the current Wails window so its height matches
// the measured height of the content element the returned ref is attached
// to. Width stays fixed (Wails has no "fit-content-width" notion and the
// dialog-style session windows want a stable horizontal footprint).
//
// On first measurement the hook also calls Window.Show()/Focus() — the
// Go-side opens the window with Hidden: true so the user never sees the
// initial placeholder size snap to the measured size. Subsequent
// measurements (content changes after mount) only adjust the size.
//
// Re-measures via ResizeObserver so adding/removing content (e.g. the
// SessionAboutToExpire title swapping at countdown zero) keeps the chrome
// tight to the content with no scrollbar.
export function useAutoSizeWindow<T extends HTMLElement>(width: number) {
const ref = useRef<T | null>(null);
useLayoutEffect(() => {
const el = ref.current;
if (!el) return;
let shown = false;
const apply = () => {
const h = Math.ceil(el.getBoundingClientRect().height);
if (h <= 0) return;
void Window.SetSize(width, h)
.then(() => {
if (shown) return;
shown = true;
void Window.Show().catch(() => {});
void Window.Focus().catch(() => {});
})
.catch(() => {});
};
apply();
const ro = new ResizeObserver(apply);
ro.observe(el);
return () => ro.disconnect();
}, [width]);
return ref;
}
@@ -0,0 +1,56 @@
import { useEffect } from "react";
export type Shortcut = {
key: string; // e.g. "k", "Escape", "/"
cmd?: boolean; // requires Cmd (mac) / Ctrl (win/linux)
shift?: boolean;
alt?: boolean;
// When true (default), preventDefault is called on a match.
preventDefault?: boolean;
};
// Listens for a keyboard shortcut on the window and invokes `callback` on
// match. Disable conditionally via `enabled` to avoid stealing keys while a
// dialog/panel is in the foreground.
export const useKeyboardShortcut = (
shortcut: Shortcut,
callback: () => void,
enabled = true,
) => {
useEffect(() => {
if (!enabled) return;
const onKey = (e: KeyboardEvent) => {
if (e.key.toLowerCase() !== shortcut.key.toLowerCase()) return;
const mod = e.metaKey || e.ctrlKey;
if (!!shortcut.cmd !== mod) return;
if (!!shortcut.shift !== e.shiftKey) return;
if (!!shortcut.alt !== e.altKey) return;
if (shortcut.preventDefault !== false) e.preventDefault();
callback();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [
shortcut.key,
shortcut.cmd,
shortcut.shift,
shortcut.alt,
shortcut.preventDefault,
callback,
enabled,
]);
};
// True on macOS — use the ⌘ glyph; otherwise show "Ctrl".
export const isMac =
typeof navigator !== "undefined" &&
/Mac|iPhone|iPad|iPod/i.test(navigator.platform);
export const formatShortcut = (shortcut: Shortcut): string => {
const parts: string[] = [];
if (shortcut.cmd) parts.push(isMac ? "⌘" : "Ctrl");
if (shortcut.shift) parts.push(isMac ? "⇧" : "Shift");
if (shortcut.alt) parts.push(isMac ? "⌥" : "Alt");
parts.push(shortcut.key.length === 1 ? shortcut.key.toUpperCase() : shortcut.key);
return parts.join(isMac ? "" : "+");
};
@@ -0,0 +1,116 @@
import { useEffect, useRef, useState } from "react";
import { Dialogs } from "@wailsio/runtime";
import i18next from "@/lib/i18n";
import { useSettings } from "@/contexts/SettingsContext.tsx";
export enum ManagementMode {
Cloud = "cloud",
SelfHosted = "selfhosted",
}
export const CLOUD_MANAGEMENT_URL = "https://api.netbird.io:443";
function normalizeManagementUrl(input: string): string {
const trimmed = input.trim();
if (!trimmed) return "";
if (/^https?:\/\//i.test(trimmed)) return trimmed;
return `https://${trimmed}`;
}
const URL_PATTERN = new RegExp(
"^(https?:\\/\\/)?" +
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|localhost|" +
"((\\d{1,3}\\.){3}\\d{1,3}))" +
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" +
"(\\?[;&a-z\\d%_.~+=-]*)?" +
"(\\#[-a-z\\d_]*)?$",
"i",
);
function isValidManagementUrl(input: string): boolean {
const trimmed = input.trim();
if (!trimmed) return false;
return URL_PATTERN.test(trimmed);
}
function modeFromUrl(url: string): ManagementMode {
return url === CLOUD_MANAGEMENT_URL ? ManagementMode.Cloud : ManagementMode.SelfHosted;
}
export function useManagementUrl() {
const { config, saveField } = useSettings();
const [mode, setModeState] = useState<ManagementMode>(
modeFromUrl(config.managementUrl),
);
const [url, setUrl] = useState(
config.managementUrl === CLOUD_MANAGEMENT_URL ? "" : config.managementUrl,
);
// Guard against double-showing the cloud-switch confirmation when the
// user toggles the segmented control multiple times before the prior
// Dialogs.Warning promise resolves. Without it each click queues a
// fresh native dialog and the user sees them stack up.
const switchConfirmOpenRef = useRef(false);
useEffect(() => {
setModeState(modeFromUrl(config.managementUrl));
if (config.managementUrl !== CLOUD_MANAGEMENT_URL) {
setUrl(config.managementUrl);
}
}, [config.managementUrl]);
const setMode = (next: ManagementMode) => {
if (
next === ManagementMode.Cloud &&
config.managementUrl !== CLOUD_MANAGEMENT_URL
) {
// Switching from a self-hosted management server to NetBird Cloud
// re-points the client at a different deployment and forces a
// reconnect/re-login. Confirm before applying.
if (switchConfirmOpenRef.current) return;
switchConfirmOpenRef.current = true;
const cancelLabel = i18next.t("common.cancel");
const confirmLabel = i18next.t("settings.general.management.switchCloudConfirm");
void Dialogs.Warning({
Title: i18next.t("settings.general.management.switchCloudTitle"),
Message: i18next.t("settings.general.management.switchCloudMessage"),
Buttons: [
{ Label: cancelLabel, IsCancel: true, IsDefault: true },
{ Label: confirmLabel },
],
})
.then((result) => {
if (result !== confirmLabel) return;
setModeState(ManagementMode.Cloud);
void saveField("managementUrl", CLOUD_MANAGEMENT_URL);
})
.finally(() => {
switchConfirmOpenRef.current = false;
});
return;
}
setModeState(next);
};
const normalizedUrl = normalizeManagementUrl(url);
const urlValid = isValidManagementUrl(url);
const targetUrl =
mode === ManagementMode.Cloud ? CLOUD_MANAGEMENT_URL : normalizedUrl;
const dirty = targetUrl !== config.managementUrl;
const showError =
mode === ManagementMode.SelfHosted && url.trim() !== "" && !urlValid;
const canSave = dirty && (mode === ManagementMode.Cloud || urlValid);
const displayUrl = mode === ManagementMode.Cloud ? CLOUD_MANAGEMENT_URL : url;
const save = () => saveField("managementUrl", targetUrl);
return {
mode,
setMode,
url,
setUrl,
displayUrl,
showError,
canSave,
save,
};
}