no unnecessary user group sync

This commit is contained in:
mbecker20
2024-06-08 02:56:53 -07:00
parent 9d116f56cb
commit c73d918e18
10 changed files with 253 additions and 133 deletions

View File

@@ -1,4 +1,4 @@
use anyhow::Context;
use anyhow::{anyhow, Context};
use mongo_indexed::doc;
use monitor_client::{
api::{execute::RunSync, write::RefreshResourceSyncPending},
@@ -48,6 +48,10 @@ impl Resolve<RunSync, (User, Update)> for State {
>(&sync, &user, PermissionLevel::Execute)
.await?;
if sync.config.repo.is_empty() {
return Err(anyhow!("resource sync repo not configured"));
}
let (res, logs, hash, message) =
crate::helpers::sync::remote::get_remote_resources(&sync)
.await

View File

@@ -1,4 +1,4 @@
use anyhow::Context;
use anyhow::{anyhow, Context};
use monitor_client::{
api::write::*,
entities::{
@@ -104,6 +104,10 @@ impl Resolve<RefreshResourceSyncPending, User> for State {
>(&sync, &user, PermissionLevel::Execute)
.await?;
if sync.config.repo.is_empty() {
return Err(anyhow!("resource sync repo not configured"));
}
let res = async {
let (res, _, hash, message) =
crate::helpers::sync::remote::get_remote_resources(&sync)

View File

@@ -275,7 +275,11 @@ pub async fn get_updates_for_view(
));
}
Ok(Some(update))
let any_change = update.to_create > 0
|| update.to_update > 0
|| update.to_delete > 0;
Ok(any_change.then_some(update))
}
pub async fn get_updates_for_execution(

View File

@@ -120,7 +120,11 @@ pub async fn get_updates_for_view(
));
}
Ok(Some(update))
let any_change = update.to_create > 0
|| update.to_update > 0
|| update.to_delete > 0;
Ok(any_change.then_some(update))
}
pub async fn get_updates_for_execution(

View File

@@ -60,7 +60,7 @@ export const RepoComponents: RequiredResourceComponents = {
<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">
{info.latest_hash}
latest commit: {info.latest_hash}
</div>
</Card>
</HoverCardTrigger>

View File

@@ -1,125 +1,24 @@
import { useRead } from "@lib/hooks";
import { RequiredResourceComponents } from "@types";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@ui/card";
import { FolderSync } from "lucide-react";
import { Card, CardDescription, CardHeader, CardTitle } from "@ui/card";
import { Clock, FolderSync } from "lucide-react";
import { Link } from "react-router-dom";
import { DeleteResource, NewResource } from "../common";
import { ResourceSyncTable } from "./table";
import { Types } from "@monitor/client";
import { ResourceSyncConfig } from "./config";
import { useState } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs";
import { ExecuteSync, RefreshSync } from "./actions";
import { sanitizeOnlySpan, sync_no_changes } from "@lib/utils";
import { Section } from "@components/layouts";
import { PendingOrConfig } from "./pending-or-config";
import {
bg_color_class_by_intention,
resource_sync_state_intention,
} from "@lib/color";
import { cn } from "@lib/utils";
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@ui/hover-card";
import { fmt_date } from "@lib/formatting";
const useResourceSync = (id?: string) =>
useRead("ListResourceSyncs", {}).data?.find((d) => d.id === id);
const PENDING_TYPE_KEYS: Array<[string, string]> = [
["Server", "server_updates"],
["Deployment", "deployment_updates"],
["Build", "build_updates"],
["Repo", "repo_updates"],
["Procedure", "procedure_updates"],
["Alerter", "alerter_updates"],
["Builder", "builder_updates"],
["Server Template", "server_template_updates"],
["Resource Sync", "resource_sync_updates"],
["Variable", "variable_updates"],
["User Group", "user_group_updates"],
];
const PendingOrConfig = ({ id }: { id: string }) => {
const [view, setView] = useState("Pending");
const sync = useRead("GetResourceSync", { sync: id }).data;
const pendingDisabled = !sync || sync_no_changes(sync);
const currentView = view === "Pending" && pendingDisabled ? "Config" : view;
const tabsList = (
<TabsList className="justify-start w-fit">
<TabsTrigger
value="Pending"
className="w-[110px]"
disabled={pendingDisabled}
>
Pending
</TabsTrigger>
<TabsTrigger value="Config" className="w-[110px]">
Config
</TabsTrigger>
</TabsList>
);
return (
<Tabs value={currentView} onValueChange={setView} className="grid gap-4">
<TabsContent value="Config">
<ResourceSyncConfig id={id} titleOther={tabsList} />
</TabsContent>
<TabsContent value="Pending">
<Section titleOther={tabsList}>
{PENDING_TYPE_KEYS.map(([type, key]) => (
<PendingView
key={type}
type={type}
pending={sync?.info?.pending?.[key]}
/>
))}
</Section>
</TabsContent>
</Tabs>
);
};
const PendingView = ({
type,
pending,
}: {
type: string;
pending: Types.SyncUpdate | undefined;
}) => {
if (!pending) return;
return (
<Card>
<CardHeader className="flex items-center justify-between gap-4">
<CardTitle>{type} Updates</CardTitle>
<div className="flex gap-4 items-center">
{pending.to_create && (
<div className="flex gap-2 items-center">
To Create: {pending.to_create}
</div>
)}
{pending.to_update && (
<div className="flex gap-2 items-center">
To Update: {pending.to_update}
</div>
)}
{pending.to_delete && (
<div className="flex gap-2 items-center">
To Delete: {pending.to_delete}
</div>
)}
</div>
</CardHeader>
<CardContent>
<pre
dangerouslySetInnerHTML={{
__html: sanitizeOnlySpan(pending.log),
}}
/>
</CardContent>
</Card>
);
};
export const ResourceSyncComponents: RequiredResourceComponents = {
list_item: (id) => useResourceSync(id),
@@ -151,9 +50,55 @@ export const ResourceSyncComponents: RequiredResourceComponents = {
Icon: () => <FolderSync className="w-4 h-4" />,
BigIcon: () => <FolderSync className="w-8 h-8" />,
Status: {},
Status: {
State: ({ id }) => {
const state = useResourceSync(id)?.info.state;
const color = bg_color_class_by_intention(
resource_sync_state_intention(state)
);
return (
<Card className={cn("w-fit", color)}>
<CardHeader className="py-0 px-2">{state}</CardHeader>
</Card>
);
},
Status: ({ id }) => {
const info = useResourceSync(id)?.info;
if (info?.last_sync_hash && info?.last_sync_message) {
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">
last sync: {info.last_sync_hash}
</div>
</Card>
</HoverCardTrigger>
<HoverCardContent align="start">
<div className="grid">
<div className="text-muted-foreground">commit message:</div>
{info.last_sync_message}
</div>
</HoverCardContent>
</HoverCard>
);
} else {
return <div className="text-muted-foreground">{"Never synced"}</div>;
}
},
},
Info: {},
Info: {
LastSync: ({ id }) => {
const last_ts = useResourceSync(id)?.info.last_sync_ts;
return (
<div className="flex items-center gap-2">
<Clock className="w-4 h-4" />
{last_ts ? fmt_date(new Date(last_ts)) : "Never"}
</div>
);
},
},
Actions: { RefreshSync, ExecuteSync },

View File

@@ -0,0 +1,139 @@
import { useRead } from "@lib/hooks";
import { sanitizeOnlySpan, sync_no_changes } from "@lib/utils";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@ui/tabs";
import { useState } from "react";
import { ResourceSyncConfig } from "./config";
import { Section } from "@components/layouts";
import { Types } from "@monitor/client";
import { Card, CardContent, CardHeader, CardTitle } from "@ui/card";
const PENDING_TYPE_KEYS: Array<[string, string]> = [
["Server", "server_updates"],
["Deployment", "deployment_updates"],
["Build", "build_updates"],
["Repo", "repo_updates"],
["Procedure", "procedure_updates"],
["Alerter", "alerter_updates"],
["Builder", "builder_updates"],
["Server Template", "server_template_updates"],
["Resource Sync", "resource_sync_updates"],
["Variable", "variable_updates"],
["User Group", "user_group_updates"],
];
export const PendingOrConfig = ({ id }: { id: string }) => {
const [view, setView] = useState("Pending");
const sync = useRead("GetResourceSync", { sync: id }).data;
const pendingDisabled = !sync || sync_no_changes(sync);
const currentView = view === "Pending" && pendingDisabled ? "Config" : view;
const pending = sync?.info?.pending;
const tabsList = (
<TabsList className="justify-start w-fit">
<TabsTrigger
value="Pending"
className="w-[110px]"
disabled={pendingDisabled}
>
Pending
</TabsTrigger>
<TabsTrigger value="Config" className="w-[110px]">
Config
</TabsTrigger>
</TabsList>
);
return (
<Tabs value={currentView} onValueChange={setView} className="grid gap-4">
<TabsContent value="Config">
<ResourceSyncConfig id={id} titleOther={tabsList} />
</TabsContent>
<TabsContent value="Pending">
<Section titleOther={tabsList}>
{pending?.hash && pending.message && (
<Card>
<CardHeader className="flex items-center gap-4 text-muted-foreground">
Commit: {pending.hash}: {pending.message}
</CardHeader>
</Card>
)}
{pending?.data.type === "Ok" &&
PENDING_TYPE_KEYS.map(([type, key]) => (
<PendingView
key={type}
type={type}
pending={pending.data.data[key]}
/>
))}
{pending?.data.type === "Err" && (
<Card>
<CardHeader className="flex items-center justify-between gap-4">
<CardTitle>Pending Error</CardTitle>
</CardHeader>
<CardContent>
<pre
dangerouslySetInnerHTML={{
__html: sanitizeOnlySpan(pending.data.data.message),
}}
/>
</CardContent>
</Card>
)}
</Section>
</TabsContent>
</Tabs>
);
};
const PendingView = ({
type,
pending,
}: {
type: string;
pending: Types.SyncUpdate | undefined;
}) => {
if (!pending) return;
return (
<Card>
<div className="flex items-center gap-4 px-8 py-4">
<CardTitle>{type} Updates</CardTitle>
<div className="flex gap-4 items-center m-0">
{pending.to_create ? (
<>
|
<div className="flex gap-2 items-center">
To Create: {pending.to_create}
</div>
</>
) : undefined}
{pending.to_update ? (
<>
|
<div className="flex gap-2 items-center">
To Update: {pending.to_update}
</div>
</>
) : undefined}
{pending.to_delete ? (
<>
|
<div className="flex gap-2 items-center">
To Delete: {pending.to_delete}
</div>
</>
) : undefined}
</div>
</div>
<CardContent>
<pre
dangerouslySetInnerHTML={{
__html: sanitizeOnlySpan(pending.log),
}}
/>
</CardContent>
</Card>
);
};

View File

@@ -2,6 +2,7 @@ import { DataTable, SortableHeader } from "@ui/data-table";
import { ResourceLink } from "../common";
import { TableTags } from "@components/tags";
import { Types } from "@monitor/client";
import { ResourceSyncComponents } from ".";
export const ResourceSyncTable = ({
syncs,
@@ -22,19 +23,16 @@ export const ResourceSyncTable = ({
<ResourceLink type="ResourceSync" id={row.original.id} />
),
},
// {
// accessorKey: "info.builder_type",
// header: ({ column }) => (
// <SortableHeader column={column} title="Provider" />
// ),
// },
// {
// accessorKey: "info.instance_type",
// header: ({ column }) => (
// <SortableHeader column={column} title="Instance Type" />
// ),
// cell: ({ row }) => <BuilderInstanceType id={row.original.id} />,
// },
{
accessorKey: "info.state",
header: ({ column }) => (
<SortableHeader column={column} title="State" />
),
cell: ({ row }) => (
<ResourceSyncComponents.Status.State id={row.original.id} />
),
size: 120,
},
{
header: "Tags",
cell: ({ row }) => <TableTags tag_ids={row.original.tags} />,

View File

@@ -171,6 +171,25 @@ export const procedure_state_intention = (status?: Types.ProcedureState) => {
}
};
export const resource_sync_state_intention = (status?: Types.ResourceSyncState) => {
switch (status) {
case undefined:
return "None";
case Types.ResourceSyncState.Unknown:
return "Unknown";
case Types.ResourceSyncState.Ok:
return "Good";
case Types.ResourceSyncState.Syncing:
return "Warning";
case Types.ResourceSyncState.Pending:
return "Warning";
case Types.ResourceSyncState.Failed:
return "Critical";
default:
return "None";
}
};
export const alert_level_intention: (
level: Types.SeverityLevel
) => ColorIntention = (level) => {

View File

@@ -39,6 +39,8 @@ export const Resource = () => {
);
const canWrite = !ui_write_disabled && perms === Types.PermissionLevel.Write;
const infoEntries = Object.entries(Components.Info);
return (
<Page
title={name}
@@ -52,13 +54,14 @@ export const Resource = () => {
}
subtitle={
<div className="flex gap-4 items-center text-muted-foreground">
{Object.entries(Components.Info).map(([key, Info], i) => (
{infoEntries.map(([key, Info], i) => (
<Fragment key={key}>
{i !== 0 && "| "}
<Info key={i} id={id} />
</Fragment>
))}
| <ExportButton targets={[{ type, id }]} />
{infoEntries.length ? "| " : ""}
<ExportButton targets={[{ type, id }]} />
</div>
}
actions={