Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/src/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@
"log": "Logs",
"log_empty_message_title": "No logs",
"log_empty_message_text": "No logs to display.",
"inspect": "Inspect",
"run_name": "Name",
"workflow_name": "Workflow",
"configuration": "Configuration",
Expand Down Expand Up @@ -573,6 +574,7 @@
"fleet_placeholder": "Filtering by fleet",
"fleet_name": "Fleet name",
"total_instances": "Number of instances",
"inspect": "Inspect",
"empty_message_title": "No fleets",
"empty_message_text": "No fleets to display.",
"nomatch_message_title": "No matches",
Expand Down
113 changes: 113 additions & 0 deletions frontend/src/pages/Fleets/Details/Inspect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import ace from 'ace-builds';
import CodeEditor, { CodeEditorProps } from '@cloudscape-design/components/code-editor';
import { Mode } from '@cloudscape-design/global-styles';

import { Container, Header, Loader } from 'components';
import { CODE_EDITOR_I18N_STRINGS } from 'components/form/CodeEditor/constants';

import { useAppSelector } from 'hooks';
import { useGetFleetDetailsQuery } from 'services/fleet';

import { selectSystemMode } from 'App/slice';

import 'ace-builds/src-noconflict/theme-cloud_editor';
import 'ace-builds/src-noconflict/theme-cloud_editor_dark';
import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/ext-language_tools';

ace.config.set('useWorker', false);

interface AceEditorElement extends HTMLElement {
env?: {
editor?: {
setReadOnly: (readOnly: boolean) => void;
};
};
}

export const FleetInspect = () => {
const { t } = useTranslation();
const params = useParams();
const paramProjectName = params.projectName ?? '';
const paramFleetId = params.fleetId ?? '';

const systemMode = useAppSelector(selectSystemMode) ?? '';

const { data: fleetData, isLoading } = useGetFleetDetailsQuery(
{
projectName: paramProjectName,
fleetId: paramFleetId,
},
{
refetchOnMountOrArgChange: true,
},
);

const [codeEditorPreferences, setCodeEditorPreferences] = useState<CodeEditorProps['preferences']>(() => ({
theme: systemMode === Mode.Dark ? 'cloud_editor_dark' : 'cloud_editor',
}));

useEffect(() => {
if (systemMode === Mode.Dark)
setCodeEditorPreferences({
theme: 'cloud_editor_dark',
});
else
setCodeEditorPreferences({
theme: 'cloud_editor',
});
}, [systemMode]);

const onCodeEditorPreferencesChange: CodeEditorProps['onPreferencesChange'] = (e) => {
setCodeEditorPreferences(e.detail);
};

const jsonContent = useMemo(() => {
if (!fleetData) return '';
return JSON.stringify(fleetData, null, 2);
}, [fleetData]);

// Set editor to read-only after it loads
useEffect(() => {
const timer = setTimeout(() => {
// Find the ace editor instance in the DOM
const editorElements = document.querySelectorAll('.ace_editor');
editorElements.forEach((element: Element) => {
const aceEditor = (element as AceEditorElement).env?.editor;
if (aceEditor) {
aceEditor.setReadOnly(true);
}
});
}, 100);

return () => clearTimeout(timer);
}, [jsonContent]);

if (isLoading)
return (
<Container>
<Loader />
</Container>
);

return (
<Container header={<Header variant="h2">{t('fleets.inspect')}</Header>}>
<CodeEditor
value={jsonContent}
language="json"
i18nStrings={CODE_EDITOR_I18N_STRINGS}
ace={ace}
themes={{ light: [], dark: [] }}
preferences={codeEditorPreferences}
onPreferencesChange={onCodeEditorPreferencesChange}
editorContentHeight={600}
onChange={() => {
// Prevent editing - onChange is required but we ignore changes
}}
/>
</Container>
);
};
6 changes: 6 additions & 0 deletions frontend/src/pages/Fleets/Details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Button, ContentLayout, DetailsHeader, Tabs } from 'components';
enum CodeTab {
Details = 'details',
Events = 'events',
Inspect = 'inspect',
}

import { useBreadcrumbs } from 'hooks';
Expand Down Expand Up @@ -96,6 +97,11 @@ export const FleetDetails: React.FC = () => {
id: CodeTab.Events,
href: ROUTES.FLEETS.DETAILS.EVENTS.FORMAT(paramProjectName, paramFleetId),
},
{
label: 'Inspect',
id: CodeTab.Inspect,
href: ROUTES.FLEETS.DETAILS.INSPECT.FORMAT(paramProjectName, paramFleetId),
},
]}
/>

Expand Down
108 changes: 108 additions & 0 deletions frontend/src/pages/Runs/Details/Inspect/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import ace from 'ace-builds';
import CodeEditor, { CodeEditorProps } from '@cloudscape-design/components/code-editor';
import { Mode } from '@cloudscape-design/global-styles';

import { Container, Header, Loader } from 'components';
import { CODE_EDITOR_I18N_STRINGS } from 'components/form/CodeEditor/constants';

import { useAppSelector } from 'hooks';
import { useGetRunQuery } from 'services/run';

import { selectSystemMode } from 'App/slice';

import 'ace-builds/src-noconflict/theme-cloud_editor';
import 'ace-builds/src-noconflict/theme-cloud_editor_dark';
import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/ext-language_tools';

ace.config.set('useWorker', false);

interface AceEditorElement extends HTMLElement {
env?: {
editor?: {
setReadOnly: (readOnly: boolean) => void;
};
};
}

