Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/api/services/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IncidentCommander } from "../axios";
import { resolvePostGrestRequestWithPagination } from "../resolve";
import { PermissionsSummary, PermissionTable } from "../types/permissions";
import { AVATAR_INFO } from "@flanksource-ui/constants";

export type FetchPermissionsInput = {
componentId?: string;
Expand Down Expand Up @@ -68,11 +69,11 @@ export function fetchPermissions(
}
) {
const queryParam = composeQueryParamForFetchPermissions(input);
const selectFields = ["*"];
const selectFields = `*,created_by(${AVATAR_INFO})`;

const { pageSize, pageIndex } = pagination;

const url = `/permissions_summary?${queryParam}&select=${selectFields.join(",")}&deleted_at=is.null&limit=${pageSize}&offset=${pageIndex * pageSize}`;
const url = `/permissions_summary?${queryParam}&select=${selectFields}&deleted_at=is.null&limit=${pageSize}&offset=${pageIndex * pageSize}`;
return resolvePostGrestRequestWithPagination(
IncidentCommander.get<PermissionsSummary[]>(url, {
headers: {
Expand Down
9 changes: 3 additions & 6 deletions src/api/types/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ export type PermissionTable = {
updated_at: string;
until?: string;
source?: string;
tags?: Record<string, string>;
agents?: string[];

// Resources
object?: PermissionGlobalObject;
Expand Down Expand Up @@ -68,17 +66,16 @@ export type PermissionsSummary = PermissionTable & {
group: any;
subject: string;
person: User;
createdBy: User;
tags: Record<string, string> | null;
agents: string[] | null;
created_by?: User;

// These represent global objects
object: PermissionGlobalObject;

// These represent object selectors per type
object_selector?: PermissionObjectSelector;

// These are objects that are specifically chosen
// These are objects that are specifically chosen (from the dropdown).
// We store their ID.
config_object: Pick<ConfigItem, "id" | "name" | "type" | "config_class">;
playbook_object: Pick<PlaybookSpec, "id" | "name" | "icon">;
connection_object: Pick<Connection, "id" | "name" | "type">;
Expand Down
107 changes: 62 additions & 45 deletions src/components/Permissions/ManagePermissions/Forms/PermissionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { Button } from "@flanksource-ui/ui/Buttons/Button";
import { Modal } from "@flanksource-ui/ui/Modal";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { AxiosError } from "axios";
import clsx from "clsx";
import { Form, Formik, useFormikContext } from "formik";
import { useMemo } from "react";
import { FaSpinner } from "react-icons/fa";
Expand Down Expand Up @@ -78,22 +77,45 @@ function PermissionFormContent({

return (
<div className="flex flex-col gap-3 p-4">
<PermissionsSubjectControls />
{isResourceIdProvided && isReadOnly ? (
<div className="flex flex-col gap-2">
<label className="text-sm font-semibold">Resource</label>
<PermissionResource />
{isReadOnly && (
<div className="rounded-md border border-yellow-300 bg-yellow-50 p-3 text-sm text-yellow-900">
<p className="font-medium">
Read-Only Mode: This resource is managed by Kubernetes CRD and
cannot be edited from the UI.
</p>
</div>
) : (
<FormikPermissionSelectResourceFields />
)}
<PermissionActionDropdown isDisabled={isReadOnly} />
<FormikCheckbox name="deny" label="Deny" disabled={isReadOnly} />
<FormikTextArea
name="description"
label="Description"
disabled={isReadOnly}
/>

<div className={isReadOnly ? "pointer-events-none opacity-60" : ""}>
<PermissionsSubjectControls disabled={isReadOnly} />
</div>

<div className={isReadOnly ? "pointer-events-none opacity-60" : ""}>
{isResourceIdProvided && isReadOnly ? (
<div className="flex flex-col gap-2">
<label className="text-sm font-semibold">Resource</label>
<PermissionResource />
</div>
) : (
<FormikPermissionSelectResourceFields />
)}
</div>

<div className={isReadOnly ? "pointer-events-none opacity-60" : ""}>
<PermissionActionDropdown isDisabled={isReadOnly} />
</div>

<div className={isReadOnly ? "pointer-events-none opacity-60" : ""}>
<FormikCheckbox name="deny" label="Deny" disabled={isReadOnly} />
</div>

<div className={isReadOnly ? "pointer-events-none opacity-60" : ""}>
<FormikTextArea
name="description"
label="Description"
disabled={isReadOnly}
/>
</div>
</div>
);
}
Expand Down Expand Up @@ -247,41 +269,36 @@ export default function PermissionForm({
id={permissionData?.id}
resourceType={"permissions"}
source={permissionData?.source}
className="flex items-center bg-gray-100 px-5 py-4"
className="flex items-center justify-between bg-gray-100 px-5 py-4"
>
<AuthorizationAccessCheck
resource={tables.permissions}
action="write"
>
<div
className={clsx(
"flex items-center bg-gray-100 px-5 py-4",
permissionData?.id ? "justify-between" : "justify-end"
)}
>
{permissionData?.id && (
<div>
{permissionData?.id && permissionData.source === "UI" && (
<AuthorizationAccessCheck
resource={tables.permissions}
action="write"
>
<DeletePermission
permissionId={permissionData.id}
onDeleted={onClose}
/>
)}
<Button
icon={
isLoading ? (
<FaSpinner className="animate-spin" />
) : undefined
}
type="submit"
text={
permissionData?.id
? "Save"
: isLoading
? "Saving ..."
: "Save"
}
className="btn-primary"
/>
</div>
</AuthorizationAccessCheck>
)}
</div>
<AuthorizationAccessCheck
resource={tables.permissions}
action="write"
>
<Button
type="submit"
text={permissionData?.id ? "Save" : "Create"}
className="btn-primary"
icon={
isLoading ? (
<FaSpinner className="animate-spin" />
) : undefined
}
disabled={isLoading}
/>
</AuthorizationAccessCheck>
</CanEditResource>
</Form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ const SWITCH_OPTION_TO_SUBJECT_TYPE = {
Playbook: "playbook"
} as const;

export default function PermissionsSubjectControls() {
type PermissionsSubjectControlsProps = {
disabled?: boolean;
};

export default function PermissionsSubjectControls({
disabled = false
}: PermissionsSubjectControlsProps) {
const { values, setFieldValue } = useFormikContext<Record<string, any>>();

const teamId = values.team_id;
Expand Down Expand Up @@ -71,6 +77,7 @@ export default function PermissionsSubjectControls() {
defaultValue="Go Template"
value={switchOption}
onChange={(v) => {
if (disabled) return;
setSwitchOption(v);
setFieldValue("subject_type", SWITCH_OPTION_TO_SUBJECT_TYPE[v]);

Expand Down
56 changes: 5 additions & 51 deletions src/components/Permissions/PermissionsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,8 @@ import { permissionObjectList } from "./ManagePermissions/Forms/FormikPermission
import { permissionsActionsList } from "./PermissionsView";
import { BsBan } from "react-icons/bs";
import { Link } from "react-router-dom";
import { Badge } from "@flanksource-ui/ui/Badge/Badge";
import CRDSource from "../Settings/CRDSource";

const formatTagText = (key: string, value: string): string => {
return `${key}: ${value}`;
};

const permissionsTableColumns: MRT_ColumnDef<PermissionsSummary>[] = [
{
header: "Subject",
Expand Down Expand Up @@ -108,41 +103,6 @@ const permissionsTableColumns: MRT_ColumnDef<PermissionsSummary>[] = [
const connection = row.original.connection_object;
const object = row.original.object;
const objectSelector = row.original.object_selector;
const { tags, agents } = row.original;

const renderRlsBadges = (): JSX.Element[] => {
const badges: JSX.Element[] = [];

// Add tag badges
if (tags && Object.keys(tags).length > 0) {
Object.entries(tags).forEach(([key, value]) => {
badges.push(
<Badge
key={`tag-${key}`}
text={formatTagText(key, value)}
color="blue"
/>
);
});
}

// Add agent badges
if (agents && agents.length > 0) {
agents.forEach((agent, index) => {
badges.push(
<Badge
key={`agent-${index}`}
text={`agent: ${agent}`}
color="gray"
/>
);
});
}

return badges;
};

const rlsBadges = renderRlsBadges();

if (objectSelector) {
return (
Expand All @@ -153,9 +113,6 @@ const permissionsTableColumns: MRT_ColumnDef<PermissionsSummary>[] = [
>
{JSON.stringify(objectSelector)}
</span>
{rlsBadges.length > 0 && (
<div className="flex flex-wrap gap-1">{rlsBadges}</div>
)}
</div>
);
}
Expand All @@ -166,9 +123,6 @@ const permissionsTableColumns: MRT_ColumnDef<PermissionsSummary>[] = [
<span>
{permissionObjectList.find((o) => o.value === object)?.label}
</span>
{rlsBadges.length > 0 && (
<div className="flex flex-wrap gap-1">{rlsBadges}</div>
)}
</div>
);
}
Expand Down Expand Up @@ -216,10 +170,6 @@ const permissionsTableColumns: MRT_ColumnDef<PermissionsSummary>[] = [
</div>
)}
</div>

{rlsBadges.length > 0 && (
<div className="flex flex-wrap gap-1">{rlsBadges}</div>
)}
</div>
);
}
Expand Down Expand Up @@ -280,14 +230,18 @@ const permissionsTableColumns: MRT_ColumnDef<PermissionsSummary>[] = [
header: "Created By",
size: 40,
Cell: ({ row }) => {
const createdBy = row.original.createdBy;
const createdBy = row.original.created_by;
const source = row.original.source;

if (source?.toLowerCase() === "KubernetesCRD".toLowerCase()) {
const id = row.original.id;
return <CRDSource source={source} id={id} showMinimal />;
}

if (!createdBy) {
return null;
}

return <Avatar user={createdBy} />;
}
}
Expand Down
Loading