[release] v0.13.0-unstable0
This commit is contained in:
parent
320b29df55
commit
4884c95a50
|
@ -1,3 +1,10 @@
|
|||
## Version 0.13.0
|
||||
- Display containers as stacks
|
||||
- new Delete modal to delete services entirely
|
||||
- cosmos-network now have container names instead for network names
|
||||
- Fix issue where search bar reset when deleting volume/network
|
||||
- Fix breadcrumbs in subpaths
|
||||
|
||||
## Version 0.12.6
|
||||
- Fix a security issue with cross-domain APIs availability
|
||||
|
||||
|
|
|
@ -15,6 +15,15 @@ const Breadcrumbs = ({ navigation, title, ...others }) => {
|
|||
const location = useLocation();
|
||||
const [main, setMain] = useState();
|
||||
const [item, setItem] = useState();
|
||||
let subItem = '';
|
||||
|
||||
// extract /servapps/stack/:stack
|
||||
const subPath = location.pathname.split('/')[3];
|
||||
if(subPath && location.pathname.split('/')[4]) {
|
||||
subItem = <Typography variant="subtitle1" color="textPrimary">
|
||||
{location.pathname.split('/')[4]}
|
||||
</Typography>;
|
||||
}
|
||||
|
||||
// set active item state
|
||||
const getCollapse = (menu) => {
|
||||
|
@ -23,7 +32,7 @@ const Breadcrumbs = ({ navigation, title, ...others }) => {
|
|||
if (collapse.type && collapse.type === 'collapse') {
|
||||
getCollapse(collapse);
|
||||
} else if (collapse.type && collapse.type === 'item') {
|
||||
if (location.pathname === collapse.url) {
|
||||
if (location.pathname.startsWith(collapse.url)) {
|
||||
setMain(menu);
|
||||
setItem(collapse);
|
||||
}
|
||||
|
@ -82,6 +91,8 @@ const Breadcrumbs = ({ navigation, title, ...others }) => {
|
|||
</Typography>
|
||||
{mainContent}
|
||||
{itemContent}
|
||||
{subPath && <Typography variant="subtitle1" color="textPrimary">{subPath}</Typography>}
|
||||
{subItem}
|
||||
</MuiBreadcrumbs>
|
||||
</Grid>
|
||||
{title && (
|
||||
|
|
|
@ -3,13 +3,13 @@ import { Card, Chip, Stack, Tooltip } from "@mui/material";
|
|||
import { useState } from "react";
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
export const DeleteButton = ({onDelete, disabled}) => {
|
||||
export const DeleteButton = ({onDelete, disabled, size}) => {
|
||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||
|
||||
return (<>
|
||||
{!confirmDelete && (<Chip label={<DeleteOutlined />}
|
||||
{!confirmDelete && (<Chip label={<DeleteOutlined size={size}/>}
|
||||
onClick={() => !disabled && setConfirmDelete(true)}/>)}
|
||||
{confirmDelete && (<Chip label={<CheckOutlined />} color="error"
|
||||
{confirmDelete && (<Chip label={<CheckOutlined size={size}/>} color="error"
|
||||
onClick={(event) => !disabled && onDelete(event)}/>)}
|
||||
</>);
|
||||
}
|
|
@ -6,12 +6,12 @@ import TableContainer from '@mui/material/TableContainer';
|
|||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import { Input, InputAdornment, Stack, TextField, useMediaQuery } from '@mui/material';
|
||||
import { CircularProgress, Input, InputAdornment, Stack, TextField, useMediaQuery } from '@mui/material';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const PrettyTableView = ({ getKey, data, columns, sort, onRowClick, linkTo, buttons, fullWidth }) => {
|
||||
const PrettyTableView = ({ isLoading, getKey, data, columns, sort, onRowClick, linkTo, buttons, fullWidth }) => {
|
||||
const [search, setSearch] = React.useState('');
|
||||
const theme = useTheme();
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
|
@ -43,7 +43,17 @@ const PrettyTableView = ({ getKey, data, columns, sort, onRowClick, linkTo, butt
|
|||
/>
|
||||
{buttons}
|
||||
</Stack>
|
||||
<TableContainer style={{width: fullWidth ? '100%': '', background: isDark ? '#252b32' : '', borderTop: '3px solid ' + theme.palette.primary.main}} component={Paper}>
|
||||
|
||||
{isLoading && (<div style={{height: '550px'}}>
|
||||
<center>
|
||||
<br />
|
||||
<CircularProgress />
|
||||
</center>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{!isLoading && <TableContainer style={{width: fullWidth ? '100%': '', background: isDark ? '#252b32' : '', borderTop: '3px solid ' + theme.palette.primary.main}} component={Paper}>
|
||||
<Table aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
|
@ -102,7 +112,7 @@ const PrettyTableView = ({ getKey, data, columns, sort, onRowClick, linkTo, butt
|
|||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</TableContainer>}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { useEffect } from 'react';
|
|||
import { redirectToLocal } from './utils/indexs';
|
||||
|
||||
const IsLoggedIn = () => useEffect(() => {
|
||||
console.log("CHECK LOGIN")
|
||||
const urlSearch = encodeURIComponent(window.location.search);
|
||||
const redirectToURL = (window.location.pathname + urlSearch);
|
||||
|
||||
|
|
|
@ -71,6 +71,7 @@ const RouteManagement = ({ routeConfig, routeNames, config, TargetContainer, noC
|
|||
PathPrefix: routeConfig.PathPrefix,
|
||||
StripPathPrefix: routeConfig.StripPathPrefix,
|
||||
AuthEnabled: routeConfig.AuthEnabled,
|
||||
HideFromDashboard: routeConfig.HideFromDashboard,
|
||||
_SmartShield_Enabled: (routeConfig.SmartShield ? routeConfig.SmartShield.Enabled : false),
|
||||
RestrictToConstellation: routeConfig.RestrictToConstellation,
|
||||
OverwriteHostHeader: routeConfig.OverwriteHostHeader,
|
||||
|
@ -273,6 +274,13 @@ const RouteManagement = ({ routeConfig, routeNames, config, TargetContainer, noC
|
|||
|
||||
<CosmosCollapse title={'Advanced Settings'}>
|
||||
<Stack spacing={2}>
|
||||
<CosmosCheckbox
|
||||
name="HideFromDashboard"
|
||||
label="Hide from Dashboard"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosFormDivider />
|
||||
<Alert severity='info'>These settings are for advanced users only. Please do not change these unless you know what you are doing.</Alert>
|
||||
<CosmosInputText
|
||||
name="OverwriteHostHeader"
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
import React from 'react';
|
||||
import { Box, IconButton, LinearProgress, Stack, Tooltip, useMediaQuery } from '@mui/material';
|
||||
import { Box, Chip, IconButton, LinearProgress, Stack, Tooltip, useMediaQuery } from '@mui/material';
|
||||
import { CheckCircleOutlined, CloseSquareOutlined, DeleteOutlined, PauseCircleOutlined, PlaySquareOutlined, ReloadOutlined, RollbackOutlined, StopOutlined, UpCircleOutlined } from '@ant-design/icons';
|
||||
import * as API from '../../api';
|
||||
import LogsInModal from '../../components/logsInModal';
|
||||
import DeleteModal from './deleteModal';
|
||||
|
||||
const GetActions = ({
|
||||
Id,
|
||||
Ids,
|
||||
state,
|
||||
image,
|
||||
refreshServApps,
|
||||
setIsUpdatingId,
|
||||
updateAvailable
|
||||
updateAvailable,
|
||||
isStack,
|
||||
containers,
|
||||
config,
|
||||
}) => {
|
||||
const [confirmDelete, setConfirmDelete] = React.useState(false);
|
||||
const isMiniMobile = useMediaQuery((theme) => theme.breakpoints.down('xsm'));
|
||||
const [pullRequest, setPullRequest] = React.useState(null);
|
||||
const [isUpdating, setIsUpdating] = React.useState(false);
|
||||
|
||||
|
||||
const doTo = (action) => {
|
||||
setIsUpdating(true);
|
||||
|
@ -27,20 +33,41 @@ const GetActions = ({
|
|||
return;
|
||||
}
|
||||
|
||||
setIsUpdatingId(Id, true);
|
||||
return API.docker.manageContainer(Id, action).then((res) => {
|
||||
setIsUpdating(false);
|
||||
refreshServApps();
|
||||
}).catch((err) => {
|
||||
setIsUpdating(false);
|
||||
refreshServApps();
|
||||
});
|
||||
return isStack ?
|
||||
(() => {
|
||||
Promise.all(Ids.map((id) => {
|
||||
setIsUpdatingId(id, true);
|
||||
|
||||
return API.docker.manageContainer(id, action)
|
||||
})).then((res) => {
|
||||
setIsUpdating(false);
|
||||
refreshServApps();
|
||||
}).catch((err) => {
|
||||
setIsUpdating(false);
|
||||
refreshServApps();
|
||||
})
|
||||
})()
|
||||
:
|
||||
(() => {
|
||||
setIsUpdatingId(Id, true);
|
||||
|
||||
API.docker.manageContainer(Id, action).then((res) => {
|
||||
setIsUpdating(false);
|
||||
refreshServApps();
|
||||
}).catch((err) => {
|
||||
setIsUpdating(false);
|
||||
refreshServApps();
|
||||
});
|
||||
})()
|
||||
};
|
||||
|
||||
let actions = [
|
||||
{
|
||||
t: 'Update Available, Click to Update',
|
||||
t: 'Update Available' + (isStack ? ', go the stack details to update' : ', Click to Update'),
|
||||
if: ['update_available'],
|
||||
es: <IconButton className="shinyButton" style={{cursor: 'not-allowed'}} color='primary' onClick={()=>{}} size={isMiniMobile ? 'medium' : 'large'}>
|
||||
<UpCircleOutlined />
|
||||
</IconButton>,
|
||||
e: <IconButton className="shinyButton" color='primary' onClick={() => {doTo('update')}} size={isMiniMobile ? 'medium' : 'large'}>
|
||||
<UpCircleOutlined />
|
||||
</IconButton>
|
||||
|
@ -48,6 +75,7 @@ const GetActions = ({
|
|||
{
|
||||
t: 'No Update Available. Click to Force Pull',
|
||||
if: ['update_not_available'],
|
||||
hideStack: true,
|
||||
e: <IconButton onClick={() => {doTo('update')}} size={isMiniMobile ? 'medium' : 'large'}>
|
||||
<UpCircleOutlined />
|
||||
</IconButton>
|
||||
|
@ -90,6 +118,7 @@ const GetActions = ({
|
|||
{
|
||||
t: 'Re-create',
|
||||
if: ['exited', 'running', 'paused', 'created', 'restarting'],
|
||||
hideStack: true,
|
||||
e: <IconButton onClick={() => doTo('recreate')} color="error" size={isMiniMobile ? 'medium' : 'large'}>
|
||||
<RollbackOutlined />
|
||||
</IconButton>
|
||||
|
@ -104,13 +133,7 @@ const GetActions = ({
|
|||
{
|
||||
t: 'Delete',
|
||||
if: ['exited', 'created'],
|
||||
e: <IconButton onClick={() => {
|
||||
if(confirmDelete) doTo('remove')
|
||||
else setConfirmDelete(true);
|
||||
}} color="error" size='large'>
|
||||
{confirmDelete ? <CheckCircleOutlined />
|
||||
: <DeleteOutlined />}
|
||||
</IconButton>
|
||||
e: <DeleteModal config={config} Ids={Ids} containers={containers} refreshServApps={refreshServApps} setIsUpdatingId={setIsUpdatingId} />
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -126,7 +149,7 @@ const GetActions = ({
|
|||
{!isUpdating && actions.filter((action) => {
|
||||
return action.if.includes(state) || (updateAvailable && action.if.includes('update_available')) || (!updateAvailable && action.if.includes('update_not_available'));
|
||||
}).map((action) => {
|
||||
return <Tooltip title={action.t}>{action.e}</Tooltip>
|
||||
return (!isStack || !action.hideStack) && <Tooltip title={action.t}>{isStack ? (action.es ? action.es : action.e) : action.e}</Tooltip>
|
||||
})}
|
||||
|
||||
{isUpdating && <Stack sx={{
|
||||
|
|
|
@ -93,6 +93,7 @@ const ContainerOverview = ({ containerInfo, config, refresh, updatesAvailable, s
|
|||
<Stack spacing={2} direction={'row'} >
|
||||
<GetActions
|
||||
Id={containerInfo.Name}
|
||||
Ids={[containerInfo.Name]}
|
||||
image={Image}
|
||||
state={State.Status}
|
||||
refreshServApps={() => {
|
||||
|
@ -103,6 +104,9 @@ const ContainerOverview = ({ containerInfo, config, refresh, updatesAvailable, s
|
|||
setIsUpdating(true);
|
||||
}}
|
||||
updateAvailable={updatesAvailable && updatesAvailable[Name]}
|
||||
isStack={false}
|
||||
containers={[containerInfo]}
|
||||
config={config}
|
||||
/>
|
||||
</Stack>
|
||||
{containerInfo.State.Status !== 'running' && (
|
||||
|
|
246
client/src/pages/servapps/deleteModal.jsx
Normal file
246
client/src/pages/servapps/deleteModal.jsx
Normal file
|
@ -0,0 +1,246 @@
|
|||
import React from 'react';
|
||||
import { Box, Button, Checkbox, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, IconButton, LinearProgress, Stack, Tooltip, useMediaQuery } from '@mui/material';
|
||||
import { ApiOutlined, CheckCircleOutlined, CloseSquareOutlined, ContainerOutlined, DatabaseOutlined, DeleteOutlined, LinkOutlined, PauseCircleOutlined, PlaySquareOutlined, ReloadOutlined, RollbackOutlined, StopOutlined, UpCircleOutlined } from '@ant-design/icons';
|
||||
import * as API from '../../api';
|
||||
import LogsInModal from '../../components/logsInModal';
|
||||
import { DeleteButton } from '../../components/delete';
|
||||
import { CosmosCheckbox } from '../config/users/formShortcuts';
|
||||
import { getContainersRoutes } from '../../utils/routes';
|
||||
|
||||
const DeleteModal = ({Ids, containers, refreshServApps, setIsUpdatingId, config}) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const [confirmDelete, setConfirmDelete] = React.useState(false);
|
||||
const [failed, setFailed] = React.useState([]);
|
||||
const [deleted, setDeleted] = React.useState([]);
|
||||
const [ignored, setIgnored] = React.useState([]);
|
||||
const [isDeleting, setIsDeleting] = React.useState(false);
|
||||
|
||||
containers = containers.map((container) => {
|
||||
if(container.Names) {
|
||||
container.Name = container.Names[0];
|
||||
}
|
||||
return container;
|
||||
});
|
||||
|
||||
const ShowAction = ({item}) => {
|
||||
if(!isDeleting) {
|
||||
return <Checkbox checked={!ignored.includes(item)} size="large" id={item} onChange={(e) => {
|
||||
if(!e.target.checked) {
|
||||
setIgnored((prev) => {
|
||||
if(!prev) return [item];
|
||||
else return [...prev, item];
|
||||
});
|
||||
} else {
|
||||
setIgnored((prev) => {
|
||||
if(!prev) return [];
|
||||
else return prev.filter((i) => i !== item);
|
||||
});
|
||||
}
|
||||
}} />
|
||||
}
|
||||
|
||||
if(failed.includes(item)) {
|
||||
return "❌"
|
||||
} else if(deleted.includes(item)) {
|
||||
return "✔️"
|
||||
} else {
|
||||
return <CircularProgress size={18} />
|
||||
}
|
||||
}
|
||||
|
||||
let networks = isOpen && containers.map((container) => {
|
||||
return Object.keys(container.NetworkSettings.Networks);
|
||||
}).flat().filter((network, index, self) => {
|
||||
return self.indexOf(network) === index;
|
||||
});
|
||||
|
||||
let volumes = isOpen && containers.map((container) => {
|
||||
return container.Mounts.filter((mount) => {
|
||||
return mount.Type === 'volume'
|
||||
}).map((mount) => {
|
||||
return mount.Name
|
||||
})
|
||||
}).flat().filter((volume, index, self) => {
|
||||
return self.indexOf(volume) === index;
|
||||
});
|
||||
|
||||
let routes = isOpen && containers.map((container) => {
|
||||
return getContainersRoutes(config, container.Name.replace('/', ''));
|
||||
}).flat().map((route) => {
|
||||
return route.Name;
|
||||
}).filter((route, index, self) => {
|
||||
return self.indexOf(route) === index;
|
||||
});
|
||||
|
||||
console.log(routes);
|
||||
|
||||
const doDelete = () => {
|
||||
setIsDeleting(true);
|
||||
|
||||
const promises = [];
|
||||
|
||||
promises.concat(
|
||||
containers.map((container) => {
|
||||
let key = container.Name + '-container';
|
||||
if (ignored.includes(key)) return;
|
||||
|
||||
return API.docker.manageContainer(container.Name, 'remove')
|
||||
.then(() => {
|
||||
setDeleted((prev) => {
|
||||
if(!prev) return [key];
|
||||
else return [...prev, key];
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
setFailed((prev) => {
|
||||
if(!prev) return [key];
|
||||
else return [...prev, key];
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
Promise.all(promises)
|
||||
.then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
const promises2 = [];
|
||||
|
||||
promises2.concat(
|
||||
networks.map((network) => {
|
||||
let key = network + '-network';
|
||||
if (ignored.includes(key)) return;
|
||||
|
||||
return API.docker.networkDelete(network)
|
||||
.then(() => {
|
||||
setDeleted((prev) => {
|
||||
if(!prev) return [key];
|
||||
else return [...prev, key];
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
setFailed((prev) => {
|
||||
if(!prev) return [key];
|
||||
else return [...prev, key];
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
promises2.concat(
|
||||
volumes.map((volume) => {
|
||||
let key = volume + '-volume';
|
||||
if (ignored.includes(key)) return;
|
||||
|
||||
return API.docker.volumeDelete(volume)
|
||||
.then(() => {
|
||||
setDeleted((prev) => {
|
||||
if(!prev) return [key];
|
||||
else return [...prev, key];
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
setFailed((prev) => {
|
||||
if(!prev) return [key];
|
||||
else return [...prev, key];
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
promises2.concat(
|
||||
routes.map((route) => {
|
||||
let key = route + '-route';
|
||||
if (ignored.includes(key)) return;
|
||||
|
||||
return API.config.deleteRoute(route)
|
||||
.then(() => {
|
||||
setDeleted((prev) => {
|
||||
if(!prev) return [key];
|
||||
else return [...prev, key];
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
setFailed((prev) => {
|
||||
if(!prev) return [key];
|
||||
else return [...prev, key];
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
return Promise.all(promises2);
|
||||
})
|
||||
}
|
||||
|
||||
return <>
|
||||
{isOpen && <>
|
||||
<Dialog open={isOpen} onClose={() => {refreshServApps() ; setIsOpen(false)}}>
|
||||
<DialogTitle>Delete Service</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
<Stack spacing={1}>
|
||||
<div>
|
||||
{isDeleting && <div>
|
||||
Deletion status:
|
||||
</div>}
|
||||
{!isDeleting && <div>
|
||||
Select what you wish to delete:
|
||||
</div>}
|
||||
</div>
|
||||
{containers.map((container) => {
|
||||
return (!isDeleting || (!ignored.includes(container.Name + "-container"))) && <div key={container.Name + "-container"}>
|
||||
<ShowAction item={container.Name + "-container"} /> <ContainerOutlined /> Container {container.Name}
|
||||
</div>
|
||||
})}
|
||||
{networks.map((network) => {
|
||||
return (!isDeleting || (!ignored.includes(network + "-network"))) &&<div key={network + "-network"}>
|
||||
<ShowAction item={network + "-network"} /> <ApiOutlined /> Network {network}
|
||||
</div>
|
||||
})}
|
||||
{volumes.map((mount) => {
|
||||
return (!isDeleting || (!ignored.includes(mount + "-volume"))) && <div key={mount + "-volume"}>
|
||||
<ShowAction item={mount + "-volume"} /> <DatabaseOutlined /> Volume {mount}
|
||||
</div>
|
||||
})}
|
||||
{routes.map((route) => {
|
||||
return (!isDeleting || (!ignored.includes(route + "-route"))) && <div key={route + "-route"}>
|
||||
<ShowAction item={route + "-route"} /> <LinkOutlined /> Route {route}
|
||||
</div>
|
||||
})}
|
||||
</Stack>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
{!isDeleting && <DialogActions>
|
||||
<Button onClick={() => setIsOpen(false)}>Cancel</Button>
|
||||
<Button onClick={() => {
|
||||
doDelete();
|
||||
}}>Delete</Button>
|
||||
</DialogActions>}
|
||||
{isDeleting && <DialogActions>
|
||||
<Button onClick={() => {
|
||||
refreshServApps();
|
||||
setIsOpen(false);
|
||||
}}>Done</Button>
|
||||
</DialogActions>}
|
||||
</Dialog>
|
||||
</>}
|
||||
|
||||
<IconButton onClick={() => {
|
||||
setIsOpen(true);
|
||||
setIsDeleting(false);
|
||||
setFailed([]);
|
||||
setDeleted([]);
|
||||
setIgnored([]);
|
||||
}} color="error" size='large'>
|
||||
<DeleteOutlined />
|
||||
</IconButton>
|
||||
</>
|
||||
}
|
||||
|
||||
export default DeleteModal;
|
|
@ -12,15 +12,18 @@ import PrettyTabbedView from '../../components/tabbedView/tabbedView';
|
|||
import ServApps from './servapps';
|
||||
import VolumeManagementList from './volumes';
|
||||
import NetworkManagementList from './networks';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
const ServappsIndex = () => {
|
||||
const { stack } = useParams();
|
||||
|
||||
return <div>
|
||||
<IsLoggedIn />
|
||||
|
||||
<PrettyTabbedView path="/cosmos-ui/servapps/:tab" tabs={[
|
||||
{!stack && <PrettyTabbedView path="/cosmos-ui/servapps/:tab" tabs={[
|
||||
{
|
||||
title: 'Containers',
|
||||
children: <ServApps />,
|
||||
children: <ServApps stack={stack} />,
|
||||
path: 'containers'
|
||||
},
|
||||
{
|
||||
|
@ -34,6 +37,9 @@ const ServappsIndex = () => {
|
|||
path: 'networks'
|
||||
},
|
||||
]}/>
|
||||
}
|
||||
|
||||
{stack && <ServApps stack={stack} />}
|
||||
|
||||
</div>;
|
||||
}
|
||||
|
|
|
@ -73,17 +73,10 @@ const NetworkManagementList = () => {
|
|||
</Button>
|
||||
</Stack>
|
||||
|
||||
{isLoading && (<div style={{ height: '550px' }}>
|
||||
<center>
|
||||
<br />
|
||||
<CircularProgress />
|
||||
</center>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && rows && (
|
||||
{rows && (
|
||||
<PrettyTableView
|
||||
data={rows}
|
||||
isLoading={isLoading}
|
||||
buttons={[
|
||||
<NewNetworkButton refresh={refresh} />,
|
||||
]}
|
||||
|
|
|
@ -22,6 +22,7 @@ import { ContainerNetworkWarning } from '../../components/containers';
|
|||
import { ServAppIcon } from '../../utils/servapp-icon';
|
||||
import MiniPlotComponent from '../dashboard/components/mini-plot';
|
||||
import { DownloadFile } from '../../api/downloadButton';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
const Item = styled(Paper)(({ theme }) => ({
|
||||
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
|
||||
|
@ -31,6 +32,15 @@ const Item = styled(Paper)(({ theme }) => ({
|
|||
color: theme.palette.text.secondary,
|
||||
}));
|
||||
|
||||
const StyledBadge = styled(Badge)(({ theme }) => ({
|
||||
'& .MuiBadge-badge': {
|
||||
right: 5,
|
||||
top: -10,
|
||||
border: `2px solid ${theme.palette.background.paper}`,
|
||||
padding: '0 4px',
|
||||
},
|
||||
}));
|
||||
|
||||
const noOver = {
|
||||
overflowX: 'auto',
|
||||
width: "100%",
|
||||
|
@ -38,7 +48,7 @@ const noOver = {
|
|||
height: "50px"
|
||||
}
|
||||
|
||||
const ServApps = () => {
|
||||
const ServApps = ({stack}) => {
|
||||
const [servApps, setServApps] = useState([]);
|
||||
const [isUpdating, setIsUpdating] = useState({});
|
||||
const [search, setSearch] = useState("");
|
||||
|
@ -114,6 +124,107 @@ const ServApps = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const statusPriority = [
|
||||
"running",
|
||||
"paused",
|
||||
"created",
|
||||
"restarting",
|
||||
"removing",
|
||||
"exited",
|
||||
"dead"
|
||||
]
|
||||
|
||||
const servAppsStacked = servApps && servApps.reduce((acc, app) => {
|
||||
// if has label cosmos-stack, add to stack
|
||||
if(!stack && (app.Labels['cosmos-stack'] || app.Labels['com.docker.compose.project'])) {
|
||||
let stackName = app.Labels['cosmos-stack'] || app.Labels['com.docker.compose.project'];
|
||||
let stackMain = app.Labels['cosmos-stack-main'] || (app.Labels['com.docker.compose.container-number'] == '1' && app.Names[0].replace('/', ''));
|
||||
|
||||
if(!acc[stackName]) {
|
||||
acc[stackName] = {
|
||||
type: 'stack',
|
||||
name: stackName,
|
||||
state: -1,
|
||||
app: {},
|
||||
apps: [],
|
||||
ports: [],
|
||||
isUpdating: false,
|
||||
updateAvailable: false,
|
||||
labels: {
|
||||
'cosmos-force-network-secured': 'true',
|
||||
'cosmos-auto-update': 'true',
|
||||
},
|
||||
networkSettings: {
|
||||
Networks: {}
|
||||
},
|
||||
};
|
||||
}
|
||||
acc[stackName].apps.push(app);
|
||||
if(statusPriority.indexOf(app.State) > statusPriority.indexOf(acc[stackName].state)) {
|
||||
acc[stackName].state = app.State;
|
||||
}
|
||||
acc[stackName].ports = acc[stackName].ports.concat(app.Ports);
|
||||
|
||||
if(!app.Labels['cosmos-force-network-secured']) {
|
||||
acc[stackName].labels['cosmos-force-network-secured'] = 'false';
|
||||
}
|
||||
|
||||
if(isUpdating[app.Names[0].replace('/', '')]) {
|
||||
acc[stackName].isUpdating = true;
|
||||
}
|
||||
|
||||
if(!app.Labels['cosmos-auto-update']) {
|
||||
acc[stackName].labels['cosmos-auto-update'] = 'false';
|
||||
}
|
||||
|
||||
acc[stackName].networkSettings = {
|
||||
...acc[stackName].networkSettings,
|
||||
...app.NetworkSettings
|
||||
};
|
||||
|
||||
if(updatesAvailable && updatesAvailable[app.Names[0]]) {
|
||||
acc[stackName].updateAvailable = true;
|
||||
}
|
||||
|
||||
if(stackMain == app.Names[0].replace('/', '') || !acc[stackName].app) {
|
||||
acc[stackName].app = app;
|
||||
}
|
||||
} else if (!stack || (stack && (app.Labels['cosmos-stack'] === stack || app.Labels['com.docker.compose.project'] === stack))){
|
||||
// else add to default stack
|
||||
acc[app.Names[0]] = {
|
||||
type: 'app',
|
||||
name: app.Names[0],
|
||||
state: app.State,
|
||||
app: app,
|
||||
apps: [app],
|
||||
isUpdating: isUpdating[app.Names[0].replace('/', '')],
|
||||
ports: app.Ports,
|
||||
networkSettings: app.NetworkSettings,
|
||||
labels: app.Labels,
|
||||
updateAvailable: updatesAvailable && updatesAvailable[app.Names[0]],
|
||||
};
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// flatten stacks with single app
|
||||
Object.keys(servAppsStacked).forEach((key) => {
|
||||
if(servAppsStacked[key].type === 'stack' && servAppsStacked[key].apps.length === 1) {
|
||||
servAppsStacked[key] = {
|
||||
...servAppsStacked[key],
|
||||
type: 'app',
|
||||
name: servAppsStacked[key].apps[0].Names[0],
|
||||
app: servAppsStacked[key].app,
|
||||
apps: [servAppsStacked[key].app],
|
||||
isUpdating: isUpdating[servAppsStacked[key].apps[0].Names[0].replace('/', '')],
|
||||
ports: servAppsStacked[key].apps[0].Ports,
|
||||
networkSettings: servAppsStacked[key].apps[0].NetworkSettings,
|
||||
labels: servAppsStacked[key].apps[0].Labels,
|
||||
updateAvailable: updatesAvailable && updatesAvailable[servAppsStacked[key].apps[0].Names[0]],
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return <div>
|
||||
<RestartModal openModal={openRestartModal} setOpenModal={setOpenRestartModal} config={config} newRoute />
|
||||
<ExposeModal
|
||||
|
@ -132,6 +243,9 @@ const ServApps = () => {
|
|||
|
||||
<Stack spacing={{xs: 1, sm: 1, md: 2 }}>
|
||||
<Stack direction="row" spacing={2}>
|
||||
{stack && <Link to="/cosmos-ui/servapps">
|
||||
<ResponsiveButton variant="secondary" startIcon={<RollbackOutlined />}>Back</ResponsiveButton>
|
||||
</Link>}
|
||||
<Input placeholder="Search"
|
||||
value={search}
|
||||
startAdornment={
|
||||
|
@ -146,6 +260,7 @@ const ServApps = () => {
|
|||
<ResponsiveButton variant="contained" startIcon={<ReloadOutlined />} onClick={() => {
|
||||
refreshServApps();
|
||||
}}>Refresh</ResponsiveButton>
|
||||
{!stack && <>
|
||||
<Link to="/cosmos-ui/servapps/new-service">
|
||||
<ResponsiveButton
|
||||
variant="contained"
|
||||
|
@ -158,6 +273,7 @@ const ServApps = () => {
|
|||
label={'Export Docker Backup'}
|
||||
contentGetter={API.config.getBackup}
|
||||
/>
|
||||
</>}
|
||||
</Stack>
|
||||
|
||||
<Grid2 container spacing={{xs: 1, sm: 1, md: 2 }}>
|
||||
|
@ -166,8 +282,8 @@ const ServApps = () => {
|
|||
<Alert severity="info">Update are available for {Object.keys(updatesAvailable).join(', ')}</Alert>
|
||||
</Item>
|
||||
</Grid2>}
|
||||
{servApps && servApps.filter(app => search.length < 2 || app.Names[0].toLowerCase().includes(search.toLowerCase())).map((app) => {
|
||||
return <Grid2 style={gridAnim} xs={12} sm={6} md={6} lg={6} xl={4} key={app.Id} item>
|
||||
{servApps && Object.values(servAppsStacked).filter(app => search.length < 2 || app.name.toLowerCase().includes(search.toLowerCase())).map((app) => {
|
||||
return <Grid2 sx={{...gridAnim}} xs={12} sm={6} md={6} lg={6} xl={4} key={app.Id} item>
|
||||
<Item>
|
||||
<Stack justifyContent='space-around' direction="column" spacing={2} padding={2} divider={<Divider orientation="horizontal" flexItem />}>
|
||||
<Stack direction="column" spacing={0} alignItems="flex-start">
|
||||
|
@ -182,29 +298,44 @@ const ServApps = () => {
|
|||
"paused": <Chip label="Paused" color="info" />,
|
||||
"exited": <Chip label="Exited" color="error" />,
|
||||
"dead": <Chip label="Dead" color="error" />,
|
||||
})[app.State]
|
||||
})[app.state]
|
||||
}
|
||||
</Typography>
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<ServAppIcon container={app} route={getFirstRoute(app)} className="loading-image" width="40px"/>
|
||||
{app.type === 'app' && <ServAppIcon container={app.app} route={getFirstRoute(app.app)} className="loading-image" width="40px"/>}
|
||||
{app.type === 'stack' && <StyledBadge overlap="circular"
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}color="primary"
|
||||
badgeContent={app.apps.length} >
|
||||
<ServAppIcon container={app.app} route={getFirstRoute(app.app)} className="loading-image" width="40px"/>
|
||||
</StyledBadge>}
|
||||
|
||||
<Stack direction="column" spacing={0} alignItems="flex-start" style={{height: '40px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'no-wrap'}}>
|
||||
<Typography variant="h5" color="text.secondary">
|
||||
{app.Names[0].replace('/', '')}
|
||||
{app.name.replace('/', '')} {app.type === 'stack' && <Chip label="Stack" color="primary" size="small" style={{fontSize: '55%'}}/>}
|
||||
</Typography>
|
||||
<Typography color="text.secondary" style={{fontSize: '80%', whiteSpace: 'nowrap', overflow: 'hidden', maxWidth: '100%', textOverflow: 'ellipsis'}}>
|
||||
{app.Image}
|
||||
{app.app.Image}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={1} width='100%'>
|
||||
<GetActions
|
||||
Id={app.Names[0].replace('/', '')}
|
||||
image={app.Image}
|
||||
state={app.State}
|
||||
Id={app.app.Names[0].replace('/', '')}
|
||||
Ids={app.apps.map((app) => {
|
||||
return app.Names[0].replace('/', '');
|
||||
})}
|
||||
image={app.app.Image}
|
||||
state={app.app.State}
|
||||
setIsUpdatingId={setIsUpdatingId}
|
||||
refreshServApps={refreshServApps}
|
||||
updateAvailable={updatesAvailable && updatesAvailable[app.Names[0]]}
|
||||
updateAvailable={app.updateAvailable}
|
||||
isStack={app.type === 'stack'}
|
||||
containers={app.apps}
|
||||
config={config}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
@ -213,7 +344,7 @@ const ServApps = () => {
|
|||
Ports
|
||||
</Typography>
|
||||
<Stack style={noOver} margin={1} direction="row" spacing={1}>
|
||||
{app.Ports.filter(p => p.IP != '::').map((port) => {
|
||||
{app.ports.filter(p => p.IP != '::').map((port) => {
|
||||
return <Tooltip title={port.PublicPort ? 'Warning, this port is publicly accessible' : ''}>
|
||||
<Chip style={{ fontSize: '80%' }} label={(port.PublicPort ? (port.PublicPort + ":") : '') + port.PrivatePort} color={port.PublicPort ? 'warning' : 'default'} />
|
||||
</Tooltip>
|
||||
|
@ -225,23 +356,23 @@ const ServApps = () => {
|
|||
Networks
|
||||
</Typography>
|
||||
<Stack style={noOver} margin={1} direction="row" spacing={1}>
|
||||
{app.NetworkSettings.Networks && Object.keys(app.NetworkSettings.Networks).map((network) => {
|
||||
{app.networkSettings.Networks && Object.keys(app.networkSettings.Networks).map((network) => {
|
||||
return <Chip style={{ fontSize: '80%' }} label={network} color={network === 'bridge' ? 'warning' : 'default'} />
|
||||
})}
|
||||
</Stack>
|
||||
</Stack>
|
||||
{isUpdating[app.Names[0].replace('/', '')] ? <div>
|
||||
{app.isUpdating ? <div>
|
||||
<CircularProgress color="inherit" />
|
||||
</div>
|
||||
:
|
||||
<Stack margin={1} direction="column" spacing={1} alignItems="flex-start">
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
Settings {app.State !== 'running' ? '(Start container to edit)' : ''}
|
||||
Settings {app.type == "app" && (app.state !== 'running' ? '(Start container to edit)' : '')}
|
||||
</Typography>
|
||||
<Stack style={{ fontSize: '80%' }} direction={"row"} alignItems="center">
|
||||
<Checkbox
|
||||
checked={app.Labels['cosmos-force-network-secured'] === 'true'}
|
||||
disabled={app.State !== 'running'}
|
||||
checked={app.labels['cosmos-force-network-secured'] === 'true'}
|
||||
disabled={app.type == "stack" || app.state !== 'running'}
|
||||
onChange={(e) => {
|
||||
const name = app.Names[0].replace('/', '');
|
||||
setIsUpdatingId(name, true);
|
||||
|
@ -255,13 +386,13 @@ const ServApps = () => {
|
|||
refreshServApps();
|
||||
})
|
||||
}}
|
||||
/> Isolate Container Network <ContainerNetworkWarning container={app} />
|
||||
/> Isolate Container Network <ContainerNetworkWarning container={app.app} />
|
||||
</Stack>
|
||||
<Stack style={{ fontSize: '80%' }} direction={"row"} alignItems="center">
|
||||
<Checkbox
|
||||
checked={app.Labels['cosmos-auto-update'] === 'true' ||
|
||||
checked={app.labels['cosmos-auto-update'] === 'true' ||
|
||||
(selfName && app.Names[0].replace('/', '') == selfName && config.AutoUpdate)}
|
||||
disabled={app.State !== 'running'}
|
||||
disabled={app.type == "stack" || app.state !== 'running'}
|
||||
onChange={(e) => {
|
||||
const name = app.Names[0].replace('/', '');
|
||||
setIsUpdatingId(name, true);
|
||||
|
@ -284,7 +415,7 @@ const ServApps = () => {
|
|||
URLs
|
||||
</Typography>
|
||||
<Stack style={noOver} spacing={2} direction="row">
|
||||
{getContainersRoutes(config, app.Names[0].replace('/', '')).map((route) => {
|
||||
{getContainersRoutes(config, app.name.replace('/', '')).map((route) => {
|
||||
return <HostChip route={route} settings/>
|
||||
})}
|
||||
{/* {getContainersRoutes(config, app.Names[0].replace('/', '')).length == 0 && */}
|
||||
|
@ -294,10 +425,10 @@ const ServApps = () => {
|
|||
style={{paddingRight: '4px'}}
|
||||
deleteIcon={<PlusCircleOutlined />}
|
||||
onClick={() => {
|
||||
setOpenModal(app);
|
||||
setOpenModal(app.app);
|
||||
}}
|
||||
onDelete={() => {
|
||||
setOpenModal(app);
|
||||
setOpenModal(app.app);
|
||||
}}
|
||||
/>
|
||||
{/* } */}
|
||||
|
@ -305,16 +436,21 @@ const ServApps = () => {
|
|||
</Stack>
|
||||
<div>
|
||||
<MiniPlotComponent agglo metrics={[
|
||||
"cosmos.system.docker.cpu." + app.Names[0].replace('/', ''),
|
||||
"cosmos.system.docker.ram." + app.Names[0].replace('/', ''),
|
||||
"cosmos.system.docker.cpu." + app.name.replace('/', ''),
|
||||
"cosmos.system.docker.ram." + app.name.replace('/', ''),
|
||||
]} labels={{
|
||||
["cosmos.system.docker.cpu." + app.Names[0].replace('/', '')]: "CPU",
|
||||
["cosmos.system.docker.ram." + app.Names[0].replace('/', '')]: "RAM"
|
||||
["cosmos.system.docker.cpu." + app.name.replace('/', '')]: "CPU",
|
||||
["cosmos.system.docker.ram." + app.name.replace('/', '')]: "RAM"
|
||||
}}/>
|
||||
</div>
|
||||
<div>
|
||||
<Link to={`/cosmos-ui/servapps/containers/${app.Names[0].replace('/', '')}`}>
|
||||
<Button variant="outlined" color="primary" fullWidth>Details</Button>
|
||||
<Link to={app.type === 'stack' ?
|
||||
`/cosmos-ui/servapps/stack/${app.name}` :
|
||||
`/cosmos-ui/servapps/containers/${app.name.replace('/', '')}`
|
||||
}>
|
||||
<Button variant="outlined" color="primary" fullWidth>
|
||||
{app.type === 'stack' ? 'View Stack' : 'View Details'}
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{/* <Stack>
|
||||
|
|
|
@ -44,17 +44,10 @@ const VolumeManagementList = () => {
|
|||
</Button>
|
||||
</Stack>
|
||||
|
||||
{isLoading && (<div style={{height: '550px'}}>
|
||||
<center>
|
||||
<br />
|
||||
<CircularProgress />
|
||||
</center>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && rows && (
|
||||
{rows && (
|
||||
<PrettyTableView
|
||||
data={rows}
|
||||
isLoading={isLoading}
|
||||
onRowClick={() => {}}
|
||||
getKey={(r) => r.Name}
|
||||
buttons={[
|
||||
|
|
|
@ -53,6 +53,10 @@ const MainRoutes = {
|
|||
path: '/cosmos-ui/servapps',
|
||||
element: <ServAppsIndex />
|
||||
},
|
||||
{
|
||||
path: '/cosmos-ui/servapps/stack/:stack',
|
||||
element: <ServAppsIndex />
|
||||
},
|
||||
{
|
||||
path: '/cosmos-ui/config-users',
|
||||
element: <UserManagement />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.12.6",
|
||||
"version": "0.13.0-unstable0",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<!-- sponsors -->
|
||||
<h3 align="center">Thanks to the sponsors:</h3></br>
|
||||
<p align="center"><a href="https://github.com/DrMxrcy"><img src="https://avatars.githubusercontent.com/DrMxrcy" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
|
||||
<a href="https://github.com/BakaDalek"><img src="https://avatars.githubusercontent.com/BakaDalek" style="border-radius:48px" width="48" height="48" alt="BakaDalek" title="BakaDalek" /></a>
|
||||
<a href="https://github.com/soldier1"><img src="https://avatars.githubusercontent.com/soldier1" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
|
||||
<a href="https://github.com/devcircus"><img src="https://avatars.githubusercontent.com/devcircus" style="border-radius:48px" width="48" height="48" alt="Clayton Stone" title="Clayton Stone" /></a>
|
||||
<a href="https://github.com/BlackrazorNZ"><img src="https://avatars.githubusercontent.com/BlackrazorNZ" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
|
||||
|
|
|
@ -106,7 +106,7 @@ func ConfigApiPatch(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
utils.RestartHTTPServer()
|
||||
|
||||
if updateReq.NewRoute.Mode == "SERVAPP" {
|
||||
if updateReq.NewRoute != nil && updateReq.NewRoute.Mode == "SERVAPP" {
|
||||
utils.Log("RouteSettingsUpdate: Service needs update: "+updateReq.NewRoute.Target)
|
||||
|
||||
target := updateReq.NewRoute.Target
|
||||
|
|
|
@ -414,7 +414,7 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
|
|||
utils.Log(fmt.Sprintf("Forcing secure %s...", serviceName))
|
||||
OnLog(fmt.Sprintf("Forcing secure %s...\n", serviceName))
|
||||
|
||||
newNetwork, errNC := CreateCosmosNetwork()
|
||||
newNetwork, errNC := CreateCosmosNetwork(serviceName)
|
||||
if errNC != nil {
|
||||
utils.Error("CreateService: Network", err)
|
||||
OnLog(utils.DoErr("Network %s cant be created\n", newNetwork))
|
||||
|
|
|
@ -64,7 +64,7 @@ func DeleteVolumeRoute(w http.ResponseWriter, req *http.Request) {
|
|||
err := DockerClient.VolumeRemove(context.Background(), volumeName, true)
|
||||
if err != nil {
|
||||
utils.Error("DeleteVolumeRoute: Error while deleting volume", err)
|
||||
utils.HTTPError(w, "Volume Deletion Error", http.StatusInternalServerError, "DV002")
|
||||
utils.HTTPError(w, "Volume Deletion Error " + err.Error(), http.StatusInternalServerError, "DV002")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ func findAvailableSubnet() string {
|
|||
return baseSubnet
|
||||
}
|
||||
|
||||
func CreateCosmosNetwork() (string, error) {
|
||||
func CreateCosmosNetwork(name string) (string, error) {
|
||||
networks, err := DockerClient.NetworkList(DockerContext, types.NetworkListOptions{})
|
||||
if err != nil {
|
||||
utils.Error("Docker Network List", err)
|
||||
|
@ -79,7 +79,7 @@ func CreateCosmosNetwork() (string, error) {
|
|||
|
||||
newNeworkName := ""
|
||||
for {
|
||||
newNeworkName = "cosmos-network-" + utils.GenerateRandomString(9)
|
||||
newNeworkName = "cosmos-" + name + "-" + utils.GenerateRandomString(3)
|
||||
exists := false
|
||||
for _, network := range networks {
|
||||
if network.Name == newNeworkName {
|
||||
|
@ -144,7 +144,7 @@ func ConnectToSecureNetwork(containerConfig types.ContainerJSON) (bool, error) {
|
|||
netName := ""
|
||||
|
||||
if(!HasLabel(containerConfig, "cosmos-network-name")) {
|
||||
newNetwork, errNC := CreateCosmosNetwork()
|
||||
newNetwork, errNC := CreateCosmosNetwork(containerConfig.Name[1:])
|
||||
if errNC != nil {
|
||||
utils.Error("DockerSecureNetworkCreate", errNC)
|
||||
return false, errNC
|
||||
|
@ -239,7 +239,7 @@ func IsConnectedToASecureCosmosNetwork(self types.ContainerJSON, containerConfig
|
|||
if err != nil {
|
||||
utils.Error("Container tries to connect to a non existing Cosmos network, replacing it.", err)
|
||||
|
||||
newNetwork, errNC := CreateCosmosNetwork()
|
||||
newNetwork, errNC := CreateCosmosNetwork(containerConfig.Name[1:])
|
||||
if errNC != nil {
|
||||
utils.Error("DockerSecureNetworkCreate", errNC)
|
||||
return false, errNC, false
|
||||
|
|
|
@ -58,6 +58,9 @@ func DB() error {
|
|||
}
|
||||
|
||||
func DisconnectDB() {
|
||||
if client == nil {
|
||||
return
|
||||
}
|
||||
if err := client.Disconnect(context.TODO()); err != nil {
|
||||
Fatal("DB", err)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue