mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
remove duplicate dir with pages
This commit is contained in:
@@ -1,18 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
import { ACMEPage } from './acme.page'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: ACMEPage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [CommonModule, IonicModule, RouterModule.forChild(routes)],
|
|
||||||
declarations: [ACMEPage],
|
|
||||||
})
|
|
||||||
export class ACMEPageModule {}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button defaultHref="system"></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>ACME</ion-title>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content class="ion-padding-top with-widgets">
|
|
||||||
<ion-item-group>
|
|
||||||
<!-- always -->
|
|
||||||
<ion-item>
|
|
||||||
<ion-label>
|
|
||||||
<h2>
|
|
||||||
Register with one or more ACME providers such as Let's Encrypt in
|
|
||||||
order to generate SSL (https) certificates on-demand for clearnet
|
|
||||||
hosting
|
|
||||||
<a [href]="docsUrl" target="_blank" rel="noreferrer">
|
|
||||||
View instructions
|
|
||||||
</a>
|
|
||||||
</h2>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item-divider>Saved Providers</ion-item-divider>
|
|
||||||
|
|
||||||
<ng-container *ngIf="acme$ | async as acme">
|
|
||||||
<ion-item button detail="false" (click)="addAcme(acme)">
|
|
||||||
<ion-icon slot="start" name="add" color="dark"></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<b>Add Provider</b>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item *ngFor="let provider of acme">
|
|
||||||
<ion-icon slot="start" name="finger-print" size="medium"></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2>{{ toAcmeName(provider.url) }}</h2>
|
|
||||||
<p>Contact: {{ provider.contactString }}</p>
|
|
||||||
</ion-label>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button (click)="editAcme(provider.url, provider.contact)">
|
|
||||||
<ion-icon slot="start" name="pencil"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
<ion-button (click)="removeAcme(provider.url)">
|
|
||||||
<ion-icon slot="start" name="trash-outline"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-item>
|
|
||||||
</ng-container>
|
|
||||||
</ion-item-group>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
import { Component } from '@angular/core'
|
|
||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import { DataModel } from '../../../services/patch-db/data-model'
|
|
||||||
import { FormDialogService } from '../../../services/form-dialog.service'
|
|
||||||
import { FormComponent } from '../../../components/form.component'
|
|
||||||
import { configBuilderToSpec } from '../../../util/configBuilderToSpec'
|
|
||||||
import { ISB, utils } from '@start9labs/start-sdk'
|
|
||||||
import { knownACME, toAcmeName } from 'src/app/util/acme'
|
|
||||||
import { map } from 'rxjs'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'acme',
|
|
||||||
templateUrl: 'acme.page.html',
|
|
||||||
styleUrls: ['acme.page.scss'],
|
|
||||||
})
|
|
||||||
export class ACMEPage {
|
|
||||||
readonly docsUrl = 'https://docs.start9.com/0.3.6/user-manual/acme'
|
|
||||||
|
|
||||||
acme$ = this.patch.watch$('serverInfo', 'acme').pipe(
|
|
||||||
map(acme => {
|
|
||||||
const providerUrls = Object.keys(acme)
|
|
||||||
return providerUrls.map(url => {
|
|
||||||
const contact = acme[url].contact.map(mailto =>
|
|
||||||
mailto.replace('mailto:', ''),
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
url,
|
|
||||||
contact,
|
|
||||||
contactString: contact.join(', '),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
toAcmeName = toAcmeName
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly loader: LoadingService,
|
|
||||||
private readonly errorService: ErrorService,
|
|
||||||
private readonly api: ApiService,
|
|
||||||
private readonly patch: PatchDB<DataModel>,
|
|
||||||
private readonly formDialog: FormDialogService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async addAcme(
|
|
||||||
providers: {
|
|
||||||
url: string
|
|
||||||
contact: string[]
|
|
||||||
contactString: string
|
|
||||||
}[],
|
|
||||||
) {
|
|
||||||
this.formDialog.open(FormComponent, {
|
|
||||||
label: 'Add ACME Provider',
|
|
||||||
data: {
|
|
||||||
spec: await configBuilderToSpec(
|
|
||||||
getAddAcmeSpec(providers.map(p => p.url)),
|
|
||||||
),
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: 'Save',
|
|
||||||
handler: async (
|
|
||||||
val: ReturnType<typeof getAddAcmeSpec>['_TYPE'],
|
|
||||||
) => {
|
|
||||||
const providerUrl =
|
|
||||||
val.provider.selection === 'other'
|
|
||||||
? val.provider.value.url
|
|
||||||
: val.provider.selection
|
|
||||||
|
|
||||||
return this.saveAcme(providerUrl, val.contact)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async editAcme(provider: string, contact: string[]) {
|
|
||||||
this.formDialog.open(FormComponent, {
|
|
||||||
label: 'Edit ACME Provider',
|
|
||||||
data: {
|
|
||||||
spec: await configBuilderToSpec(editAcmeSpec),
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: 'Save',
|
|
||||||
handler: async (val: typeof editAcmeSpec._TYPE) =>
|
|
||||||
this.saveAcme(provider, val.contact),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
value: { contact },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async removeAcme(provider: string) {
|
|
||||||
const loader = this.loader.open('Removing').subscribe()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.api.removeAcme({ provider })
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errorService.handleError(e)
|
|
||||||
} finally {
|
|
||||||
loader.unsubscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async saveAcme(providerUrl: string, contact: string[]) {
|
|
||||||
console.log(providerUrl, contact)
|
|
||||||
const loader = this.loader.open('Saving').subscribe()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.api.initAcme({
|
|
||||||
provider: new URL(providerUrl).href,
|
|
||||||
contact: contact.map(address => `mailto:${address}`),
|
|
||||||
})
|
|
||||||
return true
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errorService.handleError(e)
|
|
||||||
return false
|
|
||||||
} finally {
|
|
||||||
loader.unsubscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const emailListSpec = ISB.Value.list(
|
|
||||||
ISB.List.text(
|
|
||||||
{
|
|
||||||
name: 'Contact Emails',
|
|
||||||
description:
|
|
||||||
'Needed to obtain a certificate from a Certificate Authority',
|
|
||||||
minLength: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
inputmode: 'email',
|
|
||||||
patterns: [utils.Patterns.email],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
function getAddAcmeSpec(providers: string[]) {
|
|
||||||
const availableAcme = knownACME.filter(acme => !providers.includes(acme.url))
|
|
||||||
|
|
||||||
return ISB.InputSpec.of({
|
|
||||||
provider: ISB.Value.union(
|
|
||||||
{ name: 'Provider', default: (availableAcme[0]?.url as any) || 'other' },
|
|
||||||
ISB.Variants.of({
|
|
||||||
...availableAcme.reduce(
|
|
||||||
(obj, curr) => ({
|
|
||||||
...obj,
|
|
||||||
[curr.url]: {
|
|
||||||
name: curr.name,
|
|
||||||
spec: ISB.InputSpec.of({}),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
other: {
|
|
||||||
name: 'Other',
|
|
||||||
spec: ISB.InputSpec.of({
|
|
||||||
url: ISB.Value.text({
|
|
||||||
name: 'URL',
|
|
||||||
default: null,
|
|
||||||
required: true,
|
|
||||||
inputmode: 'url',
|
|
||||||
patterns: [utils.Patterns.url],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
contact: emailListSpec,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const editAcmeSpec = ISB.InputSpec.of({
|
|
||||||
contact: emailListSpec,
|
|
||||||
})
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { Routes, RouterModule } from '@angular/router'
|
|
||||||
import { TuiInputModule } from '@taiga-ui/kit'
|
|
||||||
import {
|
|
||||||
TuiNotificationModule,
|
|
||||||
TuiTextfieldControllerModule,
|
|
||||||
} from '@taiga-ui/core'
|
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
|
||||||
import { EmailPage } from './email.page'
|
|
||||||
import { FormModule } from 'src/app/components/form/form.module'
|
|
||||||
import { IonicModule } from '@ionic/angular'
|
|
||||||
import { TuiErrorModule, TuiModeModule } from '@taiga-ui/core'
|
|
||||||
import { TuiAppearanceModule, TuiButtonModule } from '@taiga-ui/experimental'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: EmailPage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
TuiButtonModule,
|
|
||||||
TuiInputModule,
|
|
||||||
FormModule,
|
|
||||||
TuiNotificationModule,
|
|
||||||
TuiTextfieldControllerModule,
|
|
||||||
TuiAppearanceModule,
|
|
||||||
TuiModeModule,
|
|
||||||
TuiErrorModule,
|
|
||||||
],
|
|
||||||
declarations: [EmailPage],
|
|
||||||
})
|
|
||||||
export class EmailPageModule {}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title>Email</ion-title>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button defaultHref="system"></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content class="ion-padding">
|
|
||||||
<tui-notification>
|
|
||||||
Fill out the form below to connect to an external SMTP server. With your
|
|
||||||
permission, installed services can use the SMTP server to send emails. To
|
|
||||||
grant permission to a particular service, visit that service's "Actions"
|
|
||||||
page. Not all services support sending emails.
|
|
||||||
<a
|
|
||||||
href="https://docs.start9.com/latest/user-manual/0.3.5.x/smtp"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
View instructions
|
|
||||||
</a>
|
|
||||||
</tui-notification>
|
|
||||||
<ng-container *ngIf="form$ | async as form">
|
|
||||||
<form [formGroup]="form" [style.text-align]="'right'">
|
|
||||||
<h3 class="g-title">SMTP Credentials</h3>
|
|
||||||
<form-group
|
|
||||||
*ngIf="spec | async as resolved"
|
|
||||||
[spec]="resolved"
|
|
||||||
></form-group>
|
|
||||||
<button
|
|
||||||
*ngIf="isSaved"
|
|
||||||
tuiButton
|
|
||||||
appearance="destructive"
|
|
||||||
[style.margin-top.rem]="1"
|
|
||||||
[style.margin-right.rem]="1"
|
|
||||||
(click)="save(null)"
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
tuiButton
|
|
||||||
[style.margin-top.rem]="1"
|
|
||||||
[disabled]="form.invalid"
|
|
||||||
(click)="save(form.value)"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<form [style.text-align]="'right'">
|
|
||||||
<h3 class="g-title">Send Test Email</h3>
|
|
||||||
<tui-input
|
|
||||||
[(ngModel)]="testAddress"
|
|
||||||
[ngModelOptions]="{ standalone: true }"
|
|
||||||
>
|
|
||||||
To Address
|
|
||||||
<input tuiTextfield inputmode="email" />
|
|
||||||
</tui-input>
|
|
||||||
<button
|
|
||||||
tuiButton
|
|
||||||
appearance="secondary"
|
|
||||||
[style.margin-top.rem]="1"
|
|
||||||
[disabled]="!testAddress || form.invalid"
|
|
||||||
(click)="sendTestEmail(form.value)"
|
|
||||||
>
|
|
||||||
Send
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</ng-container>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
form {
|
|
||||||
padding-top: 24px;
|
|
||||||
margin: auto;
|
|
||||||
max-width: 30rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
|
||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
|
||||||
import { IST, inputSpec } from '@start9labs/start-sdk'
|
|
||||||
import { TuiDialogService } from '@taiga-ui/core'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { switchMap, tap } from 'rxjs'
|
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|
||||||
import { FormService } from 'src/app/services/form.service'
|
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
|
||||||
import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec'
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'email-page',
|
|
||||||
templateUrl: './email.page.html',
|
|
||||||
styleUrls: ['./email.page.scss'],
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
})
|
|
||||||
export class EmailPage {
|
|
||||||
private readonly dialogs = inject(TuiDialogService)
|
|
||||||
private readonly loader = inject(LoadingService)
|
|
||||||
private readonly errorService = inject(ErrorService)
|
|
||||||
private readonly formService = inject(FormService)
|
|
||||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
|
||||||
private readonly api = inject(ApiService)
|
|
||||||
|
|
||||||
isSaved = false
|
|
||||||
testAddress = ''
|
|
||||||
|
|
||||||
readonly spec: Promise<IST.InputSpec> = configBuilderToSpec(
|
|
||||||
inputSpec.constants.customSmtp,
|
|
||||||
)
|
|
||||||
readonly form$ = this.patch.watch$('serverInfo', 'smtp').pipe(
|
|
||||||
tap(value => (this.isSaved = !!value)),
|
|
||||||
switchMap(async value =>
|
|
||||||
this.formService.createForm(await this.spec, value),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
async save(
|
|
||||||
value: typeof inputSpec.constants.customSmtp._TYPE | null,
|
|
||||||
): Promise<void> {
|
|
||||||
const loader = this.loader.open('Saving...').subscribe()
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (value) {
|
|
||||||
await this.api.setSmtp(value)
|
|
||||||
this.isSaved = true
|
|
||||||
} else {
|
|
||||||
await this.api.clearSmtp({})
|
|
||||||
this.isSaved = false
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
this.errorService.handleError(e)
|
|
||||||
} finally {
|
|
||||||
loader.unsubscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async sendTestEmail(value: typeof inputSpec.constants.customSmtp._TYPE) {
|
|
||||||
const loader = this.loader.open('Sending email...').subscribe()
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.api.testSmtp({
|
|
||||||
to: this.testAddress,
|
|
||||||
...value,
|
|
||||||
})
|
|
||||||
} catch (e: any) {
|
|
||||||
return this.errorService.handleError(e)
|
|
||||||
} finally {
|
|
||||||
loader.unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.dialogs
|
|
||||||
.open(
|
|
||||||
`A test email has been sent to ${this.testAddress}.<br /><br /><b>Check your spam folder and mark as not spam</b>`,
|
|
||||||
{
|
|
||||||
label: 'Success',
|
|
||||||
size: 's',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.subscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user