[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
|
||||
- Fix issue where multiple DBs get created at the setup
|
||||
- Add more special characters to be used for password validation
|
||||
|
|
|
@ -4,23 +4,68 @@ import * as React from 'react';
|
|||
import ThemeCustomization from './themes';
|
||||
import ScrollTop from './components/ScrollTop';
|
||||
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';
|
||||
|
||||
// ==============================|| 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 [open, setOpen] = React.useState(false);
|
||||
const [message, setMessage] = React.useState('');
|
||||
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') => {
|
||||
setMessage(message);
|
||||
setOpen(true);
|
||||
setSeverity(severity);
|
||||
})
|
||||
return (
|
||||
<ThemeCustomization>
|
||||
|
||||
return statusLoaded ?
|
||||
<ThemeCustomization PrimaryColor={PrimaryColor} SecondaryColor={SecondaryColor}>
|
||||
<Snackbar
|
||||
open={open}
|
||||
autoHideDuration={5000}
|
||||
|
@ -35,7 +80,14 @@ const App = () => {
|
|||
<Routes />
|
||||
</ScrollTop>
|
||||
</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;
|
||||
|
|
|
@ -60,4 +60,14 @@ export const checkHost = (host) => {
|
|||
100
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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_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', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}), initial)
|
||||
.then(async (response) => {
|
||||
CPU_ARCH = response.data.CPU;
|
||||
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
|
||||
}).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';
|
||||
|
||||
let auth = _auth;
|
||||
|
@ -176,6 +220,7 @@ if(isDemo) {
|
|||
isOnline = indexDemo.isOnline;
|
||||
checkHost = indexDemo.checkHost;
|
||||
getDNS = indexDemo.getDNS;
|
||||
uploadBackground = indexDemo.uploadBackground;
|
||||
}
|
||||
|
||||
export {
|
||||
|
@ -188,5 +233,6 @@ export {
|
|||
newInstall,
|
||||
isOnline,
|
||||
checkHost,
|
||||
getDNS
|
||||
getDNS,
|
||||
uploadBackground
|
||||
};
|
|
@ -1,20 +1,32 @@
|
|||
let snackit;
|
||||
|
||||
export default function wrap(apicall) {
|
||||
export default function wrap(apicall, noError = false) {
|
||||
return apicall.then(async (response) => {
|
||||
let rep;
|
||||
try {
|
||||
rep = await response.json();
|
||||
} catch {
|
||||
snackit('Server error');
|
||||
throw new Error('Server error');
|
||||
} catch (err) {
|
||||
if (!noError) {
|
||||
snackit('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) {
|
||||
return rep;
|
||||
}
|
||||
snackit(rep.message);
|
||||
|
||||
if (!noError) {
|
||||
snackit(rep.message);
|
||||
}
|
||||
|
||||
const e = new Error(rep.message);
|
||||
e.status = response.status;
|
||||
e.status = rep.status;
|
||||
e.code = rep.code;
|
||||
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 { 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 ||============================== //
|
||||
|
||||
const Logo = () => {
|
||||
const theme = useTheme();
|
||||
const isLight = theme.palette.mode === 'light';
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { Button } from '@mui/material';
|
||||
import { UploadOutlined } from '@ant-design/icons';
|
||||
|
||||
export default function UploadButtons({OnChange, accept}) {
|
||||
export default function UploadButtons({OnChange, accept, label}) {
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
|
@ -15,7 +15,7 @@ export default function UploadButtons({OnChange, accept}) {
|
|||
/>
|
||||
<label htmlFor="contained-button-file">
|
||||
<Button variant="contained" component="span" startIcon={<UploadOutlined />}>
|
||||
Upload
|
||||
{label || 'Upload'}
|
||||
</Button>
|
||||
</label>
|
||||
</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 {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -87,4 +139,8 @@
|
|||
.raw-table table th, .raw-table table td {
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.pulsing {
|
||||
animation: pulsing 2s ease-in-out infinite;
|
||||
}
|
|
@ -23,7 +23,9 @@ const NavItem = ({ item, level }) => {
|
|||
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) {
|
||||
listItemProps = { component: 'a', href: item.url, target: itemTarget };
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
Collapse,
|
||||
TextField,
|
||||
MenuItem,
|
||||
Skeleton,
|
||||
|
||||
} from '@mui/material';
|
||||
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 CountrySelect, { countries } from '../../../components/countrySelect';
|
||||
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 [config, setConfig] = React.useState(null);
|
||||
const [openModal, setOpenModal] = React.useState(false);
|
||||
const [uploadingBackground, setUploadingBackground] = React.useState(false);
|
||||
|
||||
function refresh() {
|
||||
API.config.get().then((res) => {
|
||||
|
@ -82,7 +89,11 @@ const ConfigManagement = () => {
|
|||
Email_UseTLS : config.EmailConfig.UseTLS,
|
||||
|
||||
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({
|
||||
Hostname: Yup.string().max(255).required('Hostname is required'),
|
||||
|
@ -124,7 +135,16 @@ const ConfigManagement = () => {
|
|||
...config.DockerConfig,
|
||||
SkipPruneNetwork: values.SkipPruneNetwork,
|
||||
DefaultDataPath: values.DefaultDataPath
|
||||
}
|
||||
},
|
||||
HomepageConfig: {
|
||||
...config.HomepageConfig,
|
||||
Background: values.Background
|
||||
},
|
||||
ThemeConfig: {
|
||||
...config.ThemeConfig,
|
||||
PrimaryColor: values.PrimaryColor,
|
||||
SecondaryColor: values.SecondaryColor
|
||||
},
|
||||
}
|
||||
|
||||
API.config.set(toSave).then((data) => {
|
||||
|
@ -153,6 +173,29 @@ const ConfigManagement = () => {
|
|||
{(formik) => (
|
||||
<form noValidate onSubmit={formik.handleSubmit}>
|
||||
<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">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
|
@ -231,6 +274,64 @@ const ConfigManagement = () => {
|
|||
</Grid>
|
||||
</Grid>
|
||||
</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">
|
||||
<Grid container spacing={3}>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useParams } from "react-router";
|
||||
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 * as API from "../../api";
|
||||
import wallpaper from '../../assets/images/wallpaper2.jpg';
|
||||
|
@ -10,19 +10,23 @@ import { getFaviconURL } from "../../utils/routes";
|
|||
import { Link } from "react-router-dom";
|
||||
import { getFullOrigin } from "../../utils/routes";
|
||||
import IsLoggedIn from "../../isLoggedIn";
|
||||
import { ServAppIcon } from "../../utils/servapp-icon";
|
||||
import Chart from 'react-apexcharts';
|
||||
|
||||
|
||||
export const HomeBackground = () => {
|
||||
const theme = useTheme();
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
const customPaper = API.HOME_BACKGROUND;
|
||||
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
|
||||
// backgroundImage: isDark ?
|
||||
// `linear-gradient(#371d53, #26143a)` :
|
||||
// `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>
|
||||
);
|
||||
};
|
||||
|
@ -31,15 +35,16 @@ export const TransparentHeader = () => {
|
|||
const theme = useTheme();
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
|
||||
const backColor = isDark ? '0,0,0' : '255,255,255';
|
||||
const textColor = isDark ? 'white' : 'dark';
|
||||
const backColor = isDark ? '0,0,0' : '255,255,255';
|
||||
const textColor = isDark ? 'white' : 'dark';
|
||||
|
||||
return <style>
|
||||
{`header {
|
||||
background: rgba(${backColor},0.35) !important;
|
||||
border-bottom-color: rgba(${backColor},0.4) !important;
|
||||
background: rgba(${backColor}, 0.40) !important;
|
||||
border-bottom-color: rgba(${backColor},0.45) !important;
|
||||
color: ${textColor} !important;
|
||||
font-weight: bold;
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
|
||||
header .MuiChip-label {
|
||||
|
@ -52,18 +57,20 @@ export const TransparentHeader = () => {
|
|||
}
|
||||
|
||||
.app {
|
||||
backdrop-filter: blur(15px);
|
||||
transition: background 0.1s ease-in-out;
|
||||
transition: transform 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.app:hover {
|
||||
.app-hover:hover {
|
||||
cursor: pointer;
|
||||
background: rgba(${backColor},0.8) !important;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.MuiAlert-standard {
|
||||
background: rgba(${backColor},0.35) !important;
|
||||
backdrop-filter: blur(15px);
|
||||
background: rgba(${backColor},0.40) !important;
|
||||
color: ${textColor} !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -79,6 +86,7 @@ const HomePage = () => {
|
|||
const [containers, setContainers] = useState(null);
|
||||
const theme = useTheme();
|
||||
const isDark = theme.palette.mode === 'dark';
|
||||
const isMd = useMediaQuery(theme.breakpoints.up('md'));
|
||||
|
||||
const blockStyle = {
|
||||
margin: 0,
|
||||
|
@ -88,12 +96,12 @@ const HomePage = () => {
|
|||
verticalAlign: 'middle',
|
||||
}
|
||||
|
||||
const appColor = isDark ? {
|
||||
color: 'white',
|
||||
background: 'rgba(0,0,0,0.35)',
|
||||
const appColor = isDark ? {
|
||||
color: 'white',
|
||||
background: 'rgba(0,0,0,0.40)',
|
||||
} : {
|
||||
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 || []);
|
||||
|
||||
useEffect(() => {
|
||||
refreshConfig();
|
||||
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} >
|
||||
<IsLoggedIn />
|
||||
<HomeBackground />
|
||||
<HomeBackground status={coStatus} />
|
||||
<TransparentHeader />
|
||||
<Stack style={{ zIndex: 2 }} spacing={1}>
|
||||
{coStatus && !coStatus.database && (
|
||||
|
@ -166,25 +254,93 @@ const HomePage = () => {
|
|||
</Stack>
|
||||
|
||||
<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) => {
|
||||
let skip = route.Mode == "REDIRECT";
|
||||
if(route.Mode == "SERVAPP") {
|
||||
const containerName = route.Target.split(':')[1].slice(2);
|
||||
const container = servApps.find((c) => c.Names.includes('/' + containerName));
|
||||
if(!container || container.State != "running") {
|
||||
let containerName;
|
||||
let container;
|
||||
if (route.Mode == "SERVAPP") {
|
||||
containerName = route.Target.split(':')[1].slice(2);
|
||||
container = servApps.find((c) => c.Names.includes('/' + containerName));
|
||||
if (!container || container.State != "running") {
|
||||
skip = true
|
||||
}
|
||||
}
|
||||
return !skip && <Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={2} key={route.Name}>
|
||||
<Box className='app' style={{ padding: 10, borderRadius: 5, ...appColor }}>
|
||||
return !skip && <Grid2 item xs={12} sm={6} md={4} lg={3} xl={3} xxl={3} key={route.Name}>
|
||||
<Box className='app app-hover' style={{ padding: 18, borderRadius: 5, ...appColor }}>
|
||||
<Link to={getFullOrigin(route)} target="_blank" style={{ textDecoration: 'none', ...appColor }}>
|
||||
<Stack direction="row" spacing={2} alignItems="center">
|
||||
<img className="loading-image" alt="" src={getFaviconURL(route)} width="64px" height="64px"/>
|
||||
|
||||
<div style={{minWidth: 0 }}>
|
||||
<ServAppIcon container={container} route={route} className="loading-image" width="70px" />
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<h3 style={blockStyle}>{route.Name}</h3>
|
||||
<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>
|
||||
</Stack>
|
||||
</Link>
|
||||
|
|
|
@ -89,6 +89,8 @@ const NewInstall = () => {
|
|||
component: <div>
|
||||
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.
|
||||
<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>,
|
||||
nextButtonLabel: () => {
|
||||
return 'Start';
|
||||
|
@ -100,16 +102,15 @@ const NewInstall = () => {
|
|||
<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.
|
||||
</div>
|
||||
{(status && status.docker) ?
|
||||
<Alert severity="success">
|
||||
Docker is installed and running.
|
||||
</Alert> :
|
||||
<Alert severity="error">
|
||||
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 />
|
||||
if your docker daemon is running somewhere else, please add <pre>-e DOCKER_HOST=...</pre> to your docker run command.
|
||||
</Alert>
|
||||
}
|
||||
{status && (status.docker ?
|
||||
<Alert severity="success">
|
||||
Docker is installed and running.
|
||||
</Alert> :
|
||||
<Alert severity="error">
|
||||
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 />
|
||||
if your docker daemon is running somewhere else, please add <pre>-e DOCKER_HOST=...</pre> to your docker run command.
|
||||
</Alert>)}
|
||||
{(status && status.docker) ? (
|
||||
<div>
|
||||
<center>
|
||||
|
@ -350,7 +351,7 @@ const NewInstall = () => {
|
|||
<Alert severity="info">
|
||||
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
|
||||
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>
|
||||
)}
|
||||
<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.
|
||||
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 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>,
|
||||
nextButtonLabel: () => {
|
||||
return 'Apply and Restart';
|
||||
|
|
|
@ -19,6 +19,7 @@ import GetActions from './actionBar';
|
|||
import ResponsiveButton from '../../components/responseiveButton';
|
||||
import DockerComposeImport from './containers/docker-compose';
|
||||
import { ContainerNetworkWarning } from '../../components/containers';
|
||||
import { ServAppIcon } from '../../utils/servapp-icon';
|
||||
|
||||
const Item = styled(Paper)(({ theme }) => ({
|
||||
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) => {
|
||||
let routes = getContainersRoutes(config, app.Names[0].replace('/', ''));
|
||||
if(routes.length > 0) {
|
||||
|
@ -185,7 +193,7 @@ const ServApps = () => {
|
|||
}
|
||||
</Typography>
|
||||
<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'}}>
|
||||
<Typography variant="h5" color="text.secondary">
|
||||
{app.Names[0].replace('/', '')}
|
||||
|
|
|
@ -13,11 +13,10 @@ import componentsOverride from './overrides';
|
|||
|
||||
// ==============================|| DEFAULT THEME - MAIN ||============================== //
|
||||
|
||||
export default function ThemeCustomization({ children }) {
|
||||
export default function ThemeCustomization({ children, PrimaryColor, SecondaryColor }) {
|
||||
const theme = Palette(
|
||||
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ?
|
||||
'dark' : 'light');
|
||||
|
||||
'dark' : 'light', PrimaryColor, SecondaryColor);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const themeTypography = Typography(`'Public Sans', sans-serif`);
|
||||
|
|
|
@ -7,9 +7,11 @@ import { presetPalettes } from '@ant-design/colors';
|
|||
// project import
|
||||
import ThemeOption from './theme';
|
||||
|
||||
import * as API from '../api';
|
||||
|
||||
// ==============================|| DEFAULT THEME - PALETTE ||============================== //
|
||||
|
||||
const Palette = (mode) => {
|
||||
const Palette = (mode, PrimaryColor, SecondaryColor) => {
|
||||
const colors = presetPalettes;
|
||||
|
||||
const greyPrimary = [
|
||||
|
@ -32,6 +34,13 @@ const Palette = (mode) => {
|
|||
|
||||
const paletteColor = ThemeOption(colors, mode === 'dark');
|
||||
|
||||
if(PrimaryColor) {
|
||||
paletteColor.primary.main = PrimaryColor;
|
||||
}
|
||||
if(SecondaryColor) {
|
||||
paletteColor.secondary.main = SecondaryColor;
|
||||
}
|
||||
|
||||
return createTheme(mode === 'dark' ? {
|
||||
palette: {
|
||||
mode,
|
||||
|
|
|
@ -62,7 +62,7 @@ export const DnsChallengeComp = ({ name, configName, style, multiline, type, pla
|
|||
<Stack spacing={2}>
|
||||
<Alert severity="info">
|
||||
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>
|
||||
<div className="raw-table">
|
||||
<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/pquerna/otp v1.4.0
|
||||
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.mongodb.org/mongo-driver v1.11.3
|
||||
golang.org/x/crypto v0.7.0
|
||||
golang.org/x/sys v0.6.0
|
||||
golang.org/x/sys v0.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -144,7 +144,7 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // 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/tklauser/go-sysconf v0.3.11 // 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/stringprep v1.0.3 // 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.uber.org/ratelimit v0.1.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/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.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.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
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/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=
|
||||
|
@ -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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
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.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
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/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.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/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
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.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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",
|
||||
"version": "0.7.0-unstable4",
|
||||
"version": "0.8.0-unstable",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cosmos-server",
|
||||
"version": "0.7.0-unstable4",
|
||||
"version": "0.8.0-unstable",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
|
@ -34,6 +34,7 @@
|
|||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
"react-apexcharts": "^1.4.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^18.2.0",
|
||||
|
@ -2759,6 +2760,14 @@
|
|||
"integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
|
||||
"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": {
|
||||
"version": "1.2.2",
|
||||
"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_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": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
|
||||
|
@ -8487,6 +8501,23 @@
|
|||
"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": {
|
||||
"version": "5.1.0",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "3.0.0",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.7.10",
|
||||
"version": "0.8.0-unstable",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
@ -34,6 +34,7 @@
|
|||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
"react-apexcharts": "^1.4.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"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.BlockPostWithoutReferer)
|
||||
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.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) {
|
||||
utils.Error("Too many requests. Throttling", nil)
|
||||
|
@ -290,6 +290,9 @@ func StartServer() {
|
|||
|
||||
srapi.HandleFunc("/api/markets", market.MarketGet)
|
||||
|
||||
srapi.HandleFunc("/api/background", UploadBackground)
|
||||
srapi.HandleFunc("/api/background/{ext}", GetBackground)
|
||||
|
||||
|
||||
if(!config.HTTPConfig.AcceptAllInsecureHostname) {
|
||||
srapi.Use(utils.EnsureHostname)
|
||||
|
|
|
@ -11,7 +11,9 @@ import (
|
|||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -20,7 +22,7 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
databaseStatus := true
|
||||
|
||||
if(!utils.GetMainConfig().DisableUserManagement) {
|
||||
if(!config.DisableUserManagement) {
|
||||
err := utils.DB()
|
||||
if err != nil {
|
||||
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{}{
|
||||
"status": "OK",
|
||||
"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,
|
||||
"docker": docker.DockerIsConnected,
|
||||
"letsencrypt": utils.GetMainConfig().HTTPConfig.HTTPSCertificateMode == "LETSENCRYPT" && utils.GetMainConfig().HTTPConfig.SSLEmail == "",
|
||||
|
|
|
@ -87,6 +87,18 @@ type Config struct {
|
|||
AutoUpdate bool
|
||||
OpenIDClients []OpenIDClient
|
||||
MarketConfig MarketConfig
|
||||
HomepageConfig HomepageConfig
|
||||
ThemeConfig ThemeConfig
|
||||
}
|
||||
|
||||
type HomepageConfig struct {
|
||||
Background string
|
||||
Widgets []string
|
||||
}
|
||||
|
||||
type ThemeConfig struct {
|
||||
PrimaryColor string
|
||||
SecondaryColor string
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
|
|
|
@ -12,9 +12,12 @@ import (
|
|||
"io/ioutil"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
"github.com/shirou/gopsutil/v3/mem"
|
||||
"github.com/shirou/gopsutil/v3/disk"
|
||||
"github.com/shirou/gopsutil/v3/net"
|
||||
)
|
||||
|
||||
var ConfigLock sync.Mutex
|
||||
|
@ -449,3 +452,78 @@ func Max(x, y int) int {
|
|||
}
|
||||
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