diff --git a/web/projects/shared/src/i18n/dictionaries/de.ts b/web/projects/shared/src/i18n/dictionaries/de.ts index ca331b440..46e340184 100644 --- a/web/projects/shared/src/i18n/dictionaries/de.ts +++ b/web/projects/shared/src/i18n/dictionaries/de.ts @@ -88,6 +88,8 @@ export default { 88: 'Aktionen', 89: 'nicht empfohlen', 90: 'Root-CA ist vertrauenswürdig!', + 91: 'Installierte Dienste', + 92: 'Diagnosen für den Tor-Daemon auf diesem Server', 96: 'Öffentliche Domain hinzufügen', 97: 'Wird entfernt', 100: 'Nicht gespeicherte Änderungen', diff --git a/web/projects/shared/src/i18n/dictionaries/en.ts b/web/projects/shared/src/i18n/dictionaries/en.ts index f831c47e7..3e2bb3f6a 100644 --- a/web/projects/shared/src/i18n/dictionaries/en.ts +++ b/web/projects/shared/src/i18n/dictionaries/en.ts @@ -87,6 +87,8 @@ export const ENGLISH = { 'Actions': 88, // as in, actions available to the user 'not recommended': 89, 'Root CA Trusted!': 90, + 'Installed services': 91, // as in, software services installed on this computer + 'Diagnostics for the Tor daemon on this server': 92, 'Add public domain': 96, 'Removing': 97, 'Unsaved changes': 100, diff --git a/web/projects/shared/src/i18n/dictionaries/es.ts b/web/projects/shared/src/i18n/dictionaries/es.ts index 7661e6d8e..9bbfa75b7 100644 --- a/web/projects/shared/src/i18n/dictionaries/es.ts +++ b/web/projects/shared/src/i18n/dictionaries/es.ts @@ -88,6 +88,8 @@ export default { 88: 'Acciones', 89: 'no recomendado', 90: '¡CA raíz confiable!', + 91: 'Servicios instalados', + 92: 'Diagnósticos para el demonio Tor en este servidor', 96: 'Agregar dominio público', 97: 'Eliminando', 100: 'Cambios no guardados', diff --git a/web/projects/shared/src/i18n/dictionaries/fr.ts b/web/projects/shared/src/i18n/dictionaries/fr.ts index 2caf0d027..46855f388 100644 --- a/web/projects/shared/src/i18n/dictionaries/fr.ts +++ b/web/projects/shared/src/i18n/dictionaries/fr.ts @@ -88,6 +88,8 @@ export default { 88: 'Actions', 89: 'non recommandé', 90: 'Certificat racine approuvé !', + 91: 'Services installés', + 92: 'Diagnostics pour le service Tor sur ce serveur', 96: 'Ajouter un domaine public', 97: 'Suppression', 100: 'Modifications non enregistrées', diff --git a/web/projects/shared/src/i18n/dictionaries/pl.ts b/web/projects/shared/src/i18n/dictionaries/pl.ts index 4674aaef7..4e3da7b76 100644 --- a/web/projects/shared/src/i18n/dictionaries/pl.ts +++ b/web/projects/shared/src/i18n/dictionaries/pl.ts @@ -88,6 +88,8 @@ export default { 88: 'Akcje', 89: 'niezalecane', 90: 'Główny certyfikat CA zaufany!', + 91: 'Zainstalowane usługi', + 92: 'Diagnostyka demona Tor na tym serwerze', 96: 'Dodaj domenę publiczną', 97: 'Usuwanie', 100: 'Niezapisane zmiany', diff --git a/web/projects/ui/src/app/routes/portal/components/placeholder.component.ts b/web/projects/ui/src/app/routes/portal/components/placeholder.component.ts index ca32ee3bc..0f058f8ee 100644 --- a/web/projects/ui/src/app/routes/portal/components/placeholder.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/placeholder.component.ts @@ -3,7 +3,12 @@ import { TuiIcon } from '@taiga-ui/core' @Component({ selector: 'app-placeholder', - template: '', + template: ` + @if (icon(); as icon) { + + } + + `, styles: ` :host { display: flex; @@ -26,5 +31,5 @@ import { TuiIcon } from '@taiga-ui/core' imports: [TuiIcon], }) export class PlaceholderComponent { - readonly icon = input.required() + readonly icon = input() } diff --git a/web/projects/ui/src/app/routes/portal/components/table.component.ts b/web/projects/ui/src/app/routes/portal/components/table.component.ts index c0b3c07ba..fb16d0f9c 100644 --- a/web/projects/ui/src/app/routes/portal/components/table.component.ts +++ b/web/projects/ui/src/app/routes/portal/components/table.component.ts @@ -1,5 +1,10 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core' import { i18nKey, i18nPipe } from '@start9labs/shared' +import { + TuiComparator, + TuiTable, + TuiTableDirective, +} from '@taiga-ui/addon-table' @Component({ selector: 'table[appTable]', @@ -8,7 +13,13 @@ import { i18nKey, i18nPipe } from '@start9labs/shared' @for (header of appTable(); track $index) { - {{ header | i18n }} + + {{ header | i18n }} + } @@ -22,9 +33,16 @@ import { i18nKey, i18nPipe } from '@start9labs/shared' } `, host: { class: 'g-table' }, + hostDirectives: [ + { + directive: TuiTableDirective, + inputs: ['sorter'], + }, + ], changeDetection: ChangeDetectionStrategy.OnPush, - imports: [i18nPipe], + imports: [i18nPipe, TuiTable], }) export class TableComponent { readonly appTable = input.required>() + readonly appTableSorters = input | null>>([]) } diff --git a/web/projects/ui/src/app/routes/portal/routes/logs/logs.routes.ts b/web/projects/ui/src/app/routes/portal/routes/logs/logs.routes.ts index c42623ea9..281683fcd 100644 --- a/web/projects/ui/src/app/routes/portal/routes/logs/logs.routes.ts +++ b/web/projects/ui/src/app/routes/portal/routes/logs/logs.routes.ts @@ -13,6 +13,10 @@ export const ROUTES: Routes = [ path: 'os', loadComponent: () => import('./routes/os.component'), }, + { + path: 'tor', + loadComponent: () => import('./routes/tor.component'), + }, ] export default ROUTES diff --git a/web/projects/ui/src/app/routes/portal/routes/logs/routes/outlet.component.ts b/web/projects/ui/src/app/routes/portal/routes/logs/routes/outlet.component.ts index 524a997da..97d3f69cd 100644 --- a/web/projects/ui/src/app/routes/portal/routes/logs/routes/outlet.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/logs/routes/outlet.component.ts @@ -79,6 +79,12 @@ export default class SystemLogsComponent { subtitle: 'Raw, unfiltered operating system logs', icon: '@tui.square-dashed-bottom-code', }, + { + link: 'tor', + title: 'Tor Logs', + subtitle: 'Diagnostics for the Tor daemon on this server', + icon: '@tui.target', + }, { link: 'kernel', title: 'Kernel Logs', diff --git a/web/projects/ui/src/app/routes/portal/routes/logs/routes/tor.component.ts b/web/projects/ui/src/app/routes/portal/routes/logs/routes/tor.component.ts new file mode 100644 index 000000000..45b711fa5 --- /dev/null +++ b/web/projects/ui/src/app/routes/portal/routes/logs/routes/tor.component.ts @@ -0,0 +1,32 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { i18nPipe } from '@start9labs/shared' +import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.component' +import { LogsHeaderComponent } from 'src/app/routes/portal/routes/logs/components/header.component' +import { RR } from 'src/app/services/api/api.types' +import { ApiService } from 'src/app/services/api/embassy-api.service' + +@Component({ + template: ` + + {{ 'Diagnostics for the Tor daemon on this server' | i18n }} + + + `, + styles: ` + :host { + padding: 1rem; + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [LogsComponent, LogsHeaderComponent, i18nPipe], + host: { class: 'g-page' }, +}) +export default class SystemTorComponent { + private readonly api = inject(ApiService) + + protected readonly follow = (params: RR.FollowServerLogsReq) => + this.api.followTorLogs(params) + + protected readonly fetch = (params: RR.GetServerLogsReq) => + this.api.getTorLogs(params) +} diff --git a/web/projects/ui/src/app/routes/portal/routes/marketplace/marketplace.component.ts b/web/projects/ui/src/app/routes/portal/routes/marketplace/marketplace.component.ts index 2c6cc5a6d..1df60d592 100644 --- a/web/projects/ui/src/app/routes/portal/routes/marketplace/marketplace.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/marketplace/marketplace.component.ts @@ -167,6 +167,9 @@ export default class MarketplaceComponent { takeUntilDestroyed(), tap(params => { const registry = params.get('registry') + + this.categoryService.setQuery(params.get('search') || '') + if (!registry) { this.router.navigate([], { queryParams: { diff --git a/web/projects/ui/src/app/routes/portal/routes/notifications/notifications.component.ts b/web/projects/ui/src/app/routes/portal/routes/notifications/notifications.component.ts index d3f2ee98a..3a276a086 100644 --- a/web/projects/ui/src/app/routes/portal/routes/notifications/notifications.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/notifications/notifications.component.ts @@ -49,16 +49,6 @@ import { NotificationsTableComponent } from './table.component' :host { padding: 1rem; } - - :host-context(tui-root._mobile) { - header { - display: none; - } - - section { - padding-block: 0; - } - } `, host: { class: 'g-page' }, changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/web/projects/ui/src/app/routes/portal/routes/notifications/table.component.ts b/web/projects/ui/src/app/routes/portal/routes/notifications/table.component.ts index 87fb1f4c5..f3339b5a5 100644 --- a/web/projects/ui/src/app/routes/portal/routes/notifications/table.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/notifications/table.component.ts @@ -12,6 +12,7 @@ import { ServerNotification } from 'src/app/services/api/api.types' import { TableComponent } from 'src/app/routes/portal/components/table.component' import { NotificationItemComponent } from './item.component' import { i18nPipe } from '@start9labs/shared' +import { PlaceholderComponent } from '../../components/placeholder.component' @Component({ selector: '[notifications]', @@ -48,9 +49,9 @@ import { i18nPipe } from '@start9labs/shared' } @empty { @if (notifications()) { - - {{ 'No notifications' | i18n }} - + + {{ 'No notifications' | i18n }} + } @else { @for (i of ['', '']; track $index) { @@ -71,10 +72,6 @@ import { i18nPipe } from '@start9labs/shared' transform: translateY(-50%); } - td:only-child { - text-align: center; - } - :host-context(tui-root._mobile) { input { position: absolute; @@ -97,6 +94,7 @@ import { i18nPipe } from '@start9labs/shared' TuiSkeleton, i18nPipe, TableComponent, + PlaceholderComponent, ], }) export class NotificationsTableComponent> diff --git a/web/projects/ui/src/app/routes/portal/routes/services/components/dependencies.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/components/dependencies.component.ts index c11dc26f5..5108382b3 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/components/dependencies.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/components/dependencies.component.ts @@ -18,7 +18,6 @@ import { ToManifestPipe } from '../../../pipes/to-manifest' @let services = this.services(); @for (d of pkg().currentDependencies | keyvalue; track $index) { - -// @if (running()) { -// -// } @else { -// @let unmet = hasUnmet() | async; -// -// } -// `, -// changeDetection: ChangeDetectionStrategy.OnPush, -// imports: [TuiButton, UILaunchComponent, AsyncPipe, i18nPipe], -// providers: [tuiButtonOptionsProvider({ size: 's', appearance: 'none' })], -// styles: ` -// :host { -// padding: 0; -// border: none; -// cursor: default; -// text-align: right; -// } - -// :host-context(tui-root._mobile) { -// button { -// display: none; -// } -// } -// `, -// }) -// export class ControlsComponent { -// private readonly errors = inject(DepErrorService) - -// readonly controls = inject(ControlsService) -// readonly pkg = input.required() -// readonly status = computed(() => renderPkgStatus(this.pkg())) -// readonly running = computed(() => RUNNING.includes(this.status().primary)) -// readonly manifest = computed(() => getManifest(this.pkg())) -// readonly hasUnmet = computed(() => -// this.errors.getPkgDepErrors$(this.manifest().id).pipe( -// map(errors => -// Object.keys(this.pkg().currentDependencies) -// .map(id => errors?.[id]) -// .some(Boolean), -// ), -// ), -// ) -// } diff --git a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/dashboard.component.ts b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/dashboard.component.ts index fb55b1bd4..474a82e54 100644 --- a/web/projects/ui/src/app/routes/portal/routes/services/dashboard/dashboard.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/services/dashboard/dashboard.component.ts @@ -1,160 +1,61 @@ -import { ChangeDetectionStrategy, Component, inject } from '@angular/core' +import { + ChangeDetectionStrategy, + Component, + inject, + viewChild, +} from '@angular/core' import { toSignal } from '@angular/core/rxjs-interop' -import { RouterLink } from '@angular/router' import { i18nPipe } from '@start9labs/shared' -import { TuiComparator, TuiTable } from '@taiga-ui/addon-table' -import { TuiButton, TuiLoader } from '@taiga-ui/core' import { PatchDB } from 'patch-db-client' import { map, shareReplay } from 'rxjs' -import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest' -import { DepErrorService } from 'src/app/services/dep-error.service' -import { - DataModel, - PackageDataEntry, -} from 'src/app/services/patch-db/data-model' -import { getInstalledPrimaryStatus } from 'src/app/services/pkg-status-rendering.service' +import { DataModel } from 'src/app/services/patch-db/data-model' import { TitleDirective } from 'src/app/services/title.service' -import { getManifest } from 'src/app/utils/get-package-data' - -import { ServiceComponent } from './service.component' +import { ServicesTableComponent } from './table.component' @Component({ template: ` - {{ 'Services' | i18n }} - @if (!services()) { - - } @else { - @if (services()?.length) { - - - - - - - - - - - - @for (pkg of services() | tuiTableSort; track $index) { - - } - -
- {{ 'Name' | i18n }} - - {{ 'Status' | i18n }} - {{ 'Version' | i18n }} - {{ 'Uptime' | i18n }} -
- } @else { -
-
- {{ 'Welcome to' | i18n }} - StartOS -
-

- {{ - 'To get started, visit the Marketplace and download your first service' - | i18n - }} -

-
- {{ 'View Marketplace' | i18n }} - -
- } - } + {{ 'Installed services' | i18n }} + +
+
+ {{ 'Installed services' | i18n }} +
+ +
+
`, styles: ` :host { - position: relative; - font-size: 1rem; - } - - table { - max-width: 60rem; - margin: 0 auto; + padding: 1rem; } :host-context(tui-root._mobile) { - padding: 0; - } - - section { - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - - div { - font-size: min(12vw, 4rem); - line-height: normal; + header { + display: none; } - p { - font-size: 1.5rem; - line-height: 1.25em; - padding: 0 1rem; - } - - span { - color: #ff4961; - } - - a { - margin-block-start: 1rem; + .g-card { + padding: 0; + margin-top: -0.75rem; + background: none; + box-shadow: none; } } `, host: { class: 'g-page' }, - imports: [ - ServiceComponent, - ToManifestPipe, - TuiTable, - TitleDirective, - i18nPipe, - TuiLoader, - TuiButton, - RouterLink, - ], + imports: [TitleDirective, i18nPipe, ServicesTableComponent], changeDetection: ChangeDetectionStrategy.OnPush, }) export default class DashboardComponent { - readonly errors = toSignal(inject(DepErrorService).depErrors$) readonly services = toSignal( inject>(PatchDB) .watch$('packageData') .pipe( - map(pkgs => Object.values(pkgs).sort(byName)), + map(pkgs => Object.values(pkgs)), shareReplay(1), ), + { initialValue: null }, ) - readonly name: TuiComparator = byName - - readonly status: TuiComparator = (a, b) => - getInstalledPrimaryStatus(b) > getInstalledPrimaryStatus(a) ? -1 : 1 - - readonly uptime: TuiComparator = (a, b) => - a.status.started || '' > a.status.started || '' ? -1 : 1 - - sorter = this.name -} - -function byName(a: PackageDataEntry, b: PackageDataEntry) { - return getManifest(b).title.toLowerCase() > getManifest(a).title.toLowerCase() - ? -1 - : 1 + protected _ = viewChild>('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: ` - + logo {{ 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', - }, - }, - }, - }, - }, - }, - }, }, }