feat: The Other Dude v9.0.1 — full-featured email system
ci: add GitHub Pages deployment workflow for docs site Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
335
frontend/src/lib/simpleConfigSchema.ts
Normal file
335
frontend/src/lib/simpleConfigSchema.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
* Simple Configuration Interface - Declarative Category Schema
|
||||
*
|
||||
* Maps 7 simplified configuration categories to RouterOS paths,
|
||||
* field definitions, and friendly labels. Consumed by all Simple
|
||||
* mode category panels and the category sidebar.
|
||||
*/
|
||||
|
||||
import type { LucideIcon } from 'lucide-react'
|
||||
import {
|
||||
Globe,
|
||||
Network,
|
||||
Wifi,
|
||||
ArrowLeftRight,
|
||||
Shield,
|
||||
Server,
|
||||
Settings,
|
||||
} from 'lucide-react'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface SimpleCategory {
|
||||
id: string
|
||||
label: string
|
||||
icon: LucideIcon
|
||||
description: string
|
||||
sections: SimpleCategorySection[]
|
||||
}
|
||||
|
||||
export interface SimpleCategorySection {
|
||||
label: string
|
||||
routerosPath: string
|
||||
isSingleton: boolean
|
||||
fields: SimpleFieldDef[]
|
||||
}
|
||||
|
||||
export interface SimpleFieldDef {
|
||||
key: string
|
||||
label: string
|
||||
type: 'text' | 'ip' | 'cidr' | 'number' | 'boolean' | 'select' | 'password'
|
||||
help?: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
options?: { value: string; label: string }[]
|
||||
validation?: (value: string) => string | null
|
||||
minVersion?: number
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Validators
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const IPV4_REGEX = /^(\d{1,3}\.){3}\d{1,3}$/
|
||||
const IPV6_REGEX = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/
|
||||
|
||||
export function isValidIp(value: string): boolean {
|
||||
if (!value) return false
|
||||
if (IPV4_REGEX.test(value)) {
|
||||
return value.split('.').every((octet) => {
|
||||
const n = parseInt(octet, 10)
|
||||
return n >= 0 && n <= 255
|
||||
})
|
||||
}
|
||||
return IPV6_REGEX.test(value)
|
||||
}
|
||||
|
||||
export function isValidCidr(value: string): boolean {
|
||||
if (!value) return false
|
||||
const parts = value.split('/')
|
||||
if (parts.length !== 2) return false
|
||||
const [ip, prefix] = parts
|
||||
if (!isValidIp(ip)) return false
|
||||
const prefixNum = parseInt(prefix, 10)
|
||||
if (isNaN(prefixNum)) return false
|
||||
// IPv4: 0-32, IPv6: 0-128
|
||||
const maxPrefix = ip.includes(':') ? 128 : 32
|
||||
return prefixNum >= 0 && prefixNum <= maxPrefix
|
||||
}
|
||||
|
||||
export function isValidPort(value: string): boolean {
|
||||
const n = parseInt(value, 10)
|
||||
return !isNaN(n) && n >= 1 && n <= 65535
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Category Definitions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const SIMPLE_CATEGORIES: SimpleCategory[] = [
|
||||
{
|
||||
id: 'internet',
|
||||
label: 'Internet Setup',
|
||||
icon: Globe,
|
||||
description: 'Configure how this router connects to the internet',
|
||||
sections: [
|
||||
{
|
||||
label: 'DHCP Client',
|
||||
routerosPath: '/ip/dhcp-client',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'interface', label: 'WAN Interface', type: 'select', required: true },
|
||||
{ key: 'use-peer-dns', label: 'Use ISP DNS', type: 'boolean', help: 'Accept DNS servers from your ISP' },
|
||||
{ key: 'use-peer-ntp', label: 'Use ISP NTP', type: 'boolean', help: 'Accept time servers from your ISP' },
|
||||
{ key: 'add-default-route', label: 'Add Default Route', type: 'boolean', help: 'Automatically create a default route via this connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'PPPoE Client',
|
||||
routerosPath: '/interface/pppoe-client',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'interface', label: 'Interface', type: 'select', required: true },
|
||||
{ key: 'user', label: 'PPPoE Username', type: 'text', required: true, placeholder: 'ISP username' },
|
||||
{ key: 'password', label: 'PPPoE Password', type: 'password', required: true },
|
||||
{ key: 'service-name', label: 'Service Name', type: 'text', placeholder: 'Optional' },
|
||||
{ key: 'use-peer-dns', label: 'Use ISP DNS', type: 'boolean' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Static IP',
|
||||
routerosPath: '/ip/address',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'address', label: 'IP Address / Mask', type: 'cidr', required: true, placeholder: '192.168.1.100/24' },
|
||||
{ key: 'interface', label: 'WAN Interface', type: 'select', required: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'lan',
|
||||
label: 'LAN & DHCP',
|
||||
icon: Network,
|
||||
description: 'Local network addresses, DHCP server, and IP pools',
|
||||
sections: [
|
||||
{
|
||||
label: 'LAN Address',
|
||||
routerosPath: '/ip/address',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'address', label: 'IP Address / Mask', type: 'cidr', required: true, placeholder: '192.168.88.1/24', help: 'The IP address of this router on the local network' },
|
||||
{ key: 'interface', label: 'Interface', type: 'text' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'DHCP Server',
|
||||
routerosPath: '/ip/dhcp-server',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'disabled', label: 'Enabled', type: 'boolean', help: 'Enable or disable the DHCP server' },
|
||||
{ key: 'address-pool', label: 'Address Pool', type: 'text' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'DHCP Network',
|
||||
routerosPath: '/ip/dhcp-server/network',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'gateway', label: 'Gateway', type: 'ip', placeholder: '192.168.88.1' },
|
||||
{ key: 'dns-server', label: 'DNS Servers', type: 'text', placeholder: '192.168.88.1', help: 'DNS servers provided to DHCP clients' },
|
||||
{ key: 'lease-time', label: 'Lease Time', type: 'text', placeholder: '10m', help: 'How long a DHCP lease is valid' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Address Pool',
|
||||
routerosPath: '/ip/pool',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'ranges', label: 'IP Range', type: 'text', placeholder: '192.168.88.10-192.168.88.254', help: 'Range of IP addresses for DHCP clients' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'wifi',
|
||||
label: 'WiFi',
|
||||
icon: Wifi,
|
||||
description: 'Wireless network names, passwords, and bands',
|
||||
sections: [
|
||||
{
|
||||
label: 'Wireless Interface',
|
||||
routerosPath: '/interface/wifi',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'ssid', label: 'Network Name (SSID)', type: 'text', required: true, placeholder: 'MyNetwork' },
|
||||
{ key: 'security.passphrase', label: 'Password', type: 'password', required: true, help: 'WPA2/WPA3 passphrase (min 8 characters)' },
|
||||
{ key: 'configuration.band', label: 'Band', type: 'select', options: [
|
||||
{ value: '2ghz-ax', label: '2.4 GHz' },
|
||||
{ value: '5ghz-ax', label: '5 GHz' },
|
||||
] },
|
||||
{ key: 'disabled', label: 'Enabled', type: 'boolean' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'port-forwarding',
|
||||
label: 'Port Forwarding',
|
||||
icon: ArrowLeftRight,
|
||||
description: 'Forward external ports to internal servers',
|
||||
sections: [
|
||||
{
|
||||
label: 'NAT Rules',
|
||||
routerosPath: '/ip/firewall/nat',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'dst-port', label: 'External Port', type: 'number', required: true, placeholder: '80', validation: (v) => isValidPort(v) ? null : 'Port must be 1-65535' },
|
||||
{ key: 'protocol', label: 'Protocol', type: 'select', required: true, options: [
|
||||
{ value: 'tcp', label: 'TCP' },
|
||||
{ value: 'udp', label: 'UDP' },
|
||||
{ value: '6', label: 'TCP + UDP' },
|
||||
] },
|
||||
{ key: 'to-addresses', label: 'Internal IP Address', type: 'ip', required: true, placeholder: '192.168.88.100' },
|
||||
{ key: 'to-ports', label: 'Internal Port', type: 'number', required: true, placeholder: '80', help: 'Leave same as external port if unchanged' },
|
||||
{ key: 'comment', label: 'Description', type: 'text', placeholder: 'e.g., Web Server' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'firewall',
|
||||
label: 'Firewall',
|
||||
icon: Shield,
|
||||
description: 'Basic firewall rules and address lists',
|
||||
sections: [
|
||||
{
|
||||
label: 'Filter Rules',
|
||||
routerosPath: '/ip/firewall/filter',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'chain', label: 'Chain', type: 'select', required: true, options: [
|
||||
{ value: 'input', label: 'Input' },
|
||||
{ value: 'forward', label: 'Forward' },
|
||||
] },
|
||||
{ key: 'action', label: 'Action', type: 'select', required: true, options: [
|
||||
{ value: 'accept', label: 'Accept' },
|
||||
{ value: 'drop', label: 'Drop' },
|
||||
{ value: 'reject', label: 'Reject' },
|
||||
] },
|
||||
{ key: 'protocol', label: 'Protocol', type: 'select', options: [
|
||||
{ value: 'tcp', label: 'TCP' },
|
||||
{ value: 'udp', label: 'UDP' },
|
||||
{ value: 'icmp', label: 'ICMP' },
|
||||
] },
|
||||
{ key: 'dst-port', label: 'Destination Port', type: 'number', placeholder: '22' },
|
||||
{ key: 'src-address', label: 'Source Address', type: 'text', placeholder: '192.168.88.0/24', help: 'Leave blank to match any source' },
|
||||
{ key: 'comment', label: 'Comment', type: 'text', placeholder: 'e.g., Allow SSH from LAN' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Address Lists',
|
||||
routerosPath: '/ip/firewall/address-list',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'list', label: 'List Name', type: 'text', required: true },
|
||||
{ key: 'address', label: 'Address', type: 'text', required: true, placeholder: '192.168.88.0/24' },
|
||||
{ key: 'comment', label: 'Comment', type: 'text' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'dns',
|
||||
label: 'DNS',
|
||||
icon: Server,
|
||||
description: 'DNS servers and local name resolution',
|
||||
sections: [
|
||||
{
|
||||
label: 'DNS Settings',
|
||||
routerosPath: '/ip/dns',
|
||||
isSingleton: true,
|
||||
fields: [
|
||||
{ key: 'servers', label: 'Upstream Servers', type: 'text', required: true, placeholder: '8.8.8.8,8.8.4.4', help: 'Comma-separated list of DNS server IPs used for name resolution' },
|
||||
{ key: 'allow-remote-requests', label: 'Allow Remote Requests', type: 'boolean', help: 'Allow devices on your network to use this router as their DNS server' },
|
||||
{ key: 'cache-size', label: 'Cache Size (KiB)', type: 'number', placeholder: '2048', help: 'DNS cache size in KiB' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Static Entries',
|
||||
routerosPath: '/ip/dns/static',
|
||||
isSingleton: false,
|
||||
fields: [
|
||||
{ key: 'name', label: 'Name', type: 'text', required: true, placeholder: 'myserver.local' },
|
||||
{ key: 'address', label: 'Address', type: 'ip', required: true, placeholder: '192.168.88.100' },
|
||||
{ key: 'type', label: 'Type', type: 'select', options: [
|
||||
{ value: 'A', label: 'A' },
|
||||
{ value: 'AAAA', label: 'AAAA' },
|
||||
{ value: 'CNAME', label: 'CNAME' },
|
||||
] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'system',
|
||||
label: 'System',
|
||||
icon: Settings,
|
||||
description: 'Device identity, time, passwords, and maintenance',
|
||||
sections: [
|
||||
{
|
||||
label: 'Identity',
|
||||
routerosPath: '/system/identity',
|
||||
isSingleton: true,
|
||||
fields: [
|
||||
{ key: 'name', label: 'Hostname', type: 'text', required: true, placeholder: 'e.g., Office-Router-1', help: 'A friendly name for this router, visible in the fleet dashboard' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Clock',
|
||||
routerosPath: '/system/clock',
|
||||
isSingleton: true,
|
||||
fields: [
|
||||
{ key: 'time-zone-name', label: 'Timezone', type: 'text', placeholder: 'America/New_York', help: 'IANA timezone identifier (e.g., America/New_York, Europe/London)' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'NTP Client',
|
||||
routerosPath: '/system/ntp/client',
|
||||
isSingleton: true,
|
||||
fields: [
|
||||
{ key: 'enabled', label: 'NTP Enabled', type: 'boolean' },
|
||||
{ key: 'server-dns-names', label: 'NTP Servers', type: 'text', placeholder: 'pool.ntp.org', help: 'Comma-separated NTP server hostnames' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'System Resource',
|
||||
routerosPath: '/system/resource',
|
||||
isSingleton: true,
|
||||
fields: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user