* help ios downlaod .crt and add begin add masked for addresses

* only require and show CA for public domain if addSsl

* fix type and revert i18n const

* feat: add address masking and adjust design (#3088)

* feat: add address masking and adjust design

* update lockfile

* chore: move eye button to actions

* chore: refresh notifications and handle action error

* static width for health check name

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>

* hide certificate authorities tab

* alpha.17

* add waiting health check status

* remove "on" from waiting message

* reject on abort in `.watch`

* id migration: nostr -> nostr-rs-relay

* health check waiting state

* use interface type for launch button

* better wording for masked

* cleaner

* sdk improvements

* fix type error

* fix notification badge issue

---------

Co-authored-by: Alex Inkin <alexander@inkin.ru>
Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Matt Hill
2025-12-31 11:30:57 -07:00
committed by GitHub
parent 96ae532879
commit c9a7f519b9
99 changed files with 1535 additions and 1120 deletions

View File

@@ -1,7 +1,7 @@
import { AsyncPipe } from '@angular/common'
import { Component, ElementRef, inject, input } from '@angular/core'
import { Component, ElementRef, inject } from '@angular/core'
import {
INTERSECTION_ROOT,
WA_INTERSECTION_ROOT,
WaIntersectionObserver,
} from '@ng-web-apis/intersection-observer'
import { WaMutationObserver } from '@ng-web-apis/mutation-observer'
@@ -36,12 +36,7 @@ import { SetupLogsService } from '../../services/setup-logs.service'
NgDompurifyPipe,
TuiScrollbar,
],
providers: [
{
provide: INTERSECTION_ROOT,
useExisting: ElementRef,
},
],
providers: [{ provide: WA_INTERSECTION_ROOT, useExisting: ElementRef }],
})
export class LogsWindowComponent {
readonly logs$ = inject(SetupLogsService)

View File

@@ -40,7 +40,9 @@ import { i18nKey } from '../i18n/i18n.providers'
class="button"
[iconStart]="masked ? '@tui.eye' : '@tui.eye-off'"
(click)="masked = !masked"
></button>
>
{{ 'Reveal/Hide' | i18n }}
</button>
}
</tui-textfield>
<footer class="g-buttons">

View File

