mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-18 01:50:50 +00:00
feat: extension error and console logging
This commit is contained in:
parent
9b6f034803
commit
4a243e60a7
8 changed files with 117 additions and 22 deletions
|
@ -20,7 +20,7 @@ export class Extension extends EventTarget {
|
|||
|
||||
private workerStatus = ExtensionStatus.GettingReady;
|
||||
|
||||
private workerError?: ErrorEvent;
|
||||
private workerError?: ErrorEvent | Error;
|
||||
|
||||
constructor(
|
||||
public readonly info: ExtensionEntry,
|
||||
|
@ -34,7 +34,7 @@ export class Extension extends EventTarget {
|
|||
{ type: 'module' },
|
||||
);
|
||||
this.worker.onerror = (ev) => {
|
||||
this.workerError = ev;
|
||||
this.status = ExtensionStatus.Error;
|
||||
this.dispatchEvent(new CustomEvent('error', { detail: ev }));
|
||||
};
|
||||
this.worker.onmessage = (ev: MessageEvent<ExtensionHostMessage>) =>
|
||||
|
@ -57,14 +57,27 @@ export class Extension extends EventTarget {
|
|||
const msg = ev.data;
|
||||
switch (msg.kind) {
|
||||
case 'status-change':
|
||||
this.workerStatus = msg.status;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('statusChanged', { detail: msg.status }),
|
||||
);
|
||||
this.status = msg.status;
|
||||
break;
|
||||
case 'error':
|
||||
if (msg.error instanceof Error) {
|
||||
this.workerError = msg.error;
|
||||
} else {
|
||||
this.workerError = new Error(msg.error.toString());
|
||||
}
|
||||
this.status = ExtensionStatus.Error;
|
||||
break;
|
||||
case 'log':
|
||||
this.dispatchEvent(new CustomEvent('log', { detail: msg }));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private set status(newValue: ExtensionStatus) {
|
||||
this.workerStatus = newValue;
|
||||
this.dispatchEvent(new CustomEvent('statusChanged', { detail: newValue }));
|
||||
}
|
||||
|
||||
public get status() {
|
||||
return this.workerStatus;
|
||||
}
|
||||
|
@ -100,14 +113,11 @@ export class Extension extends EventTarget {
|
|||
}
|
||||
|
||||
stop() {
|
||||
if (this.workerStatus === ExtensionStatus.Terminated) {
|
||||
if (this.status === ExtensionStatus.Terminated) {
|
||||
return;
|
||||
}
|
||||
this.worker.terminate();
|
||||
this.workerStatus = ExtensionStatus.Terminated;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('statusChanged', { detail: this.workerStatus }),
|
||||
);
|
||||
this.status = ExtensionStatus.Terminated;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
|
|
@ -22,7 +22,10 @@ export enum ExtensionStatus {
|
|||
}
|
||||
|
||||
export type ExtensionHostCommand = EHParamMessage | EHStartMessage;
|
||||
export type ExtensionHostMessage = EHStatusChangeMessage;
|
||||
export type ExtensionHostMessage =
|
||||
| EHStatusChangeMessage
|
||||
| EHErrorMessage
|
||||
| EHLogMessage;
|
||||
interface EHParamMessage {
|
||||
kind: 'arguments';
|
||||
options: ExtensionRunOptions;
|
||||
|
@ -36,3 +39,17 @@ interface EHStatusChangeMessage {
|
|||
kind: 'status-change';
|
||||
status: ExtensionStatus;
|
||||
}
|
||||
interface EHErrorMessage {
|
||||
kind: 'error';
|
||||
error: unknown;
|
||||
}
|
||||
interface EHLogMessage {
|
||||
kind: 'log';
|
||||
level: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface LogMessage {
|
||||
level: string;
|
||||
message: string;
|
||||
}
|
||||
|
|
|
@ -26,12 +26,31 @@ function setStatus(status: ExtensionStatus) {
|
|||
});
|
||||
}
|
||||
|
||||
function log(level: string) {
|
||||
// eslint-disable-next-line func-names
|
||||
return function (...args: { toString(): string }[]) {
|
||||
const message = args.join(' ');
|
||||
sendMessage({
|
||||
kind: 'log',
|
||||
level,
|
||||
message,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function start() {
|
||||
if (!extFn || !kv || extensionStatus !== ExtensionStatus.Ready)
|
||||
throw new Error('extension not ready');
|
||||
void extFn(kv).then(() => {
|
||||
setStatus(ExtensionStatus.Finished);
|
||||
});
|
||||
void extFn(kv)
|
||||
.then(() => {
|
||||
setStatus(ExtensionStatus.Finished);
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
sendMessage({
|
||||
kind: 'error',
|
||||
error,
|
||||
});
|
||||
});
|
||||
setStatus(ExtensionStatus.Running);
|
||||
}
|
||||
|
||||
|
@ -51,6 +70,12 @@ onmessage = async (ev: MessageEvent<ExtensionHostCommand>) => {
|
|||
compilerOptions: { module: ts.ModuleKind.CommonJS },
|
||||
});
|
||||
|
||||
// Replace console.* methods with something that logs to UI
|
||||
console.log = log('info');
|
||||
console.info = log('info');
|
||||
console.warn = log('warn');
|
||||
console.error = log('error');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
extFn = ExtensionFunction.constructor('kv', out.outputText);
|
||||
setStatus(ExtensionStatus.Ready);
|
||||
|
|
|
@ -331,7 +331,8 @@
|
|||
"main-loop-finished": "Active",
|
||||
"error": "Error encountered",
|
||||
"terminated": "Stopped"
|
||||
}
|
||||
},
|
||||
"error-alert": "Error details for {{name}}"
|
||||
}
|
||||
},
|
||||
"form-actions": {
|
||||
|
|
|
@ -6,9 +6,11 @@ import {
|
|||
ExtensionOptions,
|
||||
ExtensionRunOptions,
|
||||
ExtensionStatus,
|
||||
LogMessage,
|
||||
} from '~/lib/extensions/types';
|
||||
import { RootState } from '..';
|
||||
import { HTTPConfig } from '../api/types';
|
||||
import loggingReducer from '../logging/reducer';
|
||||
|
||||
interface ExtensionsState {
|
||||
ready: boolean;
|
||||
|
@ -127,6 +129,17 @@ export const createExtensionInstance = createAsyncThunk(
|
|||
payload.dependencies,
|
||||
payload.runOptions,
|
||||
);
|
||||
ext.addEventListener('log', (ev: CustomEvent<LogMessage>) => {
|
||||
dispatch(
|
||||
loggingReducer.actions.uiLogEvent({
|
||||
time: new Date(),
|
||||
caller: `extensionHost/${payload.entry.name}`,
|
||||
level: ev.detail.level,
|
||||
message: ev.detail.message,
|
||||
data: { extension: payload.entry.name },
|
||||
}),
|
||||
);
|
||||
});
|
||||
ext.addEventListener(
|
||||
'statusChanged',
|
||||
(ev: CustomEvent<ExtensionStatus>) => {
|
||||
|
|
|
@ -41,10 +41,13 @@ const loggingReducer = createSlice({
|
|||
loadedLogData(state, { payload }: PayloadAction<main.LogEntry[]>) {
|
||||
state.messages = payload
|
||||
.map(processEntry)
|
||||
.sort((a, b) => b.time.getTime() - a.time.getTime());
|
||||
.sort((a, b) => a.time.getTime() - b.time.getTime());
|
||||
},
|
||||
receivedEvent(state, { payload }: PayloadAction<main.LogEntry>) {
|
||||
state.messages = [processEntry(payload), ...state.messages];
|
||||
state.messages.push(processEntry(payload));
|
||||
},
|
||||
uiLogEvent(state, { payload }: PayloadAction<ProcessedLogEntry>) {
|
||||
state.messages.push(payload);
|
||||
},
|
||||
clearedEvents(state) {
|
||||
state.messages = [];
|
||||
|
|
|
@ -366,7 +366,7 @@ function LogDialog({ initialFilter }: LogDialogProps) {
|
|||
viewport={{ maxHeight: 'calc(80vh - 100px)' }}
|
||||
>
|
||||
<LogEntriesContainer>
|
||||
{filtered.map((entry) => (
|
||||
{filtered.reverse().map((entry) => (
|
||||
<LogItem
|
||||
key={entry.caller + entry.time.getTime().toString()}
|
||||
data={entry}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Editor, { Monaco, useMonaco } from '@monaco-editor/react';
|
||||
import {
|
||||
ExclamationTriangleIcon,
|
||||
InfoCircledIcon,
|
||||
InputIcon,
|
||||
PilcrowIcon,
|
||||
|
@ -134,6 +135,7 @@ type ExtensionListItemProps = {
|
|||
enabled: boolean;
|
||||
entry: ExtensionEntry;
|
||||
status: ExtensionStatus;
|
||||
error?: Error | ErrorEvent;
|
||||
onEdit: () => void;
|
||||
onRemove: () => void;
|
||||
onToggleEnable: () => void;
|
||||
|
@ -174,9 +176,32 @@ function ExtensionListItem(props: ExtensionListItemProps) {
|
|||
props.entry.name
|
||||
)}
|
||||
{props.enabled ? (
|
||||
<ExtensionStatusNote color={colorByStatus(props.status)}>
|
||||
{t(`pages.extensions.statuses.${props.status}`)}
|
||||
</ExtensionStatusNote>
|
||||
<>
|
||||
<ExtensionStatusNote color={colorByStatus(props.status)}>
|
||||
{t(`pages.extensions.statuses.${props.status}`)}
|
||||
</ExtensionStatusNote>
|
||||
{props.error ? (
|
||||
<Alert>
|
||||
<AlertTrigger asChild>
|
||||
<Button variation="danger" size="small">
|
||||
<ExclamationTriangleIcon />
|
||||
</Button>
|
||||
</AlertTrigger>
|
||||
<AlertContent
|
||||
variation="danger"
|
||||
title={t('pages.extensions.error-alert', {
|
||||
name: props.entry.name,
|
||||
})}
|
||||
actionButtonProps={{
|
||||
variation: 'danger',
|
||||
}}
|
||||
showCancel={false}
|
||||
>
|
||||
<code>{props.error.toString()}</code>
|
||||
</AlertContent>
|
||||
</Alert>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</ExtensionName>
|
||||
<ExtensionActions>
|
||||
|
@ -274,6 +299,7 @@ function ExtensionList({ onNew, onEdit }: ExtensionListProps) {
|
|||
entry={e}
|
||||
enabled={e.options.enabled}
|
||||
status={extensions.status[e.name]}
|
||||
error={extensions.running[e.name]?.error}
|
||||
onEdit={() => onEdit(e.name)}
|
||||
onRemove={() => {
|
||||
// Toggle enabled status
|
||||
|
|
Loading…
Reference in a new issue