cp
This commit is contained in:
parent
d68f9df66f
commit
53db1b883d
|
@ -6,6 +6,10 @@
|
||||||
- New color slider with reset buttons
|
- New color slider with reset buttons
|
||||||
- Fixed blinking modals issues
|
- Fixed blinking modals issues
|
||||||
- Added lazyloading to URL and Servapp pages images
|
- Added lazyloading to URL and Servapp pages images
|
||||||
|
- Added a button in the config page to easily download the docker backup
|
||||||
|
- Improve display or icons [fixes #121]
|
||||||
|
- Refactored Mongo connection code [fixes #111]
|
||||||
|
- Forward simultaneously TCP and UDP [fixes #122]
|
||||||
|
|
||||||
## Version 0.11.3
|
## Version 0.11.3
|
||||||
- Fix missing even subscriber on export
|
- Fix missing even subscriber on export
|
||||||
|
|
|
@ -83,6 +83,15 @@ async function addRoute(newRoute: Route): Promise<void> {
|
||||||
return rawUpdateRoute("", 'add', newRoute);
|
return rawUpdateRoute("", 'add', newRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBackup() {
|
||||||
|
return wrap(fetch('/cosmos/api/get-backup', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
get,
|
get,
|
||||||
set,
|
set,
|
||||||
|
@ -94,4 +103,5 @@ export {
|
||||||
deleteRoute,
|
deleteRoute,
|
||||||
addRoute,
|
addRoute,
|
||||||
canSendEmail,
|
canSendEmail,
|
||||||
|
getBackup,
|
||||||
};
|
};
|
|
@ -1,28 +1,41 @@
|
||||||
import { Button } from "@mui/material";
|
import { Button } from "@mui/material";
|
||||||
|
|
||||||
export const DownloadFile = ({ filename, content, label }) => {
|
export const DownloadFile = ({ filename, content, contentGetter, label }) => {
|
||||||
const downloadFile = () => {
|
const downloadFile = async () => {
|
||||||
// Create a blob with the content
|
// Get the content
|
||||||
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
if (contentGetter) {
|
||||||
|
try {
|
||||||
|
content = await contentGetter();
|
||||||
|
if(typeof content !== "string") {
|
||||||
|
content = JSON.stringify(content, null, 2);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create a link element
|
// Create a blob with the content
|
||||||
const link = document.createElement("a");
|
const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
|
||||||
link.href = URL.createObjectURL(blob);
|
|
||||||
link.download = filename;
|
|
||||||
|
|
||||||
// Append the link to the document (needed for Firefox)
|
// Create a link element
|
||||||
document.body.appendChild(link);
|
const link = document.createElement("a");
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = filename;
|
||||||
|
|
||||||
// Simulate a click to start the download
|
// Append the link to the document (needed for Firefox)
|
||||||
link.click();
|
document.body.appendChild(link);
|
||||||
|
|
||||||
// Cleanup the DOM by removing the link element
|
// Simulate a click to start the download
|
||||||
document.body.removeChild(link);
|
link.click();
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
// Cleanup the DOM by removing the link element
|
||||||
<Button onClick={downloadFile}>
|
document.body.removeChild(link);
|
||||||
{label}
|
}
|
||||||
</Button>
|
|
||||||
);
|
return (
|
||||||
|
<Button onClick={downloadFile}>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -33,6 +33,7 @@ import { SliderPicker
|
||||||
import {SetPrimaryColor, SetSecondaryColor} from '../../../App';
|
import {SetPrimaryColor, SetSecondaryColor} from '../../../App';
|
||||||
import { useClientInfos } from '../../../utils/hooks';
|
import { useClientInfos } from '../../../utils/hooks';
|
||||||
import ConfirmModal from '../../../components/confirmModal';
|
import ConfirmModal from '../../../components/confirmModal';
|
||||||
|
import { DownloadFile } from '../../../api/downloadButton';
|
||||||
|
|
||||||
const ConfigManagement = () => {
|
const ConfigManagement = () => {
|
||||||
const [config, setConfig] = React.useState(null);
|
const [config, setConfig] = React.useState(null);
|
||||||
|
@ -77,6 +78,13 @@ const ConfigManagement = () => {
|
||||||
}}
|
}}
|
||||||
label={'Purge Metrics Dashboard'}
|
label={'Purge Metrics Dashboard'}
|
||||||
content={'Are you sure you want to purge all the metrics data from the dashboards?'} />
|
content={'Are you sure you want to purge all the metrics data from the dashboards?'} />
|
||||||
|
|
||||||
|
<DownloadFile
|
||||||
|
filename={'backup.cosmos-compose.json'}
|
||||||
|
label={'Download Docker Backup'}
|
||||||
|
contentGetter={API.config.getBackup}
|
||||||
|
/>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{config && <>
|
{config && <>
|
||||||
|
|
|
@ -167,11 +167,12 @@ const _MiniPlotComponent = ({metrics, labels}) => {
|
||||||
<Stack direction='column' justifyContent={'center'} alignItems={'center'} spacing={0} style={{
|
<Stack direction='column' justifyContent={'center'} alignItems={'center'} spacing={0} style={{
|
||||||
width: '60px',
|
width: '60px',
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
{dataMetric.Values.length && <div style={{
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: '110%',
|
fontSize: '110%',
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
}}>{formaters[di](dataMetric.Values[dataMetric.Values.length - 1].Value)}</div>
|
}}>{formaters[di](dataMetric.Values[dataMetric.Values.length - 1].Value)}</div>
|
||||||
|
}
|
||||||
<div>
|
<div>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Alert, Checkbox, Chip, CircularProgress, Stack, Typography, useMediaQuery } from '@mui/material';
|
import { Alert, Checkbox, Chip, CircularProgress, Stack, Typography, useMediaQuery } from '@mui/material';
|
||||||
import MainCard from '../../../components/MainCard';
|
import MainCard from '../../../components/MainCard';
|
||||||
import { ContainerOutlined, DesktopOutlined, InfoCircleOutlined, NodeExpandOutlined, PlayCircleOutlined, PlusCircleOutlined, SafetyCertificateOutlined, SettingOutlined } from '@ant-design/icons';
|
import { ContainerOutlined, DashboardOutlined, DesktopOutlined, InfoCircleOutlined, NodeExpandOutlined, PlayCircleOutlined, PlusCircleOutlined, SafetyCertificateOutlined, SettingOutlined } from '@ant-design/icons';
|
||||||
import { getFaviconURL, getContainersRoutes } from '../../../utils/routes';
|
import { getFaviconURL, getContainersRoutes } from '../../../utils/routes';
|
||||||
import HostChip from '../../../components/hostChip';
|
import HostChip from '../../../components/hostChip';
|
||||||
import ExposeModal from '../exposeModal';
|
import ExposeModal from '../exposeModal';
|
||||||
|
@ -169,8 +169,8 @@ const ContainerOverview = ({ containerInfo, config, refresh, updatesAvailable, s
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<strong><NodeExpandOutlined /> Monitoring</strong>
|
<strong><DashboardOutlined /> Monitoring</strong>
|
||||||
<div style={{ width: '90%' }}>
|
<div style={{ width: '96%' }}>
|
||||||
<MiniPlotComponent metrics={[
|
<MiniPlotComponent metrics={[
|
||||||
"cosmos.system.docker.cpu." + Name.replace('/', ''),
|
"cosmos.system.docker.cpu." + Name.replace('/', ''),
|
||||||
"cosmos.system.docker.ram." + Name.replace('/', ''),
|
"cosmos.system.docker.ram." + Name.replace('/', ''),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
|
"io/ioutil"
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -50,3 +51,43 @@ func ConfigApiGet(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BackupFileApiGet(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if utils.AdminOnly(w, req) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Method == "GET" {
|
||||||
|
// read file
|
||||||
|
path := utils.CONFIGFOLDER + "backup.cosmos-compose.json"
|
||||||
|
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("BackupFileApiGet: Error opening file", err)
|
||||||
|
utils.HTTPError(w, "Error opening file", http.StatusInternalServerError, "HTTP002")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// read content
|
||||||
|
content, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("BackupFileApiGet: Error reading file content", err)
|
||||||
|
utils.HTTPError(w, "Error reading file content", http.StatusInternalServerError, "HTTP003")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the content type header, so that the client knows it's receiving a JSON file
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, err = w.Write(content)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("BackupFileApiGet: Error writing to response", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
utils.Error("BackupFileApiGet: Method not allowed " + req.Method, nil)
|
||||||
|
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -553,12 +553,16 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
|
||||||
|
|
||||||
port, protocol := portdef, portStuff[1]
|
port, protocol := portdef, portStuff[1]
|
||||||
|
|
||||||
|
if protocol == "" {
|
||||||
|
protocol = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
nextPort := strings.Split(port, ":")
|
nextPort := strings.Split(port, ":")
|
||||||
hostPort, contPort := nextPort[0], nextPort[1]
|
hostPort, contPort := nextPort[0], nextPort[1]
|
||||||
|
|
||||||
contPort = contPort + "/" + protocol
|
contPort = contPort + "/" + protocol
|
||||||
|
|
||||||
if hostPortsBound[hostPort] {
|
if hostPortsBound[hostPort + "/" + protocol] {
|
||||||
utils.Warn("Port " + hostPort + " already bound, skipping")
|
utils.Warn("Port " + hostPort + " already bound, skipping")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -583,7 +587,7 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
|
||||||
// Mark the container port as exposed
|
// Mark the container port as exposed
|
||||||
containerConfig.ExposedPorts[nat.Port(contPort)] = struct{}{}
|
containerConfig.ExposedPorts[nat.Port(contPort)] = struct{}{}
|
||||||
|
|
||||||
hostPortsBound[hostPort] = true
|
hostPortsBound[hostPort + "/" + protocol] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create missing folders for bind mounts
|
// Create missing folders for bind mounts
|
||||||
|
|
|
@ -332,6 +332,8 @@ func InitServer() *mux.Router {
|
||||||
srapi.HandleFunc("/api/background", UploadBackground)
|
srapi.HandleFunc("/api/background", UploadBackground)
|
||||||
srapi.HandleFunc("/api/background/{ext}", GetBackground)
|
srapi.HandleFunc("/api/background/{ext}", GetBackground)
|
||||||
|
|
||||||
|
srapi.HandleFunc("/api/get-backup", configapi.BackupFileApiGet)
|
||||||
|
|
||||||
srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices)
|
srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices)
|
||||||
srapi.HandleFunc("/api/constellation/restart", constellation.API_Restart)
|
srapi.HandleFunc("/api/constellation/restart", constellation.API_Restart)
|
||||||
srapi.HandleFunc("/api/constellation/reset", constellation.API_Reset)
|
srapi.HandleFunc("/api/constellation/reset", constellation.API_Reset)
|
||||||
|
|
|
@ -7,27 +7,35 @@ import (
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
"go.mongodb.org/mongo-driver/mongo/readpref"
|
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/writeconcern"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
var client *mongo.Client
|
var client *mongo.Client
|
||||||
|
|
||||||
func DB() error {
|
func DB() error {
|
||||||
if(GetBaseMainConfig().DisableUserManagement) {
|
if(GetMainConfig().DisableUserManagement) {
|
||||||
return errors.New("User Management is disabled")
|
return errors.New("User Management is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
uri := GetBaseMainConfig().MongoDB + "/?retryWrites=true&w=majority"
|
mongoURL := GetMainConfig().MongoDB
|
||||||
|
|
||||||
if(client != nil && client.Ping(context.TODO(), readpref.Primary()) == nil) {
|
if(client != nil && client.Ping(context.TODO(), readpref.Primary()) == nil) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
Log("(Re) Connecting to the database...")
|
Log("(Re) Connecting to the database...")
|
||||||
|
|
||||||
|
if mongoURL == "" {
|
||||||
|
return errors.New("MongoDB URL is not set, cannot connect to the database.")
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
client, err = mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
|
opts := options.Client().ApplyURI(mongoURL).SetRetryWrites(true).SetWriteConcern(writeconcern.New(writeconcern.WMajority()))
|
||||||
|
|
||||||
|
client, err = mongo.Connect(context.TODO(), opts)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue