2023-01-16 10:23:07 +00:00
|
|
|
import {
|
|
|
|
Button,
|
2023-07-31 09:54:16 +00:00
|
|
|
Chip,
|
|
|
|
ChipGroup,
|
2023-01-16 10:23:07 +00:00
|
|
|
Dropdown,
|
|
|
|
DropdownItem,
|
|
|
|
KebabToggle,
|
|
|
|
OverflowMenu,
|
|
|
|
OverflowMenuContent,
|
|
|
|
OverflowMenuControl,
|
|
|
|
OverflowMenuDropdownItem,
|
|
|
|
OverflowMenuGroup,
|
|
|
|
OverflowMenuItem,
|
|
|
|
Spinner,
|
|
|
|
} from "@patternfly/react-core";
|
|
|
|
import {
|
|
|
|
EditAltIcon,
|
|
|
|
ExternalLinkAltIcon,
|
|
|
|
Remove2Icon,
|
|
|
|
ShareAltIcon,
|
|
|
|
} from "@patternfly/react-icons";
|
|
|
|
import {
|
|
|
|
ExpandableRowContent,
|
|
|
|
TableComposable,
|
|
|
|
Tbody,
|
|
|
|
Td,
|
|
|
|
Th,
|
|
|
|
Thead,
|
|
|
|
Tr,
|
|
|
|
} from "@patternfly/react-table";
|
|
|
|
import { useState } from "react";
|
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
|
|
|
import { fetchPermission, fetchResources, updatePermissions } from "../api";
|
|
|
|
import { getPermissionRequests } from "../api/methods";
|
|
|
|
import { Links } from "../api/parse-links";
|
|
|
|
import { Permission, Resource } from "../api/representations";
|
2023-02-07 11:29:30 +00:00
|
|
|
import { ContinueCancelModal, useAlerts } from "ui-shared";
|
2023-01-16 10:23:07 +00:00
|
|
|
import { usePromise } from "../utils/usePromise";
|
|
|
|
import { EditTheResource } from "./EditTheResource";
|
|
|
|
import { PermissionRequest } from "./PermissionRequest";
|
|
|
|
import { ResourceToolbar } from "./ResourceToolbar";
|
|
|
|
import { SharedWith } from "./SharedWith";
|
|
|
|
import { ShareTheResource } from "./ShareTheResource";
|
|
|
|
|
|
|
|
type PermissionDetail = {
|
|
|
|
contextOpen?: boolean;
|
|
|
|
rowOpen?: boolean;
|
|
|
|
shareDialogOpen?: boolean;
|
|
|
|
editDialogOpen?: boolean;
|
|
|
|
permissions?: Permission[];
|
|
|
|
};
|
|
|
|
|
2023-07-31 09:54:16 +00:00
|
|
|
type ResourcesTabProps = {
|
|
|
|
isShared?: boolean;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const ResourcesTab = ({ isShared = false }: ResourcesTabProps) => {
|
2023-01-16 10:23:07 +00:00
|
|
|
const { t } = useTranslation();
|
|
|
|
const { addAlert, addError } = useAlerts();
|
|
|
|
|
|
|
|
const [params, setParams] = useState<Record<string, string>>({
|
|
|
|
first: "0",
|
|
|
|
max: "5",
|
|
|
|
});
|
|
|
|
const [links, setLinks] = useState<Links | undefined>();
|
|
|
|
const [resources, setResources] = useState<Resource[]>();
|
|
|
|
const [details, setDetails] = useState<
|
|
|
|
Record<string, PermissionDetail | undefined>
|
|
|
|
>({});
|
|
|
|
const [key, setKey] = useState(1);
|
|
|
|
const refresh = () => setKey(key + 1);
|
|
|
|
|
|
|
|
usePromise(
|
|
|
|
async (signal) => {
|
2023-07-31 09:54:16 +00:00
|
|
|
const result = await fetchResources({ signal }, params, isShared);
|
|
|
|
if (!isShared)
|
|
|
|
await Promise.all(
|
|
|
|
result.data.map(
|
|
|
|
async (r) =>
|
|
|
|
(r.shareRequests = await getPermissionRequests(r._id, {
|
|
|
|
signal,
|
|
|
|
})),
|
|
|
|
),
|
|
|
|
);
|
2023-01-16 10:23:07 +00:00
|
|
|
return result;
|
|
|
|
},
|
|
|
|
({ data, links }) => {
|
|
|
|
setResources(data);
|
|
|
|
setLinks(links);
|
|
|
|
},
|
2023-07-11 14:03:21 +00:00
|
|
|
[params, key],
|
2023-01-16 10:23:07 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
if (!resources) {
|
|
|
|
return <Spinner />;
|
|
|
|
}
|
|
|
|
|
|
|
|
const fetchPermissions = async (id: string) => {
|
|
|
|
let permissions = details[id]?.permissions || [];
|
|
|
|
if (!details[id]) {
|
|
|
|
permissions = await fetchPermission({}, id);
|
|
|
|
}
|
|
|
|
return permissions;
|
|
|
|
};
|
|
|
|
|
|
|
|
const removeShare = async (resource: Resource) => {
|
|
|
|
try {
|
|
|
|
const permissions = (await fetchPermissions(resource._id)).map(
|
|
|
|
({ username }) =>
|
|
|
|
({
|
|
|
|
username,
|
|
|
|
scopes: [],
|
2023-07-11 14:03:21 +00:00
|
|
|
}) as Permission,
|
2023-01-16 10:23:07 +00:00
|
|
|
)!;
|
|
|
|
await updatePermissions(resource._id, permissions);
|
|
|
|
setDetails({});
|
2023-03-02 12:54:34 +00:00
|
|
|
addAlert(t("unShareSuccess"));
|
2023-01-16 10:23:07 +00:00
|
|
|
} catch (error) {
|
2023-03-02 12:54:34 +00:00
|
|
|
addError(t("unShareError", { error }).toString());
|
2023-01-16 10:23:07 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const toggleOpen = async (
|
|
|
|
id: string,
|
|
|
|
field: keyof PermissionDetail,
|
2023-07-11 14:03:21 +00:00
|
|
|
open: boolean,
|
2023-01-16 10:23:07 +00:00
|
|
|
) => {
|
|
|
|
const permissions = await fetchPermissions(id);
|
|
|
|
|
|
|
|
setDetails({
|
|
|
|
...details,
|
|
|
|
[id]: { ...details[id], [field]: open, permissions },
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<ResourceToolbar
|
|
|
|
onFilter={(name) => setParams({ ...params, name })}
|
|
|
|
count={resources.length}
|
|
|
|
first={parseInt(params["first"])}
|
|
|
|
max={parseInt(params["max"])}
|
|
|
|
onNextClick={() => setParams(links?.next || {})}
|
|
|
|
onPreviousClick={() => setParams(links?.prev || {})}
|
|
|
|
onPerPageSelect={(first, max) =>
|
|
|
|
setParams({ first: `${first}`, max: `${max}` })
|
|
|
|
}
|
|
|
|
hasNext={!!links?.next}
|
|
|
|
/>
|
|
|
|
<TableComposable aria-label={t("resources")}>
|
|
|
|
<Thead>
|
|
|
|
<Tr>
|
2023-06-30 17:24:35 +00:00
|
|
|
<Th aria-hidden="true" />
|
2023-01-16 10:23:07 +00:00
|
|
|
<Th>{t("resourceName")}</Th>
|
|
|
|
<Th>{t("application")}</Th>
|
2023-08-28 15:10:15 +00:00
|
|
|
<Th aria-hidden={isShared}>
|
|
|
|
{!isShared ? t("permissionRequests") : ""}
|
|
|
|
</Th>
|
2023-01-16 10:23:07 +00:00
|
|
|
</Tr>
|
|
|
|
</Thead>
|
|
|
|
{resources.map((resource, index) => (
|
|
|
|
<Tbody
|
|
|
|
key={resource.name}
|
|
|
|
isExpanded={details[resource._id]?.rowOpen}
|
|
|
|
>
|
|
|
|
<Tr>
|
|
|
|
<Td
|
2023-07-31 09:54:16 +00:00
|
|
|
expand={
|
|
|
|
!isShared
|
|
|
|
? {
|
|
|
|
isExpanded: details[resource._id]?.rowOpen || false,
|
|
|
|
rowIndex: index,
|
|
|
|
onToggle: () =>
|
|
|
|
toggleOpen(
|
|
|
|
resource._id,
|
|
|
|
"rowOpen",
|
|
|
|
!details[resource._id]?.rowOpen,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
: undefined
|
|
|
|
}
|
2023-01-16 10:23:07 +00:00
|
|
|
/>
|
2023-08-14 14:41:58 +00:00
|
|
|
<Td
|
|
|
|
dataLabel={t("resourceName")}
|
|
|
|
data-testid={`row[${index}].name`}
|
|
|
|
>
|
|
|
|
{resource.name}
|
|
|
|
</Td>
|
2023-01-16 10:23:07 +00:00
|
|
|
<Td dataLabel={t("application")}>
|
|
|
|
<a href={resource.client.baseUrl}>
|
|
|
|
{resource.client.name || resource.client.clientId}{" "}
|
|
|
|
<ExternalLinkAltIcon />
|
|
|
|
</a>
|
|
|
|
</Td>
|
|
|
|
<Td dataLabel={t("permissionRequests")}>
|
2023-07-31 09:54:16 +00:00
|
|
|
{resource.shareRequests &&
|
|
|
|
resource.shareRequests.length > 0 && (
|
|
|
|
<PermissionRequest
|
|
|
|
resource={resource}
|
|
|
|
refresh={() => refresh()}
|
|
|
|
/>
|
|
|
|
)}
|
2023-01-16 10:23:07 +00:00
|
|
|
<ShareTheResource
|
|
|
|
resource={resource}
|
|
|
|
permissions={details[resource._id]?.permissions}
|
|
|
|
open={details[resource._id]?.shareDialogOpen || false}
|
|
|
|
onClose={() => setDetails({})}
|
|
|
|
/>
|
|
|
|
{details[resource._id]?.editDialogOpen && (
|
|
|
|
<EditTheResource
|
|
|
|
resource={resource}
|
|
|
|
permissions={details[resource._id]?.permissions}
|
|
|
|
onClose={() => setDetails({})}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</Td>
|
2023-07-31 09:54:16 +00:00
|
|
|
{isShared ? (
|
|
|
|
<Td>
|
|
|
|
{resource.scopes.length > 0 && (
|
|
|
|
<ChipGroup categoryName={t("permissions")}>
|
|
|
|
{resource.scopes.map((scope) => (
|
|
|
|
<Chip key={scope.name} isReadOnly>
|
|
|
|
{scope.displayName || scope.name}
|
|
|
|
</Chip>
|
|
|
|
))}
|
|
|
|
</ChipGroup>
|
|
|
|
)}
|
|
|
|
</Td>
|
|
|
|
) : (
|
|
|
|
<Td isActionCell>
|
|
|
|
<OverflowMenu breakpoint="lg">
|
|
|
|
<OverflowMenuContent>
|
|
|
|
<OverflowMenuGroup groupType="button">
|
|
|
|
<OverflowMenuItem>
|
|
|
|
<Button
|
|
|
|
data-testid={`share-${resource.name}`}
|
|
|
|
variant="link"
|
|
|
|
onClick={() =>
|
|
|
|
toggleOpen(resource._id, "shareDialogOpen", true)
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<ShareAltIcon /> {t("share")}
|
|
|
|
</Button>
|
|
|
|
</OverflowMenuItem>
|
|
|
|
<OverflowMenuItem>
|
|
|
|
<Dropdown
|
|
|
|
position="right"
|
|
|
|
toggle={
|
|
|
|
<KebabToggle
|
|
|
|
onToggle={(open) =>
|
|
|
|
toggleOpen(resource._id, "contextOpen", open)
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
isOpen={details[resource._id]?.contextOpen}
|
|
|
|
isPlain
|
|
|
|
dropdownItems={[
|
|
|
|
<DropdownItem
|
|
|
|
key="edit"
|
|
|
|
isDisabled={
|
|
|
|
details[resource._id]?.permissions?.length ===
|
|
|
|
0
|
|
|
|
}
|
|
|
|
onClick={() =>
|
|
|
|
toggleOpen(
|
|
|
|
resource._id,
|
|
|
|
"editDialogOpen",
|
|
|
|
true,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<EditAltIcon /> {t("edit")}
|
|
|
|
</DropdownItem>,
|
|
|
|
<ContinueCancelModal
|
|
|
|
key="unShare"
|
|
|
|
buttonTitle={
|
|
|
|
<>
|
|
|
|
<Remove2Icon /> {t("unShare")}
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
modalTitle={t("unShare")}
|
|
|
|
continueLabel={t("confirm")}
|
|
|
|
cancelLabel={t("cancel")}
|
|
|
|
component={DropdownItem}
|
|
|
|
onContinue={() => removeShare(resource)}
|
|
|
|
isDisabled={
|
|
|
|
details[resource._id]?.permissions?.length ===
|
|
|
|
0
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{t("unShareAllConfirm")}
|
|
|
|
</ContinueCancelModal>,
|
|
|
|
]}
|
|
|
|
/>
|
|
|
|
</OverflowMenuItem>
|
|
|
|
</OverflowMenuGroup>
|
|
|
|
</OverflowMenuContent>
|
|
|
|
<OverflowMenuControl>
|
|
|
|
<Dropdown
|
|
|
|
position="right"
|
|
|
|
toggle={
|
|
|
|
<KebabToggle
|
|
|
|
onToggle={(open) =>
|
|
|
|
toggleOpen(resource._id, "contextOpen", open)
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
isOpen={details[resource._id]?.contextOpen}
|
|
|
|
isPlain
|
|
|
|
dropdownItems={[
|
|
|
|
<OverflowMenuDropdownItem
|
|
|
|
key="share"
|
|
|
|
isShared
|
|
|
|
onClick={() =>
|
|
|
|
toggleOpen(resource._id, "shareDialogOpen", true)
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<ShareAltIcon /> {t("share")}
|
|
|
|
</OverflowMenuDropdownItem>,
|
|
|
|
<OverflowMenuDropdownItem
|
|
|
|
key="edit"
|
|
|
|
isShared
|
|
|
|
onClick={() =>
|
|
|
|
toggleOpen(resource._id, "editDialogOpen", true)
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<EditAltIcon /> {t("edit")}
|
|
|
|
</OverflowMenuDropdownItem>,
|
|
|
|
<ContinueCancelModal
|
|
|
|
key="unShare"
|
|
|
|
buttonTitle={
|
|
|
|
<>
|
|
|
|
<Remove2Icon /> {t("unShare")}
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
modalTitle={t("unShare")}
|
|
|
|
continueLabel={t("confirm")}
|
|
|
|
cancelLabel={t("cancel")}
|
|
|
|
component={OverflowMenuDropdownItem}
|
|
|
|
onContinue={() => removeShare(resource)}
|
|
|
|
isDisabled={
|
|
|
|
details[resource._id]?.permissions?.length === 0
|
|
|
|
}
|
|
|
|
>
|
|
|
|
{t("unShareAllConfirm")}
|
|
|
|
</ContinueCancelModal>,
|
|
|
|
]}
|
|
|
|
/>
|
|
|
|
</OverflowMenuControl>
|
|
|
|
</OverflowMenu>
|
|
|
|
</Td>
|
|
|
|
)}
|
2023-01-16 10:23:07 +00:00
|
|
|
</Tr>
|
|
|
|
<Tr isExpanded={details[resource._id]?.rowOpen || false}>
|
|
|
|
<Td colSpan={4} textCenter>
|
|
|
|
<ExpandableRowContent>
|
|
|
|
<SharedWith
|
|
|
|
permissions={details[resource._id]?.permissions}
|
|
|
|
/>
|
|
|
|
</ExpandableRowContent>
|
|
|
|
</Td>
|
|
|
|
</Tr>
|
|
|
|
</Tbody>
|
|
|
|
))}
|
|
|
|
</TableComposable>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|