From 72dd280a96ee6b984818b26b134a3a101258e23e Mon Sep 17 00:00:00 2001 From: Salil Ponde Date: Tue, 26 Dec 2023 15:24:39 +0530 Subject: [PATCH] 1.5.2 (#17) * Bumped version to 1.5.2 * Compose actions moved to definition. Implemented Deploy action (Pull+Up) --- .github/workflows/main.yml | 2 +- pkg/agent/tasks.go | 4 +- pkg/agent/tasks_compose.go | 16 ++ pkg/common/common.go | 2 +- pkg/dockerapi/compose.go | 129 +++++--------- pkg/dockerapi/models.go | 6 + pkg/server/handler/handler.go | 1 + pkg/server/handler/node_compose.go | 56 +++++++ runserver.sh | 1 + web/src/app/compose/compose-actions.tsx | 158 ------------------ .../app/compose/compose-definition-github.tsx | 148 ++++++++++++++-- .../app/compose/compose-definition-local.tsx | 143 ++++++++++++++-- web/src/app/compose/compose-definition.tsx | 94 ++++++++++- web/src/app/compose/compose-list.tsx | 2 +- .../app/credentials/github-pat-add-dialog.tsx | 6 +- web/src/components/delete-dialog.tsx | 8 +- .../components/side-nav/side-nav-compose.tsx | 7 - web/src/components/ui/button.tsx | 10 +- web/src/components/widgets/main-container.tsx | 15 +- web/src/lib/version.ts | 2 +- web/src/router.tsx | 5 - 21 files changed, 508 insertions(+), 307 deletions(-) delete mode 100644 web/src/app/compose/compose-actions.tsx diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e898a31..f57f2aa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: branches: ["main"] env: - VERSION: "1.5.1" + VERSION: "1.5.2" jobs: docker: diff --git a/pkg/agent/tasks.go b/pkg/agent/tasks.go index c72940a..fd45bb4 100644 --- a/pkg/agent/tasks.go +++ b/pkg/agent/tasks.go @@ -21,7 +21,7 @@ func startTaskSession(tqm messages.TaskQueuedMessage) { stream := false steamMessageTypes := []string{"DockerContainerLogs", "DockerContainerTerminal", - "DockerComposePull", "DockerComposeUp", "DockerComposeDown", "DockerComposeLogs"} + "DockerComposeDeploy", "DockerComposePull","DockerComposePull", "DockerComposeUp", "DockerComposeDown", "DockerComposeLogs"} if slices.Contains(steamMessageTypes, messageType) { stream = true } @@ -80,6 +80,8 @@ func startTaskSession(tqm messages.TaskQueuedMessage) { handleDockerComposeContainerList(c, taskDefinition) case "DockerComposeLogs": handleDockerComposeLogs(c, taskDefinition) + case "DockerComposeDeploy": + handleDockerComposeDeploy(c, taskDefinition) case "DockerComposePull": handleDockerComposePull(c, taskDefinition) case "DockerComposeUp": diff --git a/pkg/agent/tasks_compose.go b/pkg/agent/tasks_compose.go index 0e52dc1..d2a7cda 100644 --- a/pkg/agent/tasks_compose.go +++ b/pkg/agent/tasks_compose.go @@ -106,6 +106,22 @@ func handleDockerComposeLogs(c *websocket.Conn, messageString string) { } } +func handleDockerComposeDeploy(c *websocket.Conn, messageString string) { + m, err := messages.Parse[dockerapi.DockerComposeDeploy](messageString) + if err != nil { + err := completedWithFailure(c, "Error parsing request message") + if err != nil { + log.Debug().Err(err).Msg("Error sending message to client") + } + return + } + + err = dockerapi.ComposeDeploy(m, c) + if err != nil { + log.Debug().Err(err).Msg("Error sending message to client") + } +} + func handleDockerComposePull(c *websocket.Conn, messageString string) { m, err := messages.Parse[dockerapi.DockerComposePull](messageString) if err != nil { diff --git a/pkg/common/common.go b/pkg/common/common.go index b3f48b3..4eb5456 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -1,3 +1,3 @@ package common -const Version = "1.5.1" \ No newline at end of file +const Version = "1.5.2" \ No newline at end of file diff --git a/pkg/dockerapi/compose.go b/pkg/dockerapi/compose.go index f8fb702..a20a91e 100644 --- a/pkg/dockerapi/compose.go +++ b/pkg/dockerapi/compose.go @@ -179,9 +179,11 @@ func toEnvFormat(variables map[string]store.VariableValue) ([]string) { return ret } -func processVars(cmd *exec.Cmd, variables map[string]store.VariableValue, ws *websocket.Conn) { +func processVars(cmd *exec.Cmd, variables map[string]store.VariableValue, ws *websocket.Conn, print bool) { cmd.Env = os.Environ() - ws.WriteMessage(websocket.TextMessage, []byte("Setting below variables:\n")) + if print { + ws.WriteMessage(websocket.TextMessage, []byte("*** SETTING BELOW VARIABLES: ***\n\n")) + } keys := make([]string, 0) for k, _ := range variables { @@ -194,17 +196,18 @@ func processVars(cmd *exec.Cmd, variables map[string]store.VariableValue, ws *we if !variables[k].IsSecret { val = *variables[k].Value } - ws.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("%s=%s\n", k, val))) + if print { + ws.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("%s=%s\n", k, val))) + } } + for _, v := range toEnvFormat(variables) { cmd.Env = append(cmd.Env, v) } } -func ComposePull(req *DockerComposePull, ws *websocket.Conn) error { - go discardIncomingMessages(ws) - - dir, file, err := createTempComposeFile(req.ProjectName, req.Definition) +func performComposeAction(action string, projectName string, definition string, variables map[string]store.VariableValue, ws *websocket.Conn, printVars bool) error { + dir, file, err := createTempComposeFile(projectName, definition) log.Debug().Str("fileName", file).Msg("Created temporary compose file") if err != nil { return err @@ -213,11 +216,21 @@ func ComposePull(req *DockerComposePull, ws *websocket.Conn) error { log.Debug().Str("fileName", file).Msg("Deleting temporary compose file") os.RemoveAll(dir) }() + + var cmd *exec.Cmd + switch action { + case "up": + cmd = exec.Command("docker-compose", "-p", projectName, "-f", file, action, "-d") + case "down": + cmd = exec.Command("docker-compose", "-p", projectName, action) + case "pull": + cmd = exec.Command("docker-compose", "-p", projectName, "-f", file, action) + default: + panic(fmt.Errorf("unknown compose action %s", action)) + } + processVars(cmd, variables, ws, printVars) - cmd := exec.Command("docker-compose", "-p", req.ProjectName, "-f", file, "pull") - processVars(cmd, req.Variables, ws) - - ws.WriteMessage(websocket.TextMessage, []byte("\nStarting action: Compose Pull\n")) + ws.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("\n*** STARTING ACTION: %s ***\n\n", action))) f, err := pty.Start(cmd) if err != nil { log.Error().Err(err).Msg("pty returned error") @@ -243,99 +256,43 @@ func ComposePull(req *DockerComposePull, ws *websocket.Conn) error { } err = cmd.Wait() + ws.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("\n*** COMPLETED ACTION: %s ***\n\n", action))) + if err != nil { - log.Error().Err(err).Msg("Error executing compose pull") + log.Error().Err(err).Msg(fmt.Sprintf("Error executing compose %s", action)) } - log.Debug().Msg("compose pull session closed") return nil } -func ComposeUp(req *DockerComposeUp, ws *websocket.Conn) error { +func ComposeDeploy(req *DockerComposeDeploy, ws *websocket.Conn) error { go discardIncomingMessages(ws) - dir, file, err := createTempComposeFile(req.ProjectName, req.Definition) - log.Debug().Str("fileName", file).Msg("Created temporary compose file") - if err != nil { - return err - } - defer func() { - log.Debug().Str("fileName", file).Msg("Deleting temporary compose file") - os.RemoveAll(dir) - }() - - cmd := exec.Command("docker-compose", "-p", req.ProjectName, "-f", file, "up", "-d") - processVars(cmd, req.Variables, ws) - - ws.WriteMessage(websocket.TextMessage, []byte("\nStarting action: Compose Up\n")) - f, err := pty.Start(cmd) + err := performComposeAction("pull", req.ProjectName, req.Definition, req.Variables, ws, true) if err != nil { - log.Error().Err(err).Msg("pty returned error") return err } + err = performComposeAction("up", req.ProjectName, req.Definition, req.Variables, ws, false) - b := make([]byte, 1024) - for { - n, err := f.Read(b) - if n == 0 { - break - } - if err != nil { - if err != io.EOF { - log.Error().Err(err).Msg("Error while reading from pty") - } - break - } - _ = ws.WriteMessage(websocket.BinaryMessage, b[:n]) - // We ignore websocket write errors. This is because - // we don't want to terminate the command execution in between - // causing unexpected state - } + return err +} - err = cmd.Wait() - if err != nil { - log.Error().Err(err).Msg("Error executing compose up") - } +func ComposePull(req *DockerComposePull, ws *websocket.Conn) error { + go discardIncomingMessages(ws) + err := performComposeAction("pull", req.ProjectName, req.Definition, req.Variables, ws, true) + return err +} - log.Debug().Msg("compose up session closed") - return nil +func ComposeUp(req *DockerComposeUp, ws *websocket.Conn) error { + go discardIncomingMessages(ws) + err := performComposeAction("up", req.ProjectName, req.Definition, req.Variables, ws, true) + return err } func ComposeDown(req *DockerComposeDown, ws *websocket.Conn) error { go discardIncomingMessages(ws) - - cmd := exec.Command("docker-compose", "-p", req.ProjectName, "down") - f, err := pty.Start(cmd) - if err != nil { - log.Error().Err(err).Msg("pty returned error") - return err - } - - b := make([]byte, 1024) - for { - n, err := f.Read(b) - if n == 0 { - break - } - if err != nil { - if err != io.EOF { - log.Error().Err(err).Msg("Error while reading from pty") - } - break - } - _ = ws.WriteMessage(websocket.BinaryMessage, b[:n]) - // We ignore websocket write errors. This is because - // we don't want to terminate the command execution in between - // causing unexpected state - } - - err = cmd.Wait() - if err != nil { - log.Error().Err(err).Msg("Error executing compose down") - } - - log.Debug().Msg("compose down session closed") - return nil + err := performComposeAction("down", req.ProjectName, "", nil, ws, true) + return err } func ComposeDownNoStreaming(req *DockerComposeDownNoStreaming) error { diff --git a/pkg/dockerapi/models.go b/pkg/dockerapi/models.go index 1538119..8edbe36 100644 --- a/pkg/dockerapi/models.go +++ b/pkg/dockerapi/models.go @@ -198,6 +198,12 @@ type DockerComposeLogs struct { ProjectName string `json:"projectName"` } +type DockerComposeDeploy struct { + ProjectName string `json:"projectName"` + Definition string `json:"definition"` + Variables map[string]store.VariableValue `json:"variables"` +} + type DockerComposePull struct { ProjectName string `json:"projectName"` Definition string `json:"definition"` diff --git a/pkg/server/handler/handler.go b/pkg/server/handler/handler.go index 24752a1..a947b75 100644 --- a/pkg/server/handler/handler.go +++ b/pkg/server/handler/handler.go @@ -162,6 +162,7 @@ func (h *Handler) Register(e *echo.Echo) { node_compose_project.DELETE("/:id", h.DeleteNodeComposeProject) node_compose_project.GET("/:id/containers", h.GetNodeComposeContainerList) node_compose_project.GET("/:id/logs", h.GetNodeComposeLogs) + node_compose_project.GET("/:id/deploy", h.GetNodeComposeDeploy) node_compose_project.GET("/:id/pull", h.GetNodeComposePull) node_compose_project.GET("/:id/up", h.GetNodeComposeUp) node_compose_project.GET("/:id/down", h.GetNodeComposeDown) diff --git a/pkg/server/handler/node_compose.go b/pkg/server/handler/node_compose.go index da143fa..4b7e855 100644 --- a/pkg/server/handler/node_compose.go +++ b/pkg/server/handler/node_compose.go @@ -469,6 +469,62 @@ func (h *Handler) getComposeVariables(environmentId *uint, nodeComposeProjectId return variables } +func (h *Handler) GetNodeComposeDeploy(c echo.Context) error { + nodeId, err := strconv.Atoi(c.Param("nodeId")) + if err != nil { + return unprocessableEntity(c, errors.New("nodeId should be an integer")) + } + + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + return unprocessableEntity(c, errors.New("id should be an integer")) + } + + ncp, err := h.nodeComposeProjectStore.GetById(uint(nodeId), uint(id)) + if err != nil { + return unprocessableEntity(c, errors.New("Project not found")) + } + + definition, err := h.getComposeProjectDefinition(ncp) + if err != nil { + return unprocessableEntity(c, err) + } + + environmentId := ncp.EnvironmentId + if ncp.EnvironmentId == nil { + node, err := h.nodeStore.GetById(uint(nodeId)) + if err != nil { + return unprocessableEntity(c, errors.New("Node not found")) + } + + environmentId = node.EnvironmentId + } + + variables := h.getComposeVariables(environmentId, uint(id)) + + ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) + if err != nil { + log.Error().Err(err).Msg("Error while upgrading from http to websocket") + return err + } + defer ws.Close() + + req := dockerapi.DockerComposeDeploy{ProjectName: ncp.ProjectName, Definition: definition, Variables: variables} + if nodeId == 1 { + err := dockerapi.ComposeDeploy(&req, ws) + if err != nil { + log.Debug().Err(err).Msg("Error while calling ComposeDeploy") + } + } else { + err = messages.ProcessStreamTask[dockerapi.DockerComposeDeploy](uint(nodeId), req, ws) + if err != nil { + log.Debug().Err(err).Msg("Error while calling ComposeDeploy ProcessStreamTask") + } + } + + return nil +} + func (h *Handler) GetNodeComposePull(c echo.Context) error { nodeId, err := strconv.Atoi(c.Param("nodeId")) if err != nil { diff --git a/runserver.sh b/runserver.sh index 318450a..7812587 100644 --- a/runserver.sh +++ b/runserver.sh @@ -11,4 +11,5 @@ go build ./cmd/server export DB_CONNECTION_STRING="/tmp/db" export DATA_PATH="/tmp" export LOG_LEVEL="DEBUG" +export SSL_ENABLED="0" ./server diff --git a/web/src/app/compose/compose-actions.tsx b/web/src/app/compose/compose-actions.tsx deleted file mode 100644 index c3ea9d3..0000000 --- a/web/src/app/compose/compose-actions.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import { - Breadcrumb, - BreadcrumbCurrent, - BreadcrumbLink, - BreadcrumbSeparator, -} from "@/components/widgets/breadcrumb" -import MainArea from "@/components/widgets/main-area" -import TopBar from "@/components/widgets/top-bar" -import TopBarActions from "@/components/widgets/top-bar-actions" -import MainContent from "@/components/widgets/main-content" -import { useNavigate, useParams } from "react-router-dom" -import { useEffect, useState } from "react" -import apiBaseUrl, { wsApiBaseUrl } from "@/lib/api-base-url" -import { AttachAddon } from "@xterm/addon-attach" -import { FitAddon } from "@xterm/addon-fit" -import { Button } from "@/components/ui/button" -import { - newTerminal, - recreateTerminalElement, - toastFailed, - toastSuccess, -} from "@/lib/utils" -import useNodeHead from "@/hooks/useNodeHead" -import useNodeComposeItem from "@/hooks/useNodeComposeItem" -import useComposeLibraryItemList from "@/hooks/useComposeLibraryItemList" -import DeleteDialog from "@/components/delete-dialog" - -export default function ComposeActions() { - const { nodeId, composeProjectId } = useParams() - const { nodeHead } = useNodeHead(nodeId!) - const { nodeComposeItem, mutateNodeComposeItem } = useNodeComposeItem( - nodeId!, - composeProjectId! - ) - const { mutateComposeLibraryItemList } = useComposeLibraryItemList() - const navigate = useNavigate() - - let terminal = newTerminal() - let fitAddon = new FitAddon() - terminal.loadAddon(fitAddon) - - useEffect(() => { - const el = document.getElementById("terminal") - if (el) { - terminal.open(el) - fitAddon.fit() - addEventListener("resize", () => { - fitAddon?.fit() - }) - } - }, []) - - function handleAction(action: string) { - const url = `${wsApiBaseUrl()}/nodes/${nodeId}/compose/${composeProjectId}/${action}` - const socket = new WebSocket(url) - - socket.onclose = function () { - mutateNodeComposeItem() - } - - terminal = newTerminal() - fitAddon = new FitAddon() - terminal.loadAddon(fitAddon) - terminal.loadAddon(new AttachAddon(socket)) - - const terminalEl = recreateTerminalElement("terminalContainer", "terminal") - terminal.open(terminalEl!) - fitAddon.fit() - addEventListener("resize", () => { - fitAddon?.fit() - }) - } - - const [deleteInProgress, setDeleteInProgress] = useState(false) - const handleDelete = async () => { - setDeleteInProgress(true) - const response = await fetch( - `${apiBaseUrl()}/nodes/${nodeId}/compose/${composeProjectId}`, - { - method: "DELETE", - headers: { "Content-Type": "application/json" }, - } - ) - if (!response.ok) { - const r = await response.json() - toastFailed(r.errors?.body) - } else { - mutateComposeLibraryItemList() - setTimeout(() => { - toastSuccess("Compose project deleted.") - navigate(`/nodes/${nodeId}/compose`) - }, 500) - } - setDeleteInProgress(false) - } - - return ( - - - - Nodes - - {nodeHead?.name} - - - Compose - - - - {nodeComposeItem?.projectName}{" "} - {nodeComposeItem?.status?.startsWith("running") - ? "(Running)" - : "(Not running)"} - - - Actions - - - - -
- - - - -
-
-

Action Logs

-
-
-
-
- ) -} diff --git a/web/src/app/compose/compose-definition-github.tsx b/web/src/app/compose/compose-definition-github.tsx index 6c7fa16..95cc95c 100644 --- a/web/src/app/compose/compose-definition-github.tsx +++ b/web/src/app/compose/compose-definition-github.tsx @@ -59,8 +59,25 @@ import useNodeHead from "@/hooks/useNodeHead" import GitHubPATAddDialog from "@/app/credentials/github-pat-add-dialog" import useNodeComposeItem from "@/hooks/useNodeComposeItem" import ComposeVariableEditor from "./variable-editor/compose-variable-editor" +import DeleteDialog from "@/components/delete-dialog" -export default function ComposeDefinitionGitHub() { +export default function ComposeDefinitionGitHub({ + deleteHandler, + deleteProcessingStatus, + composeActionHandler, + logsOpen, + setLogsOpen, + editing, + setEditing, +}: { + deleteHandler: React.MouseEventHandler + deleteProcessingStatus: boolean + composeActionHandler: (action: string) => void + logsOpen: boolean + setLogsOpen: React.Dispatch> + editing: boolean + setEditing: React.Dispatch> +}) { const { nodeId } = useParams() const { nodeHead } = useNodeHead(nodeId!) const { composeProjectId } = useParams() @@ -120,18 +137,32 @@ export default function ComposeDefinitionGitHub() { } else { mutateNodeComposeItem() toastSuccess("Project has been saved.") + setEditing(false) } setIsSaving(false) } useEffect(() => { + resetForm() + }, [nodeComposeItem]) + + function resetForm() { form.reset({ credentialId: nodeComposeItem?.credentialId, projectName: nodeComposeItem?.projectName, url: nodeComposeItem?.url, }) handleLoadFileContent() - }, [nodeComposeItem]) + } + + function startEdit() { + setEditing(true) + } + + function cancelEdit() { + resetForm() + setEditing(false) + } const editorRef = useRef() @@ -186,16 +217,86 @@ export default function ComposeDefinitionGitHub() { -
- -
- +
+ + + + + + + + + +
+
@@ -217,9 +318,11 @@ export default function ComposeDefinitionGitHub() { GitHub URL of Compose File - + @@ -274,10 +380,11 @@ export default function ComposeDefinitionGitHub() {
+
+

Action Logs

+
+
) diff --git a/web/src/app/compose/compose-definition-local.tsx b/web/src/app/compose/compose-definition-local.tsx index d62795b..c4a3633 100644 --- a/web/src/app/compose/compose-definition-local.tsx +++ b/web/src/app/compose/compose-definition-local.tsx @@ -44,8 +44,25 @@ import { useTheme } from "@/components/ui/theme-provider" import useNodeHead from "@/hooks/useNodeHead" import useNodeComposeItem from "@/hooks/useNodeComposeItem" import ComposeVariableEditor from "./variable-editor/compose-variable-editor" +import DeleteDialog from "@/components/delete-dialog" -export default function ComposeDefinitionLocal() { +export default function ComposeDefinitionLocal({ + deleteHandler, + deleteProcessingStatus, + composeActionHandler, + logsOpen, + setLogsOpen, + editing, + setEditing, +}: { + deleteHandler: React.MouseEventHandler + deleteProcessingStatus: boolean + composeActionHandler: (action: string) => void + logsOpen: boolean + setLogsOpen: React.Dispatch> + editing: boolean + setEditing: React.Dispatch> +}) { const { nodeId } = useParams() const { nodeHead } = useNodeHead(nodeId!) const { composeProjectId } = useParams() @@ -113,11 +130,21 @@ export default function ComposeDefinitionLocal() { } else { mutateNodeComposeItem() toastSuccess("Project has been saved.") + setEditing(false) } setIsSaving(false) } useEffect(() => { + resetForm() + }, [nodeComposeItem, editorMounted]) + + const handleEditorDidMount: OnMount = (editor, _monaco) => { + editorRef.current = editor + setEditorMounted(editorMounted + 1) + } + + function resetForm() { form.reset({ projectName: nodeComposeItem?.projectName, definition: nodeComposeItem?.definition, @@ -125,11 +152,15 @@ export default function ComposeDefinitionLocal() { if (nodeComposeItem?.definition && editorRef.current) { editorRef.current.setValue(nodeComposeItem.definition) } - }, [nodeComposeItem, editorMounted]) + } - const handleEditorDidMount: OnMount = (editor, _monaco) => { - editorRef.current = editor - setEditorMounted(editorMounted + 1) + function startEdit() { + setEditing(true) + } + + function cancelEdit() { + resetForm() + setEditing(false) } return ( @@ -150,16 +181,86 @@ export default function ComposeDefinitionLocal() { -
- -
- +
+ + + + + + + + + +
+
@@ -181,9 +282,11 @@ export default function ComposeDefinitionLocal() { @@ -238,6 +341,10 @@ export default function ComposeDefinitionLocal() {
+
+

Action Logs

+
+
) diff --git a/web/src/app/compose/compose-definition.tsx b/web/src/app/compose/compose-definition.tsx index 16af018..f2984d6 100644 --- a/web/src/app/compose/compose-definition.tsx +++ b/web/src/app/compose/compose-definition.tsx @@ -1,16 +1,102 @@ -import { useParams } from "react-router-dom" +import { useNavigate, useParams } from "react-router-dom" import useNodeComposeItem from "@/hooks/useNodeComposeItem" import ComposeDefinitionGitHub from "./compose-definition-github" import ComposeDefinitionLocal from "./compose-definition-local" +import apiBaseUrl, { wsApiBaseUrl } from "@/lib/api-base-url" +import { + newTerminal, + recreateTerminalElement, + toastFailed, + toastSuccess, +} from "@/lib/utils" +import { FitAddon } from "@xterm/addon-fit" +import { AttachAddon } from "@xterm/addon-attach" +import { useState } from "react" export default function ComposeDefinition() { const { nodeId } = useParams() const { composeProjectId } = useParams() - const { nodeComposeItem } = useNodeComposeItem(nodeId!, composeProjectId!) + const { nodeComposeItem, mutateNodeComposeItem } = useNodeComposeItem( + nodeId!, + composeProjectId! + ) + const navigate = useNavigate() + const [logsOpen, setLogsOpen] = useState(false) + const [editing, setEditing] = useState(false) + + let terminal = newTerminal() + let fitAddon = new FitAddon() + terminal.loadAddon(fitAddon) + + function handleComposeAction(action: string) { + setLogsOpen(true) + + const url = `${wsApiBaseUrl()}/nodes/${nodeId}/compose/${composeProjectId}/${action}` + const socket = new WebSocket(url) + + socket.onclose = function () { + mutateNodeComposeItem() + } + + terminal = newTerminal() + fitAddon = new FitAddon() + terminal.loadAddon(fitAddon) + terminal.loadAddon(new AttachAddon(socket)) + + const terminalEl = recreateTerminalElement("terminalContainer", "terminal") + terminal.open(terminalEl!) + fitAddon.fit() + addEventListener("resize", () => { + fitAddon?.fit() + }) + } + + const [deleteInProgress, setDeleteInProgress] = useState(false) + const handleDelete = async () => { + setDeleteInProgress(true) + const response = await fetch( + `${apiBaseUrl()}/nodes/${nodeId}/compose/${composeProjectId}`, + { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + } + ) + if (!response.ok) { + const r = await response.json() + toastFailed(r.errors?.body) + } else { + mutateNodeComposeItem() + setTimeout(() => { + toastSuccess("Compose project deleted.") + navigate(`/nodes/${nodeId}/compose`) + }, 500) + } + setDeleteInProgress(false) + } if (nodeComposeItem?.type === "github") { - return + return ( + + ) } else { - return + return ( + + ) } } diff --git a/web/src/app/compose/compose-list.tsx b/web/src/app/compose/compose-list.tsx index 0189ef9..c937471 100644 --- a/web/src/app/compose/compose-list.tsx +++ b/web/src/app/compose/compose-list.tsx @@ -83,7 +83,7 @@ export default function ComposeList() { key={item.projectName} className={CLASSES_CLICKABLE_TABLE_ROW} onClick={() => { - navigate(`/nodes/${nodeId}/compose/${item.id}/actions`) + navigate(`/nodes/${nodeId}/compose/${item.id}/definition`) }} > {item.projectName} diff --git a/web/src/app/credentials/github-pat-add-dialog.tsx b/web/src/app/credentials/github-pat-add-dialog.tsx index bba9ff1..d41ee4e 100644 --- a/web/src/app/credentials/github-pat-add-dialog.tsx +++ b/web/src/app/credentials/github-pat-add-dialog.tsx @@ -35,8 +35,10 @@ import SpinnerIcon from "@/components/widgets/spinner-icon" export default function GitHubPATAddDialog({ buttonCaption, + disabled, }: { buttonCaption: string + disabled?: boolean }) { const [open, setOpen] = useState(false) const [isSaving, setIsSaving] = useState(false) @@ -103,7 +105,9 @@ export default function GitHubPATAddDialog({ return ( - + diff --git a/web/src/components/delete-dialog.tsx b/web/src/components/delete-dialog.tsx index 04b53ca..0d391e8 100644 --- a/web/src/components/delete-dialog.tsx +++ b/web/src/components/delete-dialog.tsx @@ -20,6 +20,7 @@ export default function DeleteDialog({ deleteHandler, isProcessing, widthClass, + buttonVisible, }: { openState?: boolean setOpenState?: React.Dispatch> @@ -29,6 +30,7 @@ export default function DeleteDialog({ deleteHandler: React.MouseEventHandler isProcessing: boolean widthClass?: string + buttonVisible?: boolean }) { const [open, setOpen] = useState(false) @@ -41,7 +43,11 @@ export default function DeleteDialog({ diff --git a/web/src/components/side-nav/side-nav-compose.tsx b/web/src/components/side-nav/side-nav-compose.tsx index acd29e9..6e9bc8f 100644 --- a/web/src/components/side-nav/side-nav-compose.tsx +++ b/web/src/components/side-nav/side-nav-compose.tsx @@ -11,13 +11,6 @@ export function SideNavCompose() { const baseUrl = `/nodes/${nodeId}/compose/${composeProjectId}` const items = [ - { - title: "Actions", - link: `${baseUrl}/actions`, - icon: ( -