Add frameworks listing and import
Signed-off-by: Bryan Frimin <bryan@getprobo.com> Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
committed by
Sacha Al Himdani
parent
7cb84d8ce8
commit
1870e050ce
75
apps/console2/src/hooks/graph/FrameworkGraph.ts
Normal file
75
apps/console2/src/hooks/graph/FrameworkGraph.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useMutationWithToasts } from "../useMutationWithToasts";
|
||||
import { useCallback } from "react";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useConfirm } from "@probo/ui";
|
||||
|
||||
export const frameworksQuery = graphql`
|
||||
query FrameworkGraphListQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
... on Organization {
|
||||
id
|
||||
frameworks(first: 100)
|
||||
@connection(key: "FrameworksListQuery_frameworks") {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...FrameworksPageCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const deleteFrameworkMutation = graphql`
|
||||
mutation FrameworkGraphDeleteMutation(
|
||||
$input: DeleteFrameworkInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deleteFramework(input: $input) {
|
||||
deletedFrameworkId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const useDeleteFrameworkMutation = (
|
||||
framework: { id: string; name: string },
|
||||
connectionId: string
|
||||
) => {
|
||||
const [commitDelete, isDeleting] = useMutationWithToasts(
|
||||
deleteFrameworkMutation,
|
||||
{
|
||||
errorMessage: "Failed to delete framework",
|
||||
successMessage: "Framework deleted successfully",
|
||||
}
|
||||
);
|
||||
const confirm = useConfirm();
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return useCallback(() => {
|
||||
confirm(
|
||||
() => {
|
||||
return commitDelete({
|
||||
variables: {
|
||||
input: {
|
||||
frameworkId: framework.id!,
|
||||
},
|
||||
connections: [connectionId],
|
||||
},
|
||||
});
|
||||
},
|
||||
{
|
||||
message: sprintf(
|
||||
__(
|
||||
'This will permanently delete framework "%s". This action cannot be undone.'
|
||||
),
|
||||
framework.name
|
||||
),
|
||||
}
|
||||
);
|
||||
}, [framework, connectionId, commitDelete]);
|
||||
};
|
||||
132
apps/console2/src/hooks/graph/__generated__/FrameworkGraphDeleteMutation.graphql.ts
generated
Normal file
132
apps/console2/src/hooks/graph/__generated__/FrameworkGraphDeleteMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @generated SignedSource<<05d197ce59adad67cfb89dff4c502c58>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DeleteFrameworkInput = {
|
||||
frameworkId: string;
|
||||
};
|
||||
export type FrameworkGraphDeleteMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: DeleteFrameworkInput;
|
||||
};
|
||||
export type FrameworkGraphDeleteMutation$data = {
|
||||
readonly deleteFramework: {
|
||||
readonly deletedFrameworkId: string;
|
||||
};
|
||||
};
|
||||
export type FrameworkGraphDeleteMutation = {
|
||||
response: FrameworkGraphDeleteMutation$data;
|
||||
variables: FrameworkGraphDeleteMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "connections"
|
||||
},
|
||||
v1 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
},
|
||||
v2 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "deletedFrameworkId",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworkGraphDeleteMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteFrameworkPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteFramework",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkGraphDeleteMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteFrameworkPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteFramework",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "deleteEdge",
|
||||
"key": "",
|
||||
"kind": "ScalarHandle",
|
||||
"name": "deletedFrameworkId",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "761a391f497aa0e2e03d0db958089712",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkGraphDeleteMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation FrameworkGraphDeleteMutation(\n $input: DeleteFrameworkInput!\n) {\n deleteFramework(input: $input) {\n deletedFrameworkId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "6ebefeb003ce3a27bd134651e1b5c94b";
|
||||
|
||||
export default node;
|
||||
302
apps/console2/src/hooks/graph/__generated__/FrameworkGraphListQuery.graphql.ts
generated
Normal file
302
apps/console2/src/hooks/graph/__generated__/FrameworkGraphListQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* @generated SignedSource<<a67eb2a88e39d7eaf96910e0318dd1bc>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type FrameworkGraphListQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
export type FrameworkGraphListQuery$data = {
|
||||
readonly organization: {
|
||||
readonly frameworks?: {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"FrameworksPageCardFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly id?: string;
|
||||
};
|
||||
};
|
||||
export type FrameworkGraphListQuery = {
|
||||
response: FrameworkGraphListQuery$data;
|
||||
variables: FrameworkGraphListQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "organizationId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "organizationId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
v7 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworkGraphListQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": "frameworks",
|
||||
"args": null,
|
||||
"concreteType": "FrameworkConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__FrameworksListQuery_frameworks_connection",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "FrameworkEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Framework",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "FrameworksPageCardFragment"
|
||||
},
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkGraphListQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v7/*: any*/),
|
||||
"concreteType": "FrameworkConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "frameworks",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "FrameworkEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Framework",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/),
|
||||
(v6/*: any*/)
|
||||
],
|
||||
"storageKey": "frameworks(first:100)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v7/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "FrameworksListQuery_frameworks",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "frameworks"
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "b31afd68395ae800a18885bca3abfa1c",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
{
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": [
|
||||
"organization",
|
||||
"frameworks"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "FrameworkGraphListQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query FrameworkGraphListQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n frameworks(first: 100) {\n edges {\n node {\n id\n ...FrameworksPageCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "4aaead183328e669aba942992db6f89c";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,219 @@
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
ActionDropdown,
|
||||
Avatar,
|
||||
Button,
|
||||
Card,
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
FileButton,
|
||||
FrameworkSelector,
|
||||
IconChevronDown,
|
||||
IconPlusLarge,
|
||||
IconTrashCan,
|
||||
IconUpload,
|
||||
PageHeader,
|
||||
useDialogRef,
|
||||
} from "@probo/ui";
|
||||
import {
|
||||
graphql,
|
||||
useFragment,
|
||||
usePreloadedQuery,
|
||||
type PreloadedQuery,
|
||||
} from "react-relay";
|
||||
import type { FrameworkGraphListQuery } from "/hooks/graph/__generated__/FrameworkGraphListQuery.graphql";
|
||||
import {
|
||||
frameworksQuery,
|
||||
useDeleteFrameworkMutation,
|
||||
} from "/hooks/graph/FrameworkGraph";
|
||||
import { Link } from "react-router";
|
||||
import type { FrameworksPageCardFragment$key } from "./__generated__/FrameworksPageCardFragment.graphql";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { useState, type ChangeEventHandler } from "react";
|
||||
import { availableFrameworks } from "@probo/helpers";
|
||||
import { CreateFrameworkDialog } from "./dialogs/CreateFrameworkDialog";
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<FrameworkGraphListQuery>;
|
||||
};
|
||||
|
||||
const importFrameworkMutation = graphql`
|
||||
mutation FrameworksPageImportMutation(
|
||||
$input: ImportFrameworkInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
importFramework(input: $input) {
|
||||
frameworkEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
...FrameworksPageCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const availableLogos = new Map(
|
||||
availableFrameworks.map((framework) => [framework.name, framework.logo])
|
||||
);
|
||||
|
||||
export default function FrameworksPage(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
usePageTitle(__("Frameworks"));
|
||||
const data = usePreloadedQuery(frameworksQuery, props.queryRef);
|
||||
const connectionId = data.organization.frameworks!.__id;
|
||||
const frameworks =
|
||||
data.organization.frameworks?.edges.map((edge) => edge.node) ?? [];
|
||||
const [commitImport, isImporting] = useMutationWithToasts(
|
||||
importFrameworkMutation,
|
||||
{
|
||||
successMessage: __("Framework imported successfully"),
|
||||
errorMessage: __("Failed to import framework"),
|
||||
}
|
||||
);
|
||||
const [isUploading, setUploading] = useState(false);
|
||||
const dialogRef = useDialogRef();
|
||||
|
||||
const importNamedFramework = async (name: string) => {
|
||||
// For custom framework, open the form
|
||||
if (name === "custom") {
|
||||
console.log(name, dialogRef);
|
||||
dialogRef.current?.open();
|
||||
return;
|
||||
}
|
||||
// Otherwise load the JSON and send the file to the server
|
||||
try {
|
||||
setUploading(true);
|
||||
const fileName = `${name}.json`;
|
||||
const json = await fetch(`/data/frameworks/${fileName}`).then((res) =>
|
||||
res.text()
|
||||
);
|
||||
const file = new File([json], fileName, {
|
||||
type: "application/json",
|
||||
});
|
||||
await importFile(file);
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const importFile = (file: File) => {
|
||||
return commitImport({
|
||||
variables: {
|
||||
input: {
|
||||
organizationId: data.organization.id!,
|
||||
file: null,
|
||||
},
|
||||
connections: [connectionId],
|
||||
},
|
||||
uploadables: {
|
||||
"input.file": file,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpload: ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
const input = event.currentTarget;
|
||||
const file = input.files?.[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
importFile(file).finally(() => {
|
||||
input.value = "";
|
||||
});
|
||||
};
|
||||
|
||||
const isLoading = isUploading || isImporting;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<CreateFrameworkDialog
|
||||
ref={dialogRef}
|
||||
connectionId={connectionId}
|
||||
organizationId={data.organization.id!}
|
||||
/>
|
||||
<PageHeader
|
||||
title={__("Frameworks")}
|
||||
description={__("Manage your compliance frameworks")}
|
||||
>
|
||||
<FileButton
|
||||
variant="secondary"
|
||||
icon={IconUpload}
|
||||
onChange={handleUpload}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{__("Import")}
|
||||
</FileButton>
|
||||
<FrameworkSelector
|
||||
onSelect={importNamedFramework}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</PageHeader>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{frameworks.map((framework) => (
|
||||
<FrameworkCard
|
||||
organizationId={data.organization.id!}
|
||||
connectionId={connectionId}
|
||||
key={framework.id}
|
||||
framework={framework}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const frameworkCardFragment = graphql`
|
||||
fragment FrameworksPageCardFragment on Framework {
|
||||
id
|
||||
name
|
||||
description
|
||||
}
|
||||
`;
|
||||
|
||||
type FrameworkCardProps = {
|
||||
organizationId: string;
|
||||
connectionId: string;
|
||||
framework: FrameworksPageCardFragment$key;
|
||||
};
|
||||
|
||||
function FrameworkCard(props: FrameworkCardProps) {
|
||||
const framework = useFragment(frameworkCardFragment, props.framework);
|
||||
const deleteFramework = useDeleteFrameworkMutation(
|
||||
framework,
|
||||
props.connectionId
|
||||
);
|
||||
const { __ } = useTranslate();
|
||||
const logo = availableLogos.get(framework.name);
|
||||
return (
|
||||
<Card padded className="p-6 bg-white rounded shadow relative">
|
||||
<div className="flex justify-between mb-3">
|
||||
{logo ? (
|
||||
<img src={logo} alt="" className="size-12" />
|
||||
) : (
|
||||
<Avatar name={framework.name} size="l" className="size-12" />
|
||||
)}
|
||||
<ActionDropdown className="z-10 relative">
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
onClick={deleteFramework}
|
||||
variant="danger"
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</div>
|
||||
<h2 className="text-xl font-medium">
|
||||
<Link
|
||||
className="hover:underline after:absolute after:content-[''] after:inset-0"
|
||||
to={`/organizations/${props.organizationId}/frameworks/${framework.id}`}
|
||||
>
|
||||
{framework.name}
|
||||
</Link>
|
||||
</h2>
|
||||
<p className="text-sm text-txt-secondary">{framework.description}</p>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
58
apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageCardFragment.graphql.ts
generated
Normal file
58
apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageCardFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @generated SignedSource<<ec6bd0c6fff930b82216d940e83d8ef0>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type FrameworksPageCardFragment$data = {
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly " $fragmentType": "FrameworksPageCardFragment";
|
||||
};
|
||||
export type FrameworksPageCardFragment$key = {
|
||||
readonly " $data"?: FrameworksPageCardFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"FrameworksPageCardFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworksPageCardFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Framework",
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "4481f380673963e72f88071031e37d14";
|
||||
|
||||
export default node;
|
||||
202
apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageImportMutation.graphql.ts
generated
Normal file
202
apps/console2/src/pages/organizations/frameworks/__generated__/FrameworksPageImportMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @generated SignedSource<<9436ef052ea63a9785d82ec810e810f1>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type ImportFrameworkInput = {
|
||||
file: any;
|
||||
organizationId: string;
|
||||
};
|
||||
export type FrameworksPageImportMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: ImportFrameworkInput;
|
||||
};
|
||||
export type FrameworksPageImportMutation$data = {
|
||||
readonly importFramework: {
|
||||
readonly frameworkEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"FrameworksPageCardFragment">;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type FrameworksPageImportMutation = {
|
||||
response: FrameworksPageImportMutation$data;
|
||||
variables: FrameworksPageImportMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "connections"
|
||||
},
|
||||
v1 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
},
|
||||
v2 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworksPageImportMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "ImportFrameworkPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "importFramework",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "FrameworkEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "frameworkEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Framework",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "FrameworksPageCardFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "FrameworksPageImportMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "ImportFrameworkPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "importFramework",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "FrameworkEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "frameworkEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Framework",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "frameworkEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "87a38788cb2e060657b552af69acb707",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworksPageImportMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation FrameworksPageImportMutation(\n $input: ImportFrameworkInput!\n) {\n importFramework(input: $input) {\n frameworkEdge {\n node {\n id\n ...FrameworksPageCardFragment\n }\n }\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "e2ff0f6d6780cf967a021a093542dab9";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,105 @@
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
Field,
|
||||
Input,
|
||||
Textarea,
|
||||
useDialogRef,
|
||||
type DialogRef,
|
||||
} from "@probo/ui";
|
||||
import type { RefObject } from "react";
|
||||
import { z } from "zod";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
|
||||
const createFrameworkMutation = graphql`
|
||||
mutation CreateFrameworkDialogMutation(
|
||||
$input: CreateFrameworkInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createFramework(input: $input) {
|
||||
frameworkEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
...FrameworksPageCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
connectionId: string;
|
||||
organizationId: string;
|
||||
ref: DialogRef;
|
||||
};
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1).max(255),
|
||||
description: z.string().max(255).optional(),
|
||||
});
|
||||
|
||||
export function CreateFrameworkDialog(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
reset,
|
||||
} = useFormWithSchema(schema, {});
|
||||
const [commitCreate, isCreating] = useMutationWithToasts(
|
||||
createFrameworkMutation,
|
||||
{
|
||||
successMessage: __("Framework created successfully"),
|
||||
errorMessage: __("Failed to create framework"),
|
||||
}
|
||||
);
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
await commitCreate({
|
||||
variables: {
|
||||
input: {
|
||||
...data,
|
||||
organizationId: props.organizationId,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
reset();
|
||||
props.ref.current?.close();
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
defaultOpen
|
||||
ref={props.ref}
|
||||
title={<Breadcrumb items={[__("Framework"), __("New Framework")]} />}
|
||||
>
|
||||
<form onSubmit={onSubmit}>
|
||||
<DialogContent padded className="space-y-4">
|
||||
<Input
|
||||
{...register("name")}
|
||||
variant="title"
|
||||
required
|
||||
placeholder={__("Framework title")}
|
||||
/>
|
||||
<Textarea
|
||||
{...register("description")}
|
||||
variant="ghost"
|
||||
autogrow
|
||||
placeholder={__("Add description")}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
<Button type="submit" disabled={isCreating}>
|
||||
{__("Create framework")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* @generated SignedSource<<a91c1ee7ea4934d7d6e524f32462832a>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type CreateFrameworkInput = {
|
||||
description: string;
|
||||
name: string;
|
||||
organizationId: string;
|
||||
};
|
||||
export type CreateFrameworkDialogMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreateFrameworkInput;
|
||||
};
|
||||
export type CreateFrameworkDialogMutation$data = {
|
||||
readonly createFramework: {
|
||||
readonly frameworkEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"FrameworksPageCardFragment">;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type CreateFrameworkDialogMutation = {
|
||||
response: CreateFrameworkDialogMutation$data;
|
||||
variables: CreateFrameworkDialogMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "connections"
|
||||
},
|
||||
v1 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
},
|
||||
v2 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "CreateFrameworkDialogMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateFrameworkPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createFramework",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "FrameworkEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "frameworkEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Framework",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "FrameworksPageCardFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "CreateFrameworkDialogMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateFrameworkPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createFramework",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "FrameworkEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "frameworkEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Framework",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "frameworkEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "d08bc5a14bbf8d131029da890bdab850",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "CreateFrameworkDialogMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation CreateFrameworkDialogMutation(\n $input: CreateFrameworkInput!\n) {\n createFramework(input: $input) {\n frameworkEdge {\n node {\n id\n ...FrameworksPageCardFragment\n }\n }\n }\n}\n\nfragment FrameworksPageCardFragment on Framework {\n id\n name\n description\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "957d00afa55875c9a6879292e8fa7bb1";
|
||||
|
||||
export default node;
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
Button,
|
||||
IconPlusLarge,
|
||||
useConfirm,
|
||||
ActionDropdown,
|
||||
DropdownItem,
|
||||
} from "@probo/ui";
|
||||
import {
|
||||
useFragment,
|
||||
@@ -157,14 +159,11 @@ function PolicyRow({
|
||||
const handleDelete = () => {
|
||||
confirm(
|
||||
() =>
|
||||
new Promise<void>((resolve) => {
|
||||
deletePolicy({
|
||||
variables: {
|
||||
input: { policyId: policy.id },
|
||||
connections: [connectionId],
|
||||
},
|
||||
onCompleted: () => resolve(),
|
||||
});
|
||||
deletePolicy({
|
||||
variables: {
|
||||
input: { policyId: policy.id },
|
||||
connections: [connectionId],
|
||||
},
|
||||
}),
|
||||
{
|
||||
message: sprintf(
|
||||
@@ -178,51 +177,52 @@ function PolicyRow({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tr to={`/organizations/${organizationId}/policies/${policy.id}`}>
|
||||
<Td>
|
||||
<div className="flex gap-4 items-center">
|
||||
<img
|
||||
src="/policy.png"
|
||||
alt=""
|
||||
width={28}
|
||||
height={36}
|
||||
className="border-4 border-highlight rounded box-content"
|
||||
/>
|
||||
{policy.title}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<Badge variant={isDraft ? "neutral" : "success"}>
|
||||
{isDraft ? __("Draft") : __("Published")}
|
||||
</Badge>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar name={policy.owner.fullName} />
|
||||
{policy.owner.fullName}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{dateFormat(policy.updatedAt, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
weekday: "short",
|
||||
})}
|
||||
</Td>
|
||||
<Td>
|
||||
{signedCount}/{signatures.length}
|
||||
</Td>
|
||||
<Td noLink width={50} className="text-end">
|
||||
<Button
|
||||
icon={IconTrashCan}
|
||||
variant="danger"
|
||||
onClick={handleDelete}
|
||||
disabled={isDeleting}
|
||||
<Tr to={`/organizations/${organizationId}/policies/${policy.id}`}>
|
||||
<Td>
|
||||
<div className="flex gap-4 items-center">
|
||||
<img
|
||||
src="/policy.png"
|
||||
alt=""
|
||||
width={28}
|
||||
height={36}
|
||||
className="border-4 border-highlight rounded box-content"
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
</>
|
||||
{policy.title}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<Badge variant={isDraft ? "neutral" : "success"}>
|
||||
{isDraft ? __("Draft") : __("Published")}
|
||||
</Badge>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Avatar name={policy.owner.fullName} />
|
||||
{policy.owner.fullName}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
{dateFormat(policy.updatedAt, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
weekday: "short",
|
||||
})}
|
||||
</Td>
|
||||
<Td>
|
||||
{signedCount}/{signatures.length}
|
||||
</Td>
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
icon={IconTrashCan}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { policiesRoutes } from "./routes/policiesRoutes.ts";
|
||||
import { vendorRoutes } from "./routes/vendorRoutes.ts";
|
||||
import { organizationViewQuery } from "./hooks/graph/OrganizationGraph.ts";
|
||||
import { peopleRoutes } from "./routes/peopleRoutes.ts";
|
||||
import { frameworkRoutes } from "./routes/frameworkRoutes.ts";
|
||||
|
||||
function ErrorBoundary() {
|
||||
const error = useRouteError();
|
||||
@@ -85,6 +86,7 @@ const routes = [
|
||||
...policiesRoutes,
|
||||
...peopleRoutes,
|
||||
...vendorRoutes,
|
||||
...frameworkRoutes,
|
||||
],
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
|
||||
18
apps/console2/src/routes/frameworkRoutes.ts
Normal file
18
apps/console2/src/routes/frameworkRoutes.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { loadQuery } from "react-relay";
|
||||
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
|
||||
import { relayEnvironment } from "/providers/RelayProviders";
|
||||
import { frameworksQuery } from "/hooks/graph/FrameworkGraph";
|
||||
import type { AppRoute } from "/routes";
|
||||
import { lazy } from "react";
|
||||
|
||||
export const frameworkRoutes = [
|
||||
{
|
||||
path: "frameworks",
|
||||
fallback: PageSkeleton,
|
||||
queryLoader: ({ organizationId }) =>
|
||||
loadQuery(relayEnvironment, frameworksQuery, { organizationId }),
|
||||
Component: lazy(
|
||||
() => import("/pages/organizations/frameworks/FrameworksPage")
|
||||
),
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
Reference in New Issue
Block a user