Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • andersharrisson/ce-deploy-ui
  • ccce/dev/ce-deploy-ui
2 results
Show changes
Commits on Source (20)
Showing
with 221 additions and 230 deletions
......@@ -11,7 +11,7 @@
"@ahooksjs/use-url-state": "^3.5.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@ess-ics/ce-ui-common": "^0.4.2",
"@ess-ics/ce-ui-common": "^0.6.0",
"@fontsource/roboto": "^4.1.0",
"@mui/icons-material": "^5.14.1",
"@mui/material": "^5.14.1",
......@@ -2655,9 +2655,9 @@
}
},
"node_modules/@ess-ics/ce-ui-common": {
"version": "0.4.2",
"resolved": "https://artifactory.esss.lu.se/artifactory/api/npm/ics-npm/@ess-ics/ce-ui-common/-/ce-ui-common-0.4.2.tgz",
"integrity": "sha1-bHkj1S9gtfMc3yF9gn1tSzwPJ6E=",
"version": "0.6.0",
"resolved": "https://artifactory.esss.lu.se/artifactory/api/npm/ics-npm/@ess-ics/ce-ui-common/-/ce-ui-common-0.6.0.tgz",
"integrity": "sha1-5yOVZhamHg44YJwx05TWPQ5Pz5I=",
"dependencies": {
"@fontsource/titillium-web": "^4.5.9",
"@mui/x-data-grid-pro": "^6.5.0",
......@@ -41722,9 +41722,9 @@
"dev": true
},
"@ess-ics/ce-ui-common": {
"version": "0.4.2",
"resolved": "https://artifactory.esss.lu.se/artifactory/api/npm/ics-npm/@ess-ics/ce-ui-common/-/ce-ui-common-0.4.2.tgz",
"integrity": "sha1-bHkj1S9gtfMc3yF9gn1tSzwPJ6E=",
"version": "0.6.0",
"resolved": "https://artifactory.esss.lu.se/artifactory/api/npm/ics-npm/@ess-ics/ce-ui-common/-/ce-ui-common-0.6.0.tgz",
"integrity": "sha1-5yOVZhamHg44YJwx05TWPQ5Pz5I=",
"requires": {
"@fontsource/titillium-web": "^4.5.9",
"@mui/x-data-grid-pro": "^6.5.0",
......@@ -40,7 +40,7 @@ describe("UserProvider", () => {
cy.wait("@getUserRoles");
cy.wait("@infoFromUserName");
cy.get("#display").contains("John Wayne").contains("DeploymentToolAdmin");
cy.get("#display").contains("John Smith").contains("DeploymentToolAdmin");
cy.get("@infoFromUserName.all").should("have.length", 1);
});
......
......@@ -7,17 +7,14 @@ import React, {
} from "react";
import { useNavigate } from "react-router-dom";
import {
Box,
Button,
Typography,
Grid,
Tooltip,
LinearProgress
} from "@mui/material";
import {
SimpleAccordion,
ConfirmationDialog,
useAPIMethod
} from "@ess-ics/ce-ui-common";
import { ConfirmationDialog, useAPIMethod } from "@ess-ics/ce-ui-common";
import Alert from "@mui/material/Alert";
import AccessControl from "../../auth/AccessControl";
import { IocActiveDeployment } from "../../../api/DataTypes";
......@@ -113,11 +110,14 @@ export default function AdministerUndeployment({ ioc, buttonDisabled }) {
onClose={onClose}
onConfirm={onConfirm}
/>
<SimpleAccordion
summary="Change deployment status to not deployed (e.g. if IOC has been manually removed)"
defaultExpanded
>
<Box sx={{ pt: 2 }}>
<Typography
sx={{ my: 2.5 }}
variant="h3"
>
Change deployment status to not deployed (e.g. if IOC has been
manually removed)
</Typography>
<Grid
container
spacing={1}
......@@ -155,7 +155,7 @@ export default function AdministerUndeployment({ ioc, buttonDisabled }) {
</Tooltip>
</Grid>
</Grid>
</SimpleAccordion>
</Box>
</>
</AccessControl>
);
......
......@@ -6,12 +6,9 @@ import React, {
useContext
} from "react";
import AccessControl from "../../auth/AccessControl";
import { ConfirmationDialog, useAPIMethod } from "@ess-ics/ce-ui-common";
import {
SimpleAccordion,
ConfirmationDialog,
useAPIMethod
} from "@ess-ics/ce-ui-common";
import {
Box,
Button,
Typography,
Grid,
......@@ -62,7 +59,7 @@ export default function ChangeHostAdmin({
// for the dialog
const [error, setError] = useState();
const [open, setOpen] = useState();
const [open, setOpen] = useState(false);
const {
value: updatedIoc,
......@@ -147,10 +144,13 @@ export default function ChangeHostAdmin({
onClose={onClose}
onConfirm={onConfirm}
/>
<SimpleAccordion
summary="Change deployment host"
defaultExpanded
>
<Box sx={{ pt: 2 }}>
<Typography
sx={{ my: 2.5 }}
variant="h3"
>
Change deployment host
</Typography>
<Grid
container
spacing={1}
......@@ -234,7 +234,7 @@ export default function ChangeHostAdmin({
</Tooltip>
</Grid>
</Grid>
</SimpleAccordion>
</Box>
</AccessControl>
</>
);
......
......@@ -2,12 +2,13 @@ import React from "react";
import { IconBadge } from "../../common/Badge/IconBadge";
import { IOCStatusIcon } from "../IOCIcons";
export function IOCBadge({ ioc }) {
export function IOCBadge({ ioc, ...rest }) {
return (
<IconBadge
icon={<IOCStatusIcon ioc={ioc} />}
title={ioc.namingName ?? ioc.name}
subheader={ioc.activeDeployment?.host?.fqdn || "---"}
{...rest}
/>
);
}
......@@ -6,8 +6,8 @@ import React, {
useMemo
} from "react";
import { useNavigate } from "react-router-dom";
import { Button, Typography, Grid, Tooltip } from "@mui/material";
import { SimpleAccordion, ConfirmationDialog } from "@ess-ics/ce-ui-common";
import { Box, Button, Typography, Grid, Tooltip } from "@mui/material";
import { ConfirmationDialog } from "@ess-ics/ce-ui-common";
import Alert from "@mui/material/Alert";
import AccessControl from "../../auth/AccessControl";
import { apiContext } from "../../../api/DeployApi";
......@@ -92,10 +92,13 @@ export default function IOCDelete({ ioc, buttonDisabled }) {
onConfirm={onConfirm}
/>
<SimpleAccordion
summary="Delete IOC"
defaultExpanded
>
<Box sx={{ pt: 2 }}>
<Typography
sx={{ my: 2.5 }}
variant="h3"
>
Delete IOC
</Typography>
<Grid
container
spacing={1}
......@@ -137,7 +140,7 @@ export default function IOCDelete({ ioc, buttonDisabled }) {
disabled={
buttonDisabled ||
ioc.operationInProgress ||
ioc.activeDeployment
Boolean(ioc.activeDeployment)
}
color="error"
variant="contained"
......@@ -149,7 +152,7 @@ export default function IOCDelete({ ioc, buttonDisabled }) {
</AccessControl>
</Grid>
</Grid>
</SimpleAccordion>
</Box>
</>
);
}
......@@ -44,7 +44,8 @@ export function IOCDeployDialog({
error: tagOrCommitIdError
} = useAPIMethod({
fcn: client.apis.Git.listTagsAndCommitIds,
call: false
call: false,
init: []
});
const getTagOrCommitId = useCallback(
(gitProjectId, reference, includeAllReference, searchMethod) => {
......
import React, { useState, useEffect, useCallback, useContext } from "react";
import { ConfirmationDialog, useAPIMethod } from "@ess-ics/ce-ui-common";
import {
SimpleAccordion,
ConfirmationDialog,
useAPIMethod
} from "@ess-ics/ce-ui-common";
import {
Box,
Button,
CircularProgress,
TextField,
......@@ -125,7 +122,7 @@ export default function IOCDetailAdmin({
}
}, [uioc, getIOC, resetTab]);
const iocIsDeployed = ioc.activeDeployment;
const iocIsDeployed = Boolean(ioc.activeDeployment);
let nameDisabledTitle = "";
if (iocIsDeployed) {
......@@ -271,10 +268,13 @@ export default function IOCDetailAdmin({
onClose={onClose}
onConfirm={onConfirm}
/>
<SimpleAccordion
summary="Modify IOC"
defaultExpanded
>
<Box sx={{ pt: 2 }}>
<Typography
sx={{ my: 2.5 }}
variant="h3"
>
Modify IOC
</Typography>
<form
onSubmit={onSubmit}
style={{
......@@ -303,8 +303,6 @@ export default function IOCDetailAdmin({
{error ? <Alert severity="error">{error}</Alert> : <></>}
<br />
<br />
<Tooltip title={disabledButtonTitle}>
<span>
<Button
......@@ -323,7 +321,7 @@ export default function IOCDetailAdmin({
</span>
</Tooltip>
</form>
</SimpleAccordion>
</Box>
</>
);
}
import { Grid, Box } from "@mui/material";
import React from "react";
import { KeyValueTable, SimpleAccordion } from "@ess-ics/ce-ui-common";
import { KeyValueTable } from "@ess-ics/ce-ui-common";
import { IOCBadge } from "../IOCBadge";
import AccessControl from "../../auth/AccessControl";
......@@ -70,17 +70,16 @@ export function IOCDetails({ ioc, getSubset = defaultSubset, alert, buttons }) {
item
xs={12}
>
<SimpleAccordion
defaultExpanded
summary={<IOCBadge ioc={ioc} />}
>
<KeyValueTable
obj={subset}
variant="overline"
sx={{ border: 0 }}
valueOptions={{ headerName: "" }}
/>
</SimpleAccordion>
<IOCBadge
ioc={ioc}
sx={{ mt: 2.5 }}
/>
<KeyValueTable
obj={subset}
variant="overline"
sx={{ border: 0 }}
valueOptions={{ headerName: "" }}
/>
</Grid>
</Grid>
</>
......
......@@ -4,7 +4,7 @@ import {
Brightness1,
Cancel,
Error,
RadioButtonUnchecked,
StopCircle,
ErrorOutline,
HelpOutline,
PlayCircleFilled,
......@@ -14,9 +14,12 @@ import Popover from "../../common/Popover";
import { AlertBannerList } from "@ess-ics/ce-ui-common";
function AlertMessagesPopoverContents({ title, alerts }) {
// for now filter out links on alerts due to issues with
// Filter out INFO level alerts
// And for now filter out links on alerts due to issues with
// focus behavior of popovers
const alertsWithoutLinks = (alerts || []).map((alert) => ({
const alertsWithoutLinks = (
alerts.filter((alert) => alert?.type.toLowerCase() !== "info") || []
).map((alert) => ({
...alert,
link: undefined
}));
......@@ -57,7 +60,7 @@ export function IOCStatusIcon({ ioc }) {
},
inactive: {
title: "Inactive",
icon: <RadioButtonUnchecked title="Inactive" />
icon: <StopCircle title="Inactive" />
},
"inactive alert": {
title: "Alert",
......
......@@ -90,19 +90,11 @@ export function IOCLiveStatus({ ioc }) {
allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}
renderNoAccess={() => <></>}
>
<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>
<LokiContainer
csEntryId={liveIOC.activeDeployment?.host.csEntryId}
iocName={ioc.namingName}
isDeployed={isIocDeployed(ioc)}
/>
<SimpleAccordion
summary="Records"
expanded={deserialize(state.records_open)}
......
......@@ -11,15 +11,12 @@ import { DeployIOC } from "../DeployIOC";
import { UndeployIOC } from "../UndeployIOC";
import {
userContext,
SimpleAccordion,
useAPIMethod,
AlertBannerList
} from "@ess-ics/ce-ui-common";
import AccessControl from "../../auth/AccessControl";
import { DeploymentStatus } from "../../../api/DataTypes";
import { IOCService } from "../IOCService";
import useUrlState from "@ahooksjs/use-url-state";
import { serialize, deserialize } from "../../common/URLState/URLState";
import { JobTable } from "../../Job";
import { apiContext } from "../../../api/DeployApi";
......@@ -36,13 +33,6 @@ export function IOCManage({
onPage
}) {
const { user } = useContext(userContext);
const [state, setState] = useUrlState(
{
log_open: "true"
},
{ navigateMode: "replace" }
);
const client = useContext(apiContext);
const [deployDialogOpen, setDeployDialogOpen] = useState(false);
......@@ -88,7 +78,7 @@ export function IOCManage({
const showControls = deploymentStatus.wasSuccessful();
let subset = {
"IOC name": (
"Naming service record": (
<Typography>
<MuiLink
href={`${window.NAMING_ADDRESS}/devices.xhtml?i=2&deviceName=${ioc.namingName}`}
......@@ -100,7 +90,7 @@ export function IOCManage({
</MuiLink>
</Typography>
),
git: (
Repository: (
<Typography>
<MuiLink
href={git}
......@@ -127,7 +117,7 @@ export function IOCManage({
}}
/>
) : (
"Can't be controlled, because IOC is not (yet) deployed succesfully"
"IOC not yet deployed"
);
}
......@@ -224,27 +214,19 @@ export function IOCManage({
allowedRoles={["DeploymentToolAdmin", "DeploymentToolIntegrator"]}
renderNoAccess={() => <></>}
>
<SimpleAccordion
summary="Log"
expanded={deserialize(state.log_open)}
onChange={(event, expanded) =>
setState({ log_open: serialize(expanded) })
}
>
<JobTable
jobs={operations}
pagination={pagination}
onPage={onPage}
loading={operationsLoading}
/>
</SimpleAccordion>
<JobTable
jobs={operations}
pagination={pagination}
onPage={onPage}
loading={operationsLoading}
/>
<DeployIOC
open={deployDialogOpen}
setOpen={setDeployDialogOpen}
submitCallback={closeDeployModal}
init={formInit}
iocId={ioc.id}
hasActiveDeployment={ioc.activeDeployment}
hasActiveDeployment={Boolean(ioc.activeDeployment)}
/>
<UndeployIOC
open={undeployDialogOpen}
......
......@@ -168,6 +168,7 @@ export function IOCTable({
pagination={pagination}
onPage={onPage}
loading={loading}
disableHover
/>
);
}
......@@ -97,18 +97,6 @@ export function JobDetails({ operation, job }) {
{operation.createdBy}
</MuiLink>
),
repository: (
<Typography>
<MuiLink
href={operation.gitProjectUrl}
target="_blank"
rel="noreferrer"
underline="hover"
>
{operation.gitProjectUrl}
</MuiLink>
</Typography>
),
"AWX job link": (
<Typography>
<MuiLink
......
import React from "react";
import { Table } from "@ess-ics/ce-ui-common";
import { Grid } from "@mui/material";
import { Box, Grid } from "@mui/material";
import { EllipsisTextLink, noWrapText } from "../common/Helper";
import { JobTypeIcon } from "./JobIcons";
import { formatDate } from "../common/Helper";
......@@ -53,12 +53,7 @@ function createTableRow(job, colorStyle) {
href={`/iocs/${job.iocId}`}
/>
),
version: (
<EllipsisTextLink
text={job.gitReference}
href={`/jobs/${job?.id}`}
/>
),
version: job.gitReference,
host: job.host?.hostName ? (
<EllipsisTextLink
text={job.host?.hostName}
......@@ -94,12 +89,15 @@ export function JobTable({
const [columns, createRow] = tableTypeSpecifics[rowType];
return (
<Table
columns={columns}
rows={jobs?.map((job) => createRow(job))}
pagination={pagination}
onPage={onPage}
loading={loading}
/>
<Box sx={{ pt: 2 }}>
<Table
columns={columns}
rows={jobs?.map((job) => createRow(job))}
pagination={pagination}
onPage={onPage}
loading={loading}
disableHover
/>
</Box>
);
}
import { Grid, Typography } from "@mui/material";
import React from "react";
export function IconBadge({ icon, secondaryIcon, title, subheader }) {
export function IconBadge({ icon, secondaryIcon, title, subheader, ...rest }) {
return (
<Grid
container
alignItems="center"
{...rest}
>
<Grid
item
......
import React, { useContext, useMemo } from "react";
import { Container } from "@mui/material";
import { Box, Typography } from "@mui/material";
import { LokiPanel } from "./LokiPanel";
import { apiContext } from "../../../api/DeployApi";
import { useAPIMethod } from "@ess-ics/ce-ui-common";
......@@ -15,14 +15,20 @@ export function LokiContainer({ csEntryId, iocName, isDeployed }) {
return (
<>
{iocHost && (
<Container>
<Box sx={{ pt: 2 }}>
<Typography
sx={{ my: 2.5 }}
variant="h3"
>
IOC log stream
</Typography>
<LokiPanel
host={iocHost}
isSyslog={false}
iocName={iocName}
isDeployed={isDeployed}
/>
</Container>
</Box>
)}
</>
);
......
......@@ -7,13 +7,13 @@ import React, {
} from "react";
import { styled } from "@mui/material/styles";
import {
Box,
Grid,
Select,
MenuItem,
InputLabel,
FormControl,
LinearProgress,
Container
LinearProgress
} from "@mui/material";
import { formatDate } from "../Helper.js";
import { Console } from "../Console/Console";
......@@ -32,7 +32,7 @@ const classes = {
undeployed: `${PREFIX}-undeployed`
};
const Root = styled("html")(({ theme }) => ({
const Root = styled("div")(({ theme }) => ({
[`& .${classes.formControl}`]: {
margin: theme.spacing(0),
minWidth: 120
......@@ -84,6 +84,77 @@ export function LokiPanel({ host, iocName, isSyslog, isDeployed }) {
const [timeRangeText, setTimeRangeText] = React.useState("12 hours");
const [periodChange, setPeriodChange] = useState(false);
const [alertIds, setAlertIds] = useState([]);
const [html, setHtml] = useState("");
const preprocessLog = useCallback(
(
logData,
isDeployed,
showWarning,
timeRangeText,
alertIds,
setAlertIds
) => {
if (logData?.warning && alertIds?.length === 0) {
const warningId = showWarning(logData.warning, "warning");
setAlertIds((alertIds) => [...alertIds, warningId]);
}
if (logData && logData.lines?.length > 0) {
const logHtml = logData.lines.map((line) => formatLogLine(line));
return `<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">.logdate { color: #5cb85c; } body.pre{font-family: Monaco, Menlo, Consolas, "Courier New", monospace;}
</style>
</head>
<body>
<pre>${logHtml.join("")}</pre>
</body>
</html>`;
} else {
if (!(isDeployed === true)) {
return "<html><body> NOT DEPLOYED YET! </body></html>";
}
}
return `<html><body> - No messages found for ${timeRangeText} period - </body></html>`;
},
[]
);
const logsToPreprocess = useCallback(
(
isSysLog,
logData,
procServLog,
isDeployed,
showWarning,
timeRangeText,
alertIds,
setAlertIds
) => {
if (isSysLog === true) {
return preprocessLog(
logData,
true,
showWarning,
timeRangeText,
alertIds,
setAlertIds
);
}
return preprocessLog(
procServLog,
isDeployed,
showWarning,
timeRangeText,
alertIds,
setAlertIds
);
},
[preprocessLog]
);
const params = useMemo(
() => ({
......@@ -171,6 +242,31 @@ export function LokiPanel({ host, iocName, isSyslog, isDeployed }) {
// return <div style={{ width: "100%" }}><LinearProgress color="primary" /></div>;
// }
useEffect(() => {
setHtml(
logsToPreprocess(
isSyslog,
sysLogData,
procServLog,
isDeployed,
showWarning,
timeRangeText,
alertIds,
setAlertIds
)
);
}, [
setHtml,
alertIds,
isDeployed,
isSyslog,
logsToPreprocess,
procServLog,
showWarning,
sysLogData,
timeRangeText
]);
const dataReady = () => {
return sysLogData || procServLog;
};
......@@ -195,7 +291,7 @@ export function LokiPanel({ host, iocName, isSyslog, isDeployed }) {
return (
<Root>
<Container maxWidth="xl">
<Box>
{sysLogData || procServLog || !isDeployed ? (
<Grid
container
......@@ -208,19 +304,10 @@ export function LokiPanel({ host, iocName, isSyslog, isDeployed }) {
md={12}
>
<Console
html={logsToPreprocess(
isSyslog,
sysLogData,
procServLog,
isDeployed,
showWarning,
timeRangeText,
alertIds,
setAlertIds
)}
html={html}
dataReady={dataReady}
extraClass={isDeployed ? classes.deployed : classes.undeployed}
title={isSyslog ? "Syslog" : "ProcServ log"}
title={isSyslog ? "Host log stream" : "IOC log stream"}
dialogHeader={header}
loading={periodChange}
/>
......@@ -231,42 +318,11 @@ export function LokiPanel({ host, iocName, isSyslog, isDeployed }) {
<LinearProgress color="primary" />
</div>
)}
</Container>
</Box>
</Root>
);
}
function logsToPreprocess(
isSysLog,
logData,
procServLog,
isDeployed,
showWarning,
timeRangeText,
alertIds,
setAlertIds
) {
if (isSysLog === true) {
return preprocessLog(
logData,
true,
showWarning,
timeRangeText,
alertIds,
setAlertIds
);
}
return preprocessLog(
procServLog,
isDeployed,
showWarning,
timeRangeText,
alertIds,
setAlertIds
);
}
function formatLogLine(logLine) {
var Convert = require("ansi-to-html");
var convert = new Convert();
......@@ -279,36 +335,3 @@ function formatLogLine(logLine) {
"<br/></span>"
);
}
function preprocessLog(
logData,
isDeployed,
showWarning,
timeRangeText,
alertIds,
setAlertIds
) {
if (logData.warning && alertIds.length === 0) {
const warningId = showWarning(logData.warning, "warning");
setAlertIds((alertIds) => [...alertIds, warningId]);
}
if (logData && logData.lines?.length > 0) {
var logHtml = logData.lines.map((line) => formatLogLine(line));
return `<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">.logdate { color: #5cb85c; } body.pre{font-family: Monaco, Menlo, Consolas, "Courier New", monospace;}
</style>
</head>
<body>
<pre>${logHtml.join("")}</pre>
</body>
</html>`;
} else {
if (!(isDeployed === true)) {
return "<html><body> NOT DEPLOYED YET! </body></html>";
}
}
return `<html><body> - No messages found for ${timeRangeText} period - </body></html>`;
}
import React, {
CircularProgress,
Container,
Fade,
TextField
} from "@mui/material";
import React, { CircularProgress, Fade, TextField } from "@mui/material";
import { Check } from "@mui/icons-material";
import { useEffect, useState } from "react";
import { useEffectAfterMount } from "../../../hooks/MountEffects";
......@@ -63,7 +58,7 @@ export function SearchBar({
const LoadingAnimation = loading ? Loading : Finished;
return (
<Container>
<>
<TextField
fullWidth
variant="outlined"
......@@ -74,9 +69,9 @@ export function SearchBar({
InputProps={{
endAdornment: showAnimation && LoadingAnimation
}}
sx={{ mb: 3 }}
/>
<div>&nbsp;</div>
{children}
</Container>
</>
);
}