Skip to content

Commit

Permalink
Merge pull request #268 from mzedel/fix/various
Browse files Browse the repository at this point in the history
Fix various gui issues
  • Loading branch information
mzedel authored Dec 17, 2024
2 parents 9a79ecc + 2a3b47f commit 2cf065a
Show file tree
Hide file tree
Showing 30 changed files with 143 additions and 65 deletions.
1 change: 1 addition & 0 deletions frontend/src/js/common-ui/docslink.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const DOCSTIPS = {
pausedDeployments: { id: 'pausedDeployments', path: 'overview/customize-the-update-process#synchronized-updates' },
retryDeployments: { id: 'retryDeployments', path: 'overview/deployment' },
releases: { id: 'releases', path: 'overview/artifact' },
rbac: { id: 'rbac', path: 'overview/role.based.access.control' },
webhookSecret: { id: 'webhookSecret', path: 'server-integration/webhooks#signature-header' }
};

Expand Down
6 changes: 2 additions & 4 deletions frontend/src/js/common-ui/filesize.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and

/* eslint-disable sonarjs/no-duplicate-string */
import React from 'react';

import { deepCompare } from '@northern.tech/utils/helpers.js';
import { deepCompare } from '@northern.tech/utils/helpers';
import { render } from '@testing-library/react';

import { undefineds } from '../../../tests/mockData.js';
import { undefineds } from '../../../tests/mockData';
import FileSize from './filesize';

describe('FileSize Component', () => {
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/js/common-ui/forms/passwordinput.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ export const PasswordInput = ({
};

const validate = async (value = '') => {
if (disabled) {
return true;
}
let { isValid, errortext } = runValidations({ id, required, validations, value });
if (confirmation && value !== confirmation) {
isValid = false;
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/js/common-ui/forms/textinput.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export const TextInput = ({
const errorKey = `${id}-error`;

const validate = value => {
if (disabled) {
return true;
}
const { isValid, errortext } = runValidations({ id, required, validations, value, wasMaybeTouched: !!errors[id] });
if (isValid) {
clearErrors(errorKey);
Expand All @@ -51,6 +54,7 @@ export const TextInput = ({
}
return isValid;
};

return (
<Controller
name={id}
Expand Down
66 changes: 50 additions & 16 deletions frontend/src/js/components/auditlogs/auditlogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const AuditLogs = () => {
const isSP = useSelector(getIsServiceProvider);
const { detail, perPage, endDate, user, sort, startDate, type, total, isLoading } = selectionState;
const [auditLogsTypes, setAuditLogsTypes] = useState(AUDIT_LOGS_TYPES);
const timers = useRef({ init: null, detailsReset: null, dirtyField: null });

useEffect(() => {
if (isSP) {
Expand All @@ -107,7 +108,8 @@ export const AuditLogs = () => {
return;
}
setDetailsReset('detail');
setTimeout(() => setDetailsReset(''), TIMEOUTS.debounceShort);
clearTimeout(timers.current.detailsReset);
timers.current.detailsReset = setTimeout(() => setDetailsReset(''), TIMEOUTS.debounceShort);
}, [type?.value]);

useEffect(() => {
Expand All @@ -126,18 +128,46 @@ export const AuditLogs = () => {
state.startDate = start;
}
dispatch(setAuditlogsState(state));
setTimeout(() => {
clearTimeout(timers.current.dirtyField);
timers.current.dirtyField = setTimeout(() => {
let field = Object.entries({ detail, type, user }).reduce((accu, [key, value]) => (accu || value ? key : accu), '');
field = field || (endDate !== tonight ? 'endDate' : field);
field = field || (state.startDate !== today ? 'startDate' : field);
setDirtyField(field);
}, TIMEOUTS.debounceDefault);
// the timeout here is slightly longer than the debounce in the filter component, otherwise the population of the filters with the url state would trigger a reset to page 1
setTimeout(() => (isInitialized.current = true), TIMEOUTS.oneSecond + TIMEOUTS.debounceDefault);
clearTimeout(timers.current.init);
timers.current.init = setTimeout(() => (isInitialized.current = true), TIMEOUTS.oneSecond + TIMEOUTS.debounceDefault);
},
[dispatch, today, tonight]
);

const updateState = useCallback(
nextState => {
let state = { ...nextState };
if (state.id && Boolean(state.open)) {
state.selectedId = state.id[0];
const [eventAction, eventTime] = atob(state.selectedId).split('|');
if (eventTime && !events.some(item => item.time === eventTime && item.action === eventAction)) {
const { start, end } = getISOStringBoundaries(new Date(eventTime));
state.endDate = end;
state.startDate = start;
}
let field = endDate !== tonight ? 'endDate' : '';
field = field || (startDate !== today ? 'startDate' : field);
setDirtyField(field);
}
// the timeout here is slightly longer than the debounce in the filter component, otherwise the population of the filters with the url state would trigger a reset to page 1
dispatch(setAuditlogsState(state)).then(() => {
clearTimeout(timers.current.init);
timers.current.init = setTimeout(() => (isInitialized.current = true), TIMEOUTS.oneSecond + TIMEOUTS.debounceDefault);
});
return;
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[dispatch, endDate, JSON.stringify(events), startDate, today, tonight]
);

useEffect(() => {
if (!hasAuditlogs || isInitialized.current !== undefined) {
return;
Expand All @@ -146,25 +176,29 @@ export const AuditLogs = () => {
const { id, open, detail, endDate, startDate, type, user } = locationParams;
let state = { ...locationParams };
if (id && Boolean(open)) {
state.selectedId = id[0];
const [eventAction, eventTime] = atob(state.selectedId).split('|');
if (eventTime && !events.some(item => item.time === eventTime && item.action === eventAction)) {
const { start, end } = getISOStringBoundaries(new Date(eventTime));
state.endDate = end;
state.startDate = start;
}
let field = endDate !== tonight ? 'endDate' : '';
field = field || (startDate !== today ? 'startDate' : field);
setDirtyField(field);
// the timeout here is slightly longer than the debounce in the filter component, otherwise the population of the filters with the url state would trigger a reset to page 1
dispatch(setAuditlogsState(state)).then(() => setTimeout(() => (isInitialized.current = true), TIMEOUTS.oneSecond + TIMEOUTS.debounceDefault));
updateState(state);
return;
}
dispatch(getAuditLogs({ page: state.page ?? 1, perPage: 50, startDate: startDate !== today ? startDate : BEGINNING_OF_TIME, endDate, user, type, detail }))
.unwrap()
.then(({ payload: result }) => initAuditlogState(result, state));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch, hasAuditlogs, JSON.stringify(events), JSON.stringify(locationParams), initAuditlogState, today, tonight]);
}, [dispatch, hasAuditlogs, JSON.stringify(events), JSON.stringify(locationParams), initAuditlogState, updateState, today, tonight]);

useEffect(() => {
if (!hasAuditlogs || !isInitialized.current) {
return;
}
updateState(locationParams);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasAuditlogs, JSON.stringify(locationParams), updateState]);

useEffect(() => {
const currentTimers = timers.current;
return () => {
Object.values(currentTimers).forEach(clearTimeout);
};
}, []);

const createCsvDownload = () => {
setCsvLoading(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { makeStyles } from 'tss-react/mui';

import Time from '@northern.tech/common-ui/time';
import { DEPLOYMENT_STATES } from '@northern.tech/store/constants';
import { useDeploymentDevice } from '@northern.tech/utils/deploymentdevicehook';
import { useDeploymentDevice } from '@northern.tech/store/deploymentdevicehook';

import { DeploymentDeviceGroup, DeploymentProgress } from '../../deployments/deploymentitem';
import DeploymentStats from '../../deployments/deploymentstatus';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ exports[`DeploymentReport Component renders correctly 1`] = `
>
Deployment
details
<h4
class="margin-none margin-left-small margin-right-small"
<i
class="margin-left-small margin-right-small"
>
ID:
d1
</h4>
</i>
</h3>
<button
class="MuiButtonBase-root MuiIconButton-root MuiIconButton-sizeLarge css-jk231o-MuiButtonBase-root-MuiIconButton-root"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/js/components/deployments/deploymentitem.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ import Confirm from '@northern.tech/common-ui/confirm';
import FileSize from '@northern.tech/common-ui/filesize';
import { RelativeTime } from '@northern.tech/common-ui/time';
import { DEPLOYMENT_STATES, DEPLOYMENT_TYPES } from '@northern.tech/store/constants';
import { useDeploymentDevice } from '@northern.tech/store/deploymentdevicehook';
import { getDeploymentState } from '@northern.tech/store/utils';
import { useDeploymentDevice } from '@northern.tech/utils/deploymentdevicehook';

import { PhaseProgressDisplay } from './deployment-report/phaseprogress';
import { getDeploymentTargetText } from './deployment-wizard/softwaredevices';
Expand Down
8 changes: 2 additions & 6 deletions frontend/src/js/components/deployments/deployments.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,6 @@ export const Deployments = () => {
}
};

const onAbortDeployment = id =>
dispatch(abortDeployment(id)).then(() => {
dispatch(setDeploymentsState({ general: { showCreationDialog: false, showReportDialog: false } }));
return Promise.resolve();
});

const changeTab = (_, tabIndex) => {
dispatch(setDeploymentsState({ general: { state: tabIndex } }));
dispatch(setSnackbar(''));
Expand All @@ -191,6 +185,8 @@ export const Deployments = () => {

const closeReport = () => dispatch(setDeploymentsState({ general: { reportType: undefined, showReportDialog: false }, selectedId: undefined }));

const onAbortDeployment = id => dispatch(abortDeployment(id)).then(closeReport);

const onCreationDismiss = () => {
dispatch(setDeploymentsState({ general: { showCreationDialog: false } }));
setDeploymentObject({});
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/js/components/deployments/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export const DeploymentReport = ({ abort, onClose, past, retry, type }) => {
title={
<>
Deployment {type !== DEPLOYMENT_STATES.scheduled ? 'details' : 'report'}
<h4 className="margin-none margin-left-small margin-right-small">ID: {deployment.id}</h4>
<i className="margin-left-small margin-right-small">ID: {deployment.id}</i>
</>
}
onLinkCopy={copyLinkToClipboard}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/js/components/devices/authorized-devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export const Authorized = ({
}, [devicesInitialized, refreshDevices, selectedState]);

useEffect(() => {
Object.keys(availableIssueOptions).forEach(key => dispatch(getIssueCountsByType(key, { filters, group: selectedGroup, state: selectedState })));
Object.keys(availableIssueOptions).forEach(key => dispatch(getIssueCountsByType({ type: key, filters, group: selectedGroup, state: selectedState })));
availableIssueOptions[DEVICE_ISSUE_OPTIONS.authRequests.key]
? dispatch(getIssueCountsByType({ type: DEVICE_ISSUE_OPTIONS.authRequests.key, options: { filters: [] } }))
: undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const PreauthDialog = ({ acceptedDevices, deviceLimit, limitMaxed, onCanc
identity_data: jsonIdentity
};
return dispatch(preauthDevice(authset))
.unwrap()
.then(() => onSubmit(shouldClose))
.catch(setErrortext);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,21 @@ export const getOptionLabel = option => {
return header?.title || option.title || option.value || option.key || option;
};

const FilterOption = (props, option) => {
const FilterOption = ({ key, ...props }, option) => {
let content = getOptionLabel(option);
if (option.category === 'recently used') {
content = (
<div className="flexbox center-aligned space-between" style={{ width: '100%' }}>
<div className="flexbox center-aligned space-between full-width">
<div>{content}</div>
<div className="muted slightly-smaller">({option.scope})</div>
</div>
);
}
return <li {...props}>{content}</li>;
return (
<li key={key} {...props}>
{content}
</li>
);
};

const optionsFilter = createFilterOptions();
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/js/components/releases/artifactdetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ export const ArtifactDetails = ({ artifact, open, showRemoveArtifactDialog }) =>
? {
title: 'Software versioning information',
content: [
{ primary: 'Software filesystem', secondary: softwareItem.key },
{ primary: 'Software name', secondary: softwareItem.name },
{ primary: 'Software version', secondary: softwareItem.version }
{ key: 'software-filesystem', primary: 'Software filesystem', secondary: softwareItem.key },
{ key: 'software-name', primary: 'Software name', secondary: softwareItem.name },
{ key: 'software-version', primary: 'Software version', secondary: softwareItem.version }
]
}
: { content: [] };
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/js/components/releases/artifactmetadatalist.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export const ArtifactMetadataList = ({ metaInfo = { content: [] } }) => {
<>
<p className="margin-bottom-none">{metaInfo.title}</p>
<List className="list-horizontal-flex" style={{ paddingTop: 0 }}>
{metaInfo.content.map((info, index) => (
<ExpandableAttribute key={`software-info-${index}`} {...info} />
{metaInfo.content.map(({ key, ...info }) => (
<ExpandableAttribute key={key} {...info} />
))}
</List>
</>
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/js/components/releases/releasedetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const defaultActions = [
{
action: ({ onTagRelease, selectedReleases }) => onTagRelease(selectedReleases),
icon: <LabelOutlinedIcon />,
isApplicable: ({ userCapabilities: { canManageReleases } }) => canManageReleases,
isApplicable: ({ userCapabilities: { canManageReleases }, selectedSingleRelease }) => canManageReleases && !selectedSingleRelease,
key: 'tag',
title: pluralized => `Tag ${pluralized}`
},
Expand Down Expand Up @@ -136,11 +136,12 @@ const useStyles = makeStyles()(theme => ({
}
}));

export const ReleaseQuickActions = ({ actionCallbacks, innerRef, selectedRelease, userCapabilities, releases }) => {
export const ReleaseQuickActions = ({ actionCallbacks, innerRef, userCapabilities, releases }) => {
const [showActions, setShowActions] = useState(false);
const [selectedReleases, setSelectedReleases] = useState([]);
const { classes } = useStyles();
const { selection: selectedRows } = useSelector(getReleaseListState);
const selectedRelease = useSelector(getSelectedRelease);

useEffect(() => {
if (releases) {
Expand All @@ -166,7 +167,7 @@ export const ReleaseQuickActions = ({ actionCallbacks, innerRef, selectedRelease
setShowActions(false);
};

const pluralized = pluralize('releases', selectedRows.length);
const pluralized = pluralize('releases', selectedRelease ? 1 : selectedRows.length);

return (
<div className={classes.container} ref={innerRef}>
Expand Down Expand Up @@ -350,7 +351,7 @@ export const ReleaseDetails = () => {
<DrawerTitle
title={
<>
Release information for <h4 className="margin-none margin-left-small margin-right-small">{releaseName}</h4>
Release information for <i className="margin-left-small margin-right-small">{releaseName}</i>
</>
}
onLinkCopy={copyLinkToClipboard}
Expand Down Expand Up @@ -381,7 +382,6 @@ export const ReleaseDetails = () => {
<ReleaseQuickActions
actionCallbacks={{ onCreateDeployment, onDeleteRelease: onToggleReleaseDeletion }}
innerRef={creationRef}
selectedRelease={release}
userCapabilities={userCapabilities}
/>
</Drawer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { Chip } from '@mui/material';

import DetailsIndicator from '@northern.tech/common-ui/detailsindicator';
import DetailsTable from '@northern.tech/common-ui/detailstable';
import { DocsTooltip } from '@northern.tech/common-ui/docslink';
import { DOCSTIPS, DocsTooltip } from '@northern.tech/common-ui/docslink';
import EnterpriseNotification from '@northern.tech/common-ui/enterpriseNotification';
import { InfoHintContainer } from '@northern.tech/common-ui/info-hint';
import { BENEFITS, UiRoleDefinition, emptyRole, settingsKeys } from '@northern.tech/store/constants';
Expand Down Expand Up @@ -96,7 +96,7 @@ export const RoleManagement = () => {
<h2 style={{ marginLeft: 20 }}>Roles</h2>
<InfoHintContainer>
<EnterpriseNotification id={BENEFITS.rbac.id} />
<DocsTooltip />
<DocsTooltip id={DOCSTIPS.rbac.id} />
</InfoHintContainer>
</div>
<DetailsTable columns={columns} items={items} onItemClick={onEditRole} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1516,7 +1516,7 @@ label+.emotion-35 {
<div
class="flexbox center-aligned margin-top "
>
System audit log
System Audit Log
<svg
aria-hidden="true"
aria-label="Granting access to the audit log will allow tracing changes to devices, releases and user accounts, as well as providing information about deployments."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React, { useEffect, useMemo, useState } from 'react';
import React, { Fragment, useEffect, useMemo, useState } from 'react';

// material ui
import { Button, Checkbox, Divider, Drawer, FormControl, FormControlLabel, FormHelperText, InputLabel, TextField, textFieldClasses } from '@mui/material';
Expand Down Expand Up @@ -191,12 +191,12 @@ export const UserDefinition = ({ currentUser, isEnterprise, onCancel, onSubmit,
return accu;
}
accu.push(
<>
<Fragment key={area}>
<InputLabel className="margin-top-small" shrink>
{scopedPermissionAreas[area]}
</InputLabel>
<TwoColumnData className={rolesClasses} config={areaPermissions} />
</>
</Fragment>
);
return accu;
}, [])}
Expand Down
Loading

0 comments on commit 2cf065a

Please sign in to comment.