seems to work

This commit is contained in:
mbecker20
2024-06-08 03:35:22 -07:00
parent c73d918e18
commit 914f4c6197
10 changed files with 232 additions and 246 deletions

View File

@@ -54,9 +54,13 @@ export const PendingOrConfig = ({ id }: { id: string }) => {
<Section titleOther={tabsList}>
{pending?.hash && pending.message && (
<Card>
<CardHeader className="flex items-center gap-4 text-muted-foreground">
Commit: {pending.hash}: {pending.message}
</CardHeader>
<div className="flex items-center gap-4 px-8 py-4">
<div className="text-muted-foreground">Latest Commit</div>
<div className="text-muted-foreground">|</div>
<div>{pending.hash}</div>
<div className="text-muted-foreground">|</div>
<div>{pending.message}</div>
</div>
</Card>
)}
{pending?.data.type === "Ok" &&
@@ -103,25 +107,28 @@ const PendingView = ({
<div className="flex gap-4 items-center m-0">
{pending.to_create ? (
<>
|
<div className="text-muted-foreground">|</div>
<div className="flex gap-2 items-center">
To Create: {pending.to_create}
<div className="text-muted-foreground">To Create:</div>
<div>{pending.to_create}</div>
</div>
</>
) : undefined}
{pending.to_update ? (
<>
|
<div className="text-muted-foreground">|</div>
<div className="flex gap-2 items-center">
To Update: {pending.to_update}
<div className="text-muted-foreground">To Update:</div>
<div>{pending.to_update}</div>
</div>
</>
) : undefined}
{pending.to_delete ? (
<>
|
<div className="text-muted-foreground">|</div>
<div className="flex gap-2 items-center">
To Delete: {pending.to_delete}
<div className="text-muted-foreground">To Delete:</div>
<div>{pending.to_delete}</div>
</div>
</>
) : undefined}

View File

@@ -22,6 +22,21 @@ export const ResourceSyncTable = ({
cell: ({ row }) => (
<ResourceLink type="ResourceSync" id={row.original.id} />
),
size: 200,
},
{
accessorKey: "info.repo",
header: ({ column }) => (
<SortableHeader column={column} title="Repo" />
),
size: 200,
},
{
accessorKey: "info.branch",
header: ({ column }) => (
<SortableHeader column={column} title="Branch" />
),
size: 200,
},
{
accessorKey: "info.state",

View File

@@ -7,10 +7,7 @@ import {
Box,
Boxes,
FolderTree,
Key,
Tag,
User,
Variable,
Settings,
} from "lucide-react";
import { Link, useLocation } from "react-router-dom";
import { ResourceComponents } from "./resources";
@@ -18,14 +15,12 @@ import { Separator } from "@ui/separator";
import { ReactNode } from "react";
import { useAtom } from "jotai";
import { homeViewAtom } from "@main";
import { useUser } from "@lib/hooks";
export const Sidebar = () => {
const user = useUser().data;
const [view, setView] = useAtom(homeViewAtom);
return (
<Card className="h-fit m-4 hidden lg:flex">
<CardContent className="h-fit grid gap-[2px] px-6 py-2">
<CardContent className="h-fit grid gap-[4px] px-6 py-2">
<SidebarLink
label="Dashboard"
to="/"
@@ -82,32 +77,12 @@ export const Sidebar = () => {
icon={<Bell className="w-4 h-4" />}
/>
<SidebarLink
label="Variables"
to="/variables"
icon={<Variable className="w-4 h-4" />}
/>
<SidebarLink
label="Tags"
to="/tags"
icon={<Tag className="w-4 h-4" />}
/>
<Separator />
{user?.admin && (
<SidebarLink
label="Users"
to="/users"
icon={<User className="w-4 h-4" />}
/>
)}
<SidebarLink
label="Api Keys"
to="/keys"
icon={<Key className="w-4 h-4" />}
label="Settings"
to="/settings"
icon={<Settings className="w-4 h-4" />}
/>
</CardContent>
</Card>

View File

@@ -1,4 +1,4 @@
import { useRead, useResourceParamType, useUser } from "@lib/hooks";
import { useRead, useResourceParamType } from "@lib/hooks";
import { ResourceComponents } from "./resources";
import {
AlertTriangle,
@@ -8,12 +8,10 @@ import {
FileQuestion,
FolderTree,
Home,
Key,
SearchX,
Tag,
Settings,
User,
Users,
Variable,
} from "lucide-react";
import {
DropdownMenu,
@@ -117,8 +115,6 @@ export const Topbar = () => {
};
const PrimaryDropdown = () => {
const user = useUser().data;
const type = useResourceParamType();
const Components = type && ResourceComponents[type];
@@ -133,20 +129,15 @@ const PrimaryDropdown = () => {
]
: location.pathname === "/"
? [<Home className="w-4 h-4" />, "Home"]
: location.pathname === "/keys"
? [<Key className="w-4 h-4" />, "Api Keys"]
: location.pathname === "/tags"
? [<Tag className="w-4 h-4" />, "Tags"]
: location.pathname === "/variables"
? [<Variable className="w-4 h-4" />, "Variables"]
: location.pathname === "/settings"
? [<Settings className="w-4 h-4" />, "Settings"]
: location.pathname === "/alerts"
? [<AlertTriangle className="w-4 h-4" />, "Alerts"]
: location.pathname === "/updates"
? [<Bell className="w-4 h-4" />, "Updates"]
: location.pathname.split("/")[1] === "user-groups"
? [<Users className="w-4 h-4" />, "User Groups"]
: location.pathname === "/users" ||
location.pathname.split("/")[1] === "users"
: location.pathname.split("/")[1] === "users"
? [<User className="w-4 h-4" />, "Users"]
: [<FileQuestion className="w-4 h-4" />, "Unknown"];
// : [<Box className="w-4 h-4" />, "Dashboard"];
@@ -199,32 +190,12 @@ const PrimaryDropdown = () => {
to="/updates"
/>
<DropdownLinkItem
label="Variables"
icon={<Variable className="w-4 h-4" />}
to="/variables"
/>
<DropdownLinkItem
label="Tags"
icon={<Tag className="w-4 h-4" />}
to="/tags"
/>
<DropdownMenuSeparator />
{user?.admin && (
<DropdownLinkItem
label="Users"
icon={<User className="w-4 h-4" />}
to="/users"
/>
)}
<DropdownLinkItem
label="Api Keys"
icon={<Box className="w-4 h-4" />}
to="/keys"
label="Settings"
icon={<Settings className="w-4 h-4" />}
to="/settings"
/>
</DropdownMenuGroup>
</DropdownMenuContent>

View File

@@ -0,0 +1,57 @@
import { useLocalStorage, useUser } from "@lib/hooks";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs";
import { CreateVariable, Variables } from "./variables";
import { CreateTag, Tags } from "./tags";
import { UsersPage } from "./users";
import { CreateKey, Keys } from "./keys";
import { Page } from "@components/layouts";
import { useState } from "react";
import { Input } from "@ui/input";
export const Settings = () => {
const user = useUser().data;
const [view, setView] = useLocalStorage("settings-view-v0", "Variables");
const [search, setSearch] = useState("");
const currentView = view === "Users" && !user?.admin ? "Variables" : view;
return (
<Page>
<Tabs value={currentView} onValueChange={setView} className="grid gap-6">
<div className="flex items-center justify-between">
<TabsList className="justify-start w-fit">
<TabsTrigger value="Variables">Variables</TabsTrigger>
<TabsTrigger value="Tags">Tags</TabsTrigger>
{user?.admin && <TabsTrigger value="Users">Users</TabsTrigger>}
<TabsTrigger value="Api Keys">Api Keys</TabsTrigger>
</TabsList>
{currentView === "Variables" && <CreateVariable />}
{currentView === "Tags" && <CreateTag />}
{currentView === "Api Keys" && <CreateKey />}
{currentView === "Users" && (
<Input
placeholder="Search"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-[250px]"
/>
)}
</div>
<TabsContent value="Variables">
<Variables />
</TabsContent>
<TabsContent value="Tags">
<Tags />
</TabsContent>
{user?.admin && (
<TabsContent value="Users">
<UsersPage search={search} />
</TabsContent>
)}
<TabsContent value="Api Keys">
<Keys />
</TabsContent>
</Tabs>
</Page>
);
};

View File

@@ -1,4 +1,3 @@
import { Page } from "@components/layouts";
import { ConfirmButton, CopyButton } from "@components/util";
import { useInvalidate, useManageUser, useRead, useSetTitle } from "@lib/hooks";
import {
@@ -11,7 +10,7 @@ import {
} from "@ui/dialog";
import { Button } from "@ui/button";
import { useToast } from "@ui/use-toast";
import { Trash, PlusCircle, Loader2, Check, Key } from "lucide-react";
import { Trash, PlusCircle, Loader2, Check } from "lucide-react";
import { useState } from "react";
import { Input } from "@ui/input";
import {
@@ -26,22 +25,14 @@ import { KeysTable } from "@components/keys/table";
export const Keys = () => {
useSetTitle("Api Keys");
const keys = useRead("ListApiKeys", {}).data ?? [];
return (
<Page
title="Api Keys"
icon={<Key className="w-8 h-8" />}
actions={<CreateKey />}
>
<KeysTable keys={keys} DeleteKey={DeleteKey} />
</Page>
);
return <KeysTable keys={keys} DeleteKey={DeleteKey} />;
};
const ONE_DAY_MS = 1000 * 60 * 60 * 24;
type ExpiresOptions = "90 days" | "180 days" | "1 year" | "never";
const CreateKey = () => {
export const CreateKey = () => {
const [open, setOpen] = useState(false);
const [name, setName] = useState("");
const [expires, setExpires] = useState<ExpiresOptions>("never");

View File

@@ -1,4 +1,3 @@
import { Page } from "@components/layouts";
import { ConfirmButton } from "@components/util";
import { useInvalidate, useRead, useSetTitle, useWrite } from "@lib/hooks";
import {
@@ -12,7 +11,7 @@ import {
import { Button } from "@ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@ui/card";
import { useToast } from "@ui/use-toast";
import { Trash, PlusCircle, Loader2, Check, Tag } from "lucide-react";
import { Trash, PlusCircle, Loader2, Check } from "lucide-react";
import { useState } from "react";
import { Input } from "@ui/input";
import { UpdateUser } from "@components/updates/details";
@@ -26,43 +25,37 @@ export const Tags = () => {
const tags = useRead("ListTags", {}).data;
return (
<Page
title="Tags"
icon={<Tag className="w-8 h-8" />}
actions={<CreateTag />}
>
<div className="flex flex-col gap-4">
<Input
placeholder="search..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-[200px] lg:w-[300px]"
/>
<DataTable
tableKey="tags"
data={tags?.filter((tag) => tag.name.includes(search)) ?? []}
columns={[
{
header: "Name",
accessorKey: "name",
},
{
header: "Owner",
cell: ({ row }) =>
row.original.owner ? (
<UpdateUser user_id={row.original.owner} />
) : (
"Unknown"
),
},
{
header: "Delete",
cell: ({ row }) => <DeleteTag tag_id={row.original._id!.$oid} />,
},
]}
/>
</div>
</Page>
<div className="flex flex-col gap-4">
<Input
placeholder="search..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-[200px] lg:w-[300px]"
/>
<DataTable
tableKey="tags"
data={tags?.filter((tag) => tag.name.includes(search)) ?? []}
columns={[
{
header: "Name",
accessorKey: "name",
},
{
header: "Owner",
cell: ({ row }) =>
row.original.owner ? (
<UpdateUser user_id={row.original.owner} />
) : (
"Unknown"
),
},
{
header: "Delete",
cell: ({ row }) => <DeleteTag tag_id={row.original._id!.$oid} />,
},
]}
/>
</div>
);
};
@@ -92,7 +85,7 @@ export const TagCards = () => {
);
};
const CreateTag = () => {
export const CreateTag = () => {
const { toast } = useToast();
const [open, setOpen] = useState(false);
const [name, setName] = useState("");
@@ -117,7 +110,7 @@ const CreateTag = () => {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className="items-center gap-2">
<Button variant="secondary" className="items-center gap-2">
New Tag <PlusCircle className="w-4 h-4" />
</Button>
</DialogTrigger>

View File

@@ -1,33 +1,19 @@
import { Page, Section } from "@components/layouts";
import { Section } from "@components/layouts";
import { NewServiceUser, NewUserGroup } from "@components/users/new";
import { UserTable } from "@components/users/table";
import { useRead, useSetTitle } from "@lib/hooks";
import { DataTable } from "@ui/data-table";
import { Input } from "@ui/input";
import { User, Users } from "lucide-react";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
export const UsersPage = () => {
export const UsersPage = ({ search }: { search: string }) => {
useSetTitle("Users");
const nav = useNavigate();
const groups = useRead("ListUserGroups", {}).data;
const users = useRead("ListUsers", {}).data;
const [search, setSearch] = useState("");
const searchSplit = search.split(" ");
return (
<Page
title="Users"
icon={<User className="w-8 h-8" />}
actions={
<Input
placeholder="Search"
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-[250px]"
/>
}
>
<div className="flex flex-col gap-6">
{/* User Groups */}
<Section
title="User Groups"
@@ -66,6 +52,6 @@ export const UsersPage = () => {
}
/>
</Section>
</Page>
</div>
);
};

View File

@@ -1,4 +1,3 @@
import { Page } from "@components/layouts";
import { ConfirmButton, TextUpdateMenu } from "@components/util";
import {
useInvalidate,
@@ -20,7 +19,7 @@ import {
} from "@ui/dialog";
import { Input } from "@ui/input";
import { useToast } from "@ui/use-toast";
import { Check, Loader2, PlusCircle, Trash, Variable } from "lucide-react";
import { Check, Loader2, PlusCircle, Trash } from "lucide-react";
import { useState } from "react";
export const Variables = () => {
@@ -51,101 +50,95 @@ export const Variables = () => {
},
});
return (
<Page
title="Variables"
icon={<Variable className="w-8 h-8" />}
actions={<CreateVariable />}
>
<div className="flex flex-col gap-4">
<Input
placeholder="search..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-[200px] lg:w-[300px]"
/>
<div className="flex flex-col gap-4">
<Input
placeholder="search..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-[200px] lg:w-[300px]"
/>
{/** VARIABLES */}
<DataTable
tableKey="variables"
data={filtered}
columns={[
{
accessorKey: "name",
header: ({ column }) => (
<SortableHeader column={column} title="Name" />
),
{/** VARIABLES */}
<DataTable
tableKey="variables"
data={filtered}
columns={[
{
accessorKey: "name",
header: ({ column }) => (
<SortableHeader column={column} title="Name" />
),
},
{
accessorKey: "value",
header: ({ column }) => (
<SortableHeader column={column} title="Value" />
),
cell: ({ row }) => {
return (
<TextUpdateMenu
title={`${row.original.name} - Value`}
placeholder="Set value"
value={row.original.value}
onUpdate={(value) => {
if (row.original.value === value) {
return;
}
updateValue({ name: row.original.name, value });
}}
triggerClassName="w-full"
disabled={disabled}
fullWidth
/>
);
},
{
accessorKey: "value",
header: ({ column }) => (
<SortableHeader column={column} title="Value" />
),
cell: ({ row }) => {
return (
<TextUpdateMenu
title={`${row.original.name} - Value`}
placeholder="Set value"
value={row.original.value}
onUpdate={(value) => {
if (row.original.value === value) {
return;
}
updateValue({ name: row.original.name, value });
}}
triggerClassName="w-full"
disabled={disabled}
fullWidth
/>
);
},
},
{
accessorKey: "description",
header: "Description",
cell: ({ row }) => {
return (
<TextUpdateMenu
title={`${row.original.name} - Description`}
placeholder="Set description"
value={row.original.description}
onUpdate={(description) => {
if (row.original.description === description) {
return;
}
updateDescription({
name: row.original.name,
description,
});
}}
triggerClassName="w-full"
disabled={disabled}
fullWidth
/>
);
},
{
accessorKey: "description",
header: "Description",
cell: ({ row }) => {
return (
<TextUpdateMenu
title={`${row.original.name} - Description`}
placeholder="Set description"
value={row.original.description}
onUpdate={(description) => {
if (row.original.description === description) {
return;
}
updateDescription({
name: row.original.name,
description,
});
}}
triggerClassName="w-full"
disabled={disabled}
fullWidth
/>
);
},
},
{
header: "Delete",
cell: ({ row }) => <DeleteVariable name={row.original.name} />,
},
]}
/>
},
{
header: "Delete",
cell: ({ row }) => <DeleteVariable name={row.original.name} />,
},
]}
/>
{/** SECRETS */}
{secrets.length && (
<div className="flex items-center gap-2 text-muted-foreground">
<div>Core Secrets:</div>
{secrets.map((secret) => (
<Badge variant="secondary">{secret}</Badge>
))}
</div>
)}
</div>
</Page>
{/** SECRETS */}
{secrets.length && (
<div className="flex items-center gap-2 text-muted-foreground">
<div>Core Secrets:</div>
{secrets.map((secret) => (
<Badge variant="secondary">{secret}</Badge>
))}
</div>
)}
</div>
);
};
const CreateVariable = () => {
export const CreateVariable = () => {
const { toast } = useToast();
const [open, setOpen] = useState(false);
const [name, setName] = useState("");
@@ -170,7 +163,7 @@ const CreateVariable = () => {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className="items-center gap-2">
<Button variant="secondary" className="items-center gap-2">
New Variable <PlusCircle className="w-4 h-4" />
</Button>
</DialogTrigger>

View File

@@ -3,12 +3,9 @@ import { useUser } from "@lib/hooks";
import { Login } from "@pages/login";
import { Resource } from "@pages/resource";
import { Resources } from "@pages/resources";
import { Keys } from "@pages/keys";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import { Tree } from "@pages/home/tree";
import { Tags } from "@pages/tags";
import { Updates } from "@pages/updates";
import { UsersPage } from "@pages/users";
import { AllResources } from "@pages/home/all_resources";
import { UserDisabled } from "@pages/user_disabled";
import { Home } from "@pages/home";
@@ -16,7 +13,7 @@ import { ResourceStats } from "@pages/resource_stats";
import { Alerts } from "@pages/alerts";
import { UserPage } from "@pages/user";
import { UserGroupPage } from "@pages/user-group";
import { Variables } from "@pages/variables";
import { Settings } from "@pages/settings";
const ROUTER = createBrowserRouter([
{
@@ -24,18 +21,19 @@ const ROUTER = createBrowserRouter([
element: <Layout />,
children: [
{ path: "", element: <Home /> },
{ path: "keys", element: <Keys /> },
{ path: "tags", element: <Tags /> },
{ path: "settings", element: <Settings /> },
// { path: "keys", element: <Keys /> },
// { path: "tags", element: <Tags /> },
{ path: "tree", element: <Tree /> },
{ path: "alerts", element: <Alerts /> },
{ path: "updates", element: <Updates /> },
{ path: "variables", element: <Variables /> },
// { path: "variables", element: <Variables /> },
{ path: "resources", element: <AllResources /> },
{ path: "user-groups/:id", element: <UserGroupPage /> },
{
path: "users",
children: [
{ path: "", element: <UsersPage /> },
// { path: "", element: <UsersPage /> },
{ path: ":id", element: <UserPage /> },
],
},