mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Misc frontend fixes (#2974)
* fix dependency input warning and extra comma * clean up buttons during install in marketplace preview * chore: grayscale and closing action-bar * fix prerelease precedence * fix duplicate url for addSsl on ssl proto * no warning for soft uninstall * fix: stop logs from repeating disconnected status and add 1 second delay between reconnection attempts * fix stop on reactivation of critical task * fix: fix disconnected toast * fix: updates styles * fix: updates styles * misc fixes * beta.33 * fix updates badge and initialization of marketplace preview controls --------- Co-authored-by: waterplea <alexander@inkin.ru> Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -46,6 +46,7 @@ import { ClientStorageService } from './services/client-storage.service'
|
||||
import { DateTransformerService } from './services/date-transformer.service'
|
||||
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
||||
import { StorageService } from './services/storage.service'
|
||||
import { FilterUpdatesPipe } from './routes/portal/routes/updates/filter-updates.pipe'
|
||||
|
||||
const {
|
||||
useMocks,
|
||||
@@ -56,6 +57,7 @@ export const APP_PROVIDERS = [
|
||||
provideEventPlugins(),
|
||||
I18N_PROVIDERS,
|
||||
FilterPackagesPipe,
|
||||
FilterUpdatesPipe,
|
||||
UntypedFormBuilder,
|
||||
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
|
||||
tuiButtonOptionsProvider({ size: 'm' }),
|
||||
|
||||
@@ -103,7 +103,6 @@ import { HeaderStatusComponent } from './status.component'
|
||||
|
||||
&:has([data-status='neutral']) {
|
||||
--status: var(--tui-status-neutral);
|
||||
filter: none;
|
||||
}
|
||||
|
||||
&:has([data-status='success']) {
|
||||
|
||||
@@ -18,18 +18,18 @@
|
||||
|
||||
@if (followLogs | logs | async; as logs) {
|
||||
<section childList (waMutationObserver)="scrollToBottom()">
|
||||
@for (log of logs; track log) {
|
||||
@for (log of logs; track $index) {
|
||||
<pre [innerHTML]="log | dompurify"></pre>
|
||||
}
|
||||
|
||||
@if ((status$ | async) !== 'connected') {
|
||||
<p class="loading-dots" [attr.data-status]="status$.value">
|
||||
<div class="loading-dots" [attr.data-status]="status$.value">
|
||||
{{
|
||||
status$.value === 'reconnecting'
|
||||
? ('Reconnecting' | i18n)
|
||||
: ('Waiting for network connectivity' | i18n)
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
} @else {
|
||||
|
||||
@@ -48,4 +48,5 @@
|
||||
pre {
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -8,16 +8,19 @@ import {
|
||||
import {
|
||||
bufferTime,
|
||||
catchError,
|
||||
concat,
|
||||
defer,
|
||||
delay,
|
||||
EMPTY,
|
||||
filter,
|
||||
ignoreElements,
|
||||
map,
|
||||
merge,
|
||||
Observable,
|
||||
of,
|
||||
repeat,
|
||||
scan,
|
||||
skipWhile,
|
||||
startWith,
|
||||
switchMap,
|
||||
take,
|
||||
tap,
|
||||
@@ -62,12 +65,19 @@ export class LogsPipe implements PipeTransform {
|
||||
),
|
||||
).pipe(
|
||||
catchError(() =>
|
||||
this.connection.pipe(
|
||||
tap(v => this.logs.status$.next(v ? 'reconnecting' : 'disconnected')),
|
||||
filter(Boolean),
|
||||
take(1),
|
||||
ignoreElements(),
|
||||
startWith(this.getMessage(false)),
|
||||
concat(
|
||||
this.logs.status$.value === 'connected'
|
||||
? of(this.getMessage(false))
|
||||
: EMPTY,
|
||||
this.connection.pipe(
|
||||
tap(v =>
|
||||
this.logs.status$.next(v ? 'reconnecting' : 'disconnected'),
|
||||
),
|
||||
filter(Boolean),
|
||||
delay(1000),
|
||||
take(1),
|
||||
ignoreElements(),
|
||||
),
|
||||
),
|
||||
),
|
||||
repeat(),
|
||||
@@ -76,11 +86,11 @@ export class LogsPipe implements PipeTransform {
|
||||
}
|
||||
|
||||
private getMessage(success: boolean): string {
|
||||
return `<p style="color: ${
|
||||
return `<div style="color: ${
|
||||
success ? 'var(--tui-status-positive)' : 'var(--tui-status-negative)'
|
||||
}; text-align: center;">${this.i18n.transform(
|
||||
success ? 'Reconnected' : 'Disconnected',
|
||||
)} at ${toLocalIsoString(new Date())}</p>`
|
||||
)} at ${toLocalIsoString(new Date())}</div>`
|
||||
}
|
||||
|
||||
private get options() {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { HeaderComponent } from './components/header/header.component'
|
||||
</main>
|
||||
<app-tabs />
|
||||
@if (update(); as update) {
|
||||
<tui-action-bar *tuiActionBar="bar">
|
||||
<tui-action-bar *tuiActionBar="bar()">
|
||||
@if (update === true) {
|
||||
<tui-icon icon="@tui.check" class="g-positive" />
|
||||
Download complete, restart to apply changes
|
||||
@@ -77,8 +77,7 @@ import { HeaderComponent } from './components/header/header.component'
|
||||
|
||||
@include taiga.transition(filter);
|
||||
|
||||
header:has([data-status='success']) + &,
|
||||
header:has([data-status='neutral']) + & {
|
||||
header:has([data-status='success']) + & {
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
@@ -104,7 +103,7 @@ export class PortalComponent {
|
||||
|
||||
readonly name = toSignal(this.patch.watch$('ui', 'name'))
|
||||
readonly update = toSignal(inject(OSService).updating$)
|
||||
bar = true
|
||||
readonly bar = signal(true)
|
||||
|
||||
getProgress(size: number, downloaded: number): number {
|
||||
return Math.round((100 * downloaded) / (size || 1))
|
||||
@@ -114,7 +113,7 @@ export class PortalComponent {
|
||||
const loader = this.loader.open('Beginning restart').subscribe()
|
||||
|
||||
try {
|
||||
this.bar = false
|
||||
this.bar.set(false)
|
||||
await this.api.restartServer({})
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
|
||||
@@ -3,11 +3,9 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
Input,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { Router } from '@angular/router'
|
||||
import { MarketplacePkgBase } from '@start9labs/marketplace'
|
||||
import {
|
||||
ErrorService,
|
||||
Exver,
|
||||
@@ -30,21 +28,20 @@ import {
|
||||
import { dryUpdate } from 'src/app/utils/dry-update'
|
||||
import { getAllPackages, getManifest } from 'src/app/utils/get-package-data'
|
||||
import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
||||
|
||||
import { MarketplacePreviewComponent } from '../modals/preview.component'
|
||||
import { MarketplaceAlertsService } from '../services/alerts.service'
|
||||
|
||||
@Component({
|
||||
selector: 'marketplace-controls',
|
||||
template: `
|
||||
@if (localPkg) {
|
||||
@if (localPkg | toManifest; as localManifest) {
|
||||
@switch (localManifest.version | compareExver: version() || '') {
|
||||
@if (localPkg(); as local) {
|
||||
@if (local.stateInfo.state === 'installed') {
|
||||
@switch ((local | toManifest).version | compareExver: version()) {
|
||||
@case (1) {
|
||||
<button
|
||||
tuiButton
|
||||
type="button"
|
||||
appearance="secondary-destructive"
|
||||
appearance="warning"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
{{ 'Downgrade' | i18n }}
|
||||
@@ -81,17 +78,17 @@ import { MarketplaceAlertsService } from '../services/alerts.service'
|
||||
{{
|
||||
('View' | i18n) +
|
||||
' ' +
|
||||
($any(localPkg.stateInfo.state | titlecase) | i18n)
|
||||
($any(local.stateInfo.state | titlecase) | i18n)
|
||||
}}
|
||||
</button>
|
||||
} @else {
|
||||
<button
|
||||
tuiButton
|
||||
type="button"
|
||||
[appearance]="localFlavor ? 'warning' : 'primary'"
|
||||
appearance="primary"
|
||||
(click)="tryInstall()"
|
||||
>
|
||||
{{ localFlavor ? ('Switch' | i18n) : ('Install' | i18n) }}
|
||||
{{ localFlavor() ? ('Switch' | i18n) : ('Install' | i18n) }}
|
||||
</button>
|
||||
}
|
||||
`,
|
||||
@@ -116,29 +113,23 @@ export class MarketplaceControlsComponent {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly preview = inject(MarketplacePreviewComponent)
|
||||
|
||||
protected readonly version = toSignal(this.preview.version$)
|
||||
|
||||
@Input({ required: true })
|
||||
pkg!: MarketplacePkgBase
|
||||
|
||||
@Input()
|
||||
localPkg!: PackageDataEntry | null
|
||||
|
||||
@Input()
|
||||
localFlavor!: boolean
|
||||
|
||||
version = input.required<string>()
|
||||
installAlert = input.required<string | null>()
|
||||
localPkg = input.required<PackageDataEntry | null>()
|
||||
localFlavor = input.required<boolean>()
|
||||
// only present if side loading
|
||||
@Input()
|
||||
file?: File
|
||||
file = input<File>()
|
||||
|
||||
async tryInstall() {
|
||||
const currentUrl = this.file
|
||||
const localPkg = this.localPkg()
|
||||
|
||||
const currentUrl = this.file()
|
||||
? null
|
||||
: await firstValueFrom(this.marketplaceService.currentRegistryUrl$)
|
||||
const originalUrl = this.localPkg?.registry || null
|
||||
const originalUrl = localPkg?.registry || null
|
||||
|
||||
if (!this.localPkg) {
|
||||
if (await this.alerts.alertInstall(this.pkg)) {
|
||||
if (!localPkg) {
|
||||
if (await this.alerts.alertInstall(this.installAlert() || '')) {
|
||||
this.installOrUpload(currentUrl)
|
||||
}
|
||||
return
|
||||
@@ -152,12 +143,11 @@ export class MarketplaceControlsComponent {
|
||||
return
|
||||
}
|
||||
|
||||
const localManifest = getManifest(this.localPkg)
|
||||
const version = this.version() || ''
|
||||
const localManifest = getManifest(localPkg)
|
||||
|
||||
if (
|
||||
hasCurrentDeps(localManifest.id, await getAllPackages(this.patch)) &&
|
||||
this.exver.compareExver(localManifest.version, version) !== 0
|
||||
this.exver.compareExver(localManifest.version, this.version()) !== 0
|
||||
) {
|
||||
this.dryInstall(currentUrl)
|
||||
} else {
|
||||
@@ -171,9 +161,8 @@ export class MarketplaceControlsComponent {
|
||||
|
||||
private async dryInstall(url: string | null) {
|
||||
const id = this.preview.pkgId
|
||||
const version = this.version() || ''
|
||||
const breakages = dryUpdate(
|
||||
{ id, version },
|
||||
{ id, version: this.version() },
|
||||
await getAllPackages(this.patch),
|
||||
this.exver,
|
||||
)
|
||||
@@ -187,7 +176,7 @@ export class MarketplaceControlsComponent {
|
||||
}
|
||||
|
||||
private async installOrUpload(url: string | null) {
|
||||
if (this.file) {
|
||||
if (this.file()) {
|
||||
await this.upload()
|
||||
this.router.navigate(['/portal', 'services'])
|
||||
} else if (url) {
|
||||
@@ -197,11 +186,10 @@ export class MarketplaceControlsComponent {
|
||||
|
||||
private async install(url: string) {
|
||||
const loader = this.loader.open('Beginning install').subscribe()
|
||||
const version = this.version() || ''
|
||||
const id = this.preview.pkgId
|
||||
|
||||
try {
|
||||
await this.marketplaceService.installPackage(id, version, url)
|
||||
await this.marketplaceService.installPackage(id, this.version(), url)
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
@@ -210,11 +198,14 @@ export class MarketplaceControlsComponent {
|
||||
}
|
||||
|
||||
private async upload() {
|
||||
const file = this.file()
|
||||
if (!file) throw new Error('no file detected')
|
||||
|
||||
const loader = this.loader.open('Starting upload').subscribe()
|
||||
|
||||
try {
|
||||
const { upload } = await this.api.sideloadPackage()
|
||||
this.api.uploadPackage(upload, this.file!).catch(console.error)
|
||||
this.api.uploadPackage(upload, file).catch(console.error)
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
|
||||
@@ -56,7 +56,8 @@ import { MarketplaceControlsComponent } from './controls.component'
|
||||
<marketplace-controls
|
||||
slot="controls"
|
||||
class="controls-wrapper"
|
||||
[pkg]="pkg()"
|
||||
[version]="pkg().version"
|
||||
[installAlert]="pkg().alerts.install"
|
||||
[localPkg]="local$ | async"
|
||||
[localFlavor]="!!(flavor$ | async)"
|
||||
/>
|
||||
|
||||
@@ -62,9 +62,7 @@ export class MarketplaceAlertsService {
|
||||
})
|
||||
}
|
||||
|
||||
async alertInstall({ alerts }: MarketplacePkgBase): Promise<boolean> {
|
||||
const content = alerts.install
|
||||
|
||||
async alertInstall(content: string): Promise<boolean> {
|
||||
return (
|
||||
!content ||
|
||||
(!!content &&
|
||||
|
||||
@@ -90,17 +90,20 @@ export type PackageActionData = {
|
||||
`,
|
||||
styles: `
|
||||
tui-notification {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 1.4rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.service-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1.4rem;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
img {
|
||||
height: 20px;
|
||||
margin-right: 4px;
|
||||
height: 1.25rem;
|
||||
margin-right: 0.25rem;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -192,7 +195,7 @@ export class ActionInputModal {
|
||||
task.when?.condition === 'input-not-matches' &&
|
||||
task.input &&
|
||||
json
|
||||
.compare(input, task.input)
|
||||
.compare(input, task.input.value)
|
||||
.some(op => op.op === 'add' || op.op === 'replace'),
|
||||
),
|
||||
)
|
||||
@@ -201,9 +204,8 @@ export class ActionInputModal {
|
||||
if (!breakages.length) return true
|
||||
|
||||
const message = `${this.i18n.transform('As a result of this change, the following services will no longer work properly and may crash')}:<ul>`
|
||||
const content = `${message}${breakages.map(
|
||||
id => `<li><b>${getManifest(packages[id]!).title}</b></li>`,
|
||||
)}</ul>` as i18nKey
|
||||
const content =
|
||||
`${message}${breakages.map(id => `<li><b>${getManifest(packages[id]!).title}</b></li>`)}</ul>` as i18nKey
|
||||
|
||||
return firstValueFrom(
|
||||
this.dialog
|
||||
|
||||
@@ -29,7 +29,8 @@ import { MarketplacePkgSideload } from './sideload.utils'
|
||||
<marketplace-controls
|
||||
slot="controls"
|
||||
class="controls-wrapper"
|
||||
[pkg]="pkg()"
|
||||
[version]="pkg().version"
|
||||
[installAlert]="pkg().alerts.install"
|
||||
[localPkg]="local$ | async"
|
||||
[localFlavor]="!!(flavor$ | async)"
|
||||
[file]="file()"
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||
import { Exver } from '@start9labs/shared'
|
||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||
import {
|
||||
InstalledState,
|
||||
PackageDataEntry,
|
||||
UpdatingState,
|
||||
} from 'src/app/services/patch-db/data-model'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
|
||||
@Pipe({
|
||||
name: 'filterUpdates',
|
||||
@@ -15,15 +11,14 @@ export class FilterUpdatesPipe implements PipeTransform {
|
||||
|
||||
transform(
|
||||
pkgs: MarketplacePkg[],
|
||||
local: Record<
|
||||
string,
|
||||
PackageDataEntry<InstalledState | UpdatingState>
|
||||
> = {},
|
||||
local: Record<string, PackageDataEntry> = {},
|
||||
): MarketplacePkg[] {
|
||||
return pkgs.filter(({ id, version, flavor }) => {
|
||||
return pkgs.filter(({ id, flavor, version }) => {
|
||||
const localPkg = local[id]
|
||||
return (
|
||||
localPkg &&
|
||||
!!localPkg &&
|
||||
(localPkg.stateInfo.state === 'installed' ||
|
||||
localPkg.stateInfo.state === 'updating') &&
|
||||
this.exver.getFlavor(localPkg.stateInfo.manifest.version) === flavor &&
|
||||
this.exver.compareExver(
|
||||
version,
|
||||
|
||||
@@ -44,7 +44,7 @@ import UpdatesComponent from './updates.component'
|
||||
template: `
|
||||
<tr (click)="expanded.set(!expanded())">
|
||||
<td>
|
||||
<div [style.gap.rem]="0.75">
|
||||
<div [style.gap.rem]="0.75" [style.padding-inline-end.rem]="1">
|
||||
<tui-avatar size="s"><img alt="" [src]="item().icon" /></tui-avatar>
|
||||
<span tuiTitle [style.margin]="'-0.125rem 0 0'">
|
||||
<b tuiFade>{{ item().title }}</b>
|
||||
@@ -81,7 +81,6 @@ import UpdatesComponent from './updates.component'
|
||||
</button>
|
||||
@if (local().stateInfo.state === 'updating') {
|
||||
<tui-progress-circle
|
||||
class="g-positive"
|
||||
size="xs"
|
||||
[max]="100"
|
||||
[value]="
|
||||
@@ -175,7 +174,7 @@ import UpdatesComponent from './updates.component'
|
||||
white-space: nowrap;
|
||||
|
||||
div {
|
||||
justify-content: flex-end;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { inject, Injectable } from '@angular/core'
|
||||
import { Exver } from '@start9labs/shared'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import {
|
||||
combineLatest,
|
||||
@@ -19,13 +18,13 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { NotificationService } from 'src/app/services/notification.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { getManifest } from 'src/app/utils/get-package-data'
|
||||
import { FilterUpdatesPipe } from '../routes/portal/routes/updates/filter-updates.pipe'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class BadgeService {
|
||||
private readonly notifications = inject(NotificationService)
|
||||
private readonly exver = inject(Exver)
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
private readonly system$ = inject(OSService).updateAvailable$.pipe(
|
||||
map(Number),
|
||||
@@ -34,6 +33,7 @@ export class BadgeService {
|
||||
.watch$('serverInfo', 'ntpSynced')
|
||||
.pipe(map(synced => Number(!synced)))
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
private readonly filterUpdatesPipe = inject(FilterUpdatesPipe)
|
||||
|
||||
private readonly local$ = inject(ConnectionService).pipe(
|
||||
filter(Boolean),
|
||||
@@ -66,17 +66,9 @@ export class BadgeService {
|
||||
([marketplace, local]) =>
|
||||
Object.entries(marketplace).reduce(
|
||||
(list, [_, store]) =>
|
||||
store?.packages.reduce(
|
||||
(result, { id, version }) =>
|
||||
local[id] &&
|
||||
this.exver.compareExver(
|
||||
version,
|
||||
getManifest(local[id]!).version,
|
||||
) === 1
|
||||
? result.add(id)
|
||||
: result,
|
||||
list,
|
||||
) || list,
|
||||
this.filterUpdatesPipe
|
||||
.transform(store?.packages || [], local)
|
||||
.reduce((result, { id }) => result.add(id), list),
|
||||
new Set<string>(),
|
||||
).size,
|
||||
),
|
||||
|
||||
@@ -57,6 +57,10 @@ export class StandardActionsService {
|
||||
content = `${content}${content ? ' ' : ''}${this.i18n.transform('Services that depend on')} ${title} ${this.i18n.transform('will no longer work properly and may crash.')}`
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
return this.doUninstall({ id, force, soft })
|
||||
}
|
||||
|
||||
this.dialog
|
||||
.openConfirm({
|
||||
label: 'Warning',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { inject, Injectable } from '@angular/core'
|
||||
import { CanActivateFn, IsActiveMatchOptions, Router } from '@angular/router'
|
||||
import { DialogService, i18nPipe } from '@start9labs/shared'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TUI_TRUE_HANDLER } from '@taiga-ui/cdk'
|
||||
import { TuiAlertService } from '@taiga-ui/core'
|
||||
import {
|
||||
@@ -47,9 +47,7 @@ export class StateService extends Observable<RR.ServerState | null> {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly router = inject(Router)
|
||||
private readonly network$ = inject(NetworkService)
|
||||
|
||||
private readonly single$ = new Subject<RR.ServerState>()
|
||||
|
||||
private readonly trigger$ = new BehaviorSubject<void>(undefined)
|
||||
private readonly poll$ = this.trigger$.pipe(
|
||||
switchMap(() =>
|
||||
@@ -101,7 +99,7 @@ export class StateService extends Observable<RR.ServerState | null> {
|
||||
})
|
||||
.pipe(
|
||||
takeUntil(
|
||||
combineLatest([this.stream$, this.network$]).pipe(
|
||||
combineLatest([this.stream$.pipe(skip(1)), this.network$]).pipe(
|
||||
filter(state => state.every(Boolean)),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
{
|
||||
"/rpc/v1": {
|
||||
"target": "http://<CHANGE_ME>/rpc/v1"
|
||||
"target": "http://<CHANGE_ME>"
|
||||
},
|
||||
"/ws/*": {
|
||||
"target": "http://<CHANGE_ME>",
|
||||
"/ws/**": {
|
||||
"target": "wss://159.223.45.210",
|
||||
"secure": false,
|
||||
"ws": true
|
||||
},
|
||||
"/public/*": {
|
||||
"target": "http://<CHANGE_ME>/public",
|
||||
"pathRewrite": {
|
||||
"^/public": ""
|
||||
}
|
||||
"/public/**": {
|
||||
"target": "http://<CHANGE_ME>"
|
||||
},
|
||||
"/rest/rpc/*": {
|
||||
"target": "http://<CHANGE_ME>/rest/rpc",
|
||||
"pathRewrite": {
|
||||
"^/rest/rpc": ""
|
||||
}
|
||||
"/rest/rpc/**": {
|
||||
"target": "http://<CHANGE_ME>"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user