jscheffl commented on code in PR #55301: URL: https://github.com/apache/airflow/pull/55301#discussion_r2328666264
########## providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/OperationsCell.tsx: ########## @@ -0,0 +1,138 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 { Box, Flex, HStack, IconButton, Textarea, VStack } from "@chakra-ui/react"; +import type { Worker } from "openapi/requests/types.gen"; +import { useState } from "react"; +import { FcCheckmark } from "react-icons/fc"; +import { HiOutlineWrenchScrewdriver } from "react-icons/hi2"; +import { ImCross } from "react-icons/im"; +import { IoMdExit } from "react-icons/io"; + +interface MaintenanceFormProps { + onSubmit: (comment: string) => void; + onCancel: () => void; +} + +const MaintenanceForm = ({ onCancel, onSubmit }: MaintenanceFormProps) => { + const [comment, setComment] = useState(""); + + const handleSubmit = () => { + if (comment.trim()) { + onSubmit(comment.trim()); + } + }; + + return ( + <VStack gap={2} align="stretch"> + <Textarea + placeholder="Enter maintenance comment (required)" + value={comment} + onChange={(e) => setComment(e.target.value)} + required + maxLength={1024} + size="sm" + /> + <HStack gap={2}> + <IconButton + size="sm" + colorScheme="green" + onClick={handleSubmit} + disabled={!comment.trim()} + aria-label="Confirm Maintenance" + > + <FcCheckmark /> + </IconButton> + <IconButton size="sm" colorScheme="red" variant="outline" onClick={onCancel} aria-label="Cancel"> Review Comment: Replication of commit https://github.com/apache/airflow/pull/55348/commits/fdd0c2b0f7eeeda79a8e00cec50a7417ef0f9406 (1) ```suggestion <IconButton size="sm" colorScheme="red" variant="outline" onClick={onCancel} aria-label="Cancel" title="Cancel" > ``` ########## providers/edge3/src/airflow/providers/edge3/worker_api/routes/ui.py: ########## @@ -100,3 +105,57 @@ def jobs( jobs=result, total_entries=len(result), ) + + +class MaintenanceRequest(BaseModel): + """Request body for maintenance operations.""" + + maintenance_comment: Annotated[str, Field(description="Comment describing the maintenance reason.")] + + +@ui_router.post( + "/worker/{worker_name}/maintenance", +) Review Comment: Just added this in commit https://github.com/apache/airflow/pull/55348/commits/ec77bd65b96fd27eb601aeda51d4fb92354bc301 and it worked for me. ########## providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/OperationsCell.tsx: ########## @@ -0,0 +1,138 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 { Box, Flex, HStack, IconButton, Textarea, VStack } from "@chakra-ui/react"; +import type { Worker } from "openapi/requests/types.gen"; +import { useState } from "react"; +import { FcCheckmark } from "react-icons/fc"; +import { HiOutlineWrenchScrewdriver } from "react-icons/hi2"; +import { ImCross } from "react-icons/im"; +import { IoMdExit } from "react-icons/io"; + +interface MaintenanceFormProps { + onSubmit: (comment: string) => void; + onCancel: () => void; +} + +const MaintenanceForm = ({ onCancel, onSubmit }: MaintenanceFormProps) => { + const [comment, setComment] = useState(""); + + const handleSubmit = () => { + if (comment.trim()) { + onSubmit(comment.trim()); + } + }; + + return ( + <VStack gap={2} align="stretch"> + <Textarea + placeholder="Enter maintenance comment (required)" + value={comment} + onChange={(e) => setComment(e.target.value)} + required + maxLength={1024} + size="sm" + /> + <HStack gap={2}> + <IconButton + size="sm" + colorScheme="green" + onClick={handleSubmit} + disabled={!comment.trim()} + aria-label="Confirm Maintenance" + > + <FcCheckmark /> + </IconButton> + <IconButton size="sm" colorScheme="red" variant="outline" onClick={onCancel} aria-label="Cancel"> + <ImCross /> + </IconButton> + </HStack> + </VStack> + ); +}; + +interface OperationsCellProps { + worker: Worker; + activeMaintenanceForm: string | null; + onSetActiveMaintenanceForm: (workerName: string | null) => void; + onRequestMaintenance: (workerName: string, comment: string) => void; + onExitMaintenance: (workerName: string) => void; +} + +export const OperationsCell = ({ + activeMaintenanceForm, + onExitMaintenance, + onRequestMaintenance, + onSetActiveMaintenanceForm, + worker, +}: OperationsCellProps) => { + const workerName = worker.worker_name; + const state = worker.state; + + let cellContent = null; + + if (state === "idle" || state === "running") { + if (activeMaintenanceForm === workerName) { + cellContent = ( + <MaintenanceForm + onSubmit={(comment) => onRequestMaintenance(workerName, comment)} + onCancel={() => onSetActiveMaintenanceForm(null)} + /> + ); + } else { + cellContent = ( + <Flex justifyContent="end"> + <IconButton + size="sm" + variant="ghost" + onClick={() => onSetActiveMaintenanceForm(workerName)} + aria-label="Enter Maintenance" Review Comment: Replication of commit https://github.com/apache/airflow/commit/fdd0c2b0f7eeeda79a8e00cec50a7417ef0f9406 (2) ```suggestion aria-label="Enter Maintenance" title="Enter Maintenance" ``` ########## providers/edge3/src/airflow/providers/edge3/worker_api/routes/ui.py: ########## @@ -100,3 +103,47 @@ def jobs( jobs=result, total_entries=len(result), ) + + +@ui_router.post( + "/worker/{worker_name}/maintenance", +) +def request_worker_maintenance( + worker_name: str, + maintenance_request: MaintenanceRequest, + session: SessionDep, Review Comment: Replication of commit https://github.com/apache/airflow/pull/55348/commits/ec77bd65b96fd27eb601aeda51d4fb92354bc301 (3) ```suggestion session: SessionDep, user: GetUserDep, ``` ########## providers/edge3/src/airflow/providers/edge3/worker_api/routes/ui.py: ########## @@ -17,18 +17,21 @@ from __future__ import annotations -from fastapi import Depends +from datetime import datetime + +from fastapi import Depends, HTTPException from sqlalchemy import select from airflow.api_fastapi.auth.managers.models.resource_details import AccessView from airflow.api_fastapi.common.db.common import SessionDep # noqa: TC001 from airflow.api_fastapi.common.router import AirflowRouter from airflow.api_fastapi.core_api.security import requires_access_view Review Comment: Replication of commit https://github.com/apache/airflow/pull/55348/commits/ec77bd65b96fd27eb601aeda51d4fb92354bc301 (1) ```suggestion from airflow.api_fastapi.core_api.security import GetUserDep, requires_access_view ``` ########## providers/edge3/src/airflow/providers/edge3/worker_api/routes/ui.py: ########## @@ -100,3 +105,57 @@ def jobs( jobs=result, total_entries=len(result), ) + + +class MaintenanceRequest(BaseModel): + """Request body for maintenance operations.""" + + maintenance_comment: Annotated[str, Field(description="Comment describing the maintenance reason.")] + + +@ui_router.post( + "/worker/{worker_name}/maintenance", +) +def request_worker_maintenance( + worker_name: str, + maintenance_request: MaintenanceRequest, + session: SessionDep, +) -> None: + """Put a worker into maintenance mode.""" + # Check if worker exists first + worker_query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name) + worker = session.scalar(worker_query) + if not worker: + raise HTTPException(status_code=404, detail=f"Worker {worker_name} not found") + + # Format the comment with timestamp and username (username will be added by plugin layer) + formatted_comment = f"[{datetime.now().strftime('%Y-%m-%d %H:%M')}] - UI user put node into maintenance mode\nComment: {maintenance_request.maintenance_comment}" + + try: + request_maintenance(worker_name, formatted_comment, session=session) + session.commit() # Explicitly commit the transaction + except Exception as e: + session.rollback() # Rollback on error + raise HTTPException(status_code=400, detail=str(e)) + + +@ui_router.delete( + "/worker/{worker_name}/maintenance", +) Review Comment: Same here: Adding in commit https://github.com/apache/airflow/pull/55348/commits/ec77bd65b96fd27eb601aeda51d4fb92354bc301 and worked fine. ########## providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/OperationsCell.tsx: ########## @@ -0,0 +1,138 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 { Box, Flex, HStack, IconButton, Textarea, VStack } from "@chakra-ui/react"; +import type { Worker } from "openapi/requests/types.gen"; +import { useState } from "react"; +import { FcCheckmark } from "react-icons/fc"; +import { HiOutlineWrenchScrewdriver } from "react-icons/hi2"; +import { ImCross } from "react-icons/im"; +import { IoMdExit } from "react-icons/io"; + +interface MaintenanceFormProps { + onSubmit: (comment: string) => void; + onCancel: () => void; +} + +const MaintenanceForm = ({ onCancel, onSubmit }: MaintenanceFormProps) => { + const [comment, setComment] = useState(""); + + const handleSubmit = () => { + if (comment.trim()) { + onSubmit(comment.trim()); + } + }; + + return ( + <VStack gap={2} align="stretch"> + <Textarea + placeholder="Enter maintenance comment (required)" + value={comment} + onChange={(e) => setComment(e.target.value)} + required + maxLength={1024} + size="sm" + /> + <HStack gap={2}> + <IconButton + size="sm" + colorScheme="green" + onClick={handleSubmit} + disabled={!comment.trim()} + aria-label="Confirm Maintenance" + > + <FcCheckmark /> + </IconButton> + <IconButton size="sm" colorScheme="red" variant="outline" onClick={onCancel} aria-label="Cancel"> + <ImCross /> + </IconButton> + </HStack> + </VStack> + ); +}; + +interface OperationsCellProps { + worker: Worker; + activeMaintenanceForm: string | null; + onSetActiveMaintenanceForm: (workerName: string | null) => void; + onRequestMaintenance: (workerName: string, comment: string) => void; + onExitMaintenance: (workerName: string) => void; +} + +export const OperationsCell = ({ + activeMaintenanceForm, + onExitMaintenance, + onRequestMaintenance, + onSetActiveMaintenanceForm, + worker, +}: OperationsCellProps) => { + const workerName = worker.worker_name; + const state = worker.state; + + let cellContent = null; + + if (state === "idle" || state === "running") { + if (activeMaintenanceForm === workerName) { + cellContent = ( + <MaintenanceForm + onSubmit={(comment) => onRequestMaintenance(workerName, comment)} + onCancel={() => onSetActiveMaintenanceForm(null)} + /> + ); + } else { + cellContent = ( + <Flex justifyContent="end"> + <IconButton + size="sm" + variant="ghost" + onClick={() => onSetActiveMaintenanceForm(workerName)} + aria-label="Enter Maintenance" + > + <HiOutlineWrenchScrewdriver /> + </IconButton> + </Flex> + ); + } + } else if ( + state === "maintenance pending" || + state === "maintenance mode" || + state === "maintenance request" || + state === "maintenance exit" || + state === "offline maintenance" + ) { + cellContent = ( + <VStack gap={2} align="stretch"> + <Box fontSize="sm" whiteSpace="pre-wrap"> + {worker.maintenance_comments || "No comment"} + </Box> + <Flex justifyContent="end"> + <IconButton + size="sm" + variant="ghost" + onClick={() => onExitMaintenance(workerName)} + aria-label="Exit Maintenance" Review Comment: Replication of commit https://github.com/apache/airflow/commit/fdd0c2b0f7eeeda79a8e00cec50a7417ef0f9406 (3) ```suggestion aria-label="Exit Maintenance" title="Exit Maintenance" ``` ########## providers/edge3/src/airflow/providers/edge3/worker_api/routes/ui.py: ########## @@ -100,3 +105,57 @@ def jobs( jobs=result, total_entries=len(result), ) + + +class MaintenanceRequest(BaseModel): + """Request body for maintenance operations.""" + + maintenance_comment: Annotated[str, Field(description="Comment describing the maintenance reason.")] + + +@ui_router.post( + "/worker/{worker_name}/maintenance", +) +def request_worker_maintenance( + worker_name: str, + maintenance_request: MaintenanceRequest, + session: SessionDep, +) -> None: + """Put a worker into maintenance mode.""" + # Check if worker exists first + worker_query = select(EdgeWorkerModel).where(EdgeWorkerModel.worker_name == worker_name) + worker = session.scalar(worker_query) + if not worker: + raise HTTPException(status_code=404, detail=f"Worker {worker_name} not found") + + # Format the comment with timestamp and username (username will be added by plugin layer) + formatted_comment = f"[{datetime.now().strftime('%Y-%m-%d %H:%M')}] - UI user put node into maintenance mode\nComment: {maintenance_request.maintenance_comment}" Review Comment: Replication of commit https://github.com/apache/airflow/commit/ec77bd65b96fd27eb601aeda51d4fb92354bc301 (4) ```suggestion formatted_comment = f"[{datetime.now().strftime('%Y-%m-%d %H:%M')}] - {user.get_name()} put node into maintenance mode\nComment: {maintenance_request.maintenance_comment}" ``` ########## providers/edge3/src/airflow/providers/edge3/plugins/www/src/components/OperationsCell.tsx: ########## @@ -0,0 +1,138 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 { Box, Flex, HStack, IconButton, Textarea, VStack } from "@chakra-ui/react"; +import type { Worker } from "openapi/requests/types.gen"; +import { useState } from "react"; +import { FcCheckmark } from "react-icons/fc"; +import { HiOutlineWrenchScrewdriver } from "react-icons/hi2"; +import { ImCross } from "react-icons/im"; +import { IoMdExit } from "react-icons/io"; + +interface MaintenanceFormProps { + onSubmit: (comment: string) => void; + onCancel: () => void; +} + +const MaintenanceForm = ({ onCancel, onSubmit }: MaintenanceFormProps) => { + const [comment, setComment] = useState(""); + + const handleSubmit = () => { + if (comment.trim()) { + onSubmit(comment.trim()); + } + }; + + return ( + <VStack gap={2} align="stretch"> + <Textarea + placeholder="Enter maintenance comment (required)" + value={comment} + onChange={(e) => setComment(e.target.value)} + required + maxLength={1024} + size="sm" + /> + <HStack gap={2}> + <IconButton + size="sm" + colorScheme="green" + onClick={handleSubmit} + disabled={!comment.trim()} + aria-label="Confirm Maintenance" + > + <FcCheckmark /> + </IconButton> + <IconButton size="sm" colorScheme="red" variant="outline" onClick={onCancel} aria-label="Cancel"> + <ImCross /> + </IconButton> + </HStack> + </VStack> + ); +}; + +interface OperationsCellProps { + worker: Worker; + activeMaintenanceForm: string | null; + onSetActiveMaintenanceForm: (workerName: string | null) => void; + onRequestMaintenance: (workerName: string, comment: string) => void; + onExitMaintenance: (workerName: string) => void; +} + +export const OperationsCell = ({ + activeMaintenanceForm, + onExitMaintenance, + onRequestMaintenance, + onSetActiveMaintenanceForm, + worker, +}: OperationsCellProps) => { + const workerName = worker.worker_name; + const state = worker.state; + + let cellContent = null; + + if (state === "idle" || state === "running") { + if (activeMaintenanceForm === workerName) { + cellContent = ( + <MaintenanceForm + onSubmit={(comment) => onRequestMaintenance(workerName, comment)} + onCancel={() => onSetActiveMaintenanceForm(null)} + /> + ); + } else { + cellContent = ( + <Flex justifyContent="end"> + <IconButton + size="sm" + variant="ghost" + onClick={() => onSetActiveMaintenanceForm(workerName)} + aria-label="Enter Maintenance" + > + <HiOutlineWrenchScrewdriver /> + </IconButton> + </Flex> + ); + } + } else if ( + state === "maintenance pending" || + state === "maintenance mode" || + state === "maintenance request" || + state === "maintenance exit" || Review Comment: Replication of commit https://github.com/apache/airflow/pull/55348/commits/119ad58cecb4155a23a88783bfb6e96ea26ae37c ```suggestion ``` -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: commits-unsubscr...@airflow.apache.org For queries about this service, please contact Infrastructure at: us...@infra.apache.org