apache-airflow-providers-edge3 1.2.0rc1__py3-none-any.whl → 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- airflow/providers/edge3/__init__.py +1 -1
- airflow/providers/edge3/cli/edge_command.py +43 -0
- airflow/providers/edge3/cli/worker.py +40 -40
- airflow/providers/edge3/models/edge_worker.py +13 -8
- airflow/providers/edge3/openapi/v2-edge-generated.yaml +249 -0
- airflow/providers/edge3/plugins/www/dist/main.umd.cjs +53 -19
- airflow/providers/edge3/plugins/www/openapi-gen/queries/common.ts +7 -0
- airflow/providers/edge3/plugins/www/openapi-gen/queries/queries.ts +44 -1
- airflow/providers/edge3/plugins/www/openapi-gen/requests/schemas.gen.ts +14 -0
- airflow/providers/edge3/plugins/www/openapi-gen/requests/services.gen.ts +158 -1
- airflow/providers/edge3/plugins/www/openapi-gen/requests/types.gen.ts +155 -0
- airflow/providers/edge3/plugins/www/package.json +14 -10
- airflow/providers/edge3/plugins/www/pnpm-lock.yaml +601 -457
- airflow/providers/edge3/plugins/www/src/components/AddQueueButton.tsx +138 -0
- airflow/providers/edge3/plugins/www/src/components/MaintenanceEditCommentButton.tsx +106 -0
- airflow/providers/edge3/plugins/www/src/components/MaintenanceEnterButton.tsx +102 -0
- airflow/providers/edge3/plugins/www/src/components/MaintenanceExitButton.tsx +92 -0
- airflow/providers/edge3/plugins/www/src/components/RemoveQueueButton.tsx +151 -0
- airflow/providers/edge3/plugins/www/src/components/WorkerDeleteButton.tsx +104 -0
- airflow/providers/edge3/plugins/www/src/components/WorkerOperations.tsx +85 -0
- airflow/providers/edge3/plugins/www/src/components/WorkerShutdownButton.tsx +104 -0
- airflow/providers/edge3/plugins/www/src/components/WorkerStateBadge.tsx +33 -0
- airflow/providers/edge3/plugins/www/src/components/ui/ScrollToAnchor.tsx +49 -0
- airflow/providers/edge3/plugins/www/src/components/ui/createToaster.ts +24 -0
- airflow/providers/edge3/plugins/www/src/components/ui/index.ts +2 -0
- airflow/providers/edge3/plugins/www/src/context/colorMode/ColorModeProvider.tsx +1 -2
- airflow/providers/edge3/plugins/www/src/context/colorMode/useColorMode.tsx +2 -5
- airflow/providers/edge3/plugins/www/src/pages/JobsPage.tsx +52 -15
- airflow/providers/edge3/plugins/www/src/pages/WorkerPage.tsx +52 -22
- airflow/providers/edge3/plugins/www/src/theme.ts +378 -130
- airflow/providers/edge3/plugins/www/vite.config.ts +2 -0
- airflow/providers/edge3/worker_api/datamodels_ui.py +12 -0
- airflow/providers/edge3/worker_api/routes/ui.py +193 -3
- {apache_airflow_providers_edge3-1.2.0rc1.dist-info → apache_airflow_providers_edge3-1.3.0.dist-info}/METADATA +7 -7
- {apache_airflow_providers_edge3-1.2.0rc1.dist-info → apache_airflow_providers_edge3-1.3.0.dist-info}/RECORD +37 -27
- {apache_airflow_providers_edge3-1.2.0rc1.dist-info → apache_airflow_providers_edge3-1.3.0.dist-info}/WHEEL +0 -0
- {apache_airflow_providers_edge3-1.2.0rc1.dist-info → apache_airflow_providers_edge3-1.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Box, Flex, VStack } from "@chakra-ui/react";
|
|
20
|
+
import type { Worker } from "openapi/requests/types.gen";
|
|
21
|
+
|
|
22
|
+
import { toaster } from "src/components/ui";
|
|
23
|
+
|
|
24
|
+
import { AddQueueButton } from "./AddQueueButton";
|
|
25
|
+
import { MaintenanceEditCommentButton } from "./MaintenanceEditCommentButton";
|
|
26
|
+
import { MaintenanceEnterButton } from "./MaintenanceEnterButton";
|
|
27
|
+
import { MaintenanceExitButton } from "./MaintenanceExitButton";
|
|
28
|
+
import { RemoveQueueButton } from "./RemoveQueueButton";
|
|
29
|
+
import { WorkerDeleteButton } from "./WorkerDeleteButton";
|
|
30
|
+
import { WorkerShutdownButton } from "./WorkerShutdownButton";
|
|
31
|
+
|
|
32
|
+
interface WorkerOperationsProps {
|
|
33
|
+
onOperations: () => void;
|
|
34
|
+
worker: Worker;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const WorkerOperations = ({ onOperations, worker }: WorkerOperationsProps) => {
|
|
38
|
+
const workerName = worker.worker_name;
|
|
39
|
+
const state = worker.state;
|
|
40
|
+
|
|
41
|
+
const onWorkerChange = (toast: Record<string, string>) => {
|
|
42
|
+
toaster.create(toast);
|
|
43
|
+
onOperations();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (state === "idle" || state === "running") {
|
|
47
|
+
return (
|
|
48
|
+
<Flex justifyContent="end" gap={2}>
|
|
49
|
+
<AddQueueButton onQueueUpdate={onWorkerChange} workerName={workerName} />
|
|
50
|
+
<RemoveQueueButton onQueueUpdate={onWorkerChange} worker={worker} />
|
|
51
|
+
<MaintenanceEnterButton onEnterMaintenance={onWorkerChange} workerName={workerName} />
|
|
52
|
+
<WorkerShutdownButton onShutdown={onWorkerChange} workerName={workerName} />
|
|
53
|
+
</Flex>
|
|
54
|
+
);
|
|
55
|
+
} else if (
|
|
56
|
+
state === "maintenance pending" ||
|
|
57
|
+
state === "maintenance mode" ||
|
|
58
|
+
state === "maintenance request" ||
|
|
59
|
+
state === "offline maintenance"
|
|
60
|
+
) {
|
|
61
|
+
return (
|
|
62
|
+
<VStack gap={2} align="stretch">
|
|
63
|
+
<Box fontSize="sm" whiteSpace="pre-wrap">
|
|
64
|
+
{worker.maintenance_comments || "No comment"}
|
|
65
|
+
</Box>
|
|
66
|
+
<Flex justifyContent="end" gap={2}>
|
|
67
|
+
<MaintenanceEditCommentButton onEditComment={onWorkerChange} workerName={workerName} />
|
|
68
|
+
<MaintenanceExitButton onExitMaintenance={onWorkerChange} workerName={workerName} />
|
|
69
|
+
{state === "offline maintenance" ? (
|
|
70
|
+
<WorkerDeleteButton onDelete={onWorkerChange} workerName={workerName} />
|
|
71
|
+
) : (
|
|
72
|
+
<WorkerShutdownButton onShutdown={onWorkerChange} workerName={workerName} />
|
|
73
|
+
)}
|
|
74
|
+
</Flex>
|
|
75
|
+
</VStack>
|
|
76
|
+
);
|
|
77
|
+
} else if (state === "offline" || state === "unknown") {
|
|
78
|
+
return (
|
|
79
|
+
<Flex justifyContent="end">
|
|
80
|
+
<WorkerDeleteButton onDelete={onWorkerChange} workerName={workerName} />
|
|
81
|
+
</Flex>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Button, CloseButton, Dialog, IconButton, Portal, Text, useDisclosure } from "@chakra-ui/react";
|
|
20
|
+
import { useUiServiceRequestWorkerShutdown } from "openapi/queries";
|
|
21
|
+
import { FaPowerOff } from "react-icons/fa";
|
|
22
|
+
|
|
23
|
+
interface WorkerShutdownButtonProps {
|
|
24
|
+
onShutdown: (toast: Record<string, string>) => void;
|
|
25
|
+
workerName: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const WorkerShutdownButton = ({ onShutdown, workerName }: WorkerShutdownButtonProps) => {
|
|
29
|
+
const { onClose, onOpen, open } = useDisclosure();
|
|
30
|
+
|
|
31
|
+
const shutdownMutation = useUiServiceRequestWorkerShutdown({
|
|
32
|
+
onError: (error) => {
|
|
33
|
+
onShutdown({
|
|
34
|
+
description: `Unable to request shutdown for worker ${workerName}: ${error}`,
|
|
35
|
+
title: "Shutdown Request Failed",
|
|
36
|
+
type: "error",
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
onSuccess: () => {
|
|
40
|
+
onShutdown({
|
|
41
|
+
description: `Worker ${workerName} was requested to shutdown.`,
|
|
42
|
+
title: "Shutdown Request Sent",
|
|
43
|
+
type: "success",
|
|
44
|
+
});
|
|
45
|
+
onClose();
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const handleShutdown = () => {
|
|
50
|
+
shutdownMutation.mutate({ workerName });
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
<IconButton
|
|
56
|
+
size="sm"
|
|
57
|
+
variant="ghost"
|
|
58
|
+
onClick={onOpen}
|
|
59
|
+
aria-label="Shutdown Worker"
|
|
60
|
+
title="Shutdown Worker"
|
|
61
|
+
colorPalette="danger"
|
|
62
|
+
>
|
|
63
|
+
<FaPowerOff />
|
|
64
|
+
</IconButton>
|
|
65
|
+
|
|
66
|
+
<Dialog.Root onOpenChange={onClose} open={open} size="md">
|
|
67
|
+
<Portal>
|
|
68
|
+
<Dialog.Backdrop />
|
|
69
|
+
<Dialog.Positioner>
|
|
70
|
+
<Dialog.Content>
|
|
71
|
+
<Dialog.Header>
|
|
72
|
+
<Dialog.Title>Shutdown worker {workerName}</Dialog.Title>
|
|
73
|
+
</Dialog.Header>
|
|
74
|
+
<Dialog.Body>
|
|
75
|
+
<Text>Are you sure you want to request shutdown for worker {workerName}?</Text>
|
|
76
|
+
<Text fontSize="sm" color="red.500" mt={2}>
|
|
77
|
+
This stops the worker on the remote edge. You can't restart it from the UI — you need to
|
|
78
|
+
start it remotely instead.
|
|
79
|
+
</Text>
|
|
80
|
+
</Dialog.Body>
|
|
81
|
+
<Dialog.Footer>
|
|
82
|
+
<Dialog.ActionTrigger asChild>
|
|
83
|
+
<Button variant="outline">Cancel</Button>
|
|
84
|
+
</Dialog.ActionTrigger>
|
|
85
|
+
<Button
|
|
86
|
+
onClick={handleShutdown}
|
|
87
|
+
colorPalette="danger"
|
|
88
|
+
loading={shutdownMutation.isPending}
|
|
89
|
+
loadingText="Shutting down..."
|
|
90
|
+
>
|
|
91
|
+
<FaPowerOff style={{ marginRight: "8px" }} />
|
|
92
|
+
Shutdown Worker
|
|
93
|
+
</Button>
|
|
94
|
+
</Dialog.Footer>
|
|
95
|
+
<Dialog.CloseTrigger asChild>
|
|
96
|
+
<CloseButton size="sm" />
|
|
97
|
+
</Dialog.CloseTrigger>
|
|
98
|
+
</Dialog.Content>
|
|
99
|
+
</Dialog.Positioner>
|
|
100
|
+
</Portal>
|
|
101
|
+
</Dialog.Root>
|
|
102
|
+
</>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
@@ -48,6 +48,38 @@ const state2Color = (state: EdgeWorkerState | null | undefined) => {
|
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
const state2TooltipText = (state: EdgeWorkerState | null | undefined) => {
|
|
52
|
+
switch (state) {
|
|
53
|
+
// see enum mapping from providers/edge3/src/airflow/providers/edge3/models/edge_worker.py:EdgeWorkerState
|
|
54
|
+
case "starting":
|
|
55
|
+
return "Edge Worker is in initialization.";
|
|
56
|
+
case "running":
|
|
57
|
+
return "Edge Worker is actively running a task.";
|
|
58
|
+
case "idle":
|
|
59
|
+
return "Edge Worker is active and waiting for a task.";
|
|
60
|
+
case "shutdown request":
|
|
61
|
+
return "Request to shutdown Edge Worker is issued. It will be picked-up on the next heartbeat, tasks will drain and then worker will terminate.";
|
|
62
|
+
case "terminating":
|
|
63
|
+
return "Edge Worker is completing work (draining running tasks) and stopping.";
|
|
64
|
+
case "offline":
|
|
65
|
+
return "Edge Worker was shut down.";
|
|
66
|
+
case "unknown":
|
|
67
|
+
return "No heartbeat signal from worker for some time, Edge Worker probably down or got disconnected.";
|
|
68
|
+
case "maintenance request":
|
|
69
|
+
return "Worker was requested to enter maintenance mode. Once worker receives this message it will pause fetching tasks and drain tasks.";
|
|
70
|
+
case "maintenance pending":
|
|
71
|
+
return "Edge Worker received the request for maintenance, waiting for tasks to finish. Once tasks are finished will move to 'maintenance mode'.";
|
|
72
|
+
case "maintenance mode":
|
|
73
|
+
return "Edge Worker is in maintenance mode. It is online but pauses fetching tasks.";
|
|
74
|
+
case "maintenance exit":
|
|
75
|
+
return "Request Worker is requested to exit maintenance mode. Once the worker receives this state it will un-pause and fetch new tasks.";
|
|
76
|
+
case "offline maintenance":
|
|
77
|
+
return "Worker was shut down in maintenance mode. It will be in maintenance mode when restarted.";
|
|
78
|
+
default:
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
51
83
|
export type Props = {
|
|
52
84
|
readonly state?: EdgeWorkerState | null;
|
|
53
85
|
} & BadgeProps;
|
|
@@ -61,6 +93,7 @@ export const WorkerStateBadge = React.forwardRef<HTMLDivElement, Props>(
|
|
|
61
93
|
px={children === undefined ? 1 : 2}
|
|
62
94
|
py={1}
|
|
63
95
|
ref={ref}
|
|
96
|
+
title={state2TooltipText(state)}
|
|
64
97
|
variant="solid"
|
|
65
98
|
{...rest}
|
|
66
99
|
>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { useEffect, useState } from "react";
|
|
20
|
+
|
|
21
|
+
interface ScrollToAnchorProps {
|
|
22
|
+
inline?: ScrollLogicalPosition;
|
|
23
|
+
block?: ScrollLogicalPosition;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const ScrollToAnchor = ({ block = "start", inline = "nearest" }: ScrollToAnchorProps): null => {
|
|
27
|
+
const [hash, setHash] = useState(() => window.location.hash);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const onHashChange = () => setHash(window.location.hash);
|
|
31
|
+
window.addEventListener("hashchange", onHashChange);
|
|
32
|
+
return () => window.removeEventListener("hashchange", onHashChange);
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (hash) {
|
|
37
|
+
const element = document.getElementById(hash.slice(1));
|
|
38
|
+
if (element) {
|
|
39
|
+
element.scrollIntoView({
|
|
40
|
+
behavior: "auto",
|
|
41
|
+
block: block,
|
|
42
|
+
inline: inline,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}, [hash, block, inline]);
|
|
47
|
+
|
|
48
|
+
return null;
|
|
49
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { createToaster } from "@chakra-ui/react";
|
|
20
|
+
|
|
21
|
+
export const toaster = createToaster({
|
|
22
|
+
pauseOnPageIdle: true,
|
|
23
|
+
placement: "bottom-end",
|
|
24
|
+
});
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
* specific language governing permissions and limitations
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
|
-
import { ThemeProvider } from "next-themes";
|
|
20
|
-
import type { ThemeProviderProps } from "next-themes/dist/types";
|
|
19
|
+
import { ThemeProvider, type ThemeProviderProps } from "next-themes";
|
|
21
20
|
|
|
22
21
|
export const ColorModeProvider = (props: ThemeProviderProps) => (
|
|
23
22
|
<ThemeProvider attribute="class" disableTransitionOnChange {...props} />
|
|
@@ -19,14 +19,11 @@
|
|
|
19
19
|
import { useTheme } from "next-themes";
|
|
20
20
|
|
|
21
21
|
export const useColorMode = () => {
|
|
22
|
-
const { resolvedTheme, setTheme } = useTheme();
|
|
23
|
-
const toggleColorMode = () => {
|
|
24
|
-
setTheme(resolvedTheme === "light" ? "dark" : "light");
|
|
25
|
-
};
|
|
22
|
+
const { resolvedTheme, setTheme, theme } = useTheme();
|
|
26
23
|
|
|
27
24
|
return {
|
|
28
25
|
colorMode: resolvedTheme as "dark" | "light" | undefined,
|
|
26
|
+
selectedTheme: theme as "dark" | "light" | "system" | undefined,
|
|
29
27
|
setColorMode: setTheme,
|
|
30
|
-
toggleColorMode,
|
|
31
28
|
};
|
|
32
29
|
};
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* specific language governing permissions and limitations
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
|
-
import { Box, Table } from "@chakra-ui/react";
|
|
19
|
+
import { Box, Link, Table, Text } from "@chakra-ui/react";
|
|
20
20
|
import { useUiServiceJobs } from "openapi/queries";
|
|
21
|
+
import { Link as RouterLink } from "react-router-dom";
|
|
22
|
+
import TimeAgo from "react-timeago";
|
|
21
23
|
|
|
22
24
|
import { ErrorAlert } from "src/components/ErrorAlert";
|
|
23
25
|
import { StateBadge } from "src/components/StateBadge";
|
|
@@ -33,9 +35,8 @@ export const JobsPage = () => {
|
|
|
33
35
|
// Use DataTable as component from Airflow-Core UI
|
|
34
36
|
// Add sorting
|
|
35
37
|
// Add filtering
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
if (data)
|
|
38
|
+
// Translation?
|
|
39
|
+
if (data?.jobs && data.jobs.length > 0)
|
|
39
40
|
return (
|
|
40
41
|
<Box p={2}>
|
|
41
42
|
<Table.Root size="sm" interactive stickyHeader striped>
|
|
@@ -58,31 +59,67 @@ export const JobsPage = () => {
|
|
|
58
59
|
<Table.Row
|
|
59
60
|
key={`${job.dag_id}.${job.run_id}.${job.task_id}.${job.map_index}.${job.try_number}`}
|
|
60
61
|
>
|
|
61
|
-
<Table.Cell>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
<Table.Cell>
|
|
63
|
+
{/* TODO Check why <Link to={`/dags/${job.dag_id}`}> is not working via react-router-dom! */}
|
|
64
|
+
<Link href={`../dags/${job.dag_id}`}>{job.dag_id}</Link>
|
|
65
|
+
</Table.Cell>
|
|
66
|
+
<Table.Cell>
|
|
67
|
+
<Link href={`../dags/${job.dag_id}/runs/${job.run_id}`}>{job.run_id}</Link>
|
|
68
|
+
</Table.Cell>
|
|
69
|
+
<Table.Cell>
|
|
70
|
+
{job.map_index >= 0 ? (
|
|
71
|
+
<Link
|
|
72
|
+
href={`../dags/${job.dag_id}/runs/${job.run_id}/tasks/${job.task_id}/mapped/${job.map_index}?try_number=${job.try_number}`}
|
|
73
|
+
>
|
|
74
|
+
{job.task_id}
|
|
75
|
+
</Link>
|
|
76
|
+
) : (
|
|
77
|
+
<Link
|
|
78
|
+
href={`../dags/${job.dag_id}/runs/${job.run_id}/tasks/${job.task_id}?try_number=${job.try_number}`}
|
|
79
|
+
>
|
|
80
|
+
{job.task_id}
|
|
81
|
+
</Link>
|
|
82
|
+
)}
|
|
83
|
+
</Table.Cell>
|
|
84
|
+
<Table.Cell>{job.map_index >= 0 ? job.map_index : "-"}</Table.Cell>
|
|
65
85
|
<Table.Cell>{job.try_number}</Table.Cell>
|
|
66
86
|
<Table.Cell>
|
|
67
87
|
<StateBadge state={job.state}>{job.state}</StateBadge>
|
|
68
88
|
</Table.Cell>
|
|
69
89
|
<Table.Cell>{job.queue}</Table.Cell>
|
|
70
|
-
<Table.Cell>
|
|
71
|
-
|
|
72
|
-
|
|
90
|
+
<Table.Cell>
|
|
91
|
+
{job.queued_dttm ? <TimeAgo date={job.queued_dttm} live={false} /> : undefined}
|
|
92
|
+
</Table.Cell>
|
|
93
|
+
<Table.Cell>
|
|
94
|
+
<RouterLink to={`/plugin/edge_worker#${job.edge_worker}`}>{job.edge_worker}</RouterLink>
|
|
95
|
+
</Table.Cell>
|
|
96
|
+
<Table.Cell>
|
|
97
|
+
{job.last_update ? <TimeAgo date={job.last_update} live={false} /> : undefined}
|
|
98
|
+
</Table.Cell>
|
|
73
99
|
</Table.Row>
|
|
74
100
|
))}
|
|
75
101
|
</Table.Body>
|
|
76
102
|
</Table.Root>
|
|
77
103
|
</Box>
|
|
78
104
|
);
|
|
105
|
+
if (data) {
|
|
106
|
+
return (
|
|
107
|
+
<Text as="div" pl={4} pt={1}>
|
|
108
|
+
Currently no jobs running. Start a Dag and then all active jobs should show up here. Note that after
|
|
109
|
+
some (configurable) time, jobs are purged from the list.
|
|
110
|
+
</Text>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
79
113
|
if (error) {
|
|
80
114
|
return (
|
|
81
|
-
<
|
|
82
|
-
<p>Unable to load data:</p>
|
|
115
|
+
<Text as="div" pl={4} pt={1}>
|
|
83
116
|
<ErrorAlert error={error} />
|
|
84
|
-
</
|
|
117
|
+
</Text>
|
|
85
118
|
);
|
|
86
119
|
}
|
|
87
|
-
return
|
|
120
|
+
return (
|
|
121
|
+
<Text as="div" pl={4} pt={1}>
|
|
122
|
+
Loading...
|
|
123
|
+
</Text>
|
|
124
|
+
);
|
|
88
125
|
};
|
|
@@ -16,27 +16,31 @@
|
|
|
16
16
|
* specific language governing permissions and limitations
|
|
17
17
|
* under the License.
|
|
18
18
|
*/
|
|
19
|
-
import { Box, Table } from "@chakra-ui/react";
|
|
19
|
+
import { Box, Code, Link, List, Table, Text } from "@chakra-ui/react";
|
|
20
20
|
import { useUiServiceWorker } from "openapi/queries";
|
|
21
|
+
import { LuExternalLink } from "react-icons/lu";
|
|
22
|
+
import TimeAgo from "react-timeago";
|
|
21
23
|
|
|
22
24
|
import { ErrorAlert } from "src/components/ErrorAlert";
|
|
25
|
+
import { WorkerOperations } from "src/components/WorkerOperations";
|
|
23
26
|
import { WorkerStateBadge } from "src/components/WorkerStateBadge";
|
|
27
|
+
import { ScrollToAnchor } from "src/components/ui";
|
|
24
28
|
import { autoRefreshInterval } from "src/utils";
|
|
25
29
|
|
|
26
30
|
export const WorkerPage = () => {
|
|
27
|
-
const { data, error } = useUiServiceWorker(undefined, {
|
|
31
|
+
const { data, error, refetch } = useUiServiceWorker(undefined, {
|
|
28
32
|
enabled: true,
|
|
29
33
|
refetchInterval: autoRefreshInterval,
|
|
30
34
|
});
|
|
31
35
|
|
|
32
36
|
// TODO to make it proper
|
|
33
37
|
// Use DataTable as component from Airflow-Core UI
|
|
34
|
-
// Add actions for maintenance / delete of orphan worker
|
|
35
38
|
// Add sorting
|
|
36
39
|
// Add filtering
|
|
37
|
-
// Add links to see jobs on worker
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
+
// Add links with filter to see jobs on worker
|
|
41
|
+
// Add time zone support for time display
|
|
42
|
+
// Translation?
|
|
43
|
+
if (data?.workers && data.workers.length > 0)
|
|
40
44
|
return (
|
|
41
45
|
<Box p={2}>
|
|
42
46
|
<Table.Root size="sm" interactive stickyHeader striped>
|
|
@@ -54,52 +58,78 @@ export const WorkerPage = () => {
|
|
|
54
58
|
</Table.Header>
|
|
55
59
|
<Table.Body>
|
|
56
60
|
{data.workers.map((worker) => (
|
|
57
|
-
<Table.Row key={worker.worker_name}>
|
|
61
|
+
<Table.Row key={worker.worker_name} id={worker.worker_name}>
|
|
58
62
|
<Table.Cell>{worker.worker_name}</Table.Cell>
|
|
59
63
|
<Table.Cell>
|
|
60
64
|
<WorkerStateBadge state={worker.state}>{worker.state}</WorkerStateBadge>
|
|
61
65
|
</Table.Cell>
|
|
62
66
|
<Table.Cell>
|
|
63
67
|
{worker.queues ? (
|
|
64
|
-
<
|
|
68
|
+
<List.Root>
|
|
65
69
|
{worker.queues.map((queue) => (
|
|
66
|
-
<
|
|
70
|
+
<List.Item key={queue}>{queue}</List.Item>
|
|
67
71
|
))}
|
|
68
|
-
</
|
|
72
|
+
</List.Root>
|
|
69
73
|
) : (
|
|
70
|
-
"(
|
|
74
|
+
"(all queues)"
|
|
71
75
|
)}
|
|
72
76
|
</Table.Cell>
|
|
73
|
-
<Table.Cell>
|
|
74
|
-
|
|
77
|
+
<Table.Cell>
|
|
78
|
+
{worker.first_online ? <TimeAgo date={worker.first_online} live={false} /> : undefined}
|
|
79
|
+
</Table.Cell>
|
|
80
|
+
<Table.Cell>
|
|
81
|
+
{worker.last_heartbeat ? <TimeAgo date={worker.last_heartbeat} live={false} /> : undefined}
|
|
82
|
+
</Table.Cell>
|
|
75
83
|
<Table.Cell>{worker.jobs_active}</Table.Cell>
|
|
76
84
|
<Table.Cell>
|
|
77
85
|
{worker.sysinfo ? (
|
|
78
|
-
<
|
|
86
|
+
<List.Root>
|
|
79
87
|
{Object.entries(worker.sysinfo).map(([key, value]) => (
|
|
80
|
-
<
|
|
88
|
+
<List.Item key={key}>
|
|
81
89
|
{key}: {value}
|
|
82
|
-
</
|
|
90
|
+
</List.Item>
|
|
83
91
|
))}
|
|
84
|
-
</
|
|
92
|
+
</List.Root>
|
|
85
93
|
) : (
|
|
86
94
|
"N/A"
|
|
87
95
|
)}
|
|
88
96
|
</Table.Cell>
|
|
89
|
-
<Table.Cell>
|
|
97
|
+
<Table.Cell>
|
|
98
|
+
<WorkerOperations worker={worker} onOperations={refetch} />
|
|
99
|
+
</Table.Cell>
|
|
90
100
|
</Table.Row>
|
|
91
101
|
))}
|
|
92
102
|
</Table.Body>
|
|
93
103
|
</Table.Root>
|
|
104
|
+
<ScrollToAnchor />
|
|
94
105
|
</Box>
|
|
95
106
|
);
|
|
107
|
+
if (data) {
|
|
108
|
+
return (
|
|
109
|
+
<Text as="div" pl={4} pt={1}>
|
|
110
|
+
No known workers. Start one via <Code>airflow edge worker [...]</Code>. See{" "}
|
|
111
|
+
<Link
|
|
112
|
+
target="_blank"
|
|
113
|
+
variant="underline"
|
|
114
|
+
color="fg.info"
|
|
115
|
+
href="https://airflow.apache.org/docs/apache-airflow-providers-edge3/stable/deployment.html"
|
|
116
|
+
>
|
|
117
|
+
Edge Worker Deployment docs <LuExternalLink />
|
|
118
|
+
</Link>{" "}
|
|
119
|
+
how to deploy a new worker.
|
|
120
|
+
</Text>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
96
123
|
if (error) {
|
|
97
124
|
return (
|
|
98
|
-
<
|
|
99
|
-
<p>Unable to load data:</p>
|
|
125
|
+
<Text as="div" pl={4} pt={1}>
|
|
100
126
|
<ErrorAlert error={error} />
|
|
101
|
-
</
|
|
127
|
+
</Text>
|
|
102
128
|
);
|
|
103
129
|
}
|
|
104
|
-
return
|
|
130
|
+
return (
|
|
131
|
+
<Text as="div" pl={4} pt={1}>
|
|
132
|
+
Loading...
|
|
133
|
+
</Text>
|
|
134
|
+
);
|
|
105
135
|
};
|