[release] v0.10.0-unstable7
This commit is contained in:
parent
ff90bdf51e
commit
735874006c
|
@ -19,7 +19,37 @@ function addDevice(device) {
|
|||
}))
|
||||
}
|
||||
|
||||
function restart() {
|
||||
return wrap(fetch('/cosmos/api/constellation/restart', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
return wrap(fetch('/cosmos/api/constellation/config', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
function getLogs() {
|
||||
return wrap(fetch('/cosmos/api/constellation/logs', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
export {
|
||||
list,
|
||||
addDevice,
|
||||
restart,
|
||||
getConfig,
|
||||
getLogs,
|
||||
};
|
94
client/src/components/apiModal.jsx
Normal file
94
client/src/components/apiModal.jsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
// material-ui
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import { Button } 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 * as React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const preStyle = {
|
||||
backgroundColor: '#000',
|
||||
color: '#fff',
|
||||
padding: '10px',
|
||||
borderRadius: '5px',
|
||||
overflow: 'auto',
|
||||
maxHeight: '500px',
|
||||
maxWidth: '100%',
|
||||
width: '100%',
|
||||
margin: '0',
|
||||
position: 'relative',
|
||||
fontSize: '12px',
|
||||
fontFamily: 'monospace',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordWrap: 'break-word',
|
||||
wordBreak: 'break-all',
|
||||
lineHeight: '1.5',
|
||||
boxShadow: '0 0 10px rgba(0,0,0,0.5)',
|
||||
border: '1px solid rgba(255,255,255,0.1)',
|
||||
boxSizing: 'border-box',
|
||||
marginBottom: '10px',
|
||||
marginTop: '10px',
|
||||
marginLeft: '0',
|
||||
marginRight: '0',
|
||||
display: 'block',
|
||||
textAlign: 'left',
|
||||
verticalAlign: 'baseline',
|
||||
opacity: '1',
|
||||
}
|
||||
|
||||
const ApiModal = ({ callback, label }) => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [content, setContent] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const getContent = async () => {
|
||||
setLoading(true);
|
||||
let content = await callback();
|
||||
setContent(content.data);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (openModal)
|
||||
getContent();
|
||||
}, [openModal]);
|
||||
|
||||
return <>
|
||||
<Dialog open={openModal} onClose={() => setOpenModal(false)}>
|
||||
<DialogTitle>Refresh Page</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
<pre style={preStyle}>
|
||||
{content}
|
||||
</pre>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<LoadingButton
|
||||
loading={loading}
|
||||
onClick={() => {
|
||||
getContent();
|
||||
}}>Refresh</LoadingButton>
|
||||
<Button onClick={() => {
|
||||
setOpenModal(false);
|
||||
}}>Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Button
|
||||
disableElevation
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setOpenModal(true);
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
</>
|
||||
};
|
||||
|
||||
export default ApiModal;
|
|
@ -11,6 +11,7 @@ import { CosmosCheckbox, CosmosFormDivider } from "../config/users/formShortcuts
|
|||
import MainCard from "../../components/MainCard";
|
||||
import { Formik } from "formik";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import ApiModal from "../../components/apiModal";
|
||||
|
||||
export const ConstellationIndex = () => {
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
@ -48,28 +49,45 @@ export const ConstellationIndex = () => {
|
|||
return <>
|
||||
<IsLoggedIn />
|
||||
{(devices && config && users) ? <>
|
||||
<Stack spacing={2}>
|
||||
<Stack spacing={2} style={{maxWidth: "1000px"}}>
|
||||
<div>
|
||||
<MainCard title={"Constellation Setup"} content={config.constellationIP}>
|
||||
<Stack spacing={2}>
|
||||
<Formik
|
||||
initialValues={{
|
||||
Enabled: config.ConstellationConfig.Enabled,
|
||||
IsRelay: config.ConstellationConfig.NebulaConfig.Relay.AMRelay,
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
let newConfig = { ...config };
|
||||
newConfig.ConstellationConfig.Enabled = values.Enabled;
|
||||
newConfig.ConstellationConfig.NebulaConfig.Relay.AMRelay = values.IsRelay;
|
||||
return API.config.set(newConfig);
|
||||
}}
|
||||
>
|
||||
{(formik) => (
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<Stack spacing={2}>
|
||||
<Stack spacing={2} direction="row">
|
||||
<Button
|
||||
disableElevation
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={async () => {
|
||||
await API.constellation.restart();
|
||||
}}
|
||||
>
|
||||
Restart Nebula
|
||||
</Button>
|
||||
<ApiModal callback={API.constellation.getLogs} label={"Show Nebula logs"} />
|
||||
<ApiModal callback={API.constellation.getConfig} label={"Render Nebula Config"} />
|
||||
</Stack>
|
||||
<CosmosCheckbox formik={formik} name="Enabled" label="Constellation Enabled" />
|
||||
<CosmosCheckbox formik={formik} name="IsRelay" label="Relay requests via this Node" />
|
||||
|
||||
<LoadingButton
|
||||
disableElevation
|
||||
loading={formik.isSubmitting}
|
||||
fullWidth
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
|
@ -85,39 +103,39 @@ export const ConstellationIndex = () => {
|
|||
</div>
|
||||
<CosmosFormDivider title={"Devices"} />
|
||||
<PrettyTableView
|
||||
data={devices}
|
||||
getKey={(r) => r.deviceName}
|
||||
buttons={[
|
||||
<AddDeviceModal isAdmin={isAdmin} users={users} config={config} refreshConfig={refreshConfig} devices={devices} />
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
title: '',
|
||||
field: getIcon,
|
||||
},
|
||||
{
|
||||
title: 'Device Name',
|
||||
field: (r) => <strong>{r.deviceName}</strong>,
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
field: (r) => <strong>{r.nickname}</strong>,
|
||||
},
|
||||
{
|
||||
title: 'Constellation IP',
|
||||
screenMin: 'md',
|
||||
field: (r) => r.ip,
|
||||
},
|
||||
{
|
||||
data={devices}
|
||||
getKey={(r) => r.deviceName}
|
||||
buttons={[
|
||||
<AddDeviceModal isAdmin={isAdmin} users={users} config={config} refreshConfig={refreshConfig} devices={devices} />
|
||||
]}
|
||||
columns={[
|
||||
{
|
||||
title: '',
|
||||
clickable: true,
|
||||
field: (r) => {
|
||||
return <DeleteButton onDelete={async () => {
|
||||
alert("caca")
|
||||
}}></DeleteButton>
|
||||
}
|
||||
field: getIcon,
|
||||
},
|
||||
{
|
||||
title: 'Device Name',
|
||||
field: (r) => <strong>{r.deviceName}</strong>,
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
field: (r) => <strong>{r.nickname}</strong>,
|
||||
},
|
||||
{
|
||||
title: 'Constellation IP',
|
||||
screenMin: 'md',
|
||||
field: (r) => r.ip,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
clickable: true,
|
||||
field: (r) => {
|
||||
return <DeleteButton onDelete={async () => {
|
||||
alert("caca")
|
||||
}}></DeleteButton>
|
||||
}
|
||||
]}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
</> : <center>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.10.0-unstable6",
|
||||
"version": "0.10.0-unstable7",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
|
|
@ -150,7 +150,7 @@ docker run -d -p 80:80 -p 443:443 --privileged --name cosmos-server -h cosmos-s
|
|||
|
||||
in this command, `-v /:/mnt/host` is optional and allow to manage folders from Cosmos, you can remove it if you don't want it but you will have to create your container's bind folders manually.
|
||||
|
||||
`--privileged` is also optional, but it is required if you use hardening software like AppArmor or SELinux, as they restrict access to the docker socket.
|
||||
`--privileged` is also optional, but it is required to use Constellation. It is also required if you use hardening software like AppArmor or SELinux, as they restrict access to the docker socket.
|
||||
|
||||
Once installed, simply go to `http://your-server-ip` and follow the instructions of the setup wizard.
|
||||
|
||||
|
|
75
src/constellation/api_nebula.go
Normal file
75
src/constellation/api_nebula.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package constellation
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
|
||||
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
func API_GetConfig(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
// read utils.CONFIGFOLDER + "nebula.yml"
|
||||
config, err := ioutil.ReadFile(utils.CONFIGFOLDER + "nebula.yml")
|
||||
|
||||
if err != nil {
|
||||
utils.Error("SettingGet: error while reading nebula.yml", err)
|
||||
utils.HTTPError(w, "Error while reading nebula.yml", http.StatusInternalServerError, "HTTP002")
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": string(config),
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func API_Restart(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
RestartNebula()
|
||||
|
||||
utils.Log("Constellation: nebula restarted")
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func API_GetLogs(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"data": logBuffer.String(),
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingGet: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
|
@ -12,8 +12,12 @@ import (
|
|||
"strings"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"io"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
var logBuffer bytes.Buffer
|
||||
|
||||
var (
|
||||
process *exec.Cmd
|
||||
processMux sync.Mutex
|
||||
|
@ -34,14 +38,16 @@ func startNebulaInBackground() error {
|
|||
return errors.New("nebula is already running")
|
||||
}
|
||||
|
||||
process = exec.Command(binaryToRun(), "-config", utils.CONFIGFOLDER + "nebula.yml")
|
||||
process = exec.Command(binaryToRun(), "-config", utils.CONFIGFOLDER+"nebula.yml")
|
||||
|
||||
process.Stderr = os.Stderr
|
||||
// Set up multi-writer for stderr
|
||||
process.Stderr = io.MultiWriter(&logBuffer, os.Stderr)
|
||||
|
||||
if utils.LoggingLevelLabels[utils.GetMainConfig().LoggingLevel] == utils.DEBUG {
|
||||
process.Stdout = os.Stdout
|
||||
// Set up multi-writer for stdout if in debug mode
|
||||
process.Stdout = io.MultiWriter(&logBuffer, os.Stdout)
|
||||
} else {
|
||||
process.Stdout = nil
|
||||
process.Stdout = io.MultiWriter(&logBuffer)
|
||||
}
|
||||
|
||||
// Start the process in the background
|
||||
|
@ -82,12 +88,19 @@ func ExportConfigToYAML(overwriteConfig utils.ConstellationConfig, outputPath st
|
|||
"192.168.201.0": []string{utils.GetMainConfig().HTTPConfig.Hostname + ":4242"},
|
||||
}
|
||||
|
||||
finalConfig.Relay.AMRelay = overwriteConfig.NebulaConfig.Relay.AMRelay
|
||||
|
||||
// Marshal the combined config to YAML
|
||||
yamlData, err := yaml.Marshal(finalConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete nebula.yml if exists
|
||||
if _, err := os.Stat(outputPath); err == nil {
|
||||
os.Remove(outputPath)
|
||||
}
|
||||
|
||||
// Write YAML data to the specified file
|
||||
yamlFile, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
|
|
|
@ -46,9 +46,11 @@ func InitConfig() {
|
|||
Relay: struct {
|
||||
AMRelay bool `yaml:"am_relay"`
|
||||
UseRelays bool `yaml:"use_relays"`
|
||||
Relays []string `yaml:"relays"`
|
||||
}{
|
||||
AMRelay: true,
|
||||
UseRelays: true,
|
||||
Relays: []string{},
|
||||
},
|
||||
TUN: struct {
|
||||
Disabled bool `yaml:"disabled"`
|
||||
|
|
|
@ -333,6 +333,9 @@ func InitServer() *mux.Router {
|
|||
srapi.HandleFunc("/api/background/{ext}", GetBackground)
|
||||
|
||||
srapi.HandleFunc("/api/constellation/devices", constellation.ConstellationAPIDevices)
|
||||
srapi.HandleFunc("/api/constellation/restart", constellation.API_Restart)
|
||||
srapi.HandleFunc("/api/constellation/config", constellation.API_GetConfig)
|
||||
srapi.HandleFunc("/api/constellation/logs", constellation.API_GetLogs)
|
||||
|
||||
if(!config.HTTPConfig.AcceptAllInsecureHostname) {
|
||||
srapi.Use(utils.EnsureHostname)
|
||||
|
|
|
@ -254,7 +254,7 @@ type NebulaConfig struct {
|
|||
Relay struct {
|
||||
AMRelay bool `yaml:"am_relay"`
|
||||
UseRelays bool `yaml:"use_relays"`
|
||||
relays []string `yaml:"relays"`
|
||||
Relays []string `yaml:"relays"`
|
||||
} `yaml:"relay"`
|
||||
|
||||
TUN struct {
|
||||
|
|
Loading…
Reference in a new issue