v0.3.0-unstable14
This commit is contained in:
parent
29fc924475
commit
e93e45d4df
17
changelog.md
17
changelog.md
|
@ -1,13 +1,16 @@
|
||||||
## Version 0.3.0
|
## Version 0.3.0
|
||||||
- Set Max nb simulatneous connections per user
|
|
||||||
- Set Global Max nb simulatneous connections
|
|
||||||
- Display nickname on invite page
|
|
||||||
- Reset self-signed certificates when hostnames changes
|
|
||||||
- Block based on geo-locations
|
|
||||||
- Block common bots
|
|
||||||
- DNS challenge for letsencrypt
|
|
||||||
- Implement 2 FA
|
- Implement 2 FA
|
||||||
- Implement SMTP to Send Email (password reset / invites)
|
- Implement SMTP to Send Email (password reset / invites)
|
||||||
|
- Add homepage
|
||||||
|
- DNS challenge for letsencrypt
|
||||||
|
- Set Max nb simulatneous connections per user
|
||||||
|
- Admin only routes (See in security tab)
|
||||||
|
- Set Global Max nb simulatneous connections
|
||||||
|
- Block based on geo-locations
|
||||||
|
- Block common bots
|
||||||
|
- Display nickname on invite page
|
||||||
|
- Reset self-signed certificates when hostnames changes
|
||||||
|
- Edit user emails
|
||||||
- Show loading on user rows on actions
|
- Show loading on user rows on actions
|
||||||
|
|
||||||
## Version 0.2.0
|
## Version 0.2.0
|
||||||
|
|
BIN
client/src/assets/images/wallpaper(1).png
Normal file
BIN
client/src/assets/images/wallpaper(1).png
Normal file
Binary file not shown.
After Width: | Height: | Size: 410 KiB |
BIN
client/src/assets/images/wallpaper.png
Normal file
BIN
client/src/assets/images/wallpaper.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 684 KiB |
|
@ -451,6 +451,12 @@ export default function CountrySelect({name, label, formik}) {
|
||||||
onChange={(event, value) => {
|
onChange={(event, value) => {
|
||||||
formik.setFieldValue(name, value)
|
formik.setFieldValue(name, value)
|
||||||
}}
|
}}
|
||||||
|
filterOptions={(options, state) => {
|
||||||
|
const inputValue = state.inputValue.toUpperCase();
|
||||||
|
return options.filter((option) => {
|
||||||
|
return countries[option].label.toUpperCase().includes(inputValue)
|
||||||
|
})
|
||||||
|
}}
|
||||||
error={Boolean(formik.touched[name] && formik.errors[name])}
|
error={Boolean(formik.touched[name] && formik.errors[name])}
|
||||||
getOptionLabel={(option) => <div style={{verticalAlign: 'middle'}}><img
|
getOptionLabel={(option) => <div style={{verticalAlign: 'middle'}}><img
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
|
|
@ -62,7 +62,6 @@ const PrettyTableView = ({ getKey, data, columns, onRowClick, linkTo }) => {
|
||||||
})
|
})
|
||||||
.map((row, key) => (
|
.map((row, key) => (
|
||||||
<TableRow
|
<TableRow
|
||||||
onClick={() => onRowClick && onRowClick(row, key)}
|
|
||||||
key={getKey(row)}
|
key={getKey(row)}
|
||||||
sx={{
|
sx={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
@ -81,6 +80,7 @@ const PrettyTableView = ({ getKey, data, columns, onRowClick, linkTo }) => {
|
||||||
|
|
||||||
(!column.screenMin || screenMin[column.screenMin]) && <TableCell
|
(!column.screenMin || screenMin[column.screenMin]) && <TableCell
|
||||||
component={(linkTo && !column.clickable) ? Link : 'td'}
|
component={(linkTo && !column.clickable) ? Link : 'td'}
|
||||||
|
onClick={() => !column.clickable && onRowClick && onRowClick(row, key)}
|
||||||
to={linkTo && linkTo(row, key)}
|
to={linkTo && linkTo(row, key)}
|
||||||
className={column.underline ? 'emphasis' : ''}
|
className={column.underline ? 'emphasis' : ''}
|
||||||
sx={{
|
sx={{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// assets
|
// assets
|
||||||
import { HomeOutlined, AppstoreOutlined } from '@ant-design/icons';
|
import { HomeOutlined, AppstoreOutlined, DashboardOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
const icons = {
|
const icons = {
|
||||||
|
@ -17,16 +17,17 @@ const dashboard = {
|
||||||
id: 'home',
|
id: 'home',
|
||||||
title: 'Home',
|
title: 'Home',
|
||||||
type: 'item',
|
type: 'item',
|
||||||
url: '/ui',
|
url: '/ui/',
|
||||||
icon: icons.HomeOutlined,
|
icon: icons.HomeOutlined,
|
||||||
breadcrumbs: false
|
breadcrumbs: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'servapps',
|
id: 'dashboard',
|
||||||
title: 'ServApps',
|
title: 'Dashboard',
|
||||||
type: 'item',
|
type: 'item',
|
||||||
url: '/ui/servapps',
|
url: '/ui/dashboard',
|
||||||
icon: AppstoreOutlined
|
icon: DashboardOutlined,
|
||||||
|
breadcrumbs: false
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// assets
|
// assets
|
||||||
import { ProfileOutlined, SettingOutlined, NodeExpandOutlined} from '@ant-design/icons';
|
import { ProfileOutlined, SettingOutlined, NodeExpandOutlined, AppstoreOutlined} from '@ant-design/icons';
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
const icons = {
|
const icons = {
|
||||||
|
@ -15,6 +15,13 @@ const pages = {
|
||||||
title: 'Management',
|
title: 'Management',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
id: 'servapps',
|
||||||
|
title: 'ServApps',
|
||||||
|
type: 'item',
|
||||||
|
url: '/ui/servapps',
|
||||||
|
icon: AppstoreOutlined
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'url',
|
id: 'url',
|
||||||
title: 'URLs',
|
title: 'URLs',
|
||||||
|
@ -24,7 +31,7 @@ const pages = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'users',
|
id: 'users',
|
||||||
title: 'Manage Users',
|
title: 'Users',
|
||||||
type: 'item',
|
type: 'item',
|
||||||
url: '/ui/config-users',
|
url: '/ui/config-users',
|
||||||
icon: icons.ProfileOutlined,
|
icon: icons.ProfileOutlined,
|
||||||
|
|
|
@ -51,6 +51,7 @@ const AuthLogin = () => {
|
||||||
// TODO: Extract ?redirect=<URL> to redirect to a specific page after login
|
// TODO: Extract ?redirect=<URL> to redirect to a specific page after login
|
||||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||||
const notLogged = urlSearchParams.get('notlogged') == 1;
|
const notLogged = urlSearchParams.get('notlogged') == 1;
|
||||||
|
const notLoggedAdmin = urlSearchParams.get('notlogged') == 2;
|
||||||
const invalid = urlSearchParams.get('invalid') == 1;
|
const invalid = urlSearchParams.get('invalid') == 1;
|
||||||
const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/ui';
|
const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/ui';
|
||||||
|
|
||||||
|
@ -78,6 +79,11 @@ const AuthLogin = () => {
|
||||||
<br />
|
<br />
|
||||||
</Grid>}
|
</Grid>}
|
||||||
|
|
||||||
|
{ notLoggedAdmin &&<Grid container spacing={2} justifyContent="center">
|
||||||
|
<Alert severity="error">You need to be Admin</Alert>
|
||||||
|
<br />
|
||||||
|
</Grid>}
|
||||||
|
|
||||||
{ invalid &&<Grid container spacing={2} justifyContent="center">
|
{ invalid &&<Grid container spacing={2} justifyContent="center">
|
||||||
<Alert severity="error">You have been disconnected. Please login to continue</Alert>
|
<Alert severity="error">You have been disconnected. Please login to continue</Alert>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -30,6 +30,7 @@ const RouteSecurity = ({ routeConfig }) => {
|
||||||
MaxBandwith: routeConfig.MaxBandwith,
|
MaxBandwith: routeConfig.MaxBandwith,
|
||||||
BlockAPIAbuse: routeConfig.BlockAPIAbuse,
|
BlockAPIAbuse: routeConfig.BlockAPIAbuse,
|
||||||
BlockCommonBots: routeConfig.BlockCommonBots,
|
BlockCommonBots: routeConfig.BlockCommonBots,
|
||||||
|
AdminOnly: routeConfig.AdminOnly,
|
||||||
_SmartShield_Enabled: (routeConfig.SmartShield ? routeConfig.SmartShield.Enabled : false),
|
_SmartShield_Enabled: (routeConfig.SmartShield ? routeConfig.SmartShield.Enabled : false),
|
||||||
_SmartShield_PolicyStrictness: (routeConfig.SmartShield ? routeConfig.SmartShield.PolicyStrictness : 0),
|
_SmartShield_PolicyStrictness: (routeConfig.SmartShield ? routeConfig.SmartShield.PolicyStrictness : 0),
|
||||||
_SmartShield_PerUserTimeBudget: (routeConfig.SmartShield ? routeConfig.SmartShield.PerUserTimeBudget : 0),
|
_SmartShield_PerUserTimeBudget: (routeConfig.SmartShield ? routeConfig.SmartShield.PerUserTimeBudget : 0),
|
||||||
|
@ -97,6 +98,12 @@ const RouteSecurity = ({ routeConfig }) => {
|
||||||
formik={formik}
|
formik={formik}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CosmosCheckbox
|
||||||
|
name="AdminOnly"
|
||||||
|
label="Admin only"
|
||||||
|
formik={formik}
|
||||||
|
/>
|
||||||
|
|
||||||
<CosmosFormDivider title={'Smart Shield'} />
|
<CosmosFormDivider title={'Smart Shield'} />
|
||||||
|
|
||||||
<CosmosCheckbox
|
<CosmosCheckbox
|
||||||
|
|
|
@ -59,6 +59,7 @@ const ConfigManagement = () => {
|
||||||
LoggingLevel: config.LoggingLevel,
|
LoggingLevel: config.LoggingLevel,
|
||||||
RequireMFA: config.RequireMFA,
|
RequireMFA: config.RequireMFA,
|
||||||
GeoBlocking: config.BlockedCountries,
|
GeoBlocking: config.BlockedCountries,
|
||||||
|
AutoUpdate: config.AutoUpdate,
|
||||||
|
|
||||||
Hostname: config.HTTPConfig.Hostname,
|
Hostname: config.HTTPConfig.Hostname,
|
||||||
GenerateMissingTLSCert: config.HTTPConfig.GenerateMissingTLSCert,
|
GenerateMissingTLSCert: config.HTTPConfig.GenerateMissingTLSCert,
|
||||||
|
@ -89,6 +90,7 @@ const ConfigManagement = () => {
|
||||||
MongoDB: values.MongoDB,
|
MongoDB: values.MongoDB,
|
||||||
LoggingLevel: values.LoggingLevel,
|
LoggingLevel: values.LoggingLevel,
|
||||||
RequireMFA: values.RequireMFA,
|
RequireMFA: values.RequireMFA,
|
||||||
|
AutoUpdate: values.AutoUpdate,
|
||||||
BlockedCountries: values.GeoBlocking,
|
BlockedCountries: values.GeoBlocking,
|
||||||
HTTPConfig: {
|
HTTPConfig: {
|
||||||
...config.HTTPConfig,
|
...config.HTTPConfig,
|
||||||
|
@ -151,6 +153,12 @@ const ConfigManagement = () => {
|
||||||
helperText="Require MFA for all users"
|
helperText="Require MFA for all users"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CosmosCheckbox
|
||||||
|
label="Auto Update Cosmos"
|
||||||
|
name="AutoUpdate"
|
||||||
|
formik={formik}
|
||||||
|
/>
|
||||||
|
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<InputLabel htmlFor="MongoDB-login">MongoDB connection string. It is advised to use Environment variable to store this securely instead. (Optional)</InputLabel>
|
<InputLabel htmlFor="MongoDB-login">MongoDB connection string. It is advised to use Environment variable to store this securely instead. (Optional)</InputLabel>
|
||||||
|
|
|
@ -29,6 +29,7 @@ const UserManagement = () => {
|
||||||
const [openCreateForm, setOpenCreateForm] = React.useState(false);
|
const [openCreateForm, setOpenCreateForm] = React.useState(false);
|
||||||
const [openDeleteForm, setOpenDeleteForm] = React.useState(false);
|
const [openDeleteForm, setOpenDeleteForm] = React.useState(false);
|
||||||
const [openInviteForm, setOpenInviteForm] = React.useState(false);
|
const [openInviteForm, setOpenInviteForm] = React.useState(false);
|
||||||
|
const [openEditEmail, setOpenEditEmail] = React.useState(false);
|
||||||
const [toAction, setToAction] = React.useState(null);
|
const [toAction, setToAction] = React.useState(null);
|
||||||
const [loadingRow, setLoadingRow] = React.useState(null);
|
const [loadingRow, setLoadingRow] = React.useState(null);
|
||||||
|
|
||||||
|
@ -121,6 +122,35 @@ const UserManagement = () => {
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog open={openEditEmail} onClose={() => setOpenEditEmail(false)}>
|
||||||
|
<DialogTitle>Edit Email</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Use this form to invite edit {openEditEmail}'s Email.
|
||||||
|
</DialogContentText>
|
||||||
|
<TextField
|
||||||
|
autoFocus
|
||||||
|
margin="dense"
|
||||||
|
id="c-email-edit"
|
||||||
|
label="Email Address"
|
||||||
|
type="email"
|
||||||
|
fullWidth
|
||||||
|
variant="standard"
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setOpenEditEmail(false)}>Cancel</Button>
|
||||||
|
<Button onClick={() => {
|
||||||
|
API.users.edit(openEditEmail, {
|
||||||
|
email: document.getElementById('c-email-edit').value,
|
||||||
|
}).then(() => {
|
||||||
|
setOpenEditEmail(false);
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
}}>Edit</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<Dialog open={openCreateForm} onClose={() => setOpenCreateForm(false)}>
|
<Dialog open={openCreateForm} onClose={() => setOpenCreateForm(false)}>
|
||||||
<DialogTitle>Create User</DialogTitle>
|
<DialogTitle>Create User</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@ -174,6 +204,9 @@ const UserManagement = () => {
|
||||||
|
|
||||||
{!isLoading && rows && (<PrettyTableView
|
{!isLoading && rows && (<PrettyTableView
|
||||||
data={rows}
|
data={rows}
|
||||||
|
onRowClick = {(r) => {
|
||||||
|
setOpenEditEmail(r.nickname);
|
||||||
|
}}
|
||||||
getKey={(r) => r.nickname}
|
getKey={(r) => r.nickname}
|
||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
|
|
|
@ -116,6 +116,12 @@ const DashboardDefault = () => {
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{coStatus && coStatus.newVersionAvailable && (
|
||||||
|
<Alert severity="warning">
|
||||||
|
A new version of Cosmos is available! Please update to the latest version to get the latest features and bug fixes.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
{coStatus && coStatus.needsRestart && (
|
{coStatus && coStatus.needsRestart && (
|
||||||
<Alert severity="warning">
|
<Alert severity="warning">
|
||||||
You have made changes to the configuration that require a restart to take effect. Please restart Cosmos to apply the changes.
|
You have made changes to the configuration that require a restart to take effect. Please restart Cosmos to apply the changes.
|
||||||
|
|
151
client/src/pages/home/index.jsx
Normal file
151
client/src/pages/home/index.jsx
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import { useParams } from "react-router";
|
||||||
|
import Back from "../../components/back";
|
||||||
|
import { Alert, Box, CircularProgress, Grid, Stack, useTheme } from "@mui/material";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import * as API from "../../api";
|
||||||
|
import wallpaper from '../../assets/images/wallpaper.png';
|
||||||
|
import Grid2 from "@mui/material/Unstable_Grid2/Grid2";
|
||||||
|
import { getFaviconURL } from "../../utils/routes";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { getFullOrigin } from "../../utils/routes";
|
||||||
|
import IsLoggedIn from "../../isLoggedIn";
|
||||||
|
|
||||||
|
|
||||||
|
const HomeBackground = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<Box sx={{ position: 'fixed', float: 'left', overflow: 'hidden', zIndex: 0, top: 0, left: 0, right: 0, bottom: 0 }}>
|
||||||
|
<img src={wallpaper} style={{ display:'inline'}} alt="Cosmos" width="100%" height="100%" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const blockStyle = {
|
||||||
|
margin: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
maxWidth: '78%',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
}
|
||||||
|
|
||||||
|
const HomePage = () => {
|
||||||
|
const { routeName } = useParams();
|
||||||
|
const [config, setConfig] = useState(null);
|
||||||
|
const [coStatus, setCoStatus] = useState(null);
|
||||||
|
|
||||||
|
const refreshStatus = () => {
|
||||||
|
API.getStatus().then((res) => {
|
||||||
|
setCoStatus(res.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshConfig = () => {
|
||||||
|
API.config.get().then((res) => {
|
||||||
|
setConfig(res.data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let routes = config && (config.HTTPConfig.ProxyConfig.Routes || []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refreshConfig();
|
||||||
|
refreshStatus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
return <Stack spacing={2} >
|
||||||
|
<IsLoggedIn />
|
||||||
|
<HomeBackground />
|
||||||
|
<style>
|
||||||
|
{`header {
|
||||||
|
background: rgba(0.2,0.2,0.2,0.2) !important;
|
||||||
|
border-bottom-color: rgba(0.4,0.4,0.4,0.4) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .MuiChip-label {
|
||||||
|
color: #eee !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .MuiButtonBase-root {
|
||||||
|
color: #eee !important;
|
||||||
|
background: rgba(0.2,0.2,0.2,0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
transition: background 0.1s ease-in-out;
|
||||||
|
transition: transform 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background: rgba(0.4,0.4,0.4,0.4) !important;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
<Stack style={{zIndex: 2}} spacing={1}>
|
||||||
|
{coStatus && !coStatus.database && (
|
||||||
|
<Alert severity="error">
|
||||||
|
No Database is setup for Cosmos! User Management and Authentication will not work.<br />
|
||||||
|
You can either setup the database, or disable user management in the configuration panel.<br />
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{coStatus && coStatus.letsencrypt && (
|
||||||
|
<Alert severity="error">
|
||||||
|
You have enabled Let's Encrypt for automatic HTTPS Certificate. You need to provide the configuration with an email address to use for Let's Encrypt in the configs.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{coStatus && coStatus.newVersionAvailable && (
|
||||||
|
<Alert severity="warning">
|
||||||
|
A new version of Cosmos is available! Please update to the latest version to get the latest features and bug fixes.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{coStatus && coStatus.needsRestart && (
|
||||||
|
<Alert severity="warning">
|
||||||
|
You have made changes to the configuration that require a restart to take effect. Please restart Cosmos to apply the changes.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{coStatus && coStatus.domain && (
|
||||||
|
<Alert severity="error">
|
||||||
|
You are using localhost or 0.0.0.0 as a hostname in the configuration. It is recommended that you use a domain name instead.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{coStatus && !coStatus.docker && (
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Grid2 container spacing={2} style={{zIndex: 2}}>
|
||||||
|
{config && config.HTTPConfig.ProxyConfig.Routes.map((route) => {
|
||||||
|
return <Grid2 item xs={12} sm={6} md={4} lg={3} xl={2} key={route.Name}>
|
||||||
|
<Box className='app' style={{padding: 10, color: 'white', background: 'rgba(0,0,0,0.35)', borderRadius: 5}}>
|
||||||
|
<Link to={getFullOrigin(route)} target="_blank" style={{textDecoration: 'none', color: 'white'}}>
|
||||||
|
<Stack direction="row" spacing={2} alignItems="center">
|
||||||
|
<img src={getFaviconURL(route)} width="64px" />
|
||||||
|
|
||||||
|
<div style={{width: '100%'}}>
|
||||||
|
<h3 style={blockStyle}>{route.Name}</h3>
|
||||||
|
<p style={blockStyle}>{route.Description}</p>
|
||||||
|
<p style={blockStyle}>{route.Target}</p>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
|
</Grid2>
|
||||||
|
})}
|
||||||
|
</Grid2>
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomePage;
|
|
@ -63,7 +63,7 @@ const NewInstall = () => {
|
||||||
label: 'Docker 🐋 (step 1/4)',
|
label: 'Docker 🐋 (step 1/4)',
|
||||||
component: <Stack item xs={12} spacing={2}>
|
component: <Stack item xs={12} spacing={2}>
|
||||||
<div>
|
<div>
|
||||||
<QuestionCircleOutlined /> Cosmos is using docker to run applications. It is optionnal, 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">
|
||||||
|
@ -98,7 +98,7 @@ const NewInstall = () => {
|
||||||
label: 'Database 🗄️ (step 2/4)',
|
label: 'Database 🗄️ (step 2/4)',
|
||||||
component: <Stack item xs={12} spacing={2}>
|
component: <Stack item xs={12} spacing={2}>
|
||||||
<div>
|
<div>
|
||||||
<QuestionCircleOutlined /> Cosmos is using a MongoDB database to store all the data. It is optionnal, but Authentication as well as the UI will not work without a database.
|
<QuestionCircleOutlined /> Cosmos is using a MongoDB database to store all the data. It is optional, but Authentication as well as the UI will not work without a database.
|
||||||
</div>
|
</div>
|
||||||
{(status && status.database) ?
|
{(status && status.database) ?
|
||||||
<Alert severity="success">
|
<Alert severity="success">
|
||||||
|
@ -412,7 +412,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 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" 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';
|
||||||
|
|
|
@ -10,6 +10,7 @@ import ServeApps from '../pages/servapps/servapps';
|
||||||
import { Navigate } from 'react-router';
|
import { Navigate } from 'react-router';
|
||||||
import RouteConfigPage from '../pages/config/routeConfigPage';
|
import RouteConfigPage from '../pages/config/routeConfigPage';
|
||||||
import logo from '../assets/images/icons/cosmos.png';
|
import logo from '../assets/images/icons/cosmos.png';
|
||||||
|
import HomePage from '../pages/home';
|
||||||
|
|
||||||
|
|
||||||
// render - dashboard
|
// render - dashboard
|
||||||
|
@ -43,6 +44,10 @@ const MainRoutes = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/ui',
|
path: '/ui',
|
||||||
|
element: <HomePage />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/ui/dashboard',
|
||||||
element: <DashboardDefault />
|
element: <DashboardDefault />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,7 +33,7 @@ const addProtocol = (url) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getOrigin = (route) => {
|
export const getOrigin = (route) => {
|
||||||
return (route.UseHost ? route.Host : '') + (route.UsePathPrefix ? route.PathPrefix : '');
|
return (route.UseHost ? route.Host : window.location.origin) + (route.UsePathPrefix ? route.PathPrefix : '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFullOrigin = (route) => {
|
export const getFullOrigin = (route) => {
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -34,6 +34,7 @@ require (
|
||||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||||
github.com/Azure/go-autorest/logger v0.2.0 // indirect
|
github.com/Azure/go-autorest/logger v0.2.0 // indirect
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||||
github.com/PuerkitoBio/goquery v1.6.0 // indirect
|
github.com/PuerkitoBio/goquery v1.6.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -76,6 +76,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
|
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cosmos-server",
|
"name": "cosmos-server",
|
||||||
"version": "0.3.0-unstable13",
|
"version": "0.3.0-unstable14",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "test-server.js",
|
"main": "test-server.js",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
40
src/CRON.go
40
src/CRON.go
|
@ -1,21 +1,46 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jasonlvhit/gocron"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
// "github.com/azukaar/cosmos-server/src/docker"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/jasonlvhit/gocron"
|
||||||
|
"github.com/Masterminds/semver"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Version struct {
|
type Version struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compareSemver compares two semantic version strings.
|
||||||
|
// Returns:
|
||||||
|
// 0 if v1 == v2
|
||||||
|
// 1 if v1 > v2
|
||||||
|
// -1 if v1 < v2
|
||||||
|
// error if there's a problem parsing either version string
|
||||||
|
func compareSemver(v1, v2 string) (int, error) {
|
||||||
|
ver1, err := semver.NewVersion(v1)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("compareSemver 1 " + v1, err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ver2, err := semver.NewVersion(v2)
|
||||||
|
if err != nil {
|
||||||
|
utils.Error("compareSemver 2 " + v2, err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ver1.Compare(ver2), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func checkVersion() {
|
func checkVersion() {
|
||||||
|
utils.NewVersionAvailable = false
|
||||||
|
|
||||||
ex, err := os.Executable()
|
ex, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -56,9 +81,16 @@ func checkVersion() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if string(body) != myVersion {
|
cp, errc := compareSemver(myVersion, string(body))
|
||||||
|
|
||||||
|
if errc != nil {
|
||||||
|
utils.Error("checkVersion", errc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cp == -1 {
|
||||||
utils.Log("New version available: " + string(body))
|
utils.Log("New version available: " + string(body))
|
||||||
// update
|
utils.NewVersionAvailable = true
|
||||||
} else {
|
} else {
|
||||||
utils.Log("No new version available")
|
utils.Log("No new version available")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConfigApiGet(w http.ResponseWriter, req *http.Request) {
|
func ConfigApiGet(w http.ResponseWriter, req *http.Request) {
|
||||||
if utils.AdminOnly(w, req) != nil {
|
if utils.LoggedInOnly(w, req) != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAdmin := utils.IsAdmin(req)
|
||||||
|
|
||||||
if(req.Method == "GET") {
|
if(req.Method == "GET") {
|
||||||
config := utils.ReadConfigFromFile()
|
config := utils.ReadConfigFromFile()
|
||||||
|
|
||||||
|
@ -18,6 +20,22 @@ func ConfigApiGet(w http.ResponseWriter, req *http.Request) {
|
||||||
config.HTTPConfig.AuthPrivateKey = ""
|
config.HTTPConfig.AuthPrivateKey = ""
|
||||||
config.HTTPConfig.TLSKey = ""
|
config.HTTPConfig.TLSKey = ""
|
||||||
|
|
||||||
|
if !isAdmin {
|
||||||
|
config.MongoDB = "***"
|
||||||
|
config.EmailConfig.Password = "***"
|
||||||
|
config.EmailConfig.Username = "***"
|
||||||
|
config.EmailConfig.Host = "***"
|
||||||
|
|
||||||
|
// filter admin only routes
|
||||||
|
filteredRoutes := make([]utils.ProxyRouteConfig, 0)
|
||||||
|
for _, route := range config.HTTPConfig.ProxyConfig.Routes {
|
||||||
|
if !route.AdminOnly {
|
||||||
|
filteredRoutes = append(filteredRoutes, route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.HTTPConfig.ProxyConfig.Routes = filteredRoutes
|
||||||
|
}
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"data": config,
|
"data": config,
|
||||||
|
|
|
@ -16,6 +16,8 @@ func main() {
|
||||||
|
|
||||||
LoadConfig()
|
LoadConfig()
|
||||||
|
|
||||||
|
checkVersion()
|
||||||
|
|
||||||
go CRON()
|
go CRON()
|
||||||
|
|
||||||
docker.Test()
|
docker.Test()
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func tokenMiddleware(enabled bool) func(next http.Handler) http.Handler {
|
func tokenMiddleware(enabled bool, adminOnly bool) func(next http.Handler) http.Handler {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
r.Header.Del("x-cosmos-user")
|
r.Header.Del("x-cosmos-user")
|
||||||
|
@ -25,6 +25,7 @@ func tokenMiddleware(enabled bool) func(next http.Handler) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
r.Header.Set("x-cosmos-user", u.Nickname)
|
r.Header.Set("x-cosmos-user", u.Nickname)
|
||||||
r.Header.Set("x-cosmos-role", strconv.Itoa((int)(u.Role)))
|
r.Header.Set("x-cosmos-role", strconv.Itoa((int)(u.Role)))
|
||||||
r.Header.Set("x-cosmos-mfa", strconv.Itoa((int)(u.MFAState)))
|
r.Header.Set("x-cosmos-mfa", strconv.Itoa((int)(u.MFAState)))
|
||||||
|
@ -37,7 +38,9 @@ func tokenMiddleware(enabled bool) func(next http.Handler) http.Handler {
|
||||||
// Replace the token with a application speicfic one
|
// Replace the token with a application speicfic one
|
||||||
r.Header.Set("x-cosmos-token", "1234567890")
|
r.Header.Set("x-cosmos-token", "1234567890")
|
||||||
|
|
||||||
if enabled {
|
if enabled && adminOnly {
|
||||||
|
utils.AdminOnlyWithRedirect(w, r)
|
||||||
|
} else if enabled {
|
||||||
utils.LoggedInOnlyWithRedirect(w, r)
|
utils.LoggedInOnlyWithRedirect(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +119,7 @@ func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination htt
|
||||||
destination = utils.BlockPostWithoutReferer(destination)
|
destination = utils.BlockPostWithoutReferer(destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
destination = tokenMiddleware(route.AuthEnabled)(utils.CORSHeader(originCORS)((destination)))
|
destination = tokenMiddleware(route.AuthEnabled, route.AdminOnly)(utils.CORSHeader(originCORS)((destination)))
|
||||||
|
|
||||||
origin.Handler(destination)
|
origin.Handler(destination)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func StatusRoute(w http.ResponseWriter, req *http.Request) {
|
func StatusRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
if !utils.GetMainConfig().NewInstall && (utils.AdminOnly(w, req) != nil) {
|
if !utils.GetMainConfig().NewInstall && (utils.LoggedInOnly(w, req) != nil) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ func StatusRoute(w http.ResponseWriter, req *http.Request) {
|
||||||
"domain": utils.GetMainConfig().HTTPConfig.Hostname == "localhost" || utils.GetMainConfig().HTTPConfig.Hostname == "0.0.0.0",
|
"domain": utils.GetMainConfig().HTTPConfig.Hostname == "localhost" || utils.GetMainConfig().HTTPConfig.Hostname == "0.0.0.0",
|
||||||
"HTTPSCertificateMode": utils.GetMainConfig().HTTPConfig.HTTPSCertificateMode,
|
"HTTPSCertificateMode": utils.GetMainConfig().HTTPConfig.HTTPSCertificateMode,
|
||||||
"needsRestart": utils.NeedsRestart,
|
"needsRestart": utils.NeedsRestart,
|
||||||
|
"newVersionAvailable": utils.NewVersionAvailable,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -29,6 +29,36 @@ func LoggedInOnlyWithRedirect(w http.ResponseWriter, req *http.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AdminOnlyWithRedirect(w http.ResponseWriter, req *http.Request) error {
|
||||||
|
userNickname := req.Header.Get("x-cosmos-user")
|
||||||
|
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
||||||
|
mfa, _ := strconv.Atoi(req.Header.Get("x-cosmos-mfa"))
|
||||||
|
isUserLoggedIn := role > 0
|
||||||
|
isUserAdmin := role > 1
|
||||||
|
|
||||||
|
if !isUserLoggedIn || userNickname == "" {
|
||||||
|
Error("AdminLoggedInOnlyWithRedirect: User is not logged in", nil)
|
||||||
|
http.Redirect(w, req, "/ui/login?notlogged=1&redirect="+req.URL.Path, http.StatusFound)
|
||||||
|
return errors.New("User is not logged")
|
||||||
|
}
|
||||||
|
|
||||||
|
if isUserLoggedIn && !isUserAdmin {
|
||||||
|
Error("AdminLoggedInOnly: User is not Authorized", nil)
|
||||||
|
HTTPError(w, "User not Authorized", http.StatusUnauthorized, "HTTP004")
|
||||||
|
return errors.New("User is not Admin")
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mfa == 1) {
|
||||||
|
http.Redirect(w, req, "/ui/loginmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||||
|
return errors.New("User requires MFA")
|
||||||
|
} else if(mfa == 2) {
|
||||||
|
http.Redirect(w, req, "/ui/newmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||||
|
return errors.New("User requires MFA Setup")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func LoggedInWeakOnly(w http.ResponseWriter, req *http.Request) error {
|
func LoggedInWeakOnly(w http.ResponseWriter, req *http.Request) error {
|
||||||
userNickname := req.Header.Get("x-cosmos-user")
|
userNickname := req.Header.Get("x-cosmos-user")
|
||||||
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
||||||
|
@ -97,6 +127,11 @@ func AdminOnly(w http.ResponseWriter, req *http.Request) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsAdmin(req *http.Request) bool {
|
||||||
|
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
||||||
|
return role > 1
|
||||||
|
}
|
||||||
|
|
||||||
func AdminOrItselfOnly(w http.ResponseWriter, req *http.Request, nickname string) error {
|
func AdminOrItselfOnly(w http.ResponseWriter, req *http.Request, nickname string) error {
|
||||||
userNickname := req.Header.Get("x-cosmos-user")
|
userNickname := req.Header.Get("x-cosmos-user")
|
||||||
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
||||||
|
|
|
@ -84,6 +84,7 @@ type Config struct {
|
||||||
BlockedCountries []string
|
BlockedCountries []string
|
||||||
ServerCountry string
|
ServerCountry string
|
||||||
RequireMFA bool
|
RequireMFA bool
|
||||||
|
AutoUpdate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
|
@ -139,6 +140,7 @@ type ProxyRouteConfig struct {
|
||||||
StripPathPrefix bool
|
StripPathPrefix bool
|
||||||
MaxBandwith int64
|
MaxBandwith int64
|
||||||
AuthEnabled bool
|
AuthEnabled bool
|
||||||
|
AdminOnly bool
|
||||||
Target string `validate:"required"`
|
Target string `validate:"required"`
|
||||||
SmartShield SmartShieldPolicy
|
SmartShield SmartShieldPolicy
|
||||||
Mode ProxyMode
|
Mode ProxyMode
|
||||||
|
|
|
@ -19,12 +19,14 @@ import (
|
||||||
var BaseMainConfig Config
|
var BaseMainConfig Config
|
||||||
var MainConfig Config
|
var MainConfig Config
|
||||||
var IsHTTPS = false
|
var IsHTTPS = false
|
||||||
|
var NewVersionAvailable = false
|
||||||
|
|
||||||
var NeedsRestart = false
|
var NeedsRestart = false
|
||||||
|
|
||||||
var DefaultConfig = Config{
|
var DefaultConfig = Config{
|
||||||
LoggingLevel: "INFO",
|
LoggingLevel: "INFO",
|
||||||
NewInstall: true,
|
NewInstall: true,
|
||||||
|
AutoUpdate: true,
|
||||||
// By default we block all countries that have a high amount of attacks
|
// By default we block all countries that have a high amount of attacks
|
||||||
// Note that Cosmos wont block the country of origin of the server even if it is in this list
|
// Note that Cosmos wont block the country of origin of the server even if it is in this list
|
||||||
BlockedCountries: []string{
|
BlockedCountries: []string{
|
||||||
|
|
Loading…
Reference in a new issue