mirror of
https://github.com/moghtech/komodo.git
synced 2026-05-06 17:35:21 -05:00
implement build / deployment copy
This commit is contained in:
@@ -5,7 +5,7 @@ use mungos::{doc, to_bson};
|
||||
use types::{
|
||||
monitor_timestamp,
|
||||
traits::{Busy, Permissioned},
|
||||
Build, Log, Operation, PermissionLevel, Update, UpdateStatus, UpdateTarget,
|
||||
Build, Log, Operation, PermissionLevel, Update, UpdateStatus, UpdateTarget, Version,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -104,6 +104,7 @@ impl State {
|
||||
.await?;
|
||||
build.name = new_name;
|
||||
build.server_id = new_server_id;
|
||||
build.version = Version::default();
|
||||
let build = self.create_full_build(build, user).await?;
|
||||
Ok(build)
|
||||
}
|
||||
|
||||
12
frontend/public/icons/duplicate.svg
Normal file
12
frontend/public/icons/duplicate.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Rounded_Rectangle_2_1_" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px" y="0px" viewBox="0 0 20 20" enable-background="new 0 0 20 20" xml:space="preserve">
|
||||
<g id="Rounded_Rectangle_2">
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#fceade" d="M15,4H1C0.45,4,0,4.45,0,5v14c0,0.55,0.45,1,1,1h14c0.55,0,1-0.45,1-1V5
|
||||
C16,4.45,15.55,4,15,4z M14,18H2V6h12V18z M19,0H5C4.45,0,4,0.45,4,1v2h2V2h12v12h-1v2h2c0.55,0,1-0.45,1-1V1
|
||||
C20,0.45,19.55,0,19,0z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 679 B |
97
frontend/src/components/CopyMenu.tsx
Normal file
97
frontend/src/components/CopyMenu.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { Component, createSignal } from "solid-js";
|
||||
import { client, pushNotification } from "..";
|
||||
import { useAppState } from "../state/StateProvider";
|
||||
import { Build, Deployment } from "../types";
|
||||
import { getId } from "../util/helpers";
|
||||
import { useToggle } from "../util/hooks";
|
||||
import ConfirmButton from "./shared/ConfirmButton";
|
||||
import Icon from "./shared/Icon";
|
||||
import Input from "./shared/Input";
|
||||
import Flex from "./shared/layout/Flex";
|
||||
import Grid from "./shared/layout/Grid";
|
||||
import CenterMenu from "./shared/menu/CenterMenu";
|
||||
import Selector from "./shared/menu/Selector";
|
||||
|
||||
const CopyMenu: Component<{
|
||||
type: "deployment" | "build";
|
||||
id: string;
|
||||
}> = (p) => {
|
||||
const navigate = useNavigate();
|
||||
const [show, toggleShow] = useToggle();
|
||||
const [newName, setNewName] = createSignal("");
|
||||
const { builds, deployments, servers } = useAppState();
|
||||
const [selectedId, setSelected] = createSignal(servers.ids()![0]);
|
||||
const name = () => {
|
||||
if (p.type === "build") {
|
||||
return builds.get(p.id)?.name;
|
||||
} else if (p.type === "deployment") {
|
||||
return deployments.get(p.id)?.deployment.name;
|
||||
}
|
||||
};
|
||||
const copy = () => {
|
||||
if (newName().length !== 0) {
|
||||
let promise: Promise<Build | Deployment>;
|
||||
if (p.type === "build") {
|
||||
promise = client.copy_build(p.id, {
|
||||
name: newName(),
|
||||
server_id: selectedId(),
|
||||
});
|
||||
} else {
|
||||
promise = client.copy_deployment(p.id, {
|
||||
name: newName(),
|
||||
server_id: selectedId(),
|
||||
});
|
||||
}
|
||||
toggleShow();
|
||||
promise.then((val) => {
|
||||
navigate(`/${p.type}/${getId(val)}`);
|
||||
});
|
||||
} else {
|
||||
pushNotification("bad", "copy name cannot be empty");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<CenterMenu
|
||||
show={show}
|
||||
toggleShow={toggleShow}
|
||||
title={`copy ${p.type} | ${name()}`}
|
||||
target={<Icon type="duplicate" />}
|
||||
targetClass="blue"
|
||||
content={() => (
|
||||
<Grid placeItems="center">
|
||||
<Flex alignItems="center">
|
||||
<Input
|
||||
placeholder="copy name"
|
||||
class="card dark"
|
||||
style={{ padding: "0.5rem" }}
|
||||
value={newName()}
|
||||
onEdit={setNewName}
|
||||
/>
|
||||
<Selector
|
||||
label="target: "
|
||||
selected={selectedId()}
|
||||
items={servers.ids()!}
|
||||
onSelect={setSelected}
|
||||
itemMap={(id) => servers.get(id)!.server.name}
|
||||
targetClass="blue"
|
||||
targetStyle={{ display: "flex", gap: "0.5rem" }}
|
||||
searchStyle={{ width: "12rem" }}
|
||||
position="bottom right"
|
||||
useSearch
|
||||
/>
|
||||
</Flex>
|
||||
<ConfirmButton
|
||||
class="green"
|
||||
onConfirm={copy}
|
||||
>
|
||||
copy {p.type}
|
||||
</ConfirmButton>
|
||||
</Grid>
|
||||
)}
|
||||
position="center"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyMenu;
|
||||
@@ -13,6 +13,7 @@ import { A, useParams } from "@solidjs/router";
|
||||
import { PermissionLevel } from "../../types";
|
||||
import { client } from "../..";
|
||||
import HoverMenu from "../shared/menu/HoverMenu";
|
||||
import CopyMenu from "../CopyMenu";
|
||||
|
||||
const Header: Component<{}> = (p) => {
|
||||
const { builds, servers } = useAppState();
|
||||
@@ -43,21 +44,24 @@ const Header: Component<{}> = (p) => {
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h1>{build().name}</h1>
|
||||
<Show when={userCanUpdate()}>
|
||||
<HoverMenu
|
||||
target={
|
||||
<ConfirmButton
|
||||
onConfirm={() => {
|
||||
client.delete_build(params.id);
|
||||
}}
|
||||
class="red"
|
||||
>
|
||||
<Icon type="trash" />
|
||||
</ConfirmButton>
|
||||
}
|
||||
content="delete build"
|
||||
position="bottom center"
|
||||
padding="0.5rem"
|
||||
/>
|
||||
<Flex alignItems="center">
|
||||
<CopyMenu type="build" id={params.id} />
|
||||
<HoverMenu
|
||||
target={
|
||||
<ConfirmButton
|
||||
onConfirm={() => {
|
||||
client.delete_build(params.id);
|
||||
}}
|
||||
class="red"
|
||||
>
|
||||
<Icon type="trash" />
|
||||
</ConfirmButton>
|
||||
}
|
||||
content="delete build"
|
||||
position="bottom center"
|
||||
padding="0.5rem"
|
||||
/>
|
||||
</Flex>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
|
||||
@@ -17,6 +17,7 @@ import Updates from "./Updates";
|
||||
import { DockerContainerState, PermissionLevel } from "../../types";
|
||||
import { A, useParams } from "@solidjs/router";
|
||||
import { client } from "../..";
|
||||
import CopyMenu from "../CopyMenu";
|
||||
|
||||
const Header: Component<{}> = (p) => {
|
||||
const { deployments, servers } = useAppState();
|
||||
@@ -53,21 +54,24 @@ const Header: Component<{}> = (p) => {
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<h1>{deployment()!.deployment.name}</h1>
|
||||
<Show when={userCanUpdate()}>
|
||||
<HoverMenu
|
||||
target={
|
||||
<ConfirmButton
|
||||
onConfirm={() => {
|
||||
client.delete_deployment(params.id);
|
||||
}}
|
||||
class="red"
|
||||
>
|
||||
<Icon type="trash" />
|
||||
</ConfirmButton>
|
||||
}
|
||||
content="delete deployment"
|
||||
position="bottom center"
|
||||
padding="0.5rem"
|
||||
/>
|
||||
<Flex alignItems="center">
|
||||
<CopyMenu type="deployment" id={params.id} />
|
||||
<HoverMenu
|
||||
target={
|
||||
<ConfirmButton
|
||||
onConfirm={() => {
|
||||
client.delete_deployment(params.id);
|
||||
}}
|
||||
class="red"
|
||||
>
|
||||
<Icon type="trash" />
|
||||
</ConfirmButton>
|
||||
}
|
||||
content="delete deployment"
|
||||
position="bottom center"
|
||||
padding="0.5rem"
|
||||
/>
|
||||
</Flex>
|
||||
</Show>
|
||||
</Flex>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
|
||||
@@ -45,7 +45,8 @@ export type IconType =
|
||||
| "cog"
|
||||
| "home"
|
||||
| "timeline-line-chart"
|
||||
| "arrow-right";
|
||||
| "arrow-right"
|
||||
| "duplicate";
|
||||
|
||||
const ICON_DIR = import.meta.env.VITE_ICON_DIR || "/assets/icons"
|
||||
|
||||
|
||||
@@ -33,10 +33,13 @@ const Selector: Component<{
|
||||
itemClass?: string;
|
||||
itemStyle?: JSX.CSSProperties;
|
||||
label?: JSXElement;
|
||||
itemMap?: (item: string) => string;
|
||||
}> = (p) => {
|
||||
const [show, toggle] = useToggle();
|
||||
const [search, setSearch] = createSignal("");
|
||||
let ref: HTMLInputElement | undefined;
|
||||
const current = () =>
|
||||
p.itemMap ? p.itemMap(p.selected) : p.selected;
|
||||
createEffect(() => {
|
||||
if (show()) setTimeout(() => ref?.focus(), 200);
|
||||
});
|
||||
@@ -46,7 +49,7 @@ const Selector: Component<{
|
||||
fallback={
|
||||
<div class={p.disabledClass} style={p.disabledStyle}>
|
||||
<Show when={p.label}>{p.label}</Show>
|
||||
{p.selected}
|
||||
{current()}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@@ -60,7 +63,7 @@ const Selector: Component<{
|
||||
target={
|
||||
<button class={p.targetClass} onClick={toggle} style={p.targetStyle}>
|
||||
<Show when={p.label}>{p.label}</Show>
|
||||
{p.selected}
|
||||
{current()}
|
||||
<Icon type="chevron-down" />
|
||||
</button>
|
||||
}
|
||||
@@ -101,7 +104,7 @@ const Selector: Component<{
|
||||
}}
|
||||
class={combineClasses(p.itemClass, s.SelectorItem)}
|
||||
>
|
||||
{item}
|
||||
{p.itemMap ? p.itemMap(item) : item}
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
|
||||
Reference in New Issue
Block a user