export const RunInspect = () => {
const { t } = useTranslation();
const params = useParams();
const paramProjectName = params.projectName ?? '';
const paramRunId = params.runId ?? '';

const systemMode = useAppSelector(selectSystemMode) ?? '';

const { data: runData, isLoading } = useGetRunQuery({
project_name: paramProjectName,
id: paramRunId,
});

const [codeEditorPreferences, setCodeEditorPreferences] = useState<CodeEditorProps['preferences']>(() => ({
theme: systemMode === Mode.Dark ? 'cloud_editor_dark' : 'cloud_editor',
}));

useEffect(() => {
if (systemMode === Mode.Dark)
setCodeEditorPreferences({
theme: 'cloud_editor_dark',
});
else
setCodeEditorPreferences({
theme: 'cloud_editor',
});
}, [systemMode]);

const onCodeEditorPreferencesChange: CodeEditorProps['onPreferencesChange'] = (e) => {
setCodeEditorPreferences(e.detail);
};

const jsonContent = useMemo(() => {
if (!runData) return '';
return JSON.stringify(runData, null, 2);
}, [runData]);

// Set editor to read-only after it loads
useEffect(() => {
const timer = setTimeout(() => {
// Find the ace editor instance in the DOM
const editorElements = document.querySelectorAll('.ace_editor');
editorElements.forEach((element: Element) => {
const aceEditor = (element as AceEditorElement).env?.editor;
if (aceEditor) {
aceEditor.setReadOnly(true);
}
});
}, 100);

return () => clearTimeout(timer);
}, [jsonContent]);

if (isLoading)
return (
<Container>
<Loader />
</Container>
);

return (
<Container header={<Header variant="h2">{t('projects.run.inspect')}</Header>}>
<CodeEditor
value={jsonContent}
language="json"
i18nStrings={CODE_EDITOR_I18N_STRINGS}
ace={ace}
themes={{ light: [], dark: [] }}
preferences={codeEditorPreferences}
onPreferencesChange={onCodeEditorPreferencesChange}
editorContentHeight={600}
onChange={() => {
// Prevent editing - onChange is required but we ignore changes
}}
/>
</Container>
);
};
1 change: 1 addition & 0 deletions frontend/src/pages/Runs/Details/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export enum CodeTab {
Metrics = 'metrics',
Logs = 'logs',
Events = 'events',
Inspect = 'inspect',
}
5 changes: 5 additions & 0 deletions frontend/src/pages/Runs/Details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ export const RunDetailsPage: React.FC = () => {
id: CodeTab.Events,
href: ROUTES.PROJECT.DETAILS.RUNS.DETAILS.EVENTS.FORMAT(paramProjectName, paramRunId),
},
{
label: 'Inspect',
id: CodeTab.Inspect,
href: ROUTES.PROJECT.DETAILS.RUNS.DETAILS.INSPECT.FORMAT(paramProjectName, paramRunId),
},
]}
/>
)}
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Logout } from 'App/Logout';
import { FleetDetails, FleetList } from 'pages/Fleets';
import { EventsList as FleetEventsList } from 'pages/Fleets/Details/Events';
import { FleetDetails as FleetDetailsGeneral } from 'pages/Fleets/Details/FleetDetails';
import { FleetInspect } from 'pages/Fleets/Details/Inspect';
import { InstanceList } from 'pages/Instances';
import { ModelsList } from 'pages/Models';
import { ModelDetails } from 'pages/Models/Details';
Expand All @@ -28,6 +29,7 @@ import {
RunDetailsPage,
RunList,
} from 'pages/Runs';
import { RunInspect } from 'pages/Runs/Details/Inspect';
import { JobDetailsPage } from 'pages/Runs/Details/Jobs/Details';
import { EventsList as JobEvents } from 'pages/Runs/Details/Jobs/Events';
import { CreditsHistoryAdd, UserAdd, UserDetails, UserEdit, UserList } from 'pages/User';
Expand Down Expand Up @@ -122,6 +124,10 @@ export const router = createBrowserRouter([
path: ROUTES.PROJECT.DETAILS.RUNS.DETAILS.EVENTS.TEMPLATE,
element: <RunEvents />,
},
{
path: ROUTES.PROJECT.DETAILS.RUNS.DETAILS.INSPECT.TEMPLATE,
element: <RunInspect />,
},
],
},
{
Expand Down Expand Up @@ -208,6 +214,10 @@ export const router = createBrowserRouter([
path: ROUTES.FLEETS.DETAILS.EVENTS.TEMPLATE,
element: <FleetEventsList />,
},
{
path: ROUTES.FLEETS.DETAILS.INSPECT.TEMPLATE,
element: <FleetInspect />,
},
],
},

Expand Down
10 changes: 10 additions & 0 deletions frontend/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export const ROUTES = {
FORMAT: (projectName: string, runId: string) =>
buildRoute(ROUTES.PROJECT.DETAILS.RUNS.DETAILS.LOGS.TEMPLATE, { projectName, runId }),
},
INSPECT: {
TEMPLATE: `/projects/:projectName/runs/:runId/inspect`,
FORMAT: (projectName: string, runId: string) =>
buildRoute(ROUTES.PROJECT.DETAILS.RUNS.DETAILS.INSPECT.TEMPLATE, { projectName, runId }),
},
JOBS: {
DETAILS: {
TEMPLATE: `/projects/:projectName/runs/:runId/jobs/:jobName`,
Expand Down Expand Up @@ -141,6 +146,11 @@ export const ROUTES = {
FORMAT: (projectName: string, fleetId: string) =>
buildRoute(ROUTES.FLEETS.DETAILS.EVENTS.TEMPLATE, { projectName, fleetId }),
},
INSPECT: {
TEMPLATE: `/projects/:projectName/fleets/:fleetId/inspect`,
FORMAT: (projectName: string, fleetId: string) =>
buildRoute(ROUTES.FLEETS.DETAILS.INSPECT.TEMPLATE, { projectName, fleetId }),
},
},
},

Expand Down
Loading