refactor: add file control to form service

This commit is contained in:
waterplea
2025-08-06 19:07:21 +07:00
parent d8d1009417
commit b35a89da29
9 changed files with 126 additions and 119 deletions

View File

@@ -1,6 +1,6 @@
import { forwardRef, Provider } from '@angular/core'
import { TUI_VALIDATION_ERRORS } from '@taiga-ui/kit'
import { IST } from '@start9labs/start-sdk'
import { TUI_FORMAT_ERROR, TUI_VALIDATION_ERRORS } from '@taiga-ui/kit'
import { FormControlComponent } from './form-control.component'
interface ValidatorsPatternError {
@@ -25,6 +25,7 @@ export const FORM_CONTROL_PROVIDERS: Provider[] = [
({ regex }) => String(regex) === String(requiredPattern),
)?.description) ||
'Invalid format',
[TUI_FORMAT_ERROR]: 'Invalid file format',
}),
},
]

View File

@@ -17,9 +17,9 @@ import { knownAuthorities, toAuthorityName } from 'src/app/utils/acme'
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
export type Authority = {
url: string | null
name: string
contact: readonly string[] | null
url?: string | null
contact?: readonly string[] | null
}
export type RemoteAuthority = Authority & { url: string }
@@ -37,11 +37,6 @@ export class AuthorityService {
readonly authorities = toSignal<Authority[]>(
this.patch.watch$('serverInfo', 'network', 'acme').pipe(
map(acme => [
{
url: null,
name: toAuthorityName(null),
contact: null,
},
...Object.keys(acme).map(url => ({
url,
name: toAuthorityName(url),

View File

@@ -19,9 +19,7 @@ import { Authority, AuthorityService } from './authority.service'
@if (authority(); as authority) {
<td>{{ authority.name }}</td>
<td>{{ authority.url || '-' }}</td>
<td class="hidden">
{{ authority.contact ? authority.contact.join(', ') : '-' }}
</td>
<td class="hidden">{{ authority.contact?.join(', ') || '-' }}</td>
<td>
<button
tuiIconButton
@@ -61,6 +59,7 @@ import { Authority, AuthorityService } from './authority.service'
<a
tuiOption
new
download
iconStart="@tui.download"
href="/static/local-root-ca.crt"
>

View File

@@ -9,6 +9,7 @@ import { AuthorityService } from './authority.service'
selector: 'authorities-table',
template: `
<table [appTable]="['Provider', 'URL', 'Contact', null]">
<tr [authority]="{ name: 'Local Root CA' }"></tr>
@for (authority of authorityService.authorities(); track $index) {
<tr [authority]="authority"></tr>
} @empty {

View File

@@ -10,9 +10,7 @@ import { TableComponent } from 'src/app/routes/portal/components/table.component
selector: 'dns',
template: `
<section class="g-card">
<header>
{{ $any('Using IP') | i18n }}
</header>
<header>{{ $any('Using IP') | i18n }}</header>
@let subdomain = context.data.subdomain;
@let wanIp = context.data.gateway.ipInfo?.wanIp || ('Error' | i18n);
@@ -26,9 +24,7 @@ import { TableComponent } from 'src/app/routes/portal/components/table.component
</tr>
<tr>
<td>A</td>
<td>
{{ subdomain ? '*.' + subdomain : '*' }}
</td>
<td>{{ subdomain ? '*.' + subdomain : '*' }}</td>
<td>{{ wanIp }}</td>
<td></td>
</tr>
@@ -37,9 +33,7 @@ import { TableComponent } from 'src/app/routes/portal/components/table.component
@if (context.data.gateway.ipInfo?.deviceType !== 'wireguard') {
<section class="g-card">
<header>
{{ $any('Using Dynamic DNS') | i18n }}
</header>
<header>{{ $any('Using Dynamic DNS') | i18n }}</header>
<table [appTable]="['Type', $any('Host'), 'Value', 'Purpose']">
<tr>
<td>ALIAS</td>
@@ -57,9 +51,16 @@ import { TableComponent } from 'src/app/routes/portal/components/table.component
</section>
}
<button tuiButton size="l" (click)="testDns()">
{{ 'Test' | i18n }}
</button>
<footer class="g-buttons">
<button tuiButton size="l" (click)="testDns()">
{{ 'Test' | i18n }}
</button>
</footer>
`,
styles: `
section {
margin: 1.5rem 0;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiButton, i18nPipe, TableComponent],

View File

@@ -8,6 +8,7 @@ import {
Validators,
} from '@angular/forms'
import { IST, utils } from '@start9labs/start-sdk'
import { tuiCreateFileFormatValidator } from '@taiga-ui/kit'
import Mustache from 'mustache'
@Injectable({
@@ -83,6 +84,7 @@ export class FormService {
currentValue?: any,
): UntypedFormGroup | UntypedFormArray | UntypedFormControl {
let value: any
console.log(spec)
switch (spec.type) {
case 'text':
if (currentValue !== undefined) {
@@ -138,6 +140,8 @@ export class FormService {
case 'multiselect':
value = currentValue === undefined ? spec.default : currentValue
return this.formBuilder.control(value, multiselectValidators(spec))
case 'file':
return this.formBuilder.control(null, fileValidators(spec))
default:
return this.formBuilder.control(null)
}
@@ -236,6 +240,18 @@ function multiselectValidators(spec: IST.ValueSpecMultiselect): ValidatorFn[] {
return validators
}
function fileValidators(spec: IST.ValueSpecFile): ValidatorFn[] {
const validators: ValidatorFn[] = [
tuiCreateFileFormatValidator(spec.extensions.join(',')),
]
if (spec.required) {
validators.push(Validators.required)
}
return validators
}
function listValidators(spec: IST.ValueSpecList): ValidatorFn[] {
const validators: ValidatorFn[] = []
validators.push(listInRange(spec.minLength, spec.maxLength))

View File

@@ -51,11 +51,8 @@ hr {
var(--tui-background-base) 90%,
transparent
);
background-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.1),
transparent
),
background-image:
linear-gradient(to bottom, rgba(255, 255, 255, 0.1), transparent),
linear-gradient(to bottom, rgba(255, 255, 255, 0.1), transparent);
box-shadow:
0 0.25rem 0.125rem rgba(0, 0, 0, 0.25),
@@ -97,11 +94,8 @@ hr {
overflow: hidden;
color: var(--tui-text-primary);
background-color: color-mix(in hsl, var(--start9-base-1) 50%, transparent);
background-image: linear-gradient(
to bottom,
var(--tui-background-neutral-2),
transparent
),
background-image:
linear-gradient(to bottom, var(--tui-background-neutral-2), transparent),
linear-gradient(to bottom, var(--tui-background-neutral-2), transparent);
background-size: 1px 100%;
background-repeat: no-repeat;
@@ -293,8 +287,8 @@ hr {
.g-buttons {
display: flex;
justify-content: flex-end;
gap: 16px;
margin-top: 24px;
gap: 1rem;
margin-top: 1.5rem;
}
.g-toggle {