feat(network): enhance network settings UI (#364)

* feat(network): enhance network settings UI with domain management and improved layout

- Added custom domain input and selection options for DHCP and local domains.
- Improved layout for displaying network settings, including DHCP lease information and IPv6 addresses.
- Refactored state management for network settings and added handlers for hostname and domain changes.
- Updated the display of network settings to enhance user experience and accessibility.

* Re-add save button

* fix: add ConfirmDialog for renewing DHCP lease and improve network settings layout

- Integrated ConfirmDialog component to confirm DHCP lease renewal.
- Enhanced the layout of network settings, including better organization of IPv4 and IPv6 information.
- Updated state management for displaying network settings and lease information.
- Improved user experience with clearer descriptions and structured UI elements.

* Fix lint errors

* fix: useRef TS2554

---------

Co-authored-by: Siyuan Miao <i@xswan.net>
This commit is contained in:
Adam Shiervani
2025-05-14 17:25:56 +02:00
committed by GitHub
parent 2aa7b8569f
commit 340babac24
8 changed files with 1045 additions and 631 deletions

View File

@@ -1,6 +1,11 @@
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import { MAX_STEPS_PER_MACRO, MAX_TOTAL_MACROS, MAX_KEYS_PER_STEP } from "@/constants/macros";
import {
MAX_STEPS_PER_MACRO,
MAX_TOTAL_MACROS,
MAX_KEYS_PER_STEP,
} from "@/constants/macros";
// Define the JsonRpc types for better type checking
interface JsonRpcResponse {
@@ -571,12 +576,12 @@ export interface UpdateState {
setOtaState: (state: UpdateState["otaState"]) => void;
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) => void;
modalView:
| "loading"
| "updating"
| "upToDate"
| "updateAvailable"
| "updateCompleted"
| "error";
| "loading"
| "updating"
| "upToDate"
| "updateAvailable"
| "updateCompleted"
| "error";
setModalView: (view: UpdateState["modalView"]) => void;
setUpdateErrorMessage: (errorMessage: string) => void;
updateErrorMessage: string | null;
@@ -640,12 +645,12 @@ export const useUsbConfigModalStore = create<UsbConfigModalState>(set => ({
interface LocalAuthModalState {
modalView:
| "createPassword"
| "deletePassword"
| "updatePassword"
| "creationSuccess"
| "deleteSuccess"
| "updateSuccess";
| "createPassword"
| "deletePassword"
| "updatePassword"
| "creationSuccess"
| "deleteSuccess"
| "updateSuccess";
setModalView: (view: LocalAuthModalState["modalView"]) => void;
}
@@ -726,12 +731,23 @@ export interface NetworkState {
setDhcpLeaseExpiry: (expiry: Date) => void;
}
export type IPv6Mode = "disabled" | "slaac" | "dhcpv6" | "slaac_and_dhcpv6" | "static" | "link_local" | "unknown";
export type IPv6Mode =
| "disabled"
| "slaac"
| "dhcpv6"
| "slaac_and_dhcpv6"
| "static"
| "link_local"
| "unknown";
export type IPv4Mode = "disabled" | "static" | "dhcp" | "unknown";
export type LLDPMode = "disabled" | "basic" | "all" | "unknown";
export type mDNSMode = "disabled" | "auto" | "ipv4_only" | "ipv6_only" | "unknown";
export type TimeSyncMode = "ntp_only" | "ntp_and_http" | "http_only" | "custom" | "unknown";
export type TimeSyncMode =
| "ntp_only"
| "ntp_and_http"
| "http_only"
| "custom"
| "unknown";
export interface NetworkSettings {
hostname: string;
@@ -756,7 +772,7 @@ export const useNetworkStateStore = create<NetworkState>((set, get) => ({
lease.lease_expiry = expiry;
set({ dhcp_lease: lease });
}
},
}));
export interface KeySequenceStep {
@@ -778,8 +794,20 @@ export interface MacrosState {
initialized: boolean;
loadMacros: () => Promise<void>;
saveMacros: (macros: KeySequence[]) => Promise<void>;
sendFn: ((method: string, params: unknown, callback?: ((resp: JsonRpcResponse) => void) | undefined) => void) | null;
setSendFn: (sendFn: ((method: string, params: unknown, callback?: ((resp: JsonRpcResponse) => void) | undefined) => void)) => void;
sendFn:
| ((
method: string,
params: unknown,
callback?: ((resp: JsonRpcResponse) => void) | undefined,
) => void)
| null;
setSendFn: (
sendFn: (
method: string,
params: unknown,
callback?: ((resp: JsonRpcResponse) => void) | undefined,
) => void,
) => void;
}
export const generateMacroId = () => {
@@ -792,7 +820,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
initialized: false,
sendFn: null,
setSendFn: (sendFn) => {
setSendFn: sendFn => {
set({ sendFn });
},
@@ -809,7 +837,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
try {
await new Promise<void>((resolve, reject) => {
sendFn("getKeyboardMacros", {}, (response) => {
sendFn("getKeyboardMacros", {}, response => {
if (response.error) {
console.error("Error loading macros:", response.error);
reject(new Error(response.error.message));
@@ -829,7 +857,7 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
set({
macros: sortedMacros,
initialized: true
initialized: true,
});
resolve();
@@ -856,15 +884,23 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
for (const macro of macros) {
if (macro.steps.length > MAX_STEPS_PER_MACRO) {
console.error(`Cannot save: macro "${macro.name}" exceeds maximum of ${MAX_STEPS_PER_MACRO} steps`);
throw new Error(`Cannot save: macro "${macro.name}" exceeds maximum of ${MAX_STEPS_PER_MACRO} steps`);
console.error(
`Cannot save: macro "${macro.name}" exceeds maximum of ${MAX_STEPS_PER_MACRO} steps`,
);
throw new Error(
`Cannot save: macro "${macro.name}" exceeds maximum of ${MAX_STEPS_PER_MACRO} steps`,
);
}
for (let i = 0; i < macro.steps.length; i++) {
const step = macro.steps[i];
if (step.keys && step.keys.length > MAX_KEYS_PER_STEP) {
console.error(`Cannot save: macro "${macro.name}" step ${i + 1} exceeds maximum of ${MAX_KEYS_PER_STEP} keys`);
throw new Error(`Cannot save: macro "${macro.name}" step ${i + 1} exceeds maximum of ${MAX_KEYS_PER_STEP} keys`);
console.error(
`Cannot save: macro "${macro.name}" step ${i + 1} exceeds maximum of ${MAX_KEYS_PER_STEP} keys`,
);
throw new Error(
`Cannot save: macro "${macro.name}" step ${i + 1} exceeds maximum of ${MAX_KEYS_PER_STEP} keys`,
);
}
}
}
@@ -874,20 +910,25 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
try {
const macrosWithSortOrder = macros.map((macro, index) => ({
...macro,
sortOrder: macro.sortOrder !== undefined ? macro.sortOrder : index
sortOrder: macro.sortOrder !== undefined ? macro.sortOrder : index,
}));
const response = await new Promise<JsonRpcResponse>((resolve) => {
sendFn("setKeyboardMacros", { params: { macros: macrosWithSortOrder } }, (response) => {
resolve(response);
});
const response = await new Promise<JsonRpcResponse>(resolve => {
sendFn(
"setKeyboardMacros",
{ params: { macros: macrosWithSortOrder } },
response => {
resolve(response);
},
);
});
if (response.error) {
console.error("Error saving macros:", response.error);
const errorMessage = typeof response.error.data === 'string'
? response.error.data
: response.error.message || "Failed to save macros";
const errorMessage =
typeof response.error.data === "string"
? response.error.data
: response.error.message || "Failed to save macros";
throw new Error(errorMessage);
}
@@ -899,5 +940,5 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
} finally {
set({ loading: false });
}
}
}));
},
}));