Files
komodo/frontend/src/components/resources/repo/index.tsx
Maxwell Becker 00968b6ea1 1.16.12 (#209)
* inc version

* Komodo interp in ui compose file

* fix auto update when image doesn't specify tag by defaulting to latest

* Pull image buttons don't need safety dialog

* WIP crosscompile

* rename

* entrypoint

* fix copy

* remove example/* from workspace

* add targets

* multiarch pkg config

* use specific COPY

* update deps

* multiarch build command

* pre compile deps

* cross compile

* enable-linger

* remove spammed log when server doesn't have docker

* add multiarch.Dockerfile

* fix casing

* fix tag

* try not let COPY fail

* try

* ARG TARGETPLATFORM

* use /app for consistency

* try

* delete cross-compile approach

* add multiarch core build

* multiarch Deno

* single arch multi arch

* typeshare cli note

* new typeshare

* remove note about aarch64 image

* test configs

* fix config file headers

* binaries dockerfile

* update cargo build

* docs

* simple

* just simple

* use -p

* add configurable binaries tag

* add multi-arch

* allow copy to fail

* fix binary paths

* frontend Dockerfiel

* use dedicated static frontend build

* auto retry getting instance state from aws

* retry 5 times

* cleanup

* simplify binary build

* try alpine and musl

* install alpine deps

* back to debian, try rustls

* move fully to rustls

* single arch builds using single binary image

* default IMAGE_TAG

* cleanup

* try caching deps

* single arch add frontend build

* rustls::crypto::ring::default_provider()

* back to simple

* comment dockerfile

* add select options prop, render checkboxes if present

* add allowSelectedIf to enable / disable rows where necessary

* rename allowSelectIf to isSelectable, allow false as global disable, disable checkboxes when not allowed

* rename isSelectable to disableRow (it works the oppsite way lol)

* selected resources hook, start deployment batch execute component

* add deployment group actions

* add deployment group actions

* add default (empty) group actions for other resources

* fix checkbox header styles

* explicitly check if disableRow is passed (this prop is cursed)

* don't disable row selection for deployments table

* don't need id for groupactions

* add group actions to resources page

* fix row checkbox (prop not cursed, i dumb)

* re-implement group action list using dropdown menu

* only make group actions clickable when at least one row selected

* add loading indicator

* gap betwen new resource and group actions

* refactor group actions

* remove "Batch" from action labels

* add group actions for relevant resources

* fix hardcode

* add selectOptions to relevant tables

* select by name not id

* expect selected to be names

* add note re selection state init for future reference

* multi select working nicely for all resources

* configure server health check timeout

* config message

* refresh processes remove dead processes

* simplify the build args

* default timeout seconds 3

---------

Co-authored-by: kv <karamvir.singh98@gmail.com>
2024-12-01 23:34:07 -08:00

269 lines
8.1 KiB
TypeScript

import { useInvalidate, useRead, useWrite } from "@lib/hooks";
import { RequiredResourceComponents } from "@types";
import { Card } from "@ui/card";
import { FolderGit, GitBranch, Loader2, RefreshCcw } from "lucide-react";
import { RepoConfig } from "./config";
import { BuildRepo, CloneRepo, PullRepo } from "./actions";
import { DeleteResource, NewResource, ResourceLink } from "../common";
import { RepoTable } from "./table";
import {
repo_state_intention,
stroke_color_class_by_intention,
} from "@lib/color";
import { cn } from "@lib/utils";
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@ui/hover-card";
import { useServer } from "../server";
import { Types } from "komodo_client";
import { DashboardPieChart } from "@pages/home/dashboard";
import { ResourcePageHeader, StatusBadge } from "@components/util";
import { Badge } from "@ui/badge";
import { useToast } from "@ui/use-toast";
import { Button } from "@ui/button";
import { useBuilder } from "../builder";
import { RenameResource } from "@components/config/util";
import { GroupActions } from "@components/group-actions";
export const useRepo = (id?: string) =>
useRead("ListRepos", {}, { refetchInterval: 10_000 }).data?.find(
(d) => d.id === id
);
export const useFullRepo = (id: string) =>
useRead("GetRepo", { repo: id }, { refetchInterval: 10_000 }).data;
const RepoIcon = ({ id, size }: { id?: string; size: number }) => {
const state = useRepo(id)?.info.state;
const color = stroke_color_class_by_intention(repo_state_intention(state));
return <GitBranch className={cn(`w-${size} h-${size}`, state && color)} />;
};
export const RepoComponents: RequiredResourceComponents = {
list_item: (id) => useRepo(id),
resource_links: (resource) => (resource.config as Types.RepoConfig).links,
Description: () => <>Build using custom scripts. Or anything else.</>,
Dashboard: () => {
const summary = useRead("GetReposSummary", {}).data;
return (
<DashboardPieChart
data={[
{ intention: "Good", value: summary?.ok ?? 0, title: "Ok" },
{
intention: "Warning",
value: (summary?.cloning ?? 0) + (summary?.pulling ?? 0),
title: "Pulling",
},
{
intention: "Critical",
value: summary?.failed ?? 0,
title: "Failed",
},
{
intention: "Unknown",
value: summary?.unknown ?? 0,
title: "Unknown",
},
]}
/>
);
},
New: ({ server_id }) => <NewResource type="Repo" server_id={server_id} />,
GroupActions: () => (
<GroupActions
type="Repo"
actions={["PullRepo", "CloneRepo", "BuildRepo"]}
/>
),
Table: ({ resources }) => (
<RepoTable repos={resources as Types.RepoListItem[]} />
),
Icon: ({ id }) => <RepoIcon id={id} size={4} />,
BigIcon: ({ id }) => <RepoIcon id={id} size={8} />,
State: ({ id }) => {
const state = useRepo(id)?.info.state;
return <StatusBadge text={state} intent={repo_state_intention(state)} />;
},
Status: {
Cloned: ({ id }) => {
const info = useRepo(id)?.info;
if (!info?.cloned_hash || info.cloned_hash === info.latest_hash) {
return null;
}
return (
<HoverCard openDelay={200}>
<HoverCardTrigger asChild>
<Card className="px-3 py-2 hover:bg-accent/50 transition-colors cursor-pointer">
<div className="text-muted-foreground text-sm text-nowrap overflow-hidden overflow-ellipsis">
cloned: {info.cloned_hash}
</div>
</Card>
</HoverCardTrigger>
<HoverCardContent align="start">
<div className="grid">
<div className="text-muted-foreground">commit message:</div>
{info.cloned_message}
</div>
</HoverCardContent>
</HoverCard>
);
},
Built: ({ id }) => {
const info = useRepo(id)?.info;
const fullInfo = useFullRepo(id)?.info;
if (!info?.built_hash || info.built_hash === info.latest_hash) {
return null;
}
return (
<HoverCard openDelay={200}>
<HoverCardTrigger asChild>
<Card className="px-3 py-2 hover:bg-accent/50 transition-colors cursor-pointer">
<div className="text-muted-foreground text-sm text-nowrap overflow-hidden overflow-ellipsis">
built: {info.built_hash}
</div>
</Card>
</HoverCardTrigger>
<HoverCardContent align="start">
<div className="grid gap-2">
<Badge
variant="secondary"
className="w-fit text-muted-foreground"
>
commit message
</Badge>
{fullInfo?.built_message}
</div>
</HoverCardContent>
</HoverCard>
);
},
Latest: ({ id }) => {
const info = useRepo(id)?.info;
const fullInfo = useFullRepo(id)?.info;
if (!info?.latest_hash) {
return null;
}
return (
<HoverCard openDelay={200}>
<HoverCardTrigger asChild>
<Card className="px-3 py-2 hover:bg-accent/50 transition-colors cursor-pointer">
<div className="text-muted-foreground text-sm text-nowrap overflow-hidden overflow-ellipsis">
latest: {info.latest_hash}
</div>
</Card>
</HoverCardTrigger>
<HoverCardContent align="start">
<div className="grid gap-2">
<Badge
variant="secondary"
className="w-fit text-muted-foreground"
>
commit message
</Badge>
{fullInfo?.latest_message}
</div>
</HoverCardContent>
</HoverCard>
);
},
Refresh: ({ id }) => {
const { toast } = useToast();
const inv = useInvalidate();
const { mutate, isPending } = useWrite("RefreshRepoCache", {
onSuccess: () => {
inv(["ListRepos"], ["GetRepo", { repo: id }]);
toast({ title: "Refreshed repo status cache" });
},
});
return (
<Button
variant="outline"
size="icon"
onClick={() => {
mutate({ repo: id });
toast({ title: "Triggered refresh of repo status cache" });
}}
>
{isPending ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<RefreshCcw className="w-4 h-4" />
)}
</Button>
);
},
},
Info: {
Target: ({ id }) => {
const info = useRepo(id)?.info;
const server = useServer(info?.server_id);
const builder = useBuilder(info?.builder_id);
return (
<div className="flex items-center gap-x-4 gap-y-2 flex-wrap">
{server?.id &&
(builder?.id ? (
<div className="pr-4 text-sm border-r">
<ResourceLink type="Server" id={server.id} />
</div>
) : (
<ResourceLink type="Server" id={server.id} />
))}
{builder?.id && <ResourceLink type="Builder" id={builder.id} />}
</div>
);
},
Repo: ({ id }) => {
const repo = useRepo(id)?.info.repo;
return (
<div className="flex items-center gap-2">
<FolderGit className="w-4 h-4" />
{repo}
</div>
);
},
Branch: ({ id }) => {
const branch = useRepo(id)?.info.branch;
return (
<div className="flex items-center gap-2">
<GitBranch className="w-4 h-4" />
{branch}
</div>
);
},
},
Actions: { BuildRepo, PullRepo, CloneRepo },
Page: {},
Config: RepoConfig,
DangerZone: ({ id }) => (
<>
<RenameResource type="Repo" id={id} />
<DeleteResource type="Repo" id={id} />
</>
),
ResourcePageHeader: ({ id }) => {
const repo = useRepo(id);
return (
<ResourcePageHeader
intent={repo_state_intention(repo?.info.state)}
icon={<RepoIcon id={id} size={8} />}
name={repo?.name}
state={repo?.info.state}
status=""
/>
);
},
};