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

Reply via email to