diff --git a/web/projects/ui/src/app/components/notifications-toast.component.ts b/web/projects/ui/src/app/components/notifications-toast.component.ts index 0920b3393..8532ba196 100644 --- a/web/projects/ui/src/app/components/notifications-toast.component.ts +++ b/web/projects/ui/src/app/components/notifications-toast.component.ts @@ -28,7 +28,7 @@ export class NotificationsToastComponent { readonly visible$: Observable = merge( this.dismiss$, inject>(PatchDB) - .watch$('serverInfo', 'unreadNotifications', 'count') + .watch$('serverInfo', 'unreadNotificationCount') .pipe( pairwise(), map(([prev, cur]) => cur > prev), diff --git a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts index 930826c4f..508966aff 100644 --- a/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts +++ b/web/projects/ui/src/app/routes/portal/components/interfaces/interface.utils.ts @@ -22,14 +22,14 @@ export const REMOVE: Partial> = { export function getClearnetSpec({ domains, - start9ToSubdomain, + start9To, }: NetworkInfo): Promise { - const start9ToDomain = `${start9ToSubdomain?.value}.start9.to` - const base = start9ToSubdomain ? { [start9ToDomain]: start9ToDomain } : {} + const start9ToDomain = `${start9To?.subdomain}.start9.to` + const base = start9To ? { [start9ToDomain]: start9ToDomain } : {} - const values = domains.reduce((prev, curr) => { + const values = Object.keys(domains).reduce((prev, curr) => { return { - [curr.value]: curr.value, + [curr]: curr, ...prev, } }, base) @@ -38,7 +38,7 @@ export function getClearnetSpec({ ISB.InputSpec.of({ domain: ISB.Value.select({ name: 'Domain', - default: domains[0].value, + default: '', values, }), subdomain: ISB.Value.text({ diff --git a/web/projects/ui/src/app/routes/portal/routes/system/settings/routes/domains/domains.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/settings/routes/domains/domains.component.ts index b84983e0c..75a5001d4 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/settings/routes/domains/domains.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/settings/routes/domains/domains.component.ts @@ -68,23 +68,14 @@ export class SettingsDomainsComponent { private readonly api = inject(ApiService) private readonly dialogs = inject(TuiDialogService) - readonly domains$ = this.patch.watch$('serverInfo', 'network').pipe( - map(network => { - const start9ToSubdomain = network.start9ToSubdomain - const start9To = !start9ToSubdomain - ? [] - : [ - { - ...start9ToSubdomain, - value: `${start9ToSubdomain.value}.start9.to`, - provider: 'Start9', - }, - ] - - return { start9To, custom: network.domains } - }), + private readonly start9To$ = this.patch.watch$( + 'serverInfo', + 'network', + 'start9To', ) + readonly domains$ = this.patch.watch$('serverInfo', 'network', 'domains') + delete(hostname?: string) { this.dialogs .open(TUI_CONFIRM, { diff --git a/web/projects/ui/src/app/routes/portal/routes/system/settings/routes/interfaces/ui.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/settings/routes/interfaces/ui.component.ts index f3e913ad0..f7bf0fd3a 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/settings/routes/interfaces/ui.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/settings/routes/interfaces/ui.component.ts @@ -46,12 +46,12 @@ export class StartOsUiComponent { readonly ui$: Observable = inject>( PatchDB, ) - .watch$('serverInfo') + .watch$('serverInfo', 'network', 'host') .pipe( - map(server => ({ + map(host => ({ ...iface, - public: server.host.bindings[iface.addressInfo.internalPort].net.public, - addresses: getAddresses(iface, server.host, this.config), + public: host.bindings[iface.addressInfo.internalPort].net.public, + addresses: getAddresses(iface, host, this.config), })), ) } diff --git a/web/projects/ui/src/app/routes/portal/routes/system/settings/settings.routes.ts b/web/projects/ui/src/app/routes/portal/routes/system/settings/settings.routes.ts index bd8779873..756624490 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/settings/settings.routes.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/settings/settings.routes.ts @@ -19,27 +19,27 @@ export default [ m => m.SettingsEmailComponent, ), }, - { - path: 'domains', - loadComponent: () => - import('./routes/domains/domains.component').then( - m => m.SettingsDomainsComponent, - ), - }, - { - path: 'proxies', - loadComponent: () => - import('./routes/proxies/proxies.component').then( - m => m.SettingsProxiesComponent, - ), - }, - { - path: 'router', - loadComponent: () => - import('./routes/router/router.component').then( - m => m.SettingsRouterComponent, - ), - }, + // { + // path: 'domains', + // loadComponent: () => + // import('./routes/domains/domains.component').then( + // m => m.SettingsDomainsComponent, + // ), + // }, + // { + // path: 'proxies', + // loadComponent: () => + // import('./routes/proxies/proxies.component').then( + // m => m.SettingsProxiesComponent, + // ), + // }, + // { + // path: 'router', + // loadComponent: () => + // import('./routes/router/router.component').then( + // m => m.SettingsRouterComponent, + // ), + // }, { path: 'wifi', loadComponent: () => diff --git a/web/projects/ui/src/app/routes/portal/routes/system/settings/settings.service.ts b/web/projects/ui/src/app/routes/portal/routes/system/settings/settings.service.ts index 19ea7f45e..7b4a3c030 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/settings/settings.service.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/settings/settings.service.ts @@ -63,24 +63,24 @@ export class SettingsService { }, ], Network: [ - { - title: 'Domains', - description: 'Manage domains for clearnet connectivity', - icon: '@tui.globe', - routerLink: 'domains', - }, - { - title: 'Proxies', - description: 'Manage proxies for inbound and outbound connections', - icon: '@tui.shuffle', - routerLink: 'proxies', - }, - { - title: 'Router Config', - description: 'Connect or configure your router for clearnet', - icon: '@tui.radio', - routerLink: 'router', - }, + // { + // title: 'Domains', + // description: 'Manage domains for clearnet connectivity', + // icon: '@tui.globe', + // routerLink: 'domains', + // }, + // { + // title: 'Proxies', + // description: 'Manage proxies for inbound and outbound connections', + // icon: '@tui.shuffle', + // routerLink: 'proxies', + // }, + // { + // title: 'Router Config', + // description: 'Connect or configure your router for clearnet', + // icon: '@tui.radio', + // routerLink: 'router', + // }, { title: 'WiFi', description: 'Add or remove WiFi networks', @@ -109,12 +109,12 @@ export class SettingsService { }, ], 'Privacy and Security': [ - { - title: 'Outbound Proxy', - description: 'Proxy outbound traffic from the StartOS main process', - icon: '@tui.shield', - action: () => this.setOutboundProxy(), - }, + // { + // title: 'Outbound Proxy', + // description: 'Proxy outbound traffic from the StartOS main process', + // icon: '@tui.shield', + // action: () => this.setOutboundProxy(), + // }, { title: 'SSH', description: @@ -154,12 +154,12 @@ export class SettingsService { ], } - private async setOutboundProxy(): Promise { - const proxy = await firstValueFrom( - this.patch.watch$('serverInfo', 'network', 'outboundProxy'), - ) - await this.proxyService.presentModalSetOutboundProxy(proxy) - } + // private async setOutboundProxy(): Promise { + // const proxy = await firstValueFrom( + // this.patch.watch$('serverInfo', 'network', 'outboundProxy'), + // ) + // await this.proxyService.presentModalSetOutboundProxy(proxy) + // } private promptResetTor() { this.wipe = false diff --git a/web/projects/ui/src/app/services/api/api.types.ts b/web/projects/ui/src/app/services/api/api.types.ts index cd7cd3000..0760d45ed 100644 --- a/web/projects/ui/src/app/services/api/api.types.ts +++ b/web/projects/ui/src/app/services/api/api.types.ts @@ -1,7 +1,4 @@ -import { - DomainInfo, - NetworkStrategy, -} from 'src/app/services/patch-db/data-model' +import { DomainInfo } from 'src/app/services/patch-db/data-model' import { FetchLogsReq, FetchLogsRes } from '@start9labs/shared' import { Dump } from 'patch-db-client' import { DataModel } from 'src/app/services/patch-db/data-model' @@ -184,7 +181,7 @@ export module RR { // domains - export type ClaimStart9ToReq = { networkStrategy: NetworkStrategy } // net.domain.me.claim + export type ClaimStart9ToReq = { networkInterfaceId: string } // net.domain.me.claim export type ClaimStart9ToRes = null export type DeleteStart9ToReq = {} // net.domain.me.delete @@ -197,7 +194,7 @@ export module RR { username: string | null password: string | null } - networkStrategy: NetworkStrategy + networkInterfaceId: string } // net.domain.add export type AddDomainRes = null @@ -677,10 +674,10 @@ export type ServerNotification = { export type NotificationData = T extends 0 ? null : T extends 1 - ? BackupReport - : T extends 2 - ? string - : any + ? BackupReport + : T extends 2 + ? string + : any export type BackupReport = { server: { diff --git a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 07ad55d98..17a29eaed 100644 --- a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -13,7 +13,6 @@ import { import { InstallingState, PackageDataEntry, - Proxy, StateInfo, UpdatingState, } from 'src/app/services/patch-db/data-model' @@ -574,25 +573,22 @@ export class MockApiService extends ApiService { async addProxy(params: RR.AddProxyReq): Promise { await pauseFor(2000) - const type: Proxy['type'] = 'inbound-outbound' - const patch = [ { - op: PatchOp.REPLACE, - path: '/serverInfo/network/proxies', - value: [ - { - id: 'abcd-efgh-ijkl-mnop', + op: PatchOp.ADD, + path: `/serverInfo/network/networkInterfaces/wga1`, + value: { + inbound: true, + outbound: true, + ipInfo: { name: params.name, - createdAt: new Date(), - type, - endpoint: '10.25.2.17', - usedBy: { - domains: [], - services: [], - }, + scopeId: 3, + deviceType: 'wireguard', + subnets: [], + wanIp: '1.1.1.1', + ntpServers: [], }, - ], + }, }, ] this.mockRevision(patch) @@ -639,12 +635,10 @@ export class MockApiService extends ApiService { const patch = [ { op: PatchOp.REPLACE, - path: '/serverInfo/network/start9ToSubdomain', + path: '/serverInfo/network/start9To', value: { - value: 'xyz', - createdAt: new Date(), - networkStrategy: params.networkStrategy, - usedBy: [], + subdomain: 'xyz', + networkInterfaceId: params.networkInterfaceId, }, }, ] @@ -660,7 +654,7 @@ export class MockApiService extends ApiService { const patch = [ { op: PatchOp.REPLACE, - path: '/serverInfo/network/start9ToSubdomain', + path: '/serverInfo/network/start9To', value: null, }, ] @@ -675,16 +669,13 @@ export class MockApiService extends ApiService { const patch = [ { op: PatchOp.REPLACE, - path: '/serverInfo/network/domains', - value: [ - { - value: params.hostname, - createdAt: new Date(), + path: `/serverInfo/network/domains`, + value: { + [params.hostname]: { + networkInterfaceId: params.networkInterfaceId, provider: params.provider.name, - networkStrategy: params.networkStrategy, - usedBy: [], }, - ], + }, }, ] this.mockRevision(patch) @@ -698,7 +689,7 @@ export class MockApiService extends ApiService { { op: PatchOp.REPLACE, path: '/serverInfo/network/domains', - value: [], + value: {}, }, ] this.mockRevision(patch) diff --git a/web/projects/ui/src/app/services/api/mock-patch.ts b/web/projects/ui/src/app/services/api/mock-patch.ts index 70d34d5b9..a26ce0759 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -39,37 +39,142 @@ export const mockPatchData: DataModel = { selected: null, lastRegion: null, }, - start9ToSubdomain: null, - domains: [], + start9To: null, + domains: {}, wanConfig: { upnp: true, forwards: [], }, - proxies: [], outboundProxy: null, - }, - networkInterfaces: { - eth0: { - public: false, - ipInfo: { - scopeId: 1, - deviceType: 'ethernet', - subnets: ['10.0.0.2/24'], - wanIp: null, - ntpServers: [], + host: { + bindings: { + 80: { + enabled: true, + net: { + assignedPort: null, + assignedSslPort: 443, + public: false, + }, + options: { + preferredExternalPort: 80, + addSsl: { + preferredExternalPort: 443, + alpn: { specified: ['http/1.1', 'h2'] }, + }, + secure: null, + }, + }, + }, + domains: {}, + onions: ['myveryownspecialtoraddress'], + hostnameInfo: { + 80: [ + { + kind: 'ip', + networkInterfaceId: 'eth0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: null, + sslPort: 443, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'wlan0', + public: false, + hostname: { + kind: 'local', + value: 'adjective-noun.local', + port: null, + sslPort: 443, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'eth0', + public: false, + hostname: { + kind: 'ipv4', + value: '10.0.0.1', + port: null, + sslPort: 443, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'wlan0', + public: false, + hostname: { + kind: 'ipv4', + value: '10.0.0.2', + port: null, + sslPort: 443, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'eth0', + public: false, + hostname: { + kind: 'ipv6', + value: 'fe80::cd00:0000:0cde:1257:0000:211e:72cd', + scopeId: 2, + port: null, + sslPort: 443, + }, + }, + { + kind: 'ip', + networkInterfaceId: 'wlan0', + public: false, + hostname: { + kind: 'ipv6', + value: 'fe80::cd00:0000:0cde:1257:0000:211e:1234', + scopeId: 3, + port: null, + sslPort: 443, + }, + }, + { + kind: 'onion', + hostname: { + value: 'myveryownspecialtoraddress.onion', + port: 80, + sslPort: 443, + }, + }, + ], }, }, - wlan0: { - public: false, - ipInfo: { - scopeId: 2, - deviceType: 'wireless', - subnets: [ - '10.0.90.12/24', - 'fe80::cd00:0000:0cde:1257:0000:211e:72cd/64', - ], - wanIp: null, - ntpServers: [], + networkInterfaces: { + eth0: { + inbound: false, + outbound: true, + ipInfo: { + name: 'Wired Connection 1', + scopeId: 1, + deviceType: 'ethernet', + subnets: ['10.0.0.2/24'], + wanIp: null, + ntpServers: [], + }, + }, + wlan0: { + inbound: false, + outbound: true, + ipInfo: { + name: 'Wireless Connection 1', + scopeId: 2, + deviceType: 'wireless', + subnets: [ + '10.0.90.12/24', + 'fe80::cd00:0000:0cde:1257:0000:211e:72cd/64', + ], + wanIp: null, + ntpServers: [], + }, }, }, }, @@ -78,10 +183,7 @@ export const mockPatchData: DataModel = { contact: ['mailto:support@start9.com'], }, }, - unreadNotifications: { - count: 4, - recent: Mock.Notifications, - }, + unreadNotificationCount: 4, // password is asdfasdf passwordHash: '$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ', @@ -96,108 +198,6 @@ export const mockPatchData: DataModel = { backupProgress: {}, }, hostname: 'random-words', - host: { - bindings: { - 80: { - enabled: true, - net: { - assignedPort: null, - assignedSslPort: 443, - public: false, - }, - options: { - preferredExternalPort: 80, - addSsl: { - preferredExternalPort: 443, - alpn: { specified: ['http/1.1', 'h2'] }, - }, - secure: null, - }, - }, - }, - domains: {}, - onions: ['myveryownspecialtoraddress'], - hostnameInfo: { - 80: [ - { - kind: 'ip', - networkInterfaceId: 'eth0', - public: false, - hostname: { - kind: 'local', - value: 'adjective-noun.local', - port: null, - sslPort: 443, - }, - }, - { - kind: 'ip', - networkInterfaceId: 'wlan0', - public: false, - hostname: { - kind: 'local', - value: 'adjective-noun.local', - port: null, - sslPort: 443, - }, - }, - { - kind: 'ip', - networkInterfaceId: 'eth0', - public: false, - hostname: { - kind: 'ipv4', - value: '10.0.0.1', - port: null, - sslPort: 443, - }, - }, - { - kind: 'ip', - networkInterfaceId: 'wlan0', - public: false, - hostname: { - kind: 'ipv4', - value: '10.0.0.2', - port: null, - sslPort: 443, - }, - }, - { - kind: 'ip', - networkInterfaceId: 'eth0', - public: false, - hostname: { - kind: 'ipv6', - value: 'fe80::cd00:0000:0cde:1257:0000:211e:72cd', - scopeId: 2, - port: null, - sslPort: 443, - }, - }, - { - kind: 'ip', - networkInterfaceId: 'wlan0', - public: false, - hostname: { - kind: 'ipv6', - value: 'fe80::cd00:0000:0cde:1257:0000:211e:1234', - scopeId: 3, - port: null, - sslPort: 443, - }, - }, - { - kind: 'onion', - hostname: { - value: 'myveryownspecialtoraddress.onion', - port: 80, - sslPort: 443, - }, - }, - ], - }, - }, pubkey: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m', caFingerprint: 'SHA-256: 63 2B 11 99 44 40 17 DF 37 FC C3 DF 0F 3D 15', ntpSynced: false, diff --git a/web/projects/ui/src/app/services/notification.service.ts b/web/projects/ui/src/app/services/notification.service.ts index 6d76b62f2..dcbda3158 100644 --- a/web/projects/ui/src/app/services/notification.service.ts +++ b/web/projects/ui/src/app/services/notification.service.ts @@ -20,7 +20,7 @@ export class NotificationService { private readonly localUnreadCount$ = new Subject() readonly unreadCount$ = merge( - this.patch.watch$('serverInfo', 'unreadNotifications', 'count'), + this.patch.watch$('serverInfo', 'unreadNotificationCount'), this.localUnreadCount$, ).pipe(shareReplay(1)) diff --git a/web/projects/ui/src/app/services/patch-db/data-model.ts b/web/projects/ui/src/app/services/patch-db/data-model.ts index 678d2448d..e7dd72fa8 100644 --- a/web/projects/ui/src/app/services/patch-db/data-model.ts +++ b/web/projects/ui/src/app/services/patch-db/data-model.ts @@ -1,17 +1,13 @@ -import { BackupJob, ServerNotifications } from '../api/api.types' +import { BackupJob } from '../api/api.types' import { T } from '@start9labs/start-sdk' export type DataModel = { ui: UIData serverInfo: Omit< T.Public['serverInfo'], - 'wifi' | 'unreadNotificationCount' + 'wifi' | 'networkInterfaces' | 'host' > & { network: NetworkInfo - unreadNotifications: { - count: number - recent: ServerNotifications - } } packageData: Record } @@ -42,14 +38,30 @@ export type UIStore = { } export type NetworkInfo = { - wifi: WiFiInfo - start9ToSubdomain: Omit | null - domains: Domain[] + wifi: T.WifiInfo & { enabled: boolean } + host: T.Host + networkInterfaces: { + [id: string]: { + inbound: boolean | null + outbound: boolean | null + ipInfo: + | (T.IpInfo & { + name: string + }) + | null + } + } + start9To: { + subdomain: string + networkInterfaceId: string + } | null + domains: { + [key: string]: Domain + } wanConfig: { upnp: boolean forwards: PortForward[] } - proxies: Proxy[] outboundProxy: string | null } @@ -65,40 +77,9 @@ export type PortForward = { error: string | null } -export type WiFiInfo = { - enabled: boolean - lastRegion: string | null - interface: string | null - ssids: Array - selected: string | null -} - export type Domain = { - value: string - createdAt: string provider: string - networkStrategy: NetworkStrategy - usedBy: { - service: { id: string | null; title: string } // null means startos - interfaces: { id: string | null; title: string }[] // null means startos - }[] -} - -export type NetworkStrategy = - | { proxy: string } - | { ipStrategy: 'ipv4' | 'ipv6' | 'dualstack' } - -export type Proxy = { - id: string - name: string - createdAt: string - type: 'outbound' | 'inbound-outbound' | 'vlan' | { error: string } - endpoint: string - // below is overlay only - usedBy: { - services: { id: string | null; title: string }[] // implies outbound - null means startos - domains: string[] // implies inbound - } + networkInterfaceId: string } export interface ServerStatusInfo { diff --git a/web/projects/ui/src/app/services/proxy.service.ts b/web/projects/ui/src/app/services/proxy.service.ts index 3f9df92a3..2f3f487f4 100644 --- a/web/projects/ui/src/app/services/proxy.service.ts +++ b/web/projects/ui/src/app/services/proxy.service.ts @@ -26,18 +26,20 @@ export class ProxyService { ) {} async presentModalSetOutboundProxy(current: string | null, pkgId?: string) { - const network = await firstValueFrom( - this.patch.watch$('serverInfo', 'network'), + const networkInterfaces = await firstValueFrom( + this.patch.watch$('serverInfo', 'network', 'networkInterfaces'), ) const config = ISB.InputSpec.of({ proxyId: ISB.Value.select({ name: 'Select Proxy', default: current || '', - values: network.proxies - .filter(p => p.type === 'outbound' || p.type === 'inbound-outbound') + values: Object.entries(networkInterfaces) + .filter( + ([_, n]) => n.outbound && n.ipInfo?.deviceType === 'wireguard', + ) .reduce>( - (prev, curr) => ({ - [curr.id]: curr.name, + (prev, [id, n]) => ({ + [id]: n.ipInfo!.name, ...prev, }), {},