From 0c72386e622805432465cf54d6e5c22803a205fa Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 4 Oct 2024 09:56:18 +0100 Subject: [PATCH 1/8] Initial attempt at this, but I don't like it. We've managed to cnofuse rendering/command layer again just because we started with the rendering rather than the command lol. --- src/safemode/PersistentConfigEditor.ts | 63 ++++++++ src/safemode/PersistentConfigRenderer.tsx | 186 ++++++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 src/safemode/PersistentConfigEditor.ts create mode 100644 src/safemode/PersistentConfigRenderer.tsx diff --git a/src/safemode/PersistentConfigEditor.ts b/src/safemode/PersistentConfigEditor.ts new file mode 100644 index 00000000..1b04c5e1 --- /dev/null +++ b/src/safemode/PersistentConfigEditor.ts @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2024 Gnuxie +// +// SPDX-License-Identifier: AFL-3.0 + +import { TObject } from "@sinclair/typebox"; +import { + ConfigDescription, + MJOLNIR_PROTECTED_ROOMS_EVENT_TYPE, + MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE, + MjolnirEnabledProtectionsDescription, + MjolnirEnabledProtectionsEventType, + MjolnirPolicyRoomsDescription, + MjolnirProtectedRoomsDescription, + PersistentConfigData, + StandardPersistentConfigData, +} from "matrix-protection-suite"; +import { + BotSDKAccountDataConfigBackend, + MatrixSendClient, +} from "matrix-protection-suite-for-matrix-bot-sdk"; + +export interface PersistentConfigEditor { + getConfigAdaptors(): PersistentConfigData[]; +} + +export class StandardPersistentConfigEditor implements PersistentConfigEditor { + private readonly configAdaptors: PersistentConfigData[] = []; + public constructor(client: MatrixSendClient) { + // We do some sweepy sweepy casting here because the ConfigMirror has methods + // that accept a specific shape, and obviously that means the type parameter + // becomes contravariant. I think the only way to fix this is to make the mirrors + // only work with the general shape rather than the specific one, in the way that + // the `remove` methods do, but I'm not convinced that works either, as those + // methods accept a Record that at least has the keys from the specific shape + // of the config. + this.configAdaptors = [ + new StandardPersistentConfigData( + MjolnirPolicyRoomsDescription as unknown as ConfigDescription, + new BotSDKAccountDataConfigBackend( + client, + MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE + ) + ), + new StandardPersistentConfigData( + MjolnirProtectedRoomsDescription as unknown as ConfigDescription, + new BotSDKAccountDataConfigBackend( + client, + MJOLNIR_PROTECTED_ROOMS_EVENT_TYPE + ) + ), + new StandardPersistentConfigData( + MjolnirEnabledProtectionsDescription as unknown as ConfigDescription, + new BotSDKAccountDataConfigBackend( + client, + MjolnirEnabledProtectionsEventType + ) + ), + ]; + } + getConfigAdaptors(): PersistentConfigData[] { + return this.configAdaptors; + } +} diff --git a/src/safemode/PersistentConfigRenderer.tsx b/src/safemode/PersistentConfigRenderer.tsx new file mode 100644 index 00000000..b936215a --- /dev/null +++ b/src/safemode/PersistentConfigRenderer.tsx @@ -0,0 +1,186 @@ +// SPDX-FileCopyrightText: 2024 Gnuxie +// +// SPDX-License-Identifier: AFL-3.0 + +import { Ok, Result, isError } from "@gnuxie/typescript-result"; +import { + DeadDocumentJSX, + DocumentNode, +} from "@the-draupnir-project/interface-manager"; +import { + MatrixRoomAlias, + MatrixRoomID, + MatrixUserID, +} from "@the-draupnir-project/matrix-basic-types"; +import { + ConfigParseError, + ConfigPropertyError, + PersistentConfigData, +} from "matrix-protection-suite"; +import { + renderMentionPill, + renderRoomPill, +} from "../commands/interface-manager/MatrixHelpRenderer"; + +// FIXME: This is backwards, we should generate some kind of object that can be rendered by the interface manager, +// that already has all the information fetched. +export interface PersistentConfigRenderer { + renderConfig(config: PersistentConfigData): Promise>; + renderStatusOfConfigAdaptors( + adaptors: PersistentConfigData[] + ): Promise>; +} + +function findError( + propertyKey: string, + errors: ConfigPropertyError[] +): ConfigPropertyError | undefined { + const path = `/${propertyKey}`; + return errors.find((error) => error.path.startsWith(path)); +} + +function findItemError( + propertyKey: string, + index: number, + errors: ConfigPropertyError[] +): ConfigPropertyError | undefined { + const path = `/${propertyKey}/${index}`; + return errors.find((error) => error.path === path); +} + +function renderPrimitiveValue(value: string, type: string): DocumentNode { + return ( + + {value} ({type}) + + ); +} + +function renderConfigPropertyValue(value: unknown): DocumentNode { + if (typeof value === "object" && value !== null) { + if (value instanceof MatrixRoomAlias || value instanceof MatrixRoomID) { + return renderRoomPill(value); + } else if (value instanceof MatrixUserID) { + return renderMentionPill(value.toString(), value.toString()); + } else { + return ( + + {String(value)}{" "} + (object) + + ); + } + } else if (typeof value === "string") { + return renderPrimitiveValue(value, "string"); + } else if (typeof value === "number") { + return renderPrimitiveValue(String(value), "number"); + } else { + return renderPrimitiveValue(String(value), "unknown"); + } +} + +function renderConfigPropertyItem( + propertyKey: string, + index: number, + value: unknown, + errors: ConfigPropertyError[] +): DocumentNode { + const error = findItemError(propertyKey, index, errors); + return ( +
  • + {renderConfigPropertyError(error)} + {index}: {renderConfigPropertyValue(value)} +
  • + ); +} + +function renderConfigPropertyError( + error: ConfigPropertyError | undefined +): string { + return error === undefined ? "🟢" : "🔴"; +} + +function renderConfigProperty( + propertyKey: string, + config: PersistentConfigData, + data: Record, + errors: ConfigPropertyError[] +): DocumentNode { + const propertyValue = data[propertyKey]; + const error = findError(propertyKey, errors); + if (Array.isArray(propertyValue)) { + return ( +
  • + {renderConfigPropertyError(error)} + {propertyKey}:{" "} +
      + {propertyValue.map((value, index) => + renderConfigPropertyItem(propertyKey, index, value, errors) + )} +
    +
  • + ); + } + return ( +
  • + {renderConfigPropertyError(error)} + {propertyKey}: {renderConfigPropertyValue(propertyValue)} +
  • + ); +} + +function renderBodgedConfig(value: unknown): DocumentNode { + return ( + + The config seems to be entirely invalid:{" "} + {renderConfigPropertyValue(value)} + + ); +} + +export const StandardPersistentConfigRenderer = Object.freeze({ + async renderConfig( + config: PersistentConfigData + ): Promise> { + const dataResult = await config.requestConfig(); + if ( + isError(dataResult) && + !(dataResult.error instanceof ConfigParseError) + ) { + return dataResult; + } + const [data, errors] = dataResult.match( + (ok) => [ok, []], + (error) => { + if (error instanceof ConfigParseError) { + return [error.config, error.errors]; + } else { + throw new TypeError( + "We should have been able to narrow to ConfigParseError" + ); + } + } + ); + if (typeof data !== "object" || data === null) { + return Ok(renderBodgedConfig(data)); + } + return Ok( + + {config.description.schema.title ?? "Untitled Config"} + {config.description + .properties() + .map((property) => + renderConfigProperty( + property.name, + config, + data as Record, + errors + ) + )} + + ); + }, + async renderStatusOfConfigAdaptors( + adaptors: PersistentConfigData[] + ): Promise> {}, +}) satisfies PersistentConfigRenderer; From 994b23574d94705b0c66ef69d60b141b9e680d77 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 4 Oct 2024 10:41:15 +0100 Subject: [PATCH 2/8] Remove side effects from config renderers. --- src/safemode/PersistentConfigEditor.ts | 36 +++++++++++ src/safemode/PersistentConfigRenderer.tsx | 74 ++++++++--------------- 2 files changed, 62 insertions(+), 48 deletions(-) diff --git a/src/safemode/PersistentConfigEditor.ts b/src/safemode/PersistentConfigEditor.ts index 1b04c5e1..cf63d16f 100644 --- a/src/safemode/PersistentConfigEditor.ts +++ b/src/safemode/PersistentConfigEditor.ts @@ -2,9 +2,11 @@ // // SPDX-License-Identifier: AFL-3.0 +import { Ok, Result, isError } from "@gnuxie/typescript-result"; import { TObject } from "@sinclair/typebox"; import { ConfigDescription, + ConfigParseError, MJOLNIR_PROTECTED_ROOMS_EVENT_TYPE, MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE, MjolnirEnabledProtectionsDescription, @@ -19,8 +21,15 @@ import { MatrixSendClient, } from "matrix-protection-suite-for-matrix-bot-sdk"; +export type PersistentConfigStatus = { + readonly description: ConfigDescription; + readonly data: unknown; + readonly error: ConfigParseError | undefined; +}; + export interface PersistentConfigEditor { getConfigAdaptors(): PersistentConfigData[]; + requestConfigStatus(): Promise>; } export class StandardPersistentConfigEditor implements PersistentConfigEditor { @@ -60,4 +69,31 @@ export class StandardPersistentConfigEditor implements PersistentConfigEditor { getConfigAdaptors(): PersistentConfigData[] { return this.configAdaptors; } + + public async requestConfigStatus(): Promise< + Result + > { + const info: PersistentConfigStatus[] = []; + for (const adaptor of this.configAdaptors) { + const dataResult = await adaptor.requestConfig(); + if (isError(dataResult)) { + if (dataResult.error instanceof ConfigParseError) { + info.push({ + description: adaptor.description, + data: dataResult.error.config, + error: dataResult.error, + }); + } else { + return dataResult; + } + } else { + info.push({ + description: adaptor.description, + data: dataResult.ok, + error: undefined, + }); + } + } + return Ok(info); + } } diff --git a/src/safemode/PersistentConfigRenderer.tsx b/src/safemode/PersistentConfigRenderer.tsx index b936215a..df8e6b9f 100644 --- a/src/safemode/PersistentConfigRenderer.tsx +++ b/src/safemode/PersistentConfigRenderer.tsx @@ -2,7 +2,6 @@ // // SPDX-License-Identifier: AFL-3.0 -import { Ok, Result, isError } from "@gnuxie/typescript-result"; import { DeadDocumentJSX, DocumentNode, @@ -12,23 +11,16 @@ import { MatrixRoomID, MatrixUserID, } from "@the-draupnir-project/matrix-basic-types"; -import { - ConfigParseError, - ConfigPropertyError, - PersistentConfigData, -} from "matrix-protection-suite"; +import { ConfigParseError, ConfigPropertyError } from "matrix-protection-suite"; import { renderMentionPill, renderRoomPill, } from "../commands/interface-manager/MatrixHelpRenderer"; +import { PersistentConfigStatus } from "./PersistentConfigEditor"; -// FIXME: This is backwards, we should generate some kind of object that can be rendered by the interface manager, -// that already has all the information fetched. export interface PersistentConfigRenderer { - renderConfig(config: PersistentConfigData): Promise>; - renderStatusOfConfigAdaptors( - adaptors: PersistentConfigData[] - ): Promise>; + renderConfigStatus(config: PersistentConfigStatus): DocumentNode; + renderAdaptorStatus(info: PersistentConfigStatus[]): DocumentNode; } function findError( @@ -95,14 +87,13 @@ function renderConfigPropertyItem( } function renderConfigPropertyError( - error: ConfigPropertyError | undefined + error: ConfigPropertyError | ConfigParseError | undefined ): string { return error === undefined ? "🟢" : "🔴"; } function renderConfigProperty( propertyKey: string, - config: PersistentConfigData, data: Record, errors: ConfigPropertyError[] ): DocumentNode { @@ -139,48 +130,35 @@ function renderBodgedConfig(value: unknown): DocumentNode { } export const StandardPersistentConfigRenderer = Object.freeze({ - async renderConfig( - config: PersistentConfigData - ): Promise> { - const dataResult = await config.requestConfig(); - if ( - isError(dataResult) && - !(dataResult.error instanceof ConfigParseError) - ) { - return dataResult; - } - const [data, errors] = dataResult.match( - (ok) => [ok, []], - (error) => { - if (error instanceof ConfigParseError) { - return [error.config, error.errors]; - } else { - throw new TypeError( - "We should have been able to narrow to ConfigParseError" - ); - } - } - ); - if (typeof data !== "object" || data === null) { - return Ok(renderBodgedConfig(data)); + renderConfigStatus(config: PersistentConfigStatus): DocumentNode { + if (typeof config.data !== "object" || config.data === null) { + return renderBodgedConfig(config.data); } - return Ok( - - {config.description.schema.title ?? "Untitled Config"} + return ( +
    + + {renderConfigPropertyError(config.error)}{" "} + {config.description.schema.title ?? "Untitled Config"} + {config.description .properties() .map((property) => renderConfigProperty( property.name, - config, - data as Record, - errors + config.data as Record, + config.error?.errors ?? [] ) )} - +
    + ); + }, + renderAdaptorStatus(info: PersistentConfigStatus[]): DocumentNode { + return ( +
      + {info.map((config) => ( +
    • {this.renderConfigStatus(config)}
    • + ))} +
    ); }, - async renderStatusOfConfigAdaptors( - adaptors: PersistentConfigData[] - ): Promise> {}, }) satisfies PersistentConfigRenderer; From b10ec0e3e4815feac937d683d07f4f05e5b50e9f Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 4 Oct 2024 15:27:50 +0100 Subject: [PATCH 3/8] Improve rendering of persistent configs in safe mode. We should really also expand by default, and array properties in details instead. Otherwise a new user might not expand by default and might not know what's wrong. --- src/safemode/DraupnirSafeMode.ts | 28 ++++++-- src/safemode/PersistentConfigEditor.ts | 60 +++++++++++++--- src/safemode/PersistentConfigRenderer.tsx | 84 +++++++++++++++++------ src/safemode/commands/StatusCommand.tsx | 22 +++++- 4 files changed, 156 insertions(+), 38 deletions(-) diff --git a/src/safemode/DraupnirSafeMode.ts b/src/safemode/DraupnirSafeMode.ts index 17bb4ae5..d5fba050 100644 --- a/src/safemode/DraupnirSafeMode.ts +++ b/src/safemode/DraupnirSafeMode.ts @@ -31,13 +31,14 @@ import { } from "../commands/interface-manager/MatrixPromptForAccept"; import { makeCommandDispatcherTimelineListener } from "./ManagementRoom"; import { SafeModeToggle } from "./SafeModeToggle"; -import { Result } from "@gnuxie/typescript-result"; +import { Result, isError } from "@gnuxie/typescript-result"; import { renderSafeModeStatusInfo, safeModeStatusInfo, } from "./commands/StatusCommand"; import { wrapInRoot } from "../commands/interface-manager/MatrixHelpRenderer"; import { sendAndAnnotateWithRecoveryOptions } from "./commands/RecoverCommand"; +import { StandardPersistentConfigEditor } from "./PersistentConfigEditor"; export class SafeModeDraupnir implements MatrixAdaptorContext { public reactionHandler: MatrixReactionHandler; @@ -118,11 +119,26 @@ export class SafeModeDraupnir implements MatrixAdaptorContext { public startupComplete(): void { void Task( - sendAndAnnotateWithRecoveryOptions( - this, - wrapInRoot(renderSafeModeStatusInfo(safeModeStatusInfo(this))), - {} - ) + (async () => { + const editor = new StandardPersistentConfigEditor(this.client); + const configStatus = await editor.supplementStatusWithSafeModeCause( + this.cause + ); + if (isError(configStatus)) { + return configStatus.elaborate( + "Failed to fetch draupnir's persistent configuration" + ); + } + return await sendAndAnnotateWithRecoveryOptions( + this, + wrapInRoot( + renderSafeModeStatusInfo( + safeModeStatusInfo(this.cause, configStatus.ok) + ) + ), + {} + ); + })() ); } diff --git a/src/safemode/PersistentConfigEditor.ts b/src/safemode/PersistentConfigEditor.ts index cf63d16f..d2502348 100644 --- a/src/safemode/PersistentConfigEditor.ts +++ b/src/safemode/PersistentConfigEditor.ts @@ -3,10 +3,10 @@ // SPDX-License-Identifier: AFL-3.0 import { Ok, Result, isError } from "@gnuxie/typescript-result"; -import { TObject } from "@sinclair/typebox"; import { ConfigDescription, ConfigParseError, + ConfigPropertyUseError, MJOLNIR_PROTECTED_ROOMS_EVENT_TYPE, MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE, MjolnirEnabledProtectionsDescription, @@ -20,16 +20,28 @@ import { BotSDKAccountDataConfigBackend, MatrixSendClient, } from "matrix-protection-suite-for-matrix-bot-sdk"; +import { SafeModeCause, SafeModeReason } from "./SafeModeCause"; export type PersistentConfigStatus = { - readonly description: ConfigDescription; - readonly data: unknown; - readonly error: ConfigParseError | undefined; + description: ConfigDescription; + data: unknown; + error: ConfigParseError | undefined; }; export interface PersistentConfigEditor { getConfigAdaptors(): PersistentConfigData[]; requestConfigStatus(): Promise>; + /** + * requestConfigStatus, but be sure to update the PeristentConfigStatus list + * with the ConfigPropertyUseError in the safe mode cause, if there is one. + * + * This is because `ConfigPropertyUseError`'s will not show up in parsing, + * only when creating Draupnir itself, and so they won't show up from just requesting + * the config alone. + */ + supplementStatusWithSafeModeCause( + cause: SafeModeCause + ): Promise>; } export class StandardPersistentConfigEditor implements PersistentConfigEditor { @@ -41,24 +53,25 @@ export class StandardPersistentConfigEditor implements PersistentConfigEditor { // only work with the general shape rather than the specific one, in the way that // the `remove` methods do, but I'm not convinced that works either, as those // methods accept a Record that at least has the keys from the specific shape - // of the config. + // of the config. OK that's not why, because I tried to remove the toMirror method. + // I don't understand why it won't work then... this.configAdaptors = [ new StandardPersistentConfigData( - MjolnirPolicyRoomsDescription as unknown as ConfigDescription, + MjolnirPolicyRoomsDescription as unknown as ConfigDescription, new BotSDKAccountDataConfigBackend( client, MJOLNIR_WATCHED_POLICY_ROOMS_EVENT_TYPE ) ), new StandardPersistentConfigData( - MjolnirProtectedRoomsDescription as unknown as ConfigDescription, + MjolnirProtectedRoomsDescription as unknown as ConfigDescription, new BotSDKAccountDataConfigBackend( client, MJOLNIR_PROTECTED_ROOMS_EVENT_TYPE ) ), new StandardPersistentConfigData( - MjolnirEnabledProtectionsDescription as unknown as ConfigDescription, + MjolnirEnabledProtectionsDescription as unknown as ConfigDescription, new BotSDKAccountDataConfigBackend( client, MjolnirEnabledProtectionsEventType @@ -96,4 +109,35 @@ export class StandardPersistentConfigEditor implements PersistentConfigEditor { } return Ok(info); } + public async supplementStatusWithSafeModeCause( + cause: SafeModeCause + ): Promise> { + const info = await this.requestConfigStatus(); + if (isError(info)) { + return info; + } + if (cause.reason === SafeModeReason.ByRequest) { + return Ok(info.ok); + } + if (!(cause.error instanceof ConfigPropertyUseError)) { + return Ok(info.ok); + } + const relevantStatus = info.ok.find( + (status) => + status.description === + (cause.error as ConfigPropertyUseError).configDescription + ); + if (relevantStatus === undefined) { + throw new TypeError( + "The cause of the safe mode error was not found in the configuration status." + ); + } + relevantStatus.error = new ConfigParseError( + "There was a problem when using a property in the configuration.", + relevantStatus.description as unknown as ConfigDescription, + [cause.error], + relevantStatus.data + ); + return Ok(info.ok); + } } diff --git a/src/safemode/PersistentConfigRenderer.tsx b/src/safemode/PersistentConfigRenderer.tsx index df8e6b9f..b8a4c5cc 100644 --- a/src/safemode/PersistentConfigRenderer.tsx +++ b/src/safemode/PersistentConfigRenderer.tsx @@ -11,7 +11,12 @@ import { MatrixRoomID, MatrixUserID, } from "@the-draupnir-project/matrix-basic-types"; -import { ConfigParseError, ConfigPropertyError } from "matrix-protection-suite"; +import { + ConfigDescription, + ConfigParseError, + ConfigPropertyError, + ConfigPropertyUseError, +} from "matrix-protection-suite"; import { renderMentionPill, renderRoomPill, @@ -80,8 +85,8 @@ function renderConfigPropertyItem( const error = findItemError(propertyKey, index, errors); return (
  • - {renderConfigPropertyError(error)} - {index}: {renderConfigPropertyValue(value)} + {renderConfigPropertyError(error)} {index}:{" "} + {renderConfigPropertyValue(value)}
  • ); } @@ -89,7 +94,19 @@ function renderConfigPropertyItem( function renderConfigPropertyError( error: ConfigPropertyError | ConfigParseError | undefined ): string { - return error === undefined ? "🟢" : "🔴"; + if (error === undefined) { + return "🟢"; + } else if (error instanceof ConfigPropertyUseError) { + return "🟠"; + } else if (error instanceof ConfigParseError) { + if (error.errors.every((e) => e instanceof ConfigPropertyUseError)) { + return "🟠"; + } else { + return "🔴"; + } + } else { + return "🔴"; + } } function renderConfigProperty( @@ -102,8 +119,7 @@ function renderConfigProperty( if (Array.isArray(propertyValue)) { return (
  • - {renderConfigPropertyError(error)} - {propertyKey}:{" "} + {renderConfigPropertyError(error)} {propertyKey}:{" "}
      {propertyValue.map((value, index) => renderConfigPropertyItem(propertyKey, index, value, errors) @@ -120,11 +136,36 @@ function renderConfigProperty( ); } -function renderBodgedConfig(value: unknown): DocumentNode { +function renderConfigDetails( + error: ConfigParseError | undefined, + description: ConfigDescription, + children: DocumentNode +): DocumentNode { return ( +
      + + {renderConfigPropertyError(error)}{" "} + {description.schema.title ?? "Untitled Config"} + {" "} + {children} +
      + ); +} + +function renderBodgedConfig(config: PersistentConfigStatus): DocumentNode { + if (config.data === undefined) { + return renderConfigDetails( + undefined, + config.description, + No data is currently persisted for this config. + ); + } + return renderConfigDetails( + config.error, + config.description, The config seems to be entirely invalid:{" "} - {renderConfigPropertyValue(value)} + {renderConfigPropertyValue(config.data)} ); } @@ -132,14 +173,12 @@ function renderBodgedConfig(value: unknown): DocumentNode { export const StandardPersistentConfigRenderer = Object.freeze({ renderConfigStatus(config: PersistentConfigStatus): DocumentNode { if (typeof config.data !== "object" || config.data === null) { - return renderBodgedConfig(config.data); + return renderBodgedConfig(config); } - return ( -
      - - {renderConfigPropertyError(config.error)}{" "} - {config.description.schema.title ?? "Untitled Config"} - + return renderConfigDetails( + config.error, + config.description, + {config.description .properties() .map((property) => @@ -149,16 +188,19 @@ export const StandardPersistentConfigRenderer = Object.freeze({ config.error?.errors ?? [] ) )} -
      + ); }, renderAdaptorStatus(info: PersistentConfigStatus[]): DocumentNode { return ( -
        - {info.map((config) => ( -
      • {this.renderConfigStatus(config)}
      • - ))} -
      + + Persistent configuration status: +
        + {info.map((config) => ( +
      • {this.renderConfigStatus(config)}
      • + ))} +
      +
      ); }, }) satisfies PersistentConfigRenderer; diff --git a/src/safemode/commands/StatusCommand.tsx b/src/safemode/commands/StatusCommand.tsx index f83e9834..8e48029a 100644 --- a/src/safemode/commands/StatusCommand.tsx +++ b/src/safemode/commands/StatusCommand.tsx @@ -19,6 +19,11 @@ import { import { SafeModeInterfaceAdaptor } from "./SafeModeAdaptor"; import { renderRecoveryOptions } from "../RecoveryOptions"; import { sendAndAnnotateWithRecoveryOptions } from "./RecoverCommand"; +import { + PersistentConfigStatus, + StandardPersistentConfigEditor, +} from "../PersistentConfigEditor"; +import { StandardPersistentConfigRenderer } from "../PersistentConfigRenderer"; export function safeModeHeader(): DocumentNode { return ( @@ -68,6 +73,7 @@ function renderSafeModeCause(safeModeCause: SafeModeCause): DocumentNode { export interface SafeModeStatusInfo { safeModeCause: SafeModeCause; + configStatus: PersistentConfigStatus[]; documentationURL: string; version: string; repository: string; @@ -88,6 +94,7 @@ export function renderSafeModeStatusInfo(
      {renderRecoveryOptions(info.safeModeCause)}
      + {StandardPersistentConfigRenderer.renderAdaptorStatus(info.configStatus)} Version: {info.version}
      @@ -110,10 +117,12 @@ export function renderSafeModeStatusInfo( } export function safeModeStatusInfo( - safeModeDraupnir: SafeModeDraupnir + cause: SafeModeCause, + configStatus: PersistentConfigStatus[] ): SafeModeStatusInfo { return { - safeModeCause: safeModeDraupnir.cause, + safeModeCause: cause, + configStatus, documentationURL: DOCUMENTATION_URL, version: SOFTWARE_VERSION, repository: PACKAGE_JSON["repository"] ?? "Unknown", @@ -127,7 +136,14 @@ export const SafeModeStatusCommand = describeCommand({ async executor( safeModeDraupnir: SafeModeDraupnir ): Promise> { - return Ok(safeModeStatusInfo(safeModeDraupnir)); + const editor = new StandardPersistentConfigEditor(safeModeDraupnir.client); + const configStatus = await editor.supplementStatusWithSafeModeCause( + safeModeDraupnir.cause + ); + if (isError(configStatus)) { + return configStatus; + } + return Ok(safeModeStatusInfo(safeModeDraupnir.cause, configStatus.ok)); }, }); From 3bce8cfce48aef41e68edd583cc84ee3d54c5074 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 4 Oct 2024 16:04:02 +0100 Subject: [PATCH 4/8] Remove
      from configs themselves. --- src/safemode/PersistentConfigRenderer.tsx | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/safemode/PersistentConfigRenderer.tsx b/src/safemode/PersistentConfigRenderer.tsx index b8a4c5cc..c41b4dd6 100644 --- a/src/safemode/PersistentConfigRenderer.tsx +++ b/src/safemode/PersistentConfigRenderer.tsx @@ -119,12 +119,16 @@ function renderConfigProperty( if (Array.isArray(propertyValue)) { return (
    • - {renderConfigPropertyError(error)} {propertyKey}:{" "} -
        - {propertyValue.map((value, index) => - renderConfigPropertyItem(propertyKey, index, value, errors) - )} -
      +
      + + {renderConfigPropertyError(error)} {propertyKey}: + +
        + {propertyValue.map((value, index) => + renderConfigPropertyItem(propertyKey, index, value, errors) + )} +
      +
    • ); } @@ -142,13 +146,13 @@ function renderConfigDetails( children: DocumentNode ): DocumentNode { return ( -
      + {renderConfigPropertyError(error)}{" "} {description.schema.title ?? "Untitled Config"} {" "} {children} -
      + ); } From 2bf9d65961f7b9a9fb047e8c7ac4a3a810c38d4e Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 4 Oct 2024 16:25:26 +0100 Subject: [PATCH 5/8] Color blind indicators for config status. --- src/safemode/PersistentConfigRenderer.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/safemode/PersistentConfigRenderer.tsx b/src/safemode/PersistentConfigRenderer.tsx index c41b4dd6..6671126a 100644 --- a/src/safemode/PersistentConfigRenderer.tsx +++ b/src/safemode/PersistentConfigRenderer.tsx @@ -23,6 +23,12 @@ import { } from "../commands/interface-manager/MatrixHelpRenderer"; import { PersistentConfigStatus } from "./PersistentConfigEditor"; +const ConfigStatusIndicator = Object.freeze({ + Ok: "✔", + UseError: "⚠", + ParseError: "❌", +}); + export interface PersistentConfigRenderer { renderConfigStatus(config: PersistentConfigStatus): DocumentNode; renderAdaptorStatus(info: PersistentConfigStatus[]): DocumentNode; @@ -95,17 +101,17 @@ function renderConfigPropertyError( error: ConfigPropertyError | ConfigParseError | undefined ): string { if (error === undefined) { - return "🟢"; + return ConfigStatusIndicator.Ok; } else if (error instanceof ConfigPropertyUseError) { - return "🟠"; + return ConfigStatusIndicator.UseError; } else if (error instanceof ConfigParseError) { if (error.errors.every((e) => e instanceof ConfigPropertyUseError)) { - return "🟠"; + return ConfigStatusIndicator.UseError; } else { - return "🔴"; + return ConfigStatusIndicator.ParseError; } } else { - return "🔴"; + return ConfigStatusIndicator.ParseError; } } From 14e9b39e6a17f3c55979565139d97f8c99018b89 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 4 Oct 2024 16:34:18 +0100 Subject: [PATCH 6/8] When config are bodged just render their reasons on the same line. --- src/safemode/PersistentConfigRenderer.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/safemode/PersistentConfigRenderer.tsx b/src/safemode/PersistentConfigRenderer.tsx index 6671126a..1cf987ed 100644 --- a/src/safemode/PersistentConfigRenderer.tsx +++ b/src/safemode/PersistentConfigRenderer.tsx @@ -153,11 +153,8 @@ function renderConfigDetails( ): DocumentNode { return ( - - {renderConfigPropertyError(error)}{" "} - {description.schema.title ?? "Untitled Config"} - {" "} - {children} + {renderConfigPropertyError(error)}{" "} + {description.schema.title ?? "Untitled Config"} {children} ); } From a104318b1b0bb55ca1af1d11220d77216022f5d9 Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 4 Oct 2024 16:49:31 +0100 Subject: [PATCH 7/8] Hide stack trace behind details. --- src/safemode/commands/StatusCommand.tsx | 28 ++++++++++++------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/safemode/commands/StatusCommand.tsx b/src/safemode/commands/StatusCommand.tsx index 8e48029a..e7092c66 100644 --- a/src/safemode/commands/StatusCommand.tsx +++ b/src/safemode/commands/StatusCommand.tsx @@ -39,22 +39,23 @@ function renderSafeModeCauseError(error: ResultError): DocumentNode { return ( Draupnir is in safe mode because Draupnir failed to start. -
      - {error.mostRelevantElaboration} -
      - Details can be found by providing the reference{" "} - {error.uuid} - to an administrator. -
      {error.toReadableString()}
      +
      + {error.mostRelevantElaboration} + Details can be found by providing the reference{" "} + {error.uuid} + to an administrator. +
      {error.toReadableString()}
      +
      ); } else { return ( Draupnir is in safe mode because Draupnir failed to start. -
      - {error.mostRelevantElaboration} -
      {error.toReadableString()}
      +
      + {error.mostRelevantElaboration} +
      {error.toReadableString()}
      +
      ); } @@ -86,11 +87,8 @@ export function renderSafeModeStatusInfo( return ( ⚠️ Draupnir is in safe mode ⚠️ - -
      - {renderSafeModeCause(info.safeModeCause)} -
      -
      +
      + {renderSafeModeCause(info.safeModeCause)}
      {renderRecoveryOptions(info.safeModeCause)}
      From b8fc12ec6640cd2d39cf838fbcf0c7a7cd1363cd Mon Sep 17 00:00:00 2001 From: gnuxie Date: Fri, 4 Oct 2024 17:25:09 +0100 Subject: [PATCH 8/8] Update for MPS 1.6.0. --- package.json | 4 ++-- yarn.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index d2cac77f..c84ce72c 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,8 @@ "js-yaml": "^4.1.0", "jsdom": "^24.0.0", "matrix-appservice-bridge": "^9.0.1", - "matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@1.5.2", - "matrix-protection-suite-for-matrix-bot-sdk": "npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@1.5.2", + "matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@1.6.0", + "matrix-protection-suite-for-matrix-bot-sdk": "npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@1.6.0", "parse-duration": "^1.0.2", "pg": "^8.8.0", "shell-quote": "^1.7.3", diff --git a/yarn.lock b/yarn.lock index d4a32bc9..37d24b73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2526,17 +2526,17 @@ matrix-appservice@^2.0.0: request-promise "^4.2.6" sanitize-html "^2.8.0" -"matrix-protection-suite-for-matrix-bot-sdk@npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite-for-matrix-bot-sdk/-/matrix-protection-suite-for-matrix-bot-sdk-1.5.2.tgz#e238d076df42bc9e509d5903630f2a04faa22db9" - integrity sha512-KqF8om8CdgepGkfVpAwCCLMAW16BwqOMH1Y7GebOToVx78sAq8sxXVgtDuXtpbZ//m42aa8EHsZG4gaqycw0xA== +"matrix-protection-suite-for-matrix-bot-sdk@npm:@gnuxie/matrix-protection-suite-for-matrix-bot-sdk@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite-for-matrix-bot-sdk/-/matrix-protection-suite-for-matrix-bot-sdk-1.6.0.tgz#ccd3f0cc1e8582e96a745d025c3031509ff1e45c" + integrity sha512-4rBarhvsaiSMPL0390Dkv78/TgQsV6/Ay+7InvF3b0wS9GFXZem7PTvvFs9TjDG0r1iovYSzpvSkoD/y3fBPOg== dependencies: "@gnuxie/typescript-result" "^1.0.0" -"matrix-protection-suite@npm:@gnuxie/matrix-protection-suite@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite/-/matrix-protection-suite-1.5.2.tgz#6dc3d85d6f2b9b36e08d289c80bd440f3ced79dd" - integrity sha512-N9LedoF6E6ComaKFoSzdn/G88XJ1fbIkZh4V7v9DEyKACo3GxlNA7yppc6e1ehFJ+QVEbW653edmwYF92qdjQQ== +"matrix-protection-suite@npm:@gnuxie/matrix-protection-suite@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite/-/matrix-protection-suite-1.6.0.tgz#f0d567946dcb4b6bdd274a291985eda103625f8b" + integrity sha512-ywxLum0c5JzxNAWw5yfgDho7jE29RNNDydzSZztbji24LCsh6nuUvJBbW4yeCLtA6qVwDwfVszP4zs66vNurJg== dependencies: "@gnuxie/typescript-result" "^1.0.0" await-lock "^2.2.2"