fix password input for backups and add adjective noun randomizer

This commit is contained in:
Matt Hill
2026-03-23 08:58:37 -06:00
parent f60a1a9ed0
commit 3d45234aae
11 changed files with 9115 additions and 71 deletions

View File

@@ -1,7 +1,8 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { TuiAutoFocus } from '@taiga-ui/cdk'
import { TuiButton, TuiDialogContext, TuiInput } from '@taiga-ui/core'
import { TuiButton, TuiDialogContext, TuiIcon, TuiInput } from '@taiga-ui/core'
import { TuiPassword } from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { i18nPipe } from '../i18n/i18n.pipe'
import { i18nKey } from '../i18n/i18n.providers'
@@ -27,22 +28,12 @@ import { i18nKey } from '../i18n/i18n.providers'
tuiAutoFocus
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="value"
[class.masked]="options.useMask && masked && value"
[placeholder]="options.placeholder || ''"
[type]="options.useMask ? 'password' : 'text'"
[autocomplete]="options.useMask ? 'off' : ''"
/>
@if (options.useMask) {
<button
tuiIconButton
type="button"
appearance="icon"
title="Toggle masking"
size="xs"
class="button"
[iconStart]="masked ? '@tui.eye' : '@tui.eye-off'"
(click)="masked = !masked"
>
{{ 'Reveal/Hide' | i18n }}
</button>
<tui-icon tuiPassword />
}
</tui-textfield>
@if (error) {
@@ -71,24 +62,22 @@ import { i18nKey } from '../i18n/i18n.providers'
.error {
color: var(--tui-status-negative);
}
.button {
pointer-events: auto;
margin-left: 0.25rem;
}
.masked {
-webkit-text-security: disc;
}
`,
imports: [FormsModule, TuiButton, TuiInput, TuiAutoFocus, i18nPipe],
imports: [
FormsModule,
TuiButton,
TuiIcon,
TuiInput,
TuiPassword,
TuiAutoFocus,
i18nPipe,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PromptModal {
private readonly context =
injectContext<TuiDialogContext<string, PromptOptions>>()
masked = this.options.useMask
value = this.options.initialValue || ''
error = ''

View File

@@ -57,3 +57,5 @@ export * from './util/unused'
export * from './util/keyboards'
export * from './util/languages'
export * from './util/hostname'
export * from './util/random-server-name'
export * from './util/server-name-validator'

View File

@@ -1,8 +1,8 @@
/**
* TS port of the Rust `normalize()` function from core/src/hostname.rs.
* Converts a free-text name into a valid hostname.
* Strips non-alphanumeric characters and produces a raw hostname string.
*/
export function normalizeHostname(name: string): string {
export function normalizeHostnameRaw(name: string): string {
let prevWasDash = true
let normalized = ''
@@ -20,5 +20,12 @@ export function normalizeHostname(name: string): string {
normalized = normalized.slice(0, -1)
}
return normalized || 'start9'
return normalized
}
/**
* Converts a free-text name into a valid hostname, with 'start9' fallback.
*/
export function normalizeHostname(name: string): string {
return normalizeHostnameRaw(name) || 'start9'
}

View File

@@ -0,0 +1,16 @@
import { ADJECTIVES, NOUNS } from './server-name-words'
/**
* Generates a random server name in "Adjective Noun" format.
* Uses the same word lists as the Rust backend (core/src/assets/).
*/
export function randomServerName(): string {
const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)]!
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)]!
return `${capitalize(adj)} ${capitalize(noun)}`
}
function capitalize(word: string): string {
return word.charAt(0).toUpperCase() + word.slice(1)
}

View File

@@ -0,0 +1,26 @@
import { AbstractControl, ValidationErrors } from '@angular/forms'
import { normalizeHostnameRaw } from './hostname'
// Matches backend normalize() threshold (core/src/hostname.rs:109)
const HOSTNAME_MIN_LENGTH = 4
// DNS label limit
const HOSTNAME_MAX_LENGTH = 63
export function serverNameValidator(
control: AbstractControl,
): ValidationErrors | null {
const name = (control.value || '').trim()
if (!name) return null
const hostname = normalizeHostnameRaw(name)
if (hostname.length < HOSTNAME_MIN_LENGTH) {
return { hostnameMinLength: true }
}
if (hostname.length > HOSTNAME_MAX_LENGTH) {
return { hostnameMaxLength: true }
}
return null
}

File diff suppressed because it is too large Load Diff