mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
add support for inbound proxies
This commit is contained in:
@@ -221,8 +221,8 @@ export class MarketplaceControlsComponent {
|
||||
const loader = this.loader.open('Starting upload').subscribe()
|
||||
|
||||
try {
|
||||
const { upload } = await this.api.sideloadPackage()
|
||||
this.api.uploadPackage(upload, file).catch(console.error)
|
||||
const res = await this.api.sideloadPackage()
|
||||
this.api.uploadFile(res.upload, file).catch(console.error)
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { ISB } from '@start9labs/start-sdk'
|
||||
import { TuiDialogOptions } from '@taiga-ui/core'
|
||||
import { TuiConfirmData } from '@taiga-ui/kit'
|
||||
|
||||
export const DELETE_OPTIONS: Partial<TuiDialogOptions<TuiConfirmData>> = {
|
||||
label: 'Confirm',
|
||||
size: 's',
|
||||
data: {
|
||||
content: 'Delete proxy? This action cannot be undone.',
|
||||
yes: 'Delete',
|
||||
no: 'Cancel',
|
||||
},
|
||||
}
|
||||
|
||||
export const wireguardSpec = ISB.InputSpec.of({
|
||||
name: ISB.Value.text({
|
||||
name: 'Name',
|
||||
description: 'A friendly name to help you remember and identify this proxy',
|
||||
required: true,
|
||||
default: null,
|
||||
}),
|
||||
// @TODO Matt same here
|
||||
// config: ISB.Value.file({
|
||||
// name: 'Wiregaurd Config',
|
||||
// required: { default: null },
|
||||
// extensions: ['.conf'],
|
||||
// }),
|
||||
})
|
||||
|
||||
export type WireguardSpec = typeof wireguardSpec.validator._TYPE
|
||||
export type ProxyUpdate = {
|
||||
name: string
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { TuiLink, TuiNotification } from '@taiga-ui/core'
|
||||
import { DocsLinkDirective } from 'projects/shared/src/public-api'
|
||||
|
||||
@Component({
|
||||
selector: 'proxies-info',
|
||||
template: `
|
||||
<tui-notification>
|
||||
Currently, StartOS only supports Wireguard proxies, which can be used for:
|
||||
<ol>
|
||||
<li>
|
||||
Proxying
|
||||
<i>outbound</i>
|
||||
traffic to mask your home/business IP from other servers accessed by
|
||||
your server/services
|
||||
</li>
|
||||
<li>
|
||||
Proxying
|
||||
<i>inbound</i>
|
||||
traffic to mask your home/business IP from anyone accessing your
|
||||
server/services over clearnet
|
||||
</li>
|
||||
<li>
|
||||
Creating a Virtual Local Area Network (VLAN) to enable private, remote
|
||||
VPN access to your server/services
|
||||
</li>
|
||||
</ol>
|
||||
<a tuiLink docsLink href="/@TODO">View instructions</a>
|
||||
</tui-notification>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiNotification, TuiLink, DocsLinkDirective],
|
||||
})
|
||||
export class ProxiesInfoComponent {}
|
||||
@@ -0,0 +1,99 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
input,
|
||||
output,
|
||||
} from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiOptGroup,
|
||||
} from '@taiga-ui/core'
|
||||
|
||||
export type WireguardProxy = T.NetworkInterfaceInfo & {
|
||||
id: string
|
||||
ipInfo: WireguardIpInfo
|
||||
}
|
||||
|
||||
export type WireguardIpInfo = T.IpInfo & {
|
||||
deviceType: 'wireguard'
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tr[proxy]',
|
||||
template: `
|
||||
<td class="label">{{ proxy().ipInfo.name }}</td>
|
||||
<td class="type">
|
||||
{{ proxy().public ? ('Public' | i18n) : ('Private' | i18n) }}
|
||||
</td>
|
||||
<td class="actions">
|
||||
<button
|
||||
tuiIconButton
|
||||
iconStart="@tui.ellipsis"
|
||||
appearance="icon"
|
||||
[tuiDropdown]="content"
|
||||
[(tuiDropdownOpen)]="open"
|
||||
[tuiDropdownMaxHeight]="9999"
|
||||
>
|
||||
<img [style.max-width.%]="60" src="assets/img/icon.png" alt="StartOS" />
|
||||
</button>
|
||||
<ng-template #content>
|
||||
<tui-data-list [style.width.rem]="13">
|
||||
<tui-opt-group>
|
||||
<button
|
||||
tuiOption
|
||||
iconStart="@tui.pencil"
|
||||
(click)="onRename.emit(proxy())"
|
||||
>
|
||||
{{ 'Rename' | i18n }}
|
||||
</button>
|
||||
<button
|
||||
tuiOption
|
||||
appearance="negative"
|
||||
iconStart="@tui.trash-2"
|
||||
(click)="onRemove.emit(proxy())"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
</tui-data-list>
|
||||
</ng-template>
|
||||
</td>
|
||||
`,
|
||||
styles: `
|
||||
td:last-child {
|
||||
grid-area: 3 / span 4;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, min-content) 1fr;
|
||||
align-items: center;
|
||||
padding: 1rem 0.5rem;
|
||||
gap: 0.5rem;
|
||||
|
||||
td {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiButton, i18nPipe, TuiDropdown, TuiDataList, TuiOptGroup],
|
||||
})
|
||||
export class ProxiesItemComponent {
|
||||
readonly proxy = input.required<WireguardProxy>()
|
||||
|
||||
onRename = output<WireguardProxy>()
|
||||
onRemove = output<WireguardProxy>()
|
||||
|
||||
open = false
|
||||
}
|
||||
@@ -1,72 +1,158 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { TuiDialogOptions, TuiButton } from '@taiga-ui/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { Observable } from 'rxjs'
|
||||
import {
|
||||
FormComponent,
|
||||
FormContext,
|
||||
} from 'src/app/routes/portal/components/form.component'
|
||||
import { DataModel, Proxy } from 'src/app/services/patch-db/data-model'
|
||||
DocsLinkDirective,
|
||||
ErrorService,
|
||||
i18nPipe,
|
||||
LoadingService,
|
||||
} from '@start9labs/shared'
|
||||
import { TuiButton, TuiLink } from '@taiga-ui/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ProxiesTableComponent } from './table.component'
|
||||
import { ProxiesInfoComponent } from './info.component'
|
||||
import { wireguardSpec, WireguardSpec } from './constants'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { map } from 'rxjs'
|
||||
import { ISB, T } from '@start9labs/start-sdk'
|
||||
import { WireguardIpInfo, WireguardProxy } from './item.component'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<proxies-info />
|
||||
<h3 class="g-title">
|
||||
Proxies
|
||||
<button tuiButton size="xs" iconStart="@tui.plus" (click)="add()">
|
||||
Add Proxy
|
||||
</button>
|
||||
</h3>
|
||||
<table class="g-table" [proxies]="proxies$ | async"></table>
|
||||
<ng-container *title>
|
||||
<a routerLink=".." tuiIconButton iconStart="@tui.arrow-left">
|
||||
{{ 'Back' | i18n }}
|
||||
</a>
|
||||
{{ 'Inbound Proxies' | i18n }}
|
||||
</ng-container>
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h3>{{ 'Inbound Proxies' | i18n }}</h3>
|
||||
<p tuiSubtitle>
|
||||
{{
|
||||
'Inbound proxies provide remote access to your server and installed services.'
|
||||
| i18n
|
||||
}}
|
||||
<a
|
||||
tuiLink
|
||||
docsLink
|
||||
href="/user-manual/inbound-proxies"
|
||||
appearance="action-grayscale"
|
||||
iconEnd="@tui.external-link"
|
||||
[pseudo]="true"
|
||||
[textContent]="'View instructions'"
|
||||
></a>
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
|
||||
<section class="g-card">
|
||||
<header>
|
||||
{{ 'Saved Proxies' | i18n }}
|
||||
<button tuiButton size="xs" iconStart="@tui.plus" (click)="add()">
|
||||
Add
|
||||
</button>
|
||||
</header>
|
||||
<div #table [proxies]="proxies$ | async"></div>
|
||||
</section>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
CommonModule,
|
||||
TuiButton,
|
||||
ProxiesInfoComponent,
|
||||
ProxiesTableComponent,
|
||||
TuiHeader,
|
||||
TitleDirective,
|
||||
i18nPipe,
|
||||
TuiLink,
|
||||
DocsLinkDirective,
|
||||
],
|
||||
})
|
||||
export default class SystemProxiesComponent {
|
||||
export default class ProxiesComponent {
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly formDialog = inject(FormDialogService)
|
||||
|
||||
readonly proxies$: Observable<Proxy[]> = inject<PatchDB<DataModel>>(
|
||||
PatchDB,
|
||||
).watch$('serverInfo', 'network', 'proxies')
|
||||
readonly proxies$ = inject<PatchDB<DataModel>>(PatchDB)
|
||||
.watch$('serverInfo', 'network')
|
||||
.pipe(
|
||||
map(network =>
|
||||
Object.entries(network.networkInterfaces)
|
||||
.filter(
|
||||
(
|
||||
record,
|
||||
): record is [
|
||||
string,
|
||||
T.NetworkInterfaceInfo & { ipInfo: WireguardIpInfo },
|
||||
] => record[1].ipInfo?.deviceType === 'wireguard',
|
||||
)
|
||||
.map(
|
||||
([id, val]) =>
|
||||
({
|
||||
...val,
|
||||
id,
|
||||
}) as WireguardProxy,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
readonly wireguardSpec = ISB.InputSpec.of({
|
||||
label: ISB.Value.text({
|
||||
name: 'Label',
|
||||
description: 'To help identify this proxy',
|
||||
required: true,
|
||||
default: null,
|
||||
}),
|
||||
type: ISB.Value.select({
|
||||
name: 'Type',
|
||||
description:
|
||||
'-**Private**: a private inbound proxy is used to access your server and installed services privately. Only clients configured and authorized to use the proxy will be granted access.\n-**Public**: a public inbound proxy is used to expose service interfaces on a case-by-case basis to the public Internet without exposing your home IP address. Only service interfaces explicitly marked "Public" will be accessible via the proxy.',
|
||||
default: 'private',
|
||||
values: {
|
||||
private: 'Private',
|
||||
public: 'Public',
|
||||
},
|
||||
}),
|
||||
config: ISB.Value.file({
|
||||
name: 'Wiregaurd Config',
|
||||
required: true,
|
||||
extensions: ['.conf'],
|
||||
}),
|
||||
})
|
||||
|
||||
async add() {
|
||||
const options: Partial<TuiDialogOptions<FormContext<WireguardSpec>>> = {
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Add Proxy',
|
||||
data: {
|
||||
spec: await wireguardSpec.build({} as any),
|
||||
spec: await configBuilderToSpec(this.wireguardSpec),
|
||||
buttons: [
|
||||
{
|
||||
text: 'Save',
|
||||
handler: value => this.save(value).then(() => true),
|
||||
handler: (input: typeof this.wireguardSpec._TYPE) =>
|
||||
this.save(input),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
this.formDialog.open(FormComponent, options)
|
||||
})
|
||||
}
|
||||
|
||||
// @TODO 041 fix type to be WireguardSpec
|
||||
private async save({ name, config }: any): Promise<boolean> {
|
||||
private async save(
|
||||
input: typeof this.wireguardSpec._TYPE & {
|
||||
config: { hash: string; file: File }
|
||||
},
|
||||
): Promise<boolean> {
|
||||
const loader = this.loader.open('Saving').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.addProxy({ name, config: config?.filePath || '' })
|
||||
await this.api.addProxy({
|
||||
label: input.label,
|
||||
config: input.config,
|
||||
public: input.type === 'public',
|
||||
})
|
||||
return true
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
|
||||
@@ -2,165 +2,76 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
Input,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import {
|
||||
DialogService,
|
||||
ErrorService,
|
||||
i18nPipe,
|
||||
LoadingService,
|
||||
} from '@start9labs/shared'
|
||||
import { ISB } from '@start9labs/start-sdk'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDialogOptions,
|
||||
TuiDialogService,
|
||||
TuiLink,
|
||||
} from '@taiga-ui/core'
|
||||
import { TUI_CONFIRM, TuiSkeleton } from '@taiga-ui/kit'
|
||||
import { TuiSkeleton } from '@taiga-ui/kit'
|
||||
import { filter } from 'rxjs'
|
||||
import {
|
||||
FormComponent,
|
||||
FormContext,
|
||||
} from 'src/app/routes/portal/components/form.component'
|
||||
import {
|
||||
DELETE_OPTIONS,
|
||||
ProxyUpdate,
|
||||
} from 'src/app/routes/portal/routes/system/routes/proxies/constants'
|
||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { Proxy } from 'src/app/services/patch-db/data-model'
|
||||
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { WireguardProxy } from './item.component'
|
||||
import { ProxiesItemComponent } from './item.component'
|
||||
|
||||
@Component({
|
||||
selector: 'table[proxies]',
|
||||
selector: '[proxies]',
|
||||
template: `
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Used By</th>
|
||||
<th [style.width.rem]="3.5"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (proxy of proxies; track $index) {
|
||||
<tr>
|
||||
<td class="title">{{ proxy.name }}</td>
|
||||
<td class="type">{{ proxy.type }}</td>
|
||||
<td class="used">
|
||||
@if (getLength(proxy); as length) {
|
||||
<button tuiLink (click)="onUsedBy(proxy)">
|
||||
Used by: {{ length }}
|
||||
</button>
|
||||
} @else {
|
||||
N/A
|
||||
}
|
||||
</td>
|
||||
<td class="actions">
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="icon"
|
||||
size="xs"
|
||||
iconStart="@tui.pencil"
|
||||
(click)="rename(proxy)"
|
||||
>
|
||||
Rename
|
||||
</button>
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="icon"
|
||||
size="xs"
|
||||
iconStart="@tui.trash-2"
|
||||
(click)="delete(proxy)"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<table [appTable]="['Label', 'Type', null]">
|
||||
@for (proxy of proxies(); track $index) {
|
||||
<tr
|
||||
[proxy]="proxy"
|
||||
(onRename)="rename($event)"
|
||||
(onRemove)="remove($event.id)"
|
||||
></tr>
|
||||
} @empty {
|
||||
@if (proxies) {
|
||||
<tr><td colspan="5">No proxies added</td></tr>
|
||||
@if (proxies()) {
|
||||
<tr>
|
||||
<td colspan="5">{{ 'No proxies' | i18n }}</td>
|
||||
</tr>
|
||||
} @else {
|
||||
<tr>
|
||||
<td colspan="5"><div [tuiSkeleton]="true">Loading</div></td>
|
||||
<td colspan="5">
|
||||
<div [tuiSkeleton]="true">{{ 'Loading' | i18n }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
`,
|
||||
styles: `
|
||||
:host-context(tui-root._mobile) {
|
||||
tr {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
td:only-child {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.title {
|
||||
order: 1;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.actions {
|
||||
order: 2;
|
||||
padding: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.type {
|
||||
order: 3;
|
||||
}
|
||||
|
||||
.used {
|
||||
order: 4;
|
||||
text-align: right;
|
||||
|
||||
&:not(:has(button)) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
:host {
|
||||
grid-column: span 6;
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiLink, TuiButton, TuiSkeleton],
|
||||
imports: [TuiSkeleton, i18nPipe, TableComponent, ProxiesItemComponent],
|
||||
})
|
||||
export class ProxiesTableComponent {
|
||||
private readonly dialogs = inject(TuiDialogService)
|
||||
export class ProxiesTableComponent<T extends WireguardProxy> {
|
||||
readonly proxies = input<readonly T[] | null>(null)
|
||||
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly formDialog = inject(FormDialogService)
|
||||
|
||||
@Input()
|
||||
proxies: readonly Proxy[] | null = null
|
||||
|
||||
getLength({ usedBy }: Proxy) {
|
||||
return usedBy.domains.length + usedBy.services.length
|
||||
}
|
||||
|
||||
onUsedBy({ name, usedBy }: Proxy) {
|
||||
let message = `Proxy "${name}" is currently used by:`
|
||||
const domains = usedBy.domains.map(d => `<li>${d}</li>`)
|
||||
const services = usedBy.services.map(s => `<li>${s.title}</li>`)
|
||||
|
||||
if (usedBy.domains.length) {
|
||||
message = `${message}<h2>Domains (inbound)</h2><ul>${domains}</ul>`
|
||||
}
|
||||
|
||||
if (usedBy.services.length) {
|
||||
message = `${message}<h2>Services (outbound)</h2>${services}`
|
||||
}
|
||||
|
||||
this.dialogs.open(message, { label: 'Used by', size: 's' }).subscribe()
|
||||
}
|
||||
|
||||
delete({ id }: Proxy) {
|
||||
this.dialogs
|
||||
.open(TUI_CONFIRM, DELETE_OPTIONS)
|
||||
remove(id: string) {
|
||||
this.dialog
|
||||
.openConfirm({ label: 'Are you sure?', size: 's' })
|
||||
.pipe(filter(Boolean))
|
||||
.subscribe(async () => {
|
||||
const loader = this.loader.open('Deleting').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.deleteProxy({ id })
|
||||
await this.api.removeProxy({ id })
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
@@ -169,30 +80,35 @@ export class ProxiesTableComponent {
|
||||
})
|
||||
}
|
||||
|
||||
async rename(proxy: Proxy) {
|
||||
const spec = { name: 'Name', required: true, default: proxy.name }
|
||||
const name = await ISB.Value.text(spec).build({} as any)
|
||||
const options: Partial<TuiDialogOptions<FormContext<{ name: string }>>> = {
|
||||
label: `Rename ${proxy.name}`,
|
||||
async rename(proxy: WireguardProxy) {
|
||||
const renameSpec = ISB.InputSpec.of({
|
||||
label: ISB.Value.text({
|
||||
name: 'Label',
|
||||
required: true,
|
||||
default: proxy.ipInfo?.name || null,
|
||||
}),
|
||||
})
|
||||
|
||||
this.formDialog.open(FormComponent, {
|
||||
label: 'Update Label',
|
||||
data: {
|
||||
spec: { name },
|
||||
spec: await configBuilderToSpec(renameSpec),
|
||||
buttons: [
|
||||
{
|
||||
text: 'Save',
|
||||
handler: value => this.update(value),
|
||||
handler: (value: typeof renameSpec._TYPE) =>
|
||||
this.update(proxy.id, value.label),
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
this.formDialog.open(FormComponent, options)
|
||||
})
|
||||
}
|
||||
|
||||
private async update(value: ProxyUpdate): Promise<boolean> {
|
||||
private async update(id: string, label: string): Promise<boolean> {
|
||||
const loader = this.loader.open('Saving').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.updateProxy(value)
|
||||
await this.api.updateProxy({ id, label })
|
||||
return true
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
|
||||
@@ -26,11 +26,6 @@ export const SYSTEM_MENU = [
|
||||
item: 'StartOS UI',
|
||||
link: 'interfaces',
|
||||
},
|
||||
{
|
||||
icon: '@tui.award',
|
||||
item: 'ACME',
|
||||
link: 'acme',
|
||||
},
|
||||
{
|
||||
icon: '@tui.mail',
|
||||
item: 'Email',
|
||||
@@ -42,6 +37,18 @@ export const SYSTEM_MENU = [
|
||||
link: 'wifi',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: '@tui.award',
|
||||
item: 'ACME',
|
||||
link: 'acme',
|
||||
},
|
||||
{
|
||||
icon: '@tui.hard-drive-download',
|
||||
item: 'Inbound Proxies',
|
||||
link: 'proxies',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: '@tui.clock',
|
||||
|
||||
@@ -72,15 +72,15 @@ export default [
|
||||
title: titleResolver,
|
||||
loadComponent: () => import('./routes/password/password.component'),
|
||||
},
|
||||
{
|
||||
path: 'proxies',
|
||||
loadComponent: () => import('./routes/proxies/proxies.component'),
|
||||
},
|
||||
// {
|
||||
// path: 'domains',
|
||||
// loadComponent: () => import('./routes/domains/domains.component')
|
||||
// },
|
||||
// {
|
||||
// path: 'proxies',
|
||||
// loadComponent: () => import('./routes/proxies/proxies.component')
|
||||
// },
|
||||
// {
|
||||
// path: 'router',
|
||||
// loadComponent: () => import('./routes/router/router.component')
|
||||
// },
|
||||
|
||||
@@ -234,7 +234,30 @@ export namespace RR {
|
||||
}
|
||||
export type CreateBackupRes = null
|
||||
|
||||
// package
|
||||
// proxy
|
||||
|
||||
export type AddProxyReq = {
|
||||
label: string
|
||||
config: string // hash of file
|
||||
public: boolean
|
||||
} // net.proxy.add
|
||||
export type AddProxyRes = {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type UpdateProxyReq = {
|
||||
id: string
|
||||
label: string
|
||||
} // net.netwok-interface.set-label
|
||||
export type UpdateProxyRes = null
|
||||
|
||||
export type RemoveProxyReq = { id: string } // net.proxy.remove
|
||||
export type RemoveProxyRes = null
|
||||
|
||||
// export type SetOutboundProxyReq = {
|
||||
// id: string | null
|
||||
// } // net.proxy.set-outbound
|
||||
// export type SetOutboundProxyRes = null
|
||||
|
||||
export type InitAcmeReq = {
|
||||
provider: 'letsencrypt' | 'letsencrypt-staging' | string
|
||||
@@ -374,7 +397,7 @@ export namespace RR {
|
||||
icon: string // base64
|
||||
}
|
||||
export type SideloadPackageRes = {
|
||||
upload: string // guid
|
||||
upload: string
|
||||
progress: string // guid
|
||||
}
|
||||
|
||||
@@ -631,35 +654,6 @@ export type DependencyErrorTransitive = {
|
||||
// export type OverridePortReq = { target: number; port: number } // net.port-forwards.override
|
||||
// export type OverridePortRes = null
|
||||
|
||||
// // ** proxies **
|
||||
|
||||
// export type AddProxyReq = {
|
||||
// name: string
|
||||
// config: string
|
||||
// } // net.proxy.add
|
||||
// export type AddProxyRes = null
|
||||
|
||||
// export type UpdateProxyReq = {
|
||||
// name: string
|
||||
// } // net.proxy.update
|
||||
// export type UpdateProxyRes = null
|
||||
|
||||
// export type DeleteProxyReq = { id: string } // net.proxy.delete
|
||||
// export type DeleteProxyRes = null
|
||||
|
||||
// // ** set outbound proxies **
|
||||
|
||||
// export type SetOsOutboundProxyReq = {
|
||||
// proxy: string | null
|
||||
// } // server.proxy.set-outbound
|
||||
// export type SetOsOutboundProxyRes = null
|
||||
|
||||
// export type SetServiceOutboundProxyReq = {
|
||||
// packageId: string
|
||||
// proxy: string | null
|
||||
// } // package.proxy.set-outbound
|
||||
// export type SetServiceOutboundProxyRes = null
|
||||
|
||||
// // ** automated backups **
|
||||
|
||||
// export type GetBackupTargetsReq = {} // backup.target.list
|
||||
|
||||
@@ -6,8 +6,8 @@ import { WebSocketSubject } from 'rxjs/webSocket'
|
||||
export abstract class ApiService {
|
||||
// http
|
||||
|
||||
// for sideloading packages
|
||||
abstract uploadPackage(guid: string, body: Blob): Promise<void>
|
||||
// for uploading files
|
||||
abstract uploadFile(guid: string, body: Blob): Promise<void>
|
||||
|
||||
// for getting static files: ex license
|
||||
abstract getStaticProxy(
|
||||
@@ -126,12 +126,6 @@ export abstract class ApiService {
|
||||
|
||||
// @TODO 041
|
||||
|
||||
// ** server outbound proxy **
|
||||
|
||||
// abstract setOsOutboundProxy(
|
||||
// params: RR.SetOsOutboundProxyReq,
|
||||
// ): Promise<RR.SetOsOutboundProxyRes>
|
||||
|
||||
// smtp
|
||||
|
||||
abstract setSmtp(params: RR.SetSMTPReq): Promise<RR.SetSMTPRes>
|
||||
@@ -182,13 +176,17 @@ export abstract class ApiService {
|
||||
|
||||
// ** proxies **
|
||||
|
||||
abstract addProxy(params: RR.AddProxyReq): Promise<RR.AddProxyRes>
|
||||
|
||||
abstract updateProxy(params: RR.UpdateProxyReq): Promise<RR.UpdateProxyRes>
|
||||
|
||||
abstract removeProxy(params: RR.RemoveProxyReq): Promise<RR.RemoveProxyRes>
|
||||
|
||||
// @TODO 041
|
||||
|
||||
// abstract addProxy(params: RR.AddProxyReq): Promise<RR.AddProxyRes>
|
||||
|
||||
// abstract updateProxy(params: RR.UpdateProxyReq): Promise<RR.UpdateProxyRes>
|
||||
|
||||
// abstract deleteProxy(params: RR.DeleteProxyReq): Promise<RR.DeleteProxyRes>
|
||||
// abstract setOutboundProxy(
|
||||
// params: RR.SetOutboundProxyReq,
|
||||
// ): Promise<RR.SetOutboundProxyRes>
|
||||
|
||||
// ** domains **
|
||||
|
||||
|
||||
@@ -33,9 +33,9 @@ export class LiveApiService extends ApiService {
|
||||
this.document.defaultView.rpcClient = this
|
||||
}
|
||||
|
||||
// for sideloading packages
|
||||
// for uploading files
|
||||
|
||||
async uploadPackage(guid: string, body: Blob): Promise<void> {
|
||||
async uploadFile(guid: string, body: Blob): Promise<void> {
|
||||
await this.httpRequest({
|
||||
method: Method.POST,
|
||||
body,
|
||||
@@ -271,12 +271,6 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'net.tor.reset', params })
|
||||
}
|
||||
|
||||
// async setOsOutboundProxy(
|
||||
// params: RR.SetOsOutboundProxyReq,
|
||||
// ): Promise<RR.SetOsOutboundProxyRes> {
|
||||
// return this.rpcRequest({ method: 'server.proxy.set-outbound', params })
|
||||
// }
|
||||
|
||||
// marketplace URLs
|
||||
|
||||
async checkOSUpdate(
|
||||
@@ -352,16 +346,22 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
// proxies
|
||||
|
||||
// async addProxy(params: RR.AddProxyReq): Promise<RR.AddProxyRes> {
|
||||
// return this.rpcRequest({ method: 'net.proxy.add', params })
|
||||
// }
|
||||
async addProxy(params: RR.AddProxyReq): Promise<RR.AddProxyRes> {
|
||||
return this.rpcRequest({ method: 'net.proxy.add', params })
|
||||
}
|
||||
|
||||
// async updateProxy(params: RR.UpdateProxyReq): Promise<RR.UpdateProxyRes> {
|
||||
// return this.rpcRequest({ method: 'net.proxy.update', params })
|
||||
// }
|
||||
async updateProxy(params: RR.UpdateProxyReq): Promise<RR.UpdateProxyRes> {
|
||||
return this.rpcRequest({ method: 'net.netwok-interface.set-label', params })
|
||||
}
|
||||
|
||||
// async deleteProxy(params: RR.DeleteProxyReq): Promise<RR.DeleteProxyRes> {
|
||||
// return this.rpcRequest({ method: 'net.proxy.delete', params })
|
||||
async removeProxy(params: RR.RemoveProxyReq): Promise<RR.RemoveProxyRes> {
|
||||
return this.rpcRequest({ method: 'net.proxy.remove', params })
|
||||
}
|
||||
|
||||
// async setOutboundProxy(
|
||||
// params: RR.SetOutboundProxyReq,
|
||||
// ): Promise<RR.SetOutboundProxyRes> {
|
||||
// return this.rpcRequest({ method: 'server.proxy.set-outbound', params })
|
||||
// }
|
||||
|
||||
// domains
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { pauseFor, Log, RPCErrorDetails, RPCOptions } from '@start9labs/shared'
|
||||
import { pauseFor, Log, RPCErrorDetails } from '@start9labs/shared'
|
||||
import { ApiService } from './embassy-api.service'
|
||||
import {
|
||||
AddOperation,
|
||||
@@ -71,7 +71,7 @@ export class MockApiService extends ApiService {
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
async uploadPackage(guid: string, body: Blob): Promise<void> {
|
||||
async uploadFile(guid: string, body: Blob): Promise<void> {
|
||||
await pauseFor(2000)
|
||||
}
|
||||
|
||||
@@ -467,23 +467,6 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
// async setOsOutboundProxy(
|
||||
// params: RR.SetOsOutboundProxyReq,
|
||||
// ): Promise<RR.SetOsOutboundProxyRes> {
|
||||
// await pauseFor(2000)
|
||||
|
||||
// const patch = [
|
||||
// {
|
||||
// op: PatchOp.REPLACE,
|
||||
// path: '/serverInfo/network/outboundProxy',
|
||||
// value: params.proxy,
|
||||
// },
|
||||
// ]
|
||||
// this.mockRevision(patch)
|
||||
|
||||
// return null
|
||||
// }
|
||||
|
||||
// marketplace URLs
|
||||
|
||||
async checkOSUpdate(
|
||||
@@ -559,56 +542,73 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
// network
|
||||
// proxies
|
||||
|
||||
// async addProxy(params: RR.AddProxyReq): Promise<RR.AddProxyRes> {
|
||||
async addProxy(params: RR.AddProxyReq): Promise<RR.AddProxyRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const id = `wga-${params.label}`
|
||||
|
||||
const patch: AddOperation<T.NetworkInterfaceInfo>[] = [
|
||||
{
|
||||
op: PatchOp.ADD,
|
||||
path: `/serverInfo/network/networkInterfaces/${id}`,
|
||||
value: {
|
||||
public: params.public,
|
||||
ipInfo: {
|
||||
name: params.label,
|
||||
scopeId: 3,
|
||||
deviceType: 'wireguard',
|
||||
subnets: [],
|
||||
wanIp: '1.1.1.1',
|
||||
ntpServers: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return { id }
|
||||
}
|
||||
|
||||
async updateProxy(params: RR.UpdateProxyReq): Promise<RR.UpdateProxyRes> {
|
||||
await pauseFor(2000)
|
||||
|
||||
const patch: ReplaceOperation<string>[] = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: `/serverInfo/network/networkInterfaces/${params.id}/label`,
|
||||
value: params.label,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async removeProxy(params: RR.RemoveProxyReq): Promise<RR.RemoveProxyRes> {
|
||||
await pauseFor(2000)
|
||||
const patch: RemoveOperation[] = [
|
||||
{
|
||||
op: PatchOp.REMOVE,
|
||||
path: `/serverInfo/network/networkInterfaces/${params.id}`,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// async setOutboundProxy(
|
||||
// params: RR.SetOutboundProxyReq,
|
||||
// ): Promise<RR.SetOutboundProxyRes> {
|
||||
// await pauseFor(2000)
|
||||
|
||||
// const patch = [
|
||||
// {
|
||||
// op: PatchOp.ADD,
|
||||
// path: `/serverInfo/network/networkInterfaces/wga1`,
|
||||
// value: {
|
||||
// inbound: true,
|
||||
// outbound: true,
|
||||
// ipInfo: {
|
||||
// name: params.name,
|
||||
// scopeId: 3,
|
||||
// deviceType: 'wireguard',
|
||||
// subnets: [],
|
||||
// wanIp: '1.1.1.1',
|
||||
// ntpServers: [],
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// ]
|
||||
// this.mockRevision(patch)
|
||||
|
||||
// return null
|
||||
// }
|
||||
|
||||
// async updateProxy(params: RR.UpdateProxyReq): Promise<RR.UpdateProxyRes> {
|
||||
// await pauseFor(2000)
|
||||
|
||||
// const patch = [
|
||||
// const patch: ReplaceOperation<string | null>[] = [
|
||||
// {
|
||||
// op: PatchOp.REPLACE,
|
||||
// path: `/serverInfo/network/proxies/0/name`,
|
||||
// value: params.name,
|
||||
// },
|
||||
// ]
|
||||
// this.mockRevision(patch)
|
||||
|
||||
// return null
|
||||
// }
|
||||
|
||||
// async deleteProxy(params: RR.DeleteProxyReq): Promise<RR.DeleteProxyRes> {
|
||||
// await pauseFor(2000)
|
||||
// const patch = [
|
||||
// {
|
||||
// op: PatchOp.REPLACE,
|
||||
// path: '/serverInfo/network/proxies',
|
||||
// value: [],
|
||||
// path: '/serverInfo/network/outboundInterface',
|
||||
// value: params.id,
|
||||
// },
|
||||
// ]
|
||||
// this.mockRevision(patch)
|
||||
|
||||
@@ -136,8 +136,7 @@ export const mockPatchData: DataModel = {
|
||||
},
|
||||
networkInterfaces: {
|
||||
eth0: {
|
||||
inbound: false,
|
||||
outbound: true,
|
||||
public: false,
|
||||
ipInfo: {
|
||||
name: 'Wired Connection 1',
|
||||
scopeId: 1,
|
||||
@@ -148,8 +147,7 @@ export const mockPatchData: DataModel = {
|
||||
},
|
||||
},
|
||||
wlan0: {
|
||||
inbound: false,
|
||||
outbound: true,
|
||||
public: false,
|
||||
ipInfo: {
|
||||
name: 'Wireless Connection 1',
|
||||
scopeId: 2,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @TODO 041
|
||||
|
||||
// import { Injectable } from '@angular/core'
|
||||
// import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
// import { TuiDialogOptions } from '@taiga-ui/core'
|
||||
|
||||
Reference in New Issue
Block a user