Custom maintenance messages support (#9417)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2025-07-01 18:35:10 +07:00
committed by GitHub
parent e617ffd15d
commit 6038cdbc04
18 changed files with 78 additions and 37 deletions
+2 -1
View File
@@ -9,7 +9,8 @@
"Unauthorized": "Neoprávněný přístup",
"UnknownMethod": "Neznámá metoda: {method}",
"InternalServerError": "Interní chyba serveru",
"MaintenanceWarning": "Plánovaná údržba za {time, plural, =1 {méně než minutu} other {# minut}}",
"MaintenanceWarning": "Plánovaná údržba za",
"MaintenanceWarningTime": "{time, plural, =1 {méně než minutu} other {# minut}}",
"AccountNotFound": "Účet nenalezen",
"AccountNotConfirmed": "Účet není potvrzen",
"WorkspaceNotFound": "Pracovní prostor nenalezen",
+2 -1
View File
@@ -9,7 +9,8 @@
"Unauthorized": "Nicht autorisiert",
"UnknownMethod": "Unbekannte Methode: {method}",
"InternalServerError": "Interner Serverfehler",
"MaintenanceWarning": "Wartung geplant in {time, plural, =1 {weniger als einer Minute} other {# Minuten}}",
"MaintenanceWarning": "Wartung geplant in",
"MaintenanceWarningTime": "{time, plural, =1 {weniger als einer Minute} other {# Minuten}}",
"AccountNotFound": "Konto nicht gefunden",
"AccountNotConfirmed": "Konto nicht bestätigt",
"WorkspaceNotFound": "Arbeitsbereich nicht gefunden",
+2 -1
View File
@@ -9,7 +9,8 @@
"Unauthorized": "Unauthorized",
"UnknownMethod": "Unknown method: {method}",
"InternalServerError": "Internal server error",
"MaintenanceWarning": "Maintenance Scheduled in {time, plural, =1 {less than a minute} other {# minutes}}",
"MaintenanceWarning": "Maintenance Scheduled in",
"MaintenanceWarningTime": "{time, plural, =1 {less than a minute} other {# minutes}}",
"AccountNotFound": "Account not found or the provided credentials are incorrect",
"AccountNotConfirmed": "Account not confirmed",
"WorkspaceNotFound": "Workspace not found",
+2 -1
View File
@@ -9,7 +9,8 @@
"Unauthorized": "No autorizado",
"UnknownMethod": "Método desconocido: {method}",
"InternalServerError": "Error interno del servidor",
"MaintenanceWarning": "Mantenimiento programado dentro de {time, plural, =1 {menos de un minuto} other {# minutos}}",
"MaintenanceWarning": "Mantenimiento programado dentro de",
"MaintenanceWarningTime": "{time, plural, =1 {menos de un minuto} other {# minutos}}",
"AccountNotFound": "Cuenta no encontrada",
"AccountNotConfirmed": "Cuenta no confirmada",
"WorkspaceNotFound": "Espacio de trabajo no encontrado",
+2 -1
View File
@@ -9,7 +9,8 @@
"Unauthorized": "Non autorisé",
"UnknownMethod": "Méthode inconnue : {method}",
"InternalServerError": "Erreur interne du serveur",
"MaintenanceWarning": "Maintenance prévue dans {time, plural, =1 {moins d'une minute} other {# minutes}}",
"MaintenanceWarning": "Maintenance prévue dans",
"MaintenanceWarningTime": "{time, plural, =1 {moins d'une minute} other {# minutes}}",
"AccountNotFound": "Compte non trouvé",
"AccountNotConfirmed": "Compte non confirmé",
"WorkspaceNotFound": "Espace de travail non trouvé",
+2 -1
View File
@@ -9,7 +9,8 @@
"Unauthorized": "Non autorizzato",
"UnknownMethod": "Metodo sconosciuto: {method}",
"InternalServerError": "Errore interno del server",
"MaintenanceWarning": "Manutenzione programmata tra {time, plural, =1 {meno di un minuto} other {# minuti}}",
"MaintenanceWarning": "Manutenzione programmata tra",
"MaintenanceWarningTime": "{time, plural, =1 {meno di un minuto} other {# minuti}}",
"AccountNotFound": "Account non trovato",
"AccountNotConfirmed": "Account non confermato",
"WorkspaceNotFound": "Spazio di lavoro non trovato",
+2 -1
View File
@@ -9,7 +9,8 @@
"Unauthorized": "認証されていません",
"UnknownMethod": "不明なメソッド: {method}",
"InternalServerError": "サーバー内部エラー",
"MaintenanceWarning": "{time, plural, =1 {1分以内に} other {#分後に}}メンテナンスが予定されています",
"MaintenanceWarning": "メンテナンスが予定されています",
"MaintenanceWarningTime": "{time, plural, =1 {1分以内に} other {#分後に}}",
"AccountNotFound": "アカウントが見つからないか、認証情報が間違っています",
"AccountNotConfirmed": "アカウントが確認されていません",
"WorkspaceNotFound": "ワークスペースが見つかりません",
+2 -1
View File
@@ -9,7 +9,8 @@
"Unauthorized": "Não autorizado",
"UnknownMethod": "Método desconhecido: {method}",
"InternalServerError": "Erro interno do servidor",
"MaintenanceWarning": "Manutenção programada dentro de {time, plural, =1 {menos de um minuto} other {# minutos}}",
"MaintenanceWarning": "Manutenção programada dentro de",
"MaintenanceWarningTime": "{time, plural, =1 {menos de um minuto} other {# minutos}}",
"AccountNotFound": "Conta não encontrada",
"AccountNotConfirmed": "Conta não confirmada",
"WorkspaceNotFound": "Espaço de trabalho não encontrado",
+2 -1
View File
@@ -9,7 +9,8 @@
"Unauthorized": "Неавторизован",
"UnknownMethod": "Неизвестный метод: {method}",
"InternalServerError": "Внутренняя ошибка сервера",
"MaintenanceWarning": "Серверные работы запланированы через {time, plural, one {# минуту} few {# минуты} other {# минут}}",
"MaintenanceWarning": "Серверные работы запланированы через",
"MaintenanceWarningTime": "{time, plural, one {# минуту} few {# минуты} other {# минут}}",
"AccountNotFound": "Аккаунт не найден",
"AccountNotConfirmed": "Аккаунт не подтвержден",
"WorkspaceNotFound": "Рабочее пространство не найдено",
+2 -1
View File
@@ -9,7 +9,8 @@
"Unauthorized": "未授权",
"UnknownMethod": "未知方法: {method}",
"InternalServerError": "内部服务器错误",
"MaintenanceWarning": "计划在 {time, plural, =1 {不到一分钟} other {# 分钟}} 内进行维护",
"MaintenanceWarning": "计划在 内进行维护 ",
"MaintenanceWarningTime": "{time, plural, =1 {不到一分钟} other {# 分钟}}",
"AccountNotFound": "账户未找到",
"AccountNotConfirmed": "账户未确认",
"WorkspaceNotFound": "工作区未找到",
+2 -1
View File
@@ -144,7 +144,8 @@ export default plugin(platformId, {
ExpiredLink: '' as StatusCode,
UnknownMethod: '' as StatusCode<{ method: string }>,
InternalServerError: '' as StatusCode,
MaintenanceWarning: '' as StatusCode<{ time: number }>,
MaintenanceWarning: '' as StatusCode<{ time: number, message?: string }>,
MaintenanceWarningTime: '' as IntlString,
AccountNotFound: '' as StatusCode<{ account?: string }>,
AccountMismatch: '' as StatusCode<{ account?: string, requiredAccount?: string }>,
AccountNotConfirmed: '' as StatusCode,
@@ -97,10 +97,12 @@
let readonlyAccount = false
let systemAccount = false
let maintenanceTime = -1
let maintenanceMessage: string | undefined
addEventListener(PlatformEvent, async (_event, _status: Status) => {
if (_status.code === platform.status.MaintenanceWarning) {
maintenanceTime = _status.params.time
maintenanceMessage = _status.params.message
} else {
if (readonlyAccount) return
if (_status.code === platform.status.ReadOnlyAccount) {
@@ -242,7 +244,12 @@
>
{#if maintenanceTime > 0}
<div class="flex-grow flex-center flex-row-center" class:maintenanceScheduled={maintenanceTime > 0}>
<Label label={platform.status.MaintenanceWarning} params={{ time: maintenanceTime }} />
{#if maintenanceMessage !== undefined && maintenanceMessage.trim() !== ''}
{maintenanceMessage}
{:else}
<Label label={platform.status.MaintenanceWarning} />
{/if}
<Label label={platform.status.MaintenanceWarningTime} params={{ time: maintenanceTime }} />
</div>
{:else if status.severity !== Severity.OK}
<StatusComponent {status} />
+2 -1
View File
@@ -112,7 +112,8 @@ export default async () => {
if (event.event === WorkspaceEvent.MaintenanceNotification) {
void setPlatformStatus(
new Status(Severity.WARNING, platform.status.MaintenanceWarning, {
time: event.params.timeMinutes
time: event.params.timeMinutes,
message: event.params.message ?? ''
})
)
}
@@ -139,27 +139,38 @@
}
$: update($ticker)
let maintenanceMessage = 'A new version is planned to be installed in'
</script>
{#if isAdminUser()}
<div class="flex flex-col">
<div class="flex-row-center p-1">
<div class="p-3">1.</div>
<Button
icon={IconArrowRight}
label={getEmbeddedLabel('Set maintenance warning')}
on:click={() => {
const endpoint = getMetadata(login.metadata.AccountsUrl) ?? ''
if (endpoint !== '') {
void fetch(
concatLink(endpoint, `/api/v1/manage?token=${token}&operation=maintenance&timeout=${warningTimeout}`),
{
method: 'PUT'
}
)
}
}}
/>
<div class="flex p-1 flex-row-center">
<div class="flex-row-center flex-grow">
<EditBox bind:value={maintenanceMessage}></EditBox>
</div>
<Button
icon={IconArrowRight}
label={getEmbeddedLabel('Set maintenance warning')}
on:click={() => {
const endpoint = getMetadata(login.metadata.AccountsUrl) ?? ''
if (endpoint !== '') {
void fetch(
concatLink(endpoint, `/api/v1/manage?token=${token}&operation=maintenance&timeout=${warningTimeout}`),
{
method: 'PUT',
body: JSON.stringify({ message: maintenanceMessage }),
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
}
)
}
}}
/>
</div>
<div class="flex-col p-1">
<div class="flex-row-center p-1">
<EditBox kind={'underline'} format={'number'} bind:value={warningTimeout} /> min
+7 -4
View File
@@ -205,6 +205,7 @@ export function startHttpServer (
try {
const token = (req.query.token as string) ?? (req.headers.authorization ?? '').split(' ')[1]
const payload = decodeToken(token)
if (payload.extra?.admin !== 'true' && payload.account !== systemAccountUuid) {
console.warn('Non admin attempt to maintenance action', { payload })
res.writeHead(404, {})
@@ -216,11 +217,13 @@ export function startHttpServer (
switch (operation) {
case 'maintenance': {
const timeMinutes = parseInt((req.query.timeout as string) ?? '5')
sessions.scheduleMaintenance(timeMinutes)
void retrieveJson(req).then((message) => {
const timeMinutes = parseInt((req.query.timeout as string) ?? '5')
sessions.scheduleMaintenance(timeMinutes, message)
res.writeHead(200)
res.end()
res.writeHead(200)
res.end()
})
return
}
case 'wipe-statistics': {
+6 -1
View File
@@ -312,8 +312,13 @@ export function serveAccount (measureCtx: MeasureContext, brandings: BrandingMap
const transactors = getAllTransactors(EndpointKind.Internal)
for (const tr of transactors) {
const serverEndpoint = tr.replaceAll('wss://', 'https://').replace('ws://', 'http://')
const jsonBody = JSON.stringify(req.request.body as any)
await fetch(serverEndpoint + `/api/v1/manage?token=${token}&operation=maintenance&timeout=${timeMinutes}`, {
method: 'PUT'
method: 'PUT',
body: jsonBody,
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
})
}
+1 -1
View File
@@ -729,7 +729,7 @@ export interface SessionManager {
closeWorkspaces: (ctx: MeasureContext) => Promise<void>
scheduleMaintenance: (timeMinutes: number) => void
scheduleMaintenance: (timeMinutes: number, message?: string) => void
profiling?: {
start: () => void
+6 -3
View File
@@ -109,6 +109,7 @@ export class TSessionManager implements SessionManager {
maintenanceTimer: any
timeMinutes = 0
maintenanceMessage?: string
modelVersion = process.env.MODEL_VERSION ?? ''
serverVersion = process.env.VERSION ?? ''
@@ -146,8 +147,9 @@ export class TSessionManager implements SessionManager {
this.ticksContext = ctx.newChild('ticks', {})
}
scheduleMaintenance (timeMinutes: number): void {
scheduleMaintenance (timeMinutes: number, message?: string): void {
this.timeMinutes = timeMinutes
this.maintenanceMessage = message
this.sendMaintenanceWarning()
@@ -190,7 +192,8 @@ export class TSessionManager implements SessionManager {
space: core.space.DerivedTx,
createdBy: core.account.System,
params: {
timeMinutes: this.timeMinutes
timeMinutes: this.timeMinutes,
message: this.maintenanceMessage
}
}
}
@@ -306,7 +309,7 @@ export class TSessionManager implements SessionManager {
wsId,
total: s.session.requests.size,
user: s.session.getUser(),
...cutObjectArray(r.params)
params: cutObjectArray(r.params)
})
}
}