mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Refactor i18n approach (#2875)
* Refactor i18n approach * chore: move to shared * chore: add default * create DialogService and update LoadingService (#2876) * complete translation infra for ui project, currently broken * cleanup and more dictionaries * chore: fix --------- Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com> Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
@@ -91,3 +91,51 @@ cp proxy.conf-sample.json proxy.conf.json
|
||||
```sh
|
||||
npm run start:ui:proxy
|
||||
```
|
||||
|
||||
## Updating translations
|
||||
|
||||
### Adding a new translation
|
||||
|
||||
When prompting AI to translate the English dictionary, it is recommended to only give it 50-100 entries at a time. Beyond that it struggles. Remember to sanity check the results and ensure keys/values align in the resulting dictionary.
|
||||
|
||||
#### Sample AI prompt
|
||||
|
||||
Translate the English dictionary below into `<language>`. Format the result as a javascript object with the numeric values of the English dictionary as keys in the translated dictionary. These translations are for the web UI of StartOS, a graphical server operating system optimized for self-hosting. Comments may be included in the English dictionary to provide additional context.
|
||||
|
||||
#### Adding to StartOS
|
||||
|
||||
- In the `shared` project:
|
||||
|
||||
1. Create a new file (`language.ts`) in `src/i18n/dictionaries`
|
||||
1. Export the dictionary in `src/public-api.ts`
|
||||
1. Update the `I18N_PROVIDERS` array in `src/i18n/i18n.providers.ts` (2 places)
|
||||
1. Update the `languages` array in `/src/i18n/i18n.service.ts`
|
||||
|
||||
- Here in this README:
|
||||
|
||||
1. Add the language to the list of supported languages below
|
||||
|
||||
### Updating the English dictionary
|
||||
|
||||
#### Sample AI prompt
|
||||
|
||||
Translate `<original>` into the languages below. Return the translations as a JSON object with the languages as keys.
|
||||
|
||||
- Spanish
|
||||
- Polish
|
||||
- German
|
||||
<!-- - Korean
|
||||
- Russian
|
||||
- Japanese
|
||||
- Hebrew
|
||||
- Arabic
|
||||
- Mandarin
|
||||
- Hindi
|
||||
- Portuguese
|
||||
- French
|
||||
- Italian
|
||||
- Thai -->
|
||||
|
||||
#### Adding to StartOS
|
||||
|
||||
In the `shared` project, copy/past the translations into their corresponding dictionaries in `/src/i18n/dictionaries`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TUI_CONFIRM } from '@taiga-ui/kit'
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { DiskInfo, LoadingService, toGuid } from '@start9labs/shared'
|
||||
import { DiskInfo, i18nKey, LoadingService, toGuid } from '@start9labs/shared'
|
||||
import { TuiDialogService } from '@taiga-ui/core'
|
||||
import { filter, from } from 'rxjs'
|
||||
import { SUCCESS, toWarning } from 'src/app/app.utils'
|
||||
@@ -25,7 +25,7 @@ export class AppComponent {
|
||||
}
|
||||
|
||||
async install(overwrite = false) {
|
||||
const loader = this.loader.open('Installing StartOS...').subscribe()
|
||||
const loader = this.loader.open('Installing StartOS' as i18nKey).subscribe()
|
||||
const logicalname = this.selected?.logicalname || ''
|
||||
|
||||
try {
|
||||
@@ -55,7 +55,7 @@ export class AppComponent {
|
||||
)
|
||||
.subscribe({
|
||||
complete: async () => {
|
||||
const loader = this.loader.open('').subscribe()
|
||||
const loader = this.loader.open('' as i18nKey).subscribe()
|
||||
|
||||
try {
|
||||
await this.api.reboot()
|
||||
|
||||
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core'
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||
import {
|
||||
DriveComponent,
|
||||
i18nPipe,
|
||||
RELATIVE_URL,
|
||||
WorkspaceConfig,
|
||||
} from '@start9labs/shared'
|
||||
@@ -38,6 +39,7 @@ const {
|
||||
TuiIcon,
|
||||
TuiSurface,
|
||||
TuiTitle,
|
||||
i18nPipe,
|
||||
],
|
||||
providers: [
|
||||
NG_EVENT_PLUGINS,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { NgModule } from '@angular/core'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { SharedPipesModule, TickerModule } from '@start9labs/shared'
|
||||
import { SharedPipesModule, TickerComponent } from '@start9labs/shared'
|
||||
import { TuiLet } from '@taiga-ui/cdk'
|
||||
import { ItemComponent } from './item.component'
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ItemComponent } from './item.component'
|
||||
CommonModule,
|
||||
RouterModule,
|
||||
SharedPipesModule,
|
||||
TickerModule,
|
||||
TickerComponent,
|
||||
TuiLet,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import { SharedPipesModule, TickerModule } from '@start9labs/shared'
|
||||
import { TuiLet } from '@taiga-ui/cdk'
|
||||
import { SharedPipesModule, TickerComponent } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-package-hero',
|
||||
@@ -142,7 +141,7 @@ import { TuiLet } from '@taiga-ui/cdk'
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CommonModule, SharedPipesModule, TickerModule, TuiLet],
|
||||
imports: [CommonModule, SharedPipesModule, TickerComponent],
|
||||
})
|
||||
export class MarketplacePackageHeroComponent {
|
||||
@Input({ required: true })
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from '@angular/forms'
|
||||
import { LoadingService, StartOSDiskInfo } from '@start9labs/shared'
|
||||
import { i18nKey, LoadingService, StartOSDiskInfo } from '@start9labs/shared'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import {
|
||||
TuiButton,
|
||||
@@ -132,7 +132,7 @@ export class CifsComponent {
|
||||
|
||||
async submit(): Promise<void> {
|
||||
const loader = this.loader
|
||||
.open('Connecting to shared folder...')
|
||||
.open('Connecting to shared folder' as i18nKey)
|
||||
.subscribe()
|
||||
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
DiskInfo,
|
||||
DriveComponent,
|
||||
ErrorService,
|
||||
i18nKey,
|
||||
LoadingService,
|
||||
toGuid,
|
||||
} from '@start9labs/shared'
|
||||
@@ -84,7 +85,9 @@ export default class AttachPage {
|
||||
}
|
||||
|
||||
private async attachDrive(guid: string, password: string) {
|
||||
const loader = this.loader.open('Connecting to drive...').subscribe()
|
||||
const loader = this.loader
|
||||
.open('Connecting to drive' as i18nKey)
|
||||
.subscribe()
|
||||
|
||||
try {
|
||||
await this.stateService.importDrive(guid, password)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import { ErrorService, ServerComponent } from '@start9labs/shared'
|
||||
@@ -64,7 +63,6 @@ import { StateService } from 'src/app/services/state.service'
|
||||
TuiCell,
|
||||
TuiIcon,
|
||||
TuiTitle,
|
||||
DatePipe,
|
||||
ServerComponent,
|
||||
PasswordDirective,
|
||||
],
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
DiskInfo,
|
||||
DriveComponent,
|
||||
ErrorService,
|
||||
i18nKey,
|
||||
LoadingService,
|
||||
toGuid,
|
||||
} from '@start9labs/shared'
|
||||
@@ -156,7 +157,9 @@ export default class StoragePage {
|
||||
logicalname: string,
|
||||
password: string,
|
||||
): Promise<void> {
|
||||
const loader = this.loader.open('Connecting to drive...').subscribe()
|
||||
const loader = this.loader
|
||||
.open('Connecting to drive' as i18nKey)
|
||||
.subscribe()
|
||||
|
||||
try {
|
||||
await this.stateService.setupEmbassy(logicalname, password)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.1 MiB After Width: | Height: | Size: 83 KiB |
@@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import { TuiProgress } from '@taiga-ui/kit'
|
||||
import { LogsWindowComponent } from './logs-window.component'
|
||||
import { i18nPipe } from '../i18n/i18n.pipe'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -9,10 +10,10 @@ import { LogsWindowComponent } from './logs-window.component'
|
||||
template: `
|
||||
<section>
|
||||
<h1 [style.font-size.rem]="2" [style.margin-bottom.rem]="2">
|
||||
Setting up your server
|
||||
{{ 'Setting up your server' | i18n }}
|
||||
</h1>
|
||||
<div *ngIf="progress.total">
|
||||
Progress: {{ (progress.total * 100).toFixed(0) }}%
|
||||
{{ 'Progress' | i18n }}: {{ (progress.total * 100).toFixed(0) }}%
|
||||
</div>
|
||||
<progress
|
||||
tuiProgressBar
|
||||
@@ -49,7 +50,7 @@ import { LogsWindowComponent } from './logs-window.component'
|
||||
background: #181818;
|
||||
}
|
||||
`,
|
||||
imports: [CommonModule, LogsWindowComponent, TuiProgress],
|
||||
imports: [CommonModule, LogsWindowComponent, TuiProgress, i18nPipe],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class InitializingComponent {
|
||||
@@ -1,19 +0,0 @@
|
||||
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 80%;
|
||||
margin: auto;
|
||||
padding: 1.5rem;
|
||||
background: var(--tui-background-elevation-1);
|
||||
border-radius: var(--tui-radius-m);
|
||||
box-shadow: var(--tui-shadow-popup);
|
||||
|
||||
--tui-background-accent-1: var(--tui-status-warning);
|
||||
}
|
||||
|
||||
tui-loader {
|
||||
flex-shrink: 0;
|
||||
min-width: 2rem;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { TuiLoader } from '@taiga-ui/core'
|
||||
import { injectContext, PolymorpheusContent } from '@taiga-ui/polymorpheus'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: '<tui-loader [textContent]="content" />',
|
||||
styleUrls: ['./loading.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiLoader],
|
||||
})
|
||||
export class LoadingComponent {
|
||||
readonly content = injectContext<{ content: PolymorpheusContent }>().content
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { TuiPopoverService } from '@taiga-ui/cdk'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { TUI_DIALOGS } from '@taiga-ui/core'
|
||||
|
||||
import { LoadingComponent } from './loading.component'
|
||||
|
||||
@Injectable({
|
||||
providedIn: `root`,
|
||||
useFactory: () => new LoadingService(TUI_DIALOGS, LoadingComponent),
|
||||
})
|
||||
export class LoadingService extends TuiPopoverService<unknown> {}
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from '@ng-web-apis/intersection-observer'
|
||||
import { WaMutationObserver } from '@ng-web-apis/mutation-observer'
|
||||
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
||||
import { SetupLogsService } from '../../services/setup-logs.service'
|
||||
import { SetupLogsService } from '../services/setup-logs.service'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -1,13 +1,15 @@
|
||||
import { TuiTextfieldControllerModule, TuiInputModule } from '@taiga-ui/legacy'
|
||||
import { TuiAutoFocus } from '@taiga-ui/cdk'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { TuiDialogContext, TuiButton } from '@taiga-ui/core'
|
||||
import { TuiAutoFocus } from '@taiga-ui/cdk'
|
||||
import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
|
||||
import { TuiInputModule, TuiTextfieldControllerModule } from '@taiga-ui/legacy'
|
||||
import {
|
||||
POLYMORPHEUS_CONTEXT,
|
||||
PolymorpheusComponent,
|
||||
} from '@taiga-ui/polymorpheus'
|
||||
import { i18nPipe } from '../i18n/i18n.pipe'
|
||||
import { i18nKey } from '../i18n/i18n.providers'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -37,10 +39,10 @@ import {
|
||||
appearance="secondary"
|
||||
(click)="cancel()"
|
||||
>
|
||||
Cancel
|
||||
{{ 'Cancel' | i18n }}
|
||||
</button>
|
||||
<button tuiButton [disabled]="!value && options.required !== false">
|
||||
{{ options.buttonText || 'Submit' }}
|
||||
{{ options.buttonText || ('Submit' | i18n) }}
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
@@ -81,6 +83,7 @@ import {
|
||||
TuiButton,
|
||||
TuiTextfieldControllerModule,
|
||||
TuiAutoFocus,
|
||||
i18nPipe,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
@@ -111,11 +114,11 @@ export class PromptModal {
|
||||
export const PROMPT = new PolymorpheusComponent(PromptModal)
|
||||
|
||||
export interface PromptOptions {
|
||||
message: string
|
||||
label?: string
|
||||
warning?: string
|
||||
buttonText?: string
|
||||
placeholder?: string
|
||||
message: i18nKey
|
||||
label?: i18nKey
|
||||
warning?: i18nKey
|
||||
buttonText?: i18nKey
|
||||
placeholder?: i18nKey
|
||||
required?: boolean
|
||||
useMask?: boolean
|
||||
initialValue?: string | null
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { Component, inject, input } from '@angular/core'
|
||||
import { TuiDialogService, TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { Component, input } from '@angular/core'
|
||||
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { StartOSDiskInfo } from '../types/api'
|
||||
|
||||
|
||||
@@ -7,9 +7,24 @@ import {
|
||||
} from '@angular/core'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: '[ticker]',
|
||||
template: '<ng-content />',
|
||||
styleUrls: ['./ticker.component.scss'],
|
||||
styles: `
|
||||
:host {
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: text-indent 1s;
|
||||
|
||||
&:hover {
|
||||
text-indent: var(--indent, 0);
|
||||
text-overflow: clip;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class TickerComponent {
|
||||
@@ -1,13 +0,0 @@
|
||||
:host {
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: text-indent 1s;
|
||||
|
||||
&:hover {
|
||||
text-indent: var(--indent, 0);
|
||||
text-overflow: clip;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
|
||||
import { TickerComponent } from './ticker.component'
|
||||
|
||||
@NgModule({
|
||||
declarations: [TickerComponent],
|
||||
exports: [TickerComponent],
|
||||
})
|
||||
export class TickerModule {}
|
||||
489
web/projects/shared/src/i18n/dictionaries/english.ts
Normal file
489
web/projects/shared/src/i18n/dictionaries/english.ts
Normal file
@@ -0,0 +1,489 @@
|
||||
// prettier-ignore
|
||||
export const ENGLISH = {
|
||||
'Change': 1, // verb
|
||||
'Update': 2, // verb
|
||||
'Reset': 3, // verb
|
||||
'System': 4, // as in, system preferences
|
||||
'General': 5, // as in, general settings
|
||||
'Email': 6,
|
||||
'Create Backup': 7, // create a backup
|
||||
'Restore Backup': 8, // restore from backup
|
||||
'Go to login': 9,
|
||||
'Test': 10, // verb
|
||||
'Skip': 11, // as in, skip this step
|
||||
'Active Sessions': 12,
|
||||
'Change Password': 13,
|
||||
'General Settings': 14,
|
||||
'Manage your overall setup and preferences': 15,
|
||||
'Browser Tab Title': 16,
|
||||
'Language': 17,
|
||||
'Disk Repair': 18,
|
||||
'Attempt automatic repair': 19,
|
||||
'Repair': 20,
|
||||
'Root Certificate Authority': 21,
|
||||
'Download your Root CA': 22,
|
||||
'Download': 23,
|
||||
'Reset Tor': 24,
|
||||
'Restart the Tor daemon on your server': 25,
|
||||
'Software Update': 26,
|
||||
'Restart to apply': 27,
|
||||
'Check for updates': 28,
|
||||
'This value will be displayed as the title of your browser tab.': 29,
|
||||
'Device Name': 30,
|
||||
'StartOS': 31,
|
||||
'Save': 32,
|
||||
'Saving': 33,
|
||||
'Warning': 34,
|
||||
'Confirm': 35,
|
||||
'Cancel': 36,
|
||||
'This action should only be executed if directed by a Start9 support specialist. We recommend backing up your device before preforming this action. If anything happens to the device during the reboot, such as losing power or unplugging the drive, the filesystem will be in an unrecoverable state. Please proceed with caution.': 37,
|
||||
'Delete': 38,
|
||||
'Tor reset in progress': 39,
|
||||
'Resetting Tor': 40,
|
||||
'Checking for updates': 41,
|
||||
'Beginning restart': 42,
|
||||
'You are on the latest version of StartOS.': 43,
|
||||
'Up to date!': 44,
|
||||
'Release Notes': 45,
|
||||
'Begin Update': 46,
|
||||
'Beginning update': 47,
|
||||
'You are currently connected over Tor. If you reset the Tor daemon, you will lose connectivity until it comes back online.': 48,
|
||||
'Reset Tor?': 49,
|
||||
'Optionally wipe state to forcibly acquire new guard nodes. It is recommended to try without wiping state first.': 50,
|
||||
'Wipe state': 51,
|
||||
'Saving high score': 52,
|
||||
'Score': 53,
|
||||
'High score': 54,
|
||||
'Save and quit': 55,
|
||||
'Bookmark this page': 56,
|
||||
'Beginning shutdown': 57,
|
||||
'Add': 58,
|
||||
'Ok': 59,
|
||||
'Are you sure you want to delete this entry?': 60,
|
||||
'This value cannot be changed once set': 61,
|
||||
'Continue': 62,
|
||||
'Click or drop file here': 63,
|
||||
'Drop file here': 64,
|
||||
'Disabled': 65,
|
||||
'Version': 66,
|
||||
'Copy': 67, // as in, copy to clipboard
|
||||
'About this server': 68,
|
||||
'System Settings': 69,
|
||||
'Restart': 70,
|
||||
'Shutdown': 71,
|
||||
'Logout': 72,
|
||||
'User manual': 73,
|
||||
'Contact support': 74,
|
||||
'Donate to Start9': 75,
|
||||
'Are you sure you want to restart your server? It can take several minutes to come back online.': 76,
|
||||
'Are you sure you want to power down your server? This can take several minutes, and your server will not come back online automatically. To power on again, You will need to physically unplug your server and plug it back in.': 77,
|
||||
'Services': 78, // as in, server-side software applications
|
||||
'Marketplace': 79,
|
||||
'Sideload': 80, // installing a service manually
|
||||
'Updates': 81,
|
||||
'Metrics': 82, // system info such as CPU, RAM, and storage usage
|
||||
'Logs': 83, // as in, application logs
|
||||
'Notifications': 84,
|
||||
'Launch UI': 85,
|
||||
'Show QR': 86,
|
||||
'Copy URL': 87,
|
||||
'Actions': 88, // as in, actions available to the user
|
||||
'not recommended': 89,
|
||||
'Root CA Trusted!': 90,
|
||||
'Add a clearnet address to expose this interface on the Internet. Clearnet addresses are fully public and not anonymous.': 91,
|
||||
'Learn more': 92,
|
||||
'Make public': 93,
|
||||
'Make private': 94,
|
||||
'No public addresses': 95,
|
||||
'Add domain': 96,
|
||||
'Removing': 97,
|
||||
'Making public': 98,
|
||||
'Making private': 99,
|
||||
'Unsaved changes': 100,
|
||||
'You have unsaved changes. Are you sure you want to leave?': 101,
|
||||
'Leave': 102,
|
||||
'Are you sure?': 103,
|
||||
'Select Domain': 104,
|
||||
'Local': 105,
|
||||
'Local addresses can only be accessed by devices connected to the same LAN as your server, either directly or using a VPN.': 106,
|
||||
'Learn More': 107,
|
||||
'Public': 108,
|
||||
'Private': 109,
|
||||
'Add an onion address to anonymously expose this interface on the darknet. Onion addresses can only be reached over the Tor network.': 110,
|
||||
'No onion addresses': 111,
|
||||
'New Onion Address': 112,
|
||||
'Private Key (optional)': 113,
|
||||
'Optionally provide a base64-encoded ed25519 private key for generating the Tor V3 (.onion) address. If not provided, a random key will be generated and used.': 114,
|
||||
'Processing 10,000 logs': 115,
|
||||
'Loading older logs': 116,
|
||||
'Waiting for network connectivity': 117,
|
||||
'Reconnecting': 118,
|
||||
'Loading logs': 119,
|
||||
'Scroll to bottom': 120,
|
||||
'Reconnected': 121,
|
||||
'Disconnected': 122,
|
||||
'More': 123,
|
||||
'The following modifications were made': 124,
|
||||
'added': 125,
|
||||
'removed': 126,
|
||||
'changed from': 127,
|
||||
'to': 128, // as in, from [blank] to [blank]
|
||||
'entry': 129, // as in, "a list entry"
|
||||
'list': 130,
|
||||
'new entry': 131,
|
||||
'new list': 132,
|
||||
'Submit': 133,
|
||||
'Close': 134,
|
||||
'OS Logs': 135,
|
||||
'Kernel Logs': 136,
|
||||
'Tor Logs': 137,
|
||||
'Raw, unfiltered operating system logs': 138,
|
||||
'Diagnostics for drivers and other kernel processes': 139,
|
||||
'Diagnostic logs for the Tor daemon on StartOS': 140,
|
||||
'Downgrade': 141,
|
||||
'Reinstall': 142,
|
||||
'View Installed': 143,
|
||||
'Switch': 144,
|
||||
'Install': 145,
|
||||
'Beginning install': 146,
|
||||
'Change Registry': 147,
|
||||
'Services from this registry are packaged and maintained by the Start9 team. If you experience an issue or have questions related to a service from this registry, one of our dedicated support staff will be happy to assist you.': 148,
|
||||
'Services from this registry are packaged and maintained by members of the Start9 community. Install at your own risk. If you experience an issue or have a question related to a service in this marketplace, please reach out to the package developer for assistance.': 149,
|
||||
'Services from this registry are undergoing beta testing and may contain bugs. Install at your own risk.': 150,
|
||||
'Services from this registry are undergoing alpha testing. They are expected to contain bugs and could damage your system. Install at your own risk.': 151,
|
||||
'This is a Custom Registry. Start9 cannot verify the integrity or functionality of services from this registry, and they could damage your system. Install at your own risk.': 152,
|
||||
'Default Registries': 153,
|
||||
'Custom Registries': 154,
|
||||
'Add custom registry': 155,
|
||||
'Save for later': 156,
|
||||
'Save and connect': 157,
|
||||
'Deleting': 158,
|
||||
'Changing registry': 159,
|
||||
'Loading': 160,
|
||||
'Registry already added': 161,
|
||||
'Validating registry': 162,
|
||||
'Are you sure you want to delete this registry?': 163,
|
||||
'Add Custom Registry': 164,
|
||||
'A fully-qualified URL of the custom registry': 165,
|
||||
'Must be a valid URL': 166,
|
||||
'installed from': 167,
|
||||
'sideloaded': 168, // as in, the application was installed by sideloading
|
||||
'This service was originally': 169,
|
||||
'but you are currently connected to': 170,
|
||||
'To install from': 171,
|
||||
'anyway, click "Continue".': 172,
|
||||
'As a result of this update, the following services will no longer work properly and may crash': 173,
|
||||
'Alert': 174,
|
||||
'Percentage used': 175,
|
||||
'User space': 176,
|
||||
'Kernel space': 177,
|
||||
'Idle': 178, // a CPU metric
|
||||
'I/O wait': 179,
|
||||
'ACME': 180,
|
||||
'Total': 181,
|
||||
'Used': 182,
|
||||
'Available': 183,
|
||||
'zram used': 184,
|
||||
'zram total': 185,
|
||||
'zram available': 186,
|
||||
'System Time': 187,
|
||||
'Uptime': 188,
|
||||
'Temperature': 189,
|
||||
'Memory': 190, // as in, computer memory
|
||||
'Storage': 191,
|
||||
'Capacity': 192, // as in, disk capacity
|
||||
'Clock sync failure': 193,
|
||||
'the docs': 194, // as in, the documentation
|
||||
'Days': 195,
|
||||
'Hours': 196,
|
||||
'Minutes': 197,
|
||||
'Seconds': 198,
|
||||
'View full': 199,
|
||||
'View report': 200,
|
||||
'Batch action': 201,
|
||||
'Mark seen': 202,
|
||||
'Mark unseen': 203,
|
||||
'Date': 204,
|
||||
'Title': 205, // as in, the title of a book
|
||||
'Service': 206, // as in, server-side software application
|
||||
'Message': 207,
|
||||
'No notifications': 208,
|
||||
'Required': 209,
|
||||
'Optional': 210,
|
||||
'No reason provided': 211,
|
||||
'Tasks': 212,
|
||||
'Type': 213,
|
||||
'Description': 214,
|
||||
'All tasks complete': 215,
|
||||
'Start': 216,
|
||||
'Stop': 217,
|
||||
'Dependencies': 218,
|
||||
'Satisfied': 219,
|
||||
'No dependencies': 220,
|
||||
'Not installed': 221,
|
||||
'Incorrect version': 222,
|
||||
'Not running': 223,
|
||||
'Action required': 224,
|
||||
'Required health check not passing': 225,
|
||||
'Dependency has a dependency issue': 226,
|
||||
'Unknown error': 227,
|
||||
'Error': 228,
|
||||
'"Rebuild container" is a harmless action that and only takes a few seconds to complete. It will likely resolve this issue.': 229,
|
||||
'"Uninstall service" is a dangerous action that will remove the service from StartOS and wipe all its data.': 230,
|
||||
'Rebuild container': 231,
|
||||
'Uninstall service': 232,
|
||||
'View full message': 233,
|
||||
'Service error': 234,
|
||||
'Awaiting result': 235,
|
||||
'Starting': 236,
|
||||
'Success': 237,
|
||||
'Health Checks': 238,
|
||||
'No health checks': 239,
|
||||
'Name': 240,
|
||||
'Status': 241,
|
||||
'Open': 242, // verb
|
||||
'Interfaces': 243, // as in user interface or application program interface
|
||||
'Hosting': 244,
|
||||
'Installing': 245,
|
||||
'See below': 246,
|
||||
'Controls': 247,
|
||||
'No services installed': 248,
|
||||
'Running': 249,
|
||||
'Stopped': 250,
|
||||
'Task Required': 251,
|
||||
'Updating': 252,
|
||||
'Stopping': 253,
|
||||
'Trust your Root CA': 254,
|
||||
'Backing Up': 255,
|
||||
'Restarting': 256,
|
||||
'Back': 257,
|
||||
'Restoring': 258,
|
||||
'Unknown': 259,
|
||||
'Reveal/Hide': 260,
|
||||
'Reveal': 261,
|
||||
'Scan this QR': 262,
|
||||
'Reset defaults': 263,
|
||||
'As a result of this change, the following services will no longer work properly and may crash': 264,
|
||||
'Service Launch Error': 265,
|
||||
'Issue': 266,
|
||||
'Failure': 267,
|
||||
'Healthy': 268,
|
||||
'finalizing': 269,
|
||||
'unknown %': 270,
|
||||
'Not provided': 271,
|
||||
'Links': 272,
|
||||
'Git Hash': 273,
|
||||
'License': 274,
|
||||
'Installed From': 275,
|
||||
'Service Repository': 276,
|
||||
'Package Repository': 277,
|
||||
'Marketing Site': 278,
|
||||
'Support Site': 279,
|
||||
'Donation Link': 280,
|
||||
'Standard Actions': 281,
|
||||
'Rebuild Service': 282, // as in, rebuild a software container
|
||||
'Rebuilds the service container. Only necessary in there is a bug in StartOS': 283,
|
||||
'Uninstall': 284,
|
||||
'Uninstalls this service from StartOS and delete all data permanently.': 285,
|
||||
'Dashboard': 286,
|
||||
'dashboard': 287,
|
||||
'actions': 288,
|
||||
'instructions': 289,
|
||||
'logs': 290, // as in, "application logs"
|
||||
'about': 291, // as in, "about this server"
|
||||
'Starting upload': 292,
|
||||
'Try again': 293,
|
||||
'Upload .s9pk package file': 294,
|
||||
'Warning: package upload will be slow over Tor. Switch to local for a better experience.': 295,
|
||||
'Upload': 296,
|
||||
'Version 1 s9pk detected. This package format is deprecated. You can sideload a V1 s9pk via start-cli if necessary.': 297,
|
||||
'Invalid package file': 298,
|
||||
'Add ACME providers in order to generate SSL (https) certificates for clearnet access.': 299,
|
||||
'View instructions': 300,
|
||||
'Saved Providers': 301, // as in, ACME service provider, such as Let's Encrypt
|
||||
'Add Provider': 302,
|
||||
'Contact': 303, // as in, "contact us"
|
||||
'Edit': 304,
|
||||
'Add ACME Provider': 305,
|
||||
'Edit ACME Provider': 306,
|
||||
'Contact Emails': 307,
|
||||
'Needed to obtain a certificate from a Certificate Authority': 308,
|
||||
'Toggle all': 309,
|
||||
'Done': 310,
|
||||
'Master Password Needed': 311,
|
||||
'Enter your master password to encrypt this backup.': 312,
|
||||
'Master Password': 313,
|
||||
'Enter master password': 314,
|
||||
'Original Password Needed': 315,
|
||||
'This backup was created with a different password. Enter the original password that was used to encrypt this backup.': 316,
|
||||
'Original Password': 317,
|
||||
'Enter original password': 318,
|
||||
'Beginning backup': 319,
|
||||
'Back up StartOS and service data by connecting to a device on your local network or a physical drive connected to your server.': 320,
|
||||
'Restore StartOS and service data from a device on your local network or a physical drive connected to your server that contains an existing backup.': 321,
|
||||
'Last Backup': 322, // as in, the last time the server was backed up
|
||||
'A folder on another computer that is connected to the same network as your Start9 server.': 323,
|
||||
'A physical drive that is plugged directly into your Start9 Server.': 324,
|
||||
'Select Services to Back Up': 325,
|
||||
'Select server backup': 326,
|
||||
'Network Folders': 327,
|
||||
'Open New': 328,
|
||||
'Hostname': 329,
|
||||
'Path': 330, // as in, a URL path
|
||||
'URL': 331,
|
||||
'Network Interface': 332,
|
||||
'Protocol': 333, // as in, http protocol
|
||||
'Model': 334, // as in, a product model
|
||||
'User Agent': 335,
|
||||
'Platform': 336, // as in, OS platform, such as iOS, Android, Linux, etc
|
||||
'Last Active': 337,
|
||||
'Created At': 338,
|
||||
'Algorithm': 339, // as in, the encryption algorithm
|
||||
'Fingerprint': 340, // as in, a fingerprint hash
|
||||
'Package Hash': 341,
|
||||
'Published': 342,
|
||||
'New Network Folder': 343,
|
||||
'Update Network Folder': 344,
|
||||
'Testing connectivity to shared folder': 345,
|
||||
'Ensure (1) target computer is connected to the same LAN as your Start9 Server, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.': 346,
|
||||
'Unable to connect': 347,
|
||||
'Network Folder does not contain a valid backup': 348,
|
||||
'Connect': 349,
|
||||
'Username': 350,
|
||||
'Password': 351,
|
||||
'The hostname of your target device on the Local Area Network.': 352,
|
||||
'On Windows, this is the fully qualified path to the shared folder, (e.g. /Desktop/my-folder). On Linux and Mac, this is the literal name of the shared folder (e.g. my-shared-folder).': 353,
|
||||
'On Linux, this is the samba username you created when sharing the folder. On Mac and Windows, this is the username of the user who is sharing the folder.': 354,
|
||||
'On Linux, this is the samba password you created when sharing the folder. On Mac and Windows, this is the password of the user who is sharing the folder.': 355,
|
||||
'Physical Drives': 356,
|
||||
'No drives detected': 357,
|
||||
'Refresh': 358,
|
||||
'Drive partition does not contain a valid backup': 359,
|
||||
'Backup Progress': 360,
|
||||
'Complete': 361,
|
||||
'Backing up': 362,
|
||||
'Waiting': 363,
|
||||
'Backup made': 364,
|
||||
'Restore selected': 365,
|
||||
'Initializing': 366,
|
||||
'Unavailable. Backup was made on a newer version of StartOS.': 367,
|
||||
'Unavailable. Service is already installed.': 368,
|
||||
'Ready to restore': 369,
|
||||
'Local Hostname': 370,
|
||||
'Created': 371,
|
||||
'Password Required': 372,
|
||||
'Enter the master password that was used to encrypt this backup. On the next screen, you will select the individual services you want to restore.': 373,
|
||||
'Decrypting drive': 374,
|
||||
'Select services to restore': 375,
|
||||
'Available for backup': 376,
|
||||
'StartOS backups detected': 377,
|
||||
'No StartOS backups detected': 378,
|
||||
'StartOS Version': 379,
|
||||
'Connecting an external SMTP server allows StartOS and your installed services to send you emails.': 380,
|
||||
'SMTP Credentials': 381,
|
||||
'Send test email': 382,
|
||||
'Send': 383,
|
||||
'Sending email': 384,
|
||||
'A test email has been sent to': 385,
|
||||
'Check your spam folder and mark as not spam.': 386,
|
||||
'The web user interface for your StartOS server, accessible from any browser.': 387,
|
||||
'Change your StartOS master password.': 388,
|
||||
'You will still need your current password to decrypt existing backups!': 389,
|
||||
'New passwords do not match': 390,
|
||||
'New password must be 12 characters or greater': 391,
|
||||
'New password must be less than 65 characters': 392,
|
||||
'Current password is invalid': 393,
|
||||
'Password changed': 394,
|
||||
'Current Password': 395,
|
||||
'New Password': 396,
|
||||
'Retype New Password': 397,
|
||||
'A session is a device that is currently logged into StartOS. For best security, terminate sessions you do not recognize or no longer use.': 398,
|
||||
'Current session': 399,
|
||||
'Other sessions': 400,
|
||||
'Terminate selected': 401,
|
||||
'Terminating sessions': 402,
|
||||
'No sessions': 403,
|
||||
'Password Needed': 404,
|
||||
'Connected': 405,
|
||||
'Forget': 406, // as in, delete or remove
|
||||
'WiFi Credentials': 407,
|
||||
'Deprecated': 408,
|
||||
'WiFi support will be removed in StartOS v0.4.1. If you do not have access to Ethernet, you can use a WiFi extender to connect to the local network, then connect your server to the extender via Ethernet. Please contact Start9 support with any questions or concerns.': 409,
|
||||
'Known Networks': 410,
|
||||
'Other Networks': 411,
|
||||
'WiFi is disabled': 412,
|
||||
'No wireless interface detected': 413,
|
||||
'Enabling WiFi': 414,
|
||||
'Disabling WiFi': 415,
|
||||
'Connecting. This could take a while': 416,
|
||||
'Retry': 417,
|
||||
'Show more': 418,
|
||||
'Release notes': 419,
|
||||
'View listing': 420,
|
||||
'Services that depend on': 421,
|
||||
'will no longer work properly and may crash.': 422,
|
||||
'Request failed': 423,
|
||||
'All services are up to date!': 424,
|
||||
'Run': 425, // as in, run a piece of software
|
||||
'Action can only be executed when service is': 426,
|
||||
'Forbidden': 427,
|
||||
'may temporarily experiences issues': 428,
|
||||
'has unmet dependencies. It will not work as expected.': 429,
|
||||
'Rebuilding container': 430,
|
||||
'Beginning uninstall': 431,
|
||||
'will permanently delete its data.': 432,
|
||||
'Uninstalling': 433,
|
||||
'Trying to reach server': 434,
|
||||
'Connection restored': 435,
|
||||
'State unknown': 436,
|
||||
'Server connected': 437,
|
||||
'No Internet': 438,
|
||||
'Connecting': 439,
|
||||
'Shutting down': 440,
|
||||
'Versions': 441,
|
||||
'New notifications': 442,
|
||||
'View': 443,
|
||||
'Reloading PWA': 444,
|
||||
'Completed': 445,
|
||||
'System data': 446,
|
||||
'Not attempted': 447,
|
||||
'Failed': 448,
|
||||
'Succeeded': 449,
|
||||
'Restart your server for these updates to take effect. It can take several minutes to come back online.': 450,
|
||||
'StartOS download complete': 451,
|
||||
'Unknown storage drive detected': 452,
|
||||
'To use a different storage drive, replace the current one and click RESTART SERVER below. To use the current storage drive, click USE CURRENT DRIVE below, then follow instructions. No data will be erased during this process.': 453,
|
||||
'Storage drive not found': 454,
|
||||
'Insert your StartOS storage drive and click RESTART SERVER below.': 455,
|
||||
'Storage drive corrupted. This could be the result of data corruption or physical damage.': 456,
|
||||
'It may or may not be possible to re-use this drive by reformatting and recovering from backup. To enter recovery mode, click ENTER RECOVERY MODE below, then follow instructions. No data will be erased during this step.': 457,
|
||||
'Filesystem error': 458,
|
||||
'Repairing the disk could help resolve this issue. Please DO NOT unplug the drive or server during this time or the situation will become worse.': 459,
|
||||
'Disk management error': 460,
|
||||
'Please contact support': 461,
|
||||
'Diagnostic Mode': 462,
|
||||
'launch error': 463,
|
||||
'View logs': 464,
|
||||
'Possible solutions': 465,
|
||||
'Setup current drive': 466,
|
||||
'Enter recovery mode': 467,
|
||||
'Server is restarting': 468,
|
||||
'Wait for the server to restart, then refresh this page.': 469,
|
||||
'Restart server': 470,
|
||||
'Repair drive': 471,
|
||||
'Setting up your server': 472,
|
||||
'Progress': 473,
|
||||
'Login to StartOS': 474,
|
||||
'Login': 475,
|
||||
'Logging in': 476,
|
||||
'Password must be less than 65 characters': 477,
|
||||
'Invalid password': 478,
|
||||
'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.': 479,
|
||||
'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.': 480,
|
||||
'You have successfully trusted your Root CA and may now log in securely.': 481,
|
||||
'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.': 482,
|
||||
'Follow instructions for your OS. By trusting your Root CA, your device can verify the authenticity of encrypted communications with your server.': 483,
|
||||
'Refresh the page. If refreshing the page does not work, you may need to quit and re-open your browser, then revisit this page.': 484,
|
||||
'StartOS UI': 485,
|
||||
'WiFi': 486,
|
||||
} as const
|
||||
490
web/projects/shared/src/i18n/dictionaries/german.ts
Normal file
490
web/projects/shared/src/i18n/dictionaries/german.ts
Normal file
@@ -0,0 +1,490 @@
|
||||
import type { i18n } from '../i18n.providers'
|
||||
|
||||
export default {
|
||||
1: 'Ändern',
|
||||
2: 'Aktualisieren',
|
||||
3: 'Zurücksetzen',
|
||||
4: 'System',
|
||||
5: 'Allgemein',
|
||||
6: 'E-Mail',
|
||||
7: 'Sicherung erstellen',
|
||||
8: 'Sicherung wiederherstellen',
|
||||
9: 'Zum Login gehen',
|
||||
10: 'Testen',
|
||||
11: 'Überspringen',
|
||||
12: 'Aktive Sitzungen',
|
||||
13: 'Passwort ändern',
|
||||
14: 'Allgemeine Einstellungen',
|
||||
15: 'Verwalten Sie Ihre Gesamteinrichtung und Einstellungen',
|
||||
16: 'Browser-Tab Titel',
|
||||
17: 'Sprache',
|
||||
18: 'Festplattenreparatur',
|
||||
19: 'Automatische Reparatur versuchen',
|
||||
20: 'Reparieren',
|
||||
21: 'Stammzertifizierungsstelle (Root-CA)',
|
||||
22: 'Laden Sie Ihre Root-CA herunter',
|
||||
23: 'Herunterladen',
|
||||
24: 'Tor zurücksetzen',
|
||||
25: 'Tor-Daemon auf Ihrem Server neu starten',
|
||||
26: 'Software-Aktualisierung',
|
||||
27: 'Neustart erforderlich',
|
||||
28: 'Nach Updates suchen',
|
||||
29: 'Dieser Wert wird als Titel Ihres Browser-Tabs angezeigt.',
|
||||
30: 'Gerätename',
|
||||
31: 'StartOS',
|
||||
32: 'Speichern',
|
||||
33: 'Wird gespeichert',
|
||||
34: 'Warnung',
|
||||
35: 'Bestätigen',
|
||||
36: 'Abbrechen',
|
||||
37: 'Diese Aktion sollte nur auf Anweisung eines Start9-Supportmitarbeiters ausgeführt werden. Wir empfehlen, vor dem Fortfahren eine Sicherung Ihres Geräts zu erstellen. Wenn während des Neustarts etwas schiefgeht, z. B. Stromausfall oder das Trennen des Laufwerks, kann das Dateisystem irreparabel beschädigt werden. Bitte fahren Sie mit Vorsicht fort.',
|
||||
38: 'Löschen',
|
||||
39: 'Tor-Reset läuft',
|
||||
40: 'Tor wird zurückgesetzt',
|
||||
41: 'Suche nach Updates',
|
||||
42: 'Neustart wird eingeleitet',
|
||||
43: 'Sie verwenden die neueste Version von StartOS.',
|
||||
44: 'Auf dem neuesten Stand!',
|
||||
45: 'Versionshinweise',
|
||||
46: 'Update starten',
|
||||
47: 'Update wird gestartet',
|
||||
48: 'Sie sind derzeit über Tor verbunden. Wenn Sie den Tor-Daemon zurücksetzen, verlieren Sie die Verbindung, bis dieser wieder online ist.',
|
||||
49: 'Tor zurücksetzen?',
|
||||
50: 'Optional Zustand löschen, um neue Guard-Nodes zu erzwingen. Es wird empfohlen, zuerst ohne das Löschen zu versuchen.',
|
||||
51: 'Zustand löschen',
|
||||
52: 'Bestenstand wird gespeichert',
|
||||
53: 'Punktzahl',
|
||||
54: 'Bestpunktzahl',
|
||||
55: 'Speichern und beenden',
|
||||
56: 'Diese Seite als Lesezeichen speichern',
|
||||
57: 'Herunterfahren wird eingeleitet',
|
||||
58: 'Hinzufügen',
|
||||
59: 'Ok',
|
||||
60: 'Möchten Sie diesen Eintrag wirklich löschen?',
|
||||
61: 'Dieser Wert kann nach dem Festlegen nicht geändert werden',
|
||||
62: 'Fortfahren',
|
||||
63: 'Klicken oder Datei hierher ziehen',
|
||||
64: 'Datei hierher ziehen',
|
||||
65: 'Deaktiviert',
|
||||
66: 'Version',
|
||||
67: 'Kopieren',
|
||||
68: 'Über diesen Server',
|
||||
69: 'Systemeinstellungen',
|
||||
70: 'Neustarten',
|
||||
71: 'Herunterfahren',
|
||||
72: 'Abmelden',
|
||||
73: 'Benutzerhandbuch',
|
||||
74: 'Support kontaktieren',
|
||||
75: 'An Start9 spenden',
|
||||
76: 'Möchten Sie den Server wirklich neu starten? Es kann einige Minuten dauern, bis er wieder online ist.',
|
||||
77: 'Möchten Sie den Server wirklich herunterfahren? Dies kann einige Minuten dauern, und der Server wird nicht automatisch wieder eingeschaltet. Um ihn erneut einzuschalten, müssen Sie das Gerät physisch vom Stromnetz trennen und wieder anschließen.',
|
||||
78: 'Dienste',
|
||||
79: 'Marktplatz',
|
||||
80: 'Manuell installieren',
|
||||
81: 'Updates',
|
||||
82: 'Metriken',
|
||||
83: 'Protokolle',
|
||||
84: 'Benachrichtigungen',
|
||||
85: 'UI starten',
|
||||
86: 'QR-Code anzeigen',
|
||||
87: 'URL kopieren',
|
||||
88: 'Aktionen',
|
||||
89: 'nicht empfohlen',
|
||||
90: 'Root-CA ist vertrauenswürdig!',
|
||||
91: 'Fügen Sie eine Clearnet-Adresse hinzu, um diese Oberfläche im Internet verfügbar zu machen. Clearnet-Adressen sind vollständig öffentlich und nicht anonym.',
|
||||
92: 'Mehr erfahren',
|
||||
93: 'Öffentlich machen',
|
||||
94: 'Privat machen',
|
||||
95: 'Keine öffentlichen Adressen',
|
||||
96: 'Domain hinzufügen',
|
||||
97: 'Wird entfernt',
|
||||
98: 'Wird öffentlich gemacht',
|
||||
99: 'Wird privat gemacht',
|
||||
100: 'Nicht gespeicherte Änderungen',
|
||||
101: 'Sie haben nicht gespeicherte Änderungen. Möchten Sie die Seite wirklich verlassen?',
|
||||
102: 'Verlassen',
|
||||
103: 'Sind Sie sicher?',
|
||||
104: 'Domain auswählen',
|
||||
105: 'Lokal',
|
||||
106: 'Lokale Adressen sind nur von Geräten erreichbar, die direkt oder über VPN mit demselben LAN wie Ihr Server verbunden sind.',
|
||||
107: 'Mehr erfahren',
|
||||
108: 'Öffentlich',
|
||||
109: 'Privat',
|
||||
110: 'Fügen Sie eine Onion-Adresse hinzu, um dieses Interface anonym im Darknet verfügbar zu machen. Onion-Adressen sind nur über das Tor-Netzwerk erreichbar.',
|
||||
111: 'Keine Onion-Adressen',
|
||||
112: 'Neue Onion-Adresse',
|
||||
113: 'Privater Schlüssel (optional)',
|
||||
114: 'Optional können Sie einen base64-codierten ed25519-Schlüssel angeben, um die Tor V3 (.onion)-Adresse zu generieren. Wenn nicht angegeben, wird ein zufälliger Schlüssel erstellt.',
|
||||
115: 'Verarbeite 10.000 Logs',
|
||||
116: 'Ältere Logs werden geladen',
|
||||
117: 'Warten auf Netzwerkverbindung',
|
||||
118: 'Wiederverbindung läuft',
|
||||
119: 'Lade Logs',
|
||||
120: 'Zum Ende scrollen',
|
||||
121: 'Wieder verbunden',
|
||||
122: 'Verbindung getrennt',
|
||||
123: 'Mehr',
|
||||
124: 'Die folgenden Änderungen wurden vorgenommen',
|
||||
125: 'hinzugefügt',
|
||||
126: 'entfernt',
|
||||
127: 'geändert von',
|
||||
128: 'zu',
|
||||
129: 'Eintrag',
|
||||
130: 'Liste',
|
||||
131: 'neuer Eintrag',
|
||||
132: 'neue Liste',
|
||||
133: 'Absenden',
|
||||
134: 'Schließen',
|
||||
135: 'Betriebssystem-Logs',
|
||||
136: 'Kernel-Logs',
|
||||
137: 'Tor-Logs',
|
||||
138: 'Rohdatenprotokolle des Betriebssystems ohne Filter',
|
||||
139: 'Diagnose für Treiber und andere Kernel-Prozesse',
|
||||
140: 'Diagnose-Logs des Tor-Daemons unter StartOS',
|
||||
141: 'Downgrade',
|
||||
142: 'Neu installieren',
|
||||
143: 'Installierte anzeigen',
|
||||
144: 'Wechseln',
|
||||
145: 'Installieren',
|
||||
146: 'Installation wird gestartet',
|
||||
147: 'Register wechseln',
|
||||
148: 'Dienste in diesem Register werden vom Start9-Team gepackt und gepflegt. Bei Problemen oder Fragen hilft Ihnen unser Support-Team gerne weiter.',
|
||||
149: 'Dienste in diesem Register werden von der Start9-Community gepflegt. Die Installation erfolgt auf eigenes Risiko. Bei Problemen wenden Sie sich bitte an den Paketentwickler.',
|
||||
150: 'Dienste in diesem Register befinden sich in der Betaphase und können Fehler enthalten. Die Installation erfolgt auf eigenes Risiko.',
|
||||
151: 'Dienste in diesem Register befinden sich in der Alphaphase. Sie enthalten voraussichtlich Fehler und könnten Ihr System beschädigen. Installation auf eigenes Risiko.',
|
||||
152: 'Dies ist ein benutzerdefiniertes Register. Start9 kann die Integrität oder Funktionalität der darin enthaltenen Dienste nicht garantieren. Installation auf eigenes Risiko.',
|
||||
153: 'Standard-Register',
|
||||
154: 'Benutzerdefinierte Register',
|
||||
155: 'Benutzerdefiniertes Register hinzufügen',
|
||||
156: 'Für später speichern',
|
||||
157: 'Speichern und verbinden',
|
||||
158: 'Wird gelöscht',
|
||||
159: 'Register wird gewechselt',
|
||||
160: 'Lade...',
|
||||
161: 'Register bereits hinzugefügt',
|
||||
162: 'Register wird überprüft',
|
||||
163: 'Möchten Sie dieses Register wirklich löschen?',
|
||||
164: 'Benutzerdefiniertes Register hinzufügen',
|
||||
165: 'Vollständige URL des benutzerdefinierten Registers',
|
||||
166: 'Muss eine gültige URL sein',
|
||||
167: 'installiert von',
|
||||
168: 'manuell installiert',
|
||||
169: 'Dieser Dienst wurde ursprünglich',
|
||||
170: 'aber derzeit sind Sie verbunden mit',
|
||||
171: 'Um von',
|
||||
172: 'zu installieren, klicken Sie trotzdem auf „Weiter“.',
|
||||
173: 'Durch dieses Update funktionieren die folgenden Dienste möglicherweise nicht mehr richtig und könnten abstürzen:',
|
||||
174: 'Warnung',
|
||||
175: 'Prozent verwendet',
|
||||
176: 'Benutzerspeicher',
|
||||
177: 'Kernelspeicher',
|
||||
178: 'Leerlauf',
|
||||
179: 'I/O-Wartezeit',
|
||||
180: 'ACME',
|
||||
181: 'Gesamt',
|
||||
182: 'Verwendet',
|
||||
183: 'Verfügbar',
|
||||
184: 'zram verwendet',
|
||||
185: 'zram gesamt',
|
||||
186: 'zram verfügbar',
|
||||
187: 'Systemzeit',
|
||||
188: 'Betriebszeit',
|
||||
189: 'Temperatur',
|
||||
190: 'Arbeitsspeicher',
|
||||
191: 'Speicher',
|
||||
192: 'Kapazität',
|
||||
193: 'Fehler bei Uhrzeitsynchronisierung',
|
||||
194: 'die Dokumentation',
|
||||
195: 'Tage',
|
||||
196: 'Stunden',
|
||||
197: 'Minuten',
|
||||
198: 'Sekunden',
|
||||
199: 'Vollständig anzeigen',
|
||||
200: 'Bericht anzeigen',
|
||||
201: 'Stapelaktion',
|
||||
202: 'Als gelesen markieren',
|
||||
203: 'Als ungelesen markieren',
|
||||
204: 'Datum',
|
||||
205: 'Titel',
|
||||
206: 'Dienst',
|
||||
207: 'Nachricht',
|
||||
208: 'Keine Benachrichtigungen',
|
||||
209: 'Erforderlich',
|
||||
210: 'Optional',
|
||||
211: 'Kein Grund angegeben',
|
||||
212: 'Aufgaben',
|
||||
213: 'Typ',
|
||||
214: 'Beschreibung',
|
||||
215: 'Alle Aufgaben abgeschlossen',
|
||||
216: 'Starten',
|
||||
217: 'Stoppen',
|
||||
218: 'Abhängigkeiten',
|
||||
219: 'Erfüllt',
|
||||
220: 'Keine Abhängigkeiten',
|
||||
221: 'Nicht installiert',
|
||||
222: 'Falsche Version',
|
||||
223: 'Nicht aktiv',
|
||||
224: 'Aktion erforderlich',
|
||||
225: 'Erforderlicher Gesundheitscheck fehlgeschlagen',
|
||||
226: 'Abhängigkeit hat ein Problem mit einer weiteren Abhängigkeit',
|
||||
227: 'Unbekannter Fehler',
|
||||
228: 'Fehler',
|
||||
229: '"Container neu bauen" ist eine harmlose Aktion, die nur wenige Sekunden dauert. Sie wird dieses Problem wahrscheinlich beheben.',
|
||||
230: '"Dienst deinstallieren" ist eine gefährliche Aktion, die den Dienst aus StartOS entfernt und alle zugehörigen Daten dauerhaft löscht.',
|
||||
231: 'Container neu bauen',
|
||||
232: 'Dienst deinstallieren',
|
||||
233: 'Vollständige Nachricht anzeigen',
|
||||
234: 'Dienstfehler',
|
||||
235: 'Warte auf Ergebnis',
|
||||
236: 'Wird gestartet',
|
||||
237: 'Erfolg',
|
||||
238: 'Gesundheitschecks',
|
||||
239: 'Keine Gesundheitschecks',
|
||||
240: 'Name',
|
||||
241: 'Status',
|
||||
242: 'Öffnen',
|
||||
243: 'Schnittstellen',
|
||||
244: 'Hosting',
|
||||
245: 'Installation läuft',
|
||||
246: 'Siehe unten',
|
||||
247: 'Steuerelemente',
|
||||
248: 'Keine Dienste installiert',
|
||||
249: 'Läuft',
|
||||
250: 'Gestoppt',
|
||||
251: 'Aufgabe erforderlich',
|
||||
252: 'Wird aktualisiert',
|
||||
253: 'Wird gestoppt',
|
||||
254: 'Vertrauen Sie Ihrer Root-CA',
|
||||
255: 'Sicherung läuft',
|
||||
256: 'Wird neu gestartet',
|
||||
257: 'Zurück',
|
||||
258: 'Wiederherstellen',
|
||||
259: 'Unbekannt',
|
||||
260: 'Anzeigen/Verbergen',
|
||||
261: 'Anzeigen',
|
||||
262: 'Diesen QR-Code scannen',
|
||||
263: 'Auf Standard zurücksetzen',
|
||||
264: 'Durch diese Änderung funktionieren die folgenden Dienste möglicherweise nicht mehr richtig und könnten abstürzen',
|
||||
265: 'Fehler beim Starten des Dienstes',
|
||||
266: 'Problem',
|
||||
267: 'Fehlgeschlagen',
|
||||
268: 'Gesund',
|
||||
269: 'Wird abgeschlossen',
|
||||
270: 'unbekannt %',
|
||||
271: 'Nicht angegeben',
|
||||
272: 'Links',
|
||||
273: 'Git-Hash',
|
||||
274: 'Lizenz',
|
||||
275: 'Installiert von',
|
||||
276: 'Dienst-Repository',
|
||||
277: 'Paket-Repository',
|
||||
278: 'Marketing-Website',
|
||||
279: 'Support-Website',
|
||||
280: 'Spendenlink',
|
||||
281: 'Standardaktionen',
|
||||
282: 'Dienst neu bauen',
|
||||
283: 'Baut den Dienst-Container neu. Nur erforderlich, wenn ein Fehler in StartOS vorliegt.',
|
||||
284: 'Deinstallieren',
|
||||
285: 'Deinstalliert diesen Dienst aus StartOS und löscht alle Daten dauerhaft.',
|
||||
286: 'Dashboard',
|
||||
287: 'dashboard',
|
||||
288: 'aktionen',
|
||||
289: 'anleitungen',
|
||||
290: 'logs',
|
||||
291: 'über',
|
||||
292: 'Upload wird gestartet',
|
||||
293: 'Erneut versuchen',
|
||||
294: '.s9pk-Paketdatei hochladen',
|
||||
295: 'Warnung: Der Upload über Tor ist langsam. Wechseln Sie für bessere Leistung ins lokale Netzwerk.',
|
||||
296: 'Hochladen',
|
||||
297: 'Version 1 s9pk erkannt. Dieses Format ist veraltet. Falls nötig, kann ein V1 s9pk über start-cli installiert werden.',
|
||||
298: 'Ungültige Paketdatei',
|
||||
299: 'Fügen Sie ACME-Anbieter hinzu, um SSL-(https)-Zertifikate für den Clearnet-Zugriff zu generieren.',
|
||||
300: 'Anleitung anzeigen',
|
||||
301: 'Gespeicherte Anbieter',
|
||||
302: 'Anbieter hinzufügen',
|
||||
303: 'Kontakt',
|
||||
304: 'Bearbeiten',
|
||||
305: 'ACME-Anbieter hinzufügen',
|
||||
306: 'ACME-Anbieter bearbeiten',
|
||||
307: 'Kontakt-E-Mails',
|
||||
308: 'Erforderlich, um ein Zertifikat von einer Zertifizierungsstelle zu erhalten',
|
||||
309: 'Alle umschalten',
|
||||
310: 'Fertig',
|
||||
311: 'Master-Passwort erforderlich',
|
||||
312: 'Geben Sie Ihr Master-Passwort ein, um diese Sicherung zu verschlüsseln.',
|
||||
313: 'Master-Passwort',
|
||||
314: 'Master-Passwort eingeben',
|
||||
315: 'Originalpasswort erforderlich',
|
||||
316: 'Diese Sicherung wurde mit einem anderen Passwort erstellt. Bitte geben Sie das ursprüngliche Passwort ein, das zur Verschlüsselung verwendet wurde.',
|
||||
317: 'Originalpasswort',
|
||||
318: 'Originalpasswort eingeben',
|
||||
319: 'Sicherung wird gestartet',
|
||||
320: 'Sichern Sie StartOS und Dienstdaten, indem Sie sich mit einem Gerät im lokalen Netzwerk oder einem physischen Laufwerk verbinden, das an Ihren Server angeschlossen ist.',
|
||||
321: 'Stellen Sie StartOS und Dienstdaten von einem Gerät im lokalen Netzwerk oder einem physischen Laufwerk mit vorhandener Sicherung wieder her.',
|
||||
322: 'Letzte Sicherung',
|
||||
323: 'Ein Ordner auf einem anderen Computer, der mit demselben Netzwerk wie Ihr Start9-Server verbunden ist.',
|
||||
324: 'Ein physisches Laufwerk, das direkt an Ihren Start9-Server angeschlossen ist.',
|
||||
325: 'Dienste für Sicherung auswählen',
|
||||
326: 'Serversicherung auswählen',
|
||||
327: 'Netzwerkordner',
|
||||
328: 'Neuen öffnen',
|
||||
329: 'Hostname',
|
||||
330: 'Pfad',
|
||||
331: 'URL',
|
||||
332: 'Netzwerkschnittstelle',
|
||||
333: 'Protokoll',
|
||||
334: 'Modell',
|
||||
335: 'User-Agent',
|
||||
336: 'Plattform',
|
||||
337: 'Zuletzt aktiv',
|
||||
338: 'Erstellt am',
|
||||
339: 'Algorithmus',
|
||||
340: 'Fingerabdruck',
|
||||
341: 'Paket-Hash',
|
||||
342: 'Veröffentlicht',
|
||||
343: 'Neuer Netzwerkordner',
|
||||
344: 'Netzwerkordner aktualisieren',
|
||||
345: 'Verbindung zum freigegebenen Ordner wird getestet',
|
||||
346: 'Stellen Sie sicher, dass (1) das Zielgerät im selben LAN ist wie Ihr Start9-Server, (2) der Zielordner freigegeben ist und (3) Hostname, Pfad und Anmeldedaten korrekt sind.',
|
||||
347: 'Verbindung fehlgeschlagen',
|
||||
348: 'Netzwerkordner enthält keine gültige Sicherung',
|
||||
349: 'Verbinden',
|
||||
350: 'Benutzername',
|
||||
351: 'Passwort',
|
||||
352: 'Der Hostname Ihres Zielgeräts im lokalen Netzwerk.',
|
||||
353: 'Unter Windows ist dies der vollständige Pfad zum freigegebenen Ordner (z. B. /Desktop/mein-ordner). Unter Linux und Mac ist es der tatsächliche Name des Ordners (z. B. mein-freigabe-ordner).',
|
||||
354: 'Unter Linux ist dies der Samba-Benutzername, den Sie beim Freigeben erstellt haben. Unter Mac und Windows ist es der Benutzername des Freigebenden.',
|
||||
355: 'Unter Linux ist dies das Samba-Passwort. Unter Mac und Windows ist es das Passwort des freigebenden Benutzers.',
|
||||
356: 'Physische Laufwerke',
|
||||
357: 'Keine Laufwerke erkannt',
|
||||
358: 'Aktualisieren',
|
||||
359: 'Die Partition enthält keine gültige Sicherung',
|
||||
360: 'Sicherungsfortschritt',
|
||||
361: 'Abgeschlossen',
|
||||
362: 'Sicherung läuft',
|
||||
363: 'Warten',
|
||||
364: 'Sicherung erstellt',
|
||||
365: 'Wiederherstellung ausgewählt',
|
||||
366: 'Initialisierung',
|
||||
367: 'Nicht verfügbar. Sicherung wurde mit einer neueren Version von StartOS erstellt.',
|
||||
368: 'Nicht verfügbar. Dienst ist bereits installiert.',
|
||||
369: 'Bereit zur Wiederherstellung',
|
||||
370: 'Lokaler Hostname',
|
||||
371: 'Erstellt',
|
||||
372: 'Passwort erforderlich',
|
||||
373: 'Geben Sie das Master-Passwort ein, das zur Verschlüsselung dieser Sicherung verwendet wurde. Im nächsten Schritt wählen Sie die Dienste aus, die wiederhergestellt werden sollen.',
|
||||
374: 'Laufwerk wird entschlüsselt',
|
||||
375: 'Dienste zur Wiederherstellung auswählen',
|
||||
376: 'Für Sicherung verfügbar',
|
||||
377: 'StartOS-Sicherungen erkannt',
|
||||
378: 'Keine StartOS-Sicherungen erkannt',
|
||||
379: 'StartOS-Version',
|
||||
380: 'Die Verbindung zu einem externen SMTP-Server ermöglicht es StartOS und seinen Diensten, E-Mails zu senden.',
|
||||
381: 'SMTP-Zugangsdaten',
|
||||
382: 'Test-E-Mail senden',
|
||||
383: 'Senden',
|
||||
384: 'E-Mail wird gesendet',
|
||||
385: 'Eine Test-E-Mail wurde gesendet an',
|
||||
386: 'Prüfen Sie Ihren Spam-Ordner und markieren Sie die Nachricht als „kein Spam“.',
|
||||
387: 'Die Web-Benutzeroberfläche Ihres StartOS-Servers, zugänglich über jeden Browser.',
|
||||
388: 'Ändern Sie Ihr Master-Passwort für StartOS.',
|
||||
389: 'Sie benötigen weiterhin Ihr aktuelles Passwort, um bestehende Sicherungen zu entschlüsseln!',
|
||||
390: 'Neue Passwörter stimmen nicht überein',
|
||||
391: 'Neues Passwort muss mindestens 12 Zeichen lang sein',
|
||||
392: 'Neues Passwort darf höchstens 64 Zeichen haben',
|
||||
393: 'Aktuelles Passwort ist ungültig',
|
||||
394: 'Passwort wurde geändert',
|
||||
395: 'Aktuelles Passwort',
|
||||
396: 'Neues Passwort',
|
||||
397: 'Neues Passwort erneut eingeben',
|
||||
398: 'Eine Sitzung ist ein Gerät, das aktuell bei StartOS angemeldet ist. Beenden Sie Sitzungen, die Sie nicht kennen oder nicht mehr verwenden.',
|
||||
399: 'Aktuelle Sitzung',
|
||||
400: 'Weitere Sitzungen',
|
||||
401: 'Ausgewählte beenden',
|
||||
402: 'Sitzungen werden beendet',
|
||||
403: 'Keine Sitzungen',
|
||||
404: 'Passwort erforderlich',
|
||||
405: 'Verbunden',
|
||||
406: 'Vergessen',
|
||||
407: 'WiFi-Zugangsdaten',
|
||||
408: 'Veraltet',
|
||||
409: 'Die WLAN-Unterstützung wird in StartOS v0.4.1 entfernt. Wenn Sie keinen Zugriff auf Ethernet haben, können Sie einen WLAN-Extender verwenden, um sich mit dem lokalen Netzwerk zu verbinden und dann Ihren Server über Ethernet an den Extender anschließen. Bitte wenden Sie sich bei Fragen an den Start9-Support.',
|
||||
410: 'Bekannte Netzwerke',
|
||||
411: 'Weitere Netzwerke',
|
||||
412: 'WiFi ist deaktiviert',
|
||||
413: 'Keine drahtlose Schnittstelle erkannt',
|
||||
414: 'WiFi wird aktiviert',
|
||||
415: 'WiFi wird deaktiviert',
|
||||
416: 'Verbindung wird hergestellt. Dies kann einen Moment dauern',
|
||||
417: 'Erneut versuchen',
|
||||
418: 'Mehr anzeigen',
|
||||
419: 'Versionshinweise',
|
||||
420: 'Eintrag anzeigen',
|
||||
421: 'Dienste, die von folgendem abhängen:',
|
||||
422: 'werden nicht mehr ordnungsgemäß funktionieren und könnten abstürzen.',
|
||||
423: 'Anfrage fehlgeschlagen',
|
||||
424: 'Alle Dienste sind auf dem neuesten Stand!',
|
||||
425: 'Ausführen',
|
||||
426: 'Aktion kann nur ausgeführt werden, wenn der Dienst',
|
||||
427: 'Verboten',
|
||||
428: 'kann vorübergehend Probleme verursachen',
|
||||
429: 'hat unerfüllte Abhängigkeiten. Es wird nicht wie erwartet funktionieren.',
|
||||
430: 'Container wird neu gebaut',
|
||||
431: 'Deinstallation wird gestartet',
|
||||
432: 'wird alle Daten dauerhaft löschen.',
|
||||
433: 'Deinstallation läuft',
|
||||
434: 'Versuche, den Server zu erreichen',
|
||||
435: 'Verbindung wiederhergestellt',
|
||||
436: 'Unbekannter Status',
|
||||
437: 'Server verbunden',
|
||||
438: 'Kein Internet',
|
||||
439: 'Verbindung wird hergestellt',
|
||||
440: 'Fährt herunter',
|
||||
441: 'Versionen',
|
||||
442: 'Neue Benachrichtigungen',
|
||||
443: 'Anzeigen',
|
||||
444: 'PWA wird neu geladen',
|
||||
445: 'Abgeschlossen',
|
||||
446: 'Systemdaten',
|
||||
447: 'Nicht versucht',
|
||||
448: 'Fehlgeschlagen',
|
||||
449: 'Erfolgreich',
|
||||
450: 'Starten Sie Ihren Server neu, damit diese Updates wirksam werden. Es kann mehrere Minuten dauern, bis er wieder online ist.',
|
||||
451: 'StartOS-Download abgeschlossen',
|
||||
452: 'Unbekanntes Speicherlaufwerk erkannt',
|
||||
453: 'Um ein anderes Speicherlaufwerk zu verwenden, ersetzen Sie das aktuelle und klicken Sie unten auf SERVER NEUSTARTEN. Um das aktuelle Speicherlaufwerk zu verwenden, klicken Sie auf AKTUELLES LAUFWERK VERWENDEN und folgen Sie den Anweisungen. Während dieses Vorgangs werden keine Daten gelöscht.',
|
||||
454: 'Speicherlaufwerk nicht gefunden',
|
||||
455: 'Fügen Sie Ihr StartOS-Speicherlaufwerk ein und klicken Sie unten auf SERVER NEUSTARTEN.',
|
||||
456: 'Speicherlaufwerk beschädigt. Dies kann durch Datenkorruption oder physische Beschädigung verursacht worden sein.',
|
||||
457: 'Es kann möglich sein, dieses Laufwerk durch Neuformatierung und Wiederherstellung aus einer Sicherung erneut zu verwenden. Um den Wiederherstellungsmodus zu starten, klicken Sie unten auf WIEDERHERSTELLUNGSMODUS STARTEN und folgen Sie den Anweisungen. In diesem Schritt werden keine Daten gelöscht.',
|
||||
458: 'Dateisystemfehler',
|
||||
459: 'Die Reparatur der Festplatte könnte das Problem beheben. Bitte NICHT das Laufwerk oder den Server währenddessen trennen – dies kann die Situation verschlimmern.',
|
||||
460: 'Fehler bei der Laufwerksverwaltung',
|
||||
461: 'Bitte kontaktieren Sie den Support',
|
||||
462: 'Diagnosemodus',
|
||||
463: 'Startfehler',
|
||||
464: 'Logs anzeigen',
|
||||
465: 'Mögliche Lösungen',
|
||||
466: 'Aktuelles Laufwerk einrichten',
|
||||
467: 'Wiederherstellungsmodus starten',
|
||||
468: 'Server wird neu gestartet',
|
||||
469: 'Warten Sie, bis der Server neu gestartet ist, und aktualisieren Sie dann diese Seite.',
|
||||
470: 'Server neu starten',
|
||||
471: 'Laufwerk reparieren',
|
||||
472: 'Ihr Server wird eingerichtet',
|
||||
473: 'Fortschritt',
|
||||
474: 'Bei StartOS anmelden',
|
||||
475: 'Anmelden',
|
||||
476: 'Anmeldung läuft',
|
||||
477: 'Passwort darf nicht länger als 64 Zeichen sein',
|
||||
478: 'Ungültiges Passwort',
|
||||
479: 'Laden Sie Ihre Root-Zertifizierungsstelle herunter und vertrauen Sie ihr, um eine sichere (HTTPS-)Verbindung herzustellen. Dies müssen Sie auf jedem Gerät wiederholen, mit dem Sie eine Verbindung zum Server herstellen.',
|
||||
480: 'Speichern Sie diese Seite, damit Sie später darauf zugreifen können. Sie finden diese Adresse auch in der Datei, die am Ende der Erstinstallation heruntergeladen wurde.',
|
||||
481: 'Sie haben Ihrer Root-Zertifizierungsstelle erfolgreich vertraut und können sich jetzt sicher anmelden.',
|
||||
482: 'Ihr Server verwendet seine Root-Zertifizierungsstelle, um SSL/TLS-Zertifikate für sich selbst und installierte Dienste zu erstellen. Diese Zertifikate werden verwendet, um die Netzwerkverbindung mit Ihren Geräten zu verschlüsseln.',
|
||||
483: 'Befolgen Sie die Anweisungen für Ihr Betriebssystem. Wenn Sie Ihrer Root-Zertifizierungsstelle vertrauen, kann Ihr Gerät die Echtheit der verschlüsselten Kommunikation mit Ihrem Server überprüfen.',
|
||||
484: 'Laden Sie die Seite neu. Wenn das nicht funktioniert, beenden Sie Ihren Browser und öffnen Sie ihn erneut, um diese Seite erneut zu besuchen.',
|
||||
485: 'StartOS-Benutzeroberfläche',
|
||||
486: 'WiFi',
|
||||
} satisfies i18n
|
||||
490
web/projects/shared/src/i18n/dictionaries/polish.ts
Normal file
490
web/projects/shared/src/i18n/dictionaries/polish.ts
Normal file
@@ -0,0 +1,490 @@
|
||||
import type { i18n } from '../i18n.providers'
|
||||
|
||||
export default {
|
||||
1: 'Zmień',
|
||||
2: 'Aktualizuj',
|
||||
3: 'Zresetuj',
|
||||
4: 'System',
|
||||
5: 'Ogólne',
|
||||
6: 'E-mail',
|
||||
7: 'Utwórz kopię zapasową',
|
||||
8: 'Przywróć kopię zapasową',
|
||||
9: 'Przejdź do logowania',
|
||||
10: 'Testuj',
|
||||
11: 'Pomiń',
|
||||
12: 'Aktywne sesje',
|
||||
13: 'Zmień hasło',
|
||||
14: 'Ustawienia ogólne',
|
||||
15: 'Zarządzaj swoim ogólnym ustawieniem i preferencjami',
|
||||
16: 'Tytuł zakładki przeglądarki',
|
||||
17: 'Język',
|
||||
18: 'Naprawa dysku',
|
||||
19: 'Spróbuj automatycznej naprawy',
|
||||
20: 'Napraw',
|
||||
21: 'Główne Centrum Certyfikacji (Root CA)',
|
||||
22: 'Pobierz swoje Root CA',
|
||||
23: 'Pobierz',
|
||||
24: 'Zresetuj Tor',
|
||||
25: 'Zrestartuj usługę Tor na swoim serwerze',
|
||||
26: 'Aktualizacja oprogramowania',
|
||||
27: 'Uruchom ponownie, aby zastosować',
|
||||
28: 'Sprawdź dostępność aktualizacji',
|
||||
29: 'Ta wartość będzie wyświetlana jako tytuł zakładki w przeglądarce.',
|
||||
30: 'Nazwa urządzenia',
|
||||
31: 'StartOS',
|
||||
32: 'Zapisz',
|
||||
33: 'Zapisywanie',
|
||||
34: 'Ostrzeżenie',
|
||||
35: 'Potwierdź',
|
||||
36: 'Anuluj',
|
||||
37: 'Tę akcję należy wykonać tylko na polecenie specjalisty wsparcia Start9. Zalecamy wykonanie kopii zapasowej urządzenia przed kontynuacją. Jeśli podczas ponownego uruchamiania urządzenia dojdzie do przerwy w zasilaniu lub odłączenia dysku, system plików może ulec trwałemu uszkodzeniu. Proszę zachować ostrożność.',
|
||||
38: 'Usuń',
|
||||
39: 'Trwa resetowanie Tora',
|
||||
40: 'Resetowanie Tora',
|
||||
41: 'Sprawdzanie aktualizacji',
|
||||
42: 'Rozpoczynanie restartu',
|
||||
43: 'Używasz najnowszej wersji StartOS.',
|
||||
44: 'Aktualne!',
|
||||
45: 'Informacje o wydaniu',
|
||||
46: 'Rozpocznij aktualizację',
|
||||
47: 'Rozpoczynanie aktualizacji',
|
||||
48: 'Obecnie jesteś połączony przez Tor. Jeśli zresetujesz usługę Tor, utracisz połączenie aż do momentu ponownego nawiązania.',
|
||||
49: 'Zresetować Tora?',
|
||||
50: 'Opcjonalnie wyczyść stan, aby wymusić pozyskanie nowych węzłów strażniczych. Zaleca się najpierw spróbować bez czyszczenia stanu.',
|
||||
51: 'Wyczyść stan',
|
||||
52: 'Zapisywanie najlepszego wyniku',
|
||||
53: 'Wynik',
|
||||
54: 'Najlepszy wynik',
|
||||
55: 'Zapisz i wyjdź',
|
||||
56: 'Dodaj stronę do zakładek',
|
||||
57: 'Rozpoczynanie wyłączania',
|
||||
58: 'Dodaj',
|
||||
59: 'Ok',
|
||||
60: 'Czy na pewno chcesz usunąć ten wpis?',
|
||||
61: 'Tej wartości nie można zmienić po jej ustawieniu',
|
||||
62: 'Kontynuuj',
|
||||
63: 'Kliknij lub upuść plik tutaj',
|
||||
64: 'Upuść plik tutaj',
|
||||
65: 'Wyłączone',
|
||||
66: 'Wersja',
|
||||
67: 'Kopiuj',
|
||||
68: 'O tym serwerze',
|
||||
69: 'Ustawienia systemowe',
|
||||
70: 'Restartuj',
|
||||
71: 'Wyłącz',
|
||||
72: 'Wyloguj się',
|
||||
73: 'Podręcznik użytkownika',
|
||||
74: 'Skontaktuj się ze wsparciem',
|
||||
75: 'Wesprzyj Start9',
|
||||
76: 'Czy na pewno chcesz zrestartować serwer? Może to potrwać kilka minut, zanim znów będzie dostępny.',
|
||||
77: 'Czy na pewno chcesz wyłączyć serwer? Może to potrwać kilka minut, a serwer nie włączy się automatycznie. Aby go uruchomić ponownie, musisz fizycznie odłączyć i ponownie podłączyć zasilanie.',
|
||||
78: 'Usługi',
|
||||
79: 'Rynek',
|
||||
80: 'Ręczna instalacja',
|
||||
81: 'Aktualizacje',
|
||||
82: 'Metryki',
|
||||
83: 'Logi',
|
||||
84: 'Powiadomienia',
|
||||
85: 'Uruchom interfejs',
|
||||
86: 'Pokaż kod QR',
|
||||
87: 'Kopiuj URL',
|
||||
88: 'Akcje',
|
||||
89: 'niezalecane',
|
||||
90: 'Zaufano CA głównemu!',
|
||||
91: 'Dodaj adres clearnet, aby udostępnić ten interfejs w Internecie. Adresy clearnet są całkowicie publiczne i nieanonimowe.',
|
||||
92: 'Dowiedz się więcej',
|
||||
93: 'Upublicznij',
|
||||
94: 'Uprywatnij',
|
||||
95: 'Brak publicznych adresów',
|
||||
96: 'Dodaj domenę',
|
||||
97: 'Usuwanie',
|
||||
98: 'Upublicznianie',
|
||||
99: 'Prywatna konfiguracja',
|
||||
100: 'Niezapisane zmiany',
|
||||
101: 'Masz niezapisane zmiany. Czy na pewno chcesz opuścić stronę?',
|
||||
102: 'Opuść',
|
||||
103: 'Czy jesteś pewien?',
|
||||
104: 'Wybierz domenę',
|
||||
105: 'Lokalna',
|
||||
106: 'Adresy lokalne są dostępne tylko dla urządzeń podłączonych do tej samej sieci LAN co Twój serwer — bezpośrednio lub przez VPN.',
|
||||
107: 'Dowiedz się więcej',
|
||||
108: 'Publiczna',
|
||||
109: 'Prywatna',
|
||||
110: 'Dodaj adres onion, aby anonimowo udostępnić ten interfejs w sieci Tor. Adresy onion są dostępne tylko przez sieć Tor.',
|
||||
111: 'Brak adresów onion',
|
||||
112: 'Nowy adres Onion',
|
||||
113: 'Klucz prywatny (opcjonalnie)',
|
||||
114: 'Opcjonalnie podaj klucz prywatny ed25519 zakodowany w base64, aby wygenerować adres Tor V3 (.onion). Jeśli nie zostanie podany, zostanie wygenerowany losowy klucz.',
|
||||
115: 'Przetwarzanie 10 000 logów',
|
||||
116: 'Ładowanie starszych logów',
|
||||
117: 'Oczekiwanie na połączenie z siecią',
|
||||
118: 'Ponowne łączenie',
|
||||
119: 'Ładowanie logów',
|
||||
120: 'Przewiń na dół',
|
||||
121: 'Połączono ponownie',
|
||||
122: 'Rozłączono',
|
||||
123: 'Więcej',
|
||||
124: 'Wprowadzono następujące zmiany',
|
||||
125: 'dodano',
|
||||
126: 'usunięto',
|
||||
127: 'zmieniono z',
|
||||
128: 'na',
|
||||
129: 'wpis',
|
||||
130: 'lista',
|
||||
131: 'nowy wpis',
|
||||
132: 'nowa lista',
|
||||
133: 'Wyślij',
|
||||
134: 'Zamknij',
|
||||
135: 'Logi systemowe',
|
||||
136: 'Logi jądra',
|
||||
137: 'Logi Tor',
|
||||
138: 'Surowe, nieprzefiltrowane logi systemu operacyjnego',
|
||||
139: 'Diagnostyka sterowników i innych procesów jądra',
|
||||
140: 'Logi diagnostyczne demona Tor na StartOS',
|
||||
141: 'Obniż wersję',
|
||||
142: 'Zainstaluj ponownie',
|
||||
143: 'Zobacz zainstalowane',
|
||||
144: 'Przełącz',
|
||||
145: 'Zainstaluj',
|
||||
146: 'Rozpoczynanie instalacji',
|
||||
147: 'Zmień rejestr',
|
||||
148: 'Usługi z tego rejestru są pakietowane i utrzymywane przez zespół Start9. Jeśli napotkasz problem lub masz pytania dotyczące usługi z tego rejestru, nasz zespół wsparcia z przyjemnością Ci pomoże.',
|
||||
149: 'Usługi z tego rejestru są tworzone i utrzymywane przez członków społeczności Start9. Instalujesz je na własne ryzyko. W przypadku problemów lub pytań skontaktuj się z twórcą pakietu.',
|
||||
150: 'Usługi z tego rejestru są w fazie beta i mogą zawierać błędy. Instalujesz je na własne ryzyko.',
|
||||
151: 'Usługi z tego rejestru są w fazie testów alfa. Mogą zawierać błędy i potencjalnie uszkodzić system. Instalujesz je na własne ryzyko.',
|
||||
152: 'To jest rejestr niestandardowy. Start9 nie może zweryfikować integralności ani działania usług z tego rejestru i mogą one uszkodzić Twój system. Instalujesz je na własne ryzyko.',
|
||||
153: 'Rejestry domyślne',
|
||||
154: 'Rejestry niestandardowe',
|
||||
155: 'Dodaj niestandardowy rejestr',
|
||||
156: 'Zapisz na później',
|
||||
157: 'Zapisz i połącz',
|
||||
158: 'Usuwanie',
|
||||
159: 'Zmiana rejestru',
|
||||
160: 'Ładowanie',
|
||||
161: 'Rejestr już dodany',
|
||||
162: 'Weryfikowanie rejestru',
|
||||
163: 'Czy na pewno chcesz usunąć ten rejestr?',
|
||||
164: 'Dodaj niestandardowy rejestr',
|
||||
165: 'Pełny URL niestandardowego rejestru',
|
||||
166: 'Musi być poprawnym adresem URL',
|
||||
167: 'zainstalowano z',
|
||||
168: 'zainstalowano ręcznie',
|
||||
169: 'Ta usługa została pierwotnie',
|
||||
170: 'ale obecnie jesteś połączony z',
|
||||
171: 'Aby zainstalować z',
|
||||
172: 'mimo to kliknij „Kontynuuj”.',
|
||||
173: 'W wyniku tej aktualizacji poniższe usługi mogą przestać działać poprawnie lub się zawiesić',
|
||||
174: 'Alert',
|
||||
175: 'Procent użycia',
|
||||
176: 'Przestrzeń użytkownika',
|
||||
177: 'Przestrzeń jądra',
|
||||
178: 'Bezczynność',
|
||||
179: 'Oczekiwanie na I/O',
|
||||
180: 'ACME',
|
||||
181: 'Łącznie',
|
||||
182: 'Użyte',
|
||||
183: 'Dostępne',
|
||||
184: 'zram użyte',
|
||||
185: 'zram łącznie',
|
||||
186: 'zram dostępne',
|
||||
187: 'Czas systemowy',
|
||||
188: 'Czas działania',
|
||||
189: 'Temperatura',
|
||||
190: 'Pamięć',
|
||||
191: 'Pamięć masowa',
|
||||
192: 'Pojemność',
|
||||
193: 'Błąd synchronizacji zegara',
|
||||
194: 'dokumentacja',
|
||||
195: 'Dni',
|
||||
196: 'Godziny',
|
||||
197: 'Minuty',
|
||||
198: 'Sekundy',
|
||||
199: 'Zobacz całość',
|
||||
200: 'Zobacz raport',
|
||||
201: 'Operacja zbiorcza',
|
||||
202: 'Oznacz jako przeczytane',
|
||||
203: 'Oznacz jako nieprzeczytane',
|
||||
204: 'Data',
|
||||
205: 'Tytuł',
|
||||
206: 'Usługa',
|
||||
207: 'Wiadomość',
|
||||
208: 'Brak powiadomień',
|
||||
209: 'Wymagane',
|
||||
210: 'Opcjonalne',
|
||||
211: 'Nie podano powodu',
|
||||
212: 'Zadania',
|
||||
213: 'Typ',
|
||||
214: 'Opis',
|
||||
215: 'Wszystkie zadania zakończone',
|
||||
216: 'Uruchom',
|
||||
217: 'Zatrzymaj',
|
||||
218: 'Zależności',
|
||||
219: 'Spełnione',
|
||||
220: 'Brak zależności',
|
||||
221: 'Nie zainstalowano',
|
||||
222: 'Nieprawidłowa wersja',
|
||||
223: 'Nie działa',
|
||||
224: 'Wymagana akcja',
|
||||
225: 'Wymagana kontrola stanu nie powiodła się',
|
||||
226: 'Zależność ma problem z zależnością',
|
||||
227: 'Nieznany błąd',
|
||||
228: 'Błąd',
|
||||
229: '„Odbuduj kontener” to nieszkodliwa akcja, która trwa tylko kilka sekund. Może rozwiązać ten problem.',
|
||||
230: '„Odinstaluj usługę” to niebezpieczna akcja, która usunie usługę ze StartOS i usunie wszystkie jej dane.',
|
||||
231: 'Odbuduj kontener',
|
||||
232: 'Odinstaluj usługę',
|
||||
233: 'Zobacz pełną wiadomość',
|
||||
234: 'Błąd usługi',
|
||||
235: 'Oczekiwanie na wynik',
|
||||
236: 'Uruchamianie',
|
||||
237: 'Sukces',
|
||||
238: 'Kontrole stanu',
|
||||
239: 'Brak kontroli stanu',
|
||||
240: 'Nazwa',
|
||||
241: 'Status',
|
||||
242: 'Otwórz',
|
||||
243: 'Interfejsy',
|
||||
244: 'Hosting',
|
||||
245: 'Instalowanie',
|
||||
246: 'Zobacz poniżej',
|
||||
247: 'Sterowanie',
|
||||
248: 'Brak zainstalowanych usług',
|
||||
249: 'Działa',
|
||||
250: 'Zatrzymano',
|
||||
251: 'Wymagane zadanie',
|
||||
252: 'Aktualizowanie',
|
||||
253: 'Zatrzymywanie',
|
||||
254: 'Zaufaj swojemu CA głównemu',
|
||||
255: 'Tworzenie kopii zapasowej',
|
||||
256: 'Ponowne uruchamianie',
|
||||
257: 'Wstecz',
|
||||
258: 'Przywracanie',
|
||||
259: 'Nieznane',
|
||||
260: 'Pokaż/Ukryj',
|
||||
261: 'Pokaż',
|
||||
262: 'Zeskanuj ten kod QR',
|
||||
263: 'Przywróć domyślne ustawienia',
|
||||
264: 'W wyniku tej zmiany następujące usługi mogą przestać działać poprawnie lub się zawiesić',
|
||||
265: 'Błąd uruchamiania usługi',
|
||||
266: 'Problem',
|
||||
267: 'Niepowodzenie',
|
||||
268: 'Zdrowa',
|
||||
269: 'finalizowanie',
|
||||
270: 'nieznany %',
|
||||
271: 'Nie podano',
|
||||
272: 'Linki',
|
||||
273: 'Hash Git',
|
||||
274: 'Licencja',
|
||||
275: 'Zainstalowano z',
|
||||
276: 'Repozytorium usługi',
|
||||
277: 'Repozytorium pakietu',
|
||||
278: 'Strona marketingowa',
|
||||
279: 'Strona wsparcia',
|
||||
280: 'Link do darowizny',
|
||||
281: 'Standardowe akcje',
|
||||
282: 'Odbuduj usługę',
|
||||
283: 'Odbudowuje kontener usługi. Konieczne tylko w przypadku błędu w StartOS',
|
||||
284: 'Odinstaluj',
|
||||
285: 'Odinstalowuje tę usługę z StartOS i trwale usuwa wszystkie dane.',
|
||||
286: 'Panel',
|
||||
287: 'panel',
|
||||
288: 'akcje',
|
||||
289: 'instrukcje',
|
||||
290: 'logi',
|
||||
291: 'informacje',
|
||||
292: 'Rozpoczynanie przesyłania',
|
||||
293: 'Spróbuj ponownie',
|
||||
294: 'Prześlij plik pakietu .s9pk',
|
||||
295: 'Uwaga: przesyłanie pakietu przez Tor będzie powolne. Przełącz na sieć lokalną dla lepszej wydajności.',
|
||||
296: 'Prześlij',
|
||||
297: 'Wykryto pakiet s9pk w wersji 1. Ten format jest przestarzały. Możesz zainstalować V1 s9pk ręcznie przez start-cli, jeśli to konieczne.',
|
||||
298: 'Nieprawidłowy plik pakietu',
|
||||
299: 'Dodaj dostawców ACME, aby wygenerować certyfikaty SSL (https) dla dostępu przez clearnet.',
|
||||
300: 'Zobacz instrukcje',
|
||||
301: 'Zapisani dostawcy',
|
||||
302: 'Dodaj dostawcę',
|
||||
303: 'Kontakt',
|
||||
304: 'Edytuj',
|
||||
305: 'Dodaj dostawcę ACME',
|
||||
306: 'Edytuj dostawcę ACME',
|
||||
307: 'Adresy e-mail kontaktowe',
|
||||
308: 'Wymagane do uzyskania certyfikatu od Urzędu Certyfikacji',
|
||||
309: 'Przełącz wszystkie',
|
||||
310: 'Gotowe',
|
||||
311: 'Wymagana hasło główne',
|
||||
312: 'Wprowadź swoje hasło główne, aby zaszyfrować tę kopię zapasową.',
|
||||
313: 'Hasło główne',
|
||||
314: 'Wprowadź hasło główne',
|
||||
315: 'Wymagane oryginalne hasło',
|
||||
316: 'Ta kopia zapasowa została utworzona z innym hasłem. Wprowadź oryginalne hasło użyte do szyfrowania tej kopii.',
|
||||
317: 'Oryginalne hasło',
|
||||
318: 'Wprowadź oryginalne hasło',
|
||||
319: 'Rozpoczynanie tworzenia kopii zapasowej',
|
||||
320: 'Utwórz kopię zapasową StartOS i danych usług, podłączając się do urządzenia w sieci lokalnej lub do fizycznego dysku podłączonego do serwera.',
|
||||
321: 'Przywróć StartOS i dane usług z urządzenia w sieci lokalnej lub z fizycznego dysku podłączonego do serwera zawierającego istniejącą kopię zapasową.',
|
||||
322: 'Ostatnia kopia zapasowa',
|
||||
323: 'Folder na innym komputerze podłączonym do tej samej sieci co Twój serwer Start9.',
|
||||
324: 'Fizyczny dysk podłączony bezpośrednio do Twojego serwera Start9.',
|
||||
325: 'Wybierz usługi do kopii zapasowej',
|
||||
326: 'Wybierz kopię zapasową serwera',
|
||||
327: 'Foldery sieciowe',
|
||||
328: 'Otwórz nowy',
|
||||
329: 'Nazwa hosta',
|
||||
330: 'Ścieżka',
|
||||
331: 'URL',
|
||||
332: 'Interfejs sieciowy',
|
||||
333: 'Protokół',
|
||||
334: 'Model',
|
||||
335: 'Agent użytkownika',
|
||||
336: 'Platforma',
|
||||
337: 'Ostatnia aktywność',
|
||||
338: 'Utworzono',
|
||||
339: 'Algorytm',
|
||||
340: 'Odcisk palca',
|
||||
341: 'Suma kontrolna pakietu',
|
||||
342: 'Opublikowano',
|
||||
343: 'Nowy folder sieciowy',
|
||||
344: 'Zaktualizuj folder sieciowy',
|
||||
345: 'Testowanie połączenia z folderem współdzielonym',
|
||||
346: 'Upewnij się, że (1) komputer docelowy jest podłączony do tej samej sieci LAN co Twój serwer Start9, (2) folder docelowy jest udostępniony i (3) nazwa hosta, ścieżka i dane logowania są poprawne.',
|
||||
347: 'Nie udało się połączyć',
|
||||
348: 'Folder sieciowy nie zawiera prawidłowej kopii zapasowej',
|
||||
349: 'Połącz',
|
||||
350: 'Nazwa użytkownika',
|
||||
351: 'Hasło',
|
||||
352: 'Nazwa hosta urządzenia docelowego w lokalnej sieci (LAN).',
|
||||
353: 'W systemie Windows jest to pełna ścieżka do folderu współdzielonego (np. /Desktop/moj-folder). W systemach Linux i Mac jest to dosłowna nazwa folderu (np. moj-folder).',
|
||||
354: 'W systemie Linux to nazwa użytkownika samba, którą utworzyłeś przy udostępnianiu folderu. W systemach Mac i Windows to nazwa użytkownika osoby udostępniającej folder.',
|
||||
355: 'W systemie Linux to hasło samba, które ustawiłeś przy udostępnianiu folderu. W Mac i Windows to hasło użytkownika udostępniającego folder.',
|
||||
356: 'Dyski fizyczne',
|
||||
357: 'Nie wykryto dysków',
|
||||
358: 'Odśwież',
|
||||
359: 'Partycja dysku nie zawiera prawidłowej kopii zapasowej',
|
||||
360: 'Postęp tworzenia kopii zapasowej',
|
||||
361: 'Zakończono',
|
||||
362: 'Tworzenie kopii zapasowej',
|
||||
363: 'Oczekiwanie',
|
||||
364: 'Utworzono kopię zapasową',
|
||||
365: 'Wybrano przywracanie',
|
||||
366: 'Inicjalizacja',
|
||||
367: 'Niedostępne. Kopia zapasowa została wykonana na nowszej wersji StartOS.',
|
||||
368: 'Niedostępne. Usługa jest już zainstalowana.',
|
||||
369: 'Gotowe do przywrócenia',
|
||||
370: 'Lokalna nazwa hosta',
|
||||
371: 'Utworzono',
|
||||
372: 'Wymagane hasło',
|
||||
373: 'Wprowadź hasło główne użyte do zaszyfrowania tej kopii zapasowej. Na następnym ekranie wybierzesz usługi do przywrócenia.',
|
||||
374: 'Odszyfrowywanie dysku',
|
||||
375: 'Wybierz usługi do przywrócenia',
|
||||
376: 'Dostępne do kopii zapasowej',
|
||||
377: 'Wykryto kopie zapasowe StartOS',
|
||||
378: 'Nie wykryto kopii zapasowych StartOS',
|
||||
379: 'Wersja StartOS',
|
||||
380: 'Podłączenie zewnętrznego serwera SMTP pozwala StartOS i zainstalowanym usługom wysyłać wiadomości e-mail.',
|
||||
381: 'Dane logowania SMTP',
|
||||
382: 'Wyślij e-mail testowy',
|
||||
383: 'Wyślij',
|
||||
384: 'Wysyłanie e-maila',
|
||||
385: 'Wiadomość testowa została wysłana do',
|
||||
386: 'Sprawdź folder spam i oznacz wiadomość jako „nie spam”.',
|
||||
387: 'Webowy interfejs użytkownika Twojego serwera StartOS, dostępny z każdej przeglądarki.',
|
||||
388: 'Zmień swoje hasło główne StartOS.',
|
||||
389: 'Wciąż będziesz potrzebować aktualnego hasła, aby odszyfrować istniejące kopie zapasowe!',
|
||||
390: 'Nowe hasła nie są zgodne',
|
||||
391: 'Nowe hasło musi mieć co najmniej 12 znaków',
|
||||
392: 'Nowe hasło musi mieć mniej niż 65 znaków',
|
||||
393: 'Bieżące hasło jest nieprawidłowe',
|
||||
394: 'Hasło zostało zmienione',
|
||||
395: 'Bieżące hasło',
|
||||
396: 'Nowe hasło',
|
||||
397: 'Wpisz nowe hasło ponownie',
|
||||
398: 'Sesja to urządzenie aktualnie zalogowane do StartOS. Dla bezpieczeństwa zakończ sesje, których nie rozpoznajesz lub już nie używasz.',
|
||||
399: 'Obecna sesja',
|
||||
400: 'Inne sesje',
|
||||
401: 'Zakończ wybrane',
|
||||
402: 'Zamykanie sesji',
|
||||
403: 'Brak sesji',
|
||||
404: 'Wymagane hasło',
|
||||
405: 'Połączono',
|
||||
406: 'Zapomnij',
|
||||
407: 'Dane logowania WiFi',
|
||||
408: 'Przestarzałe',
|
||||
409: 'Obsługa WiFi zostanie usunięta w StartOS v0.4.1. Jeśli nie masz dostępu do sieci Ethernet, możesz użyć wzmacniacza WiFi, aby połączyć się z siecią lokalną, a następnie podłączyć serwer do wzmacniacza przez Ethernet. Skontaktuj się ze wsparciem Start9 w razie pytań.',
|
||||
410: 'Znane sieci',
|
||||
411: 'Inne sieci',
|
||||
412: 'WiFi jest wyłączone',
|
||||
413: 'Nie wykryto interfejsu bezprzewodowego',
|
||||
414: 'Włączanie WiFi',
|
||||
415: 'Wyłączanie WiFi',
|
||||
416: 'Łączenie... To może chwilę potrwać',
|
||||
417: 'Spróbuj ponownie',
|
||||
418: 'Pokaż więcej',
|
||||
419: 'Informacje o wydaniu',
|
||||
420: 'Zobacz ofertę',
|
||||
421: 'Usługi zależne od',
|
||||
422: 'mogą przestać działać poprawnie i ulec awarii.',
|
||||
423: 'Żądanie nie powiodło się',
|
||||
424: 'Wszystkie usługi są aktualne!',
|
||||
425: 'Uruchom',
|
||||
426: 'Działanie można wykonać tylko, gdy usługa jest',
|
||||
427: 'Zabronione',
|
||||
428: 'może tymczasowo napotkać problemy',
|
||||
429: 'ma niespełnione zależności. Nie będzie działać zgodnie z oczekiwaniami.',
|
||||
430: 'Odbudowywanie kontenera',
|
||||
431: 'Rozpoczynanie odinstalowania',
|
||||
432: 'usunie wszystkie dane na stałe.',
|
||||
433: 'Odinstalowywanie',
|
||||
434: 'Próba połączenia z serwerem',
|
||||
435: 'Połączenie przywrócone',
|
||||
436: 'Nieznany stan',
|
||||
437: 'Połączono z serwerem',
|
||||
438: 'Brak Internetu',
|
||||
439: 'Łączenie',
|
||||
440: 'Wyłączanie',
|
||||
441: 'Wersje',
|
||||
442: 'Nowe powiadomienia',
|
||||
443: 'Zobacz',
|
||||
444: 'Przeładowywanie PWA',
|
||||
445: 'Zakończono',
|
||||
446: 'Dane systemowe',
|
||||
447: 'Nie podjęto próby',
|
||||
448: 'Niepowodzenie',
|
||||
449: 'Powodzenie',
|
||||
450: 'Zrestartuj serwer, aby zastosować te aktualizacje. Może to potrwać kilka minut.',
|
||||
451: 'Pobieranie StartOS zakończone',
|
||||
452: 'Wykryto nieznany dysk',
|
||||
453: 'Aby użyć innego dysku, zamień obecny i kliknij RESTARTUJ SERWER poniżej. Aby użyć obecnego dysku, kliknij UŻYJ OBECNEGO DYSKU i postępuj zgodnie z instrukcjami. Dane nie zostaną usunięte w tym procesie.',
|
||||
454: 'Nie znaleziono dysku',
|
||||
455: 'Włóż dysk StartOS i kliknij RESTARTUJ SERWER poniżej.',
|
||||
456: 'Dysk uszkodzony. Może to być wynikiem uszkodzenia danych lub fizycznego uszkodzenia.',
|
||||
457: 'Możliwe, że dysk da się ponownie wykorzystać po jego sformatowaniu i odzyskaniu danych z kopii zapasowej. Aby wejść w tryb odzyskiwania, kliknij WEJDŹ W TRYB ODZYSKIWANIA i postępuj zgodnie z instrukcjami. Dane nie zostaną usunięte na tym etapie.',
|
||||
458: 'Błąd systemu plików',
|
||||
459: 'Naprawa dysku może pomóc rozwiązać problem. NIE odłączaj dysku ani serwera w tym czasie — może to pogorszyć sytuację.',
|
||||
460: 'Błąd zarządzania dyskiem',
|
||||
461: 'Skontaktuj się z pomocą techniczną',
|
||||
462: 'Tryb diagnostyczny',
|
||||
463: 'błąd uruchomienia',
|
||||
464: 'Zobacz logi',
|
||||
465: 'Możliwe rozwiązania',
|
||||
466: 'Skonfiguruj obecny dysk',
|
||||
467: 'Wejdź w tryb odzyskiwania',
|
||||
468: 'Serwer się restartuje',
|
||||
469: 'Poczekaj na ponowne uruchomienie serwera, a następnie odśwież tę stronę.',
|
||||
470: 'Restartuj serwer',
|
||||
471: 'Napraw dysk',
|
||||
472: 'Konfigurowanie serwera',
|
||||
473: 'Postęp',
|
||||
474: 'Zaloguj się do StartOS',
|
||||
475: 'Zaloguj się',
|
||||
476: 'Logowanie...',
|
||||
477: 'Hasło musi mieć mniej niż 65 znaków',
|
||||
478: 'Nieprawidłowe hasło',
|
||||
479: 'Pobierz i zaufaj swojej głównej CA, aby ustanowić bezpieczne połączenie (HTTPS). Musisz powtórzyć to na każdym urządzeniu, z którego łączysz się z serwerem.',
|
||||
480: 'Zapisz tę stronę, aby móc do niej wrócić. Ten adres znajdziesz też w pliku pobranym na końcu konfiguracji.',
|
||||
481: 'Zaufano Twojej głównej CA — możesz teraz bezpiecznie się zalogować.',
|
||||
482: 'Twój serwer używa głównej CA do generowania certyfikatów SSL/TLS dla siebie i zainstalowanych usług. Te certyfikaty są wykorzystywane do szyfrowania ruchu sieciowego z urządzeniami.',
|
||||
483: 'Postępuj zgodnie z instrukcjami dla swojego systemu. Po zaufaniu głównej CA, Twoje urządzenie będzie mogło weryfikować autentyczność szyfrowanej komunikacji z serwerem.',
|
||||
484: 'Odśwież stronę. Jeśli to nie pomoże, zamknij i ponownie otwórz przeglądarkę, a następnie wróć do tej strony.',
|
||||
485: 'Interfejs StartOS',
|
||||
486: 'WiFi',
|
||||
} satisfies i18n
|
||||
490
web/projects/shared/src/i18n/dictionaries/spanish.ts
Normal file
490
web/projects/shared/src/i18n/dictionaries/spanish.ts
Normal file
@@ -0,0 +1,490 @@
|
||||
import type { i18n } from '../i18n.providers'
|
||||
|
||||
export default {
|
||||
1: 'Cambiar',
|
||||
2: 'Actualizar',
|
||||
3: 'Restablecer',
|
||||
4: 'Sistema',
|
||||
5: 'General',
|
||||
6: 'Correo electrónico',
|
||||
7: 'Crear copia de seguridad',
|
||||
8: 'Restaurar copia de seguridad',
|
||||
9: 'Ir a inicio de sesión',
|
||||
10: 'Probar',
|
||||
11: 'Omitir',
|
||||
12: 'Sesiones activas',
|
||||
13: 'Cambiar contraseña',
|
||||
14: 'Configuración general',
|
||||
15: 'Administra tu configuración y preferencias generales',
|
||||
16: 'Título de la pestaña del navegador',
|
||||
17: 'Idioma',
|
||||
18: 'Reparación de disco',
|
||||
19: 'Intentar reparación automática',
|
||||
20: 'Reparar',
|
||||
21: 'Autoridad certificadora raíz',
|
||||
22: 'Descargar tu CA raíz',
|
||||
23: 'Descargar',
|
||||
24: 'Restablecer Tor',
|
||||
25: 'Reiniciar el servicio Tor en tu servidor',
|
||||
26: 'Actualización de software',
|
||||
27: 'Reiniciar para aplicar',
|
||||
28: 'Buscar actualizaciones',
|
||||
29: 'Este valor se mostrará como el título de la pestaña de tu navegador.',
|
||||
30: 'Nombre del dispositivo',
|
||||
31: 'StartOS',
|
||||
32: 'Guardar',
|
||||
33: 'Guardando',
|
||||
34: 'Advertencia',
|
||||
35: 'Confirmar',
|
||||
36: 'Cancelar',
|
||||
37: 'Esta acción solo debe realizarse si es indicada por un especialista de soporte de Start9. Recomendamos hacer una copia de seguridad del dispositivo antes de ejecutar esta acción. Si ocurre algo durante el reinicio, como un corte de energía o desconectar el disco, el sistema de archivos quedará en un estado irrecuperable. Procede con precaución.',
|
||||
38: 'Eliminar',
|
||||
39: 'Restablecimiento de Tor en curso',
|
||||
40: 'Restableciendo Tor',
|
||||
41: 'Buscando actualizaciones',
|
||||
42: 'Iniciando reinicio',
|
||||
43: 'Estás usando la última versión de StartOS.',
|
||||
44: '¡Actualizado!',
|
||||
45: 'Notas de la versión',
|
||||
46: 'Iniciar actualización',
|
||||
47: 'Iniciando actualización',
|
||||
48: 'Actualmente estás conectado a través de Tor. Si restableces el servicio Tor, perderás la conexión hasta que vuelva a estar en línea.',
|
||||
49: '¿Restablecer Tor?',
|
||||
50: 'Opcionalmente borra el estado para forzar la adquisición de nuevos nodos de entrada. Se recomienda intentar sin borrar el estado primero.',
|
||||
51: 'Borrar estado',
|
||||
52: 'Guardando puntuación máxima',
|
||||
53: 'Puntuación',
|
||||
54: 'Puntuación máxima',
|
||||
55: 'Guardar y salir',
|
||||
56: 'Marcar esta página',
|
||||
57: 'Iniciando apagado',
|
||||
58: 'Agregar',
|
||||
59: 'Ok',
|
||||
60: '¿Estás seguro de que deseas eliminar esta entrada?',
|
||||
61: 'Este valor no se puede cambiar una vez establecido',
|
||||
62: 'Continuar',
|
||||
63: 'Haz clic o suelta el archivo aquí',
|
||||
64: 'Suelta el archivo aquí',
|
||||
65: 'Deshabilitado',
|
||||
66: 'Versión',
|
||||
67: 'Copiar',
|
||||
68: 'Acerca de este servidor',
|
||||
69: 'Configuración del sistema',
|
||||
70: 'Reiniciar',
|
||||
71: 'Apagar',
|
||||
72: 'Cerrar sesión',
|
||||
73: 'Manual del usuario',
|
||||
74: 'Contactar soporte',
|
||||
75: 'Donar a Start9',
|
||||
76: '¿Estás seguro de que deseas reiniciar tu servidor? Puede tardar varios minutos en volver a estar en línea.',
|
||||
77: '¿Estás seguro de que deseas apagar tu servidor? Esto puede tardar varios minutos, y tu servidor no volverá a encenderse automáticamente. Para encenderlo nuevamente, deberás desconectarlo y volverlo a conectar físicamente.',
|
||||
78: 'Servicios',
|
||||
79: 'Mercado',
|
||||
80: 'Instalación manual',
|
||||
81: 'Actualizaciones',
|
||||
82: 'Métricas',
|
||||
83: 'Registros',
|
||||
84: 'Notificaciones',
|
||||
85: 'Abrir interfaz',
|
||||
86: 'Mostrar QR',
|
||||
87: 'Copiar URL',
|
||||
88: 'Acciones',
|
||||
89: 'no recomendado',
|
||||
90: '¡CA raíz confiable!',
|
||||
91: 'Agrega una dirección clearnet para exponer esta interfaz en Internet. Las direcciones clearnet son totalmente públicas y no anónimas.',
|
||||
92: 'Saber más',
|
||||
93: 'Hacer público',
|
||||
94: 'Hacer privado',
|
||||
95: 'Sin direcciones públicas',
|
||||
96: 'Agregar dominio',
|
||||
97: 'Eliminando',
|
||||
98: 'Haciendo público',
|
||||
99: 'Haciendo privado',
|
||||
100: 'Cambios no guardados',
|
||||
101: 'Tienes cambios no guardados. ¿Estás seguro de que deseas salir?',
|
||||
102: 'Salir',
|
||||
103: '¿Estás seguro?',
|
||||
104: 'Seleccionar dominio',
|
||||
105: 'Local',
|
||||
106: 'Las direcciones locales solo pueden ser accedidas por dispositivos conectados a la misma red local que tu servidor, ya sea directamente o mediante una VPN.',
|
||||
107: 'Más información',
|
||||
108: 'Público',
|
||||
109: 'Privado',
|
||||
110: 'Agrega una dirección onion para exponer esta interfaz de forma anónima en la darknet. Las direcciones onion solo se pueden acceder a través de la red Tor.',
|
||||
111: 'Sin direcciones onion',
|
||||
112: 'Nueva dirección Onion',
|
||||
113: 'Clave privada (opcional)',
|
||||
114: 'Opcionalmente proporciona una clave privada ed25519 codificada en base64 para generar la dirección Tor V3 (.onion). Si no se proporciona, se generará una clave aleatoria.',
|
||||
115: 'Procesando 10,000 registros',
|
||||
116: 'Cargando registros anteriores',
|
||||
117: 'Esperando conectividad de red',
|
||||
118: 'Reconectando',
|
||||
119: 'Cargando registros',
|
||||
120: 'Desplazar hacia abajo',
|
||||
121: 'Reconectado',
|
||||
122: 'Desconectado',
|
||||
123: 'Más',
|
||||
124: 'Se realizaron las siguientes modificaciones',
|
||||
125: 'agregado',
|
||||
126: 'eliminado',
|
||||
127: 'cambiado de',
|
||||
128: 'a',
|
||||
129: 'entrada',
|
||||
130: 'lista',
|
||||
131: 'nueva entrada',
|
||||
132: 'nueva lista',
|
||||
133: 'Enviar',
|
||||
134: 'Cerrar',
|
||||
135: 'Registros del SO',
|
||||
136: 'Registros del kernel',
|
||||
137: 'Registros de Tor',
|
||||
138: 'Registros sin filtrar del sistema operativo',
|
||||
139: 'Diagnóstico de controladores y otros procesos del kernel',
|
||||
140: 'Registros de diagnóstico del servicio Tor en StartOS',
|
||||
141: 'Retroceder versión',
|
||||
142: 'Reinstalar',
|
||||
143: 'Ver instalados',
|
||||
144: 'Cambiar',
|
||||
145: 'Instalar',
|
||||
146: 'Iniciando instalación',
|
||||
147: 'Cambiar registro',
|
||||
148: 'Los servicios de este registro están empaquetados y mantenidos por el equipo de Start9. Si experimentas un problema o tienes preguntas relacionadas con un servicio de este registro, nuestro equipo de soporte estará encantado de ayudarte.',
|
||||
149: 'Los servicios de este registro están empaquetados y mantenidos por miembros de la comunidad Start9. Instálalos bajo tu propio riesgo. Si experimentas un problema o tienes una pregunta sobre un servicio en este mercado, comunícate con el desarrollador del paquete.',
|
||||
150: 'Los servicios de este registro están en fase de prueba beta y pueden contener errores. Instálalos bajo tu propio riesgo.',
|
||||
151: 'Los servicios de este registro están en fase de prueba alfa. Se espera que contengan errores y podrían dañar tu sistema. Instálalos bajo tu propio riesgo.',
|
||||
152: 'Este es un registro personalizado. Start9 no puede verificar la integridad ni el funcionamiento de los servicios de este registro, y podrían dañar tu sistema. Instálalos bajo tu propio riesgo.',
|
||||
153: 'Registros predeterminados',
|
||||
154: 'Registros personalizados',
|
||||
155: 'Agregar registro personalizado',
|
||||
156: 'Guardar para más tarde',
|
||||
157: 'Guardar y conectar',
|
||||
158: 'Eliminando',
|
||||
159: 'Cambiando registro',
|
||||
160: 'Cargando',
|
||||
161: 'Registro ya agregado',
|
||||
162: 'Validando registro',
|
||||
163: '¿Estás seguro de que deseas eliminar este registro?',
|
||||
164: 'Agregar registro personalizado',
|
||||
165: 'Una URL completamente calificada del registro personalizado',
|
||||
166: 'Debe ser una URL válida',
|
||||
167: 'instalado desde',
|
||||
168: 'instalado manualmente',
|
||||
169: 'Este servicio fue originalmente',
|
||||
170: 'pero actualmente estás conectado a',
|
||||
171: 'Para instalar desde',
|
||||
172: 'de todos modos, haz clic en "Continuar".',
|
||||
173: 'Como resultado de esta actualización, los siguientes servicios dejarán de funcionar correctamente y pueden fallar',
|
||||
174: 'Alerta',
|
||||
175: 'Porcentaje utilizado',
|
||||
176: 'Espacio de usuario',
|
||||
177: 'Espacio del kernel',
|
||||
178: 'Inactivo',
|
||||
179: 'Espera de E/S',
|
||||
180: 'ACME',
|
||||
181: 'Total',
|
||||
182: 'Usado',
|
||||
183: 'Disponible',
|
||||
184: 'zram usado',
|
||||
185: 'zram total',
|
||||
186: 'zram disponible',
|
||||
187: 'Hora del sistema',
|
||||
188: 'Tiempo en funcionamiento',
|
||||
189: 'Temperatura',
|
||||
190: 'Memoria',
|
||||
191: 'Almacenamiento',
|
||||
192: 'Capacidad',
|
||||
193: 'Fallo en la sincronización del reloj',
|
||||
194: 'la documentación',
|
||||
195: 'Días',
|
||||
196: 'Horas',
|
||||
197: 'Minutos',
|
||||
198: 'Segundos',
|
||||
199: 'Ver completo',
|
||||
200: 'Ver informe',
|
||||
201: 'Acción en lote',
|
||||
202: 'Marcar como visto',
|
||||
203: 'Marcar como no visto',
|
||||
204: 'Fecha',
|
||||
205: 'Título',
|
||||
206: 'Servicio',
|
||||
207: 'Mensaje',
|
||||
208: 'Sin notificaciones',
|
||||
209: 'Requerido',
|
||||
210: 'Opcional',
|
||||
211: 'No se proporcionó motivo',
|
||||
212: 'Tareas',
|
||||
213: 'Tipo',
|
||||
214: 'Descripción',
|
||||
215: 'Todas las tareas completas',
|
||||
216: 'Iniciar',
|
||||
217: 'Detener',
|
||||
218: 'Dependencias',
|
||||
219: 'Satisfechas',
|
||||
220: 'Sin dependencias',
|
||||
221: 'No instalado',
|
||||
222: 'Versión incorrecta',
|
||||
223: 'No en ejecución',
|
||||
224: 'Acción requerida',
|
||||
225: 'La verificación de salud requerida no está pasando',
|
||||
226: 'Una dependencia tiene un problema de dependencia',
|
||||
227: 'Error desconocido',
|
||||
228: 'Error',
|
||||
229: '"Reconstruir contenedor" es una acción inofensiva que solo toma unos segundos. Probablemente resolverá este problema.',
|
||||
230: '"Desinstalar servicio" es una acción peligrosa que eliminará el servicio de StartOS y borrará todos sus datos.',
|
||||
231: 'Reconstruir contenedor',
|
||||
232: 'Desinstalar servicio',
|
||||
233: 'Ver mensaje completo',
|
||||
234: 'Error del servicio',
|
||||
235: 'Esperando resultado',
|
||||
236: 'Iniciando',
|
||||
237: 'Éxito',
|
||||
238: 'Verificaciones de salud',
|
||||
239: 'Sin verificaciones de salud',
|
||||
240: 'Nombre',
|
||||
241: 'Estado',
|
||||
242: 'Abrir',
|
||||
243: 'Interfaces',
|
||||
244: 'Alojamiento',
|
||||
245: 'Instalando',
|
||||
246: 'Ver abajo',
|
||||
247: 'Controles',
|
||||
248: 'No hay servicios instalados',
|
||||
249: 'En ejecución',
|
||||
250: 'Detenido',
|
||||
251: 'Tarea requerida',
|
||||
252: 'Actualizando',
|
||||
253: 'Deteniendo',
|
||||
254: 'Confiar en tu CA raíz',
|
||||
255: 'Haciendo copia de seguridad',
|
||||
256: 'Reiniciando',
|
||||
257: 'Volver',
|
||||
258: 'Restaurando',
|
||||
259: 'Desconocido',
|
||||
260: 'Mostrar/Ocultar',
|
||||
261: 'Mostrar',
|
||||
262: 'Escanea este QR',
|
||||
263: 'Restablecer valores predeterminados',
|
||||
264: 'Como resultado de este cambio, los siguientes servicios dejarán de funcionar correctamente y pueden fallar',
|
||||
265: 'Error al iniciar el servicio',
|
||||
266: 'Problema',
|
||||
267: 'Fallo',
|
||||
268: 'Saludable',
|
||||
269: 'finalizando',
|
||||
270: '% desconocido',
|
||||
271: 'No proporcionado',
|
||||
272: 'Enlaces',
|
||||
273: 'Hash de Git',
|
||||
274: 'Licencia',
|
||||
275: 'Instalado desde',
|
||||
276: 'Repositorio del servicio',
|
||||
277: 'Repositorio del paquete',
|
||||
278: 'Sitio de marketing',
|
||||
279: 'Sitio de soporte',
|
||||
280: 'Enlace de donación',
|
||||
281: 'Acciones estándar',
|
||||
282: 'Reconstruir servicio',
|
||||
283: 'Reconstruye el contenedor del servicio. Solo es necesario si hay un error en StartOS',
|
||||
284: 'Desinstalar',
|
||||
285: 'Desinstala este servicio de StartOS y elimina todos los datos permanentemente.',
|
||||
286: 'Panel de control',
|
||||
287: 'panel de control',
|
||||
288: 'acciones',
|
||||
289: 'instrucciones',
|
||||
290: 'registros',
|
||||
291: 'acerca de',
|
||||
292: 'Iniciando carga',
|
||||
293: 'Intentar de nuevo',
|
||||
294: 'Subir archivo de paquete .s9pk',
|
||||
295: 'Advertencia: la carga del paquete será lenta a través de Tor. Cambia a conexión local para una mejor experiencia.',
|
||||
296: 'Subir',
|
||||
297: 'Se detectó un paquete s9pk de versión 1. Este formato está obsoleto. Puedes instalarlo manualmente con start-cli si es necesario.',
|
||||
298: 'Archivo de paquete inválido',
|
||||
299: 'Agrega proveedores ACME para generar certificados SSL (https) para el acceso desde clearnet.',
|
||||
300: 'Ver instrucciones',
|
||||
301: 'Proveedores guardados',
|
||||
302: 'Agregar proveedor',
|
||||
303: 'Contacto',
|
||||
304: 'Editar',
|
||||
305: 'Agregar proveedor ACME',
|
||||
306: 'Editar proveedor ACME',
|
||||
307: 'Correos de contacto',
|
||||
308: 'Necesarios para obtener un certificado de una Autoridad Certificadora',
|
||||
309: 'Alternar todo',
|
||||
310: 'Hecho',
|
||||
311: 'Se necesita la contraseña maestra',
|
||||
312: 'Ingresa tu contraseña maestra para cifrar esta copia de seguridad.',
|
||||
313: 'Contraseña maestra',
|
||||
314: 'Ingresa la contraseña maestra',
|
||||
315: 'Se necesita la contraseña original',
|
||||
316: 'Esta copia de seguridad fue creada con una contraseña diferente. Ingresa la contraseña original que se usó para cifrar esta copia.',
|
||||
317: 'Contraseña original',
|
||||
318: 'Ingresa la contraseña original',
|
||||
319: 'Iniciando copia de seguridad',
|
||||
320: 'Haz una copia de seguridad de StartOS y los datos de los servicios conectándote a un dispositivo en tu red local o a una unidad física conectada a tu servidor.',
|
||||
321: 'Restaura StartOS y los datos de los servicios desde un dispositivo en tu red local o una unidad física conectada a tu servidor que contenga una copia de seguridad existente.',
|
||||
322: 'Última copia de seguridad',
|
||||
323: 'Una carpeta en otro ordenador conectado a la misma red que tu servidor Start9.',
|
||||
324: 'Una unidad física conectada directamente a tu servidor Start9.',
|
||||
325: 'Seleccionar servicios para respaldar',
|
||||
326: 'Seleccionar copia de seguridad del servidor',
|
||||
327: 'Carpetas de red',
|
||||
328: 'Abrir nuevo',
|
||||
329: 'Nombre del host',
|
||||
330: 'Ruta',
|
||||
331: 'URL',
|
||||
332: 'Interfaz de red',
|
||||
333: 'Protocolo',
|
||||
334: 'Modelo',
|
||||
335: 'Agente de usuario',
|
||||
336: 'Plataforma',
|
||||
337: 'Última actividad',
|
||||
338: 'Creado en',
|
||||
339: 'Algoritmo',
|
||||
340: 'Huella digital',
|
||||
341: 'Hash del paquete',
|
||||
342: 'Publicado',
|
||||
343: 'Nueva carpeta de red',
|
||||
344: 'Actualizar carpeta de red',
|
||||
345: 'Probando la conectividad a la carpeta compartida',
|
||||
346: 'Asegúrate de que (1) el equipo de destino esté conectado a la misma LAN que tu servidor Start9, (2) la carpeta de destino esté compartida, y (3) el nombre del host, la ruta y las credenciales sean correctas.',
|
||||
347: 'No se pudo conectar',
|
||||
348: 'La carpeta de red no contiene una copia de seguridad válida',
|
||||
349: 'Conectar',
|
||||
350: 'Nombre de usuario',
|
||||
351: 'Contraseña',
|
||||
352: 'El nombre del host de tu dispositivo de destino en la red local.',
|
||||
353: 'En Windows, esta es la ruta completa a la carpeta compartida (por ejemplo, /Desktop/mi-carpeta). En Linux y Mac, es el nombre literal de la carpeta compartida (por ejemplo, mi-carpeta-compartida).',
|
||||
354: 'En Linux, este es el nombre de usuario de samba que creaste al compartir la carpeta. En Mac y Windows, es el nombre de usuario del usuario que comparte la carpeta.',
|
||||
355: 'En Linux, esta es la contraseña de samba que creaste al compartir la carpeta. En Mac y Windows, es la contraseña del usuario que comparte la carpeta.',
|
||||
356: 'Unidades físicas',
|
||||
357: 'No se detectaron unidades',
|
||||
358: 'Actualizar',
|
||||
359: 'La partición de la unidad no contiene una copia de seguridad válida',
|
||||
360: 'Progreso de la copia de seguridad',
|
||||
361: 'Completo',
|
||||
362: 'Haciendo copia de seguridad',
|
||||
363: 'Esperando',
|
||||
364: 'Copia de seguridad realizada',
|
||||
365: 'Restauración seleccionada',
|
||||
366: 'Inicializando',
|
||||
367: 'No disponible. La copia de seguridad se hizo en una versión más reciente de StartOS.',
|
||||
368: 'No disponible. El servicio ya está instalado.',
|
||||
369: 'Listo para restaurar',
|
||||
370: 'Nombre de host local',
|
||||
371: 'Creado',
|
||||
372: 'Se requiere contraseña',
|
||||
373: 'Ingresa la contraseña maestra que se usó para cifrar esta copia de seguridad. En la siguiente pantalla, podrás seleccionar los servicios individuales que deseas restaurar.',
|
||||
374: 'Descifrando unidad',
|
||||
375: 'Seleccionar servicios para restaurar',
|
||||
376: 'Disponible para copia de seguridad',
|
||||
377: 'Copias de seguridad de StartOS detectadas',
|
||||
378: 'No se detectaron copias de seguridad de StartOS',
|
||||
379: 'Versión de StartOS',
|
||||
380: 'Conectar un servidor SMTP externo permite que StartOS y tus servicios instalados te envíen correos electrónicos.',
|
||||
381: 'Credenciales SMTP',
|
||||
382: 'Enviar correo de prueba',
|
||||
383: 'Enviar',
|
||||
384: 'Enviando correo',
|
||||
385: 'Se ha enviado un correo de prueba a',
|
||||
386: 'Revisa tu carpeta de spam y márcalo como no spam.',
|
||||
387: 'La interfaz web de tu servidor StartOS, accesible desde cualquier navegador.',
|
||||
388: 'Cambia tu contraseña maestra de StartOS.',
|
||||
389: '¡Aún necesitarás tu contraseña actual para descifrar copias de seguridad existentes!',
|
||||
390: 'Las nuevas contraseñas no coinciden',
|
||||
391: 'La nueva contraseña debe tener al menos 12 caracteres',
|
||||
392: 'La nueva contraseña debe tener menos de 65 caracteres',
|
||||
393: 'La contraseña actual no es válida',
|
||||
394: 'Contraseña cambiada',
|
||||
395: 'Contraseña actual',
|
||||
396: 'Nueva contraseña',
|
||||
397: 'Reingresa nueva contraseña',
|
||||
398: 'Una sesión es un dispositivo que actualmente ha iniciado sesión en StartOS. Para mayor seguridad, cierra las sesiones que no reconozcas o que ya no uses.',
|
||||
399: 'Sesión actual',
|
||||
400: 'Otras sesiones',
|
||||
401: 'Terminar seleccionados',
|
||||
402: 'Terminando sesiones',
|
||||
403: 'Sin sesiones',
|
||||
404: 'Se requiere contraseña',
|
||||
405: 'Conectado',
|
||||
406: 'Olvidar',
|
||||
407: 'Credenciales WiFi',
|
||||
408: 'Obsoleto',
|
||||
409: 'El soporte para WiFi será eliminado en StartOS v0.4.1. Si no tienes acceso a Ethernet, puedes usar un extensor WiFi para conectarte a la red local y luego conectar tu servidor al extensor por Ethernet. Por favor, contacta al soporte de Start9 si tienes dudas o inquietudes.',
|
||||
410: 'Redes conocidas',
|
||||
411: 'Otras redes',
|
||||
412: 'WiFi está deshabilitado',
|
||||
413: 'No se detectó interfaz inalámbrica',
|
||||
414: 'Habilitando WiFi',
|
||||
415: 'Deshabilitando WiFi',
|
||||
416: 'Conectando. Esto podría tardar un poco',
|
||||
417: 'Reintentar',
|
||||
418: 'Mostrar más',
|
||||
419: 'Notas de la versión',
|
||||
420: 'Ver listado',
|
||||
421: 'Servicios que dependen de',
|
||||
422: 'ya no funcionarán correctamente y podrían fallar.',
|
||||
423: 'Solicitud fallida',
|
||||
424: '¡Todos los servicios están actualizados!',
|
||||
425: 'Ejecutar',
|
||||
426: 'La acción solo se puede ejecutar cuando el servicio está',
|
||||
427: 'Prohibido',
|
||||
428: 'puede experimentar problemas temporales',
|
||||
429: 'tiene dependencias no satisfechas. No funcionará como se espera.',
|
||||
430: 'Reconstruyendo contenedor',
|
||||
431: 'Iniciando desinstalación',
|
||||
432: 'eliminará permanentemente sus datos.',
|
||||
433: 'Desinstalando',
|
||||
434: 'Intentando alcanzar el servidor',
|
||||
435: 'Conexión restablecida',
|
||||
436: 'Estado desconocido',
|
||||
437: 'Servidor conectado',
|
||||
438: 'Sin Internet',
|
||||
439: 'Conectando',
|
||||
440: 'Apagando',
|
||||
441: 'Versiones',
|
||||
442: 'Nuevas notificaciones',
|
||||
443: 'Ver',
|
||||
444: 'Recargando PWA',
|
||||
445: 'Completado',
|
||||
446: 'Datos del sistema',
|
||||
447: 'No intentado',
|
||||
448: 'Fallido',
|
||||
449: 'Exitoso',
|
||||
450: 'Reinicia tu servidor para que estas actualizaciones surtan efecto. Puede tardar varios minutos en volver a estar en línea.',
|
||||
451: 'Descarga de StartOS completa',
|
||||
452: 'Unidad de almacenamiento desconocida detectada',
|
||||
453: 'Para usar una unidad de almacenamiento diferente, reemplaza la actual y haz clic en REINICIAR SERVIDOR abajo. Para usar la unidad actual, haz clic en USAR UNIDAD ACTUAL abajo y sigue las instrucciones. No se borrarán datos durante este proceso.',
|
||||
454: 'Unidad de almacenamiento no encontrada',
|
||||
455: 'Inserta tu unidad de almacenamiento de StartOS y haz clic en REINICIAR SERVIDOR abajo.',
|
||||
456: 'Unidad de almacenamiento dañada. Esto podría deberse a corrupción de datos o daño físico.',
|
||||
457: 'Puede o no ser posible reutilizar esta unidad reformateándola y recuperando desde una copia de seguridad. Para entrar en modo de recuperación, haz clic en ENTRAR EN MODO DE RECUPERACIÓN abajo y sigue las instrucciones. No se borrarán datos durante este paso.',
|
||||
458: 'Error del sistema de archivos',
|
||||
459: 'Reparar el disco podría ayudar a resolver este problema. Por favor, NO desconectes la unidad ni el servidor durante este tiempo o la situación empeorará.',
|
||||
460: 'Error de gestión de disco',
|
||||
461: 'Por favor, contacta con soporte',
|
||||
462: 'Modo de diagnóstico',
|
||||
463: 'error de inicio',
|
||||
464: 'Ver registros',
|
||||
465: 'Posibles soluciones',
|
||||
466: 'Configurar unidad actual',
|
||||
467: 'Entrar en modo de recuperación',
|
||||
468: 'El servidor se está reiniciando',
|
||||
469: 'Espera a que el servidor se reinicie y luego actualiza esta página.',
|
||||
470: 'Reiniciar servidor',
|
||||
471: 'Reparar unidad',
|
||||
472: 'Configurando tu servidor',
|
||||
473: 'Progreso',
|
||||
474: 'Iniciar sesión en StartOS',
|
||||
475: 'Iniciar sesión',
|
||||
476: 'Iniciando sesión',
|
||||
477: 'La contraseña debe tener menos de 65 caracteres',
|
||||
478: 'Contraseña inválida',
|
||||
479: 'Descarga y confía en tu Autoridad Certificadora raíz para establecer una conexión segura (HTTPS). Tendrás que repetir esto en cada dispositivo que uses para conectarte a tu servidor.',
|
||||
480: 'Guarda esta página para poder acceder más tarde. También puedes encontrar esta dirección en el archivo descargado al final de la configuración inicial.',
|
||||
481: 'Has confiado exitosamente en tu CA raíz y ahora puedes iniciar sesión de forma segura.',
|
||||
482: 'Tu servidor usa su CA raíz para generar certificados SSL/TLS para sí mismo y los servicios instalados. Estos certificados se utilizan para cifrar el tráfico de red con tus dispositivos cliente.',
|
||||
483: 'Sigue las instrucciones para tu sistema operativo. Al confiar en tu CA raíz, tu dispositivo puede verificar la autenticidad de las comunicaciones cifradas con tu servidor.',
|
||||
484: 'Actualiza la página. Si actualizar no funciona, puede que necesites cerrar y volver a abrir tu navegador, y luego volver a esta página.',
|
||||
485: 'Interfaz de StartOS',
|
||||
486: 'WiFi',
|
||||
} as any satisfies i18n
|
||||
19
web/projects/shared/src/i18n/i18n.pipe.ts
Normal file
19
web/projects/shared/src/i18n/i18n.pipe.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { inject, Injectable, Pipe, PipeTransform } from '@angular/core'
|
||||
import { ENGLISH } from './dictionaries/english'
|
||||
import { I18N, i18nKey } from './i18n.providers'
|
||||
|
||||
@Pipe({
|
||||
standalone: true,
|
||||
name: 'i18n',
|
||||
pure: false,
|
||||
})
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class i18nPipe implements PipeTransform {
|
||||
private readonly i18n = inject(I18N)
|
||||
|
||||
transform(englishKey: i18nKey | null | undefined): string | undefined {
|
||||
return englishKey
|
||||
? this.i18n()?.[ENGLISH[englishKey]] || englishKey
|
||||
: undefined
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,28 @@ import {
|
||||
tuiLanguageSwitcher,
|
||||
TuiLanguageSwitcherService,
|
||||
} from '@taiga-ui/i18n'
|
||||
import ENGLISH from './dictionaries/english'
|
||||
import { ENGLISH } from './dictionaries/english'
|
||||
import { i18nService } from './i18n.service'
|
||||
|
||||
export type i18n = typeof ENGLISH
|
||||
export type i18nKey = keyof typeof ENGLISH
|
||||
export type i18n = Record<(typeof ENGLISH)[i18nKey], string>
|
||||
|
||||
export const I18N = tuiCreateToken(signal(ENGLISH))
|
||||
export const I18N = tuiCreateToken(signal<i18n | null>(null))
|
||||
export const I18N_LOADER =
|
||||
tuiCreateToken<(lang: TuiLanguageName) => Promise<i18n>>()
|
||||
export const I18N_STORAGE = tuiCreateToken<
|
||||
(lang: TuiLanguageName) => Promise<void>
|
||||
>(() => Promise.resolve())
|
||||
|
||||
export const I18N_PROVIDERS = [
|
||||
tuiLanguageSwitcher(async (language: TuiLanguageName): Promise<unknown> => {
|
||||
switch (language) {
|
||||
case 'spanish':
|
||||
return import('@taiga-ui/i18n/languages/spanish')
|
||||
case 'polish':
|
||||
return import('@taiga-ui/i18n/languages/polish')
|
||||
case 'german':
|
||||
return import('@taiga-ui/i18n/languages/german')
|
||||
default:
|
||||
return import('@taiga-ui/i18n/languages/english')
|
||||
}
|
||||
@@ -29,8 +37,12 @@ export const I18N_PROVIDERS = [
|
||||
switch (language) {
|
||||
case 'spanish':
|
||||
return import('./dictionaries/spanish').then(v => v.default)
|
||||
case 'polish':
|
||||
return import('./dictionaries/polish').then(v => v.default)
|
||||
case 'german':
|
||||
return import('./dictionaries/german').then(v => v.default)
|
||||
default:
|
||||
return import('./dictionaries/english').then(v => v.default)
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -1,7 +1,6 @@
|
||||
import { inject, Injectable, signal } from '@angular/core'
|
||||
import { TuiLanguageName, TuiLanguageSwitcherService } from '@taiga-ui/i18n'
|
||||
import { I18N, I18N_LOADER } from './i18n.providers'
|
||||
import { ApiService } from '../services/api/embassy-api.service'
|
||||
import { I18N, I18N_LOADER, I18N_STORAGE } from './i18n.providers'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -9,7 +8,7 @@ import { ApiService } from '../services/api/embassy-api.service'
|
||||
export class i18nService extends TuiLanguageSwitcherService {
|
||||
private readonly i18n = inject(I18N)
|
||||
private readonly i18nLoader = inject(I18N_LOADER)
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly store = inject(I18N_STORAGE)
|
||||
|
||||
readonly loading = signal(false)
|
||||
|
||||
@@ -20,7 +19,7 @@ export class i18nService extends TuiLanguageSwitcherService {
|
||||
|
||||
super.setLanguage(language)
|
||||
this.loading.set(true)
|
||||
this.api.setDbValue(['language'], language).then(() =>
|
||||
this.store(language).then(() =>
|
||||
this.i18nLoader(language).then(value => {
|
||||
this.i18n.set(value)
|
||||
this.loading.set(false)
|
||||
@@ -28,3 +27,6 @@ export class i18nService extends TuiLanguageSwitcherService {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const languages = ['english', 'spanish', 'polish', 'german'] as const
|
||||
export type Languages = (typeof languages)[number]
|
||||
@@ -5,20 +5,21 @@
|
||||
export * from './classes/http-error'
|
||||
export * from './classes/rpc-error'
|
||||
|
||||
export * from './components/initializing/logs-window.component'
|
||||
export * from './components/initializing/initializing.component'
|
||||
export * from './components/loading/loading.component'
|
||||
export * from './components/loading/loading.component'
|
||||
export * from './components/loading/loading.service'
|
||||
export * from './components/ticker/ticker.component'
|
||||
export * from './components/ticker/ticker.module'
|
||||
export * from './components/logs-window.component'
|
||||
export * from './components/initializing.component'
|
||||
export * from './components/ticker.component'
|
||||
export * from './components/drive.component'
|
||||
export * from './components/markdown.component'
|
||||
export * from './components/prompt.component'
|
||||
export * from './components/server.component'
|
||||
|
||||
export * from './directives/drag-scroller.directive'
|
||||
export * from './directives/safe-links.directive'
|
||||
|
||||
export * from './i18n/i18n.pipe'
|
||||
export * from './i18n/i18n.providers'
|
||||
export * from './i18n/i18n.service'
|
||||
|
||||
export * from './pipes/exver/exver.module'
|
||||
export * from './pipes/exver/exver.pipe'
|
||||
export * from './pipes/shared/shared.module'
|
||||
@@ -30,10 +31,12 @@ export * from './pipes/unit-conversion/unit-conversion.pipe'
|
||||
export * from './pipes/markdown.pipe'
|
||||
|
||||
export * from './services/copy.service'
|
||||
export * from './services/dialog.service'
|
||||
export * from './services/download-html.service'
|
||||
export * from './services/exver.service'
|
||||
export * from './services/error.service'
|
||||
export * from './services/http.service'
|
||||
export * from './services/loading.service'
|
||||
export * from './services/setup-logs.service'
|
||||
|
||||
export * from './types/api'
|
||||
|
||||
93
web/projects/shared/src/services/dialog.service.ts
Normal file
93
web/projects/shared/src/services/dialog.service.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { inject, Injectable, TemplateRef } from '@angular/core'
|
||||
import {
|
||||
TuiResponsiveDialogOptions,
|
||||
TuiResponsiveDialogService,
|
||||
} from '@taiga-ui/addon-mobile'
|
||||
import { TuiAlertOptions } from '@taiga-ui/core'
|
||||
import { TUI_CONFIRM, TuiConfirmData } from '@taiga-ui/kit'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PROMPT, PromptOptions } from '../components/prompt.component'
|
||||
import { i18nPipe } from '../i18n/i18n.pipe'
|
||||
import { i18nKey } from '../i18n/i18n.providers'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DialogService {
|
||||
private readonly dialogs = inject(TuiResponsiveDialogService)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
openPrompt<T = void>(
|
||||
options: Partial<TuiResponsiveDialogOptions<PromptOptions>> & {
|
||||
label: i18nKey
|
||||
data: PromptOptions
|
||||
},
|
||||
) {
|
||||
const { message, label, placeholder, buttonText } = options.data
|
||||
|
||||
return this.dialogs.open<T>(PROMPT, {
|
||||
label: this.i18n.transform(options.label),
|
||||
data: {
|
||||
...options.data,
|
||||
message: this.i18n.transform(message),
|
||||
label: this.i18n.transform(label),
|
||||
placeholder: this.i18n.transform(placeholder),
|
||||
buttonText: this.i18n.transform(buttonText),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
openConfirm<T = void>(
|
||||
options: Partial<TuiResponsiveDialogOptions<TuiConfirmData>> & {
|
||||
label: i18nKey
|
||||
data?: TuiConfirmData & {
|
||||
content?: PolymorpheusComponent<any> | i18nKey
|
||||
yes?: i18nKey
|
||||
no?: i18nKey
|
||||
}
|
||||
},
|
||||
) {
|
||||
options.data = options.data || {}
|
||||
const { content, yes, no } = options.data
|
||||
|
||||
return this.dialogs.open<T>(TUI_CONFIRM, {
|
||||
label: this.i18n.transform(options.label),
|
||||
data: {
|
||||
...options.data,
|
||||
content: isI18n(content) ? this.i18n.transform(content) : content,
|
||||
yes: this.i18n.transform(yes),
|
||||
no: this.i18n.transform(no),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
openAlert<T = void>(
|
||||
message: i18nKey | undefined,
|
||||
options: Partial<TuiAlertOptions<any>> & {
|
||||
label?: i18nKey
|
||||
} = {},
|
||||
) {
|
||||
return this.dialogs.open<T>(this.i18n.transform(message), {
|
||||
...options,
|
||||
label: this.i18n.transform(options.label),
|
||||
})
|
||||
}
|
||||
|
||||
openComponent<T = void>(
|
||||
component: PolymorpheusComponent<any> | TemplateRef<any>,
|
||||
options: Partial<TuiResponsiveDialogOptions<any>> & {
|
||||
label?: i18nKey
|
||||
} = {},
|
||||
) {
|
||||
return this.dialogs.open<T>(component, {
|
||||
...options,
|
||||
label: this.i18n.transform(options.label),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function isI18n(
|
||||
content: PolymorpheusComponent<any> | i18nKey | undefined,
|
||||
): content is i18nKey {
|
||||
return typeof content === 'string'
|
||||
}
|
||||
45
web/projects/shared/src/services/loading.service.ts
Normal file
45
web/projects/shared/src/services/loading.service.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ChangeDetectionStrategy, Component, Injectable } from '@angular/core'
|
||||
import { TuiPopoverService } from '@taiga-ui/cdk'
|
||||
import { TUI_DIALOGS, TuiLoader } from '@taiga-ui/core'
|
||||
import { injectContext } from '@taiga-ui/polymorpheus'
|
||||
import { i18nPipe } from '../i18n/i18n.pipe'
|
||||
import { i18nKey } from '../i18n/i18n.providers'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: '<tui-loader [textContent]="content | i18n" />',
|
||||
styles: `
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 80%;
|
||||
margin: auto;
|
||||
padding: 1.5rem;
|
||||
background: var(--tui-background-elevation-1);
|
||||
border-radius: var(--tui-radius-m);
|
||||
box-shadow: var(--tui-shadow-popup);
|
||||
|
||||
--tui-background-accent-1: var(--tui-status-warning);
|
||||
}
|
||||
|
||||
tui-loader {
|
||||
flex-shrink: 0;
|
||||
min-width: 2rem;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiLoader, i18nPipe],
|
||||
})
|
||||
class LoadingComponent {
|
||||
readonly content = injectContext<{ content: i18nKey }>().content
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: `root`,
|
||||
useFactory: () => new LoadingService(TUI_DIALOGS, LoadingComponent),
|
||||
})
|
||||
export class LoadingService extends TuiPopoverService<unknown> {
|
||||
override open<G = void>(textContent: i18nKey) {
|
||||
return super.open<G>(textContent)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Component, inject, OnInit } from '@angular/core'
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||
import { Title } from '@angular/platform-browser'
|
||||
import { i18nService } from '@start9labs/shared'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { combineLatest, map, merge, startWith } from 'rxjs'
|
||||
import { i18nService } from 'src/app/i18n/i18n.service'
|
||||
import { ConnectionService } from './services/connection.service'
|
||||
import { PatchDataService } from './services/patch-data.service'
|
||||
import { DataModel } from './services/patch-db/data-model'
|
||||
|
||||
@@ -5,7 +5,13 @@ import {
|
||||
AbstractCategoryService,
|
||||
FilterPackagesPipe,
|
||||
} from '@start9labs/marketplace'
|
||||
import { RELATIVE_URL, WorkspaceConfig } from '@start9labs/shared'
|
||||
import {
|
||||
I18N_PROVIDERS,
|
||||
I18N_STORAGE,
|
||||
i18nService,
|
||||
RELATIVE_URL,
|
||||
WorkspaceConfig,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
TUI_DATE_FORMAT,
|
||||
TUI_DIALOGS_CLOSE,
|
||||
@@ -21,8 +27,6 @@ import {
|
||||
import { tuiTextfieldOptionsProvider } from '@taiga-ui/legacy'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { filter, of, pairwise } from 'rxjs'
|
||||
import { I18N_PROVIDERS } from 'src/app/i18n/i18n.providers'
|
||||
import { i18nService } from 'src/app/i18n/i18n.service'
|
||||
import {
|
||||
PATCH_CACHE,
|
||||
PatchDbSource,
|
||||
@@ -100,6 +104,14 @@ export const APP_PROVIDERS: Provider[] = [
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
provide: I18N_STORAGE,
|
||||
useFactory: () => {
|
||||
const api = inject(ApiService)
|
||||
|
||||
return (language: string) => api.setDbValue(['language'], language)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export function appInitializer(): () => void {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
} from '@angular/core'
|
||||
import { i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { BackupReport } from 'src/app/services/api/api.types'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h3 class="g-title">
|
||||
{{ 'Completed' | i18n }}: {{ data.createdAt | date: 'medium' }}
|
||||
</h3>
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>{{ 'System data' | i18n }}</strong>
|
||||
<div tuiSubtitle [style.color]="system().color">
|
||||
{{ system().result | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
<tui-icon [icon]="system().icon" [style.color]="system().color" />
|
||||
</div>
|
||||
@for (pkg of data.content.packages | keyvalue; track $index) {
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>{{ pkg.key }}</strong>
|
||||
<div tuiSubtitle [style.color]="getColor(pkg.value.error)">
|
||||
{{
|
||||
pkg.value.error
|
||||
? ('Failed' | i18n) + ': ' + pkg.value.error
|
||||
: ('Succeeded' | i18n)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<tui-icon
|
||||
[icon]="getIcon(pkg.value.error)"
|
||||
[style.color]="getColor(pkg.value.error)"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CommonModule, TuiIcon, TuiCell, TuiTitle, i18nPipe],
|
||||
})
|
||||
export class BackupsReportModal {
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
readonly data =
|
||||
injectContext<
|
||||
TuiDialogContext<void, { content: BackupReport; createdAt: string }>
|
||||
>().data
|
||||
|
||||
readonly system = computed(
|
||||
(): { result: i18nKey; icon: string; color: string } => {
|
||||
if (!this.data.content.server.attempted) {
|
||||
return {
|
||||
result: 'Not attempted',
|
||||
icon: '@tui.minus',
|
||||
color: 'var(--tui-text-secondary)',
|
||||
}
|
||||
}
|
||||
|
||||
if (this.data.content.server.error) {
|
||||
return {
|
||||
result:
|
||||
`${this.i18n.transform('Failed')}: ${this.data.content.server.error}` as i18nKey,
|
||||
icon: '@tui.circle-minus',
|
||||
color: 'var(--tui-text-negative)',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: 'Succeeded',
|
||||
icon: '@tui.check',
|
||||
color: 'var(--tui-text-positive)',
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
getColor(error: unknown) {
|
||||
return error ? 'var(--tui-text-negative)' : 'var(--tui-text-positive)'
|
||||
}
|
||||
|
||||
getIcon(error: unknown) {
|
||||
return error ? '@tui.circle-minus' : '@tui.check'
|
||||
}
|
||||
}
|
||||
|
||||
export const REPORT = new PolymorpheusComponent(BackupsReportModal)
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AsyncPipe } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiAlert } from '@taiga-ui/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { endWith, map, merge, Observable, pairwise, Subject } from 'rxjs'
|
||||
@@ -15,12 +16,14 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
[tuiAlertOptions]="{ label: 'StartOS' }"
|
||||
(tuiAlertChange)="onDismiss()"
|
||||
>
|
||||
New notifications
|
||||
<a routerLink="/notifications" [queryParams]="{ toast: true }">View</a>
|
||||
{{ 'New notifications' | i18n }}
|
||||
<a routerLink="/notifications" [queryParams]="{ toast: true }">
|
||||
{{ 'View' | i18n }}
|
||||
</a>
|
||||
</ng-template>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiAlert, RouterLink, AsyncPipe],
|
||||
imports: [TuiAlert, RouterLink, AsyncPipe, i18nPipe],
|
||||
})
|
||||
export class NotificationsToastComponent {
|
||||
private readonly dismiss$ = new Subject<boolean>()
|
||||
|
||||
@@ -70,7 +70,7 @@ export class RefreshAlertComponent {
|
||||
}
|
||||
|
||||
async pwaReload() {
|
||||
const loader = this.loader.open('Reloading PWA...').subscribe()
|
||||
const loader = this.loader.open('Reloading PWA').subscribe()
|
||||
|
||||
try {
|
||||
// attempt to update to the latest client version available
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { BackupReport } from 'src/app/services/api/api.types'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h3 class="g-title">Completed: {{ data.createdAt | date: 'medium' }}</h3>
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>System data</strong>
|
||||
<div tuiSubtitle [style.color]="system.color">{{ system.result }}</div>
|
||||
</div>
|
||||
<tui-icon [icon]="system.icon" [style.color]="system.color" />
|
||||
</div>
|
||||
@for (pkg of data.content.packages | keyvalue; track $index) {
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>{{ pkg.key }}</strong>
|
||||
<div tuiSubtitle [style.color]="getColor(pkg.value.error)">
|
||||
{{ pkg.value.error ? 'Failed: ' + pkg.value.error : 'Succeeded' }}
|
||||
</div>
|
||||
</div>
|
||||
<tui-icon
|
||||
[icon]="getIcon(pkg.value.error)"
|
||||
[style.color]="getColor(pkg.value.error)"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CommonModule, TuiIcon, TuiCell, TuiTitle],
|
||||
})
|
||||
export class BackupsReportModal {
|
||||
readonly data =
|
||||
injectContext<
|
||||
TuiDialogContext<void, { content: BackupReport; createdAt: string }>
|
||||
>().data
|
||||
|
||||
readonly system = this.getSystem()
|
||||
|
||||
getColor(error: unknown) {
|
||||
return error ? 'var(--tui-text-negative)' : 'var(--tui-text-positive)'
|
||||
}
|
||||
|
||||
getIcon(error: unknown) {
|
||||
return error ? '@tui.circle-minus' : '@tui.check'
|
||||
}
|
||||
|
||||
private getSystem() {
|
||||
if (!this.data.content.server.attempted) {
|
||||
return {
|
||||
result: 'Not Attempted',
|
||||
icon: '@tui.minus',
|
||||
color: 'var(--tui-text-secondary)',
|
||||
}
|
||||
}
|
||||
|
||||
if (this.data.content.server.error) {
|
||||
return {
|
||||
result: `Failed: ${this.data.content.server.error}`,
|
||||
icon: '@tui.circle-minus',
|
||||
color: 'var(--tui-text-negative)',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: 'Succeeded',
|
||||
icon: '@tui.check',
|
||||
color: 'var(--tui-text-positive)',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const REPORT = new PolymorpheusComponent(BackupsReportModal)
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AsyncPipe } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared'
|
||||
import { TuiAlert, TuiButton } from '@taiga-ui/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
@@ -21,14 +21,16 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
<ng-template
|
||||
[tuiAlert]="!!(visible$ | async)"
|
||||
[tuiAlertOptions]="{
|
||||
label: 'StartOS download complete!',
|
||||
label: 'StartOS download complete' | i18n,
|
||||
appearance: 'positive',
|
||||
autoClose: 0,
|
||||
}"
|
||||
(tuiAlertChange)="onDismiss()"
|
||||
>
|
||||
Restart your server for these updates to take effect. It can take several
|
||||
minutes to come back online.
|
||||
{{
|
||||
'Restart your server for these updates to take effect. It can take several minutes to come back online.'
|
||||
| i18n
|
||||
}}
|
||||
<div>
|
||||
<button
|
||||
tuiButton
|
||||
@@ -37,13 +39,13 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
style="margin-top: 8px"
|
||||
(click)="restart()"
|
||||
>
|
||||
Restart
|
||||
{{ 'Restart' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiButton, TuiAlert, AsyncPipe],
|
||||
imports: [TuiButton, TuiAlert, AsyncPipe, i18nPipe],
|
||||
})
|
||||
export class UpdateToastComponent {
|
||||
private readonly api = inject(ApiService)
|
||||
@@ -65,7 +67,7 @@ export class UpdateToastComponent {
|
||||
async restart(): Promise<void> {
|
||||
this.onDismiss()
|
||||
|
||||
const loader = this.loader.open('Restarting...').subscribe()
|
||||
const loader = this.loader.open('Restarting').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.restartServer({})
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
export default {
|
||||
ui: {
|
||||
back: 'Back',
|
||||
change: 'Change',
|
||||
update: 'Update',
|
||||
reset: 'Reset',
|
||||
},
|
||||
system: {
|
||||
outlet: {
|
||||
system: 'System',
|
||||
general: 'General',
|
||||
email: 'Email',
|
||||
backup: 'Create Backup',
|
||||
restore: 'Restore Backup',
|
||||
interfaces: 'StartOS UI',
|
||||
acme: 'ACME',
|
||||
wifi: 'WiFi',
|
||||
sessions: 'Active Sessions',
|
||||
password: 'Change Password',
|
||||
},
|
||||
general: {
|
||||
title: 'General Settings',
|
||||
subtitle: 'Manage your overall setup and preferences',
|
||||
tab: 'Browser Tab Title',
|
||||
language: 'Language',
|
||||
repair: {
|
||||
title: 'Disk Repair',
|
||||
subtitle: 'Attempt automatic repair',
|
||||
button: 'Repair',
|
||||
},
|
||||
ca: {
|
||||
title: 'Root Certificate Authority',
|
||||
subtitle: `Download your server's Root CA`,
|
||||
button: 'Download',
|
||||
},
|
||||
tor: {
|
||||
title: 'Reset Tor',
|
||||
subtitle: 'Restart the Tor daemon on your server',
|
||||
},
|
||||
update: {
|
||||
title: 'Software Update',
|
||||
button: {
|
||||
restart: 'Restart to apply',
|
||||
check: 'Check for updates',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import type { i18n } from '../i18n.providers'
|
||||
|
||||
export default {
|
||||
ui: {
|
||||
back: 'Atrás',
|
||||
change: 'Cambiar',
|
||||
update: 'Actualizar',
|
||||
reset: 'Reiniciar',
|
||||
},
|
||||
system: {
|
||||
outlet: {
|
||||
system: 'Sistema',
|
||||
general: 'General',
|
||||
email: 'Correo Electrónico',
|
||||
backup: 'Crear Copia de Seguridad',
|
||||
restore: 'Restaurar Copia de Seguridad',
|
||||
interfaces: 'Direcciones de Interfaz de Usuario',
|
||||
acme: 'ACME',
|
||||
wifi: 'WiFi',
|
||||
sessions: 'Sesiones Activas',
|
||||
password: 'Cambiar Contraseña',
|
||||
},
|
||||
general: {
|
||||
title: 'Configuración General',
|
||||
subtitle: 'Gestiona tu configuración general y preferencias',
|
||||
tab: 'Título de la Pestaña del Navegador',
|
||||
language: 'Idioma',
|
||||
repair: {
|
||||
title: 'Reparación de Disco',
|
||||
subtitle: 'Intentar reparación automática',
|
||||
button: 'Reparar',
|
||||
},
|
||||
ca: {
|
||||
title: 'Autoridad de Certificación Raíz',
|
||||
subtitle: 'Descarga la autoridad certificadora raíz de tu servidor',
|
||||
button: 'Descarga',
|
||||
},
|
||||
tor: {
|
||||
title: 'Reiniciar Tor',
|
||||
subtitle: 'Reiniciar el daemon de Tor en tu servidor',
|
||||
},
|
||||
update: {
|
||||
title: 'Actualización de Software',
|
||||
button: {
|
||||
restart: 'Reiniciar para aplicar',
|
||||
check: 'Buscar actualizaciones',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies i18n
|
||||
@@ -1,23 +0,0 @@
|
||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||
import { I18N, i18n } from './i18n.providers'
|
||||
|
||||
type DeepKeyOf<T> = {
|
||||
[K in keyof T & string]: T[K] extends {}
|
||||
? T[K] extends string
|
||||
? K
|
||||
: `${K}.${DeepKeyOf<T[K]>}`
|
||||
: never
|
||||
}[keyof T & string]
|
||||
|
||||
@Pipe({
|
||||
standalone: true,
|
||||
name: 'i18n',
|
||||
pure: false,
|
||||
})
|
||||
export class i18nPipe implements PipeTransform {
|
||||
private readonly i18n = inject(I18N)
|
||||
|
||||
transform(path: DeepKeyOf<i18n>): string {
|
||||
return path.split('.').reduce((acc, part) => acc[part], this.i18n() as any)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { RouterModule, Routes } from '@angular/router'
|
||||
import { HomePage } from './home.page'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
|
||||
const ROUTES: Routes = [
|
||||
{
|
||||
@@ -12,7 +13,7 @@ const ROUTES: Routes = [
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, TuiButton, RouterModule.forChild(ROUTES)],
|
||||
imports: [CommonModule, TuiButton, RouterModule.forChild(ROUTES), i18nPipe],
|
||||
declarations: [HomePage],
|
||||
})
|
||||
export class HomePageModule {}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<ng-container *ngIf="!restarted; else refresh">
|
||||
<h1 class="title">StartOS - Diagnostic Mode</h1>
|
||||
<h1 class="title">StartOS - {{ 'Diagnostic Mode' | i18n }}</h1>
|
||||
|
||||
<ng-container *ngIf="error">
|
||||
<h2 class="subtitle">StartOS launch error:</h2>
|
||||
<h2 class="subtitle">StartOS {{ 'launch error' | i18n }}:</h2>
|
||||
<code class="code warning">
|
||||
<p>{{ error.problem }}</p>
|
||||
<p *ngIf="error.details">{{ error.details }}</p>
|
||||
</code>
|
||||
|
||||
<a tuiButton routerLink="logs">View Logs</a>
|
||||
<a tuiButton routerLink="logs">{{ 'View logs' | i18n }}</a>
|
||||
|
||||
<h2 class="subtitle">Possible solutions:</h2>
|
||||
<h2 class="subtitle">{{ 'Possible solutions' | i18n }}:</h2>
|
||||
<code class="code"><p>{{ error.solution }}</p></code>
|
||||
|
||||
<div class="buttons">
|
||||
<button tuiButton (click)="restart()">Restart Server</button>
|
||||
<button tuiButton (click)="restart()">{{ 'Restart server' | i18n }}</button>
|
||||
|
||||
<button
|
||||
*ngIf="error.code === 15 || error.code === 25"
|
||||
@@ -22,7 +22,7 @@
|
||||
appearance="secondary"
|
||||
(click)="forgetDrive()"
|
||||
>
|
||||
{{ error.code === 15 ? 'Setup Current Drive' : 'Enter Recovery Mode'}}
|
||||
{{ error.code === 15 ? ('Setup current drive' | i18n) : ('Enter recovery mode' | i18n) }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -30,16 +30,16 @@
|
||||
appearance="secondary-destructive"
|
||||
(click)="presentAlertRepairDisk()"
|
||||
>
|
||||
Repair Drive
|
||||
{{ 'Repair drive' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #refresh>
|
||||
<h1 class="title">Server is restarting</h1>
|
||||
<h1 class="title">{{ 'Server is restarting' | i18n }}</h1>
|
||||
<h2 class="subtitle">
|
||||
Wait for the server to restart, then refresh this page.
|
||||
{{ 'Wait for the server to restart, then refresh this page.' | i18n }}
|
||||
</h2>
|
||||
<button tuiButton (click)="refreshPage()">Refresh</button>
|
||||
<button tuiButton (click)="refreshPage()">{{ 'Refresh' | i18n }}</button>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { TUI_CONFIRM } from '@taiga-ui/kit'
|
||||
import { Component, Inject } from '@angular/core'
|
||||
import { WA_WINDOW } from '@ng-web-apis/common'
|
||||
import { LoadingService } from '@start9labs/shared'
|
||||
import { TuiDialogService } from '@taiga-ui/core'
|
||||
import { DialogService, i18nKey, LoadingService } from '@start9labs/shared'
|
||||
import { filter } from 'rxjs'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
|
||||
// @TODO Alex how to use i18nPipe in this component since not standalone?
|
||||
@Component({
|
||||
selector: 'diagnostic-home',
|
||||
templateUrl: 'home.page.html',
|
||||
@@ -16,15 +15,15 @@ export class HomePage {
|
||||
restarted = false
|
||||
error?: {
|
||||
code: number
|
||||
problem: string
|
||||
solution: string
|
||||
problem: i18nKey
|
||||
solution: i18nKey
|
||||
details?: string
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly loader: LoadingService,
|
||||
private readonly api: ApiService,
|
||||
private readonly dialogs: TuiDialogService,
|
||||
private readonly dialog: DialogService,
|
||||
@Inject(WA_WINDOW) private readonly window: Window,
|
||||
readonly config: ConfigService,
|
||||
) {}
|
||||
@@ -64,7 +63,7 @@ export class HomePage {
|
||||
} else if (error.code === 2) {
|
||||
this.error = {
|
||||
code: 2,
|
||||
problem: 'Filesystem I/O error.',
|
||||
problem: 'Filesystem error',
|
||||
solution:
|
||||
'Repairing the disk could help resolve this issue. Please DO NOT unplug the drive or server during this time or the situation will become worse.',
|
||||
details: error.data?.details,
|
||||
@@ -73,7 +72,7 @@ export class HomePage {
|
||||
} else if (error.code === 48) {
|
||||
this.error = {
|
||||
code: 48,
|
||||
problem: 'Disk management error.',
|
||||
problem: 'Disk management error',
|
||||
solution:
|
||||
'Repairing the disk could help resolve this issue. Please DO NOT unplug the drive or server during this time or the situation will become worse.',
|
||||
details: error.data?.details,
|
||||
@@ -81,8 +80,8 @@ export class HomePage {
|
||||
} else {
|
||||
this.error = {
|
||||
code: error.code,
|
||||
problem: error.message,
|
||||
solution: 'Please contact support.',
|
||||
problem: error.message as i18nKey,
|
||||
solution: 'Please contact support',
|
||||
details: error.data?.details,
|
||||
}
|
||||
}
|
||||
@@ -92,7 +91,7 @@ export class HomePage {
|
||||
}
|
||||
|
||||
async restart(): Promise<void> {
|
||||
const loader = this.loader.open('Loading...').subscribe()
|
||||
const loader = this.loader.open('Loading').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.diagnosticRestart()
|
||||
@@ -105,7 +104,7 @@ export class HomePage {
|
||||
}
|
||||
|
||||
async forgetDrive(): Promise<void> {
|
||||
const loader = this.loader.open('Loading...').subscribe()
|
||||
const loader = this.loader.open('Loading').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.diagnosticForgetDrive()
|
||||
@@ -119,15 +118,15 @@ export class HomePage {
|
||||
}
|
||||
|
||||
async presentAlertRepairDisk() {
|
||||
this.dialogs
|
||||
.open(TUI_CONFIRM, {
|
||||
this.dialog
|
||||
.openConfirm({
|
||||
label: 'Warning',
|
||||
size: 's',
|
||||
data: {
|
||||
no: 'Cancel',
|
||||
yes: 'Repair',
|
||||
content:
|
||||
'<p>This action should only be executed if directed by a Start9 support specialist.</p><p>If anything happens to the device during the reboot, such as losing power or unplugging the drive, the filesystem <i>will</i> be in an unrecoverable state. Please proceed with caution.</p>',
|
||||
'This action should only be executed if directed by a Start9 support specialist. We recommend backing up your device before preforming this action. If anything happens to the device during the reboot, such as losing power or unplugging the drive, the filesystem will be in an unrecoverable state. Please proceed with caution.',
|
||||
},
|
||||
})
|
||||
.pipe(filter(Boolean))
|
||||
@@ -145,7 +144,7 @@ export class HomePage {
|
||||
}
|
||||
|
||||
private async repairDisk(): Promise<void> {
|
||||
const loader = this.loader.open('Loading...').subscribe()
|
||||
const loader = this.loader.open('Loading').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.diagnosticRepairDisk()
|
||||
|
||||
@@ -5,25 +5,18 @@
|
||||
class="card"
|
||||
>
|
||||
<tui-icon icon="@tui.lock" [style.font-size.rem]="4" />
|
||||
<h1>Trust Your Root CA</h1>
|
||||
<h1>{{ 'Trust your Root CA' | i18n }}</h1>
|
||||
<p>
|
||||
Download and trust your server's 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.
|
||||
{{ '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>
|
||||
<ol>
|
||||
<li>
|
||||
<b>Bookmark this page</b>
|
||||
- Save this page so you can access it later. You can also find the address
|
||||
in the
|
||||
<code>StartOS-info.html</code>
|
||||
file downloaded at the end of initial setup.
|
||||
<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 }}
|
||||
</li>
|
||||
<li>
|
||||
<b>Download your server's Root CA</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.
|
||||
<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 }}
|
||||
<br />
|
||||
<a
|
||||
tuiButton
|
||||
@@ -31,14 +24,12 @@
|
||||
iconEnd="@tui.download"
|
||||
href="/static/local-root-ca.crt"
|
||||
>
|
||||
Download
|
||||
{{ 'Download' | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<b>Trust your server's Root CA</b>
|
||||
- Follow instructions for your OS. By trusting your server's Root CA, your
|
||||
device can verify the authenticity of encrypted communications with your
|
||||
server.
|
||||
<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 }}
|
||||
<br />
|
||||
<a
|
||||
tuiButton
|
||||
@@ -48,13 +39,12 @@
|
||||
rel="noreferrer"
|
||||
iconEnd="@tui.external-link"
|
||||
>
|
||||
View Instructions
|
||||
{{ 'View instructions' | i18n }}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<b>Test</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.
|
||||
<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 }}
|
||||
<br />
|
||||
<button
|
||||
tuiButton
|
||||
@@ -64,7 +54,7 @@
|
||||
iconEnd="@tui.refresh-cw"
|
||||
(click)="refresh()"
|
||||
>
|
||||
Refresh
|
||||
{{ 'Refresh' | i18n }}
|
||||
</button>
|
||||
</li>
|
||||
</ol>
|
||||
@@ -76,21 +66,20 @@
|
||||
(click)="launchHttps()"
|
||||
[disabled]="caTrusted"
|
||||
>
|
||||
Skip
|
||||
{{ 'Skip' | i18n }}
|
||||
</button>
|
||||
<div><small>(not recommended)</small></div>
|
||||
<div><small>({{ 'not recommended' | i18n }})</small></div>
|
||||
</div>
|
||||
|
||||
<ng-template #trusted>
|
||||
<div tuiCardLarge tuiSurface="floating" class="card">
|
||||
<tui-icon icon="@tui.shield" class="g-positive" [style.font-size.rem]="4" />
|
||||
<h1>Root CA Trusted!</h1>
|
||||
<h1>{{ 'Root CA Trusted!' | i18n }}</h1>
|
||||
<p>
|
||||
You have successfully trusted your server's Root CA and may now log in
|
||||
securely.
|
||||
{{ 'You have successfully trusted your Root CA and may now log in securely.' | i18n }}
|
||||
</p>
|
||||
<button tuiButton iconEnd="@tui.external-link" (click)="launchHttps()">
|
||||
Go to login
|
||||
{{ 'Go to login' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CommonModule, DOCUMENT } from '@angular/common'
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { RELATIVE_URL } from '@start9labs/shared'
|
||||
import { i18nPipe, RELATIVE_URL } from '@start9labs/shared'
|
||||
import { TuiButton, TuiIcon, TuiSurface } from '@taiga-ui/core'
|
||||
import { TuiCardLarge } from '@taiga-ui/layout'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
@@ -11,7 +11,14 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
selector: 'ca-wizard',
|
||||
templateUrl: './ca-wizard.component.html',
|
||||
styleUrls: ['./ca-wizard.component.scss'],
|
||||
imports: [CommonModule, TuiIcon, TuiButton, TuiCardLarge, TuiSurface],
|
||||
imports: [
|
||||
CommonModule,
|
||||
TuiIcon,
|
||||
TuiButton,
|
||||
TuiCardLarge,
|
||||
TuiSurface,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export class CAWizardComponent {
|
||||
private readonly api = inject(ApiService)
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from '@taiga-ui/legacy'
|
||||
import { CAWizardComponent } from './ca-wizard/ca-wizard.component'
|
||||
import { LoginPage } from './login.page'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@@ -29,6 +30,7 @@ const routes: Routes = [
|
||||
TuiTextfieldControllerModule,
|
||||
TuiError,
|
||||
RouterModule.forChild(routes),
|
||||
i18nPipe,
|
||||
],
|
||||
declarations: [LoginPage],
|
||||
})
|
||||
|
||||
@@ -5,18 +5,18 @@
|
||||
<ng-template #notLanHttp>
|
||||
<div tuiCardLarge class="card">
|
||||
<img alt="StartOS Icon" class="logo" src="assets/img/icon.png" />
|
||||
<h1 class="header">Login to StartOS</h1>
|
||||
<h1 class="header">{{'Login to StartOS' | i18n}}</h1>
|
||||
<form (submit)="submit()">
|
||||
<tui-input-password
|
||||
tuiTextfieldIconLeft="@tui.key"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[(ngModel)]="password"
|
||||
(ngModelChange)="error = ''"
|
||||
(ngModelChange)="error = null"
|
||||
>
|
||||
Password
|
||||
{{'Password' | i18n}}
|
||||
</tui-input-password>
|
||||
<tui-error class="error" [error]="error || null" />
|
||||
<button tuiButton class="button">Login</button>
|
||||
<button tuiButton class="button">{{'Login' | i18n}}</button>
|
||||
</form>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Component, Inject, DestroyRef, inject } from '@angular/core'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { AuthService } from 'src/app/services/auth.service'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { LoadingService } from '@start9labs/shared'
|
||||
import { i18nKey, LoadingService } from '@start9labs/shared'
|
||||
import { DOCUMENT } from '@angular/common'
|
||||
|
||||
@Component({
|
||||
@@ -15,7 +15,7 @@ import { DOCUMENT } from '@angular/common'
|
||||
})
|
||||
export class LoginPage {
|
||||
password = ''
|
||||
error = ''
|
||||
error: i18nKey | null = null
|
||||
|
||||
constructor(
|
||||
private readonly router: Router,
|
||||
@@ -27,10 +27,10 @@ export class LoginPage {
|
||||
) {}
|
||||
|
||||
async submit() {
|
||||
this.error = ''
|
||||
this.error = null
|
||||
|
||||
const loader = this.loader
|
||||
.open('Logging in...')
|
||||
.open('Logging in')
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe()
|
||||
|
||||
@@ -50,7 +50,7 @@ export class LoginPage {
|
||||
this.router.navigate([''], { replaceUrl: true })
|
||||
} catch (e: any) {
|
||||
// code 7 is for incorrect password
|
||||
this.error = e.code === 7 ? 'Invalid Password' : e.message
|
||||
this.error = e.code === 7 ? 'Invalid password' : (e.message as i18nKey)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
|
||||
import { TuiConfirmService } from '@taiga-ui/kit'
|
||||
import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus'
|
||||
import { compare, Operation } from 'fast-json-patch'
|
||||
import { Operation } from 'fast-json-patch'
|
||||
import { FormModule } from 'src/app/routes/portal/components/form/form.module'
|
||||
import { InvalidService } from 'src/app/routes/portal/components/form/invalid.service'
|
||||
import { FormService } from 'src/app/services/form.service'
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
[disabled]="!canAdd"
|
||||
(click)="add()"
|
||||
>
|
||||
+ Add
|
||||
+ {{ 'Add' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<tui-error [error]="order | tuiFieldError | async"></tui-error>
|
||||
|
||||
@@ -9,17 +9,16 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||
import { AbstractControl, FormArrayName } from '@angular/forms'
|
||||
import {
|
||||
TUI_ANIMATIONS_SPEED,
|
||||
TuiDialogService,
|
||||
tuiFadeIn,
|
||||
tuiHeightCollapse,
|
||||
tuiParentStop,
|
||||
tuiToAnimationOptions,
|
||||
} from '@taiga-ui/core'
|
||||
import { TUI_CONFIRM } from '@taiga-ui/kit'
|
||||
import { filter } from 'rxjs'
|
||||
import { IST } from '@start9labs/start-sdk'
|
||||
import { FormService } from 'src/app/services/form.service'
|
||||
import { ERRORS } from '../form-group/form-group.component'
|
||||
import { DialogService, i18nKey } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
selector: 'form-array',
|
||||
@@ -39,8 +38,8 @@ export class FormArrayComponent {
|
||||
|
||||
private warned = false
|
||||
private readonly formService = inject(FormService)
|
||||
private readonly dialogs = inject(TuiDialogService)
|
||||
private readonly destroyRef = inject(DestroyRef)
|
||||
private readonly dialog = inject(DialogService)
|
||||
|
||||
get canAdd(): boolean {
|
||||
return (
|
||||
@@ -52,11 +51,15 @@ export class FormArrayComponent {
|
||||
|
||||
add() {
|
||||
if (!this.warned && this.spec.warning) {
|
||||
this.dialogs
|
||||
.open<boolean>(TUI_CONFIRM, {
|
||||
this.dialog
|
||||
.openConfirm<boolean>({
|
||||
label: 'Warning',
|
||||
size: 's',
|
||||
data: { content: this.spec.warning, yes: 'Ok', no: 'Cancel' },
|
||||
data: {
|
||||
content: this.spec.warning as i18nKey,
|
||||
yes: 'Ok',
|
||||
no: 'Cancel',
|
||||
},
|
||||
})
|
||||
.pipe(filter(Boolean), takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe(() => {
|
||||
@@ -70,8 +73,8 @@ export class FormArrayComponent {
|
||||
}
|
||||
|
||||
removeAt(index: number) {
|
||||
this.dialogs
|
||||
.open<boolean>(TUI_CONFIRM, {
|
||||
this.dialog
|
||||
.openConfirm<boolean>({
|
||||
label: 'Confirm',
|
||||
size: 's',
|
||||
data: {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
let-completeWith="completeWith"
|
||||
>
|
||||
{{ spec.warning }}
|
||||
<p *ngIf="immutable">This value cannot be changed once set!</p>
|
||||
<p *ngIf="immutable">{{ 'This value cannot be changed once set' | i18n }}!</p>
|
||||
<div class="buttons">
|
||||
<button
|
||||
tuiButton
|
||||
@@ -25,7 +25,7 @@
|
||||
size="s"
|
||||
(click)="completeWith(true)"
|
||||
>
|
||||
Rollback
|
||||
{{ 'Cancel' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiButton
|
||||
@@ -34,7 +34,7 @@
|
||||
size="s"
|
||||
(click)="completeWith(false)"
|
||||
>
|
||||
Accept
|
||||
{{'Continue' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -9,10 +9,11 @@ import {
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||
import { AbstractTuiNullableControl } from '@taiga-ui/legacy'
|
||||
import { filter } from 'rxjs'
|
||||
import { TuiAlertService, TuiDialogContext } from '@taiga-ui/core'
|
||||
import { TuiDialogContext } from '@taiga-ui/core'
|
||||
import { IST } from '@start9labs/start-sdk'
|
||||
import { ERRORS } from '../form-group/form-group.component'
|
||||
import { FORM_CONTROL_PROVIDERS } from './form-control.providers'
|
||||
import { DialogService, i18nKey } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
selector: 'form-control',
|
||||
@@ -34,7 +35,7 @@ export class FormControlComponent<
|
||||
warned = false
|
||||
focused = false
|
||||
readonly order = ERRORS
|
||||
private readonly alerts = inject(TuiAlertService)
|
||||
private readonly dialog = inject(DialogService)
|
||||
|
||||
get immutable(): boolean {
|
||||
return 'immutable' in this.spec && this.spec.immutable
|
||||
@@ -49,8 +50,8 @@ export class FormControlComponent<
|
||||
const previous = this.value
|
||||
|
||||
if (!this.warned && this.warning) {
|
||||
this.alerts
|
||||
.open<boolean>(this.warning, {
|
||||
this.dialog
|
||||
.openAlert<boolean>(this.warning as unknown as i18nKey, {
|
||||
label: 'Warning',
|
||||
appearance: 'warning',
|
||||
closeable: false,
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
(edited)="value = null"
|
||||
/>
|
||||
} @else {
|
||||
<small>Click or drop file here</small>
|
||||
<small>{{ 'Click or drop file here' | i18n }}</small>
|
||||
}
|
||||
</div>
|
||||
<div class="drop" [class.drop_hidden]="!drop">Drop file here</div>
|
||||
<div class="drop" [class.drop_hidden]="!drop">{{ 'Drop file here' | i18n }}</div>
|
||||
</ng-template>
|
||||
</label>
|
||||
|
||||
@@ -50,6 +50,7 @@ import { FormUnionComponent } from './form-union/form-union.component'
|
||||
import { HintPipe } from './hint.pipe'
|
||||
import { MustachePipe } from './mustache.pipe'
|
||||
import { FilterHiddenPipe } from './filter-hidden.pipe'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -82,6 +83,7 @@ import { FilterHiddenPipe } from './filter-hidden.pipe'
|
||||
TuiAppearance,
|
||||
TuiIcon,
|
||||
TuiNumberFormat,
|
||||
i18nPipe,
|
||||
],
|
||||
declarations: [
|
||||
FormGroupComponent,
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { IST } from '@start9labs/start-sdk'
|
||||
|
||||
@Pipe({
|
||||
name: 'hint',
|
||||
})
|
||||
export class HintPipe implements PipeTransform {
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
transform(spec: Exclude<IST.ValueSpec, IST.ValueSpecHidden>): string {
|
||||
const hint = []
|
||||
|
||||
@@ -13,7 +16,7 @@ export class HintPipe implements PipeTransform {
|
||||
}
|
||||
|
||||
if ('disabled' in spec && typeof spec.disabled === 'string') {
|
||||
hint.push(`Disabled: ${spec.disabled}`)
|
||||
hint.push(`${this.i18n.transform('Disabled')}: ${spec.disabled}`)
|
||||
}
|
||||
|
||||
return hint.join('\n\n')
|
||||
|
||||
@@ -2,7 +2,7 @@ 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 { CopyService } from '@start9labs/shared'
|
||||
import { CopyService, i18nPipe } from '@start9labs/shared'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
@@ -13,7 +13,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
<ng-container *ngIf="server$ | async as server">
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>Version</strong>
|
||||
<strong>{{ 'Version' | i18n }}</strong>
|
||||
<div tuiSubtitle>{{ server.version }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,7 +28,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
iconStart="@tui.copy"
|
||||
(click)="copyService.copy(gitHash)"
|
||||
>
|
||||
Copy
|
||||
{{ 'Copy' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div tuiCell>
|
||||
@@ -42,7 +42,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
iconStart="@tui.copy"
|
||||
(click)="copyService.copy(server.caFingerprint)"
|
||||
>
|
||||
Copy
|
||||
{{ 'Copy' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
@@ -50,7 +50,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
styles: ['[tuiCell] { padding-inline: 0 }'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CommonModule, TuiTitle, TuiButton, TuiCell],
|
||||
imports: [CommonModule, TuiTitle, TuiButton, TuiCell, i18nPipe],
|
||||
})
|
||||
export class AboutComponent {
|
||||
readonly server$ = inject<PatchDB<DataModel>>(PatchDB).watch$('serverInfo')
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDataList,
|
||||
TuiDialogOptions,
|
||||
TuiDropdown,
|
||||
TuiIcon,
|
||||
} from '@taiga-ui/core'
|
||||
import { TUI_CONFIRM, TuiConfirmData } from '@taiga-ui/kit'
|
||||
DialogService,
|
||||
ErrorService,
|
||||
i18nPipe,
|
||||
LoadingService,
|
||||
} from '@start9labs/shared'
|
||||
import { TuiButton, TuiDataList, TuiDropdown, TuiIcon } from '@taiga-ui/core'
|
||||
import { filter } from 'rxjs'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { AuthService } from 'src/app/services/auth.service'
|
||||
@@ -33,13 +30,13 @@ import { ABOUT } from './about.component'
|
||||
@if (status().status !== 'success') {
|
||||
<div class="status">
|
||||
<tui-icon [icon]="status().icon" />
|
||||
{{ status().message }}
|
||||
{{ status().message | i18n }}
|
||||
</div>
|
||||
}
|
||||
<tui-data-list [style.width.rem]="13">
|
||||
<tui-opt-group>
|
||||
<button tuiOption iconStart="@tui.info" (click)="about()">
|
||||
About this server
|
||||
{{ 'About this server' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
<tui-opt-group label="">
|
||||
@@ -51,7 +48,7 @@ import { ABOUT } from './about.component'
|
||||
[iconStart]="link.icon"
|
||||
[href]="link.href"
|
||||
>
|
||||
{{ link.name }}
|
||||
{{ link.name | i18n }}
|
||||
</a>
|
||||
}
|
||||
</tui-opt-group>
|
||||
@@ -62,26 +59,26 @@ import { ABOUT } from './about.component'
|
||||
routerLink="/portal/system"
|
||||
(click)="open = false"
|
||||
>
|
||||
System Settings
|
||||
{{ 'System Settings' | i18n }}
|
||||
</a>
|
||||
</tui-opt-group>
|
||||
<tui-opt-group label="">
|
||||
<button
|
||||
tuiOption
|
||||
iconStart="@tui.refresh-cw"
|
||||
(click)="promptPower('Restart')"
|
||||
(click)="promptPower('restart')"
|
||||
>
|
||||
Restart
|
||||
{{ 'Restart' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
iconStart="@tui.power"
|
||||
(click)="promptPower('Shutdown')"
|
||||
(click)="promptPower('shutdown')"
|
||||
>
|
||||
Shutdown
|
||||
{{ 'Shutdown' | i18n }}
|
||||
</button>
|
||||
<button tuiOption iconStart="@tui.log-out" (click)="logout()">
|
||||
Logout
|
||||
{{ 'Logout' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
</tui-data-list>
|
||||
@@ -115,14 +112,14 @@ import { ABOUT } from './about.component'
|
||||
host: { '[class._open]': 'open' },
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiDropdown, TuiDataList, TuiButton, TuiIcon, RouterLink],
|
||||
imports: [TuiDropdown, TuiDataList, TuiButton, TuiIcon, RouterLink, i18nPipe],
|
||||
})
|
||||
export class HeaderMenuComponent {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly auth = inject(AuthService)
|
||||
private readonly dialogs = inject(TuiResponsiveDialogService)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly dialog = inject(DialogService)
|
||||
|
||||
open = false
|
||||
|
||||
@@ -130,19 +127,41 @@ export class HeaderMenuComponent {
|
||||
readonly status = inject(STATUS)
|
||||
|
||||
about() {
|
||||
this.dialogs.open(ABOUT, { label: 'About this server' }).subscribe()
|
||||
this.dialog.openComponent(ABOUT, { label: 'About this server' }).subscribe()
|
||||
}
|
||||
|
||||
async promptPower(action: 'Restart' | 'Shutdown') {
|
||||
this.dialogs
|
||||
.open(TUI_CONFIRM, getOptions(action))
|
||||
async promptPower(action: 'restart' | 'shutdown') {
|
||||
this.dialog
|
||||
.openConfirm(
|
||||
action === 'restart'
|
||||
? {
|
||||
label: 'Restart',
|
||||
size: 's',
|
||||
data: {
|
||||
content:
|
||||
'Are you sure you want to restart your server? It can take several minutes to come back online.',
|
||||
yes: 'Restart',
|
||||
no: 'Cancel',
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: 'Warning',
|
||||
size: 's',
|
||||
data: {
|
||||
content:
|
||||
'Are you sure you want to power down your server? This can take several minutes, and your server will not come back online automatically. To power on again, You will need to physically unplug your server and plug it back in.',
|
||||
yes: 'Shutdown',
|
||||
no: 'Cancel',
|
||||
},
|
||||
},
|
||||
)
|
||||
.pipe(filter(Boolean))
|
||||
.subscribe(async () => {
|
||||
const loader = this.loader.open(`Beginning ${action}...`).subscribe()
|
||||
const loader = this.loader.open(`Beginning ${action}`).subscribe()
|
||||
|
||||
try {
|
||||
await this.api[
|
||||
action === 'Restart' ? 'restartServer' : 'shutdownServer'
|
||||
action === 'restart' ? 'restartServer' : 'shutdownServer'
|
||||
]({})
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
@@ -157,29 +176,3 @@ export class HeaderMenuComponent {
|
||||
this.auth.setUnverified()
|
||||
}
|
||||
}
|
||||
|
||||
function getOptions(
|
||||
operation: 'Restart' | 'Shutdown',
|
||||
): Partial<TuiDialogOptions<TuiConfirmData>> {
|
||||
return operation === 'Restart'
|
||||
? {
|
||||
label: 'Restart',
|
||||
size: 's',
|
||||
data: {
|
||||
content:
|
||||
'Are you sure you want to restart your server? It can take several minutes to come back online.',
|
||||
yes: 'Restart',
|
||||
no: 'Cancel',
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: 'Warning',
|
||||
size: 's',
|
||||
data: {
|
||||
content:
|
||||
'Are you sure you want to power down your server? This can take several minutes, and your server will not come back online automatically. To power on again, You will need to physically unplug your server and plug it back in',
|
||||
yes: 'Shutdown',
|
||||
no: 'Cancel',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import {
|
||||
TUI_ANIMATIONS_SPEED,
|
||||
TuiButton,
|
||||
tuiFadeIn,
|
||||
TuiHint,
|
||||
TuiIcon,
|
||||
@@ -40,7 +40,7 @@ import { getMenu } from 'src/app/utils/system-utilities'
|
||||
}
|
||||
<tui-icon [icon]="item.icon" />
|
||||
</tui-badged-content>
|
||||
<span>{{ item.name }}</span>
|
||||
<span>{{ item.name | i18n }}</span>
|
||||
</a>
|
||||
}
|
||||
`,
|
||||
@@ -178,11 +178,11 @@ import { getMenu } from 'src/app/utils/system-utilities'
|
||||
imports: [
|
||||
TuiBadgeNotification,
|
||||
TuiBadgedContent,
|
||||
TuiButton,
|
||||
RouterLink,
|
||||
TuiIcon,
|
||||
RouterLinkActive,
|
||||
TuiHint,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export class HeaderNavigationComponent {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AsyncPipe } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiIcon } from '@taiga-ui/core'
|
||||
import { STATUS } from 'src/app/services/status.service'
|
||||
|
||||
@@ -16,7 +16,7 @@ import { STATUS } from 'src/app/services/status.service'
|
||||
[attr.data-status]="status().status"
|
||||
/>
|
||||
</span>
|
||||
<span>{{ status().message }}</span>
|
||||
<span>{{ status().message | i18n }}</span>
|
||||
`,
|
||||
styles: [
|
||||
`
|
||||
@@ -48,7 +48,7 @@ import { STATUS } from 'src/app/services/status.service'
|
||||
],
|
||||
host: { '[class._connected]': 'status().status === "success"' },
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiIcon, AsyncPipe],
|
||||
imports: [TuiIcon, i18nPipe],
|
||||
})
|
||||
export class HeaderStatusComponent {
|
||||
readonly status = inject(STATUS)
|
||||
|
||||
@@ -4,8 +4,12 @@ import {
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { CopyService } from '@start9labs/shared'
|
||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||
import {
|
||||
CopyService,
|
||||
DialogService,
|
||||
i18nKey,
|
||||
i18nPipe,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
TuiButton,
|
||||
tuiButtonOptionsProvider,
|
||||
@@ -30,18 +34,18 @@ import { InterfaceComponent } from './interface.component'
|
||||
rel="noreferrer"
|
||||
[href]="actions()"
|
||||
>
|
||||
Launch UI
|
||||
{{ 'Launch UI' | i18n }}
|
||||
</a>
|
||||
}
|
||||
<button tuiIconButton iconStart="@tui.qr-code" (click)="showQR()">
|
||||
Show QR
|
||||
{{ 'Show QR' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiIconButton
|
||||
iconStart="@tui.copy"
|
||||
(click)="copyService.copy(actions())"
|
||||
>
|
||||
Copy URL
|
||||
{{ 'Copy URL' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="mobile">
|
||||
@@ -51,7 +55,7 @@ import { InterfaceComponent } from './interface.component'
|
||||
tuiDropdownOpen
|
||||
[tuiDropdown]="dropdown"
|
||||
>
|
||||
Actions
|
||||
{{ 'Actions' | i18n }}
|
||||
<ng-template #dropdown let-close>
|
||||
<tui-data-list>
|
||||
<tui-opt-group>
|
||||
@@ -63,17 +67,17 @@ import { InterfaceComponent } from './interface.component'
|
||||
rel="noreferrer"
|
||||
[href]="actions()"
|
||||
>
|
||||
Launch UI
|
||||
{{ 'Launch UI' | i18n }}
|
||||
</a>
|
||||
<button tuiOption iconStart="@tui.qr-code" (click)="showQR()">
|
||||
Show QR
|
||||
{{ 'Show QR' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
iconStart="@tui.copy"
|
||||
(click)="copyService.copy(actions()); close()"
|
||||
>
|
||||
Copy URL
|
||||
{{ 'Copy URL' | i18n }}
|
||||
</button>
|
||||
}
|
||||
</tui-opt-group>
|
||||
@@ -105,22 +109,22 @@ import { InterfaceComponent } from './interface.component'
|
||||
}
|
||||
}
|
||||
`,
|
||||
imports: [TuiButton, TuiDropdown, TuiDataList],
|
||||
imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe],
|
||||
providers: [tuiButtonOptionsProvider({ appearance: 'icon' })],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class InterfaceActionsComponent {
|
||||
readonly dialogs = inject(TuiResponsiveDialogService)
|
||||
readonly dialog = inject(DialogService)
|
||||
readonly copyService = inject(CopyService)
|
||||
readonly interface = inject(InterfaceComponent)
|
||||
|
||||
readonly actions = input.required<string>()
|
||||
|
||||
showQR() {
|
||||
this.dialogs
|
||||
.open(new PolymorpheusComponent(QRModal), {
|
||||
this.dialog
|
||||
.openComponent(new PolymorpheusComponent(QRModal), {
|
||||
size: 'auto',
|
||||
label: 'Interface URL',
|
||||
label: this.actions() as i18nKey,
|
||||
data: this.actions(),
|
||||
})
|
||||
.subscribe()
|
||||
|
||||
@@ -6,18 +6,21 @@ import {
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import {
|
||||
DialogService,
|
||||
ErrorService,
|
||||
i18nPipe,
|
||||
LoadingService,
|
||||
} from '@start9labs/shared'
|
||||
import { ISB, utils } from '@start9labs/start-sdk'
|
||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||
import {
|
||||
TuiAppearance,
|
||||
TuiButton,
|
||||
TuiDataList,
|
||||
TuiDialogOptions,
|
||||
TuiIcon,
|
||||
TuiLink,
|
||||
} from '@taiga-ui/core'
|
||||
import { TUI_CONFIRM, TuiTooltip } from '@taiga-ui/kit'
|
||||
import { TuiTooltip } from '@taiga-ui/kit'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { defaultIfEmpty, firstValueFrom, map } from 'rxjs'
|
||||
import {
|
||||
@@ -50,15 +53,17 @@ type ClearnetForm = {
|
||||
Clearnet
|
||||
<tui-icon [tuiTooltip]="tooltip" />
|
||||
<ng-template #tooltip>
|
||||
Add a clearnet address to expose this interface on the Internet.
|
||||
Clearnet addresses are fully public and not anonymous.
|
||||
{{
|
||||
'Add a clearnet address to expose this interface on the Internet. Clearnet addresses are fully public and not anonymous.'
|
||||
| i18n
|
||||
}}
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/latest/user-manual/interface-addresses#clearnet"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn More
|
||||
{{ 'Learn more' | i18n }}
|
||||
</a>
|
||||
</ng-template>
|
||||
<button
|
||||
@@ -68,14 +73,16 @@ type ClearnetForm = {
|
||||
[style.margin-inline-start]="'auto'"
|
||||
(click)="toggle()"
|
||||
>
|
||||
Make {{ isPublic() ? 'private' : 'public' }}
|
||||
{{ isPublic() ? ('Make private' | i18n) : ('Make public' | i18n) }}
|
||||
</button>
|
||||
@if (clearnet().length) {
|
||||
<button tuiButton iconStart="@tui.plus" (click)="add()">Add</button>
|
||||
<button tuiButton iconStart="@tui.plus" (click)="add()">
|
||||
{{ 'Add' | i18n }}
|
||||
</button>
|
||||
}
|
||||
</header>
|
||||
@if (clearnet().length) {
|
||||
<table [appTable]="['ACME', 'URL', '']">
|
||||
<table [appTable]="['ACME', 'URL', null]">
|
||||
@for (address of clearnet(); track $index) {
|
||||
<tr>
|
||||
<td [style.width.rem]="12">{{ address.acme | acme }}</td>
|
||||
@@ -87,7 +94,7 @@ type ClearnetForm = {
|
||||
[style.margin-inline-end.rem]="0.5"
|
||||
(click)="remove(address)"
|
||||
>
|
||||
Delete
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
@@ -95,7 +102,7 @@ type ClearnetForm = {
|
||||
iconStart="@tui.trash"
|
||||
(click)="remove(address)"
|
||||
>
|
||||
Delete
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -103,9 +110,9 @@ type ClearnetForm = {
|
||||
</table>
|
||||
} @else {
|
||||
<app-placeholder icon="@tui.app-window">
|
||||
No public addresses
|
||||
{{ 'No public addresses' | i18n }}
|
||||
<button tuiButton iconStart="@tui.plus" (click)="add()">
|
||||
Add Domain
|
||||
{{ 'Add domain' | i18n }}
|
||||
</button>
|
||||
</app-placeholder>
|
||||
}
|
||||
@@ -123,11 +130,12 @@ type ClearnetForm = {
|
||||
MaskPipe,
|
||||
AcmePipe,
|
||||
InterfaceActionsComponent,
|
||||
i18nPipe,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class InterfaceClearnetComponent {
|
||||
private readonly dialogs = inject(TuiResponsiveDialogService)
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly formDialog = inject(FormDialogService)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
@@ -146,8 +154,8 @@ export class InterfaceClearnetComponent {
|
||||
|
||||
async remove({ url }: AddressDetails) {
|
||||
const confirm = await firstValueFrom(
|
||||
this.dialogs
|
||||
.open(TUI_CONFIRM, { label: 'Are you sure?', size: 's' })
|
||||
this.dialog
|
||||
.openConfirm({ label: 'Are you sure?', size: 's' })
|
||||
.pipe(defaultIfEmpty(false)),
|
||||
)
|
||||
|
||||
@@ -205,8 +213,8 @@ export class InterfaceClearnetComponent {
|
||||
}
|
||||
|
||||
async add() {
|
||||
const options: Partial<TuiDialogOptions<FormContext<ClearnetForm>>> = {
|
||||
label: 'Select Domain/Subdomain',
|
||||
this.formDialog.open<FormContext<ClearnetForm>>(FormComponent, {
|
||||
label: 'Select Domain',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(
|
||||
ISB.InputSpec.of({
|
||||
@@ -240,12 +248,11 @@ export class InterfaceClearnetComponent {
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
this.formDialog.open(FormComponent, options)
|
||||
})
|
||||
}
|
||||
|
||||
private async save(domainInfo: ClearnetForm): Promise<boolean> {
|
||||
const loader = this.loader.open('Saving...').subscribe()
|
||||
const loader = this.loader.open('Saving').subscribe()
|
||||
|
||||
const { domain, acme } = domainInfo
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, input, Input } from '@angular/core'
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||
import { tuiButtonOptionsProvider } from '@taiga-ui/core'
|
||||
import { InterfaceClearnetComponent } from 'src/app/routes/portal/components/interfaces/clearnet.component'
|
||||
import { InterfaceLocalComponent } from 'src/app/routes/portal/components/interfaces/local.component'
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { T, utils } from '@start9labs/start-sdk'
|
||||
import { TuiDialogOptions } from '@taiga-ui/core'
|
||||
import { TuiConfirmData } from '@taiga-ui/kit'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
|
||||
export abstract class AddressesService {
|
||||
@@ -9,16 +7,6 @@ export abstract class AddressesService {
|
||||
abstract remove(): Promise<void>
|
||||
}
|
||||
|
||||
export const REMOVE: Partial<TuiDialogOptions<TuiConfirmData>> = {
|
||||
label: 'Confirm',
|
||||
size: 's',
|
||||
data: {
|
||||
content: 'Remove clearnet address?',
|
||||
yes: 'Remove',
|
||||
no: 'Cancel',
|
||||
},
|
||||
}
|
||||
|
||||
export function getAddresses(
|
||||
serviceInterface: T.ServiceInterface,
|
||||
host: T.Host,
|
||||
|
||||
@@ -5,28 +5,31 @@ import { TableComponent } from 'src/app/routes/portal/components/table.component
|
||||
import { InterfaceActionsComponent } from './actions.component'
|
||||
import { AddressDetails } from './interface.utils'
|
||||
import { MaskPipe } from './mask.pipe'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'section[local]',
|
||||
template: `
|
||||
<header>
|
||||
Local
|
||||
{{ 'Local' | i18n }}
|
||||
<tui-icon [tuiTooltip]="tooltip" />
|
||||
<ng-template #tooltip>
|
||||
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
|
||||
}}
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/latest/user-manual/interface-addresses#local"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn More
|
||||
{{ 'Learn More' | i18n }}
|
||||
</a>
|
||||
</ng-template>
|
||||
</header>
|
||||
<table [appTable]="['Network Interface', 'URL', '']">
|
||||
<table [appTable]="['Network Interface', 'URL', null]">
|
||||
@for (address of local(); track $index) {
|
||||
<tr>
|
||||
<td [style.width.rem]="12">{{ address.label }}</td>
|
||||
@@ -44,6 +47,7 @@ import { MaskPipe } from './mask.pipe'
|
||||
TableComponent,
|
||||
InterfaceActionsComponent,
|
||||
MaskPipe,
|
||||
i18nPipe,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiBadge } from '@taiga-ui/kit'
|
||||
|
||||
@Component({
|
||||
@@ -12,11 +13,11 @@ import { TuiBadge } from '@taiga-ui/kit'
|
||||
[style.margin]="'0 0.25rem -0.25rem'"
|
||||
[appearance]="public() ? 'positive' : 'negative'"
|
||||
>
|
||||
{{ public() ? 'Public' : 'Private' }}
|
||||
{{ public() ? ('Public' | i18n) : ('Private' | i18n) }}
|
||||
</tui-badge>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiBadge],
|
||||
imports: [TuiBadge, i18nPipe],
|
||||
})
|
||||
export class InterfaceStatusComponent {
|
||||
readonly public = input(false)
|
||||
|
||||
@@ -4,23 +4,21 @@ import {
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import {
|
||||
DialogService,
|
||||
ErrorService,
|
||||
i18nPipe,
|
||||
LoadingService,
|
||||
} from '@start9labs/shared'
|
||||
import { ISB, utils } from '@start9labs/start-sdk'
|
||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||
import {
|
||||
TuiAppearance,
|
||||
TuiButton,
|
||||
TuiDialogOptions,
|
||||
TuiIcon,
|
||||
TuiLink,
|
||||
TuiOption,
|
||||
} from '@taiga-ui/core'
|
||||
import {
|
||||
TUI_CONFIRM,
|
||||
TuiFade,
|
||||
TuiFluidTypography,
|
||||
TuiTooltip,
|
||||
} from '@taiga-ui/kit'
|
||||
import { TuiFade, TuiFluidTypography, TuiTooltip } from '@taiga-ui/kit'
|
||||
import { defaultIfEmpty, firstValueFrom } from 'rxjs'
|
||||
import {
|
||||
FormComponent,
|
||||
@@ -48,15 +46,17 @@ type OnionForm = {
|
||||
Tor
|
||||
<tui-icon [tuiTooltip]="tooltip" />
|
||||
<ng-template #tooltip>
|
||||
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
|
||||
}}
|
||||
<a
|
||||
tuiLink
|
||||
href="https://docs.start9.com/latest/user-manual/interface-addresses#tor"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn More
|
||||
{{ 'Learn More' | i18n }}
|
||||
</a>
|
||||
</ng-template>
|
||||
@if (tor().length) {
|
||||
@@ -66,12 +66,12 @@ type OnionForm = {
|
||||
[style.margin-inline-start]="'auto'"
|
||||
(click)="add()"
|
||||
>
|
||||
Add
|
||||
{{ 'Add' | i18n }}
|
||||
</button>
|
||||
}
|
||||
</header>
|
||||
@if (tor().length) {
|
||||
<table [appTable]="['Protocol', 'URL', '']">
|
||||
<table [appTable]="['Protocol', 'URL', null]">
|
||||
@for (address of tor(); track $index) {
|
||||
<tr>
|
||||
<td [style.width.rem]="12">{{ address.label }}</td>
|
||||
@@ -87,7 +87,7 @@ type OnionForm = {
|
||||
[style.margin-inline-end.rem]="0.5"
|
||||
(click)="remove(address)"
|
||||
>
|
||||
Delete
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
@@ -95,7 +95,7 @@ type OnionForm = {
|
||||
iconStart="@tui.trash"
|
||||
(click)="remove(address)"
|
||||
>
|
||||
Delete
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -103,8 +103,10 @@ type OnionForm = {
|
||||
</table>
|
||||
} @else {
|
||||
<app-placeholder icon="@tui.app-window">
|
||||
No Tor addresses available
|
||||
<button tuiButton iconStart="@tui.plus" (click)="add()">Add</button>
|
||||
{{ 'No onion addresses' | i18n }}
|
||||
<button tuiButton iconStart="@tui.plus" (click)="add()">
|
||||
{{ 'Add' | i18n }}
|
||||
</button>
|
||||
</app-placeholder>
|
||||
}
|
||||
`,
|
||||
@@ -128,23 +130,25 @@ type OnionForm = {
|
||||
InterfaceActionsComponent,
|
||||
TuiFade,
|
||||
TuiFluidTypography,
|
||||
i18nPipe,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class InterfaceTorComponent {
|
||||
private readonly dialogs = inject(TuiResponsiveDialogService)
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly formDialog = inject(FormDialogService)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly interface = inject(InterfaceComponent)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
readonly tor = input.required<readonly AddressDetails[]>()
|
||||
|
||||
async remove({ url }: AddressDetails) {
|
||||
const confirm = await firstValueFrom(
|
||||
this.dialogs
|
||||
.open(TUI_CONFIRM, { label: 'Are you sure?', size: 's' })
|
||||
this.dialog
|
||||
.openConfirm({ label: 'Are you sure?', size: 's' })
|
||||
.pipe(defaultIfEmpty(false)),
|
||||
)
|
||||
|
||||
@@ -175,15 +179,16 @@ export class InterfaceTorComponent {
|
||||
}
|
||||
|
||||
async add() {
|
||||
const options: Partial<TuiDialogOptions<FormContext<OnionForm>>> = {
|
||||
label: 'New Tor Address',
|
||||
this.formDialog.open<FormContext<OnionForm>>(FormComponent, {
|
||||
label: 'New Onion Address',
|
||||
data: {
|
||||
spec: await configBuilderToSpec(
|
||||
ISB.InputSpec.of({
|
||||
key: ISB.Value.text({
|
||||
name: 'Private Key (optional)',
|
||||
description:
|
||||
name: this.i18n.transform('Private Key (optional)')!,
|
||||
description: this.i18n.transform(
|
||||
'Optionally provide a base64-encoded ed25519 private key for generating the Tor V3 (.onion) address. If not provided, a random key will be generated and used.',
|
||||
),
|
||||
required: false,
|
||||
default: null,
|
||||
patterns: [utils.Patterns.base64],
|
||||
@@ -192,17 +197,16 @@ export class InterfaceTorComponent {
|
||||
),
|
||||
buttons: [
|
||||
{
|
||||
text: 'Save',
|
||||
text: this.i18n.transform('Save')!,
|
||||
handler: async value => this.save(value),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
this.formDialog.open(FormComponent, options)
|
||||
})
|
||||
}
|
||||
|
||||
private async save(form: OnionForm): Promise<boolean> {
|
||||
const loader = this.loader.open('Saving...').subscribe()
|
||||
const loader = this.loader.open('Saving').subscribe()
|
||||
|
||||
try {
|
||||
let onion = form.key
|
||||
|
||||
@@ -24,7 +24,7 @@ export class LogsDownloadDirective {
|
||||
|
||||
@HostListener('click')
|
||||
async download() {
|
||||
const loader = this.loader.open('Processing 10,000 logs...').subscribe()
|
||||
const loader = this.loader.open('Processing 10,000 logs').subscribe()
|
||||
|
||||
try {
|
||||
const { entries } = await this.logsDownload({
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
(logsFetch)="onPrevious($event)"
|
||||
>
|
||||
@if (loading) {
|
||||
<tui-loader textContent="Loading older logs" />
|
||||
<tui-loader [textContent]="'Loading older logs' | i18n" />
|
||||
}
|
||||
</section>
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
<p class="loading-dots" [attr.data-status]="status$.value">
|
||||
{{
|
||||
status$.value === 'reconnecting'
|
||||
? 'Reconnecting'
|
||||
: 'Waiting for network connectivity'
|
||||
? ('Reconnecting' | i18n)
|
||||
: ('Waiting for network connectivity' | i18n)
|
||||
}}
|
||||
</p>
|
||||
}
|
||||
</section>
|
||||
} @else {
|
||||
<tui-loader textContent="Loading logs" [style.margin-top.rem]="5" />
|
||||
<tui-loader [textContent]="'Loading logs' | i18n" [style.margin-top.rem]="5" />
|
||||
}
|
||||
|
||||
<section
|
||||
@@ -53,7 +53,7 @@
|
||||
iconStart="@tui.circle-arrow-down"
|
||||
(click)="setScroll(true); scrollToBottom()"
|
||||
>
|
||||
Scroll to bottom
|
||||
{{'Scroll to bottom' | i18n}}
|
||||
</button>
|
||||
<button
|
||||
tuiButton
|
||||
@@ -61,6 +61,6 @@
|
||||
iconStart="@tui.download"
|
||||
[logsDownload]="fetchLogs"
|
||||
>
|
||||
Download
|
||||
{{'Download' | i18n}}
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
WaIntersectionObserver,
|
||||
} from '@ng-web-apis/intersection-observer'
|
||||
import { WaMutationObserver } from '@ng-web-apis/mutation-observer'
|
||||
import { FetchLogsReq, FetchLogsRes } from '@start9labs/shared'
|
||||
import { FetchLogsReq, FetchLogsRes, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiLoader, TuiScrollbar, TuiButton } from '@taiga-ui/core'
|
||||
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
||||
import { RR } from 'src/app/services/api/api.types'
|
||||
@@ -30,6 +30,7 @@ import { BehaviorSubject } from 'rxjs'
|
||||
LogsDownloadDirective,
|
||||
LogsFetchDirective,
|
||||
LogsPipe,
|
||||
i18nPipe,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||
import { convertAnsi, Log, toLocalIsoString } from '@start9labs/shared'
|
||||
import {
|
||||
convertAnsi,
|
||||
i18nPipe,
|
||||
Log,
|
||||
toLocalIsoString,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
bufferTime,
|
||||
catchError,
|
||||
@@ -30,6 +35,7 @@ export class LogsPipe implements PipeTransform {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly logs = inject(LogsComponent)
|
||||
private readonly connection = inject(ConnectionService)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
transform(
|
||||
followLogs: (
|
||||
@@ -40,7 +46,7 @@ export class LogsPipe implements PipeTransform {
|
||||
this.logs.status$.pipe(
|
||||
skipWhile(value => value === 'connected'),
|
||||
filter(value => value === 'connected'),
|
||||
map(() => getMessage(true)),
|
||||
map(() => this.getMessage(true)),
|
||||
),
|
||||
defer(() => followLogs(this.options)).pipe(
|
||||
tap(r => this.logs.setCursor(r.startCursor)),
|
||||
@@ -62,7 +68,7 @@ export class LogsPipe implements PipeTransform {
|
||||
filter(Boolean),
|
||||
take(1),
|
||||
ignoreElements(),
|
||||
startWith(getMessage(false)),
|
||||
startWith(this.getMessage(false)),
|
||||
),
|
||||
),
|
||||
repeat(),
|
||||
@@ -70,15 +76,15 @@ export class LogsPipe implements PipeTransform {
|
||||
)
|
||||
}
|
||||
|
||||
private getMessage(success: boolean): string {
|
||||
return `<p style="color: ${
|
||||
success ? 'var(--tui-status-positive)' : 'var(--tui-status-negative)'
|
||||
}; text-align: center;">${this.i18n.transform(
|
||||
success ? 'Reconnected' : 'Disconnected',
|
||||
)} at ${toLocalIsoString(new Date())}</p>`
|
||||
}
|
||||
|
||||
private get options() {
|
||||
return this.logs.status$.value === 'connected' ? { limit: 400 } : {}
|
||||
}
|
||||
}
|
||||
|
||||
function getMessage(success: boolean): string {
|
||||
return `<p style="color: ${
|
||||
success ? 'var(--tui-status-positive)' : 'var(--tui-status-negative)'
|
||||
}; text-align: center;">${
|
||||
success ? 'Reconnected' : 'Disconnected'
|
||||
} at ${toLocalIsoString(new Date())}</p>`
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||
import { i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -7,7 +8,7 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||
<thead>
|
||||
<tr>
|
||||
@for (header of appTable(); track $index) {
|
||||
<th>{{ header }}</th>
|
||||
<th>{{ header | i18n }}</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -20,7 +21,8 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||
`,
|
||||
host: { class: 'g-table' },
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [i18nPipe],
|
||||
})
|
||||
export class TableComponent {
|
||||
readonly appTable = input.required<readonly string[]>()
|
||||
readonly appTable = input.required<Array<i18nKey | null>>()
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiResponsiveDialogService, TuiTabBar } from '@taiga-ui/addon-mobile'
|
||||
import { TuiIcon } from '@taiga-ui/core'
|
||||
import { TuiBadgeNotification } from '@taiga-ui/kit'
|
||||
@@ -27,7 +28,7 @@ const FILTER = ['/portal/services', '/portal/system', '/portal/marketplace']
|
||||
routerLinkActive
|
||||
(isActiveChange)="update()"
|
||||
>
|
||||
Services
|
||||
{{ 'Services' | i18n }}
|
||||
</a>
|
||||
<a
|
||||
tuiTabBarItem
|
||||
@@ -36,7 +37,7 @@ const FILTER = ['/portal/services', '/portal/system', '/portal/marketplace']
|
||||
routerLinkActive
|
||||
(isActiveChange)="update()"
|
||||
>
|
||||
Marketplace
|
||||
{{ 'Marketplace' | i18n }}
|
||||
</a>
|
||||
<a
|
||||
tuiTabBarItem
|
||||
@@ -46,7 +47,7 @@ const FILTER = ['/portal/services', '/portal/system', '/portal/marketplace']
|
||||
[badge]="badge()"
|
||||
(isActiveChange)="update()"
|
||||
>
|
||||
System
|
||||
{{ 'System' | i18n }}
|
||||
</a>
|
||||
<button
|
||||
tuiTabBarItem
|
||||
@@ -54,7 +55,7 @@ const FILTER = ['/portal/services', '/portal/system', '/portal/marketplace']
|
||||
(click)="more(content)"
|
||||
[badge]="all()"
|
||||
>
|
||||
More
|
||||
{{ 'More' | i18n }}
|
||||
<ng-template #content let-observer>
|
||||
@for (item of menu; track $index) {
|
||||
<a
|
||||
@@ -114,6 +115,7 @@ const FILTER = ['/portal/services', '/portal/system', '/portal/marketplace']
|
||||
TuiTabBar,
|
||||
TuiBadgeNotification,
|
||||
TuiIcon,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export class TabsComponent {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
Input,
|
||||
OnInit,
|
||||
} from '@angular/core'
|
||||
import { TuiNotification } from '@taiga-ui/core'
|
||||
import { getValueByPointer, Operation } from 'fast-json-patch'
|
||||
import { isObject } from '@start9labs/shared'
|
||||
import { i18nPipe, isObject } from '@start9labs/shared'
|
||||
import { tuiIsNumber } from '@taiga-ui/cdk'
|
||||
import { CommonModule } from '@angular/common'
|
||||
|
||||
@@ -14,14 +15,14 @@ import { CommonModule } from '@angular/common'
|
||||
selector: 'action-request-info',
|
||||
template: `
|
||||
<tui-notification *ngIf="diff.length">
|
||||
The following modifications were made:
|
||||
{{ 'The following modifications were made' | i18n }}:
|
||||
<ul>
|
||||
<li *ngFor="let d of diff" [innerHTML]="d"></li>
|
||||
</ul>
|
||||
</tui-notification>
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [CommonModule, TuiNotification],
|
||||
imports: [CommonModule, TuiNotification, i18nPipe],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styles: [
|
||||
`
|
||||
@@ -38,6 +39,8 @@ export class ActionRequestInfoComponent implements OnInit {
|
||||
@Input()
|
||||
operations: Operation[] = []
|
||||
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
diff: string[] = []
|
||||
|
||||
ngOnInit() {
|
||||
@@ -65,13 +68,13 @@ export class ActionRequestInfoComponent implements OnInit {
|
||||
private getMessage(operation: Operation): string {
|
||||
switch (operation.op) {
|
||||
case 'add':
|
||||
return `added ${this.getNewValue(operation.value)}`
|
||||
return `${this.i18n.transform('added')} ${this.getNewValue(operation.value)}`
|
||||
case 'remove':
|
||||
return `removed ${this.getOldValue(operation.path)}`
|
||||
return `${this.i18n.transform('removed')} ${this.getOldValue(operation.path)}`
|
||||
case 'replace':
|
||||
return `changed from ${this.getOldValue(
|
||||
return `${this.i18n.transform('changed from')} ${this.getOldValue(
|
||||
operation.path,
|
||||
)} to ${this.getNewValue(operation.value)}`
|
||||
)} ${this.i18n.transform('to')} ${this.getNewValue(operation.value)}`
|
||||
default:
|
||||
return `Unknown operation` // unreachable
|
||||
}
|
||||
@@ -82,9 +85,9 @@ export class ActionRequestInfoComponent implements OnInit {
|
||||
if (['string', 'number', 'boolean'].includes(typeof val)) {
|
||||
return val
|
||||
} else if (isObject(val)) {
|
||||
return 'entry'
|
||||
return this.i18n.transform('entry')!
|
||||
} else {
|
||||
return 'list'
|
||||
return this.i18n.transform('list')!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,9 +95,9 @@ export class ActionRequestInfoComponent implements OnInit {
|
||||
if (['string', 'number', 'boolean'].includes(typeof val)) {
|
||||
return val
|
||||
} else if (isObject(val)) {
|
||||
return 'new entry'
|
||||
return this.i18n.transform('new entry')!
|
||||
} else {
|
||||
return 'new list'
|
||||
return this.i18n.transform('new list')!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { TUI_TRUE_HANDLER, TUI_FALSE_HANDLER } from '@taiga-ui/cdk'
|
||||
import { TuiDialogService, TuiIcon, TuiLink, TuiButton } from '@taiga-ui/core'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { from } from 'rxjs'
|
||||
import { REPORT } from 'src/app/components/report.component'
|
||||
import { REPORT } from 'src/app/components/backup-report.component'
|
||||
import { BackupRun } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { DurationPipe } from '../pipes/duration.pipe'
|
||||
@@ -206,7 +206,7 @@ export class BackupsHistoryModal {
|
||||
}
|
||||
|
||||
async delete() {
|
||||
const loader = this.loader.open('Deleting...').subscribe()
|
||||
const loader = this.loader.open('Deleting').subscribe()
|
||||
const ids = this.selected
|
||||
.filter(Boolean)
|
||||
.map((_, i) => this.runs()?.[i].id || '')
|
||||
|
||||
@@ -202,7 +202,7 @@ export class BackupsJobsModal implements OnInit {
|
||||
.open(TUI_CONFIRM, PROMPT_OPTIONS)
|
||||
.pipe(filter(Boolean))
|
||||
.subscribe(async () => {
|
||||
const loader = this.loader.open('Deleting...').subscribe()
|
||||
const loader = this.loader.open('Deleting').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.removeBackupTarget({ id })
|
||||
|
||||
@@ -113,7 +113,7 @@ export class BackupsRecoverModal {
|
||||
|
||||
async restore(options: RecoverOption[]): Promise<void> {
|
||||
const ids = options.filter(({ checked }) => !!checked).map(({ id }) => id)
|
||||
const loader = this.loader.open('Initializing...').subscribe()
|
||||
const loader = this.loader.open('Initializing').subscribe()
|
||||
|
||||
const { targetId, serverId, password } = this.context.data
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ export class BackupsTargetsModal implements OnInit {
|
||||
}
|
||||
|
||||
async onDelete(id: string) {
|
||||
const loader = this.loader.open('Removing...').subscribe()
|
||||
const loader = this.loader.open('Removing').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.removeBackupTarget({ id })
|
||||
@@ -202,7 +202,7 @@ export class BackupsTargetsModal implements OnInit {
|
||||
| RR.AddCloudBackupTargetReq
|
||||
| RR.AddDiskBackupTargetReq,
|
||||
): Promise<RR.AddBackupTargetRes> {
|
||||
const loader = this.loader.open('Saving target...').subscribe()
|
||||
const loader = this.loader.open('Saving target').subscribe()
|
||||
|
||||
try {
|
||||
return await this.api.addBackupTarget(type, value)
|
||||
@@ -218,7 +218,7 @@ export class BackupsTargetsModal implements OnInit {
|
||||
| RR.UpdateCloudBackupTargetReq
|
||||
| RR.UpdateDiskBackupTargetReq,
|
||||
): Promise<RR.UpdateBackupTargetRes> {
|
||||
const loader = this.loader.open('Saving target...').subscribe()
|
||||
const loader = this.loader.open('Saving target').subscribe()
|
||||
|
||||
try {
|
||||
return await this.api.updateBackupTarget(type, value)
|
||||
|
||||
@@ -32,7 +32,7 @@ export class BackupsCreateService {
|
||||
targetId: string,
|
||||
pkgIds: string[],
|
||||
): Promise<void> {
|
||||
const loader = this.loader.open('Beginning backup...').subscribe()
|
||||
const loader = this.loader.open('Beginning backup').subscribe()
|
||||
|
||||
await this.api
|
||||
.createBackup({ targetId, packageIds: pkgIds })
|
||||
|
||||
@@ -81,7 +81,7 @@ export class BackupsRestoreService {
|
||||
return of(password).pipe(
|
||||
tap(() => argon2.verify(hash || '', password)),
|
||||
switchMap(() => {
|
||||
const loader = this.loader.open('Decrypting drive...').subscribe()
|
||||
const loader = this.loader.open('Decrypting drive').subscribe()
|
||||
|
||||
return this.api
|
||||
.getBackupInfo({ targetId, password })
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core'
|
||||
import { i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiAppearance, TuiButton, TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCardMedium } from '@taiga-ui/layout'
|
||||
import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.component'
|
||||
@@ -13,8 +14,8 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { TitleDirective } from 'src/app/services/title.service'
|
||||
|
||||
interface Log {
|
||||
title: string
|
||||
subtitle: string
|
||||
title: i18nKey
|
||||
subtitle: i18nKey
|
||||
icon: string
|
||||
follow: (params: RR.FollowServerLogsReq) => Promise<RR.FollowServerLogsRes>
|
||||
fetch: (params: RR.GetServerLogsReq) => Promise<RR.GetServerLogsRes>
|
||||
@@ -29,11 +30,11 @@ interface Log {
|
||||
iconStart="@tui.arrow-left"
|
||||
(click)="current.set(null)"
|
||||
>
|
||||
Back
|
||||
{{ 'Back' | i18n }}
|
||||
</button>
|
||||
{{ logs[key]?.title }}
|
||||
{{ logs[key]?.title | i18n }}
|
||||
} @else {
|
||||
Logs
|
||||
{{ 'Logs' | i18n }}
|
||||
}
|
||||
</ng-container>
|
||||
@if (current(); as key) {
|
||||
@@ -47,16 +48,16 @@ interface Log {
|
||||
class="close"
|
||||
(click)="current.set(null)"
|
||||
>
|
||||
Close
|
||||
{{ 'Close' | i18n }}
|
||||
</button>
|
||||
{{ logs[key]?.title }}
|
||||
{{ logs[key]?.title | i18n }}
|
||||
</strong>
|
||||
<p tuiSubtitle>{{ logs[key]?.subtitle }}</p>
|
||||
<p tuiSubtitle>{{ logs[key]?.subtitle | i18n }}</p>
|
||||
</header>
|
||||
@for (log of logs | keyvalue; track $index) {
|
||||
@if (log.key === current()) {
|
||||
<logs
|
||||
[context]="log.value.title"
|
||||
[context]="log.key"
|
||||
[followLogs]="log.value.follow"
|
||||
[fetchLogs]="log.value.fetch"
|
||||
/>
|
||||
@@ -71,8 +72,8 @@ interface Log {
|
||||
>
|
||||
<tui-icon [icon]="log.value.icon" />
|
||||
<span tuiTitle>
|
||||
<strong>{{ log.value.title }}</strong>
|
||||
<span tuiSubtitle>{{ log.value.subtitle }}</span>
|
||||
<strong>{{ log.value.title | i18n }}</strong>
|
||||
<span tuiSubtitle>{{ log.value.subtitle | i18n }}</span>
|
||||
</span>
|
||||
<tui-icon icon="@tui.chevron-right" />
|
||||
</button>
|
||||
@@ -164,6 +165,7 @@ interface Log {
|
||||
TuiIcon,
|
||||
TuiAppearance,
|
||||
TuiButton,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export default class SystemLogsComponent {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
LoadingService,
|
||||
sameUrl,
|
||||
ExverPipesModule,
|
||||
i18nPipe,
|
||||
} from '@start9labs/shared'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { firstValueFrom } from 'rxjs'
|
||||
@@ -45,7 +46,7 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
|
||||
appearance="secondary-destructive"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
Downgrade
|
||||
{{ 'Downgrade' | i18n }}
|
||||
</button>
|
||||
}
|
||||
@case (-1) {
|
||||
@@ -55,7 +56,7 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
|
||||
appearance="primary"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
Update
|
||||
{{ 'Update' | i18n }}
|
||||
</button>
|
||||
}
|
||||
@case (0) {
|
||||
@@ -65,7 +66,7 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
|
||||
appearance="secondary-grayscale"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
Reinstall
|
||||
{{ 'Reinstall' | i18n }}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
@@ -76,7 +77,7 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
|
||||
appearance="outline-grayscale"
|
||||
(click)="showService()"
|
||||
>
|
||||
View Installed
|
||||
{{ 'View Installed' | i18n }}
|
||||
</button>
|
||||
} @else {
|
||||
<button
|
||||
@@ -85,13 +86,19 @@ import { ToManifestPipe } from 'src/app/routes/portal/pipes/to-manifest'
|
||||
[appearance]="localFlavor ? 'warning' : 'primary'"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
{{ localFlavor ? 'Switch' : 'Install' }}
|
||||
{{ localFlavor ? ('Switch' | i18n) : ('Install' | i18n) }}
|
||||
</button>
|
||||
}
|
||||
`,
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [CommonModule, ExverPipesModule, TuiButton, ToManifestPipe],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ExverPipesModule,
|
||||
TuiButton,
|
||||
ToManifestPipe,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export class MarketplaceControlsComponent {
|
||||
private readonly alerts = inject(MarketplaceAlertsService)
|
||||
@@ -161,7 +168,7 @@ export class MarketplaceControlsComponent {
|
||||
}
|
||||
|
||||
private async install(url: string) {
|
||||
const loader = this.loader.open('Beginning Install...').subscribe()
|
||||
const loader = this.loader.open('Beginning install').subscribe()
|
||||
const { id, version } = this.pkg
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { MenuModule } from '@start9labs/marketplace'
|
||||
import {
|
||||
TuiDialogService,
|
||||
TuiIcon,
|
||||
TuiButton,
|
||||
TuiAppearance,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiIcon, TuiButton, TuiAppearance } from '@taiga-ui/core'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { MARKETPLACE_REGISTRY } from '../modals/registry.component'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { DialogService, i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -24,11 +20,11 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
iconStart="@tui.repeat"
|
||||
(click)="changeRegistry()"
|
||||
>
|
||||
Change Registry
|
||||
{{ 'Change Registry' | i18n }}
|
||||
</button>
|
||||
<button slot="mobile" class="mobile-button" (click)="changeRegistry()">
|
||||
<tui-icon tuiAppearance="icon" icon="@tui.repeat" />
|
||||
Change Registry
|
||||
{{ 'Change Registry' | i18n }}
|
||||
</button>
|
||||
</menu>
|
||||
`,
|
||||
@@ -47,17 +43,24 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
`,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [CommonModule, MenuModule, TuiButton, TuiIcon, TuiAppearance],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MenuModule,
|
||||
TuiButton,
|
||||
TuiIcon,
|
||||
TuiAppearance,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export class MarketplaceMenuComponent {
|
||||
private readonly dialogs = inject(TuiDialogService)
|
||||
private readonly dialog = inject(DialogService)
|
||||
readonly marketplace = inject(ConfigService).marketplace
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
readonly registry$ = this.marketplaceService.getRegistry$()
|
||||
|
||||
changeRegistry() {
|
||||
this.dialogs
|
||||
.open(MARKETPLACE_REGISTRY, {
|
||||
this.dialog
|
||||
.openComponent(MARKETPLACE_REGISTRY, {
|
||||
label: 'Change Registry',
|
||||
})
|
||||
.subscribe()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Component, inject, Input } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiNotification } from '@taiga-ui/core'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
|
||||
@@ -13,40 +14,34 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
>
|
||||
@switch (status) {
|
||||
@case ('success') {
|
||||
Services from this registry are packaged and maintained by the Start9
|
||||
team. If you experience an issue or have questions related to a
|
||||
service from this registry, one of our dedicated support staff will be
|
||||
happy to assist you.
|
||||
{{
|
||||
'Services from this registry are packaged and maintained by the Start9 team. If you experience an issue or have questions related to a service from this registry, one of our dedicated support staff will be happy to assist you.'
|
||||
| i18n
|
||||
}}
|
||||
}
|
||||
@case ('info') {
|
||||
Services from this registry are packaged and maintained by members of
|
||||
the Start9 community.
|
||||
<strong>Install at your own risk</strong>
|
||||
. If you experience an issue or have a question related to a service
|
||||
in this marketplace, please reach out to the package developer for
|
||||
assistance.
|
||||
{{
|
||||
'Services from this registry are packaged and maintained by members of the Start9 community. Install at your own risk. If you experience an issue or have a question related to a service in this marketplace, please reach out to the package developer for assistance.'
|
||||
| i18n
|
||||
}}
|
||||
}
|
||||
@case ('warning') {
|
||||
Services from this registry are undergoing
|
||||
<strong>beta</strong>
|
||||
testing and may contain bugs.
|
||||
<strong>Install at your own risk</strong>
|
||||
.
|
||||
{{
|
||||
'Services from this registry are undergoing beta testing and may contain bugs. Install at your own risk.'
|
||||
| i18n
|
||||
}}
|
||||
}
|
||||
@case ('error') {
|
||||
Services from this registry are undergoing
|
||||
<strong>alpha</strong>
|
||||
testing. They are expected to contain bugs and could damage your
|
||||
system.
|
||||
<strong>Install at your own risk</strong>
|
||||
.
|
||||
{{
|
||||
'Services from this registry are undergoing alpha testing. They are expected to contain bugs and could damage your system. Install at your own risk.'
|
||||
| i18n
|
||||
}}
|
||||
}
|
||||
@default {
|
||||
This is a Custom Registry. Start9 cannot verify the integrity or
|
||||
functionality of services from this registry, and they could damage
|
||||
your system.
|
||||
<strong>Install at your own risk</strong>
|
||||
.
|
||||
{{
|
||||
'This is a Custom Registry. Start9 cannot verify the integrity or functionality of services from this registry, and they could damage your system. Install at your own risk.'
|
||||
| i18n
|
||||
}}
|
||||
}
|
||||
}
|
||||
</tui-notification>
|
||||
@@ -59,7 +54,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
}
|
||||
`,
|
||||
],
|
||||
imports: [TuiNotification],
|
||||
imports: [TuiNotification, i18nPipe],
|
||||
})
|
||||
export class MarketplaceNotificationComponent {
|
||||
private readonly marketplace = inject(ConfigService).marketplace
|
||||
|
||||
@@ -17,11 +17,12 @@ import { MarketplaceMenuComponent } from './components/menu.component'
|
||||
import { MarketplaceNotificationComponent } from './components/notification.component'
|
||||
import { MarketplaceSidebarsComponent } from './components/sidebars.component'
|
||||
import { MarketplaceTileComponent } from './components/tile.component'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
<ng-container *title>Marketplace</ng-container>
|
||||
<ng-container *title>{{ 'Marketplace' | i18n }}</ng-container>
|
||||
<marketplace-menu />
|
||||
<tui-scrollbar>
|
||||
<div class="marketplace-content-wrapper">
|
||||
@@ -48,7 +49,7 @@ import { MarketplaceTileComponent } from './components/tile.component'
|
||||
</section>
|
||||
} @else {
|
||||
<h1 class="loading-text">
|
||||
Loading
|
||||
{{ 'Loading' | i18n }}
|
||||
<span class="loading-dots"></span>
|
||||
</h1>
|
||||
}
|
||||
@@ -156,6 +157,7 @@ import { MarketplaceTileComponent } from './components/tile.component'
|
||||
TuiScrollbar,
|
||||
FilterPackagesPipeModule,
|
||||
TitleDirective,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export default class MarketplaceComponent {
|
||||
|
||||
@@ -17,13 +17,13 @@ import {
|
||||
MarketplacePackageHeroComponent,
|
||||
MarketplacePkg,
|
||||
} from '@start9labs/marketplace'
|
||||
import { Exver, SharedPipesModule } from '@start9labs/shared'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDialogContext,
|
||||
TuiDialogService,
|
||||
TuiLoader,
|
||||
} from '@taiga-ui/core'
|
||||
DialogService,
|
||||
Exver,
|
||||
i18nPipe,
|
||||
SharedPipesModule,
|
||||
} from '@start9labs/shared'
|
||||
import { TuiButton, TuiDialogContext, TuiLoader } from '@taiga-ui/core'
|
||||
import { TuiRadioList } from '@taiga-ui/kit'
|
||||
import {
|
||||
BehaviorSubject,
|
||||
@@ -77,14 +77,14 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
appearance="secondary"
|
||||
(click)="completeWith(null)"
|
||||
>
|
||||
Cancel
|
||||
{{ 'Cancel' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiButton
|
||||
appearance="secondary"
|
||||
(click)="completeWith(data.value)"
|
||||
>
|
||||
Ok
|
||||
{{ 'Ok' | i18n }}
|
||||
</button>
|
||||
</footer>
|
||||
</ng-template>
|
||||
@@ -174,13 +174,14 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
TuiRadioList,
|
||||
TuiLoader,
|
||||
FlavorsComponent,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export class MarketplacePreviewComponent {
|
||||
@Input({ required: true })
|
||||
pkgId!: string
|
||||
|
||||
private readonly dialogs = inject(TuiDialogService)
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly exver = inject(Exver)
|
||||
private readonly router = inject(Router)
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
@@ -233,8 +234,8 @@ export class MarketplacePreviewComponent {
|
||||
{ version }: MarketplacePkg,
|
||||
template: TemplateRef<TuiDialogContext>,
|
||||
) {
|
||||
this.dialogs
|
||||
.open<string>(template, {
|
||||
this.dialog
|
||||
.openComponent<string>(template, {
|
||||
label: 'Versions',
|
||||
size: 's',
|
||||
data: {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { ActivatedRoute, Router } from '@angular/router'
|
||||
import { Router } from '@angular/router'
|
||||
import {
|
||||
MarketplaceRegistryComponent,
|
||||
StoreIconComponentModule,
|
||||
} from '@start9labs/marketplace'
|
||||
import {
|
||||
DialogService,
|
||||
ErrorService,
|
||||
i18nKey,
|
||||
i18nPipe,
|
||||
LoadingService,
|
||||
sameUrl,
|
||||
toUrl,
|
||||
@@ -14,11 +17,10 @@ import {
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDialogContext,
|
||||
TuiDialogService,
|
||||
TuiDialogOptions,
|
||||
TuiIcon,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TUI_CONFIRM } from '@taiga-ui/kit'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
@@ -29,13 +31,14 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
||||
import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
|
||||
import { TuiConfirmData } from '@taiga-ui/kit'
|
||||
import { IST, utils } from '@start9labs/start-sdk'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
@if (stores$ | async; as stores) {
|
||||
<h3 class="g-title">Default Registries</h3>
|
||||
<h3 class="g-title">{{ 'Default Registries' | i18n }}</h3>
|
||||
@for (registry of stores.standard; track $index) {
|
||||
<button
|
||||
tuiCell
|
||||
@@ -45,10 +48,10 @@ import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
|
||||
(click)="connect(registry.url)"
|
||||
></button>
|
||||
}
|
||||
<h3 class="g-title">Custom Registries</h3>
|
||||
<h3 class="g-title">{{ 'Custom Registries' | i18n }}</h3>
|
||||
<button tuiCell (click)="add()" [style.width]="'-webkit-fill-available'">
|
||||
<tui-icon icon="@tui.plus" [style.margin-inline.rem]="'0.5'" />
|
||||
<div tuiTitle>Add custom registry</div>
|
||||
<div tuiTitle>{{ 'Add custom registry' | i18n }}</div>
|
||||
</button>
|
||||
@for (registry of stores.alt; track $index) {
|
||||
<div class="connect-container">
|
||||
@@ -64,7 +67,7 @@ import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
|
||||
iconStart="@tui.trash-2"
|
||||
(click)="delete(registry.url, registry.name)"
|
||||
>
|
||||
Delete
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@@ -88,6 +91,7 @@ import { getMarketplaceValueSpec, getPromptOptions } from '../utils/registry'
|
||||
TuiButton,
|
||||
MarketplaceRegistryComponent,
|
||||
StoreIconComponentModule,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export class MarketplaceRegistryModal {
|
||||
@@ -95,16 +99,16 @@ export class MarketplaceRegistryModal {
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly formDialog = inject(FormDialogService)
|
||||
private readonly dialogs = inject(TuiDialogService)
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
private readonly context = injectContext<TuiDialogContext>()
|
||||
private readonly route = inject(ActivatedRoute)
|
||||
private readonly router = inject(Router)
|
||||
private readonly hosts$ = inject<PatchDB<DataModel>>(PatchDB).watch$(
|
||||
'ui',
|
||||
'marketplace',
|
||||
'knownHosts',
|
||||
)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
readonly marketplaceConfig = inject(ConfigService).marketplace
|
||||
|
||||
readonly stores$ = combineLatest([
|
||||
@@ -122,19 +126,19 @@ export class MarketplaceRegistryModal {
|
||||
)
|
||||
|
||||
add() {
|
||||
const { name, spec } = getMarketplaceValueSpec()
|
||||
const { name, spec } = this.getMarketplaceValueSpec()
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: name,
|
||||
label: name as i18nKey,
|
||||
data: {
|
||||
spec,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Save for Later',
|
||||
text: this.i18n.transform('Save for later'),
|
||||
handler: async ({ url }: { url: string }) => this.save(url),
|
||||
},
|
||||
{
|
||||
text: 'Save and Connect',
|
||||
text: this.i18n.transform('Save and connect'),
|
||||
handler: async ({ url }: { url: string }) => this.save(url, true),
|
||||
isSubmit: true,
|
||||
},
|
||||
@@ -144,11 +148,19 @@ export class MarketplaceRegistryModal {
|
||||
}
|
||||
|
||||
delete(url: string, name: string = '') {
|
||||
this.dialogs
|
||||
.open(TUI_CONFIRM, getPromptOptions(name))
|
||||
this.dialog
|
||||
.openConfirm({
|
||||
label: 'Confirm',
|
||||
size: 's',
|
||||
data: {
|
||||
content: 'Are you sure you want to delete this registry?',
|
||||
yes: 'Delete',
|
||||
no: 'Cancel',
|
||||
},
|
||||
})
|
||||
.pipe(filter(Boolean))
|
||||
.subscribe(async () => {
|
||||
const loader = this.loader.open('Deleting...').subscribe()
|
||||
const loader = this.loader.open('Deleting').subscribe()
|
||||
const hosts = await firstValueFrom(this.hosts$)
|
||||
const filtered: { [url: string]: UIStore } = Object.keys(hosts)
|
||||
.filter(key => !sameUrl(key, url))
|
||||
@@ -176,7 +188,7 @@ export class MarketplaceRegistryModal {
|
||||
): Promise<void> {
|
||||
loader.unsubscribe()
|
||||
loader.closed = false
|
||||
loader.add(this.loader.open('Changing Registry...').subscribe())
|
||||
loader.add(this.loader.open('Changing registry').subscribe())
|
||||
try {
|
||||
this.marketplaceService.setRegistryUrl(url)
|
||||
this.router.navigate([], {
|
||||
@@ -192,6 +204,36 @@ export class MarketplaceRegistryModal {
|
||||
}
|
||||
}
|
||||
|
||||
private getMarketplaceValueSpec(): IST.ValueSpecObject {
|
||||
return {
|
||||
type: 'object',
|
||||
name: this.i18n.transform('Add Custom Registry')!,
|
||||
description: null,
|
||||
warning: null,
|
||||
spec: {
|
||||
url: {
|
||||
type: 'text',
|
||||
name: 'URL',
|
||||
description: this.i18n.transform(
|
||||
'A fully-qualified URL of the custom registry',
|
||||
)!,
|
||||
inputmode: 'url',
|
||||
required: true,
|
||||
masked: false,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
patterns: [utils.Patterns.url],
|
||||
placeholder: 'e.g. https://example.org',
|
||||
default: null,
|
||||
warning: null,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
generate: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
private async save(rawUrl: string, connect = false): Promise<boolean> {
|
||||
const loader = this.loader.open('Loading').subscribe()
|
||||
const url = new URL(rawUrl).toString()
|
||||
@@ -215,12 +257,13 @@ export class MarketplaceRegistryModal {
|
||||
// Error on duplicates
|
||||
const hosts = await firstValueFrom(this.hosts$)
|
||||
const currentUrls = Object.keys(hosts).map(toUrl)
|
||||
if (currentUrls.includes(url)) throw new Error('Marketplace already added')
|
||||
if (currentUrls.includes(url))
|
||||
throw new Error(this.i18n.transform('Registry already added'))
|
||||
|
||||
// Validate
|
||||
loader.unsubscribe()
|
||||
loader.closed = false
|
||||
loader.add(this.loader.open('Validating marketplace...').subscribe())
|
||||
loader.add(this.loader.open('Validating registry').subscribe())
|
||||
|
||||
const { name } = await firstValueFrom(
|
||||
this.marketplaceService.fetchInfo$(url),
|
||||
@@ -229,7 +272,7 @@ export class MarketplaceRegistryModal {
|
||||
// Save
|
||||
loader.unsubscribe()
|
||||
loader.closed = false
|
||||
loader.add(this.loader.open('Saving...').subscribe())
|
||||
loader.add(this.loader.open('Saving').subscribe())
|
||||
|
||||
await this.api.setDbValue(['marketplace', 'knownHosts', url], { name })
|
||||
}
|
||||
|
||||
@@ -1,34 +1,37 @@
|
||||
import { TUI_CONFIRM } from '@taiga-ui/kit'
|
||||
import { inject, Injectable } from '@angular/core'
|
||||
import { MarketplacePkg, MarketplacePkgBase } from '@start9labs/marketplace'
|
||||
import { TuiDialogService } from '@taiga-ui/core'
|
||||
import { MarketplacePkgBase } from '@start9labs/marketplace'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { defaultIfEmpty, firstValueFrom } from 'rxjs'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MarketplaceAlertsService {
|
||||
private readonly dialogs = inject(TuiDialogService)
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly marketplace$ = inject<PatchDB<DataModel>>(PatchDB).watch$(
|
||||
'ui',
|
||||
'marketplace',
|
||||
)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
async alertMarketplace(url: string, originalUrl: string): Promise<boolean> {
|
||||
const marketplaces = await firstValueFrom(this.marketplace$)
|
||||
const name = marketplaces.knownHosts[url]?.name || url
|
||||
const source = marketplaces.knownHosts[originalUrl]?.name || originalUrl
|
||||
const message = source ? `installed from ${source}` : 'side loaded'
|
||||
const message = source
|
||||
? `${this.i18n.transform('installed from')} ${source}`
|
||||
: this.i18n.transform('sideloaded')
|
||||
|
||||
return new Promise(async resolve => {
|
||||
this.dialogs
|
||||
.open<boolean>(TUI_CONFIRM, {
|
||||
this.dialog
|
||||
.openConfirm<boolean>({
|
||||
label: 'Warning',
|
||||
size: 's',
|
||||
data: {
|
||||
content: `This service was originally ${message}, but you are currently connected to ${name}. To install from ${name} anyway, click "Continue".`,
|
||||
content:
|
||||
`${this.i18n.transform('This service was originally')} ${message}, ${this.i18n.transform('but you are currently connected to')} ${name}. ${this.i18n.transform('To install from')} ${name} ${this.i18n.transform('anyway, click "Continue".')}` as i18nKey,
|
||||
yes: 'Continue',
|
||||
no: 'Cancel',
|
||||
},
|
||||
@@ -39,14 +42,14 @@ export class MarketplaceAlertsService {
|
||||
}
|
||||
|
||||
async alertBreakages(breakages: string[]): Promise<boolean> {
|
||||
let content: string =
|
||||
'As a result of this update, the following services will no longer work properly and may crash:<ul>'
|
||||
let content =
|
||||
`${this.i18n.transform('As a result of this update, the following services will no longer work properly and may crash')}:<ul>'` as i18nKey
|
||||
const bullets = breakages.map(title => `<li><b>${title}</b></li>`)
|
||||
content = `${content}${bullets.join('')}</ul>`
|
||||
content = `${content}${bullets.join('')}</ul>` as i18nKey
|
||||
|
||||
return new Promise(async resolve => {
|
||||
this.dialogs
|
||||
.open<boolean>(TUI_CONFIRM, {
|
||||
this.dialog
|
||||
.openConfirm<boolean>({
|
||||
label: 'Warning',
|
||||
size: 's',
|
||||
data: {
|
||||
@@ -61,13 +64,13 @@ export class MarketplaceAlertsService {
|
||||
}
|
||||
|
||||
async alertInstall({ alerts }: MarketplacePkgBase): Promise<boolean> {
|
||||
const content = alerts.install
|
||||
const content = alerts.install as i18nKey
|
||||
|
||||
return (
|
||||
!!content &&
|
||||
new Promise(resolve => {
|
||||
this.dialogs
|
||||
.open<boolean>(TUI_CONFIRM, {
|
||||
this.dialog
|
||||
.openConfirm<boolean>({
|
||||
label: 'Alert',
|
||||
size: 's',
|
||||
data: {
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { IST } from '@start9labs/start-sdk'
|
||||
import { TuiDialogOptions } from '@taiga-ui/core'
|
||||
import { TuiConfirmData } from '@taiga-ui/kit'
|
||||
|
||||
export function getMarketplaceValueSpec(): IST.ValueSpecObject {
|
||||
return {
|
||||
type: 'object',
|
||||
name: 'Add Custom Registry',
|
||||
description: null,
|
||||
warning: null,
|
||||
spec: {
|
||||
url: {
|
||||
type: 'text',
|
||||
name: 'URL',
|
||||
description: 'A fully-qualified URL of the custom registry',
|
||||
inputmode: 'url',
|
||||
required: true,
|
||||
masked: false,
|
||||
minLength: null,
|
||||
maxLength: null,
|
||||
patterns: [
|
||||
{
|
||||
regex: `https?:\/\/[a-zA-Z0-9][a-zA-Z0-9-\.]+[a-zA-Z0-9]\.[^\s]{2,}`,
|
||||
description: 'Must be a valid URL',
|
||||
},
|
||||
],
|
||||
placeholder: 'e.g. https://example.org',
|
||||
default: null,
|
||||
warning: null,
|
||||
disabled: false,
|
||||
immutable: false,
|
||||
generate: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function getPromptOptions(
|
||||
name: string,
|
||||
): Partial<TuiDialogOptions<TuiConfirmData>> {
|
||||
return {
|
||||
label: 'Confirm',
|
||||
size: 's',
|
||||
data: {
|
||||
content: `Are you sure you want to delete ${name}?`,
|
||||
yes: 'Delete',
|
||||
no: 'Cancel',
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,14 @@ import {
|
||||
} from '@angular/core'
|
||||
import { ServerMetrics } from 'src/app/services/api/api.types'
|
||||
import { DataComponent } from './data.component'
|
||||
import { i18nKey } from '@start9labs/shared'
|
||||
|
||||
const LABELS = {
|
||||
percentageUsed: 'Percentage Used',
|
||||
userSpace: 'User Space',
|
||||
kernelSpace: 'Kernel Space',
|
||||
const LABELS: Record<string, i18nKey> = {
|
||||
percentageUsed: 'Percentage used',
|
||||
userSpace: 'User space',
|
||||
kernelSpace: 'Kernel space',
|
||||
idle: 'Idle',
|
||||
wait: 'I/O Wait',
|
||||
wait: 'I/O wait',
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -8,6 +8,7 @@ import { TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { ServerMetrics } from 'src/app/services/api/api.types'
|
||||
import { ValuePipe } from './value.pipe'
|
||||
import { i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -15,7 +16,7 @@ import { ValuePipe } from './value.pipe'
|
||||
template: `
|
||||
@for (key of keys(); track $index) {
|
||||
<div tuiCell="m">
|
||||
<span tuiTitle>{{ labels()[key] }}</span>
|
||||
<span tuiTitle>{{ labels()[key] | i18n }}</span>
|
||||
<span tuiTitle [attr.data-unit]="$any(value()?.[key])?.unit">
|
||||
{{ $any(value()?.[key])?.value | value }}
|
||||
</span>
|
||||
@@ -41,10 +42,10 @@ import { ValuePipe } from './value.pipe'
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiCell, TuiTitle, ValuePipe],
|
||||
imports: [TuiCell, TuiTitle, ValuePipe, i18nPipe],
|
||||
})
|
||||
export class DataComponent<T extends ServerMetrics[keyof ServerMetrics]> {
|
||||
readonly labels = input.required<Record<keyof T, string>>()
|
||||
readonly labels = input.required<Record<keyof T, i18nKey>>()
|
||||
readonly value = input<T>()
|
||||
readonly keys = computed(() => Object.keys(this.labels()) as Array<keyof T>)
|
||||
}
|
||||
|
||||
@@ -8,15 +8,16 @@ import { TuiProgress } from '@taiga-ui/kit'
|
||||
import { ServerMetrics } from 'src/app/services/api/api.types'
|
||||
import { DataComponent } from './data.component'
|
||||
import { ValuePipe } from './value.pipe'
|
||||
import { i18nKey } from '@start9labs/shared'
|
||||
|
||||
const LABELS = {
|
||||
percentageUsed: 'Percentage Used',
|
||||
const LABELS: Record<string, i18nKey> = {
|
||||
percentageUsed: 'Percentage used',
|
||||
total: 'Total',
|
||||
used: 'Used',
|
||||
available: 'Available',
|
||||
zramUsed: 'zram Used',
|
||||
zramTotal: 'zram Total',
|
||||
zramAvailable: 'zram Available',
|
||||
zramUsed: 'zram used',
|
||||
zramTotal: 'zram total',
|
||||
zramAvailable: 'zram available',
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -13,23 +13,24 @@ import { StorageComponent } from './storage.component'
|
||||
import { TemperatureComponent } from './temperature.component'
|
||||
import { TimeComponent } from './time.component'
|
||||
import { UptimeComponent } from './uptime.component'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'app-metrics',
|
||||
template: `
|
||||
<ng-container *title>Metrics</ng-container>
|
||||
<ng-container *title>{{ 'Metrics' | i18n }}</ng-container>
|
||||
<div>
|
||||
<section class="g-card">
|
||||
<header>System Time</header>
|
||||
<header>{{ 'System Time' | i18n }}</header>
|
||||
<metrics-time />
|
||||
</section>
|
||||
<section class="g-card">
|
||||
<header>Uptime</header>
|
||||
<header>{{ 'Uptime' | i18n }}</header>
|
||||
<metrics-uptime />
|
||||
</section>
|
||||
<section class="g-card">
|
||||
<header>Temperature</header>
|
||||
<header>{{ 'Temperature' | i18n }}</header>
|
||||
<metrics-temperature [value]="temperature()" />
|
||||
</section>
|
||||
<section class="g-card">
|
||||
@@ -37,11 +38,11 @@ import { UptimeComponent } from './uptime.component'
|
||||
<metrics-cpu [value]="metrics()?.cpu" />
|
||||
</section>
|
||||
<section class="g-card">
|
||||
<header>Memory</header>
|
||||
<header>{{ 'Memory' | i18n }}</header>
|
||||
<metrics-memory [value]="metrics()?.memory" />
|
||||
</section>
|
||||
<section class="g-card">
|
||||
<header>Storage</header>
|
||||
<header>{{ 'Storage' | i18n }}</header>
|
||||
<metrics-storage [value]="metrics()?.disk" />
|
||||
</section>
|
||||
</div>
|
||||
@@ -93,6 +94,7 @@ import { UptimeComponent } from './uptime.component'
|
||||
MemoryComponent,
|
||||
UptimeComponent,
|
||||
TimeComponent,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export default class SystemMetricsComponent {
|
||||
|
||||
@@ -7,9 +7,10 @@ import {
|
||||
import { TuiProgress } from '@taiga-ui/kit'
|
||||
import { ServerMetrics } from 'src/app/services/api/api.types'
|
||||
import { DataComponent } from './data.component'
|
||||
import { i18nKey } from '@start9labs/shared'
|
||||
|
||||
const LABELS = {
|
||||
percentageUsed: 'Percentage Used',
|
||||
const LABELS: Record<string, i18nKey> = {
|
||||
percentageUsed: 'Percentage used',
|
||||
capacity: 'Capacity',
|
||||
used: 'Used',
|
||||
available: 'Available',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import {
|
||||
TuiHint,
|
||||
TuiIcon,
|
||||
@@ -37,11 +38,11 @@ import { TimeService } from 'src/app/services/time.service'
|
||||
</tui-notification>
|
||||
}
|
||||
} @else {
|
||||
Loading...
|
||||
{{ 'Loading' | i18n }}...
|
||||
}
|
||||
<ng-template #hint>
|
||||
<div tuiTitle>
|
||||
Clock sync failure
|
||||
{{ 'Clock sync failure' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
To resolve it, refer to
|
||||
<a
|
||||
@@ -52,7 +53,7 @@ import { TimeService } from 'src/app/services/time.service'
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
[pseudo]="true"
|
||||
[textContent]="'the docs'"
|
||||
[textContent]="'the docs' | i18n"
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,6 +109,7 @@ import { TimeService } from 'src/app/services/time.service'
|
||||
TuiCell,
|
||||
TuiIcon,
|
||||
TuiHint,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export class TimeComponent {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TimeService } from 'src/app/services/time.service'
|
||||
|
||||
@Component({
|
||||
@@ -9,22 +10,22 @@ import { TimeService } from 'src/app/services/time.service'
|
||||
@if (uptime(); as time) {
|
||||
<div>
|
||||
<b>{{ time.days }}</b>
|
||||
Days
|
||||
{{ 'Days' | i18n }}
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ time.hours }}</b>
|
||||
Hours
|
||||
{{ 'Hours' | i18n }}
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ time.minutes }}</b>
|
||||
Minutes
|
||||
{{ 'Minutes' | i18n }}
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ time.seconds }}</b>
|
||||
Seconds
|
||||
{{ 'Seconds' | i18n }}
|
||||
</div>
|
||||
} @else {
|
||||
Loading...
|
||||
{{ 'Loading' | i18n }}...
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
@@ -49,6 +50,7 @@ import { TimeService } from 'src/app/services/time.service'
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [i18nPipe],
|
||||
})
|
||||
export class UptimeComponent {
|
||||
readonly uptime = toSignal(inject(TimeService).uptime$)
|
||||
|
||||
@@ -16,6 +16,7 @@ import { ServerNotification } from 'src/app/services/api/api.types'
|
||||
import { NotificationService } from 'src/app/services/notification.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { toRouterLink } from 'src/app/utils/to-router-link'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
selector: '[notificationItem]',
|
||||
@@ -47,12 +48,12 @@ import { toRouterLink } from 'src/app/utils/to-router-link'
|
||||
/>
|
||||
@if (overflow) {
|
||||
<button tuiLink (click)="service.viewModal(notificationItem, true)">
|
||||
View Full
|
||||
{{ 'View full' | i18n }}
|
||||
</button>
|
||||
}
|
||||
@if (notificationItem.code === 1 || notificationItem.code === 2) {
|
||||
<button tuiLink (click)="service.viewModal(notificationItem)">
|
||||
View Report
|
||||
{{ 'View report' | i18n }}
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
@@ -109,7 +110,7 @@ import { toRouterLink } from 'src/app/utils/to-router-link'
|
||||
}
|
||||
}
|
||||
`,
|
||||
imports: [CommonModule, RouterLink, TuiLineClamp, TuiLink, TuiIcon],
|
||||
imports: [CommonModule, RouterLink, TuiLineClamp, TuiLink, TuiIcon, i18nPipe],
|
||||
})
|
||||
export class NotificationItemComponent {
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core'
|
||||
import { ErrorService } from '@start9labs/shared'
|
||||
import { ErrorService, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
|
||||
import { RR, ServerNotifications } from 'src/app/services/api/api.types'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
@@ -14,7 +14,7 @@ import { NotificationsTableComponent } from './table.component'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ng-container *title>Notifications</ng-container>
|
||||
<ng-container *title>{{ 'Notifications' | i18n }}</ng-container>
|
||||
<h3 class="g-title">
|
||||
<button
|
||||
appearance="primary"
|
||||
@@ -29,7 +29,7 @@ import { NotificationsTableComponent } from './table.component'
|
||||
[tuiDropdownEnabled]="!!table.selected().length"
|
||||
[(tuiDropdownOpen)]="open"
|
||||
>
|
||||
Batch Action
|
||||
{{ 'Batch action' | i18n }}
|
||||
</button>
|
||||
<ng-template #dropdown>
|
||||
<tui-data-list>
|
||||
@@ -37,16 +37,16 @@ import { NotificationsTableComponent } from './table.component'
|
||||
tuiOption
|
||||
(click)="markSeen(notifications(), table.selected())"
|
||||
>
|
||||
Mark seen
|
||||
{{ 'Mark seen' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
(click)="markUnseen(notifications(), table.selected())"
|
||||
>
|
||||
Mark unseen
|
||||
{{ 'Mark unseen' | i18n }}
|
||||
</button>
|
||||
<button tuiOption (click)="remove(notifications(), table.selected())">
|
||||
Delete
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</tui-data-list>
|
||||
</ng-template>
|
||||
@@ -62,6 +62,7 @@ import { NotificationsTableComponent } from './table.component'
|
||||
TuiDataList,
|
||||
NotificationsTableComponent,
|
||||
TitleDirective,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export default class NotificationsComponent {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ServerNotifications,
|
||||
} from 'src/app/services/api/api.types'
|
||||
import { NotificationItemComponent } from './item.component'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
selector: 'table[notifications]',
|
||||
@@ -29,10 +30,10 @@ import { NotificationItemComponent } from './item.component'
|
||||
(ngModelChange)="onAll($event)"
|
||||
/>
|
||||
</th>
|
||||
<th [style.min-width.rem]="12">Date</th>
|
||||
<th [style.min-width.rem]="12">Title</th>
|
||||
<th [style.min-width.rem]="8">Service</th>
|
||||
<th>Message</th>
|
||||
<th [style.min-width.rem]="12">{{ 'Date' | i18n }}</th>
|
||||
<th [style.min-width.rem]="12">{{ 'Title' | i18n }}</th>
|
||||
<th [style.min-width.rem]="8">{{ 'Service' | i18n }}</th>
|
||||
<th>{{ 'Message' | i18n }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -53,13 +54,15 @@ import { NotificationItemComponent } from './item.component'
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="5">You have no notifications</td>
|
||||
<td colspan="5">{{ 'No notifications' | i18n }}</td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
@for (row of ['', '']; track $index) {
|
||||
<tr>
|
||||
<td colspan="5"><div [tuiSkeleton]="true">Loading</div></td>
|
||||
<td colspan="5">
|
||||
<div [tuiSkeleton]="true">{{ 'Loading' | i18n }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
@@ -75,7 +78,13 @@ import { NotificationItemComponent } from './item.component'
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [FormsModule, TuiCheckbox, NotificationItemComponent, TuiSkeleton],
|
||||
imports: [
|
||||
FormsModule,
|
||||
TuiCheckbox,
|
||||
NotificationItemComponent,
|
||||
TuiSkeleton,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export class NotificationsTableComponent implements OnChanges {
|
||||
@Input() notifications?: ServerNotifications
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { TuiButton } from '@taiga-ui/core'
|
||||
import { TuiAvatar } from '@taiga-ui/kit'
|
||||
@@ -22,16 +23,20 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
||||
</td>
|
||||
<td>
|
||||
@if (actionRequest().severity === 'critical') {
|
||||
<strong [style.color]="'var(--tui-status-warning)'">Required</strong>
|
||||
<strong [style.color]="'var(--tui-status-warning)'">
|
||||
{{ 'Required' | i18n }}
|
||||
</strong>
|
||||
} @else {
|
||||
<strong [style.color]="'var(--tui-status-info)'">Optional</strong>
|
||||
<strong [style.color]="'var(--tui-status-info)'">
|
||||
{{ 'Optional' | i18n }}
|
||||
</strong>
|
||||
}
|
||||
</td>
|
||||
<td
|
||||
[style.color]="'var(--tui-text-secondary)'"
|
||||
[style.grid-area]="'2 / span 2'"
|
||||
>
|
||||
{{ actionRequest().reason || 'No reason provided' }}
|
||||
{{ actionRequest().reason || ('No reason provided' | i18n) }}
|
||||
</td>
|
||||
<td>
|
||||
<button tuiButton (click)="handle()">
|
||||
@@ -70,7 +75,7 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiButton, TuiAvatar],
|
||||
imports: [TuiButton, TuiAvatar, i18nPipe],
|
||||
})
|
||||
export class ServiceActionRequestComponent {
|
||||
private readonly actionService = inject(ActionService)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user