mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 20:43:41 +00:00
Merge branch 'feat/preferred-port-design' of github.com:Start9Labs/start-os into sdk-comments
This commit is contained in:
@@ -305,21 +305,21 @@ export class PluginAddressesComponent {
|
||||
if (!group.tableAction || !group.pluginPkgInfo) return
|
||||
|
||||
const iface = this.value()
|
||||
const prefill: Record<string, unknown> = {}
|
||||
if (!iface) return
|
||||
|
||||
if (iface) {
|
||||
prefill['urlPluginMetadata'] = {
|
||||
packageId: this.packageId() || null,
|
||||
hostId: iface.addressInfo.hostId,
|
||||
interfaceId: iface.id,
|
||||
internalPort: iface.addressInfo.internalPort,
|
||||
}
|
||||
}
|
||||
const { addressInfo } = iface
|
||||
|
||||
this.actionService.present({
|
||||
pkgInfo: group.pluginPkgInfo,
|
||||
actionInfo: group.tableAction,
|
||||
prefill,
|
||||
prefill: {
|
||||
urlPluginMetadata: {
|
||||
packageId: this.packageId() || null,
|
||||
hostId: addressInfo.hostId,
|
||||
interfaceId: iface.id,
|
||||
internalPort: addressInfo.internalPort,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -332,26 +332,30 @@ export class PluginAddressesComponent {
|
||||
if (!group.pluginPkgInfo) return
|
||||
|
||||
const iface = this.value()
|
||||
const prefill: Record<string, unknown> = {}
|
||||
if (!iface) return
|
||||
|
||||
if (iface && address.hostnameInfo.metadata.kind === 'plugin') {
|
||||
prefill['urlPluginMetadata'] = {
|
||||
packageId: this.packageId() || null,
|
||||
hostId: iface.addressInfo.hostId,
|
||||
interfaceId: iface.id,
|
||||
internalPort: iface.addressInfo.internalPort,
|
||||
hostname: address.hostnameInfo.hostname,
|
||||
port: address.hostnameInfo.port,
|
||||
ssl: address.hostnameInfo.ssl,
|
||||
public: address.hostnameInfo.public,
|
||||
info: address.hostnameInfo.metadata.info,
|
||||
}
|
||||
}
|
||||
const { hostnameInfo } = address
|
||||
const { addressInfo } = iface
|
||||
const hostMeta = hostnameInfo.metadata
|
||||
|
||||
if (hostMeta.kind !== 'plugin') return
|
||||
|
||||
this.actionService.present({
|
||||
pkgInfo: group.pluginPkgInfo,
|
||||
actionInfo: { id: actionId, metadata },
|
||||
prefill,
|
||||
prefill: {
|
||||
urlPluginMetadata: {
|
||||
packageId: this.packageId() || null,
|
||||
hostId: addressInfo.hostId,
|
||||
interfaceId: iface.id,
|
||||
internalPort: addressInfo.internalPort,
|
||||
hostname: hostnameInfo.hostname,
|
||||
port: hostnameInfo.port,
|
||||
ssl: hostnameInfo.ssl,
|
||||
public: hostnameInfo.public,
|
||||
info: hostMeta.info,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,20 +154,21 @@ export class ServiceTaskComponent {
|
||||
}
|
||||
|
||||
async handle() {
|
||||
const task = this.task()
|
||||
const title = this.title()
|
||||
const pkg = this.pkg()
|
||||
const metadata = pkg?.actions[this.task().actionId]
|
||||
const metadata = pkg?.actions[task.actionId]
|
||||
|
||||
if (title && pkg && metadata) {
|
||||
this.actionService.present({
|
||||
pkgInfo: {
|
||||
id: this.task().packageId,
|
||||
id: task.packageId,
|
||||
title,
|
||||
status: getInstalledBaseStatus(pkg.statusInfo),
|
||||
icon: pkg.icon,
|
||||
},
|
||||
actionInfo: { id: this.task().actionId, metadata },
|
||||
requestInfo: this.task(),
|
||||
actionInfo: { id: task.actionId, metadata },
|
||||
prefill: task.input?.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
FormComponent,
|
||||
} from 'src/app/routes/portal/components/form.component'
|
||||
import { InvalidService } from 'src/app/routes/portal/components/form/containers/control.directive'
|
||||
import { TaskInfoComponent } from 'src/app/routes/portal/modals/config-dep.component'
|
||||
import { ActionService } from 'src/app/services/action.service'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
@@ -41,7 +40,6 @@ export type PackageActionData = {
|
||||
id: string
|
||||
metadata: T.ActionMetadata
|
||||
}
|
||||
requestInfo?: T.Task
|
||||
prefill?: Record<string, unknown>
|
||||
}
|
||||
|
||||
@@ -63,13 +61,6 @@ export type PackageActionData = {
|
||||
</tui-notification>
|
||||
}
|
||||
|
||||
@if (requestInfo) {
|
||||
<task-info
|
||||
[originalValue]="res.originalValue || {}"
|
||||
[operations]="res.visibleOperations || []"
|
||||
/>
|
||||
}
|
||||
|
||||
<app-form
|
||||
[spec]="res.spec"
|
||||
[value]="res.originalValue || {}"
|
||||
@@ -110,14 +101,7 @@ export type PackageActionData = {
|
||||
}
|
||||
}
|
||||
`,
|
||||
imports: [
|
||||
TuiNotification,
|
||||
TuiLoader,
|
||||
TuiButton,
|
||||
TaskInfoComponent,
|
||||
FormComponent,
|
||||
i18nPipe,
|
||||
],
|
||||
imports: [TuiNotification, TuiLoader, TuiButton, FormComponent, i18nPipe],
|
||||
providers: [InvalidService],
|
||||
})
|
||||
export class ActionInputModal {
|
||||
@@ -132,7 +116,7 @@ export class ActionInputModal {
|
||||
readonly actionId = this.context.data.actionInfo.id
|
||||
readonly warning = this.context.data.actionInfo.metadata.warning
|
||||
readonly pkgInfo = this.context.data.pkgInfo
|
||||
readonly requestInfo = this.context.data.requestInfo
|
||||
readonly prefill = this.context.data.prefill
|
||||
eventId: string | null = null
|
||||
|
||||
buttons: ActionButton<any>[] = [
|
||||
@@ -148,7 +132,7 @@ export class ActionInputModal {
|
||||
this.api.getActionInput({
|
||||
packageId: this.pkgInfo.id,
|
||||
actionId: this.actionId,
|
||||
prefill: this.context.data.prefill ?? null,
|
||||
prefill: this.prefill ?? null,
|
||||
}),
|
||||
).pipe(
|
||||
map(res => {
|
||||
@@ -156,12 +140,12 @@ export class ActionInputModal {
|
||||
const originalValue = res.value || {}
|
||||
this.eventId = res.eventId
|
||||
|
||||
const operations = this.requestInfo?.input
|
||||
const operations = this.prefill
|
||||
? compare(
|
||||
JSON.parse(JSON.stringify(originalValue)),
|
||||
utils.deepMerge(
|
||||
JSON.parse(JSON.stringify(originalValue)),
|
||||
this.requestInfo.input.value,
|
||||
this.prefill,
|
||||
) as object,
|
||||
)
|
||||
: null
|
||||
@@ -170,11 +154,6 @@ export class ActionInputModal {
|
||||
spec: res.spec,
|
||||
originalValue,
|
||||
operations,
|
||||
visibleOperations:
|
||||
operations?.filter(op => {
|
||||
const key = op.path.split('/')[1]
|
||||
return (res.spec[key!] as any)?.type !== 'hidden'
|
||||
}) ?? null,
|
||||
}
|
||||
}),
|
||||
catchError(e => {
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
LANGUAGE_TO_CODE,
|
||||
LoadingService,
|
||||
} from '@start9labs/shared'
|
||||
import { WA_WINDOW } from '@ng-web-apis/common'
|
||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||
import { TuiAnimated } from '@taiga-ui/cdk'
|
||||
import {
|
||||
@@ -45,7 +46,7 @@ import {
|
||||
import { TuiCell, tuiCellOptionsProvider } from '@taiga-ui/layout'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { filter } from 'rxjs'
|
||||
import { filter, Subscription } from 'rxjs'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { OSService } from 'src/app/services/os.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
@@ -94,6 +95,18 @@ import { KeyboardSelectComponent } from './keyboard-select.component'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
<div tuiCell tuiAppearance="outline-grayscale">
|
||||
<tui-icon icon="@tui.server" />
|
||||
<span tuiTitle>
|
||||
<strong>{{ 'Server Hostname' | i18n }}</strong>
|
||||
<span tuiSubtitle>
|
||||
{{ server.hostname }}
|
||||
</span>
|
||||
</span>
|
||||
<button tuiButton (click)="onHostname()">
|
||||
{{ 'Change' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div tuiCell tuiAppearance="outline-grayscale">
|
||||
<tui-icon icon="@tui.app-window" />
|
||||
<span tuiTitle>
|
||||
@@ -272,6 +285,7 @@ export default class SystemGeneralComponent {
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
private readonly injector = inject(INJECTOR)
|
||||
private readonly win = inject(WA_WINDOW)
|
||||
|
||||
count = 0
|
||||
|
||||
@@ -346,6 +360,82 @@ export default class SystemGeneralComponent {
|
||||
}
|
||||
}
|
||||
|
||||
onHostname() {
|
||||
const sub = this.dialog
|
||||
.openPrompt<string>({
|
||||
label: 'Server Hostname',
|
||||
data: {
|
||||
label: 'Hostname' as i18nKey,
|
||||
message:
|
||||
'This value will be used as your server hostname and mDNS address on the LAN. Only lowercase letters, numbers, and hyphens are allowed.',
|
||||
placeholder: 'start9' as i18nKey,
|
||||
required: true,
|
||||
buttonText: 'Save',
|
||||
initialValue: this.server()?.hostname || '',
|
||||
pattern: '^[a-z0-9][a-z0-9-]*$',
|
||||
patternError:
|
||||
'Hostname must contain only lowercase letters, numbers, and hyphens, and cannot start with a hyphen.',
|
||||
},
|
||||
})
|
||||
.subscribe(hostname => {
|
||||
if (this.win.location.hostname.endsWith('.local')) {
|
||||
this.confirmHostnameChange(hostname, sub)
|
||||
} else {
|
||||
this.saveHostname(hostname, sub)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private confirmHostnameChange(hostname: string, promptSub: Subscription) {
|
||||
this.dialog
|
||||
.openConfirm({
|
||||
label: 'Warning',
|
||||
data: {
|
||||
content:
|
||||
'You are currently connected via your .local address. Changing the hostname will require you to switch to the new .local address.',
|
||||
yes: 'Save',
|
||||
no: 'Cancel',
|
||||
},
|
||||
})
|
||||
.pipe(filter(Boolean))
|
||||
.subscribe(() => this.saveHostname(hostname, promptSub, true))
|
||||
}
|
||||
|
||||
private async saveHostname(
|
||||
hostname: string,
|
||||
promptSub: Subscription,
|
||||
wasLocal = false,
|
||||
) {
|
||||
const loader = this.loader.open('Saving').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.setHostname({ hostname })
|
||||
|
||||
if (wasLocal) {
|
||||
const { protocol, port } = this.win.location
|
||||
const newUrl = `${protocol}//${hostname}.local${port ? ':' + port : ''}`
|
||||
|
||||
this.dialog
|
||||
.openConfirm({
|
||||
label: 'Hostname Changed',
|
||||
data: {
|
||||
content:
|
||||
`${this.i18n.transform('Your server is now reachable at')} ${hostname}.local` as i18nKey,
|
||||
yes: 'Open new address',
|
||||
no: 'Dismiss',
|
||||
},
|
||||
})
|
||||
.pipe(filter(Boolean))
|
||||
.subscribe(() => this.win.open(newUrl, '_blank'))
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
promptSub.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
onTitle() {
|
||||
const sub = this.dialog
|
||||
.openPrompt<string>({
|
||||
|
||||
@@ -27,7 +27,6 @@ export class ActionService {
|
||||
private readonly formDialog = inject(FormDialogService)
|
||||
|
||||
async present(data: PackageActionData) {
|
||||
data.prefill = data.prefill ?? data.requestInfo?.input?.value
|
||||
const { pkgInfo, actionInfo } = data
|
||||
|
||||
if (actionInfo.metadata.hasInput) {
|
||||
|
||||
@@ -121,6 +121,8 @@ export abstract class ApiService {
|
||||
|
||||
abstract toggleKiosk(enable: boolean): Promise<null>
|
||||
|
||||
abstract setHostname(params: { hostname: string }): Promise<null>
|
||||
|
||||
abstract setKeyboard(params: FullKeyboard): Promise<null>
|
||||
|
||||
abstract setLanguage(params: SetLanguageParams): Promise<null>
|
||||
|
||||
@@ -255,6 +255,10 @@ export class LiveApiService extends ApiService {
|
||||
})
|
||||
}
|
||||
|
||||
async setHostname(params: { hostname: string }): Promise<null> {
|
||||
return this.rpcRequest({ method: 'server.set-hostname', params })
|
||||
}
|
||||
|
||||
async setKeyboard(params: FullKeyboard): Promise<null> {
|
||||
return this.rpcRequest({ method: 'server.set-keyboard', params })
|
||||
}
|
||||
|
||||
@@ -447,6 +447,21 @@ export class MockApiService extends ApiService {
|
||||
return null
|
||||
}
|
||||
|
||||
async setHostname(params: { hostname: string }): Promise<null> {
|
||||
await pauseFor(1000)
|
||||
|
||||
const patch = [
|
||||
{
|
||||
op: PatchOp.REPLACE,
|
||||
path: '/serverInfo/hostname',
|
||||
value: params.hostname,
|
||||
},
|
||||
]
|
||||
this.mockRevision(patch)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
async setKeyboard(params: FullKeyboard): Promise<null> {
|
||||
await pauseFor(1000)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user