Compare commits

...

4 Commits

Author SHA1 Message Date
Aveline e72300e0a9
Merge d4f2995ad6 into cc9ff74276 2025-10-11 16:15:47 +00:00
Siyuan d4f2995ad6 feat: add CIDR notation support for IPv4 address 2025-10-11 16:15:35 +00:00
Siyuan bc4c2d92db fix: golangci-lint warnings 2025-10-11 15:52:51 +00:00
Siyuan 8810ed4827 fix: error dump directory 2025-10-11 15:51:26 +00:00
4 changed files with 56 additions and 9 deletions

View File

@ -17,7 +17,7 @@ import (
const (
envChildID = "JETKVM_CHILD_ID"
errorDumpDir = "/userdata/jetkvm/"
errorDumpDir = "/userdata/jetkvm/crashdump"
errorDumpLastFile = "last-crash.log"
errorDumpTemplate = "jetkvm-%s.log"
)
@ -179,6 +179,18 @@ func renameFile(f *os.File, newName string) error {
return nil
}
func ensureErrorDumpDir() error {
// TODO: check if the directory is writable
f, err := os.Stat(errorDumpDir)
if err == nil && f.IsDir() {
return nil
}
if err := os.MkdirAll(errorDumpDir, 0755); err != nil {
return fmt.Errorf("failed to create error dump directory: %w", err)
}
return nil
}
func createErrorDump(logFile *os.File) {
fmt.Println()
@ -187,6 +199,12 @@ func createErrorDump(logFile *os.File) {
time.Now().Format("20060102-150405"),
)
// check if the directory exists
if err := ensureErrorDumpDir(); err != nil {
fmt.Printf("failed to ensure error dump directory: %v\n", err)
return
}
filePath := filepath.Join(errorDumpDir, fileName)
if err := renameFile(logFile, filePath); err != nil {
fmt.Printf("failed to rename file: %v\n", err)

View File

@ -177,7 +177,6 @@ func (m *MDNS) setLocalNames(localNames []string) {
}
m.localNames = localNames
return
}
func (m *MDNS) setListenOptions(listenOptions *MDNSListenOptions) {

View File

@ -2,31 +2,49 @@ import { LuPlus, LuX } from "react-icons/lu";
import { useFieldArray, useFormContext } from "react-hook-form";
import { useEffect } from "react";
import validator from "validator";
import { cx } from "cva";
import { GridCard } from "@/components/Card";
import { Button } from "@/components/Button";
import { InputFieldWithLabel } from "@/components/InputField";
import { NetworkSettings } from "@/hooks/stores";
import { netMaskFromCidr4 } from "@/utils/ip";
export default function StaticIpv4Card() {
const formMethods = useFormContext<NetworkSettings>();
const { register, formState, watch } = formMethods;
const { register, formState, watch, setValue } = formMethods;
const { fields, append, remove } = useFieldArray({ name: "ipv4_static.dns" });
// TODO: set subnet mask if IP address is in CIDR notation
useEffect(() => {
if (fields.length === 0) append("");
}, [append, fields.length]);
const dns = watch("ipv4_static.dns");
const ipv4StaticAddress = watch("ipv4_static.address");
const hideSubnetMask = ipv4StaticAddress?.includes("/");
useEffect(() => {
const parts = ipv4StaticAddress?.split("/", 2);
if (parts.length !== 2) return;
const cidrNotation = parseInt(parts[1]);
if (isNaN(cidrNotation) || cidrNotation < 0 || cidrNotation > 32) return;
const mask = netMaskFromCidr4(cidrNotation);
setValue("ipv4_static.netmask", mask);
}, [ipv4StaticAddress, setValue]);
const validate = (value: string) => {
if (!validator.isIP(value)) return "Invalid IP address";
return true;
};
const validateIsIPOrCIDR4 = (value: string) => {
if (!validator.isIP(value, 4) && !validator.isIPRange(value, 4)) return "Invalid IP address or CIDR notation";
return true;
};
return (
<GridCard>
<div className="animate-fadeIn p-4 text-black opacity-0 animation-duration-500 dark:text-white">
@ -35,24 +53,25 @@ export default function StaticIpv4Card() {
Static IPv4 Configuration
</h3>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className={cx("grid grid-cols-1 gap-4", hideSubnetMask ? "md:grid-cols-1" : "md:grid-cols-2")}>
<InputFieldWithLabel
label="IP Address"
type="text"
size="SM"
placeholder="192.168.1.100"
{...register("ipv4_static.address", { validate })}
{
...register("ipv4_static.address", { validate: validateIsIPOrCIDR4 })}
error={formState.errors.ipv4_static?.address?.message}
/>
<InputFieldWithLabel
{!hideSubnetMask && <InputFieldWithLabel
label="Subnet Mask"
type="text"
size="SM"
placeholder="255.255.255.0"
{...register("ipv4_static.netmask", { validate })}
error={formState.errors.ipv4_static?.netmask?.message}
/>
/>}
</div>
<InputFieldWithLabel

View File

@ -14,6 +14,7 @@ import { getNetworkSettings, getNetworkState } from "@/utils/jsonrpc";
import { Button } from "@components/Button";
import { GridCard } from "@components/Card";
import InputField, { InputFieldWithLabel } from "@components/InputField";
import { netMaskFromCidr4 } from "@/utils/ip";
import AutoHeight from "../components/AutoHeight";
import DhcpLeaseCard from "../components/DhcpLeaseCard";
@ -155,6 +156,16 @@ export default function SettingsNetworkRoute() {
const { register, handleSubmit, watch, formState, reset } = formMethods;
const onSubmit = async (settings: NetworkSettings) => {
if (settings.ipv4_static?.address?.includes("/")) {
const parts = settings.ipv4_static.address.split("/");
const cidrNotation = parseInt(parts[1]);
if (isNaN(cidrNotation) || cidrNotation < 0 || cidrNotation > 32) {
return notifications.error("Invalid CIDR notation for IPv4 address");
}
settings.ipv4_static.netmask = netMaskFromCidr4(cidrNotation);
settings.ipv4_static.address = parts[0];
}
send("setNetworkSettings", { settings }, async (resp) => {
if ("error" in resp) {
return notifications.error(