>('table')
}
diff --git a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/service.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/service.component.ts
index 31a5f2e88..a726300b1 100644
--- a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/service.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/service.component.ts
@@ -18,18 +18,15 @@ import { StatusComponent } from './status.component'
@Component({
selector: 'tr[appService]',
template: `
- |
+ |
|
{{ manifest.title }}
|
- |
+
+
+ |
{{ manifest.version }} |
@if (pkg.statusInfo.started; as started) {
@@ -45,7 +42,6 @@ import { StatusComponent } from './status.component'
:host {
@include taiga.transition(background);
- clip-path: inset(0 round 0.5rem);
cursor: pointer;
&:hover {
@@ -76,10 +72,14 @@ import { StatusComponent } from './status.component'
:host-context(tui-root._mobile) {
position: relative;
display: grid;
- grid-template: 1.25rem 1.5rem 1.5rem/4rem 1fr;
+ grid-template: 1.25rem 2rem 1.5rem/4rem 1fr;
align-items: center;
padding: 1rem;
+ &:hover {
+ background: none;
+ }
+
img {
height: 3rem;
width: 3rem;
diff --git a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/status.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/status.component.ts
index 04d2af2ed..974bcc794 100644
--- a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/status.component.ts
+++ b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/status.component.ts
@@ -15,12 +15,12 @@ import {
} from 'src/app/services/pkg-status-rendering.service'
@Component({
- selector: 'td[appStatus]',
+ selector: 'app-status',
template: `
@if (error()) {
} @else if (loading()) {
-
+
}
{{ statusText() | i18n }}
@@ -33,13 +33,14 @@ import {
:host {
display: flex;
align-items: center;
- gap: 0.5rem;
- height: 3rem;
+ gap: 0.25rem;
white-space: nowrap;
}
:host-context(tui-root._mobile) {
- height: auto;
+ tui-icon {
+ font-size: 1rem;
+ }
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/table.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/table.component.ts
new file mode 100644
index 000000000..9f38e1a91
--- /dev/null
+++ b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/table.component.ts
@@ -0,0 +1,111 @@
+import {
+ ChangeDetectionStrategy,
+ Component,
+ inject,
+ input,
+} from '@angular/core'
+import { FormsModule } from '@angular/forms'
+import { TableComponent } from 'src/app/routes/portal/components/table.component'
+import { T } from '@start9labs/start-sdk'
+import {
+ PackageDataEntry,
+ StateInfo,
+} from 'src/app/services/patch-db/data-model'
+import { ServiceComponent } from './service.component'
+import { TuiComparator, TuiTable } from '@taiga-ui/addon-table'
+import { getInstalledPrimaryStatus } from 'src/app/services/pkg-status-rendering.service'
+import { getManifest } from 'src/app/utils/get-package-data'
+import { ToManifestPipe } from '../../../pipes/to-manifest'
+import { toSignal } from '@angular/core/rxjs-interop'
+import { DepErrorService } from 'src/app/services/dep-error.service'
+import { i18nPipe } from '@start9labs/shared'
+import { TuiSkeleton } from '@taiga-ui/kit'
+import { PlaceholderComponent } from '../../../components/placeholder.component'
+import { TuiButton } from '@taiga-ui/core'
+import { RouterLink } from '@angular/router'
+
+@Component({
+ selector: '[services]',
+ template: `
+ @if (services()?.length === 0) {
+
+
+ {{ 'Welcome to' | i18n }}
+ StartOS!
+
+
+
+ {{
+ 'To get started, visit the Marketplace and download your first service'
+ | i18n
+ }}
+
+
+
+ {{ 'View Marketplace' | i18n }}
+
+
+ } @else {
+
+ @for (service of services() | tuiTableSort; track service) {
+
+ } @empty {
+ @for (_ of ['', '']; track $index) {
+
+ |
+ {{ 'Loading' | i18n }}
+ |
+
+ }
+ }
+
+ }
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [
+ FormsModule,
+ TableComponent,
+ ServiceComponent,
+ ToManifestPipe,
+ i18nPipe,
+ TuiSkeleton,
+ PlaceholderComponent,
+ TuiButton,
+ RouterLink,
+ TuiTable,
+ ],
+})
+export class ServicesTableComponent<
+ T extends T.PackageDataEntry & {
+ stateInfo: StateInfo
+ },
+> {
+ readonly errors = toSignal(inject(DepErrorService).depErrors$)
+
+ readonly services = input.required()
+
+ readonly name: TuiComparator = byName
+
+ readonly status: TuiComparator = (a, b) =>
+ getInstalledPrimaryStatus(b) > getInstalledPrimaryStatus(a) ? -1 : 1
+}
+
+function byName(a: PackageDataEntry, b: PackageDataEntry) {
+ return getManifest(b).title.toLowerCase() > getManifest(a).title.toLowerCase()
+ ? -1
+ : 1
+}
diff --git a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/ui-launch.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/ui-launch.component.ts
deleted file mode 100644
index ee7c9a804..000000000
--- a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/ui-launch.component.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-// import {
-// ChangeDetectionStrategy,
-// Component,
-// inject,
-// Input,
-// DOCUMENT,
-// } from '@angular/core'
-// import { i18nPipe } from '@start9labs/shared'
-// import { T } from '@start9labs/start-sdk'
-// import { tuiPure } from '@taiga-ui/cdk'
-// import { TuiDataList, TuiDropdown, TuiButton } from '@taiga-ui/core'
-// import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
-// import { InterfaceService } from '../../../components/interfaces/interface.service'
-// import { getInstalledPrimaryStatus } from 'src/app/services/pkg-status-rendering.service'
-
-// @Component({
-// selector: 'app-ui-launch',
-// template: `
-// @if (interfaces.length > 1) {
-//
-//
-//
-// @for (interface of interfaces; track $index) {
-//
-// {{ interface.name }}
-//
-// }
-//
-//
-// } @else if (interfaces[0]) {
-//
-// }
-// `,
-// styles: `
-// :host-context(tui-root._mobile) *::before {
-// font-size: 1.5rem !important;
-// }
-// `,
-// changeDetection: ChangeDetectionStrategy.OnPush,
-// imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe],
-// })
-// export class UILaunchComponent {
-// private readonly interfaceService = inject(InterfaceService)
-// private readonly document = inject(DOCUMENT)
-
-// @Input()
-// pkg!: PackageDataEntry
-
-// get interfaces(): readonly T.ServiceInterface[] {
-// return this.getInterfaces(this.pkg)
-// }
-
-// get isRunning(): boolean {
-// return getInstalledPrimaryStatus(this.pkg) === 'running'
-// }
-
-// @tuiPure
-// getInterfaces(pkg?: PackageDataEntry): T.ServiceInterface[] {
-// return pkg
-// ? Object.values(pkg.serviceInterfaces).filter(
-// i =>
-// i.type === 'ui' &&
-// (i.addressInfo.scheme === 'http' ||
-// i.addressInfo.sslScheme === 'https'),
-// )
-// : []
-// }
-
-// getHref(ui: T.ServiceInterface): string {
-// const host = this.pkg.hosts[ui.addressInfo.hostId]
-// if (!host) return ''
-// return this.interfaceService.launchableAddress(ui, host)
-// }
-
-// openUI(ui: T.ServiceInterface) {
-// this.document.defaultView?.open(this.getHref(ui), '_blank', 'noreferrer')
-// }
-// }
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 12d87af02..f0798b49a 100644
--- a/web/projects/ui/src/app/services/api/api.types.ts
+++ b/web/projects/ui/src/app/services/api/api.types.ts
@@ -73,14 +73,14 @@ export namespace RR {
uptime: number // seconds
}
- export type GetServerLogsReq = FetchLogsReq // server.logs & server.kernel-logs
+ export type GetServerLogsReq = FetchLogsReq // server.logs & server.kernel-logs & net.tor.logs
export type GetServerLogsRes = FetchLogsRes
export type FollowServerLogsReq = {
limit?: number // (optional) default is 50. Ignored if cursor provided
boot?: number | string | null // (optional) number is offset (0: current, -1 prev, +1 first), string is a specific boot id, null is all. Default is undefined
cursor?: string // the last known log. Websocket will return all logs since this log
- } // server.logs.follow & server.kernel-logs.follow
+ } // server.logs.follow & server.kernel-logs.follow & net.tor.follow-logs
export type FollowServerLogsRes = {
startCursor: string
guid: string
diff --git a/web/projects/ui/src/app/services/api/embassy-api.service.ts b/web/projects/ui/src/app/services/api/embassy-api.service.ts
index 8f33892b4..e38b6c8ce 100644
--- a/web/projects/ui/src/app/services/api/embassy-api.service.ts
+++ b/web/projects/ui/src/app/services/api/embassy-api.service.ts
@@ -86,6 +86,8 @@ export abstract class ApiService {
params: RR.GetServerLogsReq,
): Promise
+ abstract getTorLogs(params: RR.GetServerLogsReq): Promise
+
abstract getKernelLogs(
params: RR.GetServerLogsReq,
): Promise
@@ -94,6 +96,10 @@ export abstract class ApiService {
params: RR.FollowServerLogsReq,
): Promise
+ abstract followTorLogs(
+ params: RR.FollowServerLogsReq,
+ ): Promise
+
abstract followKernelLogs(
params: RR.FollowServerLogsReq,
): Promise
diff --git a/web/projects/ui/src/app/services/api/embassy-live-api.service.ts b/web/projects/ui/src/app/services/api/embassy-live-api.service.ts
index 24117297f..8f2bc6795 100644
--- a/web/projects/ui/src/app/services/api/embassy-live-api.service.ts
+++ b/web/projects/ui/src/app/services/api/embassy-live-api.service.ts
@@ -206,6 +206,10 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'server.logs', params })
}
+ async getTorLogs(params: RR.GetServerLogsReq): Promise {
+ return this.rpcRequest({ method: 'net.tor.logs', params })
+ }
+
async getKernelLogs(
params: RR.GetServerLogsReq,
): Promise {
@@ -218,6 +222,12 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'server.logs.follow', params })
}
+ async followTorLogs(
+ params: RR.FollowServerLogsReq,
+ ): Promise {
+ return this.rpcRequest({ method: 'net.tor.logs.follow', params })
+ }
+
async followKernelLogs(
params: RR.FollowServerLogsReq,
): Promise {
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 9641b8f78..ef19313af 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
@@ -290,6 +290,17 @@ export class MockApiService extends ApiService {
}
}
+ async getTorLogs(params: RR.GetServerLogsReq): Promise {
+ await pauseFor(2000)
+ const entries = this.randomLogs(params.limit)
+
+ return {
+ entries,
+ startCursor: 'start-cursor',
+ endCursor: 'end-cursor',
+ }
+ }
+
async getKernelLogs(
params: RR.GetServerLogsReq,
): Promise {
@@ -313,6 +324,16 @@ export class MockApiService extends ApiService {
}
}
+ async followTorLogs(
+ params: RR.FollowServerLogsReq,
+ ): Promise {
+ await pauseFor(2000)
+ return {
+ startCursor: 'start-cursor',
+ guid: 'logs-guid',
+ }
+ }
+
async followKernelLogs(
params: RR.FollowServerLogsReq,
): Promise {
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 6d2baf1fd..6eb52aa63 100644
--- a/web/projects/ui/src/app/services/api/mock-patch.ts
+++ b/web/projects/ui/src/app/services/api/mock-patch.ts
@@ -222,6 +222,167 @@ export const mockPatchData: DataModel = {
kiosk: true,
},
packageData: {
+ lnd: {
+ stateInfo: {
+ state: 'installed',
+ manifest: {
+ ...Mock.MockManifestLnd,
+ version: '0.11.0:0.0.1',
+ },
+ },
+ s9pk: '/media/startos/data/package-data/archive/installed/asdfasdf.s9pk',
+ icon: '/assets/img/service-icons/lnd.png',
+ lastBackup: null,
+ statusInfo: {
+ desired: { main: 'stopped' },
+ error: null,
+ health: {},
+ started: null,
+ },
+ actions: {
+ config: {
+ name: 'Config',
+ description: 'LND needs configuration before starting',
+ warning: null,
+ visibility: 'enabled',
+ allowedStatuses: 'any',
+ hasInput: true,
+ group: null,
+ },
+ connect: {
+ name: 'Connect',
+ description: 'View LND connection details',
+ warning: null,
+ visibility: 'enabled',
+ allowedStatuses: 'any',
+ hasInput: true,
+ group: 'Connecting',
+ },
+ },
+ serviceInterfaces: {
+ grpc: {
+ id: 'grpc',
+ masked: false,
+ name: 'GRPC',
+ description:
+ 'Used by dependent services and client wallets for connecting to your node',
+ type: 'api',
+ addressInfo: {
+ username: null,
+ hostId: 'qrstuv',
+ internalPort: 10009,
+ scheme: null,
+ sslScheme: 'grpc',
+ suffix: '',
+ },
+ },
+ lndconnect: {
+ id: 'lndconnect',
+ masked: true,
+ name: 'LND Connect',
+ description:
+ 'Used by client wallets adhering to LND Connect protocol to connect to your node',
+ type: 'api',
+ addressInfo: {
+ username: null,
+ hostId: 'qrstuv',
+ internalPort: 10009,
+ scheme: null,
+ sslScheme: 'lndconnect',
+ suffix: 'cert=askjdfbjadnaskjnd&macaroon=ksjbdfnhjasbndjksand',
+ },
+ },
+ p2p: {
+ id: 'p2p',
+ masked: false,
+ name: 'P2P',
+ description:
+ 'Used for connecting to other nodes on the Bitcoin network',
+ type: 'p2p',
+ addressInfo: {
+ username: null,
+ hostId: 'rstuvw',
+ internalPort: 8333,
+ scheme: 'bitcoin',
+ sslScheme: null,
+ suffix: '',
+ },
+ },
+ },
+ currentDependencies: {
+ bitcoind: {
+ title: Mock.BitcoinDep.title,
+ icon: Mock.BitcoinDep.icon,
+ kind: 'running',
+ versionRange: '>=26.0.0',
+ healthChecks: [],
+ },
+ 'btc-rpc-proxy': {
+ title: Mock.ProxyDep.title,
+ icon: Mock.ProxyDep.icon,
+ kind: 'running',
+ versionRange: '>2.0.0',
+ healthChecks: [],
+ },
+ },
+ hosts: {},
+ storeExposedDependents: [],
+ registry: 'https://registry.start9.com/',
+ developerKey: 'developer-key',
+ tasks: {
+ config: {
+ active: true,
+ task: {
+ packageId: 'lnd',
+ actionId: 'config',
+ severity: 'critical',
+ reason: 'LND needs configuration before starting',
+ },
+ },
+ connect: {
+ active: true,
+ task: {
+ packageId: 'lnd',
+ actionId: 'connect',
+ severity: 'important',
+ reason: 'View LND connection details',
+ },
+ },
+ 'bitcoind/config': {
+ active: true,
+ task: {
+ packageId: 'bitcoind',
+ actionId: 'config',
+ severity: 'critical',
+ reason: 'LND likes BTC a certain way',
+ input: {
+ kind: 'partial',
+ value: {
+ color: '#ffffff',
+ testnet: false,
+ },
+ },
+ },
+ },
+ 'bitcoind/rpc': {
+ active: true,
+ task: {
+ packageId: 'bitcoind',
+ actionId: 'rpc',
+ severity: 'important',
+ reason: `LND want's its own RPC credentials`,
+ input: {
+ kind: 'partial',
+ value: {
+ rpcsettings: {
+ rpcuser: 'lnd',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
bitcoind: {
stateInfo: {
state: 'installed',
@@ -511,166 +672,5 @@ export const mockPatchData: DataModel = {
},
},
},
- lnd: {
- stateInfo: {
- state: 'installed',
- manifest: {
- ...Mock.MockManifestLnd,
- version: '0.11.0:0.0.1',
- },
- },
- s9pk: '/media/startos/data/package-data/archive/installed/asdfasdf.s9pk',
- icon: '/assets/img/service-icons/lnd.png',
- lastBackup: null,
- statusInfo: {
- desired: { main: 'stopped' },
- error: null,
- health: {},
- started: null,
- },
- actions: {
- config: {
- name: 'Config',
- description: 'LND needs configuration before starting',
- warning: null,
- visibility: 'enabled',
- allowedStatuses: 'any',
- hasInput: true,
- group: null,
- },
- connect: {
- name: 'Connect',
- description: 'View LND connection details',
- warning: null,
- visibility: 'enabled',
- allowedStatuses: 'any',
- hasInput: true,
- group: 'Connecting',
- },
- },
- serviceInterfaces: {
- grpc: {
- id: 'grpc',
- masked: false,
- name: 'GRPC',
- description:
- 'Used by dependent services and client wallets for connecting to your node',
- type: 'api',
- addressInfo: {
- username: null,
- hostId: 'qrstuv',
- internalPort: 10009,
- scheme: null,
- sslScheme: 'grpc',
- suffix: '',
- },
- },
- lndconnect: {
- id: 'lndconnect',
- masked: true,
- name: 'LND Connect',
- description:
- 'Used by client wallets adhering to LND Connect protocol to connect to your node',
- type: 'api',
- addressInfo: {
- username: null,
- hostId: 'qrstuv',
- internalPort: 10009,
- scheme: null,
- sslScheme: 'lndconnect',
- suffix: 'cert=askjdfbjadnaskjnd&macaroon=ksjbdfnhjasbndjksand',
- },
- },
- p2p: {
- id: 'p2p',
- masked: false,
- name: 'P2P',
- description:
- 'Used for connecting to other nodes on the Bitcoin network',
- type: 'p2p',
- addressInfo: {
- username: null,
- hostId: 'rstuvw',
- internalPort: 8333,
- scheme: 'bitcoin',
- sslScheme: null,
- suffix: '',
- },
- },
- },
- currentDependencies: {
- bitcoind: {
- title: Mock.BitcoinDep.title,
- icon: Mock.BitcoinDep.icon,
- kind: 'running',
- versionRange: '>=26.0.0',
- healthChecks: [],
- },
- 'btc-rpc-proxy': {
- title: Mock.ProxyDep.title,
- icon: Mock.ProxyDep.icon,
- kind: 'running',
- versionRange: '>2.0.0',
- healthChecks: [],
- },
- },
- hosts: {},
- storeExposedDependents: [],
- registry: 'https://registry.start9.com/',
- developerKey: 'developer-key',
- tasks: {
- config: {
- active: true,
- task: {
- packageId: 'lnd',
- actionId: 'config',
- severity: 'critical',
- reason: 'LND needs configuration before starting',
- },
- },
- connect: {
- active: true,
- task: {
- packageId: 'lnd',
- actionId: 'connect',
- severity: 'important',
- reason: 'View LND connection details',
- },
- },
- 'bitcoind/config': {
- active: true,
- task: {
- packageId: 'bitcoind',
- actionId: 'config',
- severity: 'critical',
- reason: 'LND likes BTC a certain way',
- input: {
- kind: 'partial',
- value: {
- color: '#ffffff',
- testnet: false,
- },
- },
- },
- },
- 'bitcoind/rpc': {
- active: true,
- task: {
- packageId: 'bitcoind',
- actionId: 'rpc',
- severity: 'important',
- reason: `LND want's its own RPC credentials`,
- input: {
- kind: 'partial',
- value: {
- rpcsettings: {
- rpcuser: 'lnd',
- },
- },
- },
- },
- },
- },
- },
},
}
|