feat: add scheduled tasks UI to devtools plugin
Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> remove circular dependency Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> remove another unused backend defaults dependency Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> revert package.json Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> revert the other package.json Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> modify yarn lock file Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> fix api report for type Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> fix bulid api reports Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> address feedback and fixes Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> address feedback and fixes Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> add changeset Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> rebase yarn.lock Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> fix import for task response type Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> fix lint Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> fix debounce import Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> add lodash Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> remove debounce logic Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> remove unused auth Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> readd back changeset Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> remove example app from changeset Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> Update plugins/devtools/src/components/Content/ScheduledTasksContent/ScheduledTasksContent.tsx Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> Update .changeset/short-lizards-find.md Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> address feedback Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> address feedback Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> address feedback Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> address feedback Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> address feedback Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> address feedback Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com> address feedback Signed-off-by: williamwu-mongodb <william.t.wu@mongodb.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/backend-defaults': minor
|
||||
'@backstage/plugin-devtools-backend': minor
|
||||
'@backstage/plugin-devtools-common': minor
|
||||
'@backstage/plugin-devtools': minor
|
||||
---
|
||||
|
||||
Added scheduled tasks UI feature for the DevTools plugin
|
||||
@@ -311,3 +311,8 @@ auth:
|
||||
|
||||
permission:
|
||||
enabled: true
|
||||
|
||||
devTools:
|
||||
scheduledTasks:
|
||||
plugins:
|
||||
- catalog
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
ConfigContent,
|
||||
ExternalDependenciesContent,
|
||||
InfoContent,
|
||||
ScheduledTasksContent,
|
||||
} from '@backstage/plugin-devtools';
|
||||
import { DevToolsLayout } from '@backstage/plugin-devtools';
|
||||
import { UnprocessedEntitiesContent } from '@backstage/plugin-catalog-unprocessed-entities';
|
||||
@@ -31,6 +32,9 @@ const DevToolsPage = () => {
|
||||
<DevToolsLayout.Route path="config" title="Config">
|
||||
<ConfigContent />
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route path="scheduled-tasks" title="Scheduled Tasks">
|
||||
<ScheduledTasksContent />
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route
|
||||
path="external-dependencies"
|
||||
title="External Dependencies"
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
```ts
|
||||
import { BasicPermission } from '@backstage/plugin-permission-common';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -44,6 +45,12 @@ export const devToolsInfoReadPermission: BasicPermission;
|
||||
// @public
|
||||
export const devToolsPermissions: BasicPermission[];
|
||||
|
||||
// @public (undocumented)
|
||||
export const devToolsTaskSchedulerCreatePermission: BasicPermission;
|
||||
|
||||
// @public (undocumented)
|
||||
export const devToolsTaskSchedulerReadPermission: BasicPermission;
|
||||
|
||||
// @public (undocumented)
|
||||
export type Endpoint = {
|
||||
name: string;
|
||||
@@ -83,4 +90,57 @@ export type PackageDependency = {
|
||||
name: string;
|
||||
versions: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ScheduledTasks = {
|
||||
scheduledTasks?: TaskApiTasksResponse[];
|
||||
error?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface TaskApiTasksResponse {
|
||||
// (undocumented)
|
||||
pluginId: string;
|
||||
// (undocumented)
|
||||
scope: 'global' | 'local';
|
||||
// (undocumented)
|
||||
settings: {
|
||||
version: number;
|
||||
} & JsonObject;
|
||||
// (undocumented)
|
||||
taskId: string;
|
||||
// (undocumented)
|
||||
taskState:
|
||||
| {
|
||||
status: 'running';
|
||||
startedAt: string;
|
||||
timesOutAt?: string;
|
||||
lastRunError?: string;
|
||||
lastRunEndedAt?: string;
|
||||
}
|
||||
| {
|
||||
status: 'idle';
|
||||
startsAt?: string;
|
||||
lastRunError?: string;
|
||||
lastRunEndedAt?: string;
|
||||
}
|
||||
| null;
|
||||
// (undocumented)
|
||||
workerState:
|
||||
| {
|
||||
status: 'initial-wait';
|
||||
}
|
||||
| {
|
||||
status: 'idle';
|
||||
}
|
||||
| {
|
||||
status: 'running';
|
||||
}
|
||||
| null;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type TriggerScheduledTask = {
|
||||
error?: string;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -48,6 +48,22 @@ export const devToolsExternalDependenciesReadPermission = createPermission({
|
||||
attributes: { action: 'read' },
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const devToolsTaskSchedulerReadPermission = createPermission({
|
||||
name: 'devtools.task-scheduler',
|
||||
attributes: { action: 'read' },
|
||||
});
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const devToolsTaskSchedulerCreatePermission = createPermission({
|
||||
name: 'devtools.task-scheduler',
|
||||
attributes: { action: 'create' },
|
||||
});
|
||||
|
||||
/**
|
||||
* List of all Devtools permissions
|
||||
*
|
||||
@@ -58,4 +74,6 @@ export const devToolsPermissions = [
|
||||
devToolsInfoReadPermission,
|
||||
devToolsConfigReadPermission,
|
||||
devToolsExternalDependenciesReadPermission,
|
||||
devToolsTaskSchedulerReadPermission,
|
||||
devToolsTaskSchedulerCreatePermission,
|
||||
];
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
/* We want to maintain the same information as an enum, so we disable the redeclaration warning */
|
||||
/* eslint-disable @typescript-eslint/no-redeclare */
|
||||
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
|
||||
/** @public */
|
||||
export type Endpoint = {
|
||||
@@ -82,3 +82,54 @@ export type ConfigError = {
|
||||
messages?: string[];
|
||||
stack?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The shape of a task definition as returned by the service's REST API.
|
||||
* This is a duplication of the below:
|
||||
* @see https://github.com/backstage/backstage/blob/master/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TaskApiTasksResponse {
|
||||
taskId: string;
|
||||
pluginId: string;
|
||||
scope: 'global' | 'local';
|
||||
settings: { version: number } & JsonObject;
|
||||
taskState:
|
||||
| {
|
||||
status: 'running';
|
||||
startedAt: string;
|
||||
timesOutAt?: string;
|
||||
lastRunError?: string;
|
||||
lastRunEndedAt?: string;
|
||||
}
|
||||
| {
|
||||
status: 'idle';
|
||||
startsAt?: string;
|
||||
lastRunError?: string;
|
||||
lastRunEndedAt?: string;
|
||||
}
|
||||
| null;
|
||||
workerState:
|
||||
| {
|
||||
status: 'initial-wait';
|
||||
}
|
||||
| {
|
||||
status: 'idle';
|
||||
}
|
||||
| {
|
||||
status: 'running';
|
||||
}
|
||||
| null;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export type ScheduledTasks = {
|
||||
scheduledTasks?: TaskApiTasksResponse[];
|
||||
error?: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export type TriggerScheduledTask = {
|
||||
error?: string;
|
||||
};
|
||||
|
||||
@@ -34,6 +34,12 @@ Lists the configuration being used by your current running Backstage instance.
|
||||
|
||||

|
||||
|
||||
### Scheduled Tasks
|
||||
|
||||
Scheduled tasks can be viewed and triggered under the `Scheduled Tasks` tab. [See below to configure](#scheduled-tasks-configuration).
|
||||
|
||||

|
||||
|
||||
## Optional Features
|
||||
|
||||
The DevTools plugin can be setup with other tabs with additional helpful features.
|
||||
@@ -456,3 +462,14 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
apt-get update && \
|
||||
apt-get install -y ... iputils-ping
|
||||
```
|
||||
|
||||
### Scheduled Tasks Configuration
|
||||
|
||||
Scheduled tasks can be viewed and triggered under the `Scheduled Tasks` tab. You first must add the list of plugins for scheduled tasks to your config:
|
||||
|
||||
```yaml
|
||||
devTools:
|
||||
scheduledTasks:
|
||||
plugins:
|
||||
- catalog
|
||||
```
|
||||
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export interface Config {
|
||||
devTools: {
|
||||
/**
|
||||
* Scheduled tasks configuration
|
||||
* @visibility frontend
|
||||
*/
|
||||
scheduledTasks: {
|
||||
/**
|
||||
* A list of plugin IDs to select from, e.g. ['catalog', 'scaffolder']
|
||||
* @visibility frontend
|
||||
*/
|
||||
plugins: string[];
|
||||
};
|
||||
};
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 404 KiB |
@@ -39,7 +39,8 @@
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
@@ -51,6 +52,7 @@
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-compat-api": "workspace:^",
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
@@ -60,6 +62,7 @@
|
||||
"@material-ui/core": "^4.9.13",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
||||
"lodash": "^4.17.21",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-use": "^17.2.4"
|
||||
},
|
||||
@@ -67,6 +70,7 @@
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/dev-utils": "workspace:^",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"@types/react": "^18.0.0",
|
||||
"react": "^18.0.2",
|
||||
"react-dom": "^18.0.2",
|
||||
@@ -82,5 +86,6 @@
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { JSX as JSX_2 } from 'react/jsx-runtime';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { TabProps } from '@material-ui/core/Tab';
|
||||
import { TaskApiTasksResponse } from '@backstage/plugin-devtools-common';
|
||||
|
||||
// @public (undocumented)
|
||||
export const ConfigContent: () => JSX_2.Element;
|
||||
@@ -43,6 +44,16 @@ export const ExternalDependenciesContent: () => JSX_2.Element;
|
||||
// @public (undocumented)
|
||||
export const InfoContent: () => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ScheduledTaskDetailPanel: ({
|
||||
rowData,
|
||||
}: {
|
||||
rowData: TaskApiTasksResponse;
|
||||
}) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export const ScheduledTasksContent: () => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export type SubRoute = {
|
||||
path: string;
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
ConfigInfo,
|
||||
DevToolsInfo,
|
||||
ExternalDependency,
|
||||
ScheduledTasks,
|
||||
TriggerScheduledTask,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
|
||||
export const devToolsApiRef = createApiRef<DevToolsApi>({
|
||||
@@ -29,4 +31,9 @@ export interface DevToolsApi {
|
||||
getConfig(): Promise<ConfigInfo | undefined>;
|
||||
getExternalDependencies(): Promise<ExternalDependency[] | undefined>;
|
||||
getInfo(): Promise<DevToolsInfo | undefined>;
|
||||
getScheduledTasksByPlugin(plugin: string): Promise<ScheduledTasks>;
|
||||
triggerScheduledTask(
|
||||
plugin: string,
|
||||
taskId: string,
|
||||
): Promise<TriggerScheduledTask>;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
ConfigInfo,
|
||||
DevToolsInfo,
|
||||
ExternalDependency,
|
||||
ScheduledTasks,
|
||||
TriggerScheduledTask,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { DevToolsApi } from './DevToolsApi';
|
||||
@@ -42,6 +44,45 @@ export class DevToolsClient implements DevToolsApi {
|
||||
return configInfo;
|
||||
}
|
||||
|
||||
public async getScheduledTasksByPlugin(
|
||||
plugin: string,
|
||||
): Promise<ScheduledTasks> {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`;
|
||||
const url = new URL('.backstage/scheduler/v1/tasks', baseUrl);
|
||||
|
||||
const response = await this.fetchApi.fetch(url.toString());
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
const scheduledTasks = await response.json();
|
||||
return {
|
||||
scheduledTasks: scheduledTasks.tasks,
|
||||
};
|
||||
}
|
||||
|
||||
public async triggerScheduledTask(
|
||||
plugin: string,
|
||||
taskId: string,
|
||||
): Promise<TriggerScheduledTask> {
|
||||
const baseUrl = `${await this.discoveryApi.getBaseUrl(plugin)}/`;
|
||||
const url = new URL(
|
||||
`.backstage/scheduler/v1/tasks/${taskId}/trigger`,
|
||||
baseUrl,
|
||||
);
|
||||
|
||||
const response = await this.fetchApi.fetch(url.toString(), {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
return response.json() as Promise<TriggerScheduledTask>;
|
||||
}
|
||||
|
||||
public async getExternalDependencies(): Promise<
|
||||
ExternalDependency[] | undefined
|
||||
> {
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { TaskApiTasksResponse } from '@backstage/plugin-devtools-common';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
detailPanel: {
|
||||
padding: theme.spacing(2),
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
detailLabel: {
|
||||
fontWeight: 'bold',
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
errorIcon: {
|
||||
color: theme.palette.error.main,
|
||||
marginRight: theme.spacing(1),
|
||||
fontSize: '1.2rem',
|
||||
},
|
||||
detailPanelAlert: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const ScheduledTaskDetailPanel = ({
|
||||
rowData,
|
||||
}: {
|
||||
rowData: TaskApiTasksResponse;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const lastRunError = rowData.taskState?.lastRunError;
|
||||
|
||||
const DetailItem = ({ title, value }: { title: string; value: any }) => (
|
||||
<>
|
||||
<Grid item xs={3}>
|
||||
<Typography variant="subtitle2" className={classes.detailLabel}>
|
||||
{title}:
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Typography variant="body2" component="code">
|
||||
{value || 'N/A'}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box className={classes.detailPanel}>
|
||||
{lastRunError && (
|
||||
<Alert severity="error" className={classes.detailPanelAlert}>
|
||||
<strong>Last Run Error:</strong> {lastRunError}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Grid container spacing={1}>
|
||||
<DetailItem title="Worker State" value={rowData.workerState?.status} />
|
||||
<DetailItem
|
||||
title="Frequency (Cadence)"
|
||||
value={rowData.settings?.cadence?.toString()}
|
||||
/>
|
||||
<DetailItem title="Scope" value={rowData.scope} />
|
||||
<DetailItem
|
||||
title="Started At"
|
||||
value={
|
||||
rowData.taskState?.status === 'running' &&
|
||||
rowData.taskState.startedAt
|
||||
? new Date(rowData.taskState.startedAt).toLocaleString()
|
||||
: 'N/A'
|
||||
}
|
||||
/>
|
||||
<DetailItem
|
||||
title="Times Out At"
|
||||
value={
|
||||
rowData.taskState?.status === 'running' &&
|
||||
rowData.taskState.timesOutAt
|
||||
? new Date(rowData.taskState.timesOutAt).toLocaleString()
|
||||
: 'N/A'
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Autocomplete from '@material-ui/lab/Autocomplete';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { makeStyles, createStyles, Theme } from '@material-ui/core/styles';
|
||||
import { Progress, Table, TableColumn } from '@backstage/core-components';
|
||||
import Alert from '@material-ui/lab/Alert';
|
||||
import { useScheduledTasks, useTriggerScheduledTask } from '../../../hooks';
|
||||
import { TaskApiTasksResponse } from '@backstage/plugin-devtools-common';
|
||||
import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
import NightsStay from '@material-ui/icons/NightsStay';
|
||||
import Error from '@material-ui/icons/Error';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import { ScheduledTaskDetailPanel } from './ScheduledTaskDetailedPanel';
|
||||
import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
import { devToolsTaskSchedulerCreatePermission } from '@backstage/plugin-devtools-common';
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
paperStyle: {
|
||||
display: 'flex',
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
flexContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
padding: 0,
|
||||
},
|
||||
formControl: {
|
||||
minWidth: 240,
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
detailPanel: {
|
||||
padding: theme.spacing(2),
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
detailLabel: {
|
||||
fontWeight: 'bold',
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
errorIcon: {
|
||||
color: theme.palette.error.main,
|
||||
marginRight: theme.spacing(1),
|
||||
fontSize: '1.2rem',
|
||||
},
|
||||
detailPanelAlert: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const StatusDisplay = ({
|
||||
icon,
|
||||
text,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
text: string;
|
||||
}) => (
|
||||
<Box display="flex" alignItems="center">
|
||||
{icon}
|
||||
<Typography variant="body2" style={{ marginLeft: 8 }}>
|
||||
{text}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export const ScheduledTasksContent = () => {
|
||||
const classes = useStyles();
|
||||
const configApi = useApi(configApiRef);
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const plugins =
|
||||
configApi.getOptionalStringArray('devTools.scheduledTasks.plugins') || [];
|
||||
const [selectedPlugin, setSelectedPlugin] = useState(plugins[0] || '');
|
||||
const { scheduledTasks, loading, error } = useScheduledTasks(selectedPlugin);
|
||||
const { triggerTask, isTriggering, triggerError } = useTriggerScheduledTask();
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const handleAutocompleteChange = (_event: any, newValue: string | null) => {
|
||||
setSelectedPlugin(newValue || '');
|
||||
};
|
||||
|
||||
const handleCommitChange = () => {
|
||||
if (inputValue !== selectedPlugin) {
|
||||
setSelectedPlugin(inputValue);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
handleCommitChange();
|
||||
// Prevent Autocomplete's default behavior (which might select a filtered item)
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
if (!plugins || plugins.length === 0) {
|
||||
return (
|
||||
<Alert severity="info">
|
||||
No plugins configured for scheduled tasks. Please configure
|
||||
`devTools.scheduledTasks.plugins` in app-config.yaml.
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
const columns: TableColumn<TaskApiTasksResponse>[] = [
|
||||
{
|
||||
title: 'Task ID',
|
||||
field: 'taskId',
|
||||
width: '35%',
|
||||
render: (rowData: TaskApiTasksResponse) => {
|
||||
const errorIconStyle: React.CSSProperties = {
|
||||
color: '#f44336',
|
||||
marginRight: '8px',
|
||||
fontSize: '1.2rem',
|
||||
verticalAlign: 'middle',
|
||||
};
|
||||
|
||||
return (
|
||||
<Box display="flex" alignItems="center">
|
||||
{rowData.taskState?.lastRunError && (
|
||||
<Error style={errorIconStyle} />
|
||||
)}
|
||||
<Typography>{rowData.taskId}</Typography>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
field: 'taskState.status',
|
||||
width: '15%',
|
||||
render: (rowData: TaskApiTasksResponse) => {
|
||||
const status = rowData.taskState?.status;
|
||||
|
||||
if (status === 'idle') {
|
||||
return (
|
||||
<StatusDisplay icon={<NightsStay fontSize="small" />} text="Idle" />
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'running') {
|
||||
return (
|
||||
<StatusDisplay
|
||||
icon={<CircularProgress color="inherit" size="30px" />}
|
||||
text="Running"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <Typography variant="body2">{status || 'N/A'}</Typography>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Last Run',
|
||||
field: 'taskState.lastRunEndedAt',
|
||||
width: '25%',
|
||||
render: (rowData: TaskApiTasksResponse) =>
|
||||
rowData.taskState?.lastRunEndedAt
|
||||
? new Date(rowData.taskState.lastRunEndedAt).toLocaleString()
|
||||
: 'N/A',
|
||||
},
|
||||
{
|
||||
title: 'Next Run',
|
||||
width: '15%',
|
||||
render: (rowData: TaskApiTasksResponse) =>
|
||||
rowData.taskState?.status === 'idle' && rowData.taskState.startsAt
|
||||
? new Date(rowData.taskState.startsAt).toLocaleString()
|
||||
: 'N/A',
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
render: (rowData: TaskApiTasksResponse) => (
|
||||
<RequirePermission permission={devToolsTaskSchedulerCreatePermission}>
|
||||
<Tooltip title="Run Task">
|
||||
<IconButton
|
||||
aria-label="Trigger"
|
||||
onClick={() => {
|
||||
triggerTask(selectedPlugin, rowData.taskId);
|
||||
if (isTriggering) {
|
||||
<CircularProgress color="inherit" size="30px" />;
|
||||
}
|
||||
if (triggerError) {
|
||||
alertApi.post({
|
||||
message: `Error triggering task ${rowData.taskId}: ${error}`,
|
||||
severity: 'error',
|
||||
});
|
||||
} else {
|
||||
alertApi.post({
|
||||
message: `Successfully triggered task ${rowData.taskId}`,
|
||||
severity: 'success',
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</RequirePermission>
|
||||
),
|
||||
sorting: false,
|
||||
width: '10%',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Autocomplete
|
||||
className={classes.formControl}
|
||||
classes={{ root: classes.formControl }}
|
||||
freeSolo
|
||||
options={plugins}
|
||||
value={selectedPlugin}
|
||||
inputValue={inputValue}
|
||||
onChange={handleAutocompleteChange}
|
||||
onInputChange={(_event, newInputValue) => {
|
||||
setInputValue(newInputValue);
|
||||
}}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Select Plugin"
|
||||
variant="outlined"
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleCommitChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{loading && <Progress />}
|
||||
|
||||
{error && (
|
||||
<Alert severity="warning">
|
||||
The plugin ID "{selectedPlugin}" doesn't have any scheduled tasks or
|
||||
may contain a typo. Please verify the plugin ID is correct and that
|
||||
the plugin has registered scheduled tasks.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{!loading && !error && (
|
||||
<Table
|
||||
title={`Scheduled Tasks (${selectedPlugin})`}
|
||||
options={{
|
||||
paging: true,
|
||||
search: true,
|
||||
sorting: true,
|
||||
searchFieldAlignment: 'right',
|
||||
}}
|
||||
columns={columns}
|
||||
data={scheduledTasks || []}
|
||||
emptyContent={
|
||||
<Alert severity="info">
|
||||
No scheduled tasks found for {selectedPlugin}.
|
||||
</Alert>
|
||||
}
|
||||
detailPanel={({ rowData }) => {
|
||||
return <ScheduledTaskDetailPanel rowData={rowData} />;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"taskId": "cool-provider",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT24H",
|
||||
"timeoutAfterDuration": "PT60M"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "running",
|
||||
"startedAt": "2025-10-31T21:02:36.461+00:00",
|
||||
"timesOutAt": "2025-10-31T22:02:36.461+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T19:57:15.674+00:00",
|
||||
"lastRunError": "{\"name\":\"error\",\"message\":\"insert into \\\"refresh_state\\\" (\\\"entity_id\\\", \\\"entity_ref\\\", \\\"errors\\\", \\\"last_discovery_at\\\", \\\"location_key\\\", \\\"next_update_at\\\", \\\"unprocessed_entity\\\", \\\"unprocessed_hash\\\") values ($1, $2, $3, CURRENT_TIMESTAMP, $4, CURRENT_TIMESTAMP, $5, $6), ($7, $8, $9, CURRENT_TIMESTAMP, $10, CURRENT_TIMESTAMP, $11, $12), ($13, $14, $15, CURRENT_TIMESTAMP, $16, CURRENT_TIMESTAMP, $17, $18), ($19, $20, $21, CURRENT_TIMESTAMP, $22, CURRENT_TIMESTAMP, $23, $24), ($25, $26, $27, CURRENT_TIMESTAMP, $28, CURRENT_TIMESTAMP, $29, $30), ($31, $32, $33, CURRENT_TIMESTAMP, $34, CURRENT_TIMESTAMP, $35, $36), ($37, $38, $39, CURRENT_TIMESTAMP, $40, CURRENT_TIMESTAMP, $41, $42), ($43, $44, $45, CURRENT_TIMESTAMP, $46, CURRENT_TIMESTAMP, $47, $48), ($49, $50, $51, CURRENT_TIMESTAMP, $52, CURRENT_TIMESTAMP, $53, $54), ($55, $56, $57, CURRENT_TIMESTAMP, $58, CURRENT_TIMESTAMP, $59, $60), ($61, $62, $63, CURRENT_TIMESTAMP, $64, CURRENT_TIMESTAMP, $65, $66), ($67, $68, $69, CURRENT_TIMESTAMP, $70, CURRENT_TIMESTAMP, $71, $72), ($73, $74, $75, CURRENT_TIMESTAMP, $76, CURRENT_TIMESTAMP, $77, $78), ($79, $80, $81, CURRENT_TIMESTAMP, $82, CURRENT_TIMESTAMP, $83, $84), ($85, $86, $87, CURRENT_TIMESTAMP, $88, CURRENT_TIMESTAMP, $89, $90), ($91, $92, $93, CURRENT_TIMESTAMP, $94, CURRENT_TIMESTAMP, $95, $96), ($97, $98, $99, CURRENT_TIMESTAMP, $100, CURRENT_TIMESTAMP, $101, $102), ($103, $104, $105, CURRENT_TIMESTAMP, $106, CURRENT_TIMESTAMP, $107, $108), ($109, $110, $111, CURRENT_TIMESTAMP, $112, CURRENT_TIMESTAMP, $113, $114), ($115, $116, $117, CURRENT_TIMESTAMP, $118, CURRENT_TIMESTAMP, $119, $120), ($121, $122, $123, CURRENT_TIMESTAMP, $124, CURRENT_TIMESTAMP, $125, $126), ($127, $128, $129, CURRENT_TIMESTAMP, $130, CURRENT_TIMESTAMP, $131, $132), ($133, $134, $135, CURRENT_TIMESTAMP, $136, CURRENT_TIMESTAMP, $137, $138), ($139, $140, $141, CURRENT_TIMESTAMP, $142, CURRENT_TIMESTAMP, $143, $144), ($145, $146, $147, CURRENT_TIMESTAMP, $148, CURRENT_TIMESTAMP, $149, $150), ($151, $152, $153, CURRENT_TIMESTAMP, $154, CURRENT_TIMESTAMP, $155, $156), ($157, $158, $159, CURRENT_TIMESTAMP, $160, CURRENT_TIMESTAMP, $161, $162), ($163, $164, $165, CURRENT_TIMESTAMP, $166, CURRENT_TIMESTAMP, $167, $168), ($169, $170, $171, CURRENT_TIMESTAMP, $172, CURRENT_TIMESTAMP, $173, $174), ($175, $176, $177, CURRENT_TIMESTAMP, $178, CURRENT_TIMESTAMP, $179, $180), ($181, $182, $183, CURRENT_TIMESTAMP, $184, CURRENT_TIMESTAMP, $185, $186), ($187, $188, $189, CURRENT_TIMESTAMP, $190, CURRENT_TIMESTAMP, $191, $192), ($193, $194, $195, CURRENT_TIMESTAMP, $196, CURRENT_TIMESTAMP, $197, $198), ($199, $200, $201, CURRENT_TIMESTAMP, $202, CURRENT_TIMESTAMP, $203, $204), ($205, $206, $207, CURRENT_TIMESTAMP, $208, CURRENT_TIMESTAMP, $209, $210), ($211, $212, $213, CURRENT_TIMESTAMP, $214, CURRENT_TIMESTAMP, $215, $216), ($217, $218, $219, CURRENT_TIMESTAMP, $220, CURRENT_TIMESTAMP, $221, $222), ($223, $224, $225, CURRENT_TIMESTAMP, $226, CURRENT_TIMESTAMP, $227, $228), ($229, $230, $231, CURRENT_TIMESTAMP, $232, CURRENT_TIMESTAMP, $233, $234), ($235, $236, $237, CURRENT_TIMESTAMP, $238, CURRENT_TIMESTAMP, $239, $240), ($241, $242, $243, CURRENT_TIMESTAMP, $244, CURRENT_TIMESTAMP, $245, $246), ($247, $248, $249, CURRENT_TIMESTAMP, $250, CURRENT_TIMESTAMP, $251, $252), ($253, $254, $255, CURRENT_TIMESTAMP, $256, CURRENT_TIMESTAMP, $257, $258), ($259, $260, $261, CURRENT_TIMESTAMP, $262, CURRENT_TIMESTAMP, $263, $264), ($265, $266, $267, CURRENT_TIMESTAMP, $268, CURRENT_TIMESTAMP, $269, $270), ($271, $272, $273, CURRENT_TIMESTAMP, $274, CURRENT_TIMESTAMP, $275, $276), ($277, $278, $279, CURRENT_TIMESTAMP, $280, CURRENT_TIMESTAMP, $281, $282), ($283, $284, $285, CURRENT_TIMESTAMP, $286, CURRENT_TIMESTAMP, $287, $288), ($289, $290, $291, CURRENT_TIMESTAMP, $292, CURRENT_TIMESTAMP, $293, $294), ($295, $296, $297, CURRENT_TIMESTAMP, $298, CURRENT_TIMESTAMP, $299, $300) - value too long for type character varying(255)\",\"length\":99,\"severity\":\"ERROR\",\"code\":\"22001\",\"file\":\"varchar.c\",\"line\":\"637\",\"routine\":\"varchar\"}"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "running"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "some-provider",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT24H",
|
||||
"timeoutAfterDuration": "PT24H"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "idle",
|
||||
"startsAt": "2025-11-01T21:02:34.850+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T21:02:35.556+00:00"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "three-provider",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT30M",
|
||||
"timeoutAfterDuration": "PT2M"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "idle",
|
||||
"startsAt": "2025-10-31T21:16:04.145+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T20:46:09.184+00:00",
|
||||
"lastRunError": "{\"name\":\"TypeError\",\"message\":\"String.prototype.replaceAll called with a non-global RegExp argument\"}"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "a-provider",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT60M",
|
||||
"timeoutAfterDuration": "PT24H"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "idle",
|
||||
"startsAt": "2025-10-31T21:16:04.152+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T20:16:08.713+00:00"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "github-provider:provider123:refresh",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT24H",
|
||||
"timeoutAfterDuration": "PT1H"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "idle",
|
||||
"startsAt": "2025-11-01T18:16:04.153+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T18:18:56.176+00:00"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "github-provider:provider123:refresh",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT24H",
|
||||
"timeoutAfterDuration": "PT1H"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "idle",
|
||||
"startsAt": "2025-11-01T18:16:04.156+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T18:16:04.264+00:00"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "github-provider:provider234:refresh",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT24H",
|
||||
"timeoutAfterDuration": "PT1H"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "idle",
|
||||
"startsAt": "2025-11-01T18:16:04.157+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T18:16:08.564+00:00"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "github-provider:provider567:refresh",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT24H",
|
||||
"timeoutAfterDuration": "PT1H"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "idle",
|
||||
"startsAt": "2025-11-01T18:16:04.154+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T18:16:06.978+00:00"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "github-provider:provider910:refresh",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT24H",
|
||||
"timeoutAfterDuration": "PT1H"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "idle",
|
||||
"startsAt": "2025-11-01T18:16:04.160+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T18:16:08.573+00:00"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "github-provider:provider000:refresh",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT24H",
|
||||
"timeoutAfterDuration": "PT1H"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "idle",
|
||||
"startsAt": "2025-11-01T18:16:04.161+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T18:18:54.339+00:00"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"taskId": "catalog_orphan_cleanup",
|
||||
"pluginId": "catalog",
|
||||
"scope": "global",
|
||||
"settings": {
|
||||
"version": 2,
|
||||
"cadence": "PT30S",
|
||||
"timeoutAfterDuration": "PT24S"
|
||||
},
|
||||
"taskState": {
|
||||
"status": "idle",
|
||||
"startsAt": "2025-10-31T21:06:35.672+00:00",
|
||||
"lastRunEndedAt": "2025-10-31T21:06:09.106+00:00"
|
||||
},
|
||||
"workerState": {
|
||||
"status": "idle"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { ScheduledTasksContent } from './ScheduledTasksContent';
|
||||
export { ScheduledTaskDetailPanel } from './ScheduledTaskDetailedPanel';
|
||||
@@ -17,3 +17,4 @@
|
||||
export * from './ConfigContent';
|
||||
export * from './InfoContent';
|
||||
export * from './ExternalDependenciesContent';
|
||||
export * from './ScheduledTasksContent';
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
import {
|
||||
devToolsConfigReadPermission,
|
||||
devToolsInfoReadPermission,
|
||||
devToolsTaskSchedulerReadPermission,
|
||||
} from '@backstage/plugin-devtools-common';
|
||||
|
||||
import { ConfigContent } from '../Content/ConfigContent';
|
||||
import { DevToolsLayout } from '../DevToolsLayout';
|
||||
import { InfoContent } from '../Content/InfoContent';
|
||||
import { RequirePermission } from '@backstage/plugin-permission-react';
|
||||
import { ScheduledTasksContent } from '../Content/ScheduledTasksContent';
|
||||
|
||||
/** @public */
|
||||
export const DefaultDevToolsPage = () => (
|
||||
@@ -37,5 +39,10 @@ export const DefaultDevToolsPage = () => (
|
||||
<ConfigContent />
|
||||
</RequirePermission>
|
||||
</DevToolsLayout.Route>
|
||||
<DevToolsLayout.Route path="scheduled-tasks" title="Scheduled Tasks">
|
||||
<RequirePermission permission={devToolsTaskSchedulerReadPermission}>
|
||||
<ScheduledTasksContent />
|
||||
</RequirePermission>
|
||||
</DevToolsLayout.Route>
|
||||
</DevToolsLayout>
|
||||
);
|
||||
|
||||
@@ -17,3 +17,5 @@
|
||||
export { useConfig } from './useConfig';
|
||||
export { useExternalDependencies } from './useExternalDependencies';
|
||||
export { useInfo } from './useInfo';
|
||||
export { useScheduledTasks } from './useScheduledTasks';
|
||||
export { useTriggerScheduledTask } from './useTriggerScheduledTask';
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { devToolsApiRef } from '../api';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
|
||||
export const useScheduledTasks = (plugin: string) => {
|
||||
const api = useApi(devToolsApiRef);
|
||||
|
||||
const {
|
||||
value,
|
||||
loading,
|
||||
error: asyncError,
|
||||
} = useAsync(async () => {
|
||||
return api.getScheduledTasksByPlugin(plugin);
|
||||
}, [api, plugin]);
|
||||
|
||||
if (asyncError) {
|
||||
return {
|
||||
scheduledTasks: undefined,
|
||||
loading: false,
|
||||
error: asyncError.message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
scheduledTasks: value?.scheduledTasks,
|
||||
loading,
|
||||
error: value?.error,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useState, useCallback } from 'react';
|
||||
import { devToolsApiRef } from '../api';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
export const useTriggerScheduledTask = () => {
|
||||
const api = useApi(devToolsApiRef);
|
||||
const [isTriggering, setIsTriggering] = useState(false);
|
||||
const [error, setError] = useState<Error | undefined>();
|
||||
|
||||
const triggerTask = useCallback(
|
||||
async (plugin: string, taskId: string) => {
|
||||
setIsTriggering(true);
|
||||
setError(undefined);
|
||||
|
||||
try {
|
||||
await api.triggerScheduledTask(plugin, taskId);
|
||||
} catch (e) {
|
||||
setError(e);
|
||||
} finally {
|
||||
setIsTriggering(false);
|
||||
}
|
||||
},
|
||||
[api],
|
||||
);
|
||||
|
||||
return {
|
||||
triggerTask,
|
||||
isTriggering,
|
||||
triggerError: error?.message,
|
||||
};
|
||||
};
|
||||
@@ -5473,6 +5473,7 @@ __metadata:
|
||||
resolution: "@backstage/plugin-devtools@workspace:plugins/devtools"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/core-compat-api": "workspace:^"
|
||||
"@backstage/core-components": "workspace:^"
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
@@ -5484,7 +5485,9 @@ __metadata:
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
"@material-ui/lab": "npm:^4.0.0-alpha.57"
|
||||
"@testing-library/jest-dom": "npm:^6.0.0"
|
||||
"@types/lodash": "npm:^4.14.151"
|
||||
"@types/react": "npm:^18.0.0"
|
||||
lodash: "npm:^4.17.21"
|
||||
react: "npm:^18.0.2"
|
||||
react-dom: "npm:^18.0.2"
|
||||
react-json-view: "npm:^1.21.3"
|
||||
|
||||
Reference in New Issue
Block a user