@@ -1,25 +1,22 @@
import { Directive, inject, DOCUMENT } from '@angular/core'
import { Directive, DOCUMENT, inject } from '@angular/core'
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
import {
MutationObserverService,
provideMutationObserverInit,
WaMutationObserverService,
} from '@ng-web-apis/mutation-observer'
import { tuiInjectElement } from '@taiga-ui/cdk'
@Directive({
selector: '[safeLinks]',
providers: [
MutationObserverService,
provideMutationObserverInit({
childList: true,
subtree: true,
}),
WaMutationObserverService,
provideMutationObserverInit({ childList: true, subtree: true }),
],
})
export class SafeLinksDirective {
private readonly doc = inject(DOCUMENT)
private readonly el = tuiInjectElement()
private readonly sub = inject(MutationObserverService)
private readonly sub = inject(WaMutationObserverService)
.pipe(takeUntilDestroyed())
.subscribe(() => {
Array.from(this.doc.links)

View File

@@ -90,6 +90,9 @@ export default {
90: 'Root-CA ist vertrauenswürdig!',
91: 'Installierte Dienste',
92: 'Diagnosen für den Tor-Daemon auf diesem Server',
93: 'Fingerabdruck kopieren',
94: 'Warten',
95: 'Warten auf',
96: 'Öffentliche Domain hinzufügen',
97: 'Wird entfernt',
100: 'Nicht gespeicherte Änderungen',
@@ -578,7 +581,7 @@ export default {
611: 'Keine Service-Schnittstellen',
612: 'Grund',
613: 'Private Gateways für die StartOS-Benutzeroberfläche können nicht deaktiviert werden',
614: 'CA-Fingerabdruck',
614: 'Root-CA',
615: 'DHCP-Server',
616: 'DHCP-Server können nicht bearbeitet werden',
617: 'Statisch',
@@ -592,4 +595,5 @@ export default {
625: 'Eine andere Version auswählen',
626: 'Hochladen',
627: 'UI öffnen',
628: 'In Zwischenablage kopiert',
} satisfies i18n

View File

@@ -89,6 +89,9 @@ export const ENGLISH = {
'Root CA Trusted!': 90,
'Installed services': 91, // as in, software services installed on this computer
'Diagnostics for the Tor daemon on this server': 92,
'Copy fingerprint': 93, // as in the fingerprint of a root certificate authority
'Waiting': 94,
'Waiting on': 95, // as in "awaiting"
'Add public domain': 96,
'Removing': 97,
'Unsaved changes': 100,
@@ -577,7 +580,7 @@ export const ENGLISH = {
'No service interfaces': 611, // as in, there are no available interfaces (API, UI, etc) for this software application
'Reason': 612, // as in, an explanation for something
'Cannot disable private gateways for StartOS UI': 613,
'CA fingerprint': 614, // as in, the unique, fixed-length digital identifier generated from a certificate's data using a cryptographic hash function
'Root CA': 614, // as in, the unique, fixed-length digital identifier generated from a certificate's data using a cryptographic hash function
'DHCP Servers': 615,
'Cannot edit DHCP servers': 616,
'Static': 617, // as in, unchanging
@@ -591,4 +594,5 @@ export const ENGLISH = {
'Select another version': 625,
'Upload': 626, // as in, upload a file
'Open UI': 627, // as in, upload a file
'Copied to clipboard': 628,
} as const

View File

@@ -90,6 +90,9 @@ export default {
90: '¡CA raíz confiable!',
91: 'Servicios instalados',
92: 'Diagnósticos para el demonio Tor en este servidor',
93: 'Copiar huella digital',
94: 'Esperando',
95: 'En espera de',
96: 'Agregar dominio público',
97: 'Eliminando',
100: 'Cambios no guardados',
@@ -578,7 +581,7 @@ export default {
611: 'Sin interfaces de servicio',
612: 'Razón',
613: 'No se pueden deshabilitar las puertas de enlace privadas para la interfaz de usuario de StartOS',
614: 'Huella digital de la CA',
614: 'CA raíz',
615: 'Servidores DHCP',
616: 'No se pueden editar los servidores DHCP',
617: 'Estático',
@@ -592,4 +595,5 @@ export default {
625: 'Seleccionar otra versión',
626: 'Subir',
627: 'Abrir UI',
628: 'Copiado al portapapeles',
} satisfies i18n

View File

@@ -90,6 +90,9 @@ export default {
90: 'Certificat racine approuvé !',
91: 'Services installés',
92: 'Diagnostics pour le service Tor sur ce serveur',
93: 'Copier lempreinte',
94: 'En attente',
95: 'En attente de',
96: 'Ajouter un domaine public',
97: 'Suppression',
100: 'Modifications non enregistrées',
@@ -578,7 +581,7 @@ export default {
611: 'Aucune interface de service',
612: 'Raison',
613: "Impossible de désactiver les passerelles privées pour l'interface utilisateur StartOS",
614: 'Empreinte de lAC',
614: 'CA racine',
615: 'Serveurs DHCP',
616: 'Impossible de modifier les serveurs DHCP',
617: 'Statique',
@@ -592,4 +595,5 @@ export default {
625: 'Sélectionner une autre version',
626: 'Téléverser',
627: 'Ouvrir UI',
628: 'Copié dans le presse-papiers',
} satisfies i18n

View File

@@ -90,6 +90,9 @@ export default {
90: 'Główny certyfikat CA zaufany!',
91: 'Zainstalowane usługi',
92: 'Diagnostyka demona Tor na tym serwerze',
93: 'Kopiuj odcisk palca',
94: 'Oczekiwanie',
95: 'Oczekiwanie na',
96: 'Dodaj domenę publiczną',
97: 'Usuwanie',
100: 'Niezapisane zmiany',
@@ -578,7 +581,7 @@ export default {
611: 'Brak interfejsów usług',
612: 'Powód',
613: 'Nie można wyłączyć prywatnych bram dla interfejsu użytkownika StartOS',
614: 'Odcisk palca CA',
614: 'głównego CA',
615: 'Serwery DHCP',
616: 'Nie można edytować serwerów DHCP',
617: 'Statyczny',
@@ -592,4 +595,5 @@ export default {
625: 'Wybierz inną wersję',
626: 'Prześlij',
627: 'Otwórz UI',
628: 'Skopiowano do schowka',
} satisfies i18n

View File

@@ -1,5 +1,5 @@
import { forwardRef, signal } from '@angular/core'
import { tuiCreateToken, tuiProvide } from '@taiga-ui/cdk'
import { forwardRef, InjectionToken, signal } from '@angular/core'
import { tuiProvide } from '@taiga-ui/cdk'
import {
TuiLanguageName,
tuiLanguageSwitcher,
@@ -11,12 +11,19 @@ import { i18nService } from './i18n.service'
export type i18nKey = keyof typeof ENGLISH
export type i18n = Record<(typeof ENGLISH)[i18nKey], string>
export const I18N = tuiCreateToken(signal<i18n | null>(null))
export const I18N_LOADER =
tuiCreateToken<(lang: TuiLanguageName) => Promise<i18n>>()
export const I18N_STORAGE = tuiCreateToken<
export const I18N = new InjectionToken('', {
factory: () => signal<i18n | null>(null),
})
export const I18N_LOADER = new InjectionToken<
(lang: TuiLanguageName) => Promise<i18n>
>('')
export const I18N_STORAGE = new InjectionToken<
(lang: TuiLanguageName) => Promise<void>
>(() => Promise.resolve())
>('', {
factory: () => () => Promise.resolve(),
})
export const I18N_PROVIDERS = [
tuiLanguageSwitcher(async (language: TuiLanguageName): Promise<unknown> => {

View File

@@ -50,7 +50,6 @@ export * from './tokens/relative-url'
export * from './util/base-64'
export * from './util/convert-ansi'
export * from './util/copy-to-clipboard'
export * from './util/format-progress'
export * from './util/get-new-entries'
export * from './util/get-pkg-id'

View File

@@ -1,16 +1,20 @@
import { inject, Injectable } from '@angular/core'
import { Clipboard } from '@angular/cdk/clipboard'
import { TuiAlertService } from '@taiga-ui/core'
import { copyToClipboard } from '../util/copy-to-clipboard'
import { i18nPipe } from '../i18n/i18n.pipe'
@Injectable({ providedIn: 'root' })
export class CopyService {
private readonly clipboard = inject(Clipboard)
private readonly i18n = inject(i18nPipe)
private readonly alerts = inject(TuiAlertService)
async copy(text: string) {
const success = await copyToClipboard(text)
const success = this.clipboard.copy(text)
const message = success ? 'Copied to clipboard' : 'Failed'
const appearance = success ? 'positive' : 'negative'
this.alerts
.open(success ? 'Copied to clipboard!' : 'Failed to copy to clipboard.')
.subscribe()
this.alerts.open(this.i18n.transform(message), { appearance }).subscribe()
}
}

View File

@@ -17,7 +17,9 @@ export class DownloadHTMLService {
const elem = this.document.createElement('a')
elem.setAttribute(
'href',
'data:text/plain;charset=utf-8,' + encodeURIComponent(html),
URL.createObjectURL(
new Blob([html], { type: 'application/octet-stream' }),
),
)
elem.setAttribute('download', filename)
elem.style.display = 'none'

View File

@@ -16,7 +16,6 @@ import {
HttpAngularOptions,
HttpOptions,
LocalHttpResponse,
Method,
} from '../types/http.types'
import { RPCResponse, RPCOptions } from '../types/rpc.types'
import { RELATIVE_URL } from '../tokens/relative-url'
@@ -42,7 +41,7 @@ export class HttpService {
const { method, headers, params, timeout } = opts
return this.httpRequest<RPCResponse<T>>({
method: Method.POST,
method: 'POST',
url: fullUrl || this.relativeUrl,
headers,
body: { method, params },
@@ -73,7 +72,7 @@ export class HttpService {
}
let req: Observable<LocalHttpResponse<T>>
if (method === Method.GET) {
if (method === 'GET') {
req = this.http.get(url, options as any) as any
} else {
req = this.http.post(url, body, options as any) as any

View File

@@ -1,9 +1,6 @@
import { HttpHeaders, HttpResponse } from '@angular/common/http'
export enum Method {
GET = 'GET',
POST = 'POST',
}
export type Method = 'GET' | 'POST'
type ParamPrimitive = string | number | boolean

View File

@@ -1,19 +0,0 @@
export async function copyToClipboard(str: string): Promise<boolean> {
if (window.isSecureContext) {
return navigator.clipboard
.writeText(str)
.then(() => true)
.catch(() => false)
}
const el = document.createElement('textarea')
el.value = str
el.setAttribute('readonly', '')
el.style.position = 'absolute'
el.style.left = '-9999px'
document.body.appendChild(el)
el.select()
const didCopy = document.execCommand('copy')
document.body.removeChild(el)
return didCopy
}