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:
parent
1803a5ee18
commit
c5fc50be0b
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
117
src/js/components/filter-sidebar.js
Normal file
117
src/js/components/filter-sidebar.js
Normal 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;
|
@ -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) => ({
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user