diff --git a/src/components/IOC/IOCLiveStatus.js b/src/components/IOC/IOCLiveStatus.js index c7d591f0ec982c0a8dc0ca07381bb5cb016238e6..fbfc442b44fdd2cecc07ab6851fd17c32fa7627f 100644 --- a/src/components/IOC/IOCLiveStatus.js +++ b/src/components/IOC/IOCLiveStatus.js @@ -10,6 +10,8 @@ import AlertMessages from "./AlertMessages"; import AccessControl from '../../components/auth/AccessControl'; import { IocActiveDeployment } from "../../api/DataTypes"; import { theme } from "../../style/Theme"; +import useUrlState from '@ahooksjs/use-url-state'; +import { serialize, deserialize } from "../../components/common/URLState/URLState"; const useStyles = makeStyles((theme) => ({ iocNotDeployed: { @@ -19,6 +21,7 @@ const useStyles = makeStyles((theme) => ({ export function IOCLiveStatus({ ioc }) { const classes = useStyles(theme); + const [state, setState] = useUrlState({ procserv_log_open: 'true', records_open: 'false' }); const liveIOC = { ...ioc }; liveIOC.name = ioc.namingName; @@ -64,10 +67,12 @@ export function IOCLiveStatus({ ioc }) { {(liveIOC.activeDeployment?.host?.csEntryIdValid) && <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]} renderNoAccess={() => <></>}> - <SimpleAccordion summary="ProcServLog info" defaultExpanded> + <SimpleAccordion summary="ProcServLog info" expanded={deserialize(state.procserv_log_open)} + onChange={(event, expanded) => setState({procserv_log_open: serialize(expanded)})}> <LokiContainer csEntryId={liveIOC.activeDeployment?.host.csEntryId} iocName={ioc.namingName} isDeployed={isIocDeployed(ioc)} /> </SimpleAccordion> - <SimpleAccordion summary="Records"> + <SimpleAccordion summary="Records" expanded={deserialize(state.records_open)} + onChange={(event, expanded) => setState({records_open: serialize(expanded)})}> <RecordSearch iocName={ioc.namingName} rowType="iocDetails"/> </SimpleAccordion> </AccessControl> diff --git a/src/components/IOC/IOCManage.js b/src/components/IOC/IOCManage.js index 6e968777656b51f961c1cf9a88cd49baa5c8fb94..c07d40e915757fbefec7497718cf13faa8e49267 100644 --- a/src/components/IOC/IOCManage.js +++ b/src/components/IOC/IOCManage.js @@ -12,27 +12,37 @@ import AccessControl from "../auth/AccessControl"; import { JobAsyncList } from "../Job/JobAsyncList"; import { DeploymentStatus } from "../../api/DataTypes"; import { IOCService } from "./IOCService"; +import useUrlState from '@ahooksjs/use-url-state'; +import { serialize, deserialize } from "../../components/common/URLState/URLState"; export function IOCManage({ ioc, getIOC, buttonDisabled, currentCommand, commands, commandsLoading, getCommands, setButtonDisabled, commandColumnSort, setCommandColumnSort, commandLazyParams, setCommandLazyParams }) { const { user } = useContext(userContext); + const [state, setState] = useUrlState({ control_log_open: 'false', deployment_log_open: 'false', deployments_rows: '5', deployments_page: '0' }); + const [deployDialogOpen, setDeployDialogOpen] = useState(false); const [undeployDialogOpen, setUndeployDialogOpen] = useState(false); const [deployments, getDeployments] = useDeploymentListForIOC(); const [deploymentList, setDeploymentList] = useState([]); const [deploymentJob, getDeploymentJobById] = useDeploymentJobById(); const [asyncCommands, setAsyncCommands] = useState(commands.operationsList); - - const [lazyParams, setLazyParams] = useState({ - rows: 5, - page: 0 - }); const [columnSort, setColumnSort] = useState({ sortField: null, sortOrder: null }); + const lazyParams = useCallback(() => { + return({ + rows: deserialize(state.deployments_rows), + page: deserialize(state.deployments_page) + }) + }, [state]) + + const setLazyParams = useCallback((params) => { + setState({deployments_rows: serialize(params.rows), deployments_page: serialize(params.page)}) + }, [setState]) + const rowsPerPage = [5, 20]; useEffect(() => { @@ -52,7 +62,7 @@ export function IOCManage({ ioc, getIOC, buttonDisabled, currentCommand, command useEffect(() => { if(ioc) { - let requestParams = initRequestParams(lazyParams, null, columnSort); + let requestParams = initRequestParams(lazyParams(), null, columnSort); requestParams.ioc_id = ioc.id; @@ -169,15 +179,17 @@ export function IOCManage({ ioc, getIOC, buttonDisabled, currentCommand, command /> <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]} renderNoAccess={() => <></>}> - <SimpleAccordion summary="IOC Service Control log"> + <SimpleAccordion summary="IOC Service Control log" expanded={deserialize(state.control_log_open)} + onChange={(event, expanded) => setState({control_log_open: serialize(expanded)})}> <JobAsyncList jobs={asyncCommands} rowType="iocServiceControl" setJobs={setAsyncCommands} loading={commandsLoading} totalCount={commands.totalCount} lazyParams={commandLazyParams} setLazyParams={setCommandLazyParams} columnSort={commandColumnSort} setColumnSort={setCommandColumnSort} rowsPerPage={rowsPerPage} deploymentId={ioc?.activeDeployment?.id}/> </SimpleAccordion> - <SimpleAccordion summary="Deployment log"> + <SimpleAccordion summary="Deployment log" expanded={deserialize(state.deployment_log_open)} + onChange={(event, expanded) => setState({deployment_log_open: serialize(expanded)})}> <JobAsyncList jobs={deploymentList} setJobs={setDeploymentList} totalCount={deployments.totalCount} - lazyParams={lazyParams} setLazyParams={setLazyParams} columnSort={columnSort} setColumnSort={setColumnSort} + lazyParams={lazyParams()} setLazyParams={setLazyParams} columnSort={columnSort} setColumnSort={setColumnSort} rowsPerPage={rowsPerPage} /> </SimpleAccordion> <SimpleModal open={deployDialogOpen} setOpen={setDeployDialogOpen}> diff --git a/src/components/common/SearchBar/SearchBar.js b/src/components/common/SearchBar/SearchBar.js index 37d34afe5b29d52648fb315817b76e1880bf1c05..04ff9edacc07bcdd38c1c6b40bc608a89893acb3 100644 --- a/src/components/common/SearchBar/SearchBar.js +++ b/src/components/common/SearchBar/SearchBar.js @@ -5,7 +5,8 @@ import { useEffectAfterMount } from "../../../hooks/MountEffects"; import { useTypingTimer } from "../SearchBoxFilter/TypingTimer"; export function SearchBar({ search, query, loading = false, placeholder = "Search", children }) { - const [queryString, stillTyping] = useTypingTimer({init: query}); + const [value, setValue] = useState(query); + const [queryString, stillTyping] = useTypingTimer({init: value}); const [showAnimation, setShowAnimation] = useState(false); const [checkMarkOn, setCheckMarkOn] = useState(false); @@ -22,6 +23,9 @@ export function SearchBar({ search, query, loading = false, placeholder = "Searc } }, [loading]) + useEffect(() => { + setValue(query); + }, [query]) const Loading = ( <Fade in> @@ -43,7 +47,8 @@ export function SearchBar({ search, query, loading = false, placeholder = "Searc return ( <Container> - <TextField fullWidth variant="outlined" label={placeholder} onKeyUp={stillTyping} defaultValue={query?.length > 0 ? query : null} + <TextField fullWidth variant="outlined" label={placeholder} onKeyUp={stillTyping} value={value} + onChange={(newValue) => setValue(newValue.target.value)} InputProps={{ endAdornment: showAnimation && LoadingAnimation diff --git a/src/components/common/URLState/URLState.js b/src/components/common/URLState/URLState.js index daaa76a8af280c7c38866b652bf09fdfc7373b9b..dade423d76a3dde9b248642be7f7198b539b17ec 100644 --- a/src/components/common/URLState/URLState.js +++ b/src/components/common/URLState/URLState.js @@ -18,7 +18,7 @@ export function deserialize(value) { if (value === 'false') { return false } - if (!isNaN(parseInt(value))) { + if (Number(value).toString() === value && !isNaN(parseInt(value))) { return parseInt(value) } return value diff --git a/src/components/records/RecordSearch.js b/src/components/records/RecordSearch.js index e5fbff393124c95a04c5a059f2fc83f2695a2782..60ecdddd876fca5c8e424d87e7282d757c62e009 100644 --- a/src/components/records/RecordSearch.js +++ b/src/components/records/RecordSearch.js @@ -1,24 +1,32 @@ -import React, { useState, useEffect } from "react"; +import React, { useEffect, useCallback } from "react"; import { useRecordSearch } from "../../api/SwaggerApi"; import { initRequestParams } from "../common/Helper"; import { RecordTable } from "./RecordTable"; import { SearchBar } from "../common/SearchBar/SearchBar"; +import useUrlState from '@ahooksjs/use-url-state'; +import { serialize, deserialize } from "../common/URLState/URLState"; export function RecordSearch({iocName, rowType}) { const [records, getRecords, /*reset*/, loading] = useRecordSearch(); - const [query, setQuery] = useState(""); + const [state, setState] = useUrlState({ rows: '20', page: '0', query: '' }); - const [lazyParams, setLazyParams] = useState({ - first: 0, - rows: 20, - page: 0 - }); + const lazyParams = useCallback(() => { + return({ + rows: deserialize(state.rows), + page: deserialize(state.page) + }) + }, [state]); + + const setLazyParams = useCallback((params) => { + setState({rows: serialize(params.rows), page: serialize(params.page)}) +}, [setState]) const rowsPerPage = [20, 50]; useEffect(() => { - let requestParams = initRequestParams(lazyParams); + let requestParams = initRequestParams(lazyParams()); + const query = deserialize(state.query); if (query && query.length > 0) { requestParams = { ...requestParams, @@ -32,11 +40,15 @@ export function RecordSearch({iocName, rowType}) { } } getRecords(requestParams) - }, [getRecords, lazyParams, iocName, query]) + }, [getRecords, lazyParams, iocName, state]) + + const setSearch = useCallback((query) => { + setState({query: serialize(query)}) +}, [setState]); return ( - <SearchBar search={setQuery} loading={loading} placeholder="Search in Record"> - <RecordTable records={records} rowType={rowType} loading={loading} lazyParams={lazyParams} setLazyParams={setLazyParams} + <SearchBar search={setSearch} query={deserialize(state.query)} loading={loading} placeholder="Search in Record"> + <RecordTable records={records} rowType={rowType} loading={loading} lazyParams={lazyParams()} setLazyParams={setLazyParams} rowsPerPage={rowsPerPage} /> </SearchBar> ); diff --git a/src/views/IOC/IOCDetailsView.js b/src/views/IOC/IOCDetailsView.js index 3194eecdb11eb64e5efbb89620a0ccaa5f69e4bf..2e81f34e2fc893474f1a2be70619b74a8a99e206 100644 --- a/src/views/IOC/IOCDetailsView.js +++ b/src/views/IOC/IOCDetailsView.js @@ -10,21 +10,29 @@ import AccessControl from "../../components/auth/AccessControl"; import { initRequestParams } from "../../components/common/Helper"; import { useGlobalAppBar } from '../../components/navigation/GlobalAppBar/GlobalAppBar'; import { useSafePolling } from "../../hooks/Polling"; +import useUrlState from '@ahooksjs/use-url-state'; +import { serialize, deserialize } from "../../components/common/URLState/URLState"; const IOC_POLL_INTERVAL = 10000; export function IOCDetailsView({ ioc, getIOC, loading }) { - const [selectedTab, setSelectedTab] = useState("Status"); - const [firstTime, setFirstTime] = useState(true); + const [state, setState] = useUrlState({ tab: 'Status', commands_rows: '5', commands_page: '0' }); + const [buttonDisabled, setButtonDisabled] = useState(false); const navigate = useNavigate(); const [commands, getCommands, /*reset*/, commandsLoading] = useCommandList(); const [ongoingCommand, getOngoingCommand, /*reset*/, ongoingCommandLoading] = useOngoingCommand(ioc.id); - const [commandLazyParams, setCommandLazyParams] = useState({ - rows: 5, - page: 0 - }); + const commandLazyParams = useCallback(() => { + return({ + rows: deserialize(state.commands_rows), + page: deserialize(state.commands_page) + }) + }, [state]) + + const setCommandLazyParams = useCallback((params) => { + setState({commands_rows: serialize(params.rows), commands_page: serialize(params.page)}) + }, [setState]) const [commandColumnSort, setCommandColumnSort] = useState({ sortField: null, @@ -39,19 +47,12 @@ export function IOCDetailsView({ ioc, getIOC, loading }) { }, [ioc?.operationInProgress]); const handleTabChange = (event, tab) => { - setSelectedTab(tab); + setState({tab: serialize(tab)}); }; - useEffect(() => { - if (firstTime && ioc && ioc.activeDeployment) { - setSelectedTab("Status"); - setFirstTime(false); - } - }, [ioc, firstTime]); - useEffect(() => { if(ioc.activeDeployment) { - let requestParams = initRequestParams(commandLazyParams, null, commandColumnSort); + let requestParams = initRequestParams(commandLazyParams(), null, commandColumnSort); requestParams.ioc_id = ioc.id; //requestParams.deployment_id = ioc.activeDeployment.id; getCommands(requestParams); @@ -64,11 +65,11 @@ export function IOCDetailsView({ ioc, getIOC, loading }) { const resetTab = useCallback( () => { if (ioc?.activeDeployment) { - setSelectedTab("Status"); + setState({tab: 'Status'}); } else { - setSelectedTab("Manage deployment"); + setState({tab: 'Management'}); } - }, [ioc?.activeDeployment]) + }, [ioc?.activeDeployment, setState]) const setButtonDisabledAndUpdate = useCallback((isDisabled) => { setButtonDisabled(isDisabled); @@ -82,13 +83,13 @@ export function IOCDetailsView({ ioc, getIOC, loading }) { }, [ioc, setTitle]); const statusTab = <Tab key="Status" label={<Typography variant="h5">Status</Typography>} value="Status" /> - const manageDeploymentTab = <Tab key="Manage deployment" label={<Typography variant="h5">Management</Typography>} value="Manage deployment" /> - const iocAdminTab = <Tab key="IOC Admin" label={<Typography variant="h5">Admin</Typography>} value="IOC Admin" /> + const manageDeploymentTab = <Tab key="Management" label={<Typography variant="h5">Management</Typography>} value="Management" /> + const iocAdminTab = <Tab key="Admin" label={<Typography variant="h5">Admin</Typography>} value="Admin" /> const CustomTabs = ({ children }) => { return ( <Tabs - value={selectedTab} + value={deserialize(state.tab)} onChange={handleTabChange} indicatorColor="primary" textColor="primary" @@ -131,11 +132,11 @@ export function IOCDetailsView({ ioc, getIOC, loading }) { </Grid> </Grid> <Grid item xs={12} style={{ paddingBottom: 0 }}> - {selectedTab === "Status" && + {deserialize(state.tab) === "Status" && <IOCLiveStatus ioc={ioc} />} - {selectedTab === "Manage deployment" && + {deserialize(state.tab) === "Management" && <IOCManage ioc={ioc} getIOC={getIOC} @@ -147,10 +148,10 @@ export function IOCDetailsView({ ioc, getIOC, loading }) { setButtonDisabled={setButtonDisabledAndUpdate} commandColumnSort={commandColumnSort} setCommandColumnSort={setCommandColumnSort} - commandLazyParams={commandLazyParams} + commandLazyParams={commandLazyParams()} setCommandLazyParams={setCommandLazyParams} />} - {selectedTab === "IOC Admin" && <IOCAdmin ioc={ioc} getIOC={getIOC} resetTab={resetTab} buttonDisabled={buttonDisabled} />} + {deserialize(state.tab) === "Admin" && <IOCAdmin ioc={ioc} getIOC={getIOC} resetTab={resetTab} buttonDisabled={buttonDisabled} />} </Grid> </Grid> </Paper> diff --git a/src/views/IOC/IOCListView.js b/src/views/IOC/IOCListView.js index e0bc3d789228fe24a0e779ca8ada4278918250ff..6b6ebecf5793d763dc96734b61616c1c4cf31e2c 100644 --- a/src/views/IOC/IOCListView.js +++ b/src/views/IOC/IOCListView.js @@ -29,7 +29,10 @@ export function IOCListView() { const handleTabChange = useCallback((tab) => { setState((s) => (serialize(s.tab) === serialize(tab) ? {tab: serialize(tab)} : {tab: serialize(tab), page: '0'})) + changeTab(tab); + }, [setState]); + const changeTab = ((tab) => { if (tab === 0) { setDeploymentStatus("ALL"); } @@ -39,11 +42,11 @@ export function IOCListView() { else if (tab === 2) { setDeploymentStatus("NOT_DEPLOYED"); } - }, [setState]); + }); useEffect(() => { - state.tab && handleTabChange(deserialize(state.tab)) - }, [state, handleTabChange]) + state.tab && changeTab(deserialize(state.tab)) + }, [state]) const lazyParams = useCallback(() => { return({ @@ -101,7 +104,7 @@ export function IOCListView() { <Grid container spacing={1} component={Container} justify="space-between" alignItems="center" style={{ display: "flex" }} > <Grid item> <Tabs - value={parseInt(state.tab)} + value={deserialize(state.tab)} onChange={(event, tab) => handleTabChange(tab)} indicatorColor="primary" textColor="primary"> diff --git a/src/views/host/HostDetailsView.js b/src/views/host/HostDetailsView.js index f060a6d1fc1b649a5cc78a7cbeb98432530ce94c..0558149eeeaa968edce3631fcc1c3c3ee0ea0a38 100644 --- a/src/views/host/HostDetailsView.js +++ b/src/views/host/HostDetailsView.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { Paper, Link, @@ -22,6 +22,9 @@ import AccessControl from '../../components/auth/AccessControl'; import { useGlobalAppBar } from '../../components/navigation/GlobalAppBar/GlobalAppBar'; import { useHost } from "../../api/SwaggerApi"; import AlertMessages from '../../components/IOC/AlertMessages'; +import useUrlState from '@ahooksjs/use-url-state'; +import { serialize, deserialize } from "../../components/common/URLState/URLState"; + const useStyles = makeStyles((theme) => ({ secondItem: { @@ -36,13 +39,22 @@ export function HostDetailsView({ id }) { const [host] = useHost(id); const [iocs, getIocs, /*reset*/, loading] = useHostIOCList(); const [deployedIocs, setDeployedIocs] = useState([]); + + const [state, setState] = useUrlState({ iocs_rows: '5', iocs_page: '0', iocs_open: 'true', syslog_open: 'true', details_open: 'false' }); + const navigate = useNavigate(); const classes = useStyles(); - const [lazyParams, setLazyParams] = useState({ - rows: 5, - page: 0 - }); + const lazyParams = useCallback(() => { + return({ + rows: deserialize(state.iocs_rows), + page: deserialize(state.iocs_page) + }) + }, [state]) + + const setLazyParams = useCallback((params) => { + setState({iocs_rows: serialize(params.rows), iocs_page: serialize(params.page)}) + }, [setState]) const [columnSort, setColumnSort] = useState({ sortField: null, @@ -106,12 +118,14 @@ export function HostDetailsView({ id }) { {host && <HostBadge host={host} />} </CardContent> </Card> - <SimpleAccordion summary="Deployed IOCs" defaultExpanded> + <SimpleAccordion summary="Deployed IOCs" expanded={deserialize(state.iocs_open)} + onChange={(event, expanded) => setState({iocs_open: serialize(expanded)})}> <IOCAsyncList iocs={deployedIocs} setIocs={setDeployedIocs} loading={loading} rowType="host" - totalCount={iocs.totalCount} lazyParams={lazyParams} setLazyParams={setLazyParams} columnSort={columnSort} setColumnSort={setColumnSort} + totalCount={iocs.totalCount} lazyParams={lazyParams()} setLazyParams={setLazyParams} columnSort={columnSort} setColumnSort={setColumnSort} rowsPerPage={rowsPerPage} /> </SimpleAccordion> - <SimpleAccordion summary="Host details"> + <SimpleAccordion summary="Host details" expanded={deserialize(state.details_open)} + onChange={(event, expanded) => setState({details_open: serialize(expanded)})}> <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]} renderNoAccess={() => <></>} > {host && <KeyValueTable obj={renderHost} />} @@ -119,7 +133,8 @@ export function HostDetailsView({ id }) { </SimpleAccordion> <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]} renderNoAccess={() => <></>}> - <SimpleAccordion summary="Syslog info" defaultExpanded> + <SimpleAccordion summary="Syslog info" expanded={deserialize(state.syslog_open)} + onChange={(event, expanded) => setState({syslog_open: serialize(expanded)})}> {host && <Container> <LokiPanel host={host} isSyslog={true} isDeployed={true} /> diff --git a/src/views/host/HostListView.js b/src/views/host/HostListView.js index 14a3f751c3d20fc1d5610295ee8ce0fea63f0861..ca848f12adea3d114f2948f1255760c4a69bc92f 100644 --- a/src/views/host/HostListView.js +++ b/src/views/host/HostListView.js @@ -19,6 +19,8 @@ import { makeStyles } from '@material-ui/core/styles'; import { initRequestParams, transformHostQuery } from "../../components/common/Helper"; import { SearchBar } from "../../components/common/SearchBar/SearchBar"; import AccessControl from "../../components/auth/AccessControl"; +import useUrlState from '@ahooksjs/use-url-state'; +import { serialize, deserialize } from "../../components/common/URLState/URLState"; const useStyles = makeStyles((theme) => ({ root: { @@ -32,16 +34,16 @@ export function HostListView() { useGlobalAppBar("IOC hosts"); const [hosts, getHosts, /*reset*/, loading] = useCSEntrySearch(); + const [state, setState] = useUrlState({ tab: '0', own: 'false', rows: '20', page: '0', query: '' }); + const [hostFilter, setHostFilter] = useState("ALL"); - const [selectedTab, setSelectedTab] = useState(0); - const [ownOnly, setOwnOnly] = useState(false); - const [query, setQuery] = useState(""); const classes = useStyles(); - const hideOwnSlider = selectedTab === 2; + const hideOwnSlider = deserialize(state.tab) === 2; const handleTabChange = useCallback((event, tab) => { - setSelectedTab(tab); + setState((s) => (serialize(s.tab) === serialize(tab) ? {tab: serialize(tab)} : {tab: serialize(tab), page: '0'})) + if (tab === 0) { setHostFilter("ALL"); } @@ -51,38 +53,46 @@ export function HostListView() { else if (tab === 2) { setHostFilter("NO_IOCS"); } - }, []); - - + }, [setState]); const handleChangeOwn = useCallback((event) => { const own = event.target.checked; - setOwnOnly(own); - }, []) + setState({own: serialize(own), page: '0'}) + }, [setState]) + + const lazyParams = useCallback(() => { + return({ + rows: deserialize(state.rows), + page: deserialize(state.page) + }) + }, [state]) - const [lazyParams, setLazyParams] = useState({ - rows: 20, - page: 0 - }); + const setLazyParams = useCallback((params) => { + setState({rows: serialize(params.rows), page: serialize(params.page)}) + }, [setState]) const rowsPerPage = [20, 50]; useEffect(() => { - let requestParams = initRequestParams(lazyParams, transformHostQuery(query)); - requestParams.filter = ((hostFilter !== "NO_IOCS") && ownOnly) ? "OWN" : hostFilter; + let requestParams = initRequestParams(lazyParams(), transformHostQuery(deserialize(state.query))); + requestParams.filter = ((hostFilter !== "NO_IOCS") && deserialize(state.own)) ? "OWN" : hostFilter; getHosts(requestParams); - }, [getHosts, lazyParams, query, hostFilter, ownOnly]) + }, [getHosts, lazyParams, hostFilter, state]) + + const setSearch = useCallback((query) => { + setState({query: serialize(query)}) + }, [setState]); const content = ( <> - <SearchBar search={setQuery} loading={loading}> + <SearchBar search={setSearch} query={deserialize(state.query)} loading={loading}> <Hidden smUp> <HostList hosts={hosts.hostList} /> </Hidden> <Hidden xsDown> <HostTable hosts={hosts.hostList} loading={loading} - totalCount={hosts.totalCount} lazyParams={lazyParams} setLazyParams={setLazyParams} + totalCount={hosts.totalCount} lazyParams={lazyParams()} setLazyParams={setLazyParams} rowsPerPage={rowsPerPage} /> </Hidden> </SearchBar> @@ -96,7 +106,7 @@ export function HostListView() { <Grid container spacing={1} component={Container} justify="space-between" alignItems="center" style={{display: "flex"}} > <Grid item> <Tabs - value={selectedTab} + value={deserialize(state.tab)} onChange={handleTabChange} indicatorColor="primary" textColor="primary"> @@ -108,10 +118,14 @@ export function HostListView() { <Grid item> <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]} renderNoAccess={NoAccess} > - <FormControlLabel className={classes.formControl} - control={<Switch disabled={hideOwnSlider} checked={ownOnly} onChange={handleChangeOwn} />} - label={<Typography noWrap variant="h5">Only my IOC hosts</Typography>} - /> + <> + { !hideOwnSlider && + <FormControlLabel className={classes.formControl} + control={<Switch checked={deserialize(state.own)} onChange={handleChangeOwn} />} + label={<Typography noWrap variant="h5">Only my IOC hosts</Typography>} + /> + } + </> </AccessControl> </Grid> </Grid> diff --git a/src/views/jobs/JobListView.js b/src/views/jobs/JobListView.js index f9a2985ff87d9f4eaaa00577a977a437479854be..f32d203a2101f4614d3d61204019b2dad47f8a66 100644 --- a/src/views/jobs/JobListView.js +++ b/src/views/jobs/JobListView.js @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useContext, useState, useCallback } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import { Paper, Grid, FormControlLabel, Switch, Typography, Container, TextField } from '@material-ui/core'; import Tabs from '@material-ui/core/Tabs'; @@ -10,6 +10,8 @@ import { SearchBar } from '../../components/common/SearchBar/SearchBar'; import AccessControl from '../../components/auth/AccessControl'; import { Autocomplete } from '@material-ui/lab'; import { JobAsyncList } from '../../components/Job/JobAsyncList'; +import useUrlState from '@ahooksjs/use-url-state'; +import { serialize, deserialize } from "../../components/common/URLState/URLState"; const useStyles = makeStyles((theme) => ({ root: { @@ -27,18 +29,16 @@ const useStyles = makeStyles((theme) => ({ export function JobListView() { const classes = useStyles(); const [operations, getOperations, /*reset*/, loading] = useOperationsSearch(); - const [query, setQuery] = useState(""); - const [selectedTab, setSelectedTab] = useState(0); - const [ownOnly, setOwnOnly] = useState(false); + const [state, setState] = useUrlState({ tab: '0', own: 'false', rows: '20', page: '0', query: '', job_type: null }); + const { user } = useContext(userContext); const [deploymentStatus, setDeploymentStatus] = useState(null); const [jobList, setJobList] = useState([]); - const [jobType, setJobType] = useState(null); console.log("JobListView:", operations, jobList); const handleTabChange = (event, tab) => { - setSelectedTab(tab); + setState((s) => (serialize(s.tab) === serialize(tab) ? {tab: serialize(tab)} : {tab: serialize(tab), page: '0'})) if (tab === 0) { setDeploymentStatus(null); @@ -56,21 +56,27 @@ export function JobListView() { const handleChangeOwn = (event) => { const own = event.target.checked; - setOwnOnly(own); + setState({own: serialize(own), page: '0'}) }; - const [lazyParams, setLazyParams] = useState({ - rows: 20, - page: 0 - }); + const lazyParams = useCallback(() => { + return({ + rows: deserialize(state.rows), + page: deserialize(state.page) + }) + }, [state]) + + const setLazyParams = useCallback((params) => { + setState({rows: serialize(params.rows), page: serialize(params.page)}) + }, [setState]) const rowsPerPage = [20, 50]; useEffect(() => { - let requestParams = initRequestParams(lazyParams, query); + let requestParams = initRequestParams(lazyParams(), deserialize(state.query)); - if (ownOnly) { + if (deserialize(state.own)) { requestParams.user = user?.loginName; } @@ -78,34 +84,40 @@ export function JobListView() { requestParams.status = deploymentStatus; } - if (jobType) { - requestParams.type = jobType; + if (state.job_type) { + requestParams.type = deserialize(state.job_type); } getOperations(requestParams); - }, [getOperations, lazyParams, query, user, deploymentStatus, ownOnly, jobType]) + }, [getOperations, lazyParams, state, user, deploymentStatus]) useEffect(() => { setJobList(operations.operationsList); }, [operations, setJobList]) + const setSearch = useCallback((query) => { + setState({query: serialize(query)}) + }, [setState]); + const content = ( - <SearchBar search={setQuery} loading={loading} placeholder="Search in IOC name, user or git reference"> + <SearchBar search={setSearch} query={deserialize(state.query)} loading={loading} placeholder="Search in IOC name, user or git reference"> <JobAsyncList jobs={jobList} setJobs={setJobList} loading={loading} - totalCount={operations.totalCount} lazyParams={lazyParams} setLazyParams={setLazyParams} + totalCount={operations.totalCount} lazyParams={lazyParams()} setLazyParams={setLazyParams} rowsPerPage={rowsPerPage} /> </SearchBar> ); + const jobTypeOptions = [{ label: "Show all" }, { label: "Only deployments", filter: "DEPLOYMENT" }, { label: "Only commands", filter: "COMMAND" }]; + return ( <Paper className={classes.paper}> <Grid container spacing={1}> <Grid container spacing={1} component={Container} justify="space-between" alignItems="center" style={{ display: "flex" }} > <Grid item> <Tabs - value={selectedTab} + value={deserialize(state.tab)} onChange={handleTabChange} indicatorColor="primary" textColor="primary"> @@ -118,13 +130,13 @@ export function JobListView() { <Grid item xs={3}> <Autocomplete id="combo-box-demo" - options={[{ label: "Show all" }, { label: "Only deployments", filter: "DEPLOYMENT" }, { label: "Only commands", filter: "COMMAND" }]} - defaultValue={{ label: "Show all" }} + options={jobTypeOptions} + defaultValue={state.job_type ? jobTypeOptions.find(o => o.filter === state.job_type) || { label: "Show all" } : { label: "Show all" }} getOptionLabel={(option) => option.label} getOptionSelected={(option, value) => option.label === value.label} style={{ width: "100%" }} renderInput={(params) => <TextField {...params} label="Job type" variant="outlined" />} - onChange={(event, value, reason) => setJobType(value?.filter)} + onChange={(event, value, reason) => setState({job_type: value?.filter})} autoSelect /> </Grid> @@ -132,7 +144,7 @@ export function JobListView() { <AccessControl allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]} renderNoAccess={() => <></>} > <FormControlLabel className={classes.formControl} - control={<Switch checked={ownOnly} onChange={handleChangeOwn} />} + control={<Switch checked={deserialize(state.own)} onChange={handleChangeOwn} />} label={<Typography variant="h5">Only my jobs</Typography>} /> </AccessControl>