mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
add smtp to frontend (#2802)
* add smtp to frontend * left align headers * just email * change all to email * fix test-smtp api * types * fix email from and login address handling --------- Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use futures::{FutureExt};
|
use futures::FutureExt;
|
||||||
use tokio::signal::unix::signal;
|
use tokio::signal::unix::signal;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
|
|||||||
@@ -302,9 +302,9 @@ pub fn server<C: Context>() -> ParentHandler<C> {
|
|||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
"test-smtp",
|
"test-smtp",
|
||||||
from_fn_async(system::test_system_smtp)
|
from_fn_async(system::test_smtp)
|
||||||
.no_display()
|
.no_display()
|
||||||
.with_about("Send test email using system smtp server and credentials")
|
.with_about("Send test email using provided smtp server and credentials")
|
||||||
.with_call_remote::<CliContext>()
|
.with_call_remote::<CliContext>()
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use clap::Parser;
|
|||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use imbl::vector;
|
use imbl::vector;
|
||||||
use mail_send::mail_builder::MessageBuilder;
|
use mail_send::mail_builder::{self, MessageBuilder};
|
||||||
use mail_send::SmtpClientBuilder;
|
use mail_send::SmtpClientBuilder;
|
||||||
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler};
|
use rpc_toolkit::{from_fn_async, Context, Empty, HandlerExt, ParentHandler};
|
||||||
use rustls::crypto::CryptoProvider;
|
use rustls::crypto::CryptoProvider;
|
||||||
@@ -878,15 +878,33 @@ pub async fn clear_system_smtp(ctx: RpcContext) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub async fn test_system_smtp(
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Parser, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct TestSmtpParams {
|
||||||
|
#[arg(long)]
|
||||||
|
pub server: String,
|
||||||
|
#[arg(long)]
|
||||||
|
pub port: u16,
|
||||||
|
#[arg(long)]
|
||||||
|
pub from: String,
|
||||||
|
#[arg(long)]
|
||||||
|
pub to: String,
|
||||||
|
#[arg(long)]
|
||||||
|
pub login: String,
|
||||||
|
#[arg(long)]
|
||||||
|
pub password: Option<String>,
|
||||||
|
}
|
||||||
|
pub async fn test_smtp(
|
||||||
_: RpcContext,
|
_: RpcContext,
|
||||||
SmtpValue {
|
TestSmtpParams {
|
||||||
server,
|
server,
|
||||||
port,
|
port,
|
||||||
from,
|
from,
|
||||||
|
to,
|
||||||
login,
|
login,
|
||||||
password,
|
password,
|
||||||
}: SmtpValue,
|
}: TestSmtpParams,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
use rustls_pki_types::pem::PemObject;
|
use rustls_pki_types::pem::PemObject;
|
||||||
|
|
||||||
@@ -913,17 +931,35 @@ pub async fn test_system_smtp(
|
|||||||
);
|
);
|
||||||
let client = SmtpClientBuilder::new_with_tls_config(server, port, cfg)
|
let client = SmtpClientBuilder::new_with_tls_config(server, port, cfg)
|
||||||
.implicit_tls(false)
|
.implicit_tls(false)
|
||||||
.credentials((login.clone().split_once("@").unwrap().0.to_owned(), pass_val));
|
.credentials((login.split("@").next().unwrap().to_owned(), pass_val));
|
||||||
|
|
||||||
|
fn parse_address<'a>(addr: &'a str) -> mail_builder::headers::address::Address<'a> {
|
||||||
|
if addr.find("<").map_or(false, |start| {
|
||||||
|
addr.find(">").map_or(false, |end| start < end)
|
||||||
|
}) {
|
||||||
|
addr.split_once("<")
|
||||||
|
.map(|(name, addr)| (name.trim(), addr.strip_suffix(">").unwrap_or(addr)))
|
||||||
|
.unwrap()
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
addr.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let message = MessageBuilder::new()
|
let message = MessageBuilder::new()
|
||||||
.from((from.clone(), login.clone()))
|
.from(parse_address(&from))
|
||||||
.to(vec![(from, login)])
|
.to(parse_address(&to))
|
||||||
.subject("StartOS Test Email")
|
.subject("StartOS Test Email")
|
||||||
.text_body("This is a test email sent from your StartOS Server");
|
.text_body("This is a test email sent from your StartOS Server");
|
||||||
client
|
client
|
||||||
.connect()
|
.connect()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Error::new(eyre!("mail-send connection error: {:?}", e), ErrorKind::Unknown))?
|
.map_err(|e| {
|
||||||
|
Error::new(
|
||||||
|
eyre!("mail-send connection error: {:?}", e),
|
||||||
|
ErrorKind::Unknown,
|
||||||
|
)
|
||||||
|
})?
|
||||||
.send(message)
|
.send(message)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Error::new(eyre!("mail-send send error: {:?}", e), ErrorKind::Unknown))?;
|
.map_err(|e| Error::new(eyre!("mail-send send error: {:?}", e), ErrorKind::Unknown))?;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use futures::future::BoxFuture;
|
|||||||
use futures::{Future, FutureExt};
|
use futures::{Future, FutureExt};
|
||||||
use imbl::Vector;
|
use imbl::Vector;
|
||||||
use imbl_value::{to_value, InternedString};
|
use imbl_value::{to_value, InternedString};
|
||||||
use patch_db::json_ptr::{ ROOT};
|
use patch_db::json_ptr::ROOT;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ export const customSmtp = InputSpec.of<InputSpecOf<SmtpValue>, never>({
|
|||||||
name: "From Address",
|
name: "From Address",
|
||||||
required: true,
|
required: true,
|
||||||
default: null,
|
default: null,
|
||||||
placeholder: "<name>test@example.com",
|
placeholder: "Example Name <test@example.com>",
|
||||||
inputmode: "email",
|
inputmode: "email",
|
||||||
patterns: [Patterns.email],
|
patterns: [Patterns.emailWithName],
|
||||||
}),
|
}),
|
||||||
login: Value.text({
|
login: Value.text({
|
||||||
name: "Login",
|
name: "Login",
|
||||||
|
|||||||
10
sdk/base/lib/osBindings/TestSmtpParams.ts
Normal file
10
sdk/base/lib/osBindings/TestSmtpParams.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||||
|
|
||||||
|
export type TestSmtpParams = {
|
||||||
|
server: string
|
||||||
|
port: number
|
||||||
|
from: string
|
||||||
|
to: string
|
||||||
|
login: string
|
||||||
|
password: string | null
|
||||||
|
}
|
||||||
@@ -186,6 +186,7 @@ export { SignAssetParams } from "./SignAssetParams"
|
|||||||
export { SignerInfo } from "./SignerInfo"
|
export { SignerInfo } from "./SignerInfo"
|
||||||
export { SmtpValue } from "./SmtpValue"
|
export { SmtpValue } from "./SmtpValue"
|
||||||
export { StartStop } from "./StartStop"
|
export { StartStop } from "./StartStop"
|
||||||
|
export { TestSmtpParams } from "./TestSmtpParams"
|
||||||
export { UnsetPublicParams } from "./UnsetPublicParams"
|
export { UnsetPublicParams } from "./UnsetPublicParams"
|
||||||
export { UpdatingState } from "./UpdatingState"
|
export { UpdatingState } from "./UpdatingState"
|
||||||
export { VerifyCifsParams } from "./VerifyCifsParams"
|
export { VerifyCifsParams } from "./VerifyCifsParams"
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ export const email: Pattern = {
|
|||||||
description: "Must be a valid email address",
|
description: "Must be a valid email address",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const emailWithName: Pattern = {
|
||||||
|
regex: regexes.emailWithName.source,
|
||||||
|
description: "Must be a valid email address, optionally with a name",
|
||||||
|
}
|
||||||
|
|
||||||
export const base64: Pattern = {
|
export const base64: Pattern = {
|
||||||
regex: regexes.base64.source,
|
regex: regexes.base64.source,
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -26,8 +26,12 @@ export const torUrl =
|
|||||||
// https://ihateregex.io/expr/ascii/
|
// https://ihateregex.io/expr/ascii/
|
||||||
export const ascii = /^[ -~]*$/
|
export const ascii = /^[ -~]*$/
|
||||||
|
|
||||||
//https://ihateregex.io/expr/email/
|
// https://www.regular-expressions.info/email.html
|
||||||
export const email = /[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+/
|
export const email = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/
|
||||||
|
|
||||||
|
export const emailWithName = new RegExp(
|
||||||
|
`(${email.source})|([^<]*<(${email.source})>)`,
|
||||||
|
)
|
||||||
|
|
||||||
//https://rgxdb.com/r/1NUN74O6
|
//https://rgxdb.com/r/1NUN74O6
|
||||||
export const base64 =
|
export const base64 =
|
||||||
|
|||||||
28
web/package-lock.json
generated
28
web/package-lock.json
generated
@@ -116,7 +116,33 @@
|
|||||||
"rxjs": ">=7.0.0"
|
"rxjs": ">=7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"../sdk/baseDist": {},
|
"../sdk/baseDist": {
|
||||||
|
"name": "@start9labs/start-sdk-base",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@iarna/toml": "^2.2.5",
|
||||||
|
"@noble/curves": "^1.4.0",
|
||||||
|
"@noble/hashes": "^1.4.0",
|
||||||
|
"isomorphic-fetch": "^3.0.0",
|
||||||
|
"lodash.merge": "^4.6.2",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
|
"ts-matches": "^6.1.0",
|
||||||
|
"yaml": "^2.2.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^29.4.0",
|
||||||
|
"@types/lodash.merge": "^4.6.2",
|
||||||
|
"@types/mime-types": "^2.1.4",
|
||||||
|
"jest": "^29.4.3",
|
||||||
|
"peggy": "^3.0.2",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"ts-jest": "^29.0.5",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"ts-pegjs": "^4.2.1",
|
||||||
|
"tsx": "^4.7.1",
|
||||||
|
"typescript": "^5.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@adobe/css-tools": {
|
"node_modules/@adobe/css-tools": {
|
||||||
"version": "4.4.1",
|
"version": "4.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.1.tgz",
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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 {}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
form {
|
||||||
|
padding-top: 24px;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 30rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
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) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,10 +76,15 @@ const routes: Routes = [
|
|||||||
import('./ssh-keys/ssh-keys.module').then(m => m.SSHKeysPageModule),
|
import('./ssh-keys/ssh-keys.module').then(m => m.SSHKeysPageModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'wireless',
|
path: 'wifi',
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('./wifi/wifi.module').then(m => m.WifiPageModule),
|
import('./wifi/wifi.module').then(m => m.WifiPageModule),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'email',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./email/email.module').then(m => m.EmailPageModule),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -463,6 +463,15 @@ export class ServerShowPage {
|
|||||||
detail: true,
|
detail: true,
|
||||||
disabled$: of(false),
|
disabled$: of(false),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Email',
|
||||||
|
description: 'Connect to an external SMTP server for sending emails',
|
||||||
|
icon: 'mail-outline',
|
||||||
|
action: () =>
|
||||||
|
this.navCtrl.navigateForward(['email'], { relativeTo: this.route }),
|
||||||
|
detail: true,
|
||||||
|
disabled$: of(false),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'SSH',
|
title: 'SSH',
|
||||||
description:
|
description:
|
||||||
@@ -474,12 +483,12 @@ export class ServerShowPage {
|
|||||||
disabled$: of(false),
|
disabled$: of(false),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Wireless',
|
title: 'WiFi',
|
||||||
description:
|
description:
|
||||||
'Connect your server to WiFi instead of Ethernet (not recommended)',
|
'Connect your server to WiFi instead of Ethernet (not recommended)',
|
||||||
icon: 'wifi',
|
icon: 'wifi',
|
||||||
action: () =>
|
action: () =>
|
||||||
this.navCtrl.navigateForward(['wireless'], {
|
this.navCtrl.navigateForward(['wifi'], {
|
||||||
relativeTo: this.route,
|
relativeTo: this.route,
|
||||||
}),
|
}),
|
||||||
detail: true,
|
detail: true,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<ion-buttons slot="start">
|
<ion-buttons slot="start">
|
||||||
<ion-back-button defaultHref="system"></ion-back-button>
|
<ion-back-button defaultHref="system"></ion-back-button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
<ion-title>Wireless Settings</ion-title>
|
<ion-title>WiFi Settings</ion-title>
|
||||||
<ion-buttons slot="end" *ngIf="hasWifi$ | async">
|
<ion-buttons slot="end" *ngIf="hasWifi$ | async">
|
||||||
<ion-button (click)="getWifi()">
|
<ion-button (click)="getWifi()">
|
||||||
Refresh
|
Refresh
|
||||||
|
|||||||
@@ -102,6 +102,17 @@ export module RR {
|
|||||||
} // net.tor.reset
|
} // net.tor.reset
|
||||||
export type ResetTorRes = null
|
export type ResetTorRes = null
|
||||||
|
|
||||||
|
// smtp
|
||||||
|
|
||||||
|
export type SetSMTPReq = T.SmtpValue // server.set-smtp
|
||||||
|
export type SetSMTPRes = null
|
||||||
|
|
||||||
|
export type ClearSMTPReq = {} // server.clear-smtp
|
||||||
|
export type ClearSMTPRes = null
|
||||||
|
|
||||||
|
export type TestSMTPReq = SetSMTPReq & { to: string } // server.test-smtp
|
||||||
|
export type TestSMTPRes = null
|
||||||
|
|
||||||
// sessions
|
// sessions
|
||||||
|
|
||||||
export type GetSessionsReq = {} // sessions.list
|
export type GetSessionsReq = {} // sessions.list
|
||||||
|
|||||||
@@ -128,6 +128,14 @@ export abstract class ApiService {
|
|||||||
|
|
||||||
abstract resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes>
|
abstract resetTor(params: RR.ResetTorReq): Promise<RR.ResetTorRes>
|
||||||
|
|
||||||
|
// smtp
|
||||||
|
|
||||||
|
abstract setSmtp(params: RR.SetSMTPReq): Promise<RR.SetSMTPRes>
|
||||||
|
|
||||||
|
abstract clearSmtp(params: RR.ClearSMTPReq): Promise<RR.ClearSMTPRes>
|
||||||
|
|
||||||
|
abstract testSmtp(params: RR.TestSMTPReq): Promise<RR.TestSMTPRes>
|
||||||
|
|
||||||
// marketplace URLs
|
// marketplace URLs
|
||||||
|
|
||||||
abstract registryRequest<T>(
|
abstract registryRequest<T>(
|
||||||
|
|||||||
@@ -382,6 +382,20 @@ export class LiveApiService extends ApiService {
|
|||||||
return this.rpcRequest({ method: 'wifi.delete', params })
|
return this.rpcRequest({ method: 'wifi.delete', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// smtp
|
||||||
|
|
||||||
|
async setSmtp(params: RR.SetSMTPReq): Promise<RR.SetSMTPRes> {
|
||||||
|
return this.rpcRequest({ method: 'server.set-smtp', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearSmtp(params: RR.ClearSMTPReq): Promise<RR.ClearSMTPRes> {
|
||||||
|
return this.rpcRequest({ method: 'server.clear-smtp', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
async testSmtp(params: RR.TestSMTPReq): Promise<RR.TestSMTPRes> {
|
||||||
|
return this.rpcRequest({ method: 'server.test-smtp', params })
|
||||||
|
}
|
||||||
|
|
||||||
// ssh
|
// ssh
|
||||||
|
|
||||||
async getSshKeys(params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
|
async getSshKeys(params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
|
||||||
|
|||||||
@@ -557,6 +557,41 @@ export class MockApiService extends ApiService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// smtp
|
||||||
|
|
||||||
|
async setSmtp(params: RR.SetSMTPReq): Promise<RR.SetSMTPRes> {
|
||||||
|
await pauseFor(2000)
|
||||||
|
const patch = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: '/serverInfo/smtp',
|
||||||
|
value: params,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearSmtp(params: RR.ClearSMTPReq): Promise<RR.ClearSMTPRes> {
|
||||||
|
await pauseFor(2000)
|
||||||
|
const patch = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: '/serverInfo/smtp',
|
||||||
|
value: null,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async testSmtp(params: RR.TestSMTPReq): Promise<RR.TestSMTPRes> {
|
||||||
|
await pauseFor(2000)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// ssh
|
// ssh
|
||||||
|
|
||||||
async getSshKeys(params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
|
async getSshKeys(params: RR.GetSSHKeysReq): Promise<RR.GetSSHKeysRes> {
|
||||||
|
|||||||
Reference in New Issue
Block a user