mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
chore: fix
This commit is contained in:
@@ -16,7 +16,7 @@ import { StateService } from '../services/state.service'
|
|||||||
<button tuiCell="l" (click)="startFresh()">
|
<button tuiCell="l" (click)="startFresh()">
|
||||||
<span tuiAvatar="@tui.plus" appearance="positive"></span>
|
<span tuiAvatar="@tui.plus" appearance="positive"></span>
|
||||||
<div tuiTitle>
|
<div tuiTitle>
|
||||||
{{ 'Start Fresh' | i18n }}
|
<b>{{ 'Start Fresh' | i18n }}</b>
|
||||||
<div tuiSubtitle>{{ 'Set up a brand new server' | i18n }}</div>
|
<div tuiSubtitle>{{ 'Set up a brand new server' | i18n }}</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@@ -24,7 +24,7 @@ import { StateService } from '../services/state.service'
|
|||||||
<button tuiCell="l" (click)="restore()">
|
<button tuiCell="l" (click)="restore()">
|
||||||
<span tuiAvatar="@tui.archive-restore" appearance="warning"></span>
|
<span tuiAvatar="@tui.archive-restore" appearance="warning"></span>
|
||||||
<div tuiTitle>
|
<div tuiTitle>
|
||||||
{{ 'Restore from Backup' | i18n }}
|
<b>{{ 'Restore from Backup' | i18n }}</b>
|
||||||
<div tuiSubtitle>
|
<div tuiSubtitle>
|
||||||
{{ 'Restore StartOS data from an encrypted backup' | i18n }}
|
{{ 'Restore StartOS data from an encrypted backup' | i18n }}
|
||||||
</div>
|
</div>
|
||||||
@@ -34,7 +34,7 @@ import { StateService } from '../services/state.service'
|
|||||||
<button tuiCell="l" (click)="transfer()">
|
<button tuiCell="l" (click)="transfer()">
|
||||||
<span tuiAvatar="@tui.hard-drive-download" appearance="info"></span>
|
<span tuiAvatar="@tui.hard-drive-download" appearance="info"></span>
|
||||||
<div tuiTitle>
|
<div tuiTitle>
|
||||||
{{ 'Transfer' | i18n }}
|
<b>{{ 'Transfer' | i18n }}</b>
|
||||||
<div tuiSubtitle>
|
<div tuiSubtitle>
|
||||||
{{ 'Transfer data from an existing StartOS data drive' | i18n }}
|
{{ 'Transfer data from an existing StartOS data drive' | i18n }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,20 +29,20 @@ import { StateService } from '../services/state.service'
|
|||||||
<canvas matrix></canvas>
|
<canvas matrix></canvas>
|
||||||
<section tuiCardLarge>
|
<section tuiCardLarge>
|
||||||
<header tuiHeader>
|
<header tuiHeader>
|
||||||
<h2 tuiTitle>
|
<hgroup tuiTitle>
|
||||||
<span class="inline-title">
|
<h2 tuiCell="m">
|
||||||
<tui-icon icon="@tui.circle-check-big" class="g-positive" />
|
<tui-icon icon="@tui.circle-check-big" class="g-positive" />
|
||||||
{{ 'Setup Complete!' | i18n }}
|
{{ 'Setup Complete!' | i18n }}
|
||||||
</span>
|
</h2>
|
||||||
@if (!stateService.kiosk) {
|
</hgroup>
|
||||||
<span tuiSubtitle>
|
@if (!stateService.kiosk) {
|
||||||
{{
|
<p tuiSubtitle>
|
||||||
'http://start.local was for setup only. It will no longer work.'
|
{{
|
||||||
| i18n
|
'http://start.local was for setup only. It will no longer work.'
|
||||||
}}
|
| i18n
|
||||||
</span>
|
}}
|
||||||
}
|
</p>
|
||||||
</h2>
|
}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@if (!result) {
|
@if (!result) {
|
||||||
@@ -52,15 +52,15 @@ import { StateService } from '../services/state.service'
|
|||||||
@if (!stateService.kiosk) {
|
@if (!stateService.kiosk) {
|
||||||
<button tuiCell="l" (click)="download()">
|
<button tuiCell="l" (click)="download()">
|
||||||
<span tuiAvatar="@tui.download" appearance="secondary"></span>
|
<span tuiAvatar="@tui.download" appearance="secondary"></span>
|
||||||
<div tuiTitle>
|
<span tuiTitle>
|
||||||
{{ 'Download Address Info' | i18n }}
|
<b>{{ 'Download Address Info' | i18n }}</b>
|
||||||
<div tuiSubtitle>
|
<span tuiSubtitle>
|
||||||
{{
|
{{
|
||||||
"Contains your server's permanent local address and Root CA"
|
"Contains your server's permanent local address and Root CA"
|
||||||
| i18n
|
| i18n
|
||||||
}}
|
}}
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
@if (downloaded) {
|
@if (downloaded) {
|
||||||
<tui-icon icon="@tui.circle-check" class="g-positive" />
|
<tui-icon icon="@tui.circle-check" class="g-positive" />
|
||||||
}
|
}
|
||||||
@@ -76,15 +76,15 @@ import { StateService } from '../services/state.service'
|
|||||||
(click)="removeMedia()"
|
(click)="removeMedia()"
|
||||||
>
|
>
|
||||||
<span tuiAvatar="@tui.usb" appearance="secondary"></span>
|
<span tuiAvatar="@tui.usb" appearance="secondary"></span>
|
||||||
<div tuiTitle>
|
<span tuiTitle>
|
||||||
{{ 'Remove Installation Media' | i18n }}
|
<b>{{ 'Remove Installation Media' | i18n }}</b>
|
||||||
<div tuiSubtitle>
|
<span tuiSubtitle>
|
||||||
{{
|
{{
|
||||||
'Remove USB stick or other installation media from your server'
|
'Remove USB stick or other installation media from your server'
|
||||||
| i18n
|
| i18n
|
||||||
}}
|
}}
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
@if (usbRemoved) {
|
@if (usbRemoved) {
|
||||||
<tui-icon icon="@tui.circle-check" class="g-positive" />
|
<tui-icon icon="@tui.circle-check" class="g-positive" />
|
||||||
}
|
}
|
||||||
@@ -99,15 +99,15 @@ import { StateService } from '../services/state.service'
|
|||||||
(click)="acknowledgeMok()"
|
(click)="acknowledgeMok()"
|
||||||
>
|
>
|
||||||
<span tuiAvatar="@tui.shield-check" appearance="secondary"></span>
|
<span tuiAvatar="@tui.shield-check" appearance="secondary"></span>
|
||||||
<div tuiTitle>
|
<span tuiTitle>
|
||||||
{{ 'Secure Boot Enrollment' | i18n }}
|
<b>{{ 'Secure Boot Enrollment' | i18n }}</b>
|
||||||
<div tuiSubtitle>
|
<span tuiSubtitle>
|
||||||
{{
|
{{
|
||||||
'Prepare for Secure Boot key enrollment on the next reboot'
|
'Prepare for Secure Boot key enrollment on the next reboot'
|
||||||
| i18n
|
| i18n
|
||||||
}}
|
}}
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
@if (mokAcknowledged) {
|
@if (mokAcknowledged) {
|
||||||
<tui-icon icon="@tui.circle-check" class="g-positive" />
|
<tui-icon icon="@tui.circle-check" class="g-positive" />
|
||||||
}
|
}
|
||||||
@@ -126,9 +126,9 @@ import { StateService } from '../services/state.service'
|
|||||||
(click)="reboot()"
|
(click)="reboot()"
|
||||||
>
|
>
|
||||||
<span tuiAvatar="@tui.rotate-cw" appearance="secondary"></span>
|
<span tuiAvatar="@tui.rotate-cw" appearance="secondary"></span>
|
||||||
<div tuiTitle>
|
<span tuiTitle>
|
||||||
{{ 'Restart Server' | i18n }}
|
<b>{{ 'Restart Server' | i18n }}</b>
|
||||||
<div tuiSubtitle>
|
<span tuiSubtitle>
|
||||||
@if (rebooting) {
|
@if (rebooting) {
|
||||||
{{ 'Waiting for server to come back online' | i18n }}
|
{{ 'Waiting for server to come back online' | i18n }}
|
||||||
} @else if (rebooted) {
|
} @else if (rebooted) {
|
||||||
@@ -136,8 +136,8 @@ import { StateService } from '../services/state.service'
|
|||||||
} @else {
|
} @else {
|
||||||
{{ 'Restart your server to complete setup' | i18n }}
|
{{ 'Restart your server to complete setup' | i18n }}
|
||||||
}
|
}
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
@if (rebooting) {
|
@if (rebooting) {
|
||||||
<tui-loader />
|
<tui-loader />
|
||||||
} @else if (rebooted) {
|
} @else if (rebooted) {
|
||||||
@@ -147,12 +147,12 @@ import { StateService } from '../services/state.service'
|
|||||||
} @else if (stateService.kiosk) {
|
} @else if (stateService.kiosk) {
|
||||||
<button tuiCell="l" (click)="exitKiosk()">
|
<button tuiCell="l" (click)="exitKiosk()">
|
||||||
<span tuiAvatar="@tui.log-in" appearance="secondary"></span>
|
<span tuiAvatar="@tui.log-in" appearance="secondary"></span>
|
||||||
<div tuiTitle>
|
<span tuiTitle>
|
||||||
{{ 'Continue to Login' | i18n }}
|
<b>{{ 'Continue to Login' | i18n }}</b>
|
||||||
<div tuiSubtitle>
|
<span tuiSubtitle>
|
||||||
{{ 'Proceed to the StartOS login screen' | i18n }}
|
{{ 'Proceed to the StartOS login screen' | i18n }}
|
||||||
</div>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,10 +165,10 @@ import { StateService } from '../services/state.service'
|
|||||||
(click)="openLocalAddress()"
|
(click)="openLocalAddress()"
|
||||||
>
|
>
|
||||||
<span tuiAvatar="@tui.external-link" appearance="secondary"></span>
|
<span tuiAvatar="@tui.external-link" appearance="secondary"></span>
|
||||||
<div tuiTitle>
|
<span tuiTitle>
|
||||||
{{ 'Open Local Address' | i18n }}
|
<b>{{ 'Open Local Address' | i18n }}</b>
|
||||||
<div tuiSubtitle>{{ lanAddress }}</div>
|
<span tuiSubtitle>{{ lanAddress }}</span>
|
||||||
</div>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<app-documentation hidden [lanAddress]="lanAddress" />
|
<app-documentation hidden [lanAddress]="lanAddress" />
|
||||||
@@ -177,12 +177,6 @@ import { StateService } from '../services/state.service'
|
|||||||
</section>
|
</section>
|
||||||
`,
|
`,
|
||||||
styles: `
|
styles: `
|
||||||
.inline-title {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[tuiCell].disabled {
|
[tuiCell].disabled {
|
||||||
opacity: var(--tui-disabled-opacity);
|
opacity: var(--tui-disabled-opacity);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -67,10 +67,6 @@ button:disabled {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[tuiCell]:not([tuiOption]):not(:last-of-type) {
|
|
||||||
box-shadow: 0 calc(0.5rem + 1px) 0 -0.5rem var(--tui-border-normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove in Taiga v5.0
|
// TODO: Remove in Taiga v5.0
|
||||||
[tuiButton] {
|
[tuiButton] {
|
||||||
min-block-size: var(--t-size);
|
min-block-size: var(--t-size);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provideRouter(routes, withRouterConfig({ onSameUrlNavigation: 'reload' })),
|
provideRouter(routes, withRouterConfig({ onSameUrlNavigation: 'reload' })),
|
||||||
provideTaiga({ mode: 'dark' }),
|
provideTaiga({ mode: 'dark' }),
|
||||||
tuiDropdownOptionsProvider({ appearance: 'start-9' }),
|
tuiDropdownOptionsProvider({ appearance: 'start-9' }),
|
||||||
tuiDialogOptionsProvider({ appearance: 'start-9 taiga' }),
|
tuiDialogOptionsProvider({ appearance: 'start-9 taiga', size: 's' }),
|
||||||
{
|
{
|
||||||
provide: PatchDB,
|
provide: PatchDB,
|
||||||
deps: [PatchDbSource, PATCH_CACHE],
|
deps: [PatchDbSource, PATCH_CACHE],
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||||
|
import { TuiIcon } from '@taiga-ui/core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-placeholder',
|
||||||
|
template: `
|
||||||
|
@if (icon(); as icon) {
|
||||||
|
<tui-icon [icon]="icon" />
|
||||||
|
}
|
||||||
|
<ng-content />
|
||||||
|
`,
|
||||||
|
styles: `
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1rem;
|
||||||
|
font: var(--tui-typography-body-l);
|
||||||
|
color: var(--tui-text-tertiary);
|
||||||
|
|
||||||
|
tui-icon {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [TuiIcon],
|
||||||
|
})
|
||||||
|
export class PlaceholderComponent {
|
||||||
|
readonly icon = input<string>()
|
||||||
|
}
|
||||||
@@ -168,7 +168,9 @@ export class DevicesAdd {
|
|||||||
ip,
|
ip,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.dialogs.open(DEVICES_CONFIG, { data: config }).subscribe()
|
this.dialogs
|
||||||
|
.open(DEVICES_CONFIG, { data: config, closable: false })
|
||||||
|
.subscribe()
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|||||||
@@ -3,24 +3,28 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
computed,
|
computed,
|
||||||
inject,
|
inject,
|
||||||
Signal,
|
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
import { ErrorService } from '@start9labs/shared'
|
import { ErrorService } from '@start9labs/shared'
|
||||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||||
import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
|
import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
|
||||||
import { TUI_CONFIRM, TuiNotificationMiddleService } from '@taiga-ui/kit'
|
import {
|
||||||
|
TUI_CONFIRM,
|
||||||
|
TuiNotificationMiddleService,
|
||||||
|
TuiSkeleton,
|
||||||
|
} from '@taiga-ui/kit'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { filter, map } from 'rxjs'
|
import { filter, map } from 'rxjs'
|
||||||
|
import { PlaceholderComponent } from 'src/app/routes/home/components/placeholder'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { TunnelData } from 'src/app/services/patch-db/data-model'
|
import { TunnelData } from 'src/app/services/patch-db/data-model'
|
||||||
import { DEVICES_ADD } from './add'
|
import { DEVICES_ADD } from './add'
|
||||||
import { DEVICES_CONFIG } from './config'
|
import { DEVICES_CONFIG } from './config'
|
||||||
import { MappedDevice, MappedSubnet } from './utils'
|
import { MappedDevice } from './utils'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<table class="g-table">
|
<table class="g-table" [tuiSkeleton]="!devices()">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
@@ -49,7 +53,11 @@ import { MappedDevice, MappedSubnet } from './utils'
|
|||||||
iconStart="@tui.ellipsis-vertical"
|
iconStart="@tui.ellipsis-vertical"
|
||||||
>
|
>
|
||||||
Actions
|
Actions
|
||||||
<tui-data-list *tuiDropdown size="s">
|
<tui-data-list
|
||||||
|
*tuiDropdown="let close"
|
||||||
|
size="s"
|
||||||
|
(click)="close()"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
tuiOption
|
tuiOption
|
||||||
iconStart="@tui.pencil"
|
iconStart="@tui.pencil"
|
||||||
@@ -76,13 +84,23 @@ import { MappedDevice, MappedSubnet } from './utils'
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
<div class="placeholder">No devices</div>
|
<tr>
|
||||||
|
<td colspan="4">
|
||||||
|
<app-placeholder icon="@tui.laptop">No devices</app-placeholder>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [TuiButton, TuiDropdown, TuiDataList],
|
imports: [
|
||||||
|
TuiButton,
|
||||||
|
TuiDropdown,
|
||||||
|
TuiDataList,
|
||||||
|
PlaceholderComponent,
|
||||||
|
TuiSkeleton,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export default class Devices {
|
export default class Devices {
|
||||||
private readonly dialogs = inject(TuiResponsiveDialogService)
|
private readonly dialogs = inject(TuiResponsiveDialogService)
|
||||||
@@ -90,7 +108,7 @@ export default class Devices {
|
|||||||
private readonly loading = inject(TuiNotificationMiddleService)
|
private readonly loading = inject(TuiNotificationMiddleService)
|
||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
|
|
||||||
protected readonly subnets: Signal<readonly MappedSubnet[]> = toSignal(
|
protected readonly subnets = toSignal(
|
||||||
inject<PatchDB<TunnelData>>(PatchDB)
|
inject<PatchDB<TunnelData>>(PatchDB)
|
||||||
.watch$('wg', 'subnets')
|
.watch$('wg', 'subnets')
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -102,11 +120,11 @@ export default class Devices {
|
|||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
{ initialValue: [] },
|
{ initialValue: null },
|
||||||
)
|
)
|
||||||
|
|
||||||
protected readonly devices = computed(() =>
|
protected readonly devices = computed(() =>
|
||||||
this.subnets().flatMap(subnet =>
|
this.subnets()?.flatMap(subnet =>
|
||||||
Object.entries(subnet.clients).map(([ip, { name }]) => ({
|
Object.entries(subnet.clients).map(([ip, { name }]) => ({
|
||||||
subnet: {
|
subnet: {
|
||||||
name: subnet.name,
|
name: subnet.name,
|
||||||
@@ -141,7 +159,7 @@ export default class Devices {
|
|||||||
try {
|
try {
|
||||||
const data = await this.api.showDeviceConfig({ subnet: subnet.range, ip })
|
const data = await this.api.showDeviceConfig({ subnet: subnet.range, ip })
|
||||||
|
|
||||||
this.dialogs.open(DEVICES_CONFIG, { data }).subscribe()
|
this.dialogs.open(DEVICES_CONFIG, { data, closable: false }).subscribe()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
TuiCheckbox,
|
TuiCheckbox,
|
||||||
TuiDialogContext,
|
TuiDialogContext,
|
||||||
TuiError,
|
TuiError,
|
||||||
|
TuiInput,
|
||||||
TuiNumberFormat,
|
TuiNumberFormat,
|
||||||
} from '@taiga-ui/core'
|
} from '@taiga-ui/core'
|
||||||
import {
|
import {
|
||||||
@@ -35,7 +36,7 @@ import { MappedDevice, PortForwardsData } from './utils'
|
|||||||
<form tuiForm [formGroup]="form">
|
<form tuiForm [formGroup]="form">
|
||||||
<tui-textfield>
|
<tui-textfield>
|
||||||
<label tuiLabel>Label</label>
|
<label tuiLabel>Label</label>
|
||||||
<input tuiTextfield formControlName="label" />
|
<input tuiInput formControlName="label" />
|
||||||
</tui-textfield>
|
</tui-textfield>
|
||||||
<tui-error formControlName="label" />
|
<tui-error formControlName="label" />
|
||||||
<tui-textfield tuiChevron>
|
<tui-textfield tuiChevron>
|
||||||
@@ -129,6 +130,7 @@ import { MappedDevice, PortForwardsData } from './utils'
|
|||||||
TuiCheckbox,
|
TuiCheckbox,
|
||||||
TuiValueChanges,
|
TuiValueChanges,
|
||||||
TuiElasticContainer,
|
TuiElasticContainer,
|
||||||
|
TuiInput,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PortForwardsAdd {
|
export class PortForwardsAdd {
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ import {
|
|||||||
} from '@angular/forms'
|
} from '@angular/forms'
|
||||||
import { ErrorService } from '@start9labs/shared'
|
import { ErrorService } from '@start9labs/shared'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
import {
|
import { TuiButton, TuiDialogContext, TuiError, TuiInput } from '@taiga-ui/core'
|
||||||
TuiButton,
|
|
||||||
TuiDialogContext,
|
|
||||||
TuiError,
|
|
||||||
TuiTextfield,
|
|
||||||
} from '@taiga-ui/core'
|
|
||||||
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
|
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
|
||||||
import { TuiForm } from '@taiga-ui/layout'
|
import { TuiForm } from '@taiga-ui/layout'
|
||||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
@@ -27,7 +22,7 @@ export interface EditLabelData {
|
|||||||
<form tuiForm [formGroup]="form">
|
<form tuiForm [formGroup]="form">
|
||||||
<tui-textfield>
|
<tui-textfield>
|
||||||
<label tuiLabel>Label</label>
|
<label tuiLabel>Label</label>
|
||||||
<input tuiTextfield formControlName="label" />
|
<input tuiInput formControlName="label" />
|
||||||
</tui-textfield>
|
</tui-textfield>
|
||||||
<tui-error formControlName="label" />
|
<tui-error formControlName="label" />
|
||||||
<footer>
|
<footer>
|
||||||
@@ -38,7 +33,7 @@ export interface EditLabelData {
|
|||||||
</form>
|
</form>
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [ReactiveFormsModule, TuiButton, TuiError, TuiTextfield, TuiForm],
|
imports: [ReactiveFormsModule, TuiButton, TuiError, TuiInput, TuiForm],
|
||||||
})
|
})
|
||||||
export class PortForwardsEditLabel {
|
export class PortForwardsEditLabel {
|
||||||
private readonly api = inject(ApiService)
|
private readonly api = inject(ApiService)
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ import {
|
|||||||
import {
|
import {
|
||||||
TUI_CONFIRM,
|
TUI_CONFIRM,
|
||||||
TuiNotificationMiddleService,
|
TuiNotificationMiddleService,
|
||||||
|
TuiSkeleton,
|
||||||
TuiSwitch,
|
TuiSwitch,
|
||||||
} from '@taiga-ui/kit'
|
} from '@taiga-ui/kit'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { filter, map } from 'rxjs'
|
import { filter, map } from 'rxjs'
|
||||||
|
import { PlaceholderComponent } from 'src/app/routes/home/components/placeholder'
|
||||||
import { PORT_FORWARDS_ADD } from 'src/app/routes/home/routes/port-forwards/add'
|
import { PORT_FORWARDS_ADD } from 'src/app/routes/home/routes/port-forwards/add'
|
||||||
import { PORT_FORWARDS_EDIT_LABEL } from 'src/app/routes/home/routes/port-forwards/edit-label'
|
import { PORT_FORWARDS_EDIT_LABEL } from 'src/app/routes/home/routes/port-forwards/edit-label'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
@@ -34,7 +36,7 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<table class="g-table">
|
<table class="g-table" [tuiSkeleton]="!portForwards()">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
@@ -84,11 +86,14 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
iconStart="@tui.ellipsis-vertical"
|
iconStart="@tui.ellipsis-vertical"
|
||||||
>
|
>
|
||||||
Actions
|
Actions
|
||||||
<tui-data-list *tuiDropdown size="s">
|
<tui-data-list
|
||||||
|
*tuiDropdown="let close"
|
||||||
|
size="s"
|
||||||
|
(click)="close()"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
tuiOption
|
tuiOption
|
||||||
iconStart="@tui.pencil"
|
iconStart="@tui.pencil"
|
||||||
new
|
|
||||||
(click)="onEditLabel(forward)"
|
(click)="onEditLabel(forward)"
|
||||||
>
|
>
|
||||||
{{ forward.label ? 'Rename' : 'Add label' }}
|
{{ forward.label ? 'Rename' : 'Add label' }}
|
||||||
@@ -96,7 +101,6 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
<button
|
<button
|
||||||
tuiOption
|
tuiOption
|
||||||
iconStart="@tui.trash"
|
iconStart="@tui.trash"
|
||||||
new
|
|
||||||
(click)="onDelete(forward)"
|
(click)="onDelete(forward)"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -106,7 +110,13 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
<div class="placeholder">No port forwards</div>
|
<tr>
|
||||||
|
<td colspan="7">
|
||||||
|
<app-placeholder icon="@tui.globe">
|
||||||
|
No port forwards
|
||||||
|
</app-placeholder>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -120,6 +130,8 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
TuiLoader,
|
TuiLoader,
|
||||||
TuiSwitch,
|
TuiSwitch,
|
||||||
TuiTextfield,
|
TuiTextfield,
|
||||||
|
PlaceholderComponent,
|
||||||
|
TuiSkeleton,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export default class PortForwards {
|
export default class PortForwards {
|
||||||
@@ -128,8 +140,6 @@ export default class PortForwards {
|
|||||||
private readonly loading = inject(TuiNotificationMiddleService)
|
private readonly loading = inject(TuiNotificationMiddleService)
|
||||||
private readonly patch = inject<PatchDB<TunnelData>>(PatchDB)
|
private readonly patch = inject<PatchDB<TunnelData>>(PatchDB)
|
||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
|
|
||||||
private readonly portForwards = toSignal(this.patch.watch$('portForwards'))
|
|
||||||
private readonly ips = toSignal(
|
private readonly ips = toSignal(
|
||||||
this.patch.watch$('gateways').pipe(
|
this.patch.watch$('gateways').pipe(
|
||||||
map(g =>
|
map(g =>
|
||||||
@@ -157,6 +167,7 @@ export default class PortForwards {
|
|||||||
{ initialValue: [] },
|
{ initialValue: [] },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
protected readonly portForwards = toSignal(this.patch.watch$('portForwards'))
|
||||||
protected readonly forwards = computed(() =>
|
protected readonly forwards = computed(() =>
|
||||||
Object.entries(this.portForwards() || {}).map(([source, entry]) => {
|
Object.entries(this.portForwards() || {}).map(([source, entry]) => {
|
||||||
const sourceSplit = source.split(':')
|
const sourceSplit = source.split(':')
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ import { toSignal } from '@angular/core/rxjs-interop'
|
|||||||
import { utils } from '@start9labs/start-sdk'
|
import { utils } from '@start9labs/start-sdk'
|
||||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||||
import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
|
import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
|
||||||
import { TUI_CONFIRM, TuiNotificationMiddleService } from '@taiga-ui/kit'
|
import {
|
||||||
|
TUI_CONFIRM,
|
||||||
|
TuiNotificationMiddleService,
|
||||||
|
TuiSkeleton,
|
||||||
|
} from '@taiga-ui/kit'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { filter, map } from 'rxjs'
|
import { filter, map } from 'rxjs'
|
||||||
|
import { PlaceholderComponent } from 'src/app/routes/home/components/placeholder'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { TunnelData } from 'src/app/services/patch-db/data-model'
|
import { TunnelData } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
@@ -13,7 +18,7 @@ import { SUBNETS_ADD } from './add'
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<table class="g-table">
|
<table class="g-table" [tuiSkeleton]="!subnets()">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
@@ -40,7 +45,11 @@ import { SUBNETS_ADD } from './add'
|
|||||||
iconStart="@tui.ellipsis-vertical"
|
iconStart="@tui.ellipsis-vertical"
|
||||||
>
|
>
|
||||||
Actions
|
Actions
|
||||||
<tui-data-list *tuiDropdown size="s">
|
<tui-data-list
|
||||||
|
*tuiDropdown="let close"
|
||||||
|
size="s"
|
||||||
|
(click)="close()"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
tuiOption
|
tuiOption
|
||||||
iconStart="@tui.pencil"
|
iconStart="@tui.pencil"
|
||||||
@@ -60,20 +69,30 @@ import { SUBNETS_ADD } from './add'
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
} @empty {
|
} @empty {
|
||||||
<div class="placeholder">No subnets</div>
|
<tr>
|
||||||
|
<td colspan="3">
|
||||||
|
<app-placeholder icon="@tui.network">No subnets</app-placeholder>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [TuiButton, TuiDropdown, TuiDataList],
|
imports: [
|
||||||
|
TuiButton,
|
||||||
|
TuiDropdown,
|
||||||
|
TuiDataList,
|
||||||
|
PlaceholderComponent,
|
||||||
|
TuiSkeleton,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export default class Subnets {
|
export default class Subnets {
|
||||||
private readonly dialogs = inject(TuiResponsiveDialogService)
|
private readonly dialogs = inject(TuiResponsiveDialogService)
|
||||||
private readonly api = inject(ApiService)
|
private readonly api = inject(ApiService)
|
||||||
private readonly loading = inject(TuiNotificationMiddleService)
|
private readonly loading = inject(TuiNotificationMiddleService)
|
||||||
|
|
||||||
protected readonly subnets = toSignal<MappedSubnet[], []>(
|
protected readonly subnets = toSignal(
|
||||||
inject<PatchDB<TunnelData>>(PatchDB)
|
inject<PatchDB<TunnelData>>(PatchDB)
|
||||||
.watch$('wg', 'subnets')
|
.watch$('wg', 'subnets')
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -85,7 +104,7 @@ export default class Subnets {
|
|||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
{ initialValue: [] },
|
{ initialValue: null },
|
||||||
)
|
)
|
||||||
|
|
||||||
protected onAdd(): void {
|
protected onAdd(): void {
|
||||||
@@ -111,7 +130,7 @@ export default class Subnets {
|
|||||||
.open(TUI_CONFIRM, { label: 'Are you sure?' })
|
.open(TUI_CONFIRM, { label: 'Are you sure?' })
|
||||||
.pipe(filter(Boolean))
|
.pipe(filter(Boolean))
|
||||||
.subscribe(async () => {
|
.subscribe(async () => {
|
||||||
const subnet = this.subnets()[index]?.range || ''
|
const subnet = this.subnets()?.[index]?.range || ''
|
||||||
const loader = this.loading.open('').subscribe()
|
const loader = this.loading.open('').subscribe()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -125,13 +144,13 @@ export default class Subnets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getNext(): string {
|
private getNext(): string {
|
||||||
const current = this.subnets().map(s => utils.IpNet.parse(s.range))
|
const current = this.subnets()?.map(s => utils.IpNet.parse(s.range))
|
||||||
const suggestion = utils.IpNet.parse('10.59.0.1/24')
|
const suggestion = utils.IpNet.parse('10.59.0.1/24')
|
||||||
|
|
||||||
for (let i = 0; i < 256; i++) {
|
for (let i = 0; i < 256; i++) {
|
||||||
suggestion.octets[2] = Math.floor(Math.random() * 256)
|
suggestion.octets[2] = Math.floor(Math.random() * 256)
|
||||||
if (
|
if (
|
||||||
!current.some(
|
!current?.some(
|
||||||
s => s.contains(suggestion), // inverse check unnecessary since we don't allow subnets smaller than /24
|
s => s.contains(suggestion), // inverse check unnecessary since we don't allow subnets smaller than /24
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Component, computed, inject, Injectable, signal } from '@angular/core'
|
import { computed, inject, Injectable, signal } from '@angular/core'
|
||||||
import { toObservable } from '@angular/core/rxjs-interop'
|
import { toObservable } from '@angular/core/rxjs-interop'
|
||||||
import { ErrorService } from '@start9labs/shared'
|
import { ErrorService } from '@start9labs/shared'
|
||||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
import { T } from '@start9labs/start-sdk'
|
||||||
import { TuiLoader } from '@taiga-ui/core'
|
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
|
||||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
|
||||||
import {
|
import {
|
||||||
catchError,
|
catchError,
|
||||||
EMPTY,
|
EMPTY,
|
||||||
@@ -14,25 +13,16 @@ import {
|
|||||||
switchMap,
|
switchMap,
|
||||||
takeWhile,
|
takeWhile,
|
||||||
} from 'rxjs'
|
} from 'rxjs'
|
||||||
import { T } from '@start9labs/start-sdk'
|
|
||||||
import { ApiService } from './api/api.service'
|
import { ApiService } from './api/api.service'
|
||||||
import { AuthService } from './auth.service'
|
import { AuthService } from './auth.service'
|
||||||
|
|
||||||
@Component({
|
|
||||||
template: '<tui-loader size="xl" [textContent]="text" />',
|
|
||||||
imports: [TuiLoader],
|
|
||||||
})
|
|
||||||
class UpdatingDialog {
|
|
||||||
protected readonly text = 'StartTunnel is updating...'
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class UpdateService {
|
export class UpdateService {
|
||||||
private readonly api = inject(ApiService)
|
private readonly api = inject(ApiService)
|
||||||
private readonly auth = inject(AuthService)
|
private readonly auth = inject(AuthService)
|
||||||
private readonly dialogs = inject(TuiResponsiveDialogService)
|
private readonly loading = inject(TuiNotificationMiddleService)
|
||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
|
|
||||||
readonly result = signal<T.Tunnel.TunnelUpdateResult | null>(null)
|
readonly result = signal<T.Tunnel.TunnelUpdateResult | null>(null)
|
||||||
@@ -106,11 +96,8 @@ export class UpdateService {
|
|||||||
|
|
||||||
private showUpdatingDialog(): void {
|
private showUpdatingDialog(): void {
|
||||||
if (this.updatingDialog) return
|
if (this.updatingDialog) return
|
||||||
this.updatingDialog = this.dialogs
|
this.updatingDialog = this.loading
|
||||||
.open(new PolymorpheusComponent(UpdatingDialog), {
|
.open('StartTunnel is updating...')
|
||||||
closable: false,
|
|
||||||
dismissible: false,
|
|
||||||
})
|
|
||||||
.subscribe({ complete: () => (this.updatingDialog = null) })
|
.subscribe({ complete: () => (this.updatingDialog = null) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,12 +93,6 @@ tui-notification-middle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
padding: 1rem;
|
|
||||||
font: var(--tui-font-text-l);
|
|
||||||
color: var(--tui-text-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
qr-code {
|
qr-code {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -107,3 +101,21 @@ qr-code {
|
|||||||
tui-data-list {
|
tui-data-list {
|
||||||
--tui-text-action: var(--tui-text-primary);
|
--tui-text-action: var(--tui-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[tuiTheme='dark'] tui-notification-middle[style] {
|
||||||
|
&.tui-enter,
|
||||||
|
&.tui-leave {
|
||||||
|
--tui-scale: 0;
|
||||||
|
animation-name: tuiScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background: var(--tui-background-neutral-1);
|
||||||
|
backdrop-filter: blur(1rem);
|
||||||
|
box-shadow: inset 0 1px 1px var(--tui-background-neutral-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
tui-loader svg {
|
||||||
|
stroke: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
|||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
box-shadow: 0 -1px rgba(255, 255, 255, 0.1);
|
box-shadow: 0 -1px rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,8 +171,7 @@ ul {
|
|||||||
.g-table {
|
.g-table {
|
||||||
width: stretch;
|
width: stretch;
|
||||||
border: 1px solid var(--tui-background-neutral-1);
|
border: 1px solid var(--tui-background-neutral-1);
|
||||||
border-spacing: 0;
|
border-collapse: collapse !important;
|
||||||
border-collapse: collapse;
|
|
||||||
border-radius: var(--tui-radius-s);
|
border-radius: var(--tui-radius-s);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: inset 0 0 0 1px var(--tui-background-neutral-1);
|
box-shadow: inset 0 0 0 1px var(--tui-background-neutral-1);
|
||||||
|
|||||||
Reference in New Issue
Block a user