[release] v0.15.8

This commit is contained in:
Yann Stepienik
2024-02-13 19:07:44 +00:00
parent d818cd6d6d
commit 4e5645f9bb
20 changed files with 508 additions and 154 deletions

View File

@@ -1,3 +1,9 @@
## Version 0.15.0
- Added Disk management (Format, mount, SMART, etc...)
- Added Storage management (MergeFS, SnapRAID, RClone, etc...)
- Overwrite all docker networks size to prevent Cosmos from running out of IP addresses
- Added optional subnet input to the network creation
## Version 0.14.6
- Fix custom back-up folder logic

View File

@@ -43,13 +43,13 @@ const disks = {
}))
},
format({disk, format}, onProgress) {
format({disk, format, password}, onProgress) {
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({disk, format})
body: JSON.stringify({disk, format, password})
};
return fetch('/cosmos/api/disks/format', requestOptions)

View File

@@ -53,24 +53,32 @@ const LogsInModal = ({title, request, OnSuccess, OnError, closeAnytime, OnClose,
if(request === null) return;
request((newlog) => {
setLogs((old) => smartDockerLogConcat(old, newlog));
try {
request((newlog) => {
setLogs((old) => smartDockerLogConcat(old, newlog));
if(preRef.current)
preRef.current.scrollTop = preRef.current.scrollHeight;
if (newlog.includes('[OPERATION SUCCEEDED]')) {
setDone(true);
if(!alwaysShow)
close();
OnSuccess && OnSuccess();
} else if (newlog.includes('[OPERATION FAILED]')) {
setDone(true);
OnError && OnError(newlog);
} else {
setOpenModal(true);
}
});
if(preRef.current)
preRef.current.scrollTop = preRef.current.scrollHeight;
if (newlog.includes('[OPERATION SUCCEEDED]')) {
setDone(true);
if(!alwaysShow)
close();
OnSuccess && OnSuccess();
} else if (newlog.includes('[OPERATION FAILED]')) {
setDone(true);
OnError && OnError(newlog);
} else {
setOpenModal(true);
}
}).catch((err) => {
setLogs((old) => smartDockerLogConcat(old, err.message));
OnError && OnError(err);
});
} catch(err) {
setLogs((old) => smartDockerLogConcat(old, err.message));
OnError && OnError(err);
};
}, [request]);
return <>

View File

@@ -0,0 +1,82 @@
// material-ui
import * as React from 'react';
import { Alert, Button, Checkbox, FormControl, FormHelperText, Grid, InputLabel, MenuItem, Select, Stack, TextField } from '@mui/material';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import { LoadingButton } from '@mui/lab';
import { useFormik, FormikProvider } from 'formik';
import * as Yup from 'yup';
const PasswordModal = ({ textInfos, cb, OnClose }) => {
const formik = useFormik({
initialValues: {
password: ''
},
validationSchema: Yup.object({
password: Yup.string().required('Required'),
}),
onSubmit: async (values, { setErrors, setStatus, setSubmitting }) => {
setSubmitting(true);
cb(values.password).then((res) => {
setStatus({ success: true });
setSubmitting(false);
OnClose();
}).catch((err) => {
setStatus({ success: false });
setErrors({ submit: err.message });
setSubmitting(false);
});
},
});
return (
<>
<Dialog open={true} onClose={() => OnClose()}>
<FormikProvider value={formik}>
<DialogTitle>Confirm Password</DialogTitle>
<DialogContent>
<DialogContentText>
<form onSubmit={formik.handleSubmit}>
<Stack spacing={2} width={'300px'} style={{ marginTop: '10px' }}>
<TextField
fullWidth
id="password"
name="password"
label="Your Password"
value={formik.values.password}
type="password"
onChange={formik.handleChange}
error={formik.touched.password && Boolean(formik.errors.password)}
helperText={formik.touched.password && formik.errors.password}
style={{ marginBottom: '16px' }}
/>
</Stack>
</form>
{formik.errors.submit && (
<Grid item xs={12}>
<FormHelperText error>{formik.errors.submit}</FormHelperText>
</Grid>
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => OnClose()}>Cancel</Button>
<LoadingButton
disabled={formik.errors.submit}
onClick={formik.handleSubmit}
loading={formik.isSubmitting}
>
Confirm
</LoadingButton>
</DialogActions>
</FormikProvider>
</Dialog>
</>
);
};
export default PasswordModal;

View File

@@ -204,7 +204,7 @@ const NewInstall = () => {
setDatabaseEnable(false);
}
pullRequestOnSuccess();
API.getStatus().then((res) => {
return API.getStatus().then((res) => {
formik.setSubmitting(false);
formik.setStatus({ success: true });
});

View File

@@ -28,7 +28,7 @@ const GetActions = ({
if(action === 'update') {
setPullRequest(() => ((cb) => {
API.docker.updateContainerImage(Id, cb, true)
return API.docker.updateContainerImage(Id, cb, true)
}));
return;
}

View File

@@ -24,6 +24,7 @@ const NewNetworkButton = ({ fullWidth, refresh }) => {
driver: 'bridge',
attachCosmos: false,
parentInterface: '',
subnet: '',
},
validationSchema: Yup.object({
name: Yup.string().required('Required'),
@@ -93,6 +94,18 @@ const NewNetworkButton = ({ fullWidth, refresh }) => {
<MenuItem value="mcvlan">MCVLAN</MenuItem>
</Select>
</FormControl>
<TextField
fullWidth
id="subnet"
name="subnet"
label="Subnet (optional)"
value={formik.values.subnet}
onChange={formik.handleChange}
error={formik.touched.subnet && Boolean(formik.errors.subnet)}
helperText={formik.touched.subnet && formik.errors.subnet}
style={{ marginBottom: '16px' }}
/>
{formik.values.driver === 'mcvlan' && (
<TextField

View File

@@ -0,0 +1,103 @@
// material-ui
import * as React from 'react';
import { Alert, Button, Checkbox, FormControl, FormHelperText, Grid, InputLabel, MenuItem, Select, Stack, TextField } from '@mui/material';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import { LoadingButton } from '@mui/lab';
import { useFormik, FormikProvider } from 'formik';
import * as Yup from 'yup';
const FormatModal = ({ cb, OnClose }) => {
const formik = useFormik({
initialValues: {
password: '',
format: 'ext4'
},
validationSchema: Yup.object({
password: Yup.string().required('Required'),
}),
onSubmit: async (values, { setErrors, setStatus, setSubmitting }) => {
setSubmitting(true);
cb(values).then((res) => {
setStatus({ success: true });
setSubmitting(false);
OnClose();
}).catch((err) => {
setStatus({ success: false });
setErrors({ submit: err.message });
setSubmitting(false);
});
},
});
return (
<>
<Dialog open={true} onClose={() => OnClose()}>
<FormikProvider value={formik}>
<DialogTitle>Format Disk</DialogTitle>
<DialogContent>
<DialogContentText>
<form onSubmit={formik.handleSubmit}>
<Stack spacing={2} width={'300px'} style={{ marginTop: '10px' }}>
<FormControl
fullWidth
variant="outlined"
error={formik.touched.format && Boolean(formik.errors.format)}
style={{ marginBottom: '16px' }}
>
<InputLabel htmlFor="format">Disk Format</InputLabel>
<Select
id="format"
name="format"
value={formik.values.format}
onChange={formik.handleChange}
label="Disk Format"
>
<MenuItem value="ext4">Ext4 (Recommended)</MenuItem>
<MenuItem value="ext3">Ext3</MenuItem>
<MenuItem value="exfat">ExFAT</MenuItem>
</Select>
</FormControl>
<TextField
fullWidth
id="password"
name="password"
label="Confirm Your Password"
value={formik.values.password}
type="password"
onChange={formik.handleChange}
error={formik.touched.password && Boolean(formik.errors.password)}
helperText={formik.touched.password && formik.errors.password}
style={{ marginBottom: '16px' }}
/>
</Stack>
</form>
{formik.errors.submit && (
<Grid item xs={12}>
<FormHelperText error>{formik.errors.submit}</FormHelperText>
</Grid>
)}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => OnClose()}>Cancel</Button>
<LoadingButton
disabled={formik.errors.submit}
onClick={formik.handleSubmit}
loading={formik.isSubmitting}
>
Confirm
</LoadingButton>
</DialogActions>
</FormikProvider>
</Dialog>
</>
);
};
export default FormatModal;

View File

@@ -22,6 +22,8 @@ import raidIcon from '../../assets/images/icons/database.svg';
import { simplifyNumber } from "../dashboard/components/utils";
import LogsInModal from "../../components/logsInModal";
import MountDialog from "./mountDialog";
import PasswordModal from "../../components/passwordModal";
import FormatModal from "./FormatModal";
const diskStyle = {
width: "100%",
@@ -44,21 +46,37 @@ const icons = {
const FormatButton = ({disk, refresh}) => {
const [formatting, setFormatting] = useState(false);
const [loading, setLoading] = useState(false);
const [passwordConfirm, setPasswordConfirm] = useState(false);
const [values, setValues] = useState("");
return <>
<LoadingButton
loading={loading}
onClick={() => {setFormatting(true); setLoading(true)}}
variant="contained"
onClick={() => {setPasswordConfirm(true); setLoading(true)}}
variant="outlined"
color="error"
size="small"
>Format</LoadingButton>
{passwordConfirm && <FormatModal
OnClose={() => {
setPasswordConfirm(false);
setLoading(false);
}}
textInfos={`Enter your password to confirm you want to format ${disk.name}`}
cb={async (values) => {
setPasswordConfirm(false);
setFormatting(values);
}}
/>}
{formatting && <LogsInModal
request={(cb) => {
API.storage.disks.format({
disk: disk.name,
format: "ext4",
}, cb)
return API.storage.disks.format({
disk: disk.name,
format: formatting.format,
password: formatting.password,
}, cb)
}}
initialLogs={[
"Starting format disk " + disk.name + "..."
@@ -85,92 +103,94 @@ const Disk = ({disk, refresh}) => {
percent = Math.round((disk.usage / disk.size) * 100);
}
return <TreeItem sx={{
'&.Mui-selected': {
color: 'red !important',
backgroundColor: 'red'
}
}} style={diskStyle} nodeId={disk.name} label={
<div style={{
width: "100%",
padding: "10px",
border: "1px solid #eee",
borderRadius: "5px",
borderColor: darkMode ? "#555" : "#eee",
backgroundColor: darkMode ? "#333" : "#fff",
color: darkMode ? "#fff" : "#000",
}}>
<Stack direction="row" justifyContent="space-between" style={{
borderLeft: ("4px solid " + (disk.smart.Temperature ? (disk.smart.Temperature > 55 ? (disk.smart.Temperature > 70 ? "red" : "orange") : "green") : "gray")),
}}>
<Stack direction="row" spacing={2} alignItems="center" sx={{paddingLeft: '5px'}}>
<div>
{disk.smart.Temperature ? <div style={{
position: 'absolute',
top: '10px',
left: '25px',
backgroundColor: 'rgba(0,0,0,0.8)',
color: 'white',
borderRadius: '5px',
padding: '0px 5px',
}}>
<span style={{fontSize: '80%', opacity: 0.8}}>
{disk.smart.Temperature > 55 && <WarningFilled />} {disk.smart.Temperature}°C
</span></div> : ""}
<Tooltip title={disk.type}>
{icons[disk.type] ? <img width="64px" height={"64px"} src={icons[disk.type]} /> : <img width="64px" height={"64px"} src={icons["drive"]} />}
</Tooltip>
</div>
<div>
<Stack spacing={1}>
<div style={{fontWeight: 'bold'}}>
{disk.name.replace("/dev/", "")}
<span style={{opacity: 0.8, fontSize: '80%'}}>
{disk.rota ? " (HDD)" : " (SSD)"}
{disk.fstype ? ` - ${disk.fstype}` : ""}
{disk.model ? ` - ${disk.model}` : ""} {disk.mountpoint ? ` (${disk.mountpoint})` : ""}
</span>
</div>
{disk.usage ? <>
<div><LinearProgress
variant="determinate"
color={percent > 95 ? 'error' : (percent > 75 ? 'warning' : 'info')}
value={percent} /></div>
<div>{simplifyNumber(disk.usage, 'b')} / {simplifyNumber(disk.size, 'b')} ({percent}%)</div>
</>: simplifyNumber(disk.size, 'b')}
return <>
<TreeItem sx={{
'&.Mui-selected': {
color: 'red !important',
backgroundColor: 'red'
}
}} style={diskStyle} nodeId={disk.name} label={
<div style={{
width: "100%",
padding: "10px",
border: "1px solid #eee",
borderRadius: "5px",
borderColor: darkMode ? "#555" : "#eee",
backgroundColor: darkMode ? "#333" : "#fff",
color: darkMode ? "#fff" : "#000",
}}>
<Stack direction="row" justifyContent="space-between" style={{
borderLeft: ("4px solid " + (disk.smart.Temperature ? (disk.smart.Temperature > 55 ? (disk.smart.Temperature > 70 ? "red" : "orange") : "green") : "gray")),
}}>
<Stack direction="row" spacing={2} alignItems="center" sx={{paddingLeft: '5px'}}>
<div>
{disk.smart.Temperature ? <div style={{
position: 'absolute',
top: '10px',
left: '25px',
backgroundColor: 'rgba(0,0,0,0.8)',
color: 'white',
borderRadius: '5px',
padding: '0px 5px',
}}>
<span style={{fontSize: '80%', opacity: 0.8}}>
{disk.smart.Temperature > 55 && <WarningFilled />} {disk.smart.Temperature}°C
</span></div> : ""}
<Tooltip title={disk.type}>
{icons[disk.type] ? <img width="64px" height={"64px"} src={icons[disk.type]} /> : <img width="64px" height={"64px"} src={icons["drive"]} />}
</Tooltip>
</div>
<div>
<Stack spacing={1}>
<div style={{fontWeight: 'bold'}}>
{disk.name.replace("/dev/", "")}
<span style={{opacity: 0.8, fontSize: '80%'}}>
{disk.rota ? " (HDD)" : " (SSD)"}
{disk.fstype ? ` - ${disk.fstype}` : ""}
{disk.model ? ` - ${disk.model}` : ""} {disk.mountpoint ? ` (${disk.mountpoint})` : ""}
</span>
</div>
{disk.usage ? <>
<div><LinearProgress
variant="determinate"
color={percent > 95 ? 'error' : (percent > 75 ? 'warning' : 'info')}
value={percent} /></div>
<div>{simplifyNumber(disk.usage, 'b')} / {simplifyNumber(disk.size, 'b')} ({percent}%)</div>
</>: simplifyNumber(disk.size, 'b')}
</Stack>
</div>
</Stack>
<Stack direction={"row"} spacing={2} style={{textAlign: 'right'}}>
<Stack spacing={0} style={{textAlign: 'right', opacity: 0.8, fontSize: '80%'}}>
{Object.keys(disk).filter(key => key !== "name" && key !== "children" && key !== "smart"&& key !== "fstype"&& key !== "mountpoint"&& key !== "model" && key !== "usage" &&key !== "rota" && key !== "type" && key !== "size").map((key, index) => {
return <div key={index}>{key}: {
(typeof disk[key] == "object" ? JSON.stringify(disk[key]) : disk[key])
}</div>
})}
</Stack>
<Stack spacing={2} direction="column" justifyContent={"center"}>
{(disk.type == "disk" || disk.type == "part") ? <FormatButton disk={disk} refresh={refresh}/> : ""}
{disk.mountpoint ? <MountDialog disk={disk} unmount={true} refresh={refresh} /> : ""}
{(
(disk.type == "part" || (disk.type == "disk" && (!disk.children || !disk.children.length))) &&
disk.fstype &&
disk.fstype !== "swap" &&
!disk.mountpoint
) ? <MountDialog disk={disk} refresh={refresh} /> : ""}
</Stack>
</div>
</Stack>
<Stack direction={"row"} spacing={2} style={{textAlign: 'right'}}>
<Stack spacing={0} style={{textAlign: 'right', opacity: 0.8, fontSize: '80%'}}>
{Object.keys(disk).filter(key => key !== "name" && key !== "children" && key !== "smart"&& key !== "fstype"&& key !== "mountpoint"&& key !== "model" && key !== "usage" &&key !== "rota" && key !== "type" && key !== "size").map((key, index) => {
return <div key={index}>{key}: {
(typeof disk[key] == "object" ? JSON.stringify(disk[key]) : disk[key])
}</div>
})}
</Stack>
<Stack spacing={2} direction="column" justifyContent={"center"}>
{(disk.type == "disk" || disk.type == "part") ? <FormatButton disk={disk} refresh={refresh}/> : ""}
{disk.mountpoint ? <MountDialog disk={disk} unmount={true} refresh={refresh} /> : ""}
{(
(disk.type == "part" || (disk.type == "disk" && (!disk.children || !disk.children.length))) &&
disk.fstype &&
disk.fstype !== "swap" &&
!disk.mountpoint
) ? <MountDialog disk={disk} refresh={refresh} /> : ""}
</Stack>
</Stack>
</Stack>
</div>
}>
{disk.children && disk.children.map((child, index) => {
return <Disk disk={child} refresh={refresh} />
})}
</TreeItem>
</div>
}>
{disk.children && disk.children.map((child, index) => {
return <Disk disk={child} refresh={refresh} />
})}
</TreeItem>
</>;
}
export const StorageDisks = () => {
@@ -206,7 +226,6 @@ export const StorageDisks = () => {
>
{disks && disks.map((disk, index) => {
return <Disk disk={disk} refresh={async () => {
// wait 1s
await new Promise(r => setTimeout(r, 1000));
await refresh()
}} />

View File

@@ -37,7 +37,7 @@ export const StorageMounts = () => {
<div>
{mounts && mounts.map((mount, index) => {
return <div>
<FolderOutlined/> {mount.Device} - {mount.Path} ({mount.Type})
<FolderOutlined/> {mount.device} - {mount.path} ({mount.type})
</div>
})}
</div>

1
go.mod
View File

@@ -249,4 +249,5 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.4.0 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect
)

View File

@@ -1,6 +1,6 @@
{
"name": "cosmos-server",
"version": "0.15.0-unstable7",
"version": "0.15.0-unstable8",
"description": "",
"main": "test-server.js",
"bugs": {

View File

@@ -320,16 +320,18 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
}
}
_, err = DockerClient.NetworkCreate(DockerContext, networkToCreateName, doctype.NetworkCreate{
Driver: networkToCreate.Driver,
Attachable: networkToCreate.Attachable,
Internal: networkToCreate.Internal,
EnableIPv6: networkToCreate.EnableIPv6,
IPAM: &network.IPAM{
Driver: networkToCreate.IPAM.Driver,
Config: ipamConfig,
},
})
networkPayload := doctype.NetworkCreate{
Driver: networkToCreate.Driver,
Attachable: networkToCreate.Attachable,
Internal: networkToCreate.Internal,
EnableIPv6: networkToCreate.EnableIPv6,
IPAM: &network.IPAM{
Driver: networkToCreate.IPAM.Driver,
Config: ipamConfig,
},
}
_, err = CreateReasonableNetwork(networkToCreateName, networkPayload)
if err != nil {
utils.Error("CreateService: Rolling back changes because of -- Network", err)

View File

@@ -9,6 +9,7 @@ import (
"github.com/gorilla/mux"
"github.com/azukaar/cosmos-server/src/utils"
"github.com/docker/docker/api/types"
network "github.com/docker/docker/api/types/network"
)
func ListNetworksRoute(w http.ResponseWriter, req *http.Request) {
@@ -251,7 +252,8 @@ type createNetworkPayload struct {
Name string `json:"name"`
Driver string `json:"driver"`
AttachCosmos bool `json:"attachCosmos"`
parentInterface string `json:"parentInterface"`
ParentInterface string `json:"parentInterface"`
Subnet string `json:"subnet"`
}
func CreateNetworkRoute(w http.ResponseWriter, req *http.Request) {
@@ -279,11 +281,22 @@ func CreateNetworkRoute(w http.ResponseWriter, req *http.Request) {
CheckDuplicate: true,
Driver: payload.Driver,
Options: map[string]string{
"parent": payload.parentInterface,
"parent": payload.ParentInterface,
},
}
resp, err := DockerClient.NetworkCreate(context.Background(), payload.Name, networkCreate)
if payload.Subnet != "" {
networkCreate.IPAM = &network.IPAM{
Driver: "default",
Config: []network.IPAMConfig{
{
Subnet: payload.Subnet,
},
},
}
}
resp, err := CreateReasonableNetwork(payload.Name, networkCreate)
if err != nil {
utils.Error("CreateNetworkRoute: Error while creating network", err)
utils.HTTPError(w, "Network Create Error: " + err.Error(), http.StatusInternalServerError, "CN004")

View File

@@ -55,19 +55,63 @@ func doesSubnetOverlap(networks []types.NetworkResource, subnet string) bool {
return false
}
func findAvailableSubnet() string {
func findAvailableSubnets(nb int) ([]string, error) {
result := []string{}
baseSubnet := "100.0.0.0/29"
networks, err := DockerClient.NetworkList(DockerContext, types.NetworkListOptions{})
if err != nil {
panic(err)
utils.Error("Docker Network List", err)
return []string{}, err
}
for doesSubnetOverlap(networks, baseSubnet) {
baseSubnet = getNextSubnet(baseSubnet)
for i := 0; i < nb; i++ {
for doesSubnetOverlap(networks, baseSubnet) {
baseSubnet = getNextSubnet(baseSubnet)
}
result = append(result, baseSubnet)
}
return baseSubnet
if len(result) != nb {
return []string{}, errors.New("Not enough subnets available. Try cleaning up networks.")
}
return result, nil
}
func CreateReasonableNetwork(name string, networkDef types.NetworkCreate) (types.NetworkCreateResponse, error) {
// if no subnet
if networkDef.IPAM == nil {
networkDef.IPAM = &network.IPAM{
Driver: "default",
}
}
if len(networkDef.IPAM.Config) == 0 {
subnets, err := findAvailableSubnets(1)
if err != nil {
return types.NetworkCreateResponse{}, err
}
networkDef.IPAM.Config = []network.IPAMConfig{
network.IPAMConfig{
Subnet: subnets[0],
},
}
} else {
subnets, err := findAvailableSubnets(len(networkDef.IPAM.Config))
if err != nil {
return types.NetworkCreateResponse{}, err
}
for i, config := range networkDef.IPAM.Config {
if config.Subnet == "" {
config.Subnet = subnets[i]
}
}
}
return DockerClient.NetworkCreate(DockerContext, name, networkDef)
}
func CreateCosmosNetwork(name string) (string, error) {
@@ -93,7 +137,11 @@ func CreateCosmosNetwork(name string) (string, error) {
utils.Log("Creating new secure network: " + newNeworkName)
subnet := findAvailableSubnet()
subnet, err := findAvailableSubnets(1)
if err != nil {
utils.Error("Docker Network Create", err)
return "", err
}
// create network
newNetHan, err := DockerClient.NetworkCreate(DockerContext, newNeworkName, types.NetworkCreate{
@@ -102,7 +150,7 @@ func CreateCosmosNetwork(name string) (string, error) {
Driver: "default",
Config: []network.IPAMConfig{
network.IPAMConfig{
Subnet: subnet,
Subnet: subnet[0],
},
},
},
@@ -345,11 +393,14 @@ func _debounceNetworkCleanUp() func(string) {
}
func CreateLinkNetwork(containerName string, container2Name string) error {
subnet := findAvailableSubnet()
subnet, err := findAvailableSubnets(1)
if err != nil {
return err
}
// create network
networkName := "cosmos-link-" + containerName + "-" + container2Name + "-" + utils.GenerateRandomString(2)
_, err := DockerClient.NetworkCreate(DockerContext, networkName, types.NetworkCreate{
_, err = DockerClient.NetworkCreate(DockerContext, networkName, types.NetworkCreate{
CheckDuplicate: true,
Labels: map[string]string{
"cosmos-link": "true",
@@ -360,7 +411,7 @@ func CreateLinkNetwork(containerName string, container2Name string) error {
IPAM: &network.IPAM{
Config: []network.IPAMConfig{
network.IPAMConfig{
Subnet: subnet,
Subnet: subnet[0],
},
},
},

View File

@@ -161,7 +161,7 @@ func AggloMetrics(metricsList []string) []DataDefDB {
metric.ValuesAggl["hour_" + valueHourlyPool] = currentPool
} else {
utils.Warn("Metrics: Agglomeration - Pool not found : " + "hour_" + valueHourlyPool)
utils.Debug("Metrics: Agglomeration - Pool not found : " + "hour_" + valueHourlyPool)
}
if _, ok := metric.ValuesAggl["day_" + valueDailyPool]; ok {
@@ -174,7 +174,7 @@ func AggloMetrics(metricsList []string) []DataDefDB {
metric.ValuesAggl["day_" + valueDailyPool] = currentPool
} else {
utils.Warn("Metrics: Agglomeration - Pool not found: " + "day_" + valueDailyPool)
utils.Debug("Metrics: Agglomeration - Pool not found: " + "day_" + valueDailyPool)
}
values[valInd].Processed = true

View File

@@ -13,6 +13,7 @@ import (
type FormatDiskJSON struct {
Disk string `json:"disk"`
Format string `json:"format"`
Password string `json:"password"`
}
func isDiskOrPartition(path string) (string, error) {
@@ -43,15 +44,8 @@ func FormatDiskRoute(w http.ResponseWriter, req *http.Request) {
return
}
if req.Method == "POST" {
var request FormatDiskJSON
err := json.NewDecoder(req.Body).Decode(&request)
if err != nil {
utils.Error("FormatDiskRoute: Invalid User Request", err)
utils.HTTPError(w, "FormatDiskRoute Error", http.StatusInternalServerError, "FD001")
return
}
if req.Method == "POST" {
// Enable streaming of response by setting appropriate headers
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("Transfer-Encoding", "chunked")
@@ -63,10 +57,31 @@ func FormatDiskRoute(w http.ResponseWriter, req *http.Request) {
return
}
var request FormatDiskJSON
err := json.NewDecoder(req.Body).Decode(&request)
if err != nil {
utils.Error("FormatDiskRoute: Invalid User Request", err)
fmt.Fprintf(w, "[OPERATION FAILED] FormatDiskRoute Syntax Error")
http.Error(w, "FormatDiskRoute Syntax Error", http.StatusBadRequest)
return
}
nickname := req.Header.Get("x-cosmos-user")
errp := utils.CheckPassword(nickname, request.Password)
if errp != nil {
utils.Error("FormatDiskRoute: Invalid User Request", errp)
fmt.Fprintf(w, "[OPERATION FAILED] Wrong password supplied. Try again")
http.Error(w, "Wrong password supplied. Try again", http.StatusUnauthorized)
return
}
out, err := FormatDisk(request.Disk, request.Format)
if err != nil {
utils.Error("FormatDiskRoute: Error formatting disk", err)
utils.HTTPError(w, "FormatDiskRoute Error", http.StatusInternalServerError, "FD002")
fmt.Fprintf(w, "[OPERATION FAILED] Error formatting disk: " + err.Error())
http.Error(w, "Error formatting disk", http.StatusInternalServerError)
return
}
@@ -85,7 +100,8 @@ func FormatDiskRoute(w http.ResponseWriter, req *http.Request) {
out, err = CreateSinglePartition(request.Disk)
if err != nil {
utils.Error("FormatDiskRoute: Error creating partition", err)
utils.HTTPError(w, "FormatDiskRoute Error", http.StatusInternalServerError, "FD003")
fmt.Fprintf(w, "[OPERATION FAILED] Error creating partition: " + err.Error())
http.Error(w, "Error creating partition", http.StatusInternalServerError)
return
}
@@ -99,7 +115,8 @@ func FormatDiskRoute(w http.ResponseWriter, req *http.Request) {
out, err = FormatDisk(request.Disk + "1", request.Format)
if err != nil {
utils.Error("FormatDiskRoute: Error formatting partition", err)
utils.HTTPError(w, "FormatDiskRoute Error", http.StatusInternalServerError, "FD002")
fmt.Fprintf(w, "[OPERATION FAILED] Error formatting partition: " + err.Error())
http.Error(w, "Error formatting partition", http.StatusInternalServerError)
return
}

View File

@@ -60,10 +60,10 @@ func GetRecursiveDiskUsageAndSMARTInfo(devices []lsblk.BlockDevice) ([]BlockDevi
if err == nil {
devicesF[i].SMART = *smartInfo
} else {
utils.Error("GetRecursiveDiskUsageAndSMARTInfo - Error fetching SMART info for " + device.Name + " : ", err)
utils.Warn("GetRecursiveDiskUsageAndSMARTInfo - Error fetching SMART info for " + device.Name + " : " + err.Error())
}
} else {
utils.Error("GetRecursiveDiskUsageAndSMARTInfo - Error fetching SMART info for " + device.Name + " : ", err)
utils.Warn("GetRecursiveDiskUsageAndSMARTInfo - Error fetching SMART info for " + device.Name + " : " + err.Error())
}
}
@@ -78,8 +78,6 @@ func GetRecursiveDiskUsageAndSMARTInfo(devices []lsblk.BlockDevice) ([]BlockDevi
}
func GetDiskUsage(path string) (perc uint64, err error) {
fmt.Println("[STORAGE] Getting usage of disk " + path)
// Get the disk usage using the df command
cmd := exec.Command("df", "-k", path)

View File

@@ -25,7 +25,7 @@ func UserLogin(w http.ResponseWriter, req *http.Request) {
utils.Error("UserLogin: User already logged ing", nil)
utils.HTTPError(w, "User is already logged in", http.StatusUnauthorized, "UL002")
return
}
}
var request LoginRequestJSON
err1 := json.NewDecoder(req.Body).Decode(&request)
@@ -36,7 +36,7 @@ func UserLogin(w http.ResponseWriter, req *http.Request) {
}
c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users")
defer closeDb()
defer closeDb()
if errCo != nil {
utils.Error("Database Connect", errCo)
utils.HTTPError(w, "Database Error", http.StatusInternalServerError, "DB001")

View File

@@ -13,13 +13,18 @@ import (
"fmt"
"sync"
"time"
"errors"
"path/filepath"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/net"
"golang.org/x/net/publicsuffix"
"github.com/Masterminds/semver"
"golang.org/x/crypto/bcrypt"
"go.mongodb.org/mongo-driver/mongo"
)
var ConfigLock sync.Mutex
@@ -711,3 +716,39 @@ func CompareSemver(v1, v2 string) (int, error) {
return ver1.Compare(ver2), nil
}
func CheckPassword(nickname, password string) error {
time.Sleep(time.Duration(rand.Float64()*1)*time.Second)
c, closeDb, errCo := GetEmbeddedCollection(GetRootAppId(), "users")
defer closeDb()
if errCo != nil {
return errCo
}
user := User{}
err3 := c.FindOne(nil, map[string]interface{}{
"Nickname": nickname,
}).Decode(&user)
if err3 == mongo.ErrNoDocuments {
bcrypt.CompareHashAndPassword([]byte("$2a$14$4nzsVwEnR3.jEbMTME7kqeCo4gMgR/Tuk7ivNExvXjr73nKvLgHka"), []byte("dummyPassword"))
return err3
} else if err3 != nil {
bcrypt.CompareHashAndPassword([]byte("$2a$14$4nzsVwEnR3.jEbMTME7kqeCo4gMgR/Tuk7ivNExvXjr73nKvLgHka"), []byte("dummyPassword"))
return err3
} else if user.Password == "" {
return errors.New("User not registered")
} else {
err2 := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err2 != nil {
return err2
}
return nil
}
}