mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-06 21:49:08 +02:00
remove unused stuff, refactor frontend folder structure
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user