Преглед изворни кода

[release] v0.13.0-unstable5

Yann Stepienik пре 1 година
родитељ
комит
23ab2f7a97

+ 8 - 2
changelog.md

@@ -1,9 +1,15 @@
 ## Version 0.13.0
 ## Version 0.13.0
  - Display containers as stacks
  - Display containers as stacks
- - new Delete modal to delete services entirely
- - cosmos-network now have container names instead for network names
+ - New Delete modal to delete services entirely
+ - Cosmos-networks now have specific names instead for a generic names
  - Fix issue where search bar reset when deleting volume/network
  - Fix issue where search bar reset when deleting volume/network
  - Fix breadcrumbs in subpaths
  - Fix breadcrumbs in subpaths
+ - Remove graphs from non-admin UI to prevent errors
+ - Rewrite the overwriting container logic to fix race conditions
+ - Edit container user and devices from UI
+ - Fix bug where Cosmos Constellation's UDP ports by a TCP one
+ - Support array command and single device in docker-compose import
+ - Add default alert.. by default
 
 
 ## Version 0.12.6
 ## Version 0.12.6
  - Fix a security issue with cross-domain APIs availability
  - Fix a security issue with cross-domain APIs availability

+ 2 - 1
client/src/menu-items/dashboard.jsx

@@ -27,7 +27,8 @@ const dashboard = {
             type: 'item',
             type: 'item',
             url: '/cosmos-ui/monitoring',
             url: '/cosmos-ui/monitoring',
             icon: DashboardOutlined,
             icon: DashboardOutlined,
-            breadcrumbs: false
+            breadcrumbs: false,
+            adminOnly: true
         },
         },
         {
         {
             id: 'market',
             id: 'market',

+ 4 - 2
client/src/pages/home/index.jsx

@@ -124,6 +124,8 @@ const HomePage = () => {
     }
     }
 
 
     function getMetrics() {
     function getMetrics() {
+        if(!isAdmin) return;
+        
         API.metrics.get([
         API.metrics.get([
             "cosmos.system.cpu.0",
             "cosmos.system.cpu.0",
             "cosmos.system.ram",
             "cosmos.system.ram",
@@ -260,7 +262,7 @@ const HomePage = () => {
     
     
     let latestCPU, latestRAM, latestRAMRaw, maxRAM, maxRAMRaw = 0;
     let latestCPU, latestRAM, latestRAMRaw, maxRAM, maxRAMRaw = 0;
 
 
-    if(metrics) {
+    if(isAdmin && metrics) {
     
     
         if(metrics["cosmos.system.cpu.0"] && metrics["cosmos.system.cpu.0"].Values && metrics["cosmos.system.cpu.0"].Values.length > 0)
         if(metrics["cosmos.system.cpu.0"] && metrics["cosmos.system.cpu.0"].Values && metrics["cosmos.system.cpu.0"].Values.length > 0)
             latestCPU = metrics["cosmos.system.cpu.0"].Values[metrics["cosmos.system.cpu.0"].Values.length - 1].Value;
             latestCPU = metrics["cosmos.system.cpu.0"].Values[metrics["cosmos.system.cpu.0"].Values.length - 1].Value;
@@ -336,7 +338,7 @@ const HomePage = () => {
         </Stack>
         </Stack>
 
 
         <Grid2 container spacing={2} style={{ zIndex: 2 }}>
         <Grid2 container spacing={2} style={{ zIndex: 2 }}>
-            {coStatus && !coStatus.MonitoringDisabled && (<>
+            {isAdmin && coStatus && !coStatus.MonitoringDisabled && (<>
                 {isMd && !metrics && (<>
                 {isMd && !metrics && (<>
                     <Grid2 item xs={12} sm={6} md={6} lg={3} xl={3} xxl={3} key={'000'}>
                     <Grid2 item xs={12} sm={6} md={6} lg={3} xl={3} xxl={3} key={'000'}>
                         <Box className='app' style={{height: '106px', borderRadius: 5, ...appColor }}>
                         <Box className='app' style={{height: '106px', borderRadius: 5, ...appColor }}>

+ 26 - 1
client/src/pages/servapps/containers/docker-compose.jsx

@@ -186,7 +186,6 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
         // convert to the proper format
         // convert to the proper format
         if (doc.services) {
         if (doc.services) {
           Object.keys(doc.services).forEach((key) => {
           Object.keys(doc.services).forEach((key) => {
-
             // convert volumes
             // convert volumes
             if (doc.services[key].volumes) {
             if (doc.services[key].volumes) {
               if (Array.isArray(doc.services[key].volumes)) {
               if (Array.isArray(doc.services[key].volumes)) {
@@ -273,6 +272,30 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
               }
               }
             }
             }
 
 
+            // convert devices
+            if (doc.services[key].devices) {
+              console.log(1)
+              if (Array.isArray(doc.services[key].devices)) {
+                console.log(2)
+                let devices = [];
+                doc.services[key].devices.forEach((device) => {
+                  if(device.indexOf(':') === -1) {
+                    devices.push(device + ':' + device);
+                  } else {
+                    devices.push(device);
+                  }
+                });
+                doc.services[key].devices = devices;
+              }
+            }
+
+            // convert command 
+            if (doc.services[key].command) {
+              if (typeof doc.services[key].command !== 'string') {
+                doc.services[key].command = doc.services[key].command.join(' ');
+              }
+            }
+
             // ensure container_name
             // ensure container_name
             if (!doc.services[key].container_name) {
             if (!doc.services[key].container_name) {
               doc.services[key].container_name = key;
               doc.services[key].container_name = key;
@@ -652,9 +675,11 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaul
                         Config: {
                         Config: {
                           Env: value.environment || [],
                           Env: value.environment || [],
                           Labels: value.labels || {},
                           Labels: value.labels || {},
+                          User: value.user || '',
                         },
                         },
                         HostConfig: {
                         HostConfig: {
                           RestartPolicy: {},
                           RestartPolicy: {},
+                          Devices: value.devices || [],
                         }
                         }
                       }}
                       }}
                       OnChange={(containerInfo) => {
                       OnChange={(containerInfo) => {

+ 11 - 0
client/src/pages/servapps/containers/newServiceForm.jsx

@@ -69,6 +69,9 @@ const NewDockerServiceForm = () => {
         image: containerInfo.Config.Image,
         image: containerInfo.Config.Image,
         environment: containerInfo.Config.Env,
         environment: containerInfo.Config.Env,
         labels: containerInfo.Config.Labels,
         labels: containerInfo.Config.Labels,
+        devices: containerInfo.HostConfig.Devices.map((device) => {
+          return `${device.PathOnHost}:${device.PathInContainer}:`;
+        }),
         expose: containerInfo.Config.ExposedPorts,
         expose: containerInfo.Config.ExposedPorts,
         tty: containerInfo.Config.Tty,
         tty: containerInfo.Config.Tty,
         stdin_open: containerInfo.Config.OpenStdin,
         stdin_open: containerInfo.Config.OpenStdin,
@@ -142,9 +145,17 @@ const NewDockerServiceForm = () => {
                 Name: values.name,
                 Name: values.name,
                 Env: values.envVars,
                 Env: values.envVars,
                 Labels: values.labels,
                 Labels: values.labels,
+                User: values.user,
               },
               },
               HostConfig: {
               HostConfig: {
                 ...containerInfo.HostConfig,
                 ...containerInfo.HostConfig,
+                Devices: values.devices.map((device) => {
+                  return {
+                    PathOnHost: device.split(':')[0],
+                    PathInContainer: device.split(':')[1],
+                    CgroupPermissions: 'rwm',
+                  };
+                }),
                 RestartPolicy: {
                 RestartPolicy: {
                   Name: values.restartPolicy,
                   Name: values.restartPolicy,
                 },
                 },

+ 112 - 30
client/src/pages/servapps/containers/setup.jsx

@@ -3,7 +3,7 @@ import { Field, Formik } from 'formik';
 import { Button, Stack, Grid, MenuItem, TextField, IconButton, FormHelperText, useMediaQuery, useTheme, Alert, FormControlLabel, Checkbox } from '@mui/material';
 import { Button, Stack, Grid, MenuItem, TextField, IconButton, FormHelperText, useMediaQuery, useTheme, Alert, FormControlLabel, Checkbox } from '@mui/material';
 import MainCard from '../../../components/MainCard';
 import MainCard from '../../../components/MainCard';
 import { CosmosCheckbox, CosmosFormDivider, CosmosInputText, CosmosSelect }
 import { CosmosCheckbox, CosmosFormDivider, CosmosInputText, CosmosSelect }
-   from '../../config/users/formShortcuts';
+  from '../../config/users/formShortcuts';
 import { DeleteOutlined, PlusCircleOutlined } from '@ant-design/icons';
 import { DeleteOutlined, PlusCircleOutlined } from '@ant-design/icons';
 import * as API from '../../../api';
 import * as API from '../../../api';
 import { LoadingButton } from '@mui/lab';
 import { LoadingButton } from '@mui/lab';
@@ -15,20 +15,28 @@ const containerInfoFrom = (values) => {
   values.labels.forEach((label) => {
   values.labels.forEach((label) => {
     labels[label.key] = label.value;
     labels[label.key] = label.value;
   });
   });
+
+  const devices = values.devices.map((device) => {
+    return `${device.key}:${device.value}`;
+  });
+
   const envVars = values.envVars.map((envVar) => {
   const envVars = values.envVars.map((envVar) => {
     return `${envVar.key}=${envVar.value}`;
     return `${envVar.key}=${envVar.value}`;
   });
   });
+  
   const realvalues = {
   const realvalues = {
     ...values,
     ...values,
     envVars: envVars,
     envVars: envVars,
     labels: labels,
     labels: labels,
+    devices: devices,
   };
   };
+
   realvalues.interactive = realvalues.interactive ? 2 : 1;
   realvalues.interactive = realvalues.interactive ? 2 : 1;
-  
+
   return realvalues;
   return realvalues;
 }
 }
 
 
-const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refresh, newContainer, OnForceSecure}) => {
+const DockerContainerSetup = ({ noCard, containerInfo, installer, OnChange, refresh, newContainer, OnForceSecure }) => {
   const restartPolicies = [
   const restartPolicies = [
     ['no', 'No Restart'],
     ['no', 'No Restart'],
     ['always', 'Always Restart'],
     ['always', 'Always Restart'],
@@ -40,7 +48,7 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
   const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
   const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
   const padding = isMobile ? '6px 4px' : '12px 10px';
   const padding = isMobile ? '6px 4px' : '12px 10px';
   const [latestImage, setLatestImage] = React.useState(containerInfo.Config.Image);
   const [latestImage, setLatestImage] = React.useState(containerInfo.Config.Image);
-  
+
   const wrapCard = (children) => {
   const wrapCard = (children) => {
     if (noCard) return children;
     if (noCard) return children;
     return <MainCard title="Docker Container Setup">
     return <MainCard title="Docker Container Setup">
@@ -55,13 +63,19 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
           name: containerInfo.Name.replace('/', ''),
           name: containerInfo.Name.replace('/', ''),
           image: containerInfo.Config.Image,
           image: containerInfo.Config.Image,
           restartPolicy: containerInfo.HostConfig.RestartPolicy.Name,
           restartPolicy: containerInfo.HostConfig.RestartPolicy.Name,
+          user: containerInfo.Config.User,
           envVars: containerInfo.Config.Env.map((envVar) => {
           envVars: containerInfo.Config.Env.map((envVar) => {
             const [key, value] = envVar.split(/=(.*)/s);
             const [key, value] = envVar.split(/=(.*)/s);
-            return { key, value }; 
+            return { key, value };
           }),
           }),
           labels: Object.keys(containerInfo.Config.Labels).map((key) => {
           labels: Object.keys(containerInfo.Config.Labels).map((key) => {
             return { key, value: containerInfo.Config.Labels[key] };
             return { key, value: containerInfo.Config.Labels[key] };
           }),
           }),
+          devices: containerInfo.HostConfig.Devices.map((device) => {
+            return (typeof device == "string") ? 
+              { key: device.split(":")[0], value: (device.split(":")[1] || device.split(":")[0]) } 
+            : { key: device.PathOnHost, value: device.PathInContainer };
+          }),
           interactive: containerInfo.Config.Tty && containerInfo.Config.OpenStdin,
           interactive: containerInfo.Config.Tty && containerInfo.Config.OpenStdin,
         }}
         }}
         enableReinitialize
         enableReinitialize
@@ -88,12 +102,12 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
           return errors;
           return errors;
         }}
         }}
         onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
         onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
-          if(values.image !== latestImage) {
-            setPullRequest(() => ((cb) => API.docker.pullImage(values.image,cb, true)));
+          if (values.image !== latestImage) {
+            setPullRequest(() => ((cb) => API.docker.pullImage(values.image, cb, true)));
             return;
             return;
           }
           }
 
 
-          if(newContainer) return false;
+          if (newContainer) return false;
           delete values.name;
           delete values.name;
 
 
           setSubmitting(true);
           setSubmitting(true);
@@ -106,11 +120,11 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
               setSubmitting(false);
               setSubmitting(false);
               refresh && refresh();
               refresh && refresh();
             }
             }
-          ).catch((err) => {
-            setStatus({ success: false });
-            setErrors({ submit: err.message });
-            setSubmitting(false);
-          });
+            ).catch((err) => {
+              setStatus({ success: false });
+              setErrors({ submit: err.message });
+              setSubmitting(false);
+            });
         }}
         }}
       >
       >
         {(formik) => (
         {(formik) => (
@@ -125,39 +139,45 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
             />
             />
             <Stack spacing={2}>
             <Stack spacing={2}>
               {wrapCard(<>
               {wrapCard(<>
-              {containerInfo.State && containerInfo.State.Status !== 'running' && (
-              <Alert severity="warning" style={{ marginBottom: '15px' }}>
-                  This container is not running. Editing any settings will cause the container to start again.
-                </Alert>
-              )}
+                {containerInfo.State && containerInfo.State.Status !== 'running' && (
+                  <Alert severity="warning" style={{ marginBottom: '15px' }}>
+                    This container is not running. Editing any settings will cause the container to start again.
+                  </Alert>
+                )}
                 <Grid container spacing={4}>
                 <Grid container spacing={4}>
-                {!installer && <>
+                  {!installer && <>
                     {newContainer && <CosmosInputText
                     {newContainer && <CosmosInputText
                       name="name"
                       name="name"
                       label="Name"
                       label="Name"
                       placeholder="Name"
                       placeholder="Name"
                       formik={formik}
                       formik={formik}
-                      />}
+                    />}
                     <CosmosInputText
                     <CosmosInputText
                       name="image"
                       name="image"
                       label="Image"
                       label="Image"
                       placeholder="Image"
                       placeholder="Image"
                       formik={formik}
                       formik={formik}
-                      />
+                    />
                     <CosmosSelect
                     <CosmosSelect
                       name="restartPolicy"
                       name="restartPolicy"
                       label="Restart Policy"
                       label="Restart Policy"
                       placeholder="Restart Policy"
                       placeholder="Restart Policy"
                       options={restartPolicies}
                       options={restartPolicies}
                       formik={formik}
                       formik={formik}
-                      />
+                    />
+                    <CosmosInputText
+                      name="user"
+                      label="User"
+                      placeholder="User"
+                      formik={formik}
+                    />
                     <CosmosCheckbox
                     <CosmosCheckbox
                       name="interactive"
                       name="interactive"
                       label="Interactive Mode"
                       label="Interactive Mode"
                       formik={formik}
                       formik={formik}
                     />
                     />
                     {OnForceSecure && <Grid item xs={12}>
                     {OnForceSecure && <Grid item xs={12}>
-                    <Checkbox
+                      <Checkbox
                         type="checkbox"
                         type="checkbox"
                         as={FormControlLabel}
                         as={FormControlLabel}
                         control={<Checkbox size="large" />}
                         control={<Checkbox size="large" />}
@@ -170,14 +190,14 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
                           OnForceSecure(e.target.checked);
                           OnForceSecure(e.target.checked);
                         }}
                         }}
                       />
                       />
-                  </Grid>}
+                    </Grid>}
                   </>}
                   </>}
 
 
                   <CosmosFormDivider title={'Environment Variables'} />
                   <CosmosFormDivider title={'Environment Variables'} />
                   <Grid item xs={12}>
                   <Grid item xs={12}>
                     {formik.values.envVars.map((envVar, idx) => (
                     {formik.values.envVars.map((envVar, idx) => (
                       <Grid container key={idx}>
                       <Grid container key={idx}>
-                        <Grid item xs={5} style={{padding}}>
+                        <Grid item xs={5} style={{ padding }}>
                           <TextField
                           <TextField
                             label="Key"
                             label="Key"
                             fullWidth
                             fullWidth
@@ -189,7 +209,7 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
                             }}
                             }}
                           />
                           />
                         </Grid>
                         </Grid>
-                        <Grid item xs={6} style={{padding}}>
+                        <Grid item xs={6} style={{ padding }}>
                           <TextField
                           <TextField
                             fullWidth
                             fullWidth
                             label="Value"
                             label="Value"
@@ -201,7 +221,7 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
                             }}
                             }}
                           />
                           />
                         </Grid>
                         </Grid>
-                        <Grid item xs={1} style={{padding}}>
+                        <Grid item xs={1} style={{ padding }}>
                           <IconButton
                           <IconButton
                             fullWidth
                             fullWidth
                             variant="outlined"
                             variant="outlined"
@@ -231,11 +251,12 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
                       Add
                       Add
                     </ResponsiveButton>
                     </ResponsiveButton>
                   </Grid>
                   </Grid>
+
                   <CosmosFormDivider title={'Labels'} />
                   <CosmosFormDivider title={'Labels'} />
                   <Grid item xs={12}>
                   <Grid item xs={12}>
                     {formik.values.labels.map((label, idx) => (
                     {formik.values.labels.map((label, idx) => (
                       <Grid container key={idx}>
                       <Grid container key={idx}>
-                        <Grid item xs={5} style={{padding}}>
+                        <Grid item xs={5} style={{ padding }}>
                           <TextField
                           <TextField
                             fullWidth
                             fullWidth
                             label="Key"
                             label="Key"
@@ -247,7 +268,7 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
                             }}
                             }}
                           />
                           />
                         </Grid>
                         </Grid>
-                        <Grid item xs={6} style={{padding}}>
+                        <Grid item xs={6} style={{ padding }}>
                           <TextField
                           <TextField
                             label="Value"
                             label="Value"
                             fullWidth
                             fullWidth
@@ -259,7 +280,7 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
                             }}
                             }}
                           />
                           />
                         </Grid>
                         </Grid>
-                        <Grid item xs={1} style={{padding}}>
+                        <Grid item xs={1} style={{ padding }}>
                           <IconButton
                           <IconButton
                             fullWidth
                             fullWidth
                             variant="outlined"
                             variant="outlined"
@@ -289,6 +310,67 @@ const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refre
                       Add
                       Add
                     </ResponsiveButton>
                     </ResponsiveButton>
                   </Grid>
                   </Grid>
+
+                  <CosmosFormDivider title={'Devices'} />
+                  <Grid item xs={12}>
+                    {formik.values.devices.map((device, idx) => (
+                      <Grid container key={idx}>
+                        <Grid item xs={5} style={{ padding }}>
+                          <TextField
+                            fullWidth
+                            label="Host Path"
+                            value={device.key}
+                            onChange={(e) => {
+                              const newDevices = [...formik.values.devices];
+                              newDevices[idx].key = e.target.value;
+                              formik.setFieldValue('devices', newDevices);
+                            }}
+                          />
+                        </Grid>
+                        <Grid item xs={6} style={{ padding }}>
+                          <TextField
+                            label="Container Path"
+                            fullWidth
+                            value={device.value}
+                            onChange={(e) => {
+                              const newDevices = [...formik.values.devices];
+                              newDevices[idx].value = e.target.value;
+                              formik.setFieldValue('devices', newDevices);
+                            }}
+                          />
+                        </Grid>
+                        <Grid item xs={1} style={{ padding }}>
+                          <IconButton
+                            fullWidth
+                            variant="outlined"
+                            color="error"
+                            onClick={() => {
+                              const newDevices = [...formik.values.devices];
+                              newDevices.splice(idx, 1);
+                              formik.setFieldValue('devices', newDevices);
+                            }}
+                          >
+                            <DeleteOutlined />
+                          </IconButton>
+                        </Grid>
+                      </Grid>
+                    ))}
+                    <ResponsiveButton
+                      variant="outlined"
+                      color="primary"
+                      size='large'
+                      onClick={() => {
+                        const newDevices = [...formik.values.devices];
+                        newDevices.push({ key: '', value: '' });
+                        formik.setFieldValue('devices', newDevices);
+                      }}
+                      startIcon={<PlusCircleOutlined />}
+                    >
+                      Add
+                    </ResponsiveButton>
+                  </Grid>
+
+
                 </Grid>
                 </Grid>
               </>)}
               </>)}
               {!newContainer && <MainCard>
               {!newContainer && <MainCard>

+ 9 - 1
client/src/pages/servapps/servapps.jsx

@@ -283,7 +283,15 @@ const ServApps = ({stack}) => {
             <Alert severity="info">Update are available for {Object.keys(updatesAvailable).join(', ')}</Alert>
             <Alert severity="info">Update are available for {Object.keys(updatesAvailable).join(', ')}</Alert>
           </Item>
           </Item>
         </Grid2>}
         </Grid2>}
-        {servApps && Object.values(servAppsStacked).filter(app => search.length < 2 || app.name.toLowerCase().includes(search.toLowerCase())).map((app) => {
+        {servApps && Object.values(servAppsStacked)
+          .filter(app => {
+            if (search.length < 2) return true;
+            if (app.name.toLowerCase().includes(search.toLowerCase())) return true;
+            if (app.app.Image.toLowerCase().includes(search.toLowerCase())) return true;
+            if (app.app.Id.toLowerCase().includes(search.toLowerCase())) return true;
+            if (app.apps.find((app) => app.Names[0].toLowerCase().includes(search.toLowerCase()))) return true;
+          })
+          .map((app) => {
           return <Grid2 sx={{...gridAnim}} xs={12} sm={6} md={6} lg={6} xl={4} key={app.Id} item>
           return <Grid2 sx={{...gridAnim}} xs={12} sm={6} md={6} lg={6} xl={4} key={app.Id} item>
             <Item>
             <Item>
             <Stack justifyContent='space-around' direction="column" spacing={2} padding={2} divider={<Divider orientation="horizontal" flexItem />}>
             <Stack justifyContent='space-around' direction="column" spacing={2} padding={2} divider={<Divider orientation="horizontal" flexItem />}>

+ 2 - 2
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "cosmos-server",
   "name": "cosmos-server",
-  "version": "0.13.0-unstable4",
+  "version": "0.13.0-unstable5",
   "description": "",
   "description": "",
   "main": "test-server.js",
   "main": "test-server.js",
   "bugs": {
   "bugs": {
@@ -80,7 +80,7 @@
     "build": "sh build.sh",
     "build": "sh build.sh",
     "dev": "npm run build && npm run start",
     "dev": "npm run build && npm run start",
     "dockerdevbuild": "docker build -f dockerfile.local --tag cosmos-dev .",
     "dockerdevbuild": "docker build -f dockerfile.local --tag cosmos-dev .",
-    "dockerdevrun": "docker stop cosmos-dev; docker rm cosmos-dev; docker run --cap-add NET_ADMIN -d -p 7200:443 -p 80:80 -p 53:53 -p 443:443 -p 4242:4242 -e DOCKER_HOST=tcp://host.docker.internal:2375 -e COSMOS_MONGODB=$MONGODB -e COSMOS_LOG_LEVEL=DEBUG -v /:/mnt/host  --restart=unless-stopped -h cosmos-dev --name cosmos-dev cosmos-dev",
+    "dockerdevrun": "docker stop cosmos-dev; docker rm cosmos-dev; docker run --cap-add NET_ADMIN -d -p 7200:443 -p 80:80 -p 53:53 -p 443:443 -p 4242:4242/udp -e DOCKER_HOST=tcp://host.docker.internal:2375 -e COSMOS_MONGODB=$MONGODB -e COSMOS_LOG_LEVEL=DEBUG -v /:/mnt/host  --restart=unless-stopped -h cosmos-dev --name cosmos-dev cosmos-dev",
     "dockerdev": "npm run client-build && npm run dockerdevbuild && npm run dockerdevrun",
     "dockerdev": "npm run client-build && npm run dockerdevbuild && npm run dockerdevrun",
     "demo": "vite build --base=/cosmos-ui/ --mode demo",
     "demo": "vite build --base=/cosmos-ui/ --mode demo",
     "devdemo": "vite --mode demo"
     "devdemo": "vite --mode demo"

+ 1 - 1
src/constellation/index.go

@@ -11,7 +11,7 @@ func Init() {
 
 
 	// if date is > 1st of January 2024
 	// if date is > 1st of January 2024
 	timeNow := time.Now()
 	timeNow := time.Now()
-	if  timeNow.Year() > 2024 || (timeNow.Year() == 2024 && timeNow.Month() > 1) {
+	if  timeNow.Year() > 2024 || (timeNow.Year() == 2024 && timeNow.Month() > 3) {
 		utils.Error("Constellation: this preview version has expired, please update to use the lastest version of Constellation.", nil)
 		utils.Error("Constellation: this preview version has expired, please update to use the lastest version of Constellation.", nil)
 		// disable constellation
 		// disable constellation
 		configFile := utils.ReadConfigFromFile()
 		configFile := utils.ReadConfigFromFile()

+ 69 - 39
src/docker/api_blueprint.go

@@ -153,6 +153,19 @@ func Rollback(actions []DockerServiceCreateRollback , OnLog func(string)) {
 				// Edit Container
 				// Edit Container
 				_, err := EditContainer(action.Name, action.Was, false)
 				_, err := EditContainer(action.Name, action.Was, false)
 	
 	
+				if err != nil {
+					utils.Error("Rollback: Container", err)
+					OnLog(utils.DoErr("Rollback: Container %s", err))
+				} else {
+					utils.Log(fmt.Sprintf("Rolled back container %s", action.Name))
+					OnLog(fmt.Sprintf("Rolled back container %s\n", action.Name))
+				}	
+			} else if action.Action == "restore" {
+				utils.Log(fmt.Sprintf("Restoring container %s...", action.Name))
+
+				// Edit Container
+				_, err := EditContainer("", action.Was, true)
+	
 				if err != nil {
 				if err != nil {
 					utils.Error("Rollback: Container", err)
 					utils.Error("Rollback: Container", err)
 					OnLog(utils.DoErr("Rollback: Container %s", err))
 					OnLog(utils.DoErr("Rollback: Container %s", err))
@@ -708,15 +721,40 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 
 
 		// check if container exist
 		// check if container exist
 		existingContainer, err := DockerClient.ContainerInspect(DockerContext, container.Name)
 		existingContainer, err := DockerClient.ContainerInspect(DockerContext, container.Name)
-		if err == nil {
+		if err == nil {		
+			
+			// Edit Container
+			oldConfig := doctype.ContainerJSON{}
+			oldConfig.ContainerJSONBase = new(doctype.ContainerJSONBase)
+			oldConfig.Config = existingContainer.Config
+			oldConfig.HostConfig = existingContainer.HostConfig
+			oldConfig.Name = existingContainer.Name
+			oldConfig.NetworkSettings = existingContainer.NetworkSettings
+
 			utils.Warn("CreateService: Container " + container.Name + " already exist, overwriting.")
 			utils.Warn("CreateService: Container " + container.Name + " already exist, overwriting.")
 			OnLog(utils.DoWarn("Container " + container.Name + " already exist, overwriting.\n"))
 			OnLog(utils.DoWarn("Container " + container.Name + " already exist, overwriting.\n"))
 	
 	
-			// Edit Container
-			newConfig := doctype.ContainerJSON{}
-			newConfig.ContainerJSONBase = new(doctype.ContainerJSONBase)
-			newConfig.Config = containerConfig
-			newConfig.HostConfig = hostConfig
+			// stop the container 
+			utils.Log("CreateService: Stopping container: " + container.Name)
+			OnLog("Stopping container: " + container.Name + "\n")
+			err = DockerClient.ContainerStop(DockerContext, container.Name, conttype.StopOptions{})
+			if err != nil {
+				utils.Error("CreateService: Rolling back changes because of -- Container", err)
+				OnLog(utils.DoErr("Rolling back changes because of -- Container creation error: "+err.Error()))
+				Rollback(rollbackActions, OnLog)
+				return err
+			}
+
+			// remove the container
+			utils.Log("CreateService: Removing container: " + container.Name)
+			OnLog("Removing container: " + container.Name + "\n")
+			err = DockerClient.ContainerRemove(DockerContext, container.Name, doctype.ContainerRemoveOptions{})
+			if err != nil {
+				utils.Error("CreateService: Rolling back changes because of -- Container", err)
+				OnLog(utils.DoErr("Rolling back changes because of -- Container creation error: "+err.Error()))
+				Rollback(rollbackActions, OnLog)
+				return err
+			}
 
 
 			// check if there are persistent env var
 			// check if there are persistent env var
 			if containerConfig.Labels["cosmos-persistent-env"] != "" {
 			if containerConfig.Labels["cosmos-persistent-env"] != "" {
@@ -741,53 +779,42 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 					// if it exist, copy value to new container
 					// if it exist, copy value to new container
 					if exists {
 					if exists {
 						wasReplace := false
 						wasReplace := false
-						for i, newEnvVar := range newConfig.Config.Env {
+						for i, newEnvVar := range containerConfig.Env {
 							if strings.HasPrefix(newEnvVar, envVar + "=") {
 							if strings.HasPrefix(newEnvVar, envVar + "=") {
-								newConfig.Config.Env[i] = envVar + "=" + strings.TrimPrefix(existingEnvVarValue, envVar + "=")
+								containerConfig.Env[i] = envVar + "=" + strings.TrimPrefix(existingEnvVarValue, envVar + "=")
 								wasReplace = true
 								wasReplace = true
 								break
 								break
 							}
 							}
 						}
 						}
 						if !wasReplace {
 						if !wasReplace {
-							newConfig.Config.Env = append(newConfig.Config.Env, envVar + "=" + strings.TrimPrefix(existingEnvVarValue, envVar + "="))
+							containerConfig.Env = append(containerConfig.Env, envVar + "=" + strings.TrimPrefix(existingEnvVarValue, envVar + "="))
 						}
 						}
 					}
 					}
 				}
 				}
 			}
 			}
 			
 			
-			_, errEdit := EditContainer(container.Name, newConfig, false)
-
-			if errEdit != nil {
-				utils.Error("CreateService: Rolling back changes because of -- Container", err)
-				OnLog(utils.DoErr("Rolling back changes because of -- Container creation error: "+err.Error()))
-				Rollback(rollbackActions, OnLog)
-				return err
-			}
-
-			// rollback action
-		
 			rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
 			rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
-				Action: "revert",
+				Action: "restore",
 				Type:   "container",
 				Type:   "container",
-				Name:   container.Name,
-				Was: existingContainer,
+				Was: oldConfig,
 			})
 			})
-		} else {
-			_, err = DockerClient.ContainerCreate(DockerContext, containerConfig, hostConfig, networkingConfig, nil, container.Name)
-
-			if err != nil {
-				utils.Error("CreateService: Rolling back changes because of -- Container", err)
-				OnLog(utils.DoErr("Rolling back changes because of -- Container creation error: "+err.Error()))
-				Rollback(rollbackActions, OnLog)
-				return err
-			}
+		}
 		
 		
-			rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
-				Action: "remove",
-				Type:   "container",
-				Name:   container.Name,
-			})
-		}	
+		_, err = DockerClient.ContainerCreate(DockerContext, containerConfig, hostConfig, networkingConfig, nil, container.Name)
+
+		if err != nil {
+			utils.Error("CreateService: Rolling back changes because of -- Container", err)
+			OnLog(utils.DoErr("Rolling back changes because of -- Container creation error: "+err.Error()))
+			Rollback(rollbackActions, OnLog)
+			return err
+		}
+	
+		rollbackActions = append(rollbackActions, DockerServiceCreateRollback{
+			Action: "remove",
+			Type:   "container",
+			Name:   container.Name,
+		})
+	
 
 
 		// connect to networks
 		// connect to networks
 		for netName, netConfig := range container.Networks {
 		for netName, netConfig := range container.Networks {
@@ -880,7 +907,7 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 		err = DockerClient.ContainerStart(DockerContext, container.Name, doctype.ContainerStartOptions{})
 		err = DockerClient.ContainerStart(DockerContext, container.Name, doctype.ContainerStartOptions{})
 		if err != nil {
 		if err != nil {
 			utils.Error("CreateService: Start Container", err)
 			utils.Error("CreateService: Start Container", err)
-			OnLog(utils.DoErr("Rolling back changes because of -- Container start error: "+err.Error()))
+			OnLog(utils.DoErr("Rolling back changes because of -- Container start error" + container.Name + " : "+err.Error()))
 			Rollback(rollbackActions, OnLog)
 			Rollback(rollbackActions, OnLog)
 			return err
 			return err
 		}
 		}
@@ -986,6 +1013,8 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
 func ReOrderServices(serviceMap map[string]ContainerCreateRequestContainer) ([]ContainerCreateRequestContainer, error) {
 func ReOrderServices(serviceMap map[string]ContainerCreateRequestContainer) ([]ContainerCreateRequestContainer, error) {
 	startOrder := []ContainerCreateRequestContainer{}
 	startOrder := []ContainerCreateRequestContainer{}
 
 
+	utils.Debug(fmt.Sprintf("ReOrderServices:  start: %s", serviceMap))
+
 	for len(serviceMap) > 0 {
 	for len(serviceMap) > 0 {
 		// Keep track of whether we've added any services in this iteration
 		// Keep track of whether we've added any services in this iteration
 		changed := false
 		changed := false
@@ -1009,6 +1038,7 @@ func ReOrderServices(serviceMap map[string]ContainerCreateRequestContainer) ([]C
 
 
 			// If all dependencies are started, we can add this service to startOrder
 			// If all dependencies are started, we can add this service to startOrder
 			if allDependenciesStarted {
 			if allDependenciesStarted {
+				utils.Debug(fmt.Sprintf("ReOrderServices:  adding: %s", name))
 				startOrder = append(startOrder, service)
 				startOrder = append(startOrder, service)
 				delete(serviceMap, name)
 				delete(serviceMap, name)
 				changed = true
 				changed = true

+ 17 - 0
src/docker/api_updateContainer.go

@@ -15,8 +15,10 @@ import (
 
 
 type ContainerForm struct {
 type ContainerForm struct {
 	Image          string            `json:"image"`
 	Image          string            `json:"image"`
+	User 				   string            `json:"user"`
 	RestartPolicy  string            `json:"restartPolicy"`
 	RestartPolicy  string            `json:"restartPolicy"`
 	Env            []string          `json:"envVars"`
 	Env            []string          `json:"envVars"`
+	Devices        []string `json:"devices"`
 	Labels         map[string]string `json:"labels"`
 	Labels         map[string]string `json:"labels"`
 	PortBindings   nat.PortMap       `json:"portBindings"`
 	PortBindings   nat.PortMap       `json:"portBindings"`
 	Volumes        []mount.Mount     `json:"Volumes"`
 	Volumes        []mount.Mount     `json:"Volumes"`
@@ -69,14 +71,29 @@ func UpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
 			container.Config.Image = form.Image
 			container.Config.Image = form.Image
 		}
 		}
 		if(form.RestartPolicy != "") {
 		if(form.RestartPolicy != "") {
+			// THIS IS HACK BECAUSE USER IS NULLABLE, BETTER SOLUTION TO COME
+			container.Config.User = form.User
 			container.HostConfig.RestartPolicy = containerType.RestartPolicy{Name: form.RestartPolicy}
 			container.HostConfig.RestartPolicy = containerType.RestartPolicy{Name: form.RestartPolicy}
 		}
 		}
 		if(form.Env != nil) {
 		if(form.Env != nil) {
 			container.Config.Env = form.Env
 			container.Config.Env = form.Env
 		}
 		}
+		
+		if(form.Devices != nil) {
+			container.HostConfig.Devices = []containerType.DeviceMapping{}
+			for _, device := range form.Devices {
+				container.HostConfig.Devices = append(container.HostConfig.Devices, containerType.DeviceMapping{
+					PathOnHost:        device,
+					PathInContainer:   device,
+					CgroupPermissions: "rwm",
+				})
+			}
+		}
+
 		if(form.Labels != nil) {
 		if(form.Labels != nil) {
 			container.Config.Labels = form.Labels
 			container.Config.Labels = form.Labels
 		}
 		}
+
 		if(form.PortBindings != nil) {
 		if(form.PortBindings != nil) {
 			utils.Debug(fmt.Sprintf("UpdateContainer: PortBindings: %v", form.PortBindings))
 			utils.Debug(fmt.Sprintf("UpdateContainer: PortBindings: %v", form.PortBindings))
 			container.HostConfig.PortBindings = form.PortBindings
 			container.HostConfig.PortBindings = form.PortBindings

+ 2 - 2
src/docker/checkPorts.go

@@ -72,12 +72,12 @@ func CheckPorts() error {
 	finalPorts := []string{}
 	finalPorts := []string{}
 
 
 	for containerPort, hostConfig := range inspect.NetworkSettings.Ports {
 	for containerPort, hostConfig := range inspect.NetworkSettings.Ports {
-		utils.Debug("Container port: " + containerPort.Port())
+		utils.Debug("Container port: " + containerPort.Port() + "/" + containerPort.Proto())
 		
 		
 		for _, hostPort := range hostConfig {
 		for _, hostPort := range hostConfig {
 			utils.Debug("Host port: " + hostPort.HostPort)
 			utils.Debug("Host port: " + hostPort.HostPort)
 			ports[hostPort.HostPort] = struct{}{}
 			ports[hostPort.HostPort] = struct{}{}
-			finalPorts = append(finalPorts, hostPort.HostPort + ":" + containerPort.Port())
+			finalPorts = append(finalPorts, hostPort.HostPort + ":" + containerPort.Port() + "/" + containerPort.Proto())
 		}
 		}
 	}
 	}
 
 

+ 2 - 0
src/docker/docker.go

@@ -144,6 +144,8 @@ func EditContainer(oldContainerID string, newConfig types.ContainerJSON, noLock
 	oldContainer := newConfig
 	oldContainer := newConfig
 
 
 	if(oldContainerID != "") {
 	if(oldContainerID != "") {
+		utils.Log("EditContainer - inspecting previous container " + oldContainerID)
+
 		// create missing folders
 		// create missing folders
 		
 		
 		for _, newmount := range newConfig.HostConfig.Mounts {
 		for _, newmount := range newConfig.HostConfig.Mounts {

+ 78 - 0
src/utils/utils.go

@@ -73,6 +73,84 @@ var DefaultConfig = Config{
       "https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-only/hosts",
       "https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-only/hosts",
 		},
 		},
 	},
 	},
+  MonitoringAlerts: map[string]Alert{
+    "Anti Crypto-Miner": {
+      Name: "Anti Crypto-Miner",
+      Enabled: false,
+      Period: "daily",
+      TrackingMetric: "cosmos.system.docker.cpu.*",
+			LastTriggered: time.Time{},
+      Condition: AlertCondition {
+        Operator: "gt",
+        Value: 80,
+        Percent: false,
+      },
+      Actions: []AlertAction {
+				AlertAction {
+          Type: "notification",
+          Target: "",
+        },
+        AlertAction {
+          Type: "email",
+          Target: "",
+        },
+        AlertAction {
+          Type: "stop",
+          Target: "",
+        },
+			},
+      Throttled: false,
+      Severity: "warn",
+    },
+    "Anti Memory Leak": {
+      Name: "Anti Memory Leak",
+      Enabled: false,
+      Period: "daily",
+      TrackingMetric: "cosmos.system.docker.ram.*",
+			LastTriggered: time.Time{},
+      Condition: AlertCondition {
+        Operator: "gt",
+        Value: 80,
+        Percent: true,
+      },
+      Actions: []AlertAction {
+        {
+          Type: "notification",
+          Target: "",
+        },
+        {
+          Type: "email",
+          Target: "",
+        },
+        {
+          Type: "stop",
+          Target: "",
+        },
+      },
+      Throttled: false,
+      Severity: "warn",
+    },
+    "Disk Full Notification": {
+      Name: "Disk Full Notification",
+      Enabled: true,
+      Period: "latest",
+      TrackingMetric: "cosmos.system.disk./",
+			LastTriggered: time.Time{},
+      Condition: AlertCondition {
+        Operator: "gt",
+        Value: 95,
+        Percent: true,
+      },
+      Actions: []AlertAction {
+        {
+          Type: "notification",
+          Target: "",
+        },
+			},
+      Throttled: true,
+      Severity: "warn",
+    },
+  },
 }
 }
 
 
 func FileExists(path string) bool {
 func FileExists(path string) bool {