Skip to content

Commit

Permalink
feat: add permissions tabs for connection and playbooks
Browse files Browse the repository at this point in the history
Fixes #2246

fix: fix minor issues related to the permissions tab

fix: fix failing tests and hard delete permissions

fix: don't allow deleting of crd source permissions

fix: fix failing test

feat: add form for adding permissions in resource and fix form issues

Fixes #2246

fix: fix failing build due to removed search layout component
  • Loading branch information
mainawycliffe committed Oct 9, 2024
1 parent 02744bb commit b7b2b96
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 97 deletions.
4 changes: 1 addition & 3 deletions src/api/services/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,5 @@ export function updatePermission(permission: PermissionTable) {
}

export function deletePermission(id: string) {
return IncidentCommander.patch(`/permissions?id=eq.${id}`, {
deleted_at: "now()"
});
return IncidentCommander.delete(`/permissions?id=eq.${id}`);
}
9 changes: 4 additions & 5 deletions src/components/Configs/ConfigLink/ConfigLink.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { getConfigsByID } from "@flanksource-ui/api/services/configs";
import { ConfigIcon } from "@flanksource-ui/ui/Icons/ConfigIcon";
import TextSkeletonLoader from "@flanksource-ui/ui/SkeletonLoader/TextSkeletonLoader";
import { useQuery } from "@tanstack/react-query";
import clsx from "clsx";
import { HTMLAttributeAnchorTarget } from "react";
Expand All @@ -26,7 +25,7 @@ export default function ConfigLink({
showPrimaryIcon = true,
showSecondaryIcon = true
}: ConfigLinkProps) {
const { data: configFromRequest, isLoading } = useQuery({
const { data: configFromRequest } = useQuery({
queryKey: ["config", configId, config],
queryFn: () => getConfigsByID(configId!),
// Only run the query if we have a configId and we don't have a config
Expand All @@ -35,9 +34,9 @@ export default function ConfigLink({

const data = config || configFromRequest;

if (isLoading) {
return <TextSkeletonLoader className="h-5 w-24" />;
}
// if (isLoading) {
// return <TextSkeletonLoader className="h-5 w-24" />;
// }

if (!data) {
return null;
Expand Down
131 changes: 88 additions & 43 deletions src/components/Connections/ConnectionFormModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Tab, Tabs } from "@flanksource-ui/ui/Tabs/Tabs";
import React, { useEffect, useState } from "react";
import { Icon } from "../../ui/Icons/Icon";
import { Modal } from "../../ui/Modal";
import PermissionsView from "../Permissions/PermissionsView";
import ConnectionForm from "./ConnectionForm";
import ConnectionListView from "./ConnectionListView";
import ConnectionSpecEditor from "./ConnectionSpecEditor";
Expand Down Expand Up @@ -81,6 +83,8 @@ export default function ConnectionFormModal({
ConnectionType | undefined
>(() => connectionTypes.find((item) => item.title === formValue?.type));

const [activeTab, setActiveTab] = useState<"form" | "permissions">("form");

useEffect(() => {
let connection = connectionTypes.find(
(item) => item.value === formValue?.type
Expand All @@ -93,35 +97,76 @@ export default function ConnectionFormModal({
connectionType;

return (
<div className="flex flex-col">
<Modal
title={
connectionType ? (
<div
className="flex flex-row items-center gap-2 overflow-y-auto"
key={connectionType.title}
>
{typeof connectionType?.icon === "string" ? (
<Icon name={connectionType?.icon} />
) : (
connectionType.icon
)}
<div className="text-lg font-semibold">
{connectionType.title} Connection Details
</div>
<Modal
title={
connectionType ? (
<div
className="flex flex-row items-center gap-2 overflow-y-auto"
key={connectionType.title}
>
{typeof connectionType?.icon === "string" ? (
<Icon name={connectionType?.icon} />
) : (
connectionType.icon
)}
<div className="text-lg font-semibold">
{connectionType.title} Connection Details
</div>
) : (
<div className="text-lg font-semibold">Select Connection Type</div>
)
}
onClose={() => {
setIsOpen(false);
}}
open={isOpen}
bodyClass="flex flex-col w-full flex-1 h-full overflow-y-auto"
helpLink="reference/connections/"
>
{type ? (
</div>
) : (
<div className="text-lg font-semibold">Select Connection Type</div>
)
}
onClose={() => {
setIsOpen(false);
}}
open={isOpen}
size="full"
bodyClass="flex flex-col w-full flex-1 h-full overflow-y-auto"
helpLink="reference/connections/"
containerClassName="h-full overflow-auto"
>
{type ? (
formValue?.id ? (
<Tabs
activeTab={activeTab}
onSelectTab={(label) => setActiveTab(label)}
>
<Tab
label="Edit Connection"
value={"form"}
className="flex flex-1 flex-col"
>
<ConnectionForm
handleBack={() => setConnectionType(undefined)}
connectionType={type}
onConnectionSubmit={onConnectionSubmit}
onConnectionDelete={onConnectionDelete}
formValue={formValue}
className={className}
isSubmitting={isSubmitting}
isDeleting={isDeleting}
/>
</Tab>

<Tab
label="Permissions"
value={"permissions"}
className="flex flex-1 flex-col"
>
<PermissionsView
hideResourceColumn
permissionRequest={{
connectionId: formValue.id
}}
showAddPermission
newPermissionData={{
connection_id: formValue.id
}}
/>
</Tab>
</Tabs>
) : (
<ConnectionForm
handleBack={() => setConnectionType(undefined)}
connectionType={type}
Expand All @@ -132,20 +177,20 @@ export default function ConnectionFormModal({
isSubmitting={isSubmitting}
isDeleting={isDeleting}
/>
) : formValue?.id ? (
<ConnectionSpecEditor
handleBack={() => setConnectionType(undefined)}
onConnectionSubmit={onConnectionSubmit}
onConnectionDelete={onConnectionDelete}
formValue={formValue}
className={className}
isSubmitting={isSubmitting}
isDeleting={isDeleting}
/>
) : (
<ConnectionListView setConnectionType={setConnectionType} />
)}
</Modal>
</div>
)
) : formValue?.id ? (
<ConnectionSpecEditor
handleBack={() => setConnectionType(undefined)}
onConnectionSubmit={onConnectionSubmit}
onConnectionDelete={onConnectionDelete}
formValue={formValue}
className={className}
isSubmitting={isSubmitting}
isDeleting={isDeleting}
/>
) : (
<ConnectionListView setConnectionType={setConnectionType} />
)}
</Modal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PermissionTable } from "@flanksource-ui/api/types/permissions";
import FormikCheckbox from "@flanksource-ui/components/Forms/Formik/FormikCheckbox";
import FormikTextArea from "@flanksource-ui/components/Forms/Formik/FormikTextArea";
import FormikTextInput from "@flanksource-ui/components/Forms/Formik/FormikTextInput";
import CanEditResource from "@flanksource-ui/components/Settings/CanEditResource";
import {
toastError,
toastSuccess
Expand All @@ -29,7 +30,7 @@ import PermissionsSubjectControls from "./PermissionSubjectControls";
type PermissionFormProps = {
onClose: () => void;
isOpen: boolean;
data?: PermissionTable;
data?: Partial<PermissionTable>;
};

export default function PermissionForm({
Expand All @@ -43,7 +44,8 @@ export default function PermissionForm({
data?.config_id ||
data?.canary_id ||
data?.canary_id ||
data?.playbook_id
data?.playbook_id ||
data?.connection_id
);
}, [data]);

Expand Down Expand Up @@ -107,7 +109,7 @@ export default function PermissionForm({
config_id: data?.config_id,
canary_id: data?.canary_id,
playbook_id: data?.playbook_id,
deny: data?.deny,
deny: data?.deny ?? false,
description: data?.description,
connection_id: data?.connection_id,
created_at: data?.created_at,
Expand Down Expand Up @@ -147,34 +149,42 @@ export default function PermissionForm({
<FormikCheckbox name="deny" label="Is deny action" />
<FormikTextArea name="description" label="Description" />
</div>
<AuthorizationAccessCheck
resource={tables.permissions}
action="write"
<CanEditResource
id={data?.id}
name={"Permission"}
resourceType={"permissions"}
source={data?.source}
className="flex flex-col"
>
<div
className={clsx(
"flex items-center bg-gray-100 px-5 py-4",
data?.id ? "justify-between" : "justify-end"
)}
<AuthorizationAccessCheck
resource={tables.permissions}
action="write"
>
{data?.id && (
<DeletePermission
permissionId={data.id}
onDeleted={onClose}
<div
className={clsx(
"flex items-center bg-gray-100 px-5 py-4",
data?.id ? "justify-between" : "justify-end"
)}
>
{data?.id && (
<DeletePermission
permissionId={data.id}
onDeleted={onClose}
/>
)}
<Button
icon={
isLoading ? (
<FaSpinner className="animate-spin" />
) : undefined
}
type="submit"
text={data?.id ? "Save" : isLoading ? "Saving ..." : "Save"}
className="btn-primary"
/>
)}
<Button
icon={
isLoading ? (
<FaSpinner className="animate-spin" />
) : undefined
}
type="submit"
text={data?.id ? "Save" : isLoading ? "Saving ..." : "Save"}
className="btn-primary"
/>
</div>
</AuthorizationAccessCheck>
</div>
</AuthorizationAccessCheck>
</CanEditResource>
</Form>
</Formik>
</div>
Expand Down
34 changes: 32 additions & 2 deletions src/components/Permissions/PermissionsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,36 @@ import {
fetchPermissions,
FetchPermissionsInput
} from "@flanksource-ui/api/services/permissions";
import { PermissionAPIResponse } from "@flanksource-ui/api/types/permissions";
import {
PermissionAPIResponse,
PermissionTable
} from "@flanksource-ui/api/types/permissions";
import useReactTablePaginationState from "@flanksource-ui/ui/DataTable/Hooks/useReactTablePaginationState";
import { useQuery } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import { Button } from "..";
import PermissionForm from "./ManagePermissions/Forms/PermissionForm";
import PermissionsTable from "./PermissionsTable";

type PermissionsViewProps = {
permissionRequest: FetchPermissionsInput;
setIsLoading?: (isLoading: boolean) => void;
hideResourceColumn?: boolean;
newPermissionData?: Partial<PermissionTable>;
showAddPermission?: boolean;
};

export default function PermissionsView({
permissionRequest,
setIsLoading = () => {},
hideResourceColumn = false
hideResourceColumn = false,
newPermissionData,
showAddPermission = false
}: PermissionsViewProps) {
const [selectedPermission, setSelectedPermission] =
useState<PermissionAPIResponse>();
const { pageSize, pageIndex } = useReactTablePaginationState();
const [isPermissionModalOpen, setIsPermissionModalOpen] = useState(false);

const { isLoading, data, refetch } = useQuery({
queryKey: [
Expand Down Expand Up @@ -51,6 +60,17 @@ export default function PermissionsView({

return (
<>
{showAddPermission && (
<div className="flex flex-row items-center justify-between p-2">
<Button
onClick={() => {
setIsPermissionModalOpen(true);
}}
>
Add Permission
</Button>
</div>
)}
<PermissionsTable
permissions={permissions}
isLoading={isLoading}
Expand All @@ -69,6 +89,16 @@ export default function PermissionsView({
data={selectedPermission}
/>
)}
{showAddPermission && (
<PermissionForm
data={newPermissionData}
isOpen={isPermissionModalOpen}
onClose={() => {
setIsPermissionModalOpen(false);
refetch();
}}
/>
)}
</>
);
}
Loading

0 comments on commit b7b2b96

Please sign in to comment.