begin frontend refactor for ephemeral build support

This commit is contained in:
mbecker20
2023-02-21 18:11:43 +00:00
parent e2b5a02008
commit a121ae0828
13 changed files with 209 additions and 37 deletions

View File

@@ -3,8 +3,8 @@ WORKDIR /builder
COPY ./core ./core
# COPY ./lib/types ./lib/types
# COPY ./lib/helpers ./lib/helpers
COPY ./lib/types ./lib/types
COPY ./lib/helpers ./lib/helpers
COPY ./lib/db_client ./lib/db_client
COPY ./lib/periphery_client ./lib/periphery_client

View File

@@ -47,10 +47,10 @@ export const NewDeployment: Component<{ serverID: string }> = (p) => {
);
};
export const NewBuild: Component<{ serverID: string }> = (p) => {
export const NewBuild: Component<{}> = (p) => {
const [showNew, toggleShowNew] = useToggle();
const create = (name: string) => {
client.create_build({ name, server_id: p.serverID });
client.create_build({ name });
};
return (
<Show

View File

@@ -11,31 +11,29 @@ type State = {
const context = createContext<State>();
export const ActionStateProvider: ParentComponent<{}> = (p) => {
export const ActionStateProvider: ParentComponent<{ build_id: string }> = (p) => {
const { ws } = useAppState();
const params = useParams();
const [actions, setActions] = createStore<BuildActionState>({
building: false,
recloning: false,
updating: false,
});
createEffect(() => {
client.get_build_action_state(params.id).then(setActions);
client.get_build_action_state(p.build_id).then(setActions);
});
onCleanup(
ws.subscribe([Operation.BuildBuild], (update) => {
if (update.target.id === params.id) {
if (update.target.id === p.build_id) {
setActions("building", update.status !== UpdateStatus.Complete);
}
})
);
onCleanup(
ws.subscribe([Operation.RecloneBuild], (update) => {
if (update.target.id === params.id) {
setActions("recloning", update.status !== UpdateStatus.Complete);
}
})
);
// onCleanup(
// ws.subscribe([Operation.RecloneBuild], (update) => {
// if (update.target.id === params.id) {
// setActions("recloning", update.status !== UpdateStatus.Complete);
// }
// })
// );
// onCleanup(
// ws.subscribe([DELETE_BUILD], ({ complete, buildID }) => {
// if (buildID === selected.id()) {

View File

@@ -10,14 +10,14 @@ import { useActionStates } from "./ActionStateProvider";
import { client } from "../..";
import { combineClasses, getId } from "../../util/helpers";
import { useParams } from "@solidjs/router";
import { PermissionLevel, ServerStatus } from "../../types";
import { PermissionLevel, ServerStatus, ServerWithStatus } from "../../types";
const Actions: Component<{}> = (p) => {
const { user } = useUser();
const params = useParams() as { id: string };
const { builds, servers } = useAppState();
const build = () => builds.get(params.id)!;
const server = () => build() && servers.get(build()!.server_id);
const server = () => (build() && build().server_id) ? servers.get(build()!.server_id!) : undefined;
const actions = useActionStates();
const userCanExecute = () =>
user().admin ||
@@ -48,7 +48,7 @@ const Actions: Component<{}> = (p) => {
</ConfirmButton>
</Show>
</Flex>
<Flex class={combineClasses("action shadow")}>
{/* <Flex class={combineClasses("action shadow")}>
reclone{" "}
<Show
when={!actions.recloning}
@@ -67,7 +67,7 @@ const Actions: Component<{}> = (p) => {
<Icon type="reset" />
</ConfirmButton>
</Show>
</Flex>
</Flex> */}
</Grid>
</Grid>
</Show>

View File

@@ -35,7 +35,7 @@ const Build: Component<{}> = (p) => {
onCleanup(() => unsub);
return (
<Show when={build()} fallback={<NotFound type="build" />}>
<ActionStateProvider>
<ActionStateProvider build_id={params.id}>
<Grid
style={{
width: "100%",

View File

@@ -6,6 +6,7 @@ import { useAppState } from "../../state/StateProvider";
import Grid from "../shared/layout/Grid";
import SimpleTabs from "../shared/tabs/SimpleTabs";
import Summary from "./Summary";
import Builds from "./Tree/Builds";
import Groups from "./Tree/Groups";
import { TreeProvider } from "./Tree/Provider";
import Servers from "./Tree/Servers";
@@ -36,6 +37,10 @@ const Home: Component<{}> = (p) => {
title: "servers",
element: () => <Servers serverIDs={servers.ids()!} showAdd />,
},
{
title: "builds",
element: () => <Builds />
}
]}
/>
</TreeProvider>

View File

@@ -0,0 +1,146 @@
import { A } from "@solidjs/router";
import { Component, createMemo, createSignal, For, Show } from "solid-js";
import { client } from "../../..";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import { PermissionLevel } from "../../../types";
import { getId, readableMonitorTimestamp } from "../../../util/helpers";
import { ActionStateProvider, useActionStates } from "../../build/ActionStateProvider";
import { NewBuild } from "../../New";
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 Loading from "../../shared/loading/Loading";
import Selector from "../../shared/menu/Selector";
import { TreeSortType, TREE_SORTS, useTreeState } from "./Provider";
const Builds: Component<{}> = (p) => {
const { user } = useUser();
const { builds } = useAppState();
const { sort, setSort, build_sorter } = useTreeState();
const [buildFilter, setBuildFilter] = createSignal("");
const buildIDs = createMemo(() => {
if (builds.loaded()) {
const filters = buildFilter()
.split(" ")
.filter((term) => term.length > 0)
.map((term) => term.toLowerCase());
return builds
.ids()!
.filter((id) => {
const name = builds.get(id)!.name;
for (const term of filters) {
if (!name.includes(term)) {
return false;
}
}
return true;
})
.sort(build_sorter());
} else {
return undefined;
}
});
return (
<Grid>
<Grid gridTemplateColumns="1fr auto auto">
<Input
placeholder="filter builds"
value={buildFilter()}
onEdit={setBuildFilter}
style={{ width: "100%", padding: "0.5rem" }}
/>
<Selector
selected={sort()}
items={TREE_SORTS as any as string[]}
onSelect={(mode) => setSort(mode as TreeSortType)}
position="bottom right"
targetClass="blue"
targetStyle={{ height: "100%" }}
containerStyle={{ height: "100%" }}
/>
<Show when={user().admin || user().create_build_permissions}>
<NewBuild />
</Show>
</Grid>
<For each={buildIDs()}>
{(id) => (
<ActionStateProvider build_id={id}>
<Build id={id} />
</ActionStateProvider>
)}
</For>
</Grid>
);
};
const Build: Component<{ id: string }> = (p) => {
const { user } = useUser();
const { builds } = useAppState();
const build = () => builds.get(p.id)!
const version = () => {
return `v${build().version.major}.${build().version.minor}.${
build().version.patch
}`;
};
const lastBuiltAt = () => {
if (
build().last_built_at === undefined ||
build().last_built_at?.length === 0 ||
build().last_built_at === "never"
) {
return "not built";
} else {
return readableMonitorTimestamp(build().last_built_at!);
}
};
const actions = useActionStates();
const userCanExecute = () =>
user().admin ||
build().permissions![getId(user())] === PermissionLevel.Execute ||
build().permissions![getId(user())] === PermissionLevel.Update;
return (
<A
href={`/build/${p.id}`}
class="card light shadow"
style={{
width: "100%",
height: "fit-content",
"box-sizing": "border-box",
"justify-content": "space-between",
}}
>
<h1 style={{ "font-size": "1.25rem" }}>{build().name}</h1>
<Flex alignItems="center">
<div>{version()}</div>
<div style={{ opacity: 0.7 }}>{lastBuiltAt()}</div>
<Show when={userCanExecute()}>
<Show
when={!actions.building}
fallback={
<button class="green" onClick={e => {
e.stopPropagation();
e.preventDefault();
}}>
<Loading type="spinner" />
</button>
}
>
<ConfirmButton
class="green"
onConfirm={() => {
client.build(p.id);
}}
>
<Icon type="build" width="0.9rem" />
</ConfirmButton>
</Show>
</Show>
</Flex>
</A>
);
};
export default Builds;

View File

@@ -6,7 +6,7 @@ export const TREE_SORTS = ["name", "created"] as const;
export type TreeSortType = typeof TREE_SORTS[number];
const value = () => {
const { servers, groups } = useAppState();
const { servers, groups, builds } = useAppState();
const [sort, setSort] = useLocalStorage<TreeSortType>(
TREE_SORTS[0],
"home-sort-v1"
@@ -29,7 +29,7 @@ const value = () => {
}
};
const group_sorter = () => {
if (!groups.loaded) return () => 0;
if (!groups.loaded()) return () => 0;
if (sort() === "name") {
return (a: string, b: string) => {
const ga = groups.get(a)!;
@@ -44,12 +44,30 @@ const value = () => {
} else {
return () => 0;
}
};
const build_sorter = () => {
if (!builds.loaded()) return () => 0;
if (sort() === "name") {
return (a: string, b: string) => {
const ba = builds.get(a)!;
const bb = builds.get(b)!;
if (ba.name < bb.name) {
return -1;
} else if (ba.name > bb.name) {
return 1;
}
return 0;
};
} else {
return () => 0;
}
};
return {
sort,
setSort,
server_sorter,
group_sorter,
build_sorter
};
}

View File

@@ -17,6 +17,7 @@ const ConfirmButton: Component<{
onBlur={() => set(false)}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
if (confirm()) {
p.onConfirm && p.onConfirm();
} else {

View File

@@ -21,12 +21,12 @@ export interface Build {
_id?: string;
name: string;
permissions?: PermissionsMap;
server_id: string;
server_id?: string;
aws_config?: AwsBuilderConfig;
version: Version;
repo?: string;
branch?: string;
github_account?: string;
on_clone?: Command;
pre_build?: Command;
docker_build_args?: DockerBuildArgs;
docker_account?: string;
@@ -37,7 +37,6 @@ export interface Build {
export interface BuildActionState {
building: boolean;
recloning: boolean;
updating: boolean;
}
@@ -58,6 +57,17 @@ export interface BuildVersionsReponse {
ts: string;
}
export interface AwsBuilderConfig {
region?: string;
instance_type?: string;
ami_id?: string;
volume_gb?: number;
subnet_id?: string;
security_group_ids?: string[];
key_pair_name?: string;
assign_public_ip?: boolean;
}
export interface Deployment {
_id?: string;
name: string;
@@ -327,9 +337,10 @@ export interface Log {
export interface User {
_id?: string;
username: string;
enabled: boolean;
admin: boolean;
create_server_permissions: boolean;
enabled?: boolean;
admin?: boolean;
create_server_permissions?: boolean;
create_build_permissions?: boolean;
avatar?: string;
secrets?: ApiSecret[];
password?: string;
@@ -382,7 +393,6 @@ export enum Operation {
UpdateBuild = "update_build",
DeleteBuild = "delete_build",
BuildBuild = "build_build",
RecloneBuild = "reclone_build",
CreateDeployment = "create_deployment",
UpdateDeployment = "update_deployment",
DeleteDeployment = "delete_deployment",
@@ -449,7 +459,6 @@ export enum ProcedureOperation {
PruneContainersServer = "prune_containers_server",
PruneNetworksServer = "prune_networks_server",
BuildBuild = "build_build",
RecloneBuild = "reclone_build",
DeployContainer = "deploy_container",
StopContainer = "stop_container",
StartContainer = "start_container",

View File

@@ -2,16 +2,12 @@
Generated by typeshare 1.0.0
*/
import { PermissionLevel, PermissionsTarget } from "../types";
export interface CreateBuildBody {
name: string;
server_id: string;
}
export interface CopyBuildBody {
name: string;
server_id: string;
}
export interface BuildVersionsQuery {

View File

@@ -88,7 +88,6 @@ pub struct Build {
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct BuildActionState {
pub building: bool,
pub recloning: bool,
pub updating: bool,
}

View File

@@ -65,7 +65,7 @@ impl Busy for DeploymentActionState {
impl Busy for BuildActionState {
fn busy(&self) -> bool {
self.building || self.recloning || self.updating
self.building || self.updating
}
}