Fix links for docs (#2908)

* fix docs paths

* docsLink directive

* fix: bugs (#2909)

---------

Co-authored-by: Alex Inkin <alexander@inkin.ru>
This commit is contained in:
Matt Hill
2025-04-24 14:14:08 -06:00
committed by GitHub
parent 2cf4864078
commit 05dd760388
29 changed files with 193 additions and 167 deletions

View File

@@ -1,6 +1,6 @@
# Contributing to StartOS # Contributing to StartOS
This guide is for contributing to the StartOS. If you are interested in packaging a service for StartOS, visit the [service packaging guide](https://docs.start9.com/latest/developer-docs/). If you are interested in promoting, providing technical support, creating tutorials, or helping in other ways, please visit the [Start9 website](https://start9.com/contribute). This guide is for contributing to the StartOS. If you are interested in packaging a service for StartOS, visit the [service packaging guide](https://docs.start9.com/latest/packaging-guide/). If you are interested in promoting, providing technical support, creating tutorials, or helping in other ways, please visit the [Start9 website](https://start9.com/contribute).
## Collaboration ## Collaboration

View File

@@ -601,10 +601,6 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
setupPostInstall: (fn: InstallFn<Manifest, Store>) => PostInstall.of(fn), setupPostInstall: (fn: InstallFn<Manifest, Store>) => PostInstall.of(fn),
/** /**
* @description Use this function to determine how this service will be hosted and served. The function executes on service install, service update, and inputSpec save. * @description Use this function to determine how this service will be hosted and served. The function executes on service install, service update, and inputSpec save.
*
* "input" will be of type `Input` for inputSpec save. It will be `null` for install and update.
*
* To learn about creating multi-hosts and interfaces, check out the {@link https://docs.start9.com/packaging-guide/learn/interfaces documentation}.
* @param inputSpec - The inputSpec spec of this service as exported from /inputSpec/spec. * @param inputSpec - The inputSpec spec of this service as exported from /inputSpec/spec.
* @param fn - an async function that returns an array of interface receipts. The function always has access to `effects`; it has access to `input` only after inputSpec save, otherwise `input` will be null. * @param fn - an async function that returns an array of interface receipts. The function always has access to `effects`; it has access to `input` only after inputSpec save, otherwise `input` will be null.
* @example * @example
@@ -612,8 +608,7 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
* *
* ``` * ```
export const setInterfaces = sdk.setupInterfaces( export const setInterfaces = sdk.setupInterfaces(
inputSpecSpec, async ({ effects }) => {
async ({ effects, input }) => {
// ** UI multi-host ** // ** UI multi-host **
const uiMulti = sdk.MultiHost.of(effects, 'ui-multi') const uiMulti = sdk.MultiHost.of(effects, 'ui-multi')
const uiMultiOrigin = await uiMulti.bindPort(80, { const uiMultiOrigin = await uiMulti.bindPort(80, {

View File

@@ -62,7 +62,7 @@
<a <a
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
href="https://docs.start9.com/0.3.5.x/developer-docs/" href="https://docs.start9.com/latest/packaging-guide/"
> >
<span>Package a service</span> <span>Package a service</span>
<tui-icon tuiAppearance="icon" icon="@tui.external-link" /> <tui-icon tuiAppearance="icon" icon="@tui.external-link" />
@@ -90,7 +90,7 @@
<a <a
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
href="https://docs.start9.com/0.3.5.x/developer-docs/" href="https://docs.start9.com/latest/packaging-guide/"
> >
<span>Package a service</span> <span>Package a service</span>
<tui-icon tuiAppearance="icon" icon="@tui.external-link" /> <tui-icon tuiAppearance="icon" icon="@tui.external-link" />

View File

@@ -1,4 +1,5 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { DocsLinkDirective } from '@start9labs/shared'
@Component({ @Component({
standalone: true, standalone: true,
@@ -43,9 +44,8 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
<p> <p>
Download your server's Root CA and Download your server's Root CA and
<a <a
href="https://docs.start9.com/0.3.5.x/user-manual/connecting-lan" docsLink
target="_blank" href="/user-manual/trust-ca.html"
rel="noreferrer"
style="color: #6866cc; font-weight: bold; text-decoration: none" style="color: #6866cc; font-weight: bold; text-decoration: none"
> >
follow the instructions follow the instructions
@@ -110,9 +110,8 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
<span style="font-weight: bold">Note:</span> <span style="font-weight: bold">Note:</span>
This address will only work from a Tor-enabled browser. This address will only work from a Tor-enabled browser.
<a <a
href="https://docs.start9.com/0.3.5.x/user-manual/connecting-tor" docsLink
target="_blank" href="/user-manual/connecting-remotely/tor.html"
rel="noreferrer"
style="color: #6866cc; font-weight: bold; text-decoration: none" style="color: #6866cc; font-weight: bold; text-decoration: none"
> >
Follow the instructions Follow the instructions
@@ -135,6 +134,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
</html> </html>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [DocsLinkDirective],
}) })
export class DocumentationComponent { export class DocumentationComponent {
@Input({ required: true }) lanAddress!: string @Input({ required: true }) lanAddress!: string

View File

@@ -0,0 +1,31 @@
import {
computed,
Directive,
inject,
InjectionToken,
input,
} from '@angular/core'
const HOST = 'https://staging.docs.start9.com'
export const VERSION = new InjectionToken<string>('VERSION')
@Directive({
selector: '[docsLink]',
standalone: true,
host: {
target: '_blank',
rel: 'noreferrer',
'[href]': 'url()',
},
})
export class DocsLinkDirective {
private readonly version = inject(VERSION)
readonly href = input.required<string>()
protected readonly url = computed(() => {
const path = this.href()
const relative = path.startsWith('/') ? path : `/${path}`
return `${HOST}${relative}?os=${this.version}`
})
}

View File

@@ -13,6 +13,7 @@ export * from './components/markdown.component'
export * from './components/prompt.component' export * from './components/prompt.component'
export * from './components/server.component' export * from './components/server.component'
export * from './directives/docs-link.directive'
export * from './directives/drag-scroller.directive' export * from './directives/drag-scroller.directive'
export * from './directives/safe-links.directive' export * from './directives/safe-links.directive'

View File

@@ -30,7 +30,7 @@ export function getErrorMessage(e: HttpError | string, link?: string): string {
'Request Error. Your browser blocked the request. This is usually caused by a corrupt browser cache or an overly aggressive ad blocker. Please clear your browser cache and/or adjust your ad blocker and try again' 'Request Error. Your browser blocked the request. This is usually caused by a corrupt browser cache or an overly aggressive ad blocker. Please clear your browser cache and/or adjust your ad blocker and try again'
} else if (!e.message) { } else if (!e.message) {
message = 'Unknown Error' message = 'Unknown Error'
link = 'https://docs.start9.com/latest/support/faq' link = 'https://docs.start9.com/help/common-issues.html'
} else { } else {
message = e.message message = e.message
} }

View File

@@ -10,6 +10,7 @@ import {
I18N_STORAGE, I18N_STORAGE,
i18nService, i18nService,
RELATIVE_URL, RELATIVE_URL,
VERSION,
WorkspaceConfig, WorkspaceConfig,
} from '@start9labs/shared' } from '@start9labs/shared'
import { import {
@@ -27,6 +28,7 @@ import {
import { tuiTextfieldOptionsProvider } from '@taiga-ui/legacy' import { tuiTextfieldOptionsProvider } from '@taiga-ui/legacy'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { filter, of, pairwise } from 'rxjs' import { filter, of, pairwise } from 'rxjs'
import { ConfigService } from 'src/app/services/config.service'
import { import {
PATCH_CACHE, PATCH_CACHE,
PatchDbSource, PatchDbSource,
@@ -112,6 +114,10 @@ export const APP_PROVIDERS: Provider[] = [
return (language: string) => api.setDbValue(['language'], language) return (language: string) => api.setDbValue(['language'], language)
}, },
}, },
{
provide: VERSION,
useFactory: () => inject(ConfigService).version,
},
] ]
export function appInitializer(): () => void { export function appInitializer(): () => void {

View File

@@ -2,7 +2,7 @@ import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { RouterLink } from '@angular/router' import { RouterLink } from '@angular/router'
import { i18nPipe } from '@start9labs/shared' import { i18nPipe } from '@start9labs/shared'
import { TuiAlert } from '@taiga-ui/core' import { TuiAlert, TuiLink } from '@taiga-ui/core'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { endWith, map, merge, Observable, pairwise, Subject } from 'rxjs' import { endWith, map, merge, Observable, pairwise, Subject } from 'rxjs'
import { DataModel } from 'src/app/services/patch-db/data-model' import { DataModel } from 'src/app/services/patch-db/data-model'
@@ -17,13 +17,17 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
(tuiAlertChange)="onDismiss()" (tuiAlertChange)="onDismiss()"
> >
{{ 'New notifications' | i18n }} {{ 'New notifications' | i18n }}
<a routerLink="/notifications" [queryParams]="{ toast: true }"> <a
tuiLink
routerLink="/portal/notifications"
[queryParams]="{ toast: true }"
>
{{ 'View' | i18n }} {{ 'View' | i18n }}
</a> </a>
</ng-template> </ng-template>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiAlert, RouterLink, AsyncPipe, i18nPipe], imports: [TuiAlert, RouterLink, AsyncPipe, i18nPipe, TuiLink],
}) })
export class NotificationsToastComponent { export class NotificationsToastComponent {
private readonly dismiss$ = new Subject<boolean>() private readonly dismiss$ = new Subject<boolean>()

View File

@@ -7,16 +7,27 @@
<tui-icon icon="@tui.lock" [style.font-size.rem]="4" /> <tui-icon icon="@tui.lock" [style.font-size.rem]="4" />
<h1>{{ 'Trust your Root CA' | i18n }}</h1> <h1>{{ 'Trust your Root CA' | i18n }}</h1>
<p> <p>
{{ 'Download and trust your Root Certificate Authority to establish a secure (HTTPS) connection. You will need to repeat this on every device you use to connect to your server.' | i18n }} {{
'Download and trust your Root Certificate Authority to establish a secure (HTTPS) connection. You will need to repeat this on every device you use to connect to your server.'
| i18n
}}
</p> </p>
<ol> <ol>
<li> <li>
<b>{{ 'Bookmark this page' | i18n }}</b> <b>{{ 'Bookmark this page' | i18n }}</b>
- {{ 'Save this page so you can access it later. You can also find this address in the file downloaded at the end of initial setup.' | i18n }} -
{{
'Save this page so you can access it later. You can also find this address in the file downloaded at the end of initial setup.'
| i18n
}}
</li> </li>
<li> <li>
<b>{{ 'Download your Root CA' | i18n }}</b> <b>{{ 'Download your Root CA' | i18n }}</b>
- {{ 'Your server uses its Root CA to generate SSL/TLS certificates for itself and installed services. These certificates are then used to encrypt network traffic with your client devices.' | i18n }} -
{{
'Your server uses its Root CA to generate SSL/TLS certificates for itself and installed services. These certificates are then used to encrypt network traffic with your client devices.'
| i18n
}}
<br /> <br />
<a <a
tuiButton tuiButton
@@ -29,14 +40,17 @@
</li> </li>
<li> <li>
<b>{{ 'Trust your Root CA' | i18n }}</b> <b>{{ 'Trust your Root CA' | i18n }}</b>
- {{ 'Follow instructions for your OS. By trusting your Root CA, your device can verify the authenticity of encrypted communications with your server.' | i18n }} -
{{
'Follow instructions for your OS. By trusting your Root CA, your device can verify the authenticity of encrypted communications with your server.'
| i18n
}}
<br /> <br />
<a <a
tuiButton tuiButton
docsLink
size="s" size="s"
href="https://docs.start9.com/0.3.5.x/user-manual/trust-ca" href="/user-manual/trust-ca.html"
target="_blank"
rel="noreferrer"
iconEnd="@tui.external-link" iconEnd="@tui.external-link"
> >
{{ 'View instructions' | i18n }} {{ 'View instructions' | i18n }}
@@ -44,7 +58,11 @@
</li> </li>
<li> <li>
<b>{{ 'Test' | i18n }}</b> <b>{{ 'Test' | i18n }}</b>
- {{ 'Refresh the page. If refreshing the page does not work, you may need to quit and re-open your browser, then revisit this page.' | i18n }} -
{{
'Refresh the page. If refreshing the page does not work, you may need to quit and re-open your browser, then revisit this page.'
| i18n
}}
<br /> <br />
<button <button
tuiButton tuiButton
@@ -68,7 +86,9 @@
> >
{{ 'Skip' | i18n }} {{ 'Skip' | i18n }}
</button> </button>
<div><small>({{ 'not recommended' | i18n }})</small></div> <div>
<small>({{ 'not recommended' | i18n }})</small>
</div>
</div> </div>
<ng-template #trusted> <ng-template #trusted>
@@ -76,7 +96,10 @@
<tui-icon icon="@tui.shield" class="g-positive" [style.font-size.rem]="4" /> <tui-icon icon="@tui.shield" class="g-positive" [style.font-size.rem]="4" />
<h1>{{ 'Root CA Trusted!' | i18n }}</h1> <h1>{{ 'Root CA Trusted!' | i18n }}</h1>
<p> <p>
{{ 'You have successfully trusted your Root CA and may now log in securely.' | i18n }} {{
'You have successfully trusted your Root CA and may now log in securely.'
| i18n
}}
</p> </p>
<button tuiButton iconEnd="@tui.external-link" (click)="launchHttps()"> <button tuiButton iconEnd="@tui.external-link" (click)="launchHttps()">
{{ 'Go to login' | i18n }} {{ 'Go to login' | i18n }}

View File

@@ -1,6 +1,6 @@
import { CommonModule, DOCUMENT } from '@angular/common' import { CommonModule, DOCUMENT } from '@angular/common'
import { Component, inject } from '@angular/core' import { Component, inject } from '@angular/core'
import { i18nPipe, RELATIVE_URL } from '@start9labs/shared' import { DocsLinkDirective, i18nPipe, RELATIVE_URL } from '@start9labs/shared'
import { TuiButton, TuiIcon, TuiSurface } from '@taiga-ui/core' import { TuiButton, TuiIcon, TuiSurface } from '@taiga-ui/core'
import { TuiCardLarge } from '@taiga-ui/layout' import { TuiCardLarge } from '@taiga-ui/layout'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -18,6 +18,7 @@ import { ConfigService } from 'src/app/services/config.service'
TuiCardLarge, TuiCardLarge,
TuiSurface, TuiSurface,
i18nPipe, i18nPipe,
DocsLinkDirective,
], ],
}) })
export class CAWizardComponent { export class CAWizardComponent {

View File

@@ -1,16 +1,17 @@
import { TuiCell } from '@taiga-ui/layout'
import { TuiTitle, TuiButton } from '@taiga-ui/core'
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { CopyService, i18nPipe } from '@start9labs/shared' import { CopyService, i18nPipe } from '@start9labs/shared'
import { TuiButton, TuiTitle } from '@taiga-ui/core'
import { TuiFade } from '@taiga-ui/kit'
import { TuiCell } from '@taiga-ui/layout'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus' import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
@Component({ @Component({
template: ` template: `
<ng-container *ngIf="server$ | async as server"> @if (server(); as server) {
<div tuiCell> <div tuiCell>
<div tuiTitle> <div tuiTitle>
<strong>{{ 'Version' | i18n }}</strong> <strong>{{ 'Version' | i18n }}</strong>
@@ -20,7 +21,7 @@ import { ConfigService } from 'src/app/services/config.service'
<div tuiCell> <div tuiCell>
<div tuiTitle> <div tuiTitle>
<strong>Git Hash</strong> <strong>Git Hash</strong>
<div tuiSubtitle>{{ gitHash }}</div> <div tuiSubtitle tuiFade>{{ gitHash }}</div>
</div> </div>
<button <button
tuiIconButton tuiIconButton
@@ -34,7 +35,7 @@ import { ConfigService } from 'src/app/services/config.service'
<div tuiCell> <div tuiCell>
<div tuiTitle> <div tuiTitle>
<strong>CA fingerprint</strong> <strong>CA fingerprint</strong>
<div tuiSubtitle>{{ server.caFingerprint }}</div> <div tuiSubtitle tuiFade>{{ server.caFingerprint }}</div>
</div> </div>
<button <button
tuiIconButton tuiIconButton
@@ -45,17 +46,19 @@ import { ConfigService } from 'src/app/services/config.service'
{{ 'Copy' | i18n }} {{ 'Copy' | i18n }}
</button> </button>
</div> </div>
</ng-container> }
`, `,
styles: ['[tuiCell] { padding-inline: 0 }'], styles: '[tuiCell] { padding-inline: 0; white-space: nowrap }',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [CommonModule, TuiTitle, TuiButton, TuiCell, i18nPipe], imports: [TuiTitle, TuiButton, TuiCell, i18nPipe, TuiFade],
}) })
export class AboutComponent { export class AboutComponent {
readonly server$ = inject<PatchDB<DataModel>>(PatchDB).watch$('serverInfo')
readonly copyService = inject(CopyService) readonly copyService = inject(CopyService)
readonly gitHash = inject(ConfigService).gitHash readonly gitHash = inject(ConfigService).gitHash
readonly server = toSignal(
inject<PatchDB<DataModel>>(PatchDB).watch$('serverInfo'),
)
} }
export const ABOUT = new PolymorpheusComponent(AboutComponent) export const ABOUT = new PolymorpheusComponent(AboutComponent)

View File

@@ -1,10 +1,12 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { RouterLink, RouterLinkActive } from '@angular/router' import { RouterLink } from '@angular/router'
import { import {
DialogService, DialogService,
DocsLinkDirective,
ErrorService, ErrorService,
i18nPipe, i18nPipe,
LoadingService, LoadingService,
SafeLinksDirective,
} from '@start9labs/shared' } from '@start9labs/shared'
import { import {
TuiButton, TuiButton,
@@ -17,7 +19,6 @@ import { filter } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { AuthService } from 'src/app/services/auth.service' import { AuthService } from 'src/app/services/auth.service'
import { STATUS } from 'src/app/services/status.service' import { STATUS } from 'src/app/services/status.service'
import { RESOURCES } from 'src/app/utils/resources'
import { ABOUT } from './about.component' import { ABOUT } from './about.component'
@Component({ @Component({
@@ -48,18 +49,24 @@ import { ABOUT } from './about.component'
{{ 'About this server' | i18n }} {{ 'About this server' | i18n }}
</button> </button>
</tui-opt-group> </tui-opt-group>
<tui-opt-group label=""> <tui-opt-group label="" safeLinks>
@for (link of links; track $index) { <a tuiOption docsLink iconStart="@tui.book-open" href="/user-manual">
<a {{ 'User manual' | i18n }}
tuiOption </a>
target="_blank" <a
rel="noreferrer" tuiOption
[iconStart]="link.icon" iconStart="@tui.headphones"
[href]="link.href" href="https://start9.com/contact"
> >
{{ link.name | i18n }} {{ 'Contact support' | i18n }}
</a> </a>
} <a
tuiOption
iconStart="@tui.dollar-sign"
href="https://donate.start9.com"
>
{{ 'Donate to Start9' | i18n }}
</a>
</tui-opt-group> </tui-opt-group>
<tui-opt-group label=""> <tui-opt-group label="">
<a <a
@@ -128,8 +135,9 @@ import { ABOUT } from './about.component'
TuiIcon, TuiIcon,
RouterLink, RouterLink,
i18nPipe, i18nPipe,
RouterLinkActive,
TuiHint, TuiHint,
DocsLinkDirective,
SafeLinksDirective,
], ],
}) })
export class HeaderMenuComponent { export class HeaderMenuComponent {
@@ -141,7 +149,6 @@ export class HeaderMenuComponent {
open = false open = false
readonly links = RESOURCES
readonly status = inject(STATUS) readonly status = inject(STATUS)
about() { about() {

View File

@@ -59,7 +59,7 @@ type ClearnetForm = {
}} }}
<a <a
tuiLink tuiLink
href="https://docs.start9.com/latest/user-manual/interface-addresses#clearnet" href="/user-manual/connecting-remotely/clearnet.html"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >

View File

@@ -5,7 +5,7 @@ import { TableComponent } from 'src/app/routes/portal/components/table.component
import { InterfaceActionsComponent } from './actions.component' import { InterfaceActionsComponent } from './actions.component'
import { AddressDetails } from './interface.utils' import { AddressDetails } from './interface.utils'
import { MaskPipe } from './mask.pipe' import { MaskPipe } from './mask.pipe'
import { i18nPipe } from '@start9labs/shared' import { DocsLinkDirective, i18nPipe } from '@start9labs/shared'
@Component({ @Component({
standalone: true, standalone: true,
@@ -19,12 +19,7 @@ import { i18nPipe } from '@start9labs/shared'
'Local addresses can only be accessed by devices connected to the same LAN as your server, either directly or using a VPN.' 'Local addresses can only be accessed by devices connected to the same LAN as your server, either directly or using a VPN.'
| i18n | i18n
}} }}
<a <a tuiLink docsLink href="/user-manual/connecting-locally.html">
tuiLink
href="https://docs.start9.com/latest/user-manual/interface-addresses#local"
target="_blank"
rel="noreferrer"
>
{{ 'Learn More' | i18n }} {{ 'Learn More' | i18n }}
</a> </a>
</ng-template> </ng-template>
@@ -48,6 +43,7 @@ import { i18nPipe } from '@start9labs/shared'
InterfaceActionsComponent, InterfaceActionsComponent,
MaskPipe, MaskPipe,
i18nPipe, i18nPipe,
DocsLinkDirective,
], ],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })

View File

@@ -6,6 +6,7 @@ import {
} from '@angular/core' } from '@angular/core'
import { import {
DialogService, DialogService,
DocsLinkDirective,
ErrorService, ErrorService,
i18nPipe, i18nPipe,
LoadingService, LoadingService,
@@ -50,12 +51,7 @@ type OnionForm = {
'Add an onion address to anonymously expose this interface on the darknet. Onion addresses can only be reached over the Tor network.' 'Add an onion address to anonymously expose this interface on the darknet. Onion addresses can only be reached over the Tor network.'
| i18n | i18n
}} }}
<a <a tuiLink docsLink href="/user-manual/connecting-remotely/tor.html">
tuiLink
href="https://docs.start9.com/latest/user-manual/interface-addresses#tor"
target="_blank"
rel="noreferrer"
>
{{ 'Learn More' | i18n }} {{ 'Learn More' | i18n }}
</a> </a>
</ng-template> </ng-template>
@@ -131,6 +127,7 @@ type OnionForm = {
TuiFade, TuiFade,
TuiFluidTypography, TuiFluidTypography,
i18nPipe, i18nPipe,
DocsLinkDirective,
], ],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })

View File

@@ -18,6 +18,7 @@ import { GetBackupIconPipe } from '../pipes/get-backup-icon.pipe'
import { ToHumanCronPipe } from '../pipes/to-human-cron.pipe' import { ToHumanCronPipe } from '../pipes/to-human-cron.pipe'
import { BackupJobBuilder } from '../utils/job-builder' import { BackupJobBuilder } from '../utils/job-builder'
import { EDIT } from './edit.component' import { EDIT } from './edit.component'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
@Component({ @Component({
template: ` template: `
@@ -25,14 +26,7 @@ import { EDIT } from './edit.component'
Scheduling automatic backups is an excellent way to ensure your StartOS Scheduling automatic backups is an excellent way to ensure your StartOS
data is safely backed up. StartOS will issue a notification whenever one data is safely backed up. StartOS will issue a notification whenever one
of your scheduled backups succeeds or fails. of your scheduled backups succeeds or fails.
<a <a tuiLink docsLink href="/@TODO">View instructions</a>
tuiLink
href="https://docs.start9.com/latest/user-manual/backups/backup-jobs"
target="_blank"
rel="noreferrer"
>
View instructions
</a>
</tui-notification> </tui-notification>
<h3 class="g-title"> <h3 class="g-title">
Saved Jobs Saved Jobs
@@ -147,6 +141,7 @@ import { EDIT } from './edit.component'
GetBackupIconPipe, GetBackupIconPipe,
TuiSkeleton, TuiSkeleton,
TuiLink, TuiLink,
DocsLinkDirective,
], ],
}) })
export class BackupsJobsModal implements OnInit { export class BackupsJobsModal implements OnInit {

View File

@@ -23,6 +23,7 @@ import {
googleDriveSpec, googleDriveSpec,
remoteBackupTargetSpec, remoteBackupTargetSpec,
} from '../types/target' } from '../types/target'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
@Component({ @Component({
template: ` template: `
@@ -31,14 +32,7 @@ import {
backups. They can be physical drives plugged into your server, shared backups. They can be physical drives plugged into your server, shared
folders on your Local Area Network (LAN), or third party clouds such as folders on your Local Area Network (LAN), or third party clouds such as
Dropbox or Google Drive. Dropbox or Google Drive.
<a <a tuiLink docsLink href="/@TODO">View instructions</a>
tuiLink
href="https://docs.start9.com/latest/user-manual/backups/backup-targets"
target="_blank"
rel="noreferrer"
>
View instructions
</a>
</tui-notification> </tui-notification>
<h3 class="g-title"> <h3 class="g-title">
Unknown Physical Drives Unknown Physical Drives
@@ -77,6 +71,7 @@ import {
BackupsPhysicalComponent, BackupsPhysicalComponent,
BackupsTargetsComponent, BackupsTargetsComponent,
TuiLink, TuiLink,
DocsLinkDirective,
], ],
}) })
export class BackupsTargetsModal implements OnInit { export class BackupsTargetsModal implements OnInit {

View File

@@ -1,7 +1,7 @@
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { i18nPipe } from '@start9labs/shared' import { DocsLinkDirective, i18nPipe } from '@start9labs/shared'
import { import {
TuiHint, TuiHint,
TuiIcon, TuiIcon,
@@ -47,11 +47,10 @@ import { TimeService } from 'src/app/services/time.service'
To resolve it, refer to To resolve it, refer to
<a <a
tuiLink tuiLink
docsLink
iconEnd="@tui.external-link" iconEnd="@tui.external-link"
appearance="" appearance=""
href="https://docs.start9.com/0.3.5.x/support/common-issues#clock-sync-failure" href="/help/common-issues.html#clock-sync-failure"
target="_blank"
rel="noreferrer"
[pseudo]="true" [pseudo]="true"
[textContent]="'the docs' | i18n" [textContent]="'the docs' | i18n"
></a> ></a>
@@ -110,6 +109,7 @@ import { TimeService } from 'src/app/services/time.service'
TuiIcon, TuiIcon,
TuiHint, TuiHint,
i18nPipe, i18nPipe,
DocsLinkDirective,
], ],
}) })
export class TimeComponent { export class TimeComponent {

View File

@@ -1,7 +1,12 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop' import { toSignal } from '@angular/core/rxjs-interop'
import { RouterLink } from '@angular/router' import { RouterLink } from '@angular/router'
import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared' import {
DocsLinkDirective,
ErrorService,
i18nPipe,
LoadingService,
} from '@start9labs/shared'
import { ISB, utils } from '@start9labs/start-sdk' import { ISB, utils } from '@start9labs/start-sdk'
import { TuiButton, TuiLink, TuiLoader, TuiTitle } from '@taiga-ui/core' import { TuiButton, TuiLink, TuiLoader, TuiTitle } from '@taiga-ui/core'
import { TuiCell, TuiHeader } from '@taiga-ui/layout' import { TuiCell, TuiHeader } from '@taiga-ui/layout'
@@ -33,9 +38,8 @@ import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
}} }}
<a <a
tuiLink tuiLink
href="https://docs.start9.com/latest/user-manual/acme" docsLink
target="_blank" href="/user-manual/connecting-remotely/clearnet.html#adding-acme"
rel="noreferrer"
appearance="action-grayscale" appearance="action-grayscale"
iconEnd="@tui.external-link" iconEnd="@tui.external-link"
[pseudo]="true" [pseudo]="true"
@@ -108,6 +112,7 @@ import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
RouterLink, RouterLink,
TitleDirective, TitleDirective,
i18nPipe, i18nPipe,
DocsLinkDirective,
], ],
}) })
export default class SystemAcmeComponent { export default class SystemAcmeComponent {

View File

@@ -9,6 +9,7 @@ import { toSignal } from '@angular/core/rxjs-interop'
import { ActivatedRoute, RouterLink } from '@angular/router' import { ActivatedRoute, RouterLink } from '@angular/router'
import { import {
DialogService, DialogService,
DocsLinkDirective,
i18nPipe, i18nPipe,
UnitConversionPipesModule, UnitConversionPipesModule,
} from '@start9labs/shared' } from '@start9labs/shared'
@@ -64,9 +65,8 @@ import { BACKUP_RESTORE } from './restore.component'
}} }}
<a <a
tuiLink tuiLink
href="https://docs.start9.com/0.3.5.x/user-manual/backups/backup-create" docsLink
target="_blank" href="/user-manual/backup-create.html"
rel="noreferrer"
appearance="action-grayscale" appearance="action-grayscale"
iconEnd="@tui.external-link" iconEnd="@tui.external-link"
[pseudo]="true" [pseudo]="true"
@@ -79,9 +79,8 @@ import { BACKUP_RESTORE } from './restore.component'
}} }}
<a <a
tuiLink tuiLink
href="https://docs.start9.com/0.3.5.x/user-manual/backups/backup-restore" docsLink
target="_blank" href="/user-manual/backup-restore.html"
rel="noreferrer"
appearance="action-grayscale" appearance="action-grayscale"
iconEnd="@tui.external-link" iconEnd="@tui.external-link"
[pseudo]="true" [pseudo]="true"
@@ -120,9 +119,8 @@ import { BACKUP_RESTORE } from './restore.component'
}} }}
<a <a
tuiLink tuiLink
href="https://docs.start9.com/0.3.5.x/user-manual/backups/backup-create#network-folder" docsLink
target="_blank" href="/user-manual/backup-create.html#network-folder"
rel="noreferrer"
appearance="action-grayscale" appearance="action-grayscale"
iconEnd="@tui.external-link" iconEnd="@tui.external-link"
[pseudo]="true" [pseudo]="true"
@@ -157,6 +155,7 @@ import { BACKUP_RESTORE } from './restore.component'
BackupPhysicalComponent, BackupPhysicalComponent,
BackupProgressComponent, BackupProgressComponent,
i18nPipe, i18nPipe,
DocsLinkDirective,
], ],
}) })
export default class SystemBackupComponent implements OnInit { export default class SystemBackupComponent implements OnInit {

View File

@@ -1,23 +1,17 @@
import { ChangeDetectionStrategy, Component } from '@angular/core' import { ChangeDetectionStrategy, Component } from '@angular/core'
import { TuiLink, TuiNotification } from '@taiga-ui/core' import { TuiLink, TuiNotification } from '@taiga-ui/core'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
@Component({ @Component({
selector: 'domains-info', selector: 'domains-info',
template: ` template: `
<tui-notification> <tui-notification>
Adding domains permits accessing your server and services over clearnet. Adding domains permits accessing your server and services over clearnet.
<a <a tuiLink docsLink href="/@TODO">View instructions</a>
tuiLink
href="https://docs.start9.com/latest/user-manual/domains"
target="_blank"
rel="noreferrer"
>
View instructions
</a>
</tui-notification> </tui-notification>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [TuiNotification, TuiLink], imports: [TuiNotification, TuiLink, DocsLinkDirective],
}) })
export class DomainsInfoComponent {} export class DomainsInfoComponent {}

View File

@@ -4,6 +4,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { RouterLink } from '@angular/router' import { RouterLink } from '@angular/router'
import { import {
DialogService, DialogService,
DocsLinkDirective,
ErrorService, ErrorService,
i18nKey, i18nKey,
i18nPipe, i18nPipe,
@@ -40,9 +41,8 @@ import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
}} }}
<a <a
tuiLink tuiLink
href="https://docs.start9.com/latest/user-manual/smtp" docsLink
target="_blank" href="/user-manual/smtp"
rel="noreferrer"
appearance="action-grayscale" appearance="action-grayscale"
iconEnd="@tui.external-link" iconEnd="@tui.external-link"
[pseudo]="true" [pseudo]="true"
@@ -140,6 +140,7 @@ import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
RouterLink, RouterLink,
TitleDirective, TitleDirective,
i18nPipe, i18nPipe,
DocsLinkDirective,
], ],
}) })
export default class SystemEmailComponent { export default class SystemEmailComponent {

View File

@@ -229,6 +229,7 @@ export default class SystemGeneralComponent {
private readonly document = inject(DOCUMENT) private readonly document = inject(DOCUMENT)
private readonly dialog = inject(DialogService) private readonly dialog = inject(DialogService)
private readonly i18n = inject(i18nPipe) private readonly i18n = inject(i18nPipe)
private readonly injector = inject(INJECTOR)
wipe = false wipe = false
count = 0 count = 0
@@ -261,7 +262,7 @@ export default class SystemGeneralComponent {
} }
onTitle() { onTitle() {
this.dialog const sub = this.dialog
.openPrompt<string>({ .openPrompt<string>({
label: 'Browser Tab Title', label: 'Browser Tab Title',
data: { data: {
@@ -279,8 +280,11 @@ export default class SystemGeneralComponent {
try { try {
await this.api.setDbValue(['name'], name || null) await this.api.setDbValue(['name'], name || null)
} catch (e: any) {
this.errorService.handleError(e)
} finally { } finally {
loader.unsubscribe() loader.unsubscribe()
sub.unsubscribe()
} }
}) })
} }
@@ -293,7 +297,7 @@ export default class SystemGeneralComponent {
data: { data: {
content: new PolymorpheusComponent( content: new PolymorpheusComponent(
SystemWipeComponent, SystemWipeComponent,
inject(INJECTOR), this.injector,
), ),
yes: 'Reset', yes: 'Reset',
no: 'Cancel', no: 'Cancel',

View File

@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component } from '@angular/core' import { ChangeDetectionStrategy, Component } from '@angular/core'
import { TuiLink, TuiNotification } from '@taiga-ui/core' import { TuiLink, TuiNotification } from '@taiga-ui/core'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
@Component({ @Component({
selector: 'proxies-info', selector: 'proxies-info',
@@ -24,18 +25,11 @@ import { TuiLink, TuiNotification } from '@taiga-ui/core'
VPN access to your server/services VPN access to your server/services
</li> </li>
</ol> </ol>
<a <a tuiLink docsLink href="/@TODO">View instructions</a>
tuiLink
href="https://docs.start9.com/latest/user-manual/vpns/"
target="_blank"
rel="noreferrer"
>
View instructions
</a>
</tui-notification> </tui-notification>
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [TuiNotification, TuiLink], imports: [TuiNotification, TuiLink, DocsLinkDirective],
}) })
export class ProxiesInfoComponent {} export class ProxiesInfoComponent {}

View File

@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { TuiLink, TuiNotification } from '@taiga-ui/core' import { TuiLink, TuiNotification } from '@taiga-ui/core'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
@Component({ @Component({
selector: 'router-info', selector: 'router-info',
@@ -14,14 +15,7 @@ import { TuiLink, TuiNotification } from '@taiga-ui/core'
</p> </p>
If you are running multiple servers, you may want to override specific If you are running multiple servers, you may want to override specific
ports to suite your needs. ports to suite your needs.
<a <a tuiLink docsLink href="/@TODO">View instructions</a>
tuiLink
href="https://docs.start9.com/latest/user-manual/port-forwards/upnp#override"
target="_blank"
rel="noreferrer"
>
View instructions
</a>
} @else { } @else {
<strong>UPnP Disabled</strong> <strong>UPnP Disabled</strong>
<p> <p>
@@ -31,21 +25,14 @@ import { TuiLink, TuiNotification } from '@taiga-ui/core'
</p> </p>
Alternatively, you can enable UPnP in your router for automatic Alternatively, you can enable UPnP in your router for automatic
configuration. configuration.
<a <a tuiLink docsLink href="/@TODO">View instructions</a>
tuiLink
href="https://docs.start9.com/latest/user-manual/port-forwards/manual"
target="_blank"
rel="noreferrer"
>
View instructions
</a>
} }
</tui-notification> </tui-notification>
`, `,
styles: ['strong { font-size: 1rem }'], styles: ['strong { font-size: 1rem }'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true, standalone: true,
imports: [TuiNotification, TuiLink], imports: [TuiNotification, TuiLink, DocsLinkDirective],
}) })
export class RouterInfoComponent { export class RouterInfoComponent {
@Input() @Input()

View File

@@ -9,6 +9,7 @@ import { catchError, defer, of } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { TitleDirective } from 'src/app/services/title.service' import { TitleDirective } from 'src/app/services/title.service'
import { SSHTableComponent } from './table.component' import { SSHTableComponent } from './table.component'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
@Component({ @Component({
template: ` template: `
@@ -23,9 +24,8 @@ import { SSHTableComponent } from './table.component'
Manage your SSH keys to access your server from the command line Manage your SSH keys to access your server from the command line
<a <a
tuiLink tuiLink
href="https://docs.start9.com/latest/user-manual/ssh" docsLink
target="_blank" href="/@TODO"
rel="noreferrer"
appearance="action-grayscale" appearance="action-grayscale"
iconEnd="@tui.external-link" iconEnd="@tui.external-link"
[pseudo]="true" [pseudo]="true"
@@ -62,6 +62,7 @@ import { SSHTableComponent } from './table.component'
TuiHeader, TuiHeader,
TuiTitle, TuiTitle,
TuiLink, TuiLink,
DocsLinkDirective,
], ],
}) })
export default class SystemSSHComponent { export default class SystemSSHComponent {

View File

@@ -1,19 +0,0 @@
import { i18nKey } from '@start9labs/shared'
export const RESOURCES: { name: i18nKey; icon: string; href: string }[] = [
{
name: 'User manual',
icon: '@tui.book-open',
href: 'https://docs.start9.com/0.3.5.x/user-manual',
},
{
name: 'Contact support',
icon: '@tui.headphones',
href: 'https://start9.com/contact',
},
{
name: 'Donate to Start9',
icon: '@tui.dollar-sign',
href: 'https://donate.start9.com',
},
]

View File

@@ -1,5 +1,9 @@
@import '@taiga-ui/core/styles/taiga-ui-local'; @import '@taiga-ui/core/styles/taiga-ui-local';
:root {
color-scheme: light dark;
}
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
@@ -21,7 +25,9 @@ hr {
:root { :root {
--bumper: 0.375rem; --bumper: 0.375rem;
--tui-font-text: 'Proxima Nova', 'Manrope', -apple-system, 'BlinkMacSystemFont', system-ui, 'Roboto', 'Segoe UI', 'Helvetica Neue', sans-serif; --tui-font-text: 'Proxima Nova', 'Manrope', -apple-system,
'BlinkMacSystemFont', system-ui, 'Roboto', 'Segoe UI', 'Helvetica Neue',
sans-serif;
} }
.g-page { .g-page {