feat: install.sh One-Liner-Bootstrap + System-Adressen-Card auf IP-Page
scripts/install.sh: full curl-Onliner für Debian 13 trixie analog mail-gateway/scripts/install.sh — OS+Arch-Detection, Pre-flight- Tools, GPG-Key (nmg.asc, geteilt mit mail-gateway), APT-Source-Line trixie main, apt install edgeguard, Service-Smoke + healthz-Probe. Bestimmungsort: get.netcell-edgeguard.de (Hosting separat). UI: IP-Adressen-Page bekommt eine "Adressen am Kernel"-Card oben, analog zur Networks-Page. Listet jede vom Kernel sichtbare IP (lo + eth0 + …) mit Family-Tag (IPv4/IPv6) — read-only. Verwaltete Adressen darunter wie zuvor. User-Feedback: "die bestehenden IP-Adressen werden nicht angezeigt" — adressiert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { Button, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography, message } from 'antd'
|
||||
import { Button, Card, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography, message } from 'antd'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -19,6 +19,20 @@ interface IPAddress {
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
interface SystemInterface {
|
||||
ifname: string
|
||||
flags?: string[]
|
||||
link_type?: string
|
||||
addr_info?: Array<{ family: 'inet' | 'inet6'; local: string; prefixlen: number }>
|
||||
}
|
||||
|
||||
interface SystemAddress {
|
||||
ifname: string
|
||||
family: 'inet' | 'inet6'
|
||||
address: string
|
||||
prefix: number
|
||||
}
|
||||
|
||||
interface IPFormValues {
|
||||
interface_id: number
|
||||
address: string
|
||||
@@ -42,12 +56,38 @@ async function listIfaces(): Promise<NetworkInterface[]> {
|
||||
return (r.data.data as { interfaces?: NetworkInterface[] }).interfaces ?? []
|
||||
}
|
||||
|
||||
// Flatten /system/interfaces into one row per (ifname, address) so
|
||||
// the operator sees every kernel-side IP at a glance — including
|
||||
// addresses that EdgeGuard hasn't taken under management yet.
|
||||
async function listSystemAddresses(): Promise<SystemAddress[]> {
|
||||
const r = await apiClient.get('/system/interfaces')
|
||||
if (!isEnvelope(r.data)) return []
|
||||
const ifs = (r.data.data as { interfaces?: SystemInterface[] }).interfaces ?? []
|
||||
const out: SystemAddress[] = []
|
||||
for (const i of ifs) {
|
||||
for (const a of i.addr_info ?? []) {
|
||||
out.push({
|
||||
ifname: i.ifname,
|
||||
family: a.family,
|
||||
address: a.local,
|
||||
prefix: a.prefixlen,
|
||||
})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export default function IPAddressesPage() {
|
||||
const { t } = useTranslation()
|
||||
const qc = useQueryClient()
|
||||
|
||||
const { data: ips, isLoading } = useQuery({ queryKey: ['ip-addresses'], queryFn: listIPs })
|
||||
const { data: ifs } = useQuery({ queryKey: ['network-interfaces'], queryFn: listIfaces })
|
||||
const { data: sysAddrs } = useQuery({
|
||||
queryKey: ['system', 'addresses'],
|
||||
queryFn: listSystemAddresses,
|
||||
refetchInterval: 60_000,
|
||||
})
|
||||
|
||||
const ifaceLabel = (id: number) => {
|
||||
const i = ifs?.find((x) => x.id === id)
|
||||
@@ -122,6 +162,27 @@ export default function IPAddressesPage() {
|
||||
<div>
|
||||
<Typography.Title level={3}>{t('ips.title')}</Typography.Title>
|
||||
<Typography.Paragraph type="secondary">{t('ips.intro')}</Typography.Paragraph>
|
||||
|
||||
<Card title={t('ips.systemDiscovered')} size="small" style={{ marginBottom: 16 }}>
|
||||
{(sysAddrs ?? []).length === 0
|
||||
? <Typography.Text type="secondary">—</Typography.Text>
|
||||
: (
|
||||
<Table
|
||||
size="small"
|
||||
rowKey={(r) => `${r.ifname}-${r.address}`}
|
||||
dataSource={sysAddrs ?? []}
|
||||
pagination={false}
|
||||
columns={[
|
||||
{ title: t('ips.interface'), dataIndex: 'ifname', key: 'ifname', render: (s: string) => <code>{s}</code> },
|
||||
{ title: t('ips.address'), key: 'addr', render: (_, row: SystemAddress) => <code>{row.address}/{row.prefix}</code> },
|
||||
{ title: t('ips.family'), dataIndex: 'family', key: 'family', render: (f: string) => <Tag>{f === 'inet' ? 'IPv4' : 'IPv6'}</Tag> },
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Card>
|
||||
|
||||
<Typography.Title level={5} style={{ marginTop: 8 }}>{t('ips.managedTitle')}</Typography.Title>
|
||||
<Button type="primary" style={{ marginBottom: 16 }} onClick={() => {
|
||||
setCreating(true); form.resetFields()
|
||||
form.setFieldsValue({ prefix: 24, is_vip: false, active: true })
|
||||
|
||||
Reference in New Issue
Block a user