implemented access object but didn't add the file to git haha silly me.
This commit is contained in:
parent
eff8a544ba
commit
b0274ad639
2 changed files with 535 additions and 0 deletions
474
src/user/AccessObject.tsx
Normal file
474
src/user/AccessObject.tsx
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
import {
|
||||
Avatar,
|
||||
AppBar,
|
||||
Box,
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemAvatar,
|
||||
Toolbar,
|
||||
Tooltip,
|
||||
Typography,
|
||||
ToggleButtonGroup,
|
||||
ToggleButton
|
||||
} from "@mui/material";
|
||||
import {Close as CloseIcon} from "@mui/icons-material";
|
||||
// import { ToggleButton, ToggleButtonGroup } from "@mui";
|
||||
import ResponsiveDialog from "common/ResponsiveDialog";
|
||||
import SearchBar from "common/SearchBar";
|
||||
import { or } from "utils";
|
||||
import DeviceOverview from "device/DeviceOverview";
|
||||
import GroupOverview from "group/GroupOverview";
|
||||
import { useMobile, usePrevious, useSnackbar } from "hooks";
|
||||
import { Device, Group, Team, newScope } from "models";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import {
|
||||
useDeviceAPI,
|
||||
useGlobalState,
|
||||
useGroupAPI,
|
||||
usePermissionAPI,
|
||||
useTeamAPI,
|
||||
useUserAPI
|
||||
} from "providers";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import InfiniteScroll from "react-infinite-scroller";
|
||||
// import { useHistory } from "react-router";
|
||||
import { capitalize } from "utils/strings";
|
||||
import { SupervisedUserCircle as TeamIcon } from "@mui/icons-material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
closeDialogCallback: () => void;
|
||||
}
|
||||
|
||||
const REQUEST_LIMIT = 25;
|
||||
|
||||
export default function AccessObject(props: Props) {
|
||||
const [{ user }, dispatch] = useGlobalState();
|
||||
// const history = useHistory();
|
||||
const navigate = useNavigate()
|
||||
const isMobile = useMobile();
|
||||
const { error, info } = useSnackbar();
|
||||
const permissionAPI = usePermissionAPI();
|
||||
const deviceAPI = useDeviceAPI();
|
||||
const groupAPI = useGroupAPI();
|
||||
const teamAPI = useTeamAPI();
|
||||
const userAPI = useUserAPI();
|
||||
const { isOpen, closeDialogCallback } = props;
|
||||
const prevOpen = usePrevious(isOpen);
|
||||
const [kind, setKind] = useState<string>("device");
|
||||
const prevKind = usePrevious(kind);
|
||||
const [searchValue, setSearchValue] = useState<string>("");
|
||||
const prevSearchValue = usePrevious(searchValue);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [devices, setDevices] = useState<Device[]>([]);
|
||||
const [groups, setGroups] = useState<Group[]>([]);
|
||||
const [teams, setTeams] = useState<Team[]>([]);
|
||||
const [groupsOffset, setGroupsOffset] = useState<number>(0);
|
||||
const [groupsTotal, setGroupsTotal] = useState<number>(0);
|
||||
const [teamsOffset, setTeamsOffset] = useState<number>(0);
|
||||
const [teamsTotal, setTeamsTotal] = useState<number>(0);
|
||||
const [devicesOffset, setDevicesOffset] = useState<number>(0);
|
||||
const [devicesTotal, setDevicesTotal] = useState<number>(0);
|
||||
|
||||
const close = () => {
|
||||
closeDialogCallback();
|
||||
};
|
||||
|
||||
const getAccess = (key: number | string, name: string, url: string, useImitation = false) => {
|
||||
user.settings.useTeam = false;
|
||||
dispatch({ key: "as", value: "" });
|
||||
userAPI.updateUser(user.id(), user.protobuf()).then(() => {
|
||||
info("Will no longer view as team by default");
|
||||
permissionAPI
|
||||
.shareObject(
|
||||
newScope(kind, key.toString()),
|
||||
user.settings.email,
|
||||
[
|
||||
pond.Permission.PERMISSION_USERS,
|
||||
pond.Permission.PERMISSION_READ,
|
||||
pond.Permission.PERMISSION_WRITE,
|
||||
pond.Permission.PERMISSION_SHARE
|
||||
],
|
||||
useImitation
|
||||
)
|
||||
.then((_response: any) => {
|
||||
// history.push(url);
|
||||
navigate(url)
|
||||
close();
|
||||
})
|
||||
.catch((_err: any) => {
|
||||
error("Failed to grant you access to " + name);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const load = useCallback(() => {
|
||||
setLoading(true);
|
||||
|
||||
kind === "group"
|
||||
? groupAPI
|
||||
.listGroups(REQUEST_LIMIT, 0, "asc", undefined, searchValue, true)
|
||||
.then((response: any) => {
|
||||
let rGroupsOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
|
||||
let rGroupsTotal: number = response.data.total ? response.data.total : 0;
|
||||
let groups: Group[] = or(response.data.groups, []).map((g: any) => {
|
||||
return Group.create(pond.Group.fromObject(g));
|
||||
});
|
||||
setGroups(groups);
|
||||
setGroupsOffset(rGroupsOffset);
|
||||
setGroupsTotal(rGroupsTotal);
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
setGroups([]);
|
||||
setGroupsOffset(0);
|
||||
setGroupsTotal(0);
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
: kind === "team"
|
||||
? teamAPI
|
||||
.listTeams(REQUEST_LIMIT, 0, "asc", undefined, searchValue, true)
|
||||
.then(response => {
|
||||
let rTeamsOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
|
||||
let rTeamsTotal: number = response.data.total ? response.data.total : 0;
|
||||
let teams: Team[] = or(response.data.teams, []).map((g: any) => {
|
||||
return Team.create(pond.Team.fromObject(g));
|
||||
});
|
||||
setTeams(teams);
|
||||
setTeamsOffset(rTeamsOffset);
|
||||
setTeamsTotal(rTeamsTotal);
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
: deviceAPI
|
||||
.list(
|
||||
REQUEST_LIMIT,
|
||||
0,
|
||||
"asc",
|
||||
undefined,
|
||||
searchValue,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
.then((response: any) => {
|
||||
let rDevicesOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
|
||||
let rDevicesTotal: number = response.data.total ? response.data.total : 0;
|
||||
let devices: Device[] = or(response.data.devices, []).map((d: any) => {
|
||||
return Device.create(pond.Device.fromObject(d));
|
||||
});
|
||||
setDevices(devices);
|
||||
setDevicesTotal(rDevicesTotal);
|
||||
setDevicesOffset(rDevicesOffset);
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
setDevices([]);
|
||||
setDevicesOffset(0);
|
||||
setDevicesTotal(0);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, [deviceAPI, groupAPI, teamAPI, kind, searchValue]);
|
||||
|
||||
const loadMoreDevices = useCallback(() => {
|
||||
deviceAPI
|
||||
.list(
|
||||
REQUEST_LIMIT,
|
||||
devicesOffset,
|
||||
"asc",
|
||||
undefined,
|
||||
searchValue,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
.then((response: any) => {
|
||||
let rDevicesOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
|
||||
let rDevicesTotal: number = response.data.total ? response.data.total : 0;
|
||||
let moreDevices: Device[] = or(response.data.devices, []).map((d: any) => {
|
||||
return Device.create(pond.Device.fromObject(d));
|
||||
});
|
||||
setDevices([...new Set([...devices, ...moreDevices])]);
|
||||
setDevicesTotal(rDevicesTotal);
|
||||
setDevicesOffset(rDevicesOffset);
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
setDevices([]);
|
||||
setDevicesOffset(0);
|
||||
setDevicesTotal(0);
|
||||
});
|
||||
}, [deviceAPI, devicesOffset, searchValue, devices]);
|
||||
|
||||
const loadMoreGroups = useCallback(() => {
|
||||
groupAPI
|
||||
.listGroups(REQUEST_LIMIT, groupsOffset, "asc", undefined, searchValue, true)
|
||||
.then((response: any) => {
|
||||
let rGroupsOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
|
||||
let rGroupsTotal: number = response.data.total ? response.data.total : 0;
|
||||
let moreGroups: Group[] = or(response.data.groups, []).map((g: any) => {
|
||||
return Group.create(pond.Group.fromObject(g));
|
||||
});
|
||||
setGroups([...new Set([...groups, ...moreGroups])]);
|
||||
setGroupsOffset(rGroupsOffset);
|
||||
setGroupsTotal(rGroupsTotal);
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
setGroups([]);
|
||||
setGroupsOffset(0);
|
||||
setGroupsTotal(0);
|
||||
});
|
||||
}, [groupAPI, groupsOffset, searchValue, groups]);
|
||||
|
||||
const loadMoreTeams = useCallback(() => {
|
||||
teamAPI
|
||||
.listTeams(REQUEST_LIMIT, teamsOffset, "asc", undefined, searchValue, true)
|
||||
.then((response: any) => {
|
||||
let rTeamsOffset: number = response.data.nextOffset ? response.data.nextOffset : 0;
|
||||
let rTeamsTotal: number = response.data.total ? response.data.total : 0;
|
||||
let moreTeams: Team[] = or(response.data.teams, []).map((g: any) => {
|
||||
return Team.create(pond.Team.fromObject(g));
|
||||
});
|
||||
setTeams([...new Set([...teams, ...moreTeams])]);
|
||||
setTeamsOffset(rTeamsOffset);
|
||||
setTeamsTotal(rTeamsTotal);
|
||||
})
|
||||
.catch((_error: any) => {
|
||||
setTeams([]);
|
||||
setTeamsOffset(0);
|
||||
setTeamsTotal(0);
|
||||
});
|
||||
}, [teamAPI, teamsOffset, searchValue, teams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && (prevSearchValue !== searchValue || prevKind !== kind || isOpen !== prevOpen)) {
|
||||
load();
|
||||
}
|
||||
}, [
|
||||
prevSearchValue,
|
||||
searchValue,
|
||||
load,
|
||||
kind,
|
||||
prevKind,
|
||||
loadMoreDevices,
|
||||
loadMoreGroups,
|
||||
isOpen,
|
||||
prevOpen
|
||||
]);
|
||||
|
||||
const groupURL = (group: Group) => {
|
||||
return "/groups/" + group.id().toString();
|
||||
};
|
||||
|
||||
const groupItem = (g: Group) => {
|
||||
return (
|
||||
<ListItem
|
||||
key={g.id()}
|
||||
divider
|
||||
onClick={() => getAccess(g.id(), g.name(), groupURL(g))}>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Tooltip title={"ID: " + g.id()}>
|
||||
<span>{g.name()}</span>
|
||||
</Tooltip>
|
||||
}
|
||||
secondaryTypographyProps={{ component: "span" }}
|
||||
secondary={<GroupOverview group={g} variant="row" />}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
const deviceURL = (device: Device) => {
|
||||
return "/devices/" + device.id().toString();
|
||||
};
|
||||
|
||||
const teamURL = (team: Team) => {
|
||||
return "/teams/" + team.key();
|
||||
};
|
||||
|
||||
const deviceItem = (d: Device) => {
|
||||
return (
|
||||
<ListItem
|
||||
key={d.id()}
|
||||
divider
|
||||
onClick={() => getAccess(d.id(), d.name(), deviceURL(d))}>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Tooltip title={"ID: " + d.id()}>
|
||||
<span>{d.name()}</span>
|
||||
</Tooltip>
|
||||
}
|
||||
secondaryTypographyProps={{ component: "span" }}
|
||||
secondary={<DeviceOverview device={d} components={[]} disableAddTag />}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
const teamItem = (d: Team) => {
|
||||
return (
|
||||
<ListItem
|
||||
key={d.key()}
|
||||
divider
|
||||
onClick={() => getAccess(d.key(), d.name(), teamURL(d), false)}>
|
||||
<ListItemAvatar>
|
||||
<Avatar src={d.settings.avatar}>
|
||||
{/* Draw the team icon id src icon errors */}
|
||||
<TeamIcon style={{ fontSize: 38 }} />
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Tooltip title={"Key: " + d.key()}>
|
||||
<span>{d.name()}</span>
|
||||
</Tooltip>
|
||||
}
|
||||
secondaryTypographyProps={{ component: "span" }}
|
||||
secondary={d.settings.info}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ResponsiveDialog
|
||||
open={isOpen}
|
||||
onClose={close}
|
||||
aria-labelledby="access-object-dialog"
|
||||
fullScreen>
|
||||
<AppBar position="sticky">
|
||||
<Toolbar>
|
||||
<IconButton edge="start" color="inherit" onClick={close} aria-label="close">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" noWrap>
|
||||
Access {capitalize(kind)}
|
||||
</Typography>
|
||||
<Box flex={1} display="flex" justifyContent="flex-end" marginLeft={2}>
|
||||
<ToggleButtonGroup
|
||||
value={kind}
|
||||
size="small"
|
||||
exclusive
|
||||
onChange={(_, newKind) => newKind !== null && setKind(newKind)}
|
||||
aria-label="object-kind">
|
||||
<ToggleButton value="device" aria-label="device-kind">
|
||||
Devices
|
||||
</ToggleButton>
|
||||
<ToggleButton value="group" aria-label="group-kind">
|
||||
Groups
|
||||
</ToggleButton>
|
||||
<ToggleButton value="team" aria-label="team-kind">
|
||||
Teams
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
{!isMobile && (
|
||||
<Box marginLeft={2}>
|
||||
<SearchBar value={searchValue} onChange={value => setSearchValue(value)} />
|
||||
</Box>
|
||||
)}
|
||||
</Toolbar>
|
||||
{isMobile && (
|
||||
<Toolbar>
|
||||
<Box flex={1} display="flex" justifyContent="flex-end">
|
||||
<SearchBar value={searchValue} onChange={value => setSearchValue(value)} />
|
||||
</Box>
|
||||
</Toolbar>
|
||||
)}
|
||||
</AppBar>
|
||||
|
||||
{loading ? (
|
||||
<Box padding={4} height="100%" display="flex" justifyContent="center" alignItems="center">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : kind === "group" ? (
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
loadMore={() => loadMoreGroups()}
|
||||
hasMore={groups.length < groupsTotal}
|
||||
useWindow={false}
|
||||
loader={
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignContent="center"
|
||||
marginTop={2}
|
||||
key="load-devices">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
}>
|
||||
<List style={{ height: "100%", minHeight: "250px" }}>
|
||||
{groups.length === 0 ? (
|
||||
<ListItem style={{ justifyContent: "center", paddingTop: "16px" }}>
|
||||
<Typography color="textSecondary" variant="h5">
|
||||
No groups found
|
||||
</Typography>
|
||||
</ListItem>
|
||||
) : (
|
||||
groups.map(g => groupItem(g))
|
||||
)}
|
||||
</List>
|
||||
</InfiniteScroll>
|
||||
) : kind === "team" ? (
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
loadMore={() => loadMoreTeams()}
|
||||
hasMore={teams.length < teamsTotal}
|
||||
useWindow={false}
|
||||
loader={
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignContent="center"
|
||||
marginTop={2}
|
||||
key="load-devices">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
}>
|
||||
<List style={{ height: "100%", minHeight: "250px" }}>
|
||||
{teams.length === 0 ? (
|
||||
<ListItem style={{ justifyContent: "center", paddingTop: "16px" }}>
|
||||
<Typography color="textSecondary" variant="h5">
|
||||
No teams found
|
||||
</Typography>
|
||||
</ListItem>
|
||||
) : (
|
||||
teams.map(t => teamItem(t))
|
||||
)}
|
||||
</List>
|
||||
</InfiniteScroll>
|
||||
) : (
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
loadMore={() => loadMoreDevices()}
|
||||
hasMore={devices.length < devicesTotal}
|
||||
useWindow={false}
|
||||
loader={
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignContent="center"
|
||||
marginTop={2}
|
||||
key="load-devices">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
}>
|
||||
<List style={{ height: "100%", minHeight: "250px" }}>
|
||||
{devices.length === 0 ? (
|
||||
<ListItem style={{ justifyContent: "center", paddingTop: "16px" }}>
|
||||
<Typography color="textSecondary" variant="h5">
|
||||
No devices found
|
||||
</Typography>
|
||||
</ListItem>
|
||||
) : (
|
||||
devices.map(d => deviceItem(d))
|
||||
)}
|
||||
</List>
|
||||
</InfiniteScroll>
|
||||
)}
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue