[release] v0.8.0-unstable
This commit is contained in:
parent
6f1d395df3
commit
5288f0d954
|
@ -1,3 +1,7 @@
|
||||||
|
## Version 0.8.0
|
||||||
|
- Stop showing Docker not connected when first loading status
|
||||||
|
- Add privacy settings to external links
|
||||||
|
|
||||||
## Version 0.7.1 -> 0.7.10
|
## Version 0.7.1 -> 0.7.10
|
||||||
- Fix issue where multiple DBs get created at the setup
|
- Fix issue where multiple DBs get created at the setup
|
||||||
- Add more special characters to be used for password validation
|
- Add more special characters to be used for password validation
|
||||||
|
|
|
@ -4,23 +4,68 @@ import * as React from 'react';
|
||||||
import ThemeCustomization from './themes';
|
import ThemeCustomization from './themes';
|
||||||
import ScrollTop from './components/ScrollTop';
|
import ScrollTop from './components/ScrollTop';
|
||||||
import Snackbar from '@mui/material/Snackbar';
|
import Snackbar from '@mui/material/Snackbar';
|
||||||
import {Alert} from '@mui/material';
|
import {Alert, Box} from '@mui/material';
|
||||||
|
import logo from './assets/images/icons/cosmos.png';
|
||||||
|
|
||||||
|
import * as API from './api';
|
||||||
|
|
||||||
import { setSnackit } from './api/wrap';
|
import { setSnackit } from './api/wrap';
|
||||||
|
|
||||||
// ==============================|| APP - THEME, ROUTER, LOCAL ||============================== //
|
// ==============================|| APP - THEME, ROUTER, LOCAL ||============================== //
|
||||||
|
|
||||||
|
const LoadingAnimation = () => (
|
||||||
|
<div className="loader">
|
||||||
|
<div className="dot"></div>
|
||||||
|
<div className="dot"></div>
|
||||||
|
<div className="dot"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export let SetPrimaryColor = () => {};
|
||||||
|
export let SetSecondaryColor = () => {};
|
||||||
|
export let GlobalPrimaryColor = '';
|
||||||
|
export let GlobalSecondaryColor = '';
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [message, setMessage] = React.useState('');
|
const [message, setMessage] = React.useState('');
|
||||||
const [severity, setSeverity] = React.useState('error');
|
const [severity, setSeverity] = React.useState('error');
|
||||||
|
const [statusLoaded, setStatusLoaded] = React.useState(false);
|
||||||
|
const [PrimaryColor, setPrimaryColor] = React.useState(API.PRIMARY_COLOR);
|
||||||
|
const [SecondaryColor, setSecondaryColor] = React.useState(API.SECONDARY_COLOR);
|
||||||
|
|
||||||
|
SetPrimaryColor = (color) => {
|
||||||
|
setPrimaryColor(color);
|
||||||
|
GlobalPrimaryColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetSecondaryColor = (color) => {
|
||||||
|
setSecondaryColor(color);
|
||||||
|
GlobalSecondaryColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
API.getStatus(true).then((r) => {
|
||||||
|
if(r) {
|
||||||
|
setStatusLoaded(true);
|
||||||
|
}
|
||||||
|
setPrimaryColor(API.PRIMARY_COLOR);
|
||||||
|
setSecondaryColor(API.SECONDARY_COLOR);
|
||||||
|
}).catch(() => {
|
||||||
|
setStatusLoaded(true);
|
||||||
|
setPrimaryColor(API.PRIMARY_COLOR);
|
||||||
|
setSecondaryColor(API.SECONDARY_COLOR);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
setSnackit((message, severity='error') => {
|
setSnackit((message, severity='error') => {
|
||||||
setMessage(message);
|
setMessage(message);
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
setSeverity(severity);
|
setSeverity(severity);
|
||||||
})
|
})
|
||||||
return (
|
|
||||||
<ThemeCustomization>
|
return statusLoaded ?
|
||||||
|
<ThemeCustomization PrimaryColor={PrimaryColor} SecondaryColor={SecondaryColor}>
|
||||||
<Snackbar
|
<Snackbar
|
||||||
open={open}
|
open={open}
|
||||||
autoHideDuration={5000}
|
autoHideDuration={5000}
|
||||||
|
@ -35,7 +80,14 @@ const App = () => {
|
||||||
<Routes />
|
<Routes />
|
||||||
</ScrollTop>
|
</ScrollTop>
|
||||||
</ThemeCustomization>
|
</ThemeCustomization>
|
||||||
)
|
|
||||||
|
: <div>
|
||||||
|
<Box sx={{ position: 'fixed', top: 0, bottom: 0, left: 0, right: 0, display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
|
||||||
|
{/* <img src={logo} style={{ display:'inline', height: '200px'}} className='pulsing' /> */}
|
||||||
|
<LoadingAnimation />
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
|
@ -61,3 +61,13 @@ export const checkHost = (host) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const uploadBackground = (file) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
"status": "ok",
|
||||||
|
"data": ""
|
||||||
|
})}, 100 );
|
||||||
|
});
|
||||||
|
}
|
|
@ -16,17 +16,52 @@ import wrap from './wrap';
|
||||||
export let CPU_ARCH = 'amd64';
|
export let CPU_ARCH = 'amd64';
|
||||||
export let CPU_AVX = true;
|
export let CPU_AVX = true;
|
||||||
|
|
||||||
let getStatus = () => {
|
export let HOME_BACKGROUND;
|
||||||
|
export let PRIMARY_COLOR;
|
||||||
|
export let SECONDARY_COLOR;
|
||||||
|
|
||||||
|
export let FIRST_LOAD = false;
|
||||||
|
|
||||||
|
let getStatus = (initial) => {
|
||||||
return wrap(fetch('/cosmos/api/status', {
|
return wrap(fetch('/cosmos/api/status', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
}))
|
}), initial)
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
CPU_ARCH = response.data.CPU;
|
CPU_ARCH = response.data.CPU;
|
||||||
CPU_AVX = response.data.AVX;
|
CPU_AVX = response.data.AVX;
|
||||||
|
HOME_BACKGROUND = response.data.homepage.Background;
|
||||||
|
PRIMARY_COLOR = response.data.theme.PrimaryColor;
|
||||||
|
SECONDARY_COLOR = response.data.theme.SecondaryColor;
|
||||||
|
FIRST_LOAD = true;
|
||||||
return response
|
return response
|
||||||
|
}).catch((response) => {
|
||||||
|
const urlSearch = encodeURIComponent(window.location.search);
|
||||||
|
const redirectTo = (window.location.pathname + urlSearch);
|
||||||
|
|
||||||
|
if(response.status != 'OK') {
|
||||||
|
if(
|
||||||
|
window.location.href.indexOf('/cosmos-ui/newInstall') == -1 &&
|
||||||
|
window.location.href.indexOf('/cosmos-ui/login') == -1 &&
|
||||||
|
window.location.href.indexOf('/cosmos-ui/loginmfa') == -1 &&
|
||||||
|
window.location.href.indexOf('/cosmos-ui/newmfa') == -1 &&
|
||||||
|
window.location.href.indexOf('/cosmos-ui/register') == -1 &&
|
||||||
|
window.location.href.indexOf('/cosmos-ui/forgot-password') == -1) {
|
||||||
|
if(response.status == 'NEW_INSTALL') {
|
||||||
|
window.location.href = '/cosmos-ui/newInstall';
|
||||||
|
} else if (response.status == 'error' && response.code == "HTTP004") {
|
||||||
|
window.location.href = '/cosmos-ui/login?redirect=' + redirectTo;
|
||||||
|
} else if (response.status == 'error' && response.code == "HTTP006") {
|
||||||
|
window.location.href = '/cosmos-ui/loginmfa?redirect=' + redirectTo;
|
||||||
|
} else if (response.status == 'error' && response.code == "HTTP007") {
|
||||||
|
window.location.href = '/cosmos-ui/newmfa?redirect=' + redirectTo;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "nothing";
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,6 +192,15 @@ let getDNS = (host) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let uploadBackground = (file) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('background', file);
|
||||||
|
return wrap(fetch('/cosmos/api/background', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const isDemo = import.meta.env.MODE === 'demo';
|
const isDemo = import.meta.env.MODE === 'demo';
|
||||||
|
|
||||||
let auth = _auth;
|
let auth = _auth;
|
||||||
|
@ -176,6 +220,7 @@ if(isDemo) {
|
||||||
isOnline = indexDemo.isOnline;
|
isOnline = indexDemo.isOnline;
|
||||||
checkHost = indexDemo.checkHost;
|
checkHost = indexDemo.checkHost;
|
||||||
getDNS = indexDemo.getDNS;
|
getDNS = indexDemo.getDNS;
|
||||||
|
uploadBackground = indexDemo.uploadBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -188,5 +233,6 @@ export {
|
||||||
newInstall,
|
newInstall,
|
||||||
isOnline,
|
isOnline,
|
||||||
checkHost,
|
checkHost,
|
||||||
getDNS
|
getDNS,
|
||||||
|
uploadBackground
|
||||||
};
|
};
|
|
@ -1,20 +1,32 @@
|
||||||
let snackit;
|
let snackit;
|
||||||
|
|
||||||
export default function wrap(apicall) {
|
export default function wrap(apicall, noError = false) {
|
||||||
return apicall.then(async (response) => {
|
return apicall.then(async (response) => {
|
||||||
let rep;
|
let rep;
|
||||||
try {
|
try {
|
||||||
rep = await response.json();
|
rep = await response.json();
|
||||||
} catch {
|
} catch (err) {
|
||||||
|
if (!noError) {
|
||||||
snackit('Server error');
|
snackit('Server error');
|
||||||
throw new Error('Server error');
|
throw new Error('Server error');
|
||||||
|
} else {
|
||||||
|
const e = new Error(rep.message);
|
||||||
|
e.status = rep.status;
|
||||||
|
e.code = rep.code;
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (response.status == 200) {
|
if (response.status == 200) {
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!noError) {
|
||||||
snackit(rep.message);
|
snackit(rep.message);
|
||||||
|
}
|
||||||
|
|
||||||
const e = new Error(rep.message);
|
const e = new Error(rep.message);
|
||||||
e.status = response.status;
|
e.status = rep.status;
|
||||||
e.code = rep.code;
|
e.code = rep.code;
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
|
|
BIN
client/src/assets/images/icons/cosmos_simple_black.png
Normal file
BIN
client/src/assets/images/icons/cosmos_simple_black.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
client/src/assets/images/icons/cosmos_simple_white.png
Normal file
BIN
client/src/assets/images/icons/cosmos_simple_white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
|
@ -2,12 +2,14 @@
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { fontWeight } from '@mui/system';
|
import { fontWeight } from '@mui/system';
|
||||||
|
|
||||||
import logo from '../../assets/images/icons/cosmos.png';
|
import logo from '../../assets/images/icons/cosmos_simple_black.png';
|
||||||
|
import logoDark from '../../assets/images/icons/cosmos_simple_white.png';
|
||||||
|
|
||||||
// ==============================|| LOGO SVG ||============================== //
|
// ==============================|| LOGO SVG ||============================== //
|
||||||
|
|
||||||
const Logo = () => {
|
const Logo = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const isLight = theme.palette.mode === 'light';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
/**
|
/**
|
||||||
|
@ -17,7 +19,7 @@ const Logo = () => {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
<>
|
<>
|
||||||
<img src={logo} alt="Cosmos" width="50" />
|
<img src={isLight ? logo : logoDark} alt="Cosmos" width="40" />
|
||||||
<span style={{fontWeight: 'bold', fontSize: '170%', paddingLeft:'10px'}}> Cosmos</span>
|
<span style={{fontWeight: 'bold', fontSize: '170%', paddingLeft:'10px'}}> Cosmos</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { UploadOutlined } from '@ant-design/icons';
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export default function UploadButtons({OnChange, accept}) {
|
export default function UploadButtons({OnChange, accept, label}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
|
@ -15,7 +15,7 @@ export default function UploadButtons({OnChange, accept}) {
|
||||||
/>
|
/>
|
||||||
<label htmlFor="contained-button-file">
|
<label htmlFor="contained-button-file">
|
||||||
<Button variant="contained" component="span" startIcon={<UploadOutlined />}>
|
<Button variant="contained" component="span" startIcon={<UploadOutlined />}>
|
||||||
Upload
|
{label || 'Upload'}
|
||||||
</Button>
|
</Button>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,6 +21,58 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes pulsing {
|
||||||
|
0% { -webkit-transform: scale(1); }
|
||||||
|
50% { -webkit-transform: scale(1.1); }
|
||||||
|
100% { -webkit-transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 80%, 100% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: scale(1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background-color: #222;
|
||||||
|
border-radius: 100%;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
margin: 0 0.25rem;
|
||||||
|
|
||||||
|
/* Animation */
|
||||||
|
animation: bounce 1.4s infinite;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.dot {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot:nth-child(1) {
|
||||||
|
animation-delay: -0.32s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot:nth-child(2) {
|
||||||
|
animation-delay: -0.16s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot:nth-child(3) {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.shinyButton {
|
.shinyButton {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
@ -88,3 +140,7 @@
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pulsing {
|
||||||
|
animation: pulsing 2s ease-in-out infinite;
|
||||||
|
}
|
|
@ -23,7 +23,9 @@ const NavItem = ({ item, level }) => {
|
||||||
itemTarget = '_blank';
|
itemTarget = '_blank';
|
||||||
}
|
}
|
||||||
|
|
||||||
let listItemProps = { component: forwardRef((props, ref) => <Link ref={ref} {...props} to={item.url} target={itemTarget} />) };
|
let listItemProps = { component: forwardRef((props, ref) => <Link ref={ref} {...props} to={item.url} target={itemTarget}
|
||||||
|
rel={itemTarget === '_blank' ? 'noopener noreferrer' : ''}
|
||||||
|
/>) };
|
||||||
if (item?.external) {
|
if (item?.external) {
|
||||||
listItemProps = { component: 'a', href: item.url, target: itemTarget };
|
listItemProps = { component: 'a', href: item.url, target: itemTarget };
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
Collapse,
|
Collapse,
|
||||||
TextField,
|
TextField,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
Skeleton,
|
||||||
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||||
|
@ -31,11 +32,17 @@ import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOut
|
||||||
import { CosmosCheckbox, CosmosFormDivider, CosmosInputPassword, CosmosInputText, CosmosSelect } from './formShortcuts';
|
import { CosmosCheckbox, CosmosFormDivider, CosmosInputPassword, CosmosInputText, CosmosSelect } from './formShortcuts';
|
||||||
import CountrySelect, { countries } from '../../../components/countrySelect';
|
import CountrySelect, { countries } from '../../../components/countrySelect';
|
||||||
import { DnsChallengeComp } from '../../../utils/dns-challenge-comp';
|
import { DnsChallengeComp } from '../../../utils/dns-challenge-comp';
|
||||||
|
import { values } from 'lodash';
|
||||||
|
import UploadButtons from '../../../components/fileUpload';
|
||||||
|
import { TwitterPicker
|
||||||
|
} from 'react-color';
|
||||||
|
import {SetPrimaryColor, SetSecondaryColor} from '../../../App';
|
||||||
|
|
||||||
|
|
||||||
const ConfigManagement = () => {
|
const ConfigManagement = () => {
|
||||||
const [config, setConfig] = React.useState(null);
|
const [config, setConfig] = React.useState(null);
|
||||||
const [openModal, setOpenModal] = React.useState(false);
|
const [openModal, setOpenModal] = React.useState(false);
|
||||||
|
const [uploadingBackground, setUploadingBackground] = React.useState(false);
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
API.config.get().then((res) => {
|
API.config.get().then((res) => {
|
||||||
|
@ -82,7 +89,11 @@ const ConfigManagement = () => {
|
||||||
Email_UseTLS : config.EmailConfig.UseTLS,
|
Email_UseTLS : config.EmailConfig.UseTLS,
|
||||||
|
|
||||||
SkipPruneNetwork: config.DockerConfig.SkipPruneNetwork,
|
SkipPruneNetwork: config.DockerConfig.SkipPruneNetwork,
|
||||||
DefaultDataPath: config.DockerConfig.DefaultDataPath || "/usr"
|
DefaultDataPath: config.DockerConfig.DefaultDataPath || "/usr",
|
||||||
|
|
||||||
|
Background: config && config.HomepageConfig && config.HomepageConfig.Background,
|
||||||
|
PrimaryColor: config && config.ThemeConfig && config.ThemeConfig.PrimaryColor,
|
||||||
|
SecondaryColor: config && config.ThemeConfig && config.ThemeConfig.SecondaryColor,
|
||||||
}}
|
}}
|
||||||
validationSchema={Yup.object().shape({
|
validationSchema={Yup.object().shape({
|
||||||
Hostname: Yup.string().max(255).required('Hostname is required'),
|
Hostname: Yup.string().max(255).required('Hostname is required'),
|
||||||
|
@ -124,7 +135,16 @@ const ConfigManagement = () => {
|
||||||
...config.DockerConfig,
|
...config.DockerConfig,
|
||||||
SkipPruneNetwork: values.SkipPruneNetwork,
|
SkipPruneNetwork: values.SkipPruneNetwork,
|
||||||
DefaultDataPath: values.DefaultDataPath
|
DefaultDataPath: values.DefaultDataPath
|
||||||
}
|
},
|
||||||
|
HomepageConfig: {
|
||||||
|
...config.HomepageConfig,
|
||||||
|
Background: values.Background
|
||||||
|
},
|
||||||
|
ThemeConfig: {
|
||||||
|
...config.ThemeConfig,
|
||||||
|
PrimaryColor: values.PrimaryColor,
|
||||||
|
SecondaryColor: values.SecondaryColor
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
API.config.set(toSave).then((data) => {
|
API.config.set(toSave).then((data) => {
|
||||||
|
@ -153,6 +173,29 @@ const ConfigManagement = () => {
|
||||||
{(formik) => (
|
{(formik) => (
|
||||||
<form noValidate onSubmit={formik.handleSubmit}>
|
<form noValidate onSubmit={formik.handleSubmit}>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
|
<MainCard>
|
||||||
|
{formik.errors.submit && (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<FormHelperText error>{formik.errors.submit}</FormHelperText>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<AnimateButton>
|
||||||
|
<Button
|
||||||
|
disableElevation
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
fullWidth
|
||||||
|
size="large"
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</AnimateButton>
|
||||||
|
</Grid>
|
||||||
|
</MainCard>
|
||||||
|
|
||||||
<MainCard title="General">
|
<MainCard title="General">
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
|
@ -232,6 +275,64 @@ const ConfigManagement = () => {
|
||||||
</Grid>
|
</Grid>
|
||||||
</MainCard>
|
</MainCard>
|
||||||
|
|
||||||
|
<MainCard title="Appearance">
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
{!uploadingBackground && formik.values.Background && <img src=
|
||||||
|
{formik.values.Background} alt="preview seems broken. Please re-upload."
|
||||||
|
width={285} />}
|
||||||
|
{uploadingBackground && <Skeleton variant="rectangular" width={285} height={140} />}
|
||||||
|
<Stack spacing={1} direction="row">
|
||||||
|
<UploadButtons
|
||||||
|
accept='.jpg, .png, .gif, .jpeg, .webp, .bmp, .avif, .tiff, .svg'
|
||||||
|
label="Upload Wallpaper"
|
||||||
|
OnChange={(e) => {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
API.uploadBackground(file).then((data) => {
|
||||||
|
formik.setFieldValue('Background', "/cosmos/api/background/" + data.data.extension.replace(".", ""));
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => {
|
||||||
|
formik.setFieldValue('Background', "");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset Wallpaper
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<InputLabel style={{marginBottom: '10px'}} htmlFor="PrimaryColor">Primary Color</InputLabel>
|
||||||
|
<TwitterPicker
|
||||||
|
id="PrimaryColor"
|
||||||
|
color={formik.values.PrimaryColor}
|
||||||
|
onChange={color => {
|
||||||
|
let colorRGB = `rgb(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b}, ${color.rgb.a})`
|
||||||
|
formik.setFieldValue('PrimaryColor', colorRGB);
|
||||||
|
SetPrimaryColor(colorRGB);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<InputLabel style={{marginBottom: '10px'}} htmlFor="SecondaryColor">Secondary Color</InputLabel>
|
||||||
|
<TwitterPicker
|
||||||
|
id="SecondaryColor"
|
||||||
|
color={formik.values.SecondaryColor}
|
||||||
|
onChange={color => formik.setFieldValue('SecondaryColor', color.rgb)}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</MainCard>
|
||||||
|
|
||||||
<MainCard title="HTTP">
|
<MainCard title="HTTP">
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useParams } from "react-router";
|
import { useParams } from "react-router";
|
||||||
import Back from "../../components/back";
|
import Back from "../../components/back";
|
||||||
import { Alert, Box, CircularProgress, Grid, Stack, useTheme } from "@mui/material";
|
import { Alert, Box, CircularProgress, Grid, Stack, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import * as API from "../../api";
|
import * as API from "../../api";
|
||||||
import wallpaper from '../../assets/images/wallpaper2.jpg';
|
import wallpaper from '../../assets/images/wallpaper2.jpg';
|
||||||
|
@ -10,19 +10,23 @@ import { getFaviconURL } from "../../utils/routes";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { getFullOrigin } from "../../utils/routes";
|
import { getFullOrigin } from "../../utils/routes";
|
||||||
import IsLoggedIn from "../../isLoggedIn";
|
import IsLoggedIn from "../../isLoggedIn";
|
||||||
|
import { ServAppIcon } from "../../utils/servapp-icon";
|
||||||
|
import Chart from 'react-apexcharts';
|
||||||
|
|
||||||
|
|
||||||
export const HomeBackground = () => {
|
export const HomeBackground = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isDark = theme.palette.mode === 'dark';
|
const isDark = theme.palette.mode === 'dark';
|
||||||
|
const customPaper = API.HOME_BACKGROUND;
|
||||||
return (
|
return (
|
||||||
<Box sx={{ position: 'fixed', float: 'left', overflow: 'hidden', zIndex: 0, top: 0, left: 0, right: 0, bottom: 0,
|
<Box sx={{
|
||||||
|
position: 'fixed', float: 'left', overflow: 'hidden', zIndex: 0, top: 0, left: 0, right: 0, bottom: 0,
|
||||||
// gradient
|
// gradient
|
||||||
// backgroundImage: isDark ?
|
// backgroundImage: isDark ?
|
||||||
// `linear-gradient(#371d53, #26143a)` :
|
// `linear-gradient(#371d53, #26143a)` :
|
||||||
// `linear-gradient(#e6d3fb, #c8b0e2)`,
|
// `linear-gradient(#e6d3fb, #c8b0e2)`,
|
||||||
}}>
|
}}>
|
||||||
<img src={isDark ? wallpaper : wallpaperLight } style={{ display: 'inline' }} alt="" draggable="false" width="100%" height="100%" />
|
<img src={customPaper ? customPaper : (isDark ? wallpaper : wallpaperLight)} style={{ display: 'inline' }} alt="" draggable="false" width="100%" height="100%" />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -36,10 +40,11 @@ export const TransparentHeader = () => {
|
||||||
|
|
||||||
return <style>
|
return <style>
|
||||||
{`header {
|
{`header {
|
||||||
background: rgba(${backColor},0.35) !important;
|
background: rgba(${backColor}, 0.40) !important;
|
||||||
border-bottom-color: rgba(${backColor},0.4) !important;
|
border-bottom-color: rgba(${backColor},0.45) !important;
|
||||||
color: ${textColor} !important;
|
color: ${textColor} !important;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
}
|
}
|
||||||
|
|
||||||
header .MuiChip-label {
|
header .MuiChip-label {
|
||||||
|
@ -52,18 +57,20 @@ export const TransparentHeader = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
.app {
|
.app {
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
transition: background 0.1s ease-in-out;
|
transition: background 0.1s ease-in-out;
|
||||||
transition: transform 0.1s ease-in-out;
|
transition: transform 0.1s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app:hover {
|
.app-hover:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: rgba(${backColor},0.8) !important;
|
background: rgba(${backColor},0.8) !important;
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.MuiAlert-standard {
|
.MuiAlert-standard {
|
||||||
background: rgba(${backColor},0.35) !important;
|
backdrop-filter: blur(15px);
|
||||||
|
background: rgba(${backColor},0.40) !important;
|
||||||
color: ${textColor} !important;
|
color: ${textColor} !important;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
@ -79,6 +86,7 @@ const HomePage = () => {
|
||||||
const [containers, setContainers] = useState(null);
|
const [containers, setContainers] = useState(null);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isDark = theme.palette.mode === 'dark';
|
const isDark = theme.palette.mode === 'dark';
|
||||||
|
const isMd = useMediaQuery(theme.breakpoints.up('md'));
|
||||||
|
|
||||||
const blockStyle = {
|
const blockStyle = {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
|
@ -90,10 +98,10 @@ const HomePage = () => {
|
||||||
|
|
||||||
const appColor = isDark ? {
|
const appColor = isDark ? {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
background: 'rgba(0,0,0,0.35)',
|
background: 'rgba(0,0,0,0.40)',
|
||||||
} : {
|
} : {
|
||||||
color: 'black',
|
color: 'black',
|
||||||
background: 'rgba(255,255,255,0.35)',
|
background: 'rgba(255,255,255,0.40)',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,17 +120,97 @@ const HomePage = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let routes = config && (config.HTTPConfig.ProxyConfig.Routes || []);
|
let routes = config && (config.HTTPConfig.ProxyConfig.Routes || []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refreshConfig();
|
refreshConfig();
|
||||||
refreshStatus();
|
refreshStatus();
|
||||||
|
|
||||||
|
// const interval = setInterval(() => {
|
||||||
|
// refreshStatus();
|
||||||
|
// }, 5000);
|
||||||
|
|
||||||
|
// return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const optionsRadial = {
|
||||||
|
plotOptions: {
|
||||||
|
radialBar: {
|
||||||
|
startAngle: -135,
|
||||||
|
endAngle: 225,
|
||||||
|
hollow: {
|
||||||
|
margin: 0,
|
||||||
|
size: "70%",
|
||||||
|
background: "#fff",
|
||||||
|
image: undefined,
|
||||||
|
imageOffsetX: 0,
|
||||||
|
imageOffsetY: 0,
|
||||||
|
position: "front",
|
||||||
|
dropShadow: {
|
||||||
|
enabled: true,
|
||||||
|
top: 3,
|
||||||
|
left: 0,
|
||||||
|
blur: 4,
|
||||||
|
opacity: 0.24
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
track: {
|
||||||
|
background: "#fff",
|
||||||
|
strokeWidth: "67%",
|
||||||
|
margin: 0, // margin is in pixels
|
||||||
|
dropShadow: {
|
||||||
|
enabled: true,
|
||||||
|
top: -3,
|
||||||
|
left: 0,
|
||||||
|
blur: 4,
|
||||||
|
opacity: 0.35
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
dataLabels: {
|
||||||
|
showOn: "always",
|
||||||
|
name: {
|
||||||
|
show: false,
|
||||||
|
color: "#888",
|
||||||
|
fontSize: "13px"
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
formatter: function (val) {
|
||||||
|
return val + "%";
|
||||||
|
},
|
||||||
|
color: "#111",
|
||||||
|
offsetY: 9,
|
||||||
|
fontSize: "24px",
|
||||||
|
show: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: "gradient",
|
||||||
|
gradient: {
|
||||||
|
shade: "dark",
|
||||||
|
type: "horizontal",
|
||||||
|
shadeIntensity: 0.5,
|
||||||
|
gradientToColors: ["#ABE5A1"],
|
||||||
|
inverseColors: true,
|
||||||
|
opacityFrom: 1,
|
||||||
|
opacityTo: 1,
|
||||||
|
stops: [0, 100]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
lineCap: "round"
|
||||||
|
},
|
||||||
|
labels: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return <Stack spacing={2} >
|
return <Stack spacing={2} >
|
||||||
<IsLoggedIn />
|
<IsLoggedIn />
|
||||||
<HomeBackground />
|
<HomeBackground status={coStatus} />
|
||||||
<TransparentHeader />
|
<TransparentHeader />
|
||||||
<Stack style={{ zIndex: 2 }} spacing={1}>
|
<Stack style={{ zIndex: 2 }} spacing={1}>
|
||||||
{coStatus && !coStatus.database && (
|
{coStatus && !coStatus.database && (
|
||||||
|
@ -166,25 +254,93 @@ const HomePage = () => {
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<Grid2 container spacing={2} style={{ zIndex: 2 }}>
|
<Grid2 container spacing={2} style={{ zIndex: 2 }}>
|
||||||
|
{/* {(!isMd || !coStatus || !coStatus.resources || !coStatus.resources.cpu ) && (<>
|
||||||
|
<Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={3} key={'000'}>
|
||||||
|
<Box className='app' style={{ padding: '20px', height: "107px", borderRadius: 5, ...appColor }}>
|
||||||
|
<Stack style={{flexGrow: 1}} spacing={0}>
|
||||||
|
<div style={{fontSize: '18px', fontWeight: "bold"}}>CPU</div>
|
||||||
|
<div>--</div>
|
||||||
|
<div>--</div>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={3} key={'001'}>
|
||||||
|
<Box className='app' style={{ padding: '20px', height: "107px", borderRadius: 5, ...appColor }}>
|
||||||
|
<Stack style={{flexGrow: 1}} spacing={0}>
|
||||||
|
<div style={{fontSize: '18px', fontWeight: "bold"}}>RAM</div>
|
||||||
|
<div>Available: --GB</div>
|
||||||
|
<div>Used: --GB</div>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Grid2>
|
||||||
|
</>)}
|
||||||
|
|
||||||
|
{isMd && coStatus && coStatus.resources.cpu && (<>
|
||||||
|
<Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={3} key={'000'}>
|
||||||
|
<Box className='app' style={{height: '106px', borderRadius: 5, ...appColor }}>
|
||||||
|
<Stack direction="row" justifyContent={'center'} alignItems={'center'} style={{ height: "100%" }}>
|
||||||
|
<div style={{width: '110px', height: '97px'}}>
|
||||||
|
<Chart
|
||||||
|
options={optionsRadial}
|
||||||
|
series={[parseInt(coStatus.resources.cpu)]}
|
||||||
|
type="radialBar"
|
||||||
|
height={120}
|
||||||
|
width={120}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Stack style={{flexGrow: 1}} spacing={0}>
|
||||||
|
<div style={{fontSize: '18px', fontWeight: "bold"}}>CPU</div>
|
||||||
|
<div>{coStatus.CPU}</div>
|
||||||
|
<div>{coStatus.AVX ? "AVX Supported" : "No AVX Support"}</div>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Grid2>
|
||||||
|
<Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={3} key={'001'}>
|
||||||
|
<Box className='app' style={{height: '106px',borderRadius: 5, ...appColor }}>
|
||||||
|
<Stack direction="row" justifyContent={'center'} alignItems={'center'} style={{ height: "100%" }}>
|
||||||
|
<div style={{width: '110px', height: '97px'}}>
|
||||||
|
<Chart
|
||||||
|
options={optionsRadial}
|
||||||
|
series={[parseInt(
|
||||||
|
coStatus.resources.ram / (coStatus.resources.ram + coStatus.resources.ramFree) * 100
|
||||||
|
)]}
|
||||||
|
type="radialBar"
|
||||||
|
height={120}
|
||||||
|
width={120}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Stack style={{flexGrow: 1}} spacing={0}>
|
||||||
|
<div style={{fontSize: '18px', fontWeight: "bold"}}>RAM</div>
|
||||||
|
<div>Available: {Math.ceil((coStatus.resources.ram + coStatus.resources.ramFree) / 1024 / 1024 / 1024)}GB</div>
|
||||||
|
<div>Used: {Math.ceil(coStatus.resources.ram / 1024 / 1024 / 1024)}GB</div>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Grid2>
|
||||||
|
|
||||||
|
</>)} */}
|
||||||
|
|
||||||
{config && servApps && routes.map((route) => {
|
{config && servApps && routes.map((route) => {
|
||||||
let skip = route.Mode == "REDIRECT";
|
let skip = route.Mode == "REDIRECT";
|
||||||
if(route.Mode == "SERVAPP") {
|
let containerName;
|
||||||
const containerName = route.Target.split(':')[1].slice(2);
|
let container;
|
||||||
const container = servApps.find((c) => c.Names.includes('/' + containerName));
|
if (route.Mode == "SERVAPP") {
|
||||||
if(!container || container.State != "running") {
|
containerName = route.Target.split(':')[1].slice(2);
|
||||||
|
container = servApps.find((c) => c.Names.includes('/' + containerName));
|
||||||
|
if (!container || container.State != "running") {
|
||||||
skip = true
|
skip = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return !skip && <Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={2} key={route.Name}>
|
return !skip && <Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={3} key={route.Name}>
|
||||||
<Box className='app' style={{ padding: 10, borderRadius: 5, ...appColor }}>
|
<Box className='app app-hover' style={{ padding: 18, borderRadius: 5, ...appColor }}>
|
||||||
<Link to={getFullOrigin(route)} target="_blank" style={{ textDecoration: 'none', ...appColor }}>
|
<Link to={getFullOrigin(route)} target="_blank" style={{ textDecoration: 'none', ...appColor }}>
|
||||||
<Stack direction="row" spacing={2} alignItems="center">
|
<Stack direction="row" spacing={2} alignItems="center">
|
||||||
<img className="loading-image" alt="" src={getFaviconURL(route)} width="64px" height="64px"/>
|
<ServAppIcon container={container} route={route} className="loading-image" width="70px" />
|
||||||
|
<div style={{ minWidth: 0 }}>
|
||||||
<div style={{minWidth: 0 }}>
|
|
||||||
<h3 style={blockStyle}>{route.Name}</h3>
|
<h3 style={blockStyle}>{route.Name}</h3>
|
||||||
<p style={blockStyle}>{route.Description}</p>
|
<p style={blockStyle}>{route.Description}</p>
|
||||||
<p style={{...blockStyle, fontSize: '90%', paddingTop: '3px', opacity: '0.45'}}>{route.Target}</p>
|
<p style={{ ...blockStyle, fontSize: '90%', paddingTop: '3px', opacity: '0.45' }}>{route.Target}</p>
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -89,6 +89,8 @@ const NewInstall = () => {
|
||||||
component: <div>
|
component: <div>
|
||||||
First of all, thanks a lot for trying out Cosmos! And Welcome to the setup wizard.
|
First of all, thanks a lot for trying out Cosmos! And Welcome to the setup wizard.
|
||||||
This wizard will guide you through the setup of Cosmos. It will take about 2-3 minutes and you will be ready to go.
|
This wizard will guide you through the setup of Cosmos. It will take about 2-3 minutes and you will be ready to go.
|
||||||
|
<br /><br />
|
||||||
|
<a target='_blank' rel="noopener noreferrer" href="https://cosmos-cloud.io/doc/2%20setup">📄 Don't hesitate to rely on the documentation to guide you!</a>
|
||||||
</div>,
|
</div>,
|
||||||
nextButtonLabel: () => {
|
nextButtonLabel: () => {
|
||||||
return 'Start';
|
return 'Start';
|
||||||
|
@ -100,7 +102,7 @@ const NewInstall = () => {
|
||||||
<div>
|
<div>
|
||||||
<QuestionCircleOutlined /> Cosmos is using docker to run applications. It is optional, but Cosmos will run in reverse-proxy-only mode if it cannot connect to Docker.
|
<QuestionCircleOutlined /> Cosmos is using docker to run applications. It is optional, but Cosmos will run in reverse-proxy-only mode if it cannot connect to Docker.
|
||||||
</div>
|
</div>
|
||||||
{(status && status.docker) ?
|
{status && (status.docker ?
|
||||||
<Alert severity="success">
|
<Alert severity="success">
|
||||||
Docker is installed and running.
|
Docker is installed and running.
|
||||||
</Alert> :
|
</Alert> :
|
||||||
|
@ -108,8 +110,7 @@ const NewInstall = () => {
|
||||||
Docker is not connected! Please check your docker connection.<br/>
|
Docker is not connected! Please check your docker connection.<br/>
|
||||||
Did you forget to add <pre>-v /var/run/docker.sock:/var/run/docker.sock</pre> to your docker run command?<br />
|
Did you forget to add <pre>-v /var/run/docker.sock:/var/run/docker.sock</pre> to your docker run command?<br />
|
||||||
if your docker daemon is running somewhere else, please add <pre>-e DOCKER_HOST=...</pre> to your docker run command.
|
if your docker daemon is running somewhere else, please add <pre>-e DOCKER_HOST=...</pre> to your docker run command.
|
||||||
</Alert>
|
</Alert>)}
|
||||||
}
|
|
||||||
{(status && status.docker) ? (
|
{(status && status.docker) ? (
|
||||||
<div>
|
<div>
|
||||||
<center>
|
<center>
|
||||||
|
@ -350,7 +351,7 @@ const NewInstall = () => {
|
||||||
<Alert severity="info">
|
<Alert severity="info">
|
||||||
You have enabled the DNS challenge. Make sure you have set the environment variables for your DNS provider.
|
You have enabled the DNS challenge. Make sure you have set the environment variables for your DNS provider.
|
||||||
You can enable it now, but make sure you have set up your API tokens accordingly before attempting to access
|
You can enable it now, but make sure you have set up your API tokens accordingly before attempting to access
|
||||||
Cosmos after this installer. See doc here: <a target="_blank" href="https://go-acme.github.io/lego/dns/">https://go-acme.github.io/lego/dns/</a>
|
Cosmos after this installer. See doc here: <a target="_blank" rel="noopener noreferrer" href="https://go-acme.github.io/lego/dns/">https://go-acme.github.io/lego/dns/</a>
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<DnsChallengeComp
|
<DnsChallengeComp
|
||||||
|
@ -536,7 +537,7 @@ const NewInstall = () => {
|
||||||
Well done! You have successfully installed Cosmos. You can now login to your server using the admin account you created.
|
Well done! You have successfully installed Cosmos. You can now login to your server using the admin account you created.
|
||||||
If you have changed the hostname, don't forget to use that URL to access your server after the restart.
|
If you have changed the hostname, don't forget to use that URL to access your server after the restart.
|
||||||
If you have are running into issues, check the logs for any error messages and edit the file in the /config folder.
|
If you have are running into issues, check the logs for any error messages and edit the file in the /config folder.
|
||||||
If you still don't manage, please join our <a target="_blank" href="https://discord.gg/PwMWwsrwHA">Discord server</a> and we'll be happy to help!
|
If you still don't manage, please join our <a target="_blank" rel="noopener noreferrer" href="https://discord.gg/PwMWwsrwHA">Discord server</a> and we'll be happy to help!
|
||||||
</div>,
|
</div>,
|
||||||
nextButtonLabel: () => {
|
nextButtonLabel: () => {
|
||||||
return 'Apply and Restart';
|
return 'Apply and Restart';
|
||||||
|
|
|
@ -19,6 +19,7 @@ import GetActions from './actionBar';
|
||||||
import ResponsiveButton from '../../components/responseiveButton';
|
import ResponsiveButton from '../../components/responseiveButton';
|
||||||
import DockerComposeImport from './containers/docker-compose';
|
import DockerComposeImport from './containers/docker-compose';
|
||||||
import { ContainerNetworkWarning } from '../../components/containers';
|
import { ContainerNetworkWarning } from '../../components/containers';
|
||||||
|
import { ServAppIcon } from '../../utils/servapp-icon';
|
||||||
|
|
||||||
const Item = styled(Paper)(({ theme }) => ({
|
const Item = styled(Paper)(({ theme }) => ({
|
||||||
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
|
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
|
||||||
|
@ -108,6 +109,13 @@ const ServApps = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getFirstRoute = (app) => {
|
||||||
|
let routes = getContainersRoutes(config, app.Names[0].replace('/', ''));
|
||||||
|
if(routes.length > 0) {
|
||||||
|
return routes[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getFirstRouteFavIcon = (app) => {
|
const getFirstRouteFavIcon = (app) => {
|
||||||
let routes = getContainersRoutes(config, app.Names[0].replace('/', ''));
|
let routes = getContainersRoutes(config, app.Names[0].replace('/', ''));
|
||||||
if(routes.length > 0) {
|
if(routes.length > 0) {
|
||||||
|
@ -185,7 +193,7 @@ const ServApps = () => {
|
||||||
}
|
}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack direction="row" spacing={2} alignItems="center">
|
<Stack direction="row" spacing={2} alignItems="center">
|
||||||
<img className="loading-image" alt="" src={getFirstRouteFavIcon(app)} width="40px" />
|
<ServAppIcon container={app} route={getFirstRoute(app)} className="loading-image" width="40px"/>
|
||||||
<Stack direction="column" spacing={0} alignItems="flex-start" style={{height: '40px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'no-wrap'}}>
|
<Stack direction="column" spacing={0} alignItems="flex-start" style={{height: '40px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'no-wrap'}}>
|
||||||
<Typography variant="h5" color="text.secondary">
|
<Typography variant="h5" color="text.secondary">
|
||||||
{app.Names[0].replace('/', '')}
|
{app.Names[0].replace('/', '')}
|
||||||
|
|
|
@ -13,11 +13,10 @@ import componentsOverride from './overrides';
|
||||||
|
|
||||||
// ==============================|| DEFAULT THEME - MAIN ||============================== //
|
// ==============================|| DEFAULT THEME - MAIN ||============================== //
|
||||||
|
|
||||||
export default function ThemeCustomization({ children }) {
|
export default function ThemeCustomization({ children, PrimaryColor, SecondaryColor }) {
|
||||||
const theme = Palette(
|
const theme = Palette(
|
||||||
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ?
|
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ?
|
||||||
'dark' : 'light');
|
'dark' : 'light', PrimaryColor, SecondaryColor);
|
||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const themeTypography = Typography(`'Public Sans', sans-serif`);
|
const themeTypography = Typography(`'Public Sans', sans-serif`);
|
||||||
|
|
|
@ -7,9 +7,11 @@ import { presetPalettes } from '@ant-design/colors';
|
||||||
// project import
|
// project import
|
||||||
import ThemeOption from './theme';
|
import ThemeOption from './theme';
|
||||||
|
|
||||||
|
import * as API from '../api';
|
||||||
|
|
||||||
// ==============================|| DEFAULT THEME - PALETTE ||============================== //
|
// ==============================|| DEFAULT THEME - PALETTE ||============================== //
|
||||||
|
|
||||||
const Palette = (mode) => {
|
const Palette = (mode, PrimaryColor, SecondaryColor) => {
|
||||||
const colors = presetPalettes;
|
const colors = presetPalettes;
|
||||||
|
|
||||||
const greyPrimary = [
|
const greyPrimary = [
|
||||||
|
@ -32,6 +34,13 @@ const Palette = (mode) => {
|
||||||
|
|
||||||
const paletteColor = ThemeOption(colors, mode === 'dark');
|
const paletteColor = ThemeOption(colors, mode === 'dark');
|
||||||
|
|
||||||
|
if(PrimaryColor) {
|
||||||
|
paletteColor.primary.main = PrimaryColor;
|
||||||
|
}
|
||||||
|
if(SecondaryColor) {
|
||||||
|
paletteColor.secondary.main = SecondaryColor;
|
||||||
|
}
|
||||||
|
|
||||||
return createTheme(mode === 'dark' ? {
|
return createTheme(mode === 'dark' ? {
|
||||||
palette: {
|
palette: {
|
||||||
mode,
|
mode,
|
||||||
|
|
|
@ -62,7 +62,7 @@ export const DnsChallengeComp = ({ name, configName, style, multiline, type, pla
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Alert severity="info">
|
<Alert severity="info">
|
||||||
Please be careful you are filling the correct values. Check the doc if unsure. Leave blank unused variables. <br />
|
Please be careful you are filling the correct values. Check the doc if unsure. Leave blank unused variables. <br />
|
||||||
Doc link: <a href={dnsConfig[formik.values[name]].url} target="_blank">{dnsConfig[formik.values[name]].url}</a>
|
Doc link: <a href={dnsConfig[formik.values[name]].url} rel="noopener noreferrer" target="_blank">{dnsConfig[formik.values[name]].url}</a>
|
||||||
</Alert>
|
</Alert>
|
||||||
<div className="raw-table">
|
<div className="raw-table">
|
||||||
<div dangerouslySetInnerHTML={{__html: dnsConfig[formik.values[name]].docs}}></div>
|
<div dangerouslySetInnerHTML={{__html: dnsConfig[formik.values[name]].docs}}></div>
|
||||||
|
|
7
client/src/utils/servapp-icon.jsx
Normal file
7
client/src/utils/servapp-icon.jsx
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { getFaviconURL } from "./routes";
|
||||||
|
|
||||||
|
export const ServAppIcon = ({route, container, ...pprops}) => {
|
||||||
|
return (container && container.Labels["cosmos-icon"]) ?
|
||||||
|
<img src={container.Labels["cosmos-icon"]} {...pprops}></img>
|
||||||
|
: <img src={getFaviconURL(route)} {...pprops}></img>
|
||||||
|
};
|
8
go.mod
8
go.mod
|
@ -20,11 +20,11 @@ require (
|
||||||
github.com/oschwald/geoip2-golang v1.8.0
|
github.com/oschwald/geoip2-golang v1.8.0
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/roberthodgen/spa-server v0.0.0-20171007154335-bb87b4ff3253
|
github.com/roberthodgen/spa-server v0.0.0-20171007154335-bb87b4ff3253
|
||||||
github.com/shirou/gopsutil/v3 v3.23.3
|
github.com/shirou/gopsutil/v3 v3.23.5
|
||||||
go.deanishe.net/favicon v0.1.0
|
go.deanishe.net/favicon v0.1.0
|
||||||
go.mongodb.org/mongo-driver v1.11.3
|
go.mongodb.org/mongo-driver v1.11.3
|
||||||
golang.org/x/crypto v0.7.0
|
golang.org/x/crypto v0.7.0
|
||||||
golang.org/x/sys v0.6.0
|
golang.org/x/sys v0.8.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -144,7 +144,7 @@ require (
|
||||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/stretchr/objx v0.5.0 // indirect
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
github.com/stretchr/testify v1.8.2 // indirect
|
github.com/stretchr/testify v1.8.3 // indirect
|
||||||
github.com/subosito/gotenv v1.2.0 // indirect
|
github.com/subosito/gotenv v1.2.0 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||||
|
@ -154,7 +154,7 @@ require (
|
||||||
github.com/xdg-go/scram v1.1.1 // indirect
|
github.com/xdg-go/scram v1.1.1 // indirect
|
||||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
go.opencensus.io v0.22.5 // indirect
|
go.opencensus.io v0.22.5 // indirect
|
||||||
go.uber.org/ratelimit v0.1.0 // indirect
|
go.uber.org/ratelimit v0.1.0 // indirect
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -1154,8 +1154,12 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
github.com/shirou/gopsutil/v3 v3.23.3 h1:Syt5vVZXUDXPEXpIBt5ziWsJ4LdSAAxF4l/xZeQgSEE=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
github.com/shirou/gopsutil/v3 v3.23.3/go.mod h1:lSBNN6t3+D6W5e5nXTxc8KIMMVxAcS+6IJlffjRRlMU=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.23.5 h1:5SgDCeQ0KW0S4N0znjeM/eFHXXOKyv2dVNgRq/c9P6Y=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.23.5/go.mod h1:Ng3Maa27Q2KARVJ0SPZF5NdrQSC3XHKP8IIWrHgMeLY=
|
||||||
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
github.com/shoenig/go-m1cpu v0.1.4/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||||
|
@ -1238,6 +1242,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||||
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
|
@ -1302,6 +1308,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
go.deanishe.net/favicon v0.1.0 h1:Afy941gjRik+DjUUcYHUxcztFEeFse2ITBkMMOlgefM=
|
go.deanishe.net/favicon v0.1.0 h1:Afy941gjRik+DjUUcYHUxcztFEeFse2ITBkMMOlgefM=
|
||||||
|
@ -1602,6 +1610,8 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
48
package-lock.json
generated
48
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "cosmos-server",
|
"name": "cosmos-server",
|
||||||
"version": "0.7.0-unstable4",
|
"version": "0.8.0-unstable",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cosmos-server",
|
"name": "cosmos-server",
|
||||||
"version": "0.7.0-unstable4",
|
"version": "0.8.0-unstable",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/colors": "^6.0.0",
|
"@ant-design/colors": "^6.0.0",
|
||||||
"@ant-design/icons": "^4.7.0",
|
"@ant-design/icons": "^4.7.0",
|
||||||
|
@ -34,6 +34,7 @@
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-apexcharts": "^1.4.0",
|
"react-apexcharts": "^1.4.0",
|
||||||
|
"react-color": "^2.19.3",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"react-device-detect": "^2.2.2",
|
"react-device-detect": "^2.2.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
@ -2759,6 +2760,14 @@
|
||||||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@icons/material": {
|
||||||
|
"version": "0.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
|
||||||
|
"integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jamesives/github-sponsors-readme-action": {
|
"node_modules/@jamesives/github-sponsors-readme-action": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jamesives/github-sponsors-readme-action/-/github-sponsors-readme-action-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jamesives/github-sponsors-readme-action/-/github-sponsors-readme-action-1.2.2.tgz",
|
||||||
|
@ -7735,6 +7744,11 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/material-colors": {
|
||||||
|
"version": "1.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
|
||||||
|
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg=="
|
||||||
|
},
|
||||||
"node_modules/memoize-one": {
|
"node_modules/memoize-one": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||||
|
@ -8487,6 +8501,23 @@
|
||||||
"react": ">=0.13"
|
"react": ">=0.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-color": {
|
||||||
|
"version": "2.19.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz",
|
||||||
|
"integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@icons/material": "^0.2.4",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"lodash-es": "^4.17.15",
|
||||||
|
"material-colors": "^1.2.1",
|
||||||
|
"prop-types": "^15.5.10",
|
||||||
|
"reactcss": "^1.2.0",
|
||||||
|
"tinycolor2": "^1.4.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-copy-to-clipboard": {
|
"node_modules/react-copy-to-clipboard": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz",
|
||||||
|
@ -8777,6 +8808,14 @@
|
||||||
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reactcss": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/redent": {
|
"node_modules/redent": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||||
|
@ -9609,6 +9648,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
|
||||||
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/tinycolor2": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="
|
||||||
|
},
|
||||||
"node_modules/titleize": {
|
"node_modules/titleize": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cosmos-server",
|
"name": "cosmos-server",
|
||||||
"version": "0.7.10",
|
"version": "0.8.0-unstable",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "test-server.js",
|
"main": "test-server.js",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -34,6 +34,7 @@
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-apexcharts": "^1.4.0",
|
"react-apexcharts": "^1.4.0",
|
||||||
|
"react-color": "^2.19.3",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"react-device-detect": "^2.2.2",
|
"react-device-detect": "^2.2.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|
112
src/background.go
Normal file
112
src/background.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"io"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UploadBackground(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if utils.AdminOnly(w, req) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.Method == "POST") {
|
||||||
|
// parse the form data
|
||||||
|
err := req.ParseMultipartForm(1 << 20)
|
||||||
|
if err != nil {
|
||||||
|
utils.HTTPError(w, "Error parsing form data", http.StatusInternalServerError, "FORM001")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve the file part of the form
|
||||||
|
file, header, err := req.FormFile("background")
|
||||||
|
if err != nil {
|
||||||
|
utils.HTTPError(w, "Error retrieving file from form data", http.StatusInternalServerError, "FORM002")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// get the file extension
|
||||||
|
ext := filepath.Ext(header.Filename)
|
||||||
|
|
||||||
|
// create a new file in the config directory
|
||||||
|
dst, err := os.Create("/config/background" + ext)
|
||||||
|
if err != nil {
|
||||||
|
utils.HTTPError(w, "Error creating destination file", http.StatusInternalServerError, "FILE004")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer dst.Close()
|
||||||
|
|
||||||
|
// copy the uploaded file to the destination file
|
||||||
|
if _, err := io.Copy(dst, file); err != nil {
|
||||||
|
utils.HTTPError(w, "Error writing to destination file", http.StatusInternalServerError, "FILE005")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a response to the client
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"status": "OK",
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"filename": header.Filename,
|
||||||
|
"size": header.Size,
|
||||||
|
"extension": ext,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
utils.Error("UploadBackground: Method not allowed - " + req.Method, nil)
|
||||||
|
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetBackground(w http.ResponseWriter, req *http.Request) {
|
||||||
|
if utils.LoggedInOnly(w, req) != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := mux.Vars(req)
|
||||||
|
ext := vars["ext"]
|
||||||
|
|
||||||
|
validExtensions := map[string]bool{
|
||||||
|
"jpg": true,
|
||||||
|
"jpeg": true,
|
||||||
|
"png": true,
|
||||||
|
"gif": true,
|
||||||
|
"bmp": true,
|
||||||
|
"svg": true,
|
||||||
|
"webp": true,
|
||||||
|
"tiff": true,
|
||||||
|
"avif": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validExtensions[ext] {
|
||||||
|
utils.HTTPError(w, "Invalid file extension", http.StatusBadRequest, "FILE001")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(req.Method == "GET") {
|
||||||
|
// get the background image
|
||||||
|
bg, err := ioutil.ReadFile("/config/background." + ext)
|
||||||
|
if err != nil {
|
||||||
|
utils.HTTPError(w, "Error reading background image", http.StatusInternalServerError, "FILE003")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a response to the client
|
||||||
|
w.Header().Set("Content-Type", "image/" + ext)
|
||||||
|
w.Write(bg)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
utils.Error("GetBackground: Method not allowed - " + req.Method, nil)
|
||||||
|
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
|
@ -176,7 +176,7 @@ func SecureAPI(userRouter *mux.Router, public bool) {
|
||||||
userRouter.Use(utils.MiddlewareTimeout(45 * time.Second))
|
userRouter.Use(utils.MiddlewareTimeout(45 * time.Second))
|
||||||
userRouter.Use(utils.BlockPostWithoutReferer)
|
userRouter.Use(utils.BlockPostWithoutReferer)
|
||||||
userRouter.Use(proxy.BotDetectionMiddleware)
|
userRouter.Use(proxy.BotDetectionMiddleware)
|
||||||
userRouter.Use(httprate.Limit(60, 1*time.Minute,
|
userRouter.Use(httprate.Limit(120, 1*time.Minute,
|
||||||
httprate.WithKeyFuncs(httprate.KeyByIP),
|
httprate.WithKeyFuncs(httprate.KeyByIP),
|
||||||
httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
|
httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||||
utils.Error("Too many requests. Throttling", nil)
|
utils.Error("Too many requests. Throttling", nil)
|
||||||
|
@ -290,6 +290,9 @@ func StartServer() {
|
||||||
|
|
||||||
srapi.HandleFunc("/api/markets", market.MarketGet)
|
srapi.HandleFunc("/api/markets", market.MarketGet)
|
||||||
|
|
||||||
|
srapi.HandleFunc("/api/background", UploadBackground)
|
||||||
|
srapi.HandleFunc("/api/background/{ext}", GetBackground)
|
||||||
|
|
||||||
|
|
||||||
if(!config.HTTPConfig.AcceptAllInsecureHostname) {
|
if(!config.HTTPConfig.AcceptAllInsecureHostname) {
|
||||||
srapi.Use(utils.EnsureHostname)
|
srapi.Use(utils.EnsureHostname)
|
||||||
|
|
|
@ -11,7 +11,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func StatusRoute(w http.ResponseWriter, req *http.Request) {
|
func StatusRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
if !utils.GetMainConfig().NewInstall && (utils.LoggedInOnly(w, req) != nil) {
|
config := utils.GetMainConfig()
|
||||||
|
|
||||||
|
if !config.NewInstall && (utils.LoggedInOnly(w, req) != nil) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +22,7 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
databaseStatus := true
|
databaseStatus := true
|
||||||
|
|
||||||
if(!utils.GetMainConfig().DisableUserManagement) {
|
if(!config.DisableUserManagement) {
|
||||||
err := utils.DB()
|
err := utils.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("Status: Database error", err)
|
utils.Error("Status: Database error", err)
|
||||||
|
@ -40,6 +42,15 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
|
"homepage": config.HomepageConfig,
|
||||||
|
"theme": config.ThemeConfig,
|
||||||
|
"resources": map[string]interface{}{
|
||||||
|
// "ram": utils.GetRAMUsage(),
|
||||||
|
// "ramFree": utils.GetAvailableRAM(),
|
||||||
|
// "cpu": utils.GetCPUUsage(),
|
||||||
|
// "disk": utils.GetDiskUsage(),
|
||||||
|
// "network": utils.GetNetworkUsage(),
|
||||||
|
},
|
||||||
"database": databaseStatus,
|
"database": databaseStatus,
|
||||||
"docker": docker.DockerIsConnected,
|
"docker": docker.DockerIsConnected,
|
||||||
"letsencrypt": utils.GetMainConfig().HTTPConfig.HTTPSCertificateMode == "LETSENCRYPT" && utils.GetMainConfig().HTTPConfig.SSLEmail == "",
|
"letsencrypt": utils.GetMainConfig().HTTPConfig.HTTPSCertificateMode == "LETSENCRYPT" && utils.GetMainConfig().HTTPConfig.SSLEmail == "",
|
||||||
|
|
|
@ -87,6 +87,18 @@ type Config struct {
|
||||||
AutoUpdate bool
|
AutoUpdate bool
|
||||||
OpenIDClients []OpenIDClient
|
OpenIDClients []OpenIDClient
|
||||||
MarketConfig MarketConfig
|
MarketConfig MarketConfig
|
||||||
|
HomepageConfig HomepageConfig
|
||||||
|
ThemeConfig ThemeConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type HomepageConfig struct {
|
||||||
|
Background string
|
||||||
|
Widgets []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThemeConfig struct {
|
||||||
|
PrimaryColor string
|
||||||
|
SecondaryColor string
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
|
|
|
@ -12,9 +12,12 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
"github.com/shirou/gopsutil/v3/mem"
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ConfigLock sync.Mutex
|
var ConfigLock sync.Mutex
|
||||||
|
@ -449,3 +452,78 @@ func Max(x, y int) int {
|
||||||
}
|
}
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCPUUsage() ([]float64) {
|
||||||
|
percentages, _ := cpu.Percent(time.Second, false)
|
||||||
|
return percentages
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRAMUsage() (uint64) {
|
||||||
|
v, _ := mem.VirtualMemory()
|
||||||
|
return v.Used
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiskStatus struct {
|
||||||
|
Path string
|
||||||
|
TotalBytes uint64
|
||||||
|
UsedBytes uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDiskUsage() []DiskStatus {
|
||||||
|
partitions, err := disk.Partitions(false)
|
||||||
|
if err != nil {
|
||||||
|
Error("Error getting disk partitions", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var diskStatuses []DiskStatus
|
||||||
|
|
||||||
|
for _, partition := range partitions {
|
||||||
|
usageStat, err := disk.Usage(partition.Mountpoint)
|
||||||
|
if err != nil {
|
||||||
|
Error("Error getting disk usage", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
diskStatus := DiskStatus{
|
||||||
|
Path: partition.Mountpoint,
|
||||||
|
TotalBytes: usageStat.Total,
|
||||||
|
UsedBytes: usageStat.Used,
|
||||||
|
}
|
||||||
|
diskStatuses = append(diskStatuses, diskStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return diskStatuses
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkStatus struct {
|
||||||
|
BytesSent uint64
|
||||||
|
BytesRecv uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNetworkUsage() NetworkStatus {
|
||||||
|
initialStat, err := net.IOCounters(true)
|
||||||
|
if err != nil {
|
||||||
|
Error("Error getting network usage", err)
|
||||||
|
return NetworkStatus{}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
finalStat, err := net.IOCounters(true)
|
||||||
|
if err != nil {
|
||||||
|
Error("Error getting network usage", err)
|
||||||
|
return NetworkStatus{}
|
||||||
|
}
|
||||||
|
|
||||||
|
res := NetworkStatus{
|
||||||
|
BytesSent: 0,
|
||||||
|
BytesRecv: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range initialStat {
|
||||||
|
res.BytesSent += finalStat[i].BytesSent - initialStat[i].BytesSent
|
||||||
|
res.BytesRecv += finalStat[i].BytesRecv - initialStat[i].BytesRecv
|
||||||
|
}
|
||||||
|
|
||||||
|
return NetworkStatus{}
|
||||||
|
}
|
Loading…
Reference in a new issue