1
0
mirror of https://gitlab.com/psono/psono-client synced 2025-04-19 03:22:16 +03:00

Added filters

Signed-off-by: Sascha Pfeiffer <sascha.pfeiffer@esaqa.com>
This commit is contained in:
Sascha Pfeiffer 2024-09-11 13:34:33 +02:00
parent 1803a5ee18
commit c5fc50be0b
9 changed files with 216 additions and 24 deletions

View File

@ -31,6 +31,6 @@ Visit the [License.md](/LICENSE.md) for more details
- Websocket behind reverse proxy with SSL
npx webpack serve --client-web-socket-url wss://psonoclient.chickahoona.com/ws --config webpack.environment.behindnginx.js
npx webpack serve --client-web-socket-url wss://psonoclient.chickahoona.com/ws --config webpack.environment.dev.js

View File

@ -8,7 +8,6 @@
"author": "Sascha Pfeiffer",
"scripts": {
"dev": "webpack serve --config webpack.environment.dev.js",
"behindnginx": "webpack serve --config webpack.environment.behindnginx.js",
"buildchrome": "webpack --config webpack.environment.prod.chrome.js",
"buildfirefox": "webpack --config webpack.environment.prod.firefox.js",
"buildwebclient": "webpack --config webpack.environment.prod.webclient.js",

View File

@ -1,4 +1,6 @@
{
"SHOW_HIDE_FILTER": "Show / hide filter",
"FILTERS": "Filters",
"SERVER_UNSUPPORTED": "Server unsupported",
"THE_VERSION_OF_THE_SERVER_IS_TOO_OLD_AND_NOT_SUPPORTED_PLEASE_UPGRADE": "The version of the server is too old. This client doesn't support the old version anymore. Please update your server or tell your administrator to update it.",
"GO_TO_DOCUMENTATION": "Go to Documentation",

View File

@ -100,6 +100,22 @@ const DatastoreTree = (props) => {
return 0;
})
.filter((item) => !item["hidden"] && !item["deleted"])
.filter((item) => {
if (!props.selectedFilters || Object.keys(props.selectedFilters).filter((key) => props.selectedFilters[key]).length === 0) {
return true
}
for (const filter of Object.keys(props.selectedFilters)) {
if (!props.selectedFilters[filter]) {
continue
}
if (filter.startsWith('entry_type:')) {
if (!item.hasOwnProperty("type") || `entry_type:${item["type"]}` !== filter) {
return false
}
}
}
return true
})
.forEach(item => formatDatastoreItems(item, acc, false, nodePath.concat(item), currentPath))
}

View File

@ -0,0 +1,117 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Drawer, IconButton, Typography, Checkbox, FormControlLabel, List, ListItem, Divider } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import { makeStyles } from '@mui/styles';
const useStyles = makeStyles((theme) => ({
drawerPaper: {
width: 300,
paddingLeft: theme.spacing(2),
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2),
overflowY: 'auto',
transition: 'none',
},
header: {
fontSize: "14px",
fontWeight: 'bold',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: theme.spacing(1.5),
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
},
list: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(2),
},
title: {
fontWeight: 'bold',
},
sectionTitle: {
marginTop: theme.spacing(1.5),
marginBottom: theme.spacing(0.5),
color: '#666',
},
listItem: {
paddingTop: 0,
paddingBottom: 0,
paddingLeft: theme.spacing(1),
paddingRight: theme.spacing(1),
},
formControlLabel: {
fontSize: "14px",
},
}));
const FilterSideBar = ({ open, onClose, filters, selectedFilters, toggleFilter }) => {
const classes = useStyles();
const { t } = useTranslation();
return (
<Drawer
anchor="right"
open={open}
onClose={onClose}
classes={{ paper: classes.drawerPaper }}
variant="persistent"
transitionDuration={0}
ModalProps={{
keepMounted: true,
}}
sx={{
position: 'absolute',
right: 0,
top: 0,
height: '100%',
width: open ? '300px' : '0',
zIndex: 1300,
transition: 'none',
'& .MuiDrawer-paper': {
position: 'absolute',
top: 0,
right: 0,
height: '100%',
width: open ? 300 : 0,
transition: 'none',
paddingLeft: 0,
},
}}
>
<div className={classes.header}>
<Typography variant="h6" className={classes.title}>
{t("FILTERS")}
</Typography>
<IconButton onClick={onClose}>
<CloseIcon />
</IconButton>
</div>
<Divider />
<List className={classes.list}>
{filters.map((section) => (
<div key={section.label}>
<Typography variant="body2" className={classes.sectionTitle}>
{section.label}
</Typography>
{section.options.map((option) => (
<ListItem key={option.key} className={classes.listItem}>
<FormControlLabel
control={
<Checkbox
checked={!!selectedFilters[option.key]}
onChange={() => toggleFilter(option.key)}
/>
}
label={<Typography className={classes.formControlLabel}>{option.label}</Typography>}
/>
</ListItem>
))}
</div>
))}
</List>
</Drawer>
);
};
export default FilterSideBar;

View File

@ -13,7 +13,6 @@ import MultifactorAuthenticatorDuo from "./multifactor-authentication-duo";
import MultifactorAuthenticatorIvalt from "./multifactor-authentication-ivalt";
import deviceService from "../../services/device";
import browserClient from "../../services/browser-client";
import userService from "../../services/user";
import { useSelector } from "react-redux";
const useStyles = makeStyles((theme) => ({

View File

@ -1,4 +1,4 @@
import React, { useState, useReducer } from "react";
import React, { useState, useReducer, useRef } from "react";
import { useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import { differenceInSeconds } from "date-fns";
@ -41,12 +41,18 @@ import DialogProgress from "../../components/dialogs/progress";
import widgetService from "../../services/widget";
import {useHotkeys} from "react-hotkeys-hook";
import DatastoreToolbar from "./toolbar";
import FilterSideBar from "../../components/filter-sidebar";
import itemBlueprintService from "../../services/item-blueprint";
const useStyles = makeStyles((theme) => ({
root: {
position: 'relative',
display: "flex",
padding: "15px",
},
contentShift: {
marginRight: 300, // Same as drawer width
},
loader: {
textAlign: "center",
marginTop: "30px",
@ -75,6 +81,11 @@ const useStyles = makeStyles((theme) => ({
listItemIcon: {
minWidth: theme.spacing(4),
},
filterSideBar: {
marginTop: `-${theme.spacing(2)}`,
height: `calc(100% + ${theme.spacing(4)})`,
overflowY: 'auto',
},
}));
function useWidth() {
@ -93,6 +104,7 @@ function useWidth() {
const DatastoreView = (props) => {
const width = useWidth();
const bigScreen = ["lg", "md", "xl"].includes(width);
const hugeScreen = ["xl"].includes(width);
let { defaultSearch, secretType, secretId } = useParams();
const [progress, setProgress] = React.useState(0);
const serverStatus = useSelector((state) => state.server.status);
@ -106,10 +118,13 @@ const DatastoreView = (props) => {
const [massOperationSelected, setMassOperationSelected] = useState({});
const [showMassOperationControls, setShowMassOperationControls] = useState();
const [error, setError] = useState(null);
const [selectedFilters, setSelectedFilters] = useState({});
const [contextMenuPosition, setContextMenuPosition] = useState({
mouseX: null,
mouseY: null,
});
const [showFilter, setShowFilter] = useState(false);
const [unlockOfflineCache, setUnlockOfflineCache] = useState(false);
const [newFolderOpen, setNewFolderOpen] = useState(false);
@ -135,6 +150,9 @@ const DatastoreView = (props) => {
const [datastore, setDatastore] = useState(null);
React.useEffect(() => {
setShowFilter(["xl"].includes(width));
}, [width]);
useHotkeys('shift', (event, handler) => {
if (event.type === "keydown") {
@ -493,6 +511,33 @@ const DatastoreView = (props) => {
mouseY: null,
});
};
const toggleShowFilter = () => {
setShowFilter(!showFilter);
};
const toggleFilter = (key) => {
setSelectedFilters((prev) => {
const newSelectedFilters = {
//...prev, deselect others
}
if (!prev[key]) {
newSelectedFilters[key] = !prev[key]
}
return newSelectedFilters
});
};
const filters = [{
label: t("ENTRY_TYPES"),
options: itemBlueprintService.getEntryTypes(true, false).map((e) => {
return {
key: `entry_type:${e.value}`,
label: t(e.title)
}
}),
}];
return (
<Base {...props}>
<BaseTitle>{t("DATASTORE")}</BaseTitle>
@ -526,6 +571,8 @@ const DatastoreView = (props) => {
}}
search={search}
setSearch={setSearch}
toggleShowFilter={toggleShowFilter}
filterCount={Object.keys(selectedFilters).filter((key) => selectedFilters[key]).length}
datastore={datastore}
onNewFolder={() => onNewFolder(datastore, [])}
onNewEntry={() => onNewEntry(datastore, [])}
@ -534,7 +581,7 @@ const DatastoreView = (props) => {
</AppBar>
<div className={classes.root}
onContextMenu={newSecurityReport === 'REQUIRED' ? null : onContextMenu}>
<Grid container>
<Grid container className={`${(showFilter && bigScreen && !editEntryOpen) || (showFilter && hugeScreen) ? classes.contentShift : ''}`}>
<Grid item xs={12} sm={12} md={12}>
<AlertSecurityReport className={classes.securityReportAlert}/>
</Grid>
@ -567,8 +614,16 @@ const DatastoreView = (props) => {
isSelectable={isSelectable}
deleteFolderLabel={t('MOVE_TO_TRASH')}
deleteItemLabel={t('MOVE_TO_TRASH')}
selectedFilters={selectedFilters}
/>
)}
<FilterSideBar
open={showFilter}
onClose={() => setShowFilter(false)}
filters={filters}
toggleFilter={toggleFilter}
selectedFilters={selectedFilters}
/>
</Grid>
</Grid>
</div>

View File

@ -2,6 +2,7 @@ import React, { useState } from "react";
import { useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import { Badge } from '@mui/material';
import { alpha } from "@mui/material/styles";
import { makeStyles } from '@mui/styles';
import Toolbar from "@mui/material/Toolbar";
@ -18,6 +19,7 @@ import AddIcon from "@mui/icons-material/Add";
import Tooltip from "@mui/material/Tooltip";
import DeleteIcon from "@mui/icons-material/Delete";
import OpenWithIcon from "@mui/icons-material/OpenWith";
import FilterListIcon from '@mui/icons-material/FilterList';
import Search from "../../components/search";
import DialogTrashBin from "../../components/dialogs/trash-bin";
@ -62,7 +64,7 @@ const useStyles = makeStyles((theme) => ({
}));
const DatastoreToolbar = ({onNewFolder, onNewEntry, newSecurityReportRequired, datastore, search, setSearch, onMassDelete, onMassMove, hasMassOperationSelected}) => {
const DatastoreToolbar = ({onNewFolder, onNewEntry, newSecurityReportRequired, datastore, search, setSearch, onMassDelete, onMassMove, hasMassOperationSelected, toggleShowFilter, filterCount}) => {
const offlineMode = useSelector((state) => state.client.offlineMode);
const classes = useStyles();
const { t } = useTranslation();
@ -85,7 +87,6 @@ const DatastoreToolbar = ({onNewFolder, onNewEntry, newSecurityReportRequired, d
const openTrashBin = (event) => {
setTrashBinOpen(true);
};
return (
<Toolbar
className={classes.toolbarRoot}>
@ -181,6 +182,26 @@ const DatastoreToolbar = ({onNewFolder, onNewEntry, newSecurityReportRequired, d
</Tooltip>
</>
)}
{!!toggleShowFilter && (
<>
<Divider className={classes.divider} orientation="vertical"/>
<Tooltip title={t("SHOW_HIDE_FILTER")}>
<IconButton
aria-label="filter"
onClick={toggleShowFilter}
size="large"
>
<Badge
color="primary"
badgeContent={filterCount ? filterCount : 0}
invisible={!filterCount}
>
<FilterListIcon />
</Badge>
</IconButton>
</Tooltip>
</>
)}
{trashBinOpen && (
<DialogTrashBin

View File

@ -1,17 +0,0 @@
const { merge } = require('webpack-merge');
//const chrome = require('./webpack.target.chrome');
//const firefox = require('./webpack.target.firefox');
const webclient = require('./webpack.target.webclient');
const developConfig = {
mode: 'development',
devtool: 'inline-source-map',
};
module.exports = () => {
//merge(chrome, developConfig),
//merge(firefox, developConfig),
const config= merge(webclient, developConfig)
return config
};