mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-07 16:01:24 +00:00
fix tags filter
This commit is contained in:
@@ -6,6 +6,11 @@ import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "@ui/dialog";
|
||||
import { useState } from "react";
|
||||
import { AlertLevel } from ".";
|
||||
import { fmt_date_with_minutes } from "@lib/formatting";
|
||||
import { DialogDescription } from "@radix-ui/react-dialog";
|
||||
import {
|
||||
alert_level_intention,
|
||||
text_color_class_by_intention,
|
||||
} from "@lib/color";
|
||||
|
||||
export const AlertDetailsDialog = ({ id }: { id: string }) => {
|
||||
const [open, set] = useState(false);
|
||||
@@ -23,20 +28,42 @@ export const AlertDetailsDialog = ({ id }: { id: string }) => {
|
||||
<DialogHeader className="flex-row justify-between w-full">
|
||||
{alert && (
|
||||
<>
|
||||
<div className="flex gap-4 items-center">
|
||||
<ResourceLink
|
||||
type={alert.target.type as UsableResource}
|
||||
id={alert.target.id}
|
||||
/>
|
||||
<AlertLevel level={alert.level} />
|
||||
</div>
|
||||
<ResourceLink
|
||||
type={alert.target.type as UsableResource}
|
||||
id={alert.target.id}
|
||||
/>
|
||||
<div className="text-muted-foreground">
|
||||
{fmt_date_with_minutes(new Date(alert.ts))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DialogHeader>
|
||||
<pre>{JSON.stringify(alert.data, undefined, 2)}</pre>
|
||||
<DialogDescription>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="flex gap-2">
|
||||
<div className="text-muted-foreground">status:</div>{" "}
|
||||
<div
|
||||
className={text_color_class_by_intention(
|
||||
alert.resolved
|
||||
? "Good"
|
||||
: alert_level_intention(alert.level)
|
||||
)}
|
||||
>
|
||||
{alert.resolved ? "RESOLVED" : "OPEN"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="text-muted-foreground">type:</div>{" "}
|
||||
{alert.data.type}
|
||||
</div>
|
||||
<div className="flex gap-2 text-muted-foreground">
|
||||
level: <AlertLevel level={alert.level} />
|
||||
</div>
|
||||
</div>
|
||||
<pre>{JSON.stringify(alert.data.data, undefined, 2)}</pre>
|
||||
</div>
|
||||
</DialogDescription>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { fmt_date_with_minutes } from "@lib/formatting";
|
||||
import { Types } from "@monitor/client";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
import { AlertLevel } from ".";
|
||||
@@ -33,10 +32,6 @@ export const AlertsTable = ({ alerts }: { alerts: Types.Alert[] }) => {
|
||||
header: "Alert Type",
|
||||
accessorKey: "variant",
|
||||
},
|
||||
{
|
||||
header: "Opened",
|
||||
accessorFn: ({ ts }) => fmt_date_with_minutes(new Date(ts)),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NewLayout } from "@components/layouts";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { useRead, useTagsFilter, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@monitor/client";
|
||||
import {
|
||||
Select,
|
||||
@@ -17,7 +17,7 @@ import { DataTable } from "@ui/data-table";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
import { AlerterConfig } from "./config";
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { DeleteResource, ResourceLink } from "../common";
|
||||
|
||||
const useAlerter = (id?: string) =>
|
||||
@@ -92,11 +92,12 @@ export const AlerterComponents: RequiredResourceComponents = {
|
||||
return (
|
||||
<DataTable
|
||||
data={
|
||||
alerters?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
alerters?.filter(
|
||||
(resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
(searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true)
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { useRead, useTagsFilter } from "@lib/hooks";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
import { fmt_date_with_minutes, fmt_version } from "@lib/formatting";
|
||||
import { ResourceLink } from "../common";
|
||||
@@ -11,11 +11,12 @@ export const BuildTable = ({ search }: { search?: string }) => {
|
||||
return (
|
||||
<DataTable
|
||||
data={
|
||||
builds?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
builds?.filter(
|
||||
(resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
(searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true)
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { NewLayout } from "@components/layouts";
|
||||
import { useTagsFilter } from "@components/tags";
|
||||
import { useRead, useWrite } from "@lib/hooks";
|
||||
import { useRead, useTagsFilter, useWrite } from "@lib/hooks";
|
||||
import { Types } from "@monitor/client";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
@@ -92,11 +91,12 @@ export const BuilderComponents: RequiredResourceComponents = {
|
||||
return (
|
||||
<DataTable
|
||||
data={
|
||||
builders?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
builders?.filter(
|
||||
(resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
(searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true)
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { Types } from "@monitor/client";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { useRead, useTagsFilter } from "@lib/hooks";
|
||||
import { ResourceLink } from "../common";
|
||||
import { DeploymentComponents } from ".";
|
||||
|
||||
@@ -17,11 +17,12 @@ export const DeploymentTable = ({
|
||||
return (
|
||||
<DataTable
|
||||
data={
|
||||
deployments?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
deployments?.filter(
|
||||
(resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
(searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true)
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { useRead, useTagsFilter } from "@lib/hooks";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { ResourceLink } from "../common";
|
||||
|
||||
export const ProcedureTable = ({ search }: { search?: string }) => {
|
||||
@@ -10,11 +10,12 @@ export const ProcedureTable = ({ search }: { search?: string }) => {
|
||||
return (
|
||||
<DataTable
|
||||
data={
|
||||
procedures?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
procedures?.filter(
|
||||
(resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
(searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true)
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { useRead, useTagsFilter } from "@lib/hooks";
|
||||
import { RequiredResourceComponents } from "@types";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
@@ -41,11 +41,12 @@ export const RepoComponents: RequiredResourceComponents = {
|
||||
return (
|
||||
<DataTable
|
||||
data={
|
||||
repos?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
repos?.filter(
|
||||
(resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
(searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true)
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { TagsWithBadge } from "@components/tags";
|
||||
import { useRead, useTagsFilter } from "@lib/hooks";
|
||||
import { DataTable } from "@ui/data-table";
|
||||
import { ServerComponents } from ".";
|
||||
import { ResourceLink } from "../common";
|
||||
@@ -11,11 +11,12 @@ export const ServerTable = ({ search }: { search?: string }) => {
|
||||
return (
|
||||
<DataTable
|
||||
data={
|
||||
servers?.filter((resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true
|
||||
servers?.filter(
|
||||
(resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
(searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true)
|
||||
) ?? []
|
||||
}
|
||||
columns={[
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useInvalidate, useRead, useWrite } from "@lib/hooks";
|
||||
import { tagsAtom, useInvalidate, useRead, useWrite } from "@lib/hooks";
|
||||
import { cn } from "@lib/utils";
|
||||
import { Types } from "@monitor/client";
|
||||
import { Badge } from "@ui/badge";
|
||||
@@ -14,19 +14,11 @@ import {
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@ui/popover";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
import { useAtom } from "jotai";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
import { MinusCircle, PlusCircle, SearchX, Tag } from "lucide-react";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
|
||||
type TargetExcludingSystem = Exclude<Types.ResourceTarget, { type: "System" }>;
|
||||
|
||||
const tagsAtom = atomWithStorage<string[]>("tags-v0", []);
|
||||
|
||||
export const useTagsFilter = () => {
|
||||
const [tags] = useAtom(tagsAtom);
|
||||
return tags;
|
||||
};
|
||||
|
||||
export const TagsFilter = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
@@ -104,7 +96,7 @@ export const TagsFilterTags = ({
|
||||
{tag_ids?.map((tag_id) => (
|
||||
<Badge
|
||||
key={tag_id}
|
||||
variant="destructive"
|
||||
variant="secondary"
|
||||
className="flex gap-1 px-2 py-1.5 cursor-pointer"
|
||||
onClick={() => onBadgeClick && onBadgeClick(tag_id)}
|
||||
>
|
||||
@@ -125,12 +117,14 @@ export const ResourceTags = ({
|
||||
click_to_delete?: boolean;
|
||||
className?: string;
|
||||
}) => {
|
||||
const { toast } = useToast();
|
||||
const inv = useInvalidate();
|
||||
const { type, id } = target;
|
||||
const resource = useRead(`List${type}s`, {}).data?.find((d) => d.id === id);
|
||||
const { mutate } = useWrite("UpdateTagsOnResource", {
|
||||
onSuccess: () => {
|
||||
inv([`List${type}s`]);
|
||||
toast({ title: "Removed tag" });
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -55,15 +55,15 @@ export const stroke_color_class_by_intention = (intention: ColorIntention) => {
|
||||
export const bg_color_class_by_intention = (intention: ColorIntention) => {
|
||||
switch (intention) {
|
||||
case "Good":
|
||||
return "bg-green-500";
|
||||
return "bg-green-400 dark:bg-green-700";
|
||||
case "Neutral":
|
||||
return "bg-blue-500";
|
||||
return "bg-blue-400 dark:bg-blue-700";
|
||||
case "Warning":
|
||||
return "bg-orange-500";
|
||||
return "bg-orange-400 dark:bg-orange-700";
|
||||
case "Critical":
|
||||
return "bg-red-500";
|
||||
return "bg-red-400 dark:bg-red-700";
|
||||
case "Unknown":
|
||||
return "bg-purple-500";
|
||||
return "bg-purple-400 dark:bg-purple-700";
|
||||
case "None":
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
} from "@tanstack/react-query";
|
||||
import { UsableResource } from "@types";
|
||||
import { useToast } from "@ui/use-toast";
|
||||
import { useAtom } from "jotai";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
import { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
@@ -174,4 +176,11 @@ export const useSetTitle = (more?: string) => {
|
||||
document.title = title;
|
||||
}
|
||||
}, [title]);
|
||||
}
|
||||
}
|
||||
|
||||
export const tagsAtom = atomWithStorage<string[]>("tags-v0", []);
|
||||
|
||||
export const useTagsFilter = () => {
|
||||
const [tags] = useAtom(tagsAtom);
|
||||
return tags;
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
import { OpenAlerts } from "@components/alert";
|
||||
import { Page, Section } from "@components/layouts";
|
||||
import { ResourceComponents } from "@components/resources";
|
||||
import { TagsFilter, useTagsFilter } from "@components/tags";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { TagsFilter } from "@components/tags";
|
||||
import { useRead, useTagsFilter } from "@lib/hooks";
|
||||
import { RequiredResourceComponents, UsableResource } from "@types";
|
||||
import { Input } from "@ui/input";
|
||||
import { useState } from "react";
|
||||
@@ -28,7 +28,12 @@ export const AllResources = () => {
|
||||
>
|
||||
<OpenAlerts />
|
||||
{Object.entries(ResourceComponents).map(([type, Components]) => (
|
||||
<TableSection type={type} Components={Components} search={search} />
|
||||
<TableSection
|
||||
key={type}
|
||||
type={type}
|
||||
Components={Components}
|
||||
search={search}
|
||||
/>
|
||||
))}
|
||||
</Page>
|
||||
);
|
||||
@@ -44,8 +49,13 @@ const TableSection = ({
|
||||
search?: string;
|
||||
}) => {
|
||||
const tags = useTagsFilter();
|
||||
const searchSplit = search?.split(" ") || [];
|
||||
const count = useRead(`List${type as UsableResource}s`, {}).data?.filter(
|
||||
(resource) => tags.every((tag) => resource.tags.includes(tag))
|
||||
(resource) =>
|
||||
tags.every((tag) => resource.tags.includes(tag)) &&
|
||||
(searchSplit.length > 0
|
||||
? searchSplit.every((search) => resource.name.includes(search))
|
||||
: true)
|
||||
).length;
|
||||
|
||||
if (!count) return;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ResourceComponents } from "@components/resources";
|
||||
import { OpenAlerts } from "@components/alert";
|
||||
import { useUser } from "@lib/hooks";
|
||||
import { ResourceLink } from "@components/resources/common";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export const Dashboard = () => {
|
||||
return (
|
||||
@@ -59,9 +60,9 @@ const RecentlyViewed = () => {
|
||||
actions=""
|
||||
>
|
||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{recently_viewed?.slice(0, 6).map(
|
||||
({ type, id }) =>
|
||||
type !== "System" && (
|
||||
{recently_viewed?.slice(0, 6).map(({ type, id }) => (
|
||||
<Fragment key={type + id}>
|
||||
{type !== "System" && (
|
||||
<Card
|
||||
onClick={() => nav(`/${type.toLowerCase()}s/${id}`)}
|
||||
className="px-3 py-2 h-fit hover:bg-accent/50 group-focus:bg-accent/50 transition-colors cursor-pointer"
|
||||
@@ -71,8 +72,9 @@ const RecentlyViewed = () => {
|
||||
{type}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
)}
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
|
||||
@@ -3,8 +3,8 @@ import { Page, Section } from "@components/layouts";
|
||||
import { ResourceComponents } from "@components/resources";
|
||||
import { DeploymentTable } from "@components/resources/deployment/table";
|
||||
import { ServerComponents } from "@components/resources/server";
|
||||
import { TagsFilter, TagsWithBadge, useTagsFilter } from "@components/tags";
|
||||
import { useRead } from "@lib/hooks";
|
||||
import { TagsFilter, TagsWithBadge } from "@components/tags";
|
||||
import { useRead, useTagsFilter } from "@lib/hooks";
|
||||
import { Button } from "@ui/button";
|
||||
import { Card, CardHeader, CardTitle } from "@ui/card";
|
||||
import { Fragment, useState } from "react";
|
||||
|
||||
@@ -1,26 +1,15 @@
|
||||
import { Page, Section, ResourceCard } from "@components/layouts";
|
||||
import { Page, Section } from "@components/layouts";
|
||||
import { ResourceComponents } from "@components/resources";
|
||||
import { TagsFilter, useTagsFilter } from "@components/tags";
|
||||
import { useRead, useResourceParamType, useSetTitle } from "@lib/hooks";
|
||||
import { Button } from "@ui/button";
|
||||
import { TagsFilter } from "@components/tags";
|
||||
import { useResourceParamType, useSetTitle } from "@lib/hooks";
|
||||
import { Input } from "@ui/input";
|
||||
import { useState } from "react";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
const viewAtom = atomWithStorage<"cards" | "table">("list-show-as-v0", "table");
|
||||
|
||||
export const Resources = () => {
|
||||
const type = useResourceParamType()!;
|
||||
useSetTitle(type + "s");
|
||||
const Components = ResourceComponents[type];
|
||||
|
||||
const tags = useTagsFilter();
|
||||
|
||||
const list = useRead(`List${type}s`, { query: { tags } }).data;
|
||||
|
||||
const [search, set] = useState("");
|
||||
const [view, setView] = useAtom(viewAtom);
|
||||
|
||||
return (
|
||||
<Page
|
||||
@@ -28,14 +17,6 @@ export const Resources = () => {
|
||||
actions={
|
||||
<div className="grid gap-4 justify-items-end">
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
setView((v) => (v === "cards" ? "table" : "cards"))
|
||||
}
|
||||
>
|
||||
show as {view === "cards" ? "table" : "cards"}
|
||||
</Button>
|
||||
<Input
|
||||
value={search}
|
||||
onChange={(e) => set(e.target.value)}
|
||||
@@ -49,15 +30,7 @@ export const Resources = () => {
|
||||
}
|
||||
>
|
||||
<Section title="">
|
||||
{view === "cards" ? (
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{list?.map(({ id }) => (
|
||||
<ResourceCard key={id} target={{ type, id }} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Components.Table />
|
||||
)}
|
||||
<Components.Table />
|
||||
</Section>
|
||||
</Page>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user