Proxy settings + register page
This commit is contained in:
parent
17e42b8ae0
commit
28a44da3ad
|
@ -58,7 +58,7 @@ jobs:
|
|||
|
||||
- run:
|
||||
name: Build UI
|
||||
command: node .bin/vite build
|
||||
command: node .bin/vite build --base=/ui/
|
||||
|
||||
- run:
|
||||
name: Build Linux (ARM)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import wrap from './wrap';
|
||||
|
||||
function login(values) {
|
||||
return fetch('/cosmos/api/login', {
|
||||
return wrap(fetch('/cosmos/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(values)
|
||||
})
|
||||
.then((res) => res.json())
|
||||
}))
|
||||
}
|
||||
|
||||
function me() {
|
||||
|
@ -20,7 +20,17 @@ function me() {
|
|||
.then((res) => res.json())
|
||||
}
|
||||
|
||||
function logout() {
|
||||
return wrap(fetch('/cosmos/api/logout/', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
export {
|
||||
login,
|
||||
logout,
|
||||
me
|
||||
};
|
35
client/src/api/config.jsx
Normal file
35
client/src/api/config.jsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import wrap from './wrap';
|
||||
|
||||
function get() {
|
||||
return wrap(fetch('/cosmos/api/config', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
function set(values) {
|
||||
return wrap(fetch('/cosmos/api/config', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(values),
|
||||
}))
|
||||
}
|
||||
|
||||
function restart() {
|
||||
return wrap(fetch('/cosmos/api/restart', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
export {
|
||||
get,
|
||||
set,
|
||||
restart
|
||||
};
|
|
@ -1,6 +1,8 @@
|
|||
import * as auth from './authentication.jsx';
|
||||
import * as users from './users.jsx';
|
||||
import * as config from './config.jsx';
|
||||
export {
|
||||
auth,
|
||||
users
|
||||
users,
|
||||
config
|
||||
};
|
|
@ -1,17 +1,15 @@
|
|||
import wrap from './wrap';
|
||||
|
||||
function list() {
|
||||
return fetch('/cosmos/api/users', {
|
||||
return wrap(fetch('/cosmos/api/users', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
}))
|
||||
}
|
||||
|
||||
function create(values) {
|
||||
alert(JSON.stringify(values))
|
||||
return wrap(fetch('/cosmos/api/users', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -22,16 +20,13 @@ function create(values) {
|
|||
}
|
||||
|
||||
function register(values) {
|
||||
return fetch('/cosmos/api/register', {
|
||||
return wrap(fetch('/cosmos/api/register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(values),
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
}))
|
||||
}
|
||||
|
||||
function invite(values) {
|
||||
|
@ -45,34 +40,31 @@ function invite(values) {
|
|||
}
|
||||
|
||||
function edit(nickname, values) {
|
||||
return fetch('/cosmos/api/users/'+nickname, {
|
||||
return wrap(fetch('/cosmos/api/users/'+nickname, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(values),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
}))
|
||||
}
|
||||
|
||||
function get(nickname) {
|
||||
return fetch('/cosmos/api/users/'+nickname, {
|
||||
return wrap(fetch('/cosmos/api/users/'+nickname, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
}))
|
||||
}
|
||||
|
||||
function deleteUser(nickname) {
|
||||
return fetch('/cosmos/api/users/'+nickname, {
|
||||
return wrap(fetch('/cosmos/api/users/'+nickname, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
}))
|
||||
}
|
||||
|
||||
export {
|
||||
|
|
|
@ -7,7 +7,7 @@ export default function wrap(apicall) {
|
|||
return rep;
|
||||
}
|
||||
snackit(rep.message);
|
||||
throw new Error(rep);
|
||||
throw new Error(rep.message);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -41,3 +41,7 @@
|
|||
animation: shake 1s;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
|
||||
.code {
|
||||
background-color: rgba(0.2,0.2,0.2,0.2);
|
||||
}
|
|
@ -3,11 +3,12 @@ import * as API from './api';
|
|||
import { useEffect } from 'react';
|
||||
|
||||
const isLoggedIn = () => useEffect(() => {
|
||||
console.log("CHECK LOGIN")
|
||||
API.auth.me().then((data) => {
|
||||
if(data.status != 'OK') {
|
||||
window.location.href = '/login';
|
||||
window.location.href = '/ui/login';
|
||||
}
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
export default isLoggedIn;
|
|
@ -1,5 +1,5 @@
|
|||
// material-ui
|
||||
import { Box, IconButton, Link, useMediaQuery } from '@mui/material';
|
||||
import { Box, Chip, IconButton, Link, useMediaQuery } from '@mui/material';
|
||||
import { GithubOutlined } from '@ant-design/icons';
|
||||
|
||||
// project import
|
||||
|
@ -18,9 +18,12 @@ const HeaderContent = () => {
|
|||
{!matchesXs && <Search />}
|
||||
{matchesXs && <Box sx={{ width: '100%', ml: 1 }} />}
|
||||
|
||||
<Notification />
|
||||
{!matchesXs && <Profile />}
|
||||
{matchesXs && <MobileSection />}
|
||||
<Link href="/ui/logout" underline="none">
|
||||
<Chip label="Logout" />
|
||||
</Link>
|
||||
{/* <Notification /> */}
|
||||
{/* {!matchesXs && <Profile />}
|
||||
{matchesXs && <MobileSection />} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ const dashboard = {
|
|||
id: 'home',
|
||||
title: 'Home',
|
||||
type: 'item',
|
||||
url: '/',
|
||||
url: '/ui',
|
||||
icon: icons.HomeOutlined,
|
||||
breadcrumbs: false
|
||||
}
|
||||
|
|
|
@ -19,21 +19,21 @@ const pages = {
|
|||
id: 'proxy',
|
||||
title: 'Proxy Routes',
|
||||
type: 'item',
|
||||
url: '/config/proxy',
|
||||
url: '/ui/config/proxy',
|
||||
icon: icons.NodeExpandOutlined,
|
||||
},
|
||||
{
|
||||
id: 'users',
|
||||
title: 'Manage Users',
|
||||
type: 'item',
|
||||
url: '/config/users',
|
||||
url: '/ui/config/users',
|
||||
icon: icons.ProfileOutlined,
|
||||
},
|
||||
{
|
||||
id: 'config',
|
||||
title: 'Configuration',
|
||||
type: 'item',
|
||||
url: '/config/general',
|
||||
url: '/ui/config/general',
|
||||
icon: icons.SettingOutlined,
|
||||
}
|
||||
]
|
||||
|
|
36
client/src/pages/authentication/Logoff.jsx
Normal file
36
client/src/pages/authentication/Logoff.jsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import AuthRegister from './auth-forms/AuthRegister';
|
||||
import AuthWrapper from './AuthWrapper';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import * as API from '../../api';
|
||||
|
||||
// ================================|| REGISTER ||================================ //
|
||||
|
||||
const Logout = () => {
|
||||
useEffect(() => {
|
||||
API.auth.logout()
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
window.location.href = '/ui/login';
|
||||
}, 2000);
|
||||
});
|
||||
},[]);
|
||||
|
||||
return <AuthWrapper>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h3">
|
||||
You have been logged off. Redirecting you...
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>;
|
||||
}
|
||||
|
||||
export default Logout;
|
|
@ -4,27 +4,38 @@ import { Link } from 'react-router-dom';
|
|||
import { Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import FirebaseRegister from './auth-forms/AuthRegister';
|
||||
import AuthRegister from './auth-forms/AuthRegister';
|
||||
import AuthWrapper from './AuthWrapper';
|
||||
|
||||
// ================================|| REGISTER ||================================ //
|
||||
|
||||
const Register = () => (
|
||||
<AuthWrapper>
|
||||
const Register = () => {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const formType = urlSearchParams.get('t');
|
||||
const isInviteLink = formType === '2';
|
||||
const isRegister = formType === '1';
|
||||
const nickname = urlSearchParams.get('nickname');
|
||||
const regkey = urlSearchParams.get('key');
|
||||
|
||||
return <AuthWrapper>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
||||
<Typography variant="h3">Sign up</Typography>
|
||||
<Typography component={Link} to="/login" variant="body1" sx={{ textDecoration: 'none' }} color="primary">
|
||||
Already have an account?
|
||||
</Typography>
|
||||
<Typography variant="h3">{
|
||||
isInviteLink ? 'Invitation' : 'Password Reset'
|
||||
}</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FirebaseRegister />
|
||||
<AuthRegister
|
||||
nickname={nickname}
|
||||
isRegister={isRegister}
|
||||
isInviteLink={isInviteLink}
|
||||
regkey={regkey}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
</AuthWrapper>;
|
||||
}
|
||||
|
||||
export default Register;
|
||||
|
|
30
client/src/pages/authentication/Signup.jsx
Normal file
30
client/src/pages/authentication/Signup.jsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
// material-ui
|
||||
import { Grid, Stack, Typography } from '@mui/material';
|
||||
|
||||
// project import
|
||||
import FirebaseRegister from './auth-forms/AuthRegister';
|
||||
import AuthWrapper from './AuthWrapper';
|
||||
|
||||
// ================================|| REGISTER ||================================ //
|
||||
|
||||
const Signup = () => (
|
||||
<AuthWrapper>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
||||
<Typography variant="h3">Sign up</Typography>
|
||||
<Typography component={Link} to="/ui/login" variant="body1" sx={{ textDecoration: 'none' }} color="primary">
|
||||
Already have an account?
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FirebaseRegister />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AuthWrapper>
|
||||
);
|
||||
|
||||
export default Signup;
|
|
@ -51,7 +51,7 @@ const AuthLogin = () => {
|
|||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const notLogged = urlSearchParams.get('notlogged') == 1;
|
||||
const invalid = urlSearchParams.get('invalid') == 1;
|
||||
const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/';
|
||||
const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/ui';
|
||||
|
||||
useEffect(() => {
|
||||
API.auth.me().then((data) => {
|
||||
|
@ -170,7 +170,7 @@ const AuthLogin = () => {
|
|||
|
||||
<Grid item xs={12} sx={{ mt: -1 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<FormControlLabel
|
||||
{/* <FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
|
@ -184,7 +184,7 @@ const AuthLogin = () => {
|
|||
/>
|
||||
<Link variant="h6" component={RouterLink} to="" color="text.primary">
|
||||
Forgot Password?
|
||||
</Link>
|
||||
</Link> */}
|
||||
</Stack>
|
||||
</Grid>
|
||||
{errors.submit && (
|
||||
|
|
|
@ -15,13 +15,16 @@ import {
|
|||
InputLabel,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography
|
||||
Typography,
|
||||
Alert
|
||||
} from '@mui/material';
|
||||
|
||||
// third party
|
||||
import * as Yup from 'yup';
|
||||
import { Formik } from 'formik';
|
||||
|
||||
import * as API from '../../../api';
|
||||
|
||||
// project import
|
||||
import FirebaseSocial from './FirebaseSocial';
|
||||
import AnimateButton from '../../../components/@extended/AnimateButton';
|
||||
|
@ -32,7 +35,7 @@ import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
|||
|
||||
// ============================|| FIREBASE - REGISTER ||============================ //
|
||||
|
||||
const AuthRegister = () => {
|
||||
const AuthRegister = ({nickname, isRegister, isInviteLink, regkey}) => {
|
||||
const [level, setLevel] = useState();
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const handleClickShowPassword = () => {
|
||||
|
@ -56,7 +59,7 @@ const AuthRegister = () => {
|
|||
<>
|
||||
<Formik
|
||||
initialValues={{
|
||||
firstname: '',
|
||||
nickname: nickname,
|
||||
lastname: '',
|
||||
email: '',
|
||||
company: '',
|
||||
|
@ -64,112 +67,61 @@ const AuthRegister = () => {
|
|||
submit: null
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
firstname: Yup.string().max(255).required('First Name is required'),
|
||||
lastname: Yup.string().max(255).required('Last Name is required'),
|
||||
email: Yup.string().email('Must be a valid email').max(255).required('Email is required'),
|
||||
password: Yup.string().max(255).required('Password is required')
|
||||
nickname: Yup.string().max(255).required('Nickname is required'),
|
||||
password: Yup.string()
|
||||
.max(255)
|
||||
.required('Password is required')
|
||||
.matches(
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/,
|
||||
'Must Contain 8 Characters, One Uppercase, One Lowercase, One Number and one special case Character'
|
||||
),
|
||||
})}
|
||||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||
try {
|
||||
setStatus({ success: false });
|
||||
return API.users.register({
|
||||
nickname: nickname,
|
||||
registerKey: regkey,
|
||||
password: values.password,
|
||||
}).then((res) => {
|
||||
setStatus({ success: true });
|
||||
setSubmitting(false);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
window.location.href = '/ui/login';
|
||||
}).catch((err) => {
|
||||
setStatus({ success: false });
|
||||
setErrors({ submit: err.message });
|
||||
setSubmitting(false);
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
|
||||
<form noValidate onSubmit={handleSubmit}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} md={6}>
|
||||
{isInviteLink ? <Grid item xs={12}>
|
||||
<Alert severity="info">
|
||||
<strong>Invite Link</strong> - You have been invited to join this Cosmos instance. This Nickname has been provided to us by your administrator. Keep note of it, you will need it to login.
|
||||
</Alert>
|
||||
</Grid> : ''}
|
||||
{isInviteLink ? <Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor="firstname-signup">First Name*</InputLabel>
|
||||
<InputLabel htmlFor="nickname-signup">Nickname</InputLabel>
|
||||
<OutlinedInput
|
||||
id="firstname-login"
|
||||
type="firstname"
|
||||
value={values.firstname}
|
||||
name="firstname"
|
||||
id="nickname-login"
|
||||
type="nickname"
|
||||
value={nickname}
|
||||
name="nickname"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
placeholder="John"
|
||||
placeholder=""
|
||||
disabled={true}
|
||||
fullWidth
|
||||
error={Boolean(touched.firstname && errors.firstname)}
|
||||
error={Boolean(touched.nickname && errors.nickname)}
|
||||
/>
|
||||
{touched.firstname && errors.firstname && (
|
||||
<FormHelperText error id="helper-text-firstname-signup">
|
||||
{errors.firstname}
|
||||
{touched.nickname && errors.nickname && (
|
||||
<FormHelperText error id="helper-text-nickname-signup">
|
||||
{errors.nickname}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor="lastname-signup">Last Name*</InputLabel>
|
||||
<OutlinedInput
|
||||
fullWidth
|
||||
error={Boolean(touched.lastname && errors.lastname)}
|
||||
id="lastname-signup"
|
||||
type="lastname"
|
||||
value={values.lastname}
|
||||
name="lastname"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
placeholder="Doe"
|
||||
inputProps={{}}
|
||||
/>
|
||||
{touched.lastname && errors.lastname && (
|
||||
<FormHelperText error id="helper-text-lastname-signup">
|
||||
{errors.lastname}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor="company-signup">Company</InputLabel>
|
||||
<OutlinedInput
|
||||
fullWidth
|
||||
error={Boolean(touched.company && errors.company)}
|
||||
id="company-signup"
|
||||
value={values.company}
|
||||
name="company"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
placeholder="Demo Inc."
|
||||
inputProps={{}}
|
||||
/>
|
||||
{touched.company && errors.company && (
|
||||
<FormHelperText error id="helper-text-company-signup">
|
||||
{errors.company}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor="email-signup">Email Address*</InputLabel>
|
||||
<OutlinedInput
|
||||
fullWidth
|
||||
error={Boolean(touched.email && errors.email)}
|
||||
id="email-login"
|
||||
type="email"
|
||||
value={values.email}
|
||||
name="email"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
placeholder="demo@company.com"
|
||||
inputProps={{}}
|
||||
/>
|
||||
{touched.email && errors.email && (
|
||||
<FormHelperText error id="helper-text-email-signup">
|
||||
{errors.email}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid> : ''}
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor="password-signup">Password</InputLabel>
|
||||
|
@ -198,7 +150,7 @@ const AuthRegister = () => {
|
|||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
placeholder="******"
|
||||
placeholder="********"
|
||||
inputProps={{}}
|
||||
/>
|
||||
{touched.password && errors.password && (
|
||||
|
@ -220,18 +172,6 @@ const AuthRegister = () => {
|
|||
</Grid>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="body2">
|
||||
By Signing up, you agree to our
|
||||
<Link variant="subtitle2" component={RouterLink} to="#">
|
||||
Terms of Service
|
||||
</Link>
|
||||
and
|
||||
<Link variant="subtitle2" component={RouterLink} to="#">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</Typography>
|
||||
</Grid>
|
||||
{errors.submit && (
|
||||
<Grid item xs={12}>
|
||||
<FormHelperText error>{errors.submit}</FormHelperText>
|
||||
|
@ -248,18 +188,12 @@ const AuthRegister = () => {
|
|||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Create Account
|
||||
{
|
||||
isRegister ? 'Register' : 'Reset Password'
|
||||
}
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider>
|
||||
<Typography variant="caption">Sign up with</Typography>
|
||||
</Divider>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FirebaseSocial />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
)}
|
||||
|
|
328
client/src/pages/config/users/configman.jsx
Normal file
328
client/src/pages/config/users/configman.jsx
Normal file
|
@ -0,0 +1,328 @@
|
|||
import * as React from 'react';
|
||||
import isLoggedIn from '../../../isLoggedIn';
|
||||
import * as API from '../../../api';
|
||||
import MainCard from '../../../components/MainCard';
|
||||
import { Formik, Field } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
Link,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography,
|
||||
FormHelperText,
|
||||
Collapse,
|
||||
TextField,
|
||||
MenuItem,
|
||||
|
||||
} from '@mui/material';
|
||||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||
import AnimateButton from '../../../components/@extended/AnimateButton';
|
||||
import RestartModal from './restart';
|
||||
import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined } from '@ant-design/icons';
|
||||
|
||||
|
||||
const ConfigManagement = () => {
|
||||
isLoggedIn();
|
||||
const [config, setConfig] = React.useState(null);
|
||||
const [openModal, setOpenModal] = React.useState(false);
|
||||
|
||||
function refresh() {
|
||||
API.config.get().then((res) => {
|
||||
setConfig(res.data);
|
||||
});
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
refresh();
|
||||
}, []);
|
||||
|
||||
return <div style={{maxWidth: '1000px', margin: ''}}>
|
||||
<Button variant="contained" color="primary" startIcon={<SyncOutlined />} onClick={() => {
|
||||
refresh();
|
||||
}}>Refresh</Button><br /><br />
|
||||
{config && <>
|
||||
<RestartModal openModal={openModal} setOpenModal={setOpenModal} />
|
||||
<Formik
|
||||
initialValues={{
|
||||
MongoDB: config.MongoDB,
|
||||
LoggingLevel: config.LoggingLevel,
|
||||
|
||||
Hostname: config.HTTPConfig.Hostname,
|
||||
GenerateMissingTLSCert: config.HTTPConfig.GenerateMissingTLSCert,
|
||||
GenerateMissingAuthCert: config.HTTPConfig.GenerateMissingAuthCert,
|
||||
HTTPPort: config.HTTPConfig.HTTPPort,
|
||||
HTTPSPort: config.HTTPConfig.HTTPSPort,
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
Hostname: Yup.string().max(255).required('Hostname is required'),
|
||||
MongoDB: Yup.string().max(512),
|
||||
LoggingLevel: Yup.string().max(255).required('Logging Level is required'),
|
||||
})}
|
||||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||
try {
|
||||
let toSave = {
|
||||
...config,
|
||||
MongoDB: values.MongoDB,
|
||||
LoggingLevel: values.LoggingLevel,
|
||||
HTTPConfig: {
|
||||
...config.HTTPConfig,
|
||||
Hostname: values.Hostname,
|
||||
GenerateMissingTLSCert: values.GenerateMissingTLSCert,
|
||||
GenerateMissingAuthCert: values.GenerateMissingAuthCert,
|
||||
HTTPPort: values.HTTPPort,
|
||||
HTTPSPort: values.HTTPSPort,
|
||||
}
|
||||
}
|
||||
|
||||
API.config.set(toSave).then((data) => {
|
||||
if (data.status == 'error') {
|
||||
setStatus({ success: false });
|
||||
if (data.code == 'UL001') {
|
||||
setErrors({ submit: 'Wrong nickname or password. Try again or try resetting your password' });
|
||||
} else if (data.status == 'error') {
|
||||
setErrors({ submit: 'Unexpected error. Try again later.' });
|
||||
}
|
||||
setSubmitting(false);
|
||||
return;
|
||||
} else {
|
||||
setStatus({ success: true });
|
||||
setSubmitting(false);
|
||||
setOpenModal(true);
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
setStatus({ success: false });
|
||||
setErrors({ submit: err.message });
|
||||
setSubmitting(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values }) => (
|
||||
<form noValidate onSubmit={handleSubmit}>
|
||||
<MainCard title="General">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Alert severity="info">This page allow you to edit the configuration file. Any Environment Variable overwritting configuration won't appear here.</Alert>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor="MongoDB-login">MongoDB connection string. It is advised to use Environment variable to store this securely instead. (Optional)</InputLabel>
|
||||
<OutlinedInput
|
||||
id="MongoDB-login"
|
||||
type="password"
|
||||
value={values.MongoDB}
|
||||
name="MongoDB"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
placeholder="MongoDB"
|
||||
fullWidth
|
||||
error={Boolean(touched.MongoDB && errors.MongoDB)}
|
||||
/>
|
||||
{touched.MongoDB && errors.MongoDB && (
|
||||
<FormHelperText error id="standard-weight-helper-text-MongoDB-login">
|
||||
{errors.MongoDB}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor="LoggingLevel-login">Level of logging (Default: INFO)</InputLabel>
|
||||
<TextField
|
||||
className="px-2 my-2"
|
||||
variant="outlined"
|
||||
name="LoggingLevel"
|
||||
id="LoggingLevel"
|
||||
select
|
||||
value={values.LoggingLevel}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
touched.LoggingLevel &&
|
||||
Boolean(errors.LoggingLevel)
|
||||
}
|
||||
helperText={
|
||||
touched.LoggingLevel && errors.LoggingLevel
|
||||
}
|
||||
>
|
||||
<MenuItem key={"DEBUG"} value={"DEBUG"}>
|
||||
DEBUG
|
||||
</MenuItem>
|
||||
<MenuItem key={"INFO"} value={"INFO"}>
|
||||
INFO
|
||||
</MenuItem>
|
||||
<MenuItem key={"WARNING"} value={"WARNING"}>
|
||||
WARNING
|
||||
</MenuItem>
|
||||
<MenuItem key={"ERROR"} value={"ERROR"}>
|
||||
ERROR
|
||||
</MenuItem>
|
||||
</TextField>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<MainCard title="HTTP">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor="Hostname-login">Hostname: This will be used to restrict access to your Cosmos Server (Default: 0.0.0.0)</InputLabel>
|
||||
<OutlinedInput
|
||||
id="Hostname-login"
|
||||
type="text"
|
||||
value={values.Hostname}
|
||||
name="Hostname"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
placeholder="Hostname"
|
||||
fullWidth
|
||||
error={Boolean(touched.Hostname && errors.Hostname)}
|
||||
/>
|
||||
{touched.Hostname && errors.Hostname && (
|
||||
<FormHelperText error id="standard-weight-helper-text-Hostname-login">
|
||||
{errors.Hostname}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor="HTTPPort-login">HTTP Port (Default: 80)</InputLabel>
|
||||
<OutlinedInput
|
||||
id="HTTPPort-login"
|
||||
type="text"
|
||||
value={values.HTTPPort}
|
||||
name="HTTPPort"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
placeholder="HTTPPort"
|
||||
fullWidth
|
||||
error={Boolean(touched.HTTPPort && errors.HTTPPort)}
|
||||
/>
|
||||
{touched.HTTPPort && errors.HTTPPort && (
|
||||
<FormHelperText error id="standard-weight-helper-text-HTTPPort-login">
|
||||
{errors.HTTPPort}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor="HTTPSPort-login">HTTPS Port (Default: 443)</InputLabel>
|
||||
<OutlinedInput
|
||||
id="HTTPSPort-login"
|
||||
type="text"
|
||||
value={values.HTTPSPort}
|
||||
name="HTTPSPort"
|
||||
onBlur={handleBlur}
|
||||
onChange={handleChange}
|
||||
placeholder="HTTPSPort"
|
||||
fullWidth
|
||||
error={Boolean(touched.HTTPSPort && errors.HTTPSPort)}
|
||||
/>
|
||||
{touched.HTTPSPort && errors.HTTPSPort && (
|
||||
<FormHelperText error id="standard-weight-helper-text-HTTPSPort-login">
|
||||
{errors.HTTPSPort}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
<br /><br />
|
||||
<MainCard title="Security Certificates">
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Alert severity="info">For security reasons, It is not possible to remotely change the Private keys of any certificates on your instance. It is advised to manually edit the config file, or better, use Environment Variables to store them.</Alert>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<Field
|
||||
type="checkbox"
|
||||
name="GenerateMissingTLSCert"
|
||||
as={FormControlLabel}
|
||||
control={<Checkbox size="large" />}
|
||||
label="Generate missing HTTPS Certificates automatically (Default: true)"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<Field
|
||||
type="checkbox"
|
||||
name="GenerateMissingAuthCert"
|
||||
as={FormControlLabel}
|
||||
control={<Checkbox size="large" />}
|
||||
label="Generate missing Authentication Certificates automatically (Default: true)"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<h4>Authentication Public Key</h4>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<pre className='code'>
|
||||
{config.HTTPConfig.AuthPublicKey}
|
||||
</pre>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<h4>Root HTTPS Public Key</h4>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<pre className='code'>
|
||||
{config.HTTPConfig.TLSCert}
|
||||
</pre>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</MainCard>
|
||||
|
||||
<br /><br />
|
||||
|
||||
<MainCard>
|
||||
{errors.submit && (
|
||||
<Grid item xs={12}>
|
||||
<FormHelperText error>{errors.submit}</FormHelperText>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<AnimateButton>
|
||||
<Button
|
||||
disableElevation
|
||||
disabled={isSubmitting}
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</>}
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default ConfigManagement;
|
126
client/src/pages/config/users/formShortcuts.jsx
Normal file
126
client/src/pages/config/users/formShortcuts.jsx
Normal file
|
@ -0,0 +1,126 @@
|
|||
import * as React from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
Link,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography,
|
||||
FormHelperText,
|
||||
Collapse,
|
||||
TextField,
|
||||
MenuItem,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Accordion,
|
||||
Chip,
|
||||
|
||||
} from '@mui/material';
|
||||
import { Formik, Field } from 'formik';
|
||||
import { DownOutlined, UpOutlined } from '@ant-design/icons';
|
||||
import AnimateButton from '../../../components/@extended/AnimateButton';
|
||||
import RestartModal from './restart';
|
||||
|
||||
|
||||
export const CosmosInputText = ({ name, placeholder, label, formik }) => {
|
||||
return <Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor={name}>{label}</InputLabel>
|
||||
<OutlinedInput
|
||||
id={name}
|
||||
type="text"
|
||||
value={formik.values[name]}
|
||||
name={name}
|
||||
onBlur={formik.handleBlur}
|
||||
onChange={formik.handleChange}
|
||||
placeholder={placeholder}
|
||||
fullWidth
|
||||
error={Boolean(formik.touched[name] && formik.errors[name])}
|
||||
/>
|
||||
{formik.touched[name] && formik.errors[name] && (
|
||||
<FormHelperText error id="standard-weight-helper-text-name-login">
|
||||
{formik.errors[name]}
|
||||
</FormHelperText>
|
||||
)}
|
||||
</Stack>
|
||||
</Grid>
|
||||
}
|
||||
|
||||
export const CosmosSelect = ({ name, label, formik, options }) => {
|
||||
return <Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<InputLabel htmlFor={name}>{label}</InputLabel>
|
||||
<TextField
|
||||
className="px-2 my-2"
|
||||
variant="outlined"
|
||||
name={name}
|
||||
id={name}
|
||||
select
|
||||
value={formik.values[name]}
|
||||
onChange={formik.handleChange}
|
||||
error={
|
||||
formik.touched[name] &&
|
||||
Boolean(formik.errors[name])
|
||||
}
|
||||
helperText={
|
||||
formik.touched[name] && formik.errors[name]
|
||||
}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<MenuItem key={option[0]} value={option[0]}>
|
||||
{option[1]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Stack>
|
||||
</Grid>;
|
||||
}
|
||||
|
||||
export const CosmosCheckbox = ({ name, label, formik }) => {
|
||||
return <Grid item xs={12}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<Field
|
||||
type="checkbox"
|
||||
name={name}
|
||||
as={FormControlLabel}
|
||||
control={<Checkbox size="large" />}
|
||||
label={label}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
}
|
||||
|
||||
export const CosmosCollapse = ({ children, title }) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
return <Grid item xs={12}>
|
||||
<Stack spacing={1}>
|
||||
<Accordion>
|
||||
<AccordionSummary
|
||||
expandIcon={<DownOutlined />}
|
||||
aria-controls="panel1a-content"
|
||||
id="panel1a-header"
|
||||
>
|
||||
<Typography>{title}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{children}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Stack>
|
||||
</Grid>
|
||||
}
|
||||
|
||||
export function CosmosFormDivider({title}) {
|
||||
return <Grid item xs={12}>
|
||||
<Divider>
|
||||
<Chip label={title} />
|
||||
</Divider>
|
||||
</Grid>
|
||||
}
|
169
client/src/pages/config/users/proxyman.jsx
Normal file
169
client/src/pages/config/users/proxyman.jsx
Normal file
|
@ -0,0 +1,169 @@
|
|||
import * as React from 'react';
|
||||
import isLoggedIn from '../../../isLoggedIn';
|
||||
import * as API from '../../../api';
|
||||
import MainCard from '../../../components/MainCard';
|
||||
import { Formik, Field } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
Link,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography,
|
||||
FormHelperText,
|
||||
Collapse,
|
||||
TextField,
|
||||
MenuItem,
|
||||
|
||||
} from '@mui/material';
|
||||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||
import AnimateButton from '../../../components/@extended/AnimateButton';
|
||||
import RestartModal from './restart';
|
||||
import RouteManagement from './routeman';
|
||||
|
||||
|
||||
const ProxyManagement = () => {
|
||||
isLoggedIn();
|
||||
const [config, setConfig] = React.useState(null);
|
||||
const [openModal, setOpenModal] = React.useState(false);
|
||||
const [error, setError] = React.useState(null);
|
||||
|
||||
function updateRoutes(routes) {
|
||||
let con = {
|
||||
...config,
|
||||
HTTPConfig: {
|
||||
...config.HTTPConfig,
|
||||
ProxyConfig: {
|
||||
...config.HTTPConfig.ProxyConfig,
|
||||
Routes: routes,
|
||||
},
|
||||
},
|
||||
};
|
||||
setConfig(con);
|
||||
return con;
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
API.config.get().then((res) => {
|
||||
setConfig(res.data);
|
||||
});
|
||||
}
|
||||
|
||||
function up(key) {
|
||||
if (key > 0) {
|
||||
let tmp = routes[key];
|
||||
routes[key] = routes[key-1];
|
||||
routes[key-1] = tmp;
|
||||
updateRoutes(routes);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteRoute(key) {
|
||||
routes.splice(key, 1);
|
||||
updateRoutes(routes);
|
||||
}
|
||||
|
||||
function down(key) {
|
||||
if (key < routes.length - 1) {
|
||||
let tmp = routes[key];
|
||||
routes[key] = routes[key+1];
|
||||
routes[key+1] = tmp;
|
||||
updateRoutes(routes);
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
refresh();
|
||||
}, []);
|
||||
|
||||
let routes = config && (config.HTTPConfig.ProxyConfig.Routes || []);
|
||||
|
||||
return <div style={{ maxWidth: '1000px', margin: '' }}>
|
||||
<Button variant="contained" color="primary" startIcon={<SyncOutlined />} onClick={() => {
|
||||
refresh();
|
||||
}}>Refresh</Button>
|
||||
<Button variant="contained" color="primary" startIcon={<PlusCircleOutlined />} onClick={() => {
|
||||
routes.push({
|
||||
Name: 'New Route',
|
||||
Description: 'New Route',
|
||||
UseHost: false,
|
||||
Host: '',
|
||||
UsePathPrefix: false,
|
||||
PathPrefix: '',
|
||||
Timeout: 30000,
|
||||
ThrottlePerMinute: 100,
|
||||
CORSOrigin: '',
|
||||
StripPathPrefix: false,
|
||||
Static: false,
|
||||
SPAMode: false,
|
||||
});
|
||||
updateRoutes(routes);
|
||||
}}>Create</Button>
|
||||
|
||||
<br /><br />
|
||||
|
||||
{config && <>
|
||||
<RestartModal openModal={openModal} setOpenModal={setOpenModal} />
|
||||
{routes && routes.map((route,key) => (<>
|
||||
<RouteManagement routeConfig={route} setRouteConfig={(newRoute) => {
|
||||
routes[key] = newRoute;
|
||||
}}
|
||||
up={() => up(key)}
|
||||
down={() => down(key)}
|
||||
deleteRoute={() => deleteRoute(key)}
|
||||
/>
|
||||
<br /><br />
|
||||
</>))}
|
||||
|
||||
{routes &&
|
||||
<MainCard>
|
||||
{error && (
|
||||
<Grid item xs={12}>
|
||||
<FormHelperText error>{error}</FormHelperText>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<AnimateButton>
|
||||
<Button
|
||||
disableElevation
|
||||
disabled={false}
|
||||
fullWidth
|
||||
onClick={() => {
|
||||
API.config.set(updateRoutes(routes)).then(() => {
|
||||
setOpenModal(true);
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
setError(err.message);
|
||||
});
|
||||
}}
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</AnimateButton>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
}
|
||||
{!routes && <>
|
||||
<Typography variant="h6" gutterBottom component="div">
|
||||
No routes configured.
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
</>}
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default ProxyManagement;
|
52
client/src/pages/config/users/restart.jsx
Normal file
52
client/src/pages/config/users/restart.jsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
// material-ui
|
||||
import * as React from 'react';
|
||||
import { Button, Typography } from '@mui/material';
|
||||
import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined , SyncOutlined, UserOutlined, KeyOutlined } from '@ant-design/icons';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import * as API from '../../../api';
|
||||
import MainCard from '../../../components/MainCard';
|
||||
import isLoggedIn from '../../../isLoggedIn';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const RestartModal = ({openModal, setOpenModal}) => {
|
||||
return <>
|
||||
<Dialog open={openModal} onClose={() => setOpenModal(false)}>
|
||||
<DialogTitle>Restart Server</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
A restart is required to apply changes. Do you want to restart?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenModal(false)}>Later</Button>
|
||||
<Button onClick={() => {
|
||||
API.config.restart()
|
||||
.then(() => {
|
||||
refresh();
|
||||
setOpenModal(false);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000)
|
||||
})
|
||||
}}>Restart</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>;
|
||||
};
|
||||
|
||||
export default RestartModal;
|
180
client/src/pages/config/users/routeman.jsx
Normal file
180
client/src/pages/config/users/routeman.jsx
Normal file
|
@ -0,0 +1,180 @@
|
|||
import * as React from 'react';
|
||||
import isLoggedIn from '../../../isLoggedIn';
|
||||
import * as API from '../../../api';
|
||||
import MainCard from '../../../components/MainCard';
|
||||
import { Formik, Field } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
Link,
|
||||
OutlinedInput,
|
||||
Stack,
|
||||
Typography,
|
||||
FormHelperText,
|
||||
Collapse,
|
||||
TextField,
|
||||
MenuItem,
|
||||
Card,
|
||||
Chip,
|
||||
|
||||
} from '@mui/material';
|
||||
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||
import AnimateButton from '../../../components/@extended/AnimateButton';
|
||||
import RestartModal from './restart';
|
||||
import { CosmosCheckbox, CosmosCollapse, CosmosFormDivider, CosmosInputText, CosmosSelect } from './formShortcuts';
|
||||
import { DownOutlined, UpOutlined, CheckOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
|
||||
const RouteManagement = ({ routeConfig, setRouteConfig, up, down, deleteRoute }) => {
|
||||
const [confirmDelete, setConfirmDelete] = React.useState(false);
|
||||
|
||||
return <div style={{ maxWidth: '1000px', margin: '' }}>
|
||||
{routeConfig && <>
|
||||
<Formik
|
||||
initialValues={{
|
||||
Name: routeConfig.Name,
|
||||
Description: routeConfig.Description,
|
||||
Mode: routeConfig.Mode,
|
||||
Target: routeConfig.Target,
|
||||
UseHost: routeConfig.UseHost,
|
||||
Host: routeConfig.Host,
|
||||
UsePathPrefix: routeConfig.UsePathPrefix,
|
||||
PathPrefix: routeConfig.PathPrefix,
|
||||
StripPathPrefix: routeConfig.StripPathPrefix,
|
||||
Timeout: routeConfig.Timeout,
|
||||
ThrottlePerMinute: routeConfig.ThrottlePerMinute,
|
||||
CORSOrigin: routeConfig.CORSOrigin,
|
||||
}}
|
||||
validationSchema={Yup.object().shape({
|
||||
|
||||
})}
|
||||
validate={(values) => {
|
||||
setRouteConfig(values);
|
||||
}}
|
||||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{(formik) => (
|
||||
<form noValidate onSubmit={formik.handleSubmit}>
|
||||
<MainCard title={
|
||||
<div>{routeConfig.Name}
|
||||
<Chip label={<UpOutlined />} onClick={() => up()}/>
|
||||
<Chip label={<DownOutlined />} onClick={() => down()}/>
|
||||
{!confirmDelete && (<Chip label={<DeleteOutlined />} onClick={() => setConfirmDelete(true)}/>)}
|
||||
{confirmDelete && (<Chip label={<CheckOutlined />} onClick={() => deleteRoute()}/>)}
|
||||
</div>
|
||||
}>
|
||||
<Grid container spacing={2}>
|
||||
|
||||
<CosmosInputText
|
||||
name="Name"
|
||||
label="Name"
|
||||
placeholder="Name"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosInputText
|
||||
name="Description"
|
||||
label="Description"
|
||||
placeholder="Description"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosCollapse title="Settings">
|
||||
<Grid container spacing={2}>
|
||||
<CosmosFormDivider title={'Target'}/>
|
||||
|
||||
<CosmosSelect
|
||||
name="Mode"
|
||||
label="Mode"
|
||||
formik={formik}
|
||||
options={[
|
||||
["PROXY", "Proxy"],
|
||||
["STATIC", "Static Folder"],
|
||||
["SPA", "Single Page Application"],
|
||||
]}
|
||||
/>
|
||||
|
||||
<CosmosInputText
|
||||
name="Target"
|
||||
label={formik.values.Mode == "PROXY" ? "Target URL" : "Target Folder Path"}
|
||||
placeholder={formik.values.Mode == "PROXY" ? "localhost:8080" : "/path/to/my/app"}
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosFormDivider title={'Source'}/>
|
||||
|
||||
<CosmosCheckbox
|
||||
name="UseHost"
|
||||
label="Use Host"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
{formik.values.UseHost && <CosmosInputText
|
||||
name="Host"
|
||||
label="Host"
|
||||
placeholder="Host"
|
||||
formik={formik}
|
||||
/>}
|
||||
|
||||
<CosmosCheckbox
|
||||
name="UsePathPrefix"
|
||||
label="Use Path Prefix"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
{formik.values.UsePathPrefix && <CosmosInputText
|
||||
name="PathPrefix"
|
||||
label="Path Prefix"
|
||||
placeholder="Path Prefix"
|
||||
formik={formik}
|
||||
/>}
|
||||
|
||||
{formik.values.UsePathPrefix && <CosmosCheckbox
|
||||
name="StripPathPrefix"
|
||||
label="Strip Path Prefix"
|
||||
formik={formik}
|
||||
/>}
|
||||
|
||||
<CosmosFormDivider title={'Security'}/>
|
||||
|
||||
<CosmosInputText
|
||||
name="Timeout"
|
||||
label="Timeout in milliseconds (0 for no timeout, at least 30000 or less recommended)"
|
||||
placeholder="Timeout"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosInputText
|
||||
name="ThrottlePerMinute"
|
||||
label="Maximum number of requests Per Minute (0 for no limit, at least 100 or less recommended)"
|
||||
placeholder="Throttle Per Minute"
|
||||
formik={formik}
|
||||
/>
|
||||
|
||||
<CosmosInputText
|
||||
name="CORSOrigin"
|
||||
label="Custom CORS Origin (Recommended to leave blank)"
|
||||
placeholder="CORS Origin"
|
||||
formik={formik}
|
||||
/>
|
||||
</Grid>
|
||||
</CosmosCollapse>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</>}
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default RouteManagement;
|
|
@ -18,10 +18,7 @@ import TextField from '@mui/material/TextField';
|
|||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
|
||||
import * as API from '../../../api';
|
||||
|
||||
// project import
|
||||
import MainCard from '../../../components/MainCard';
|
||||
import isLoggedIn from '../../../isLoggedIn';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
@ -53,13 +50,13 @@ const UserManagement = () => {
|
|||
refresh();
|
||||
}, [])
|
||||
|
||||
function sendlink(nickname) {
|
||||
function sendlink(nickname, formType) {
|
||||
API.users.invite({
|
||||
nickname
|
||||
})
|
||||
.then((values) => {
|
||||
let sendLink = window.location.origin + '/register?nickname='+nickname+'&key=' + values.data.registerKey;
|
||||
setToAction({...values.data, nickname, sendLink});
|
||||
let sendLink = window.location.origin + '/ui/register?t='+formType+'&nickname='+nickname+'&key=' + values.data.registerKey;
|
||||
setToAction({...values.data, nickname, sendLink, formType});
|
||||
setOpenInviteForm(true);
|
||||
});
|
||||
}
|
||||
|
@ -216,12 +213,12 @@ const UserManagement = () => {
|
|||
{isRegistered ?
|
||||
(<Button variant="contained" color="primary" onClick={
|
||||
() => {
|
||||
sendlink(row.nickname);
|
||||
sendlink(row.nickname, 1);
|
||||
}
|
||||
}>Send password reset</Button>) :
|
||||
(<Button variant="contained" className={inviteExpired ? 'shinyButton' : ''} onClick={
|
||||
() => {
|
||||
sendlink(row.nickname);
|
||||
sendlink(row.nickname, 2);
|
||||
}
|
||||
} color="primary">Re-Send Invite</Button>)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import path from 'path';
|
||||
import { lazy } from 'react';
|
||||
|
||||
// project import
|
||||
import Loadable from '../components/Loadable';
|
||||
import MinimalLayout from '../layout/MinimalLayout';
|
||||
import Logout from '../pages/authentication/Logoff';
|
||||
|
||||
// render - login
|
||||
const AuthLogin = Loadable(lazy(() => import('../pages/authentication/Login')));
|
||||
|
@ -15,12 +17,16 @@ const LoginRoutes = {
|
|||
element: <MinimalLayout />,
|
||||
children: [
|
||||
{
|
||||
path: 'login',
|
||||
path: '/ui/login',
|
||||
element: <AuthLogin />
|
||||
},
|
||||
{
|
||||
path: 'register',
|
||||
path: '/ui/register',
|
||||
element: <AuthRegister />
|
||||
},
|
||||
{
|
||||
path: '/ui/logout',
|
||||
element: <Logout />
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -4,6 +4,8 @@ import { lazy } from 'react';
|
|||
import Loadable from '../components/Loadable';
|
||||
import MainLayout from '../layout/MainLayout';
|
||||
import UserManagement from '../pages/config/users/usermanagement';
|
||||
import ConfigManagement from '../pages/config/users/configman';
|
||||
import ProxyManagement from '../pages/config/users/proxyman';
|
||||
|
||||
// render - dashboard
|
||||
const DashboardDefault = Loadable(lazy(() => import('../pages/dashboard')));
|
||||
|
@ -24,15 +26,15 @@ const MainRoutes = {
|
|||
element: <MainLayout />,
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
path: '/ui/',
|
||||
element: <DashboardDefault />
|
||||
},
|
||||
{
|
||||
path: 'color',
|
||||
path: '/ui/color',
|
||||
element: <Color />
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
path: '/ui/dashboard',
|
||||
children: [
|
||||
{
|
||||
path: 'default',
|
||||
|
@ -41,19 +43,27 @@ const MainRoutes = {
|
|||
]
|
||||
},
|
||||
{
|
||||
path: 'config/users',
|
||||
path: '/ui/config/users',
|
||||
element: <UserManagement />
|
||||
},
|
||||
{
|
||||
path: 'shadow',
|
||||
path: '/ui/config/general',
|
||||
element: <ConfigManagement />
|
||||
},
|
||||
{
|
||||
path: '/ui/config/proxy',
|
||||
element: <ProxyManagement />
|
||||
},
|
||||
{
|
||||
path: '/ui/shadow',
|
||||
element: <Shadow />
|
||||
},
|
||||
{
|
||||
path: 'typography',
|
||||
path: '/ui/typography',
|
||||
element: <Typography />
|
||||
},
|
||||
{
|
||||
path: 'icons/ant',
|
||||
path: '/ui/icons/ant',
|
||||
element: <AntIcons />
|
||||
}
|
||||
]
|
||||
|
|
|
@ -20,8 +20,8 @@ export const strengthColor = (count) => {
|
|||
// password strength indicator
|
||||
export const strengthIndicator = (number) => {
|
||||
let strengths = 0;
|
||||
if (number.length > 5) strengths += 1;
|
||||
if (number.length > 7) strengths += 1;
|
||||
if (number.length > 9) strengths += 1;
|
||||
if (number.length > 11) strengths += 1;
|
||||
if (hasNumber(number)) strengths += 1;
|
||||
if (hasSpecial(number)) strengths += 1;
|
||||
if (hasMixed(number)) strengths += 1;
|
||||
|
|
|
@ -86,6 +86,6 @@
|
|||
},
|
||||
"description": "Cosmos Server",
|
||||
"name": "cosmos-server",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"wrapInstallFolder": "src"
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
package user
|
||||
package configapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"../utils"
|
||||
)
|
||||
|
||||
func ConfigApiGet(w http.ResponseWriter, req *http.Request) {
|
||||
if AdminOnly(w, req) != nil {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -16,8 +15,8 @@ func ConfigApiGet(w http.ResponseWriter, req *http.Request) {
|
|||
config := utils.GetBaseMainConfig()
|
||||
|
||||
// delete AuthPrivateKey and TLSKey
|
||||
config.AuthPrivateKey = ""
|
||||
config.TLSKey = ""
|
||||
config.HTTPConfig.AuthPrivateKey = ""
|
||||
config.HTTPConfig.TLSKey = ""
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
package user
|
||||
package configapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"../utils"
|
||||
)
|
||||
|
||||
func ConfigApiGet(w http.ResponseWriter, req *http.Request) {
|
||||
if AdminOnly(w, req) != nil {
|
||||
func ConfigApiRestart(w http.ResponseWriter, req *http.Request) {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if(req.Method == "GET") {
|
||||
utils.RestartServer()
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK"
|
||||
"status": "OK",
|
||||
})
|
||||
utils.RestartServer()
|
||||
} else {
|
||||
utils.Error("Restart: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
|
|
18
src/configapi/route.go
Normal file
18
src/configapi/route.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package configapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"../utils"
|
||||
)
|
||||
|
||||
func ConfigRoute(w http.ResponseWriter, req *http.Request) {
|
||||
if(req.Method == "GET") {
|
||||
ConfigApiGet(w, req)
|
||||
} else if (req.Method == "PUT") {
|
||||
ConfigApiSet(w, req)
|
||||
} else {
|
||||
utils.Error("UserRoute: Method not allowed" + req.Method, nil)
|
||||
utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
|
||||
return
|
||||
}
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
package user
|
||||
package configapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"github.com/gorilla/mux"
|
||||
"../utils"
|
||||
)
|
||||
|
||||
func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
||||
if AdminOnly(w, req) != nil {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -32,20 +31,20 @@ func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
// restore AuthPrivateKey and TLSKey
|
||||
config := utils.GetBaseMainConfig()
|
||||
request.AuthPrivateKey = config.AuthPrivateKey
|
||||
request.TLSKey = config.TLSKey
|
||||
request.HTTPConfig.AuthPrivateKey = config.HTTPConfig.AuthPrivateKey
|
||||
request.HTTPConfig.TLSKey = config.HTTPConfig.TLSKey
|
||||
|
||||
err := utils.SaveConfigTofile(request)
|
||||
utils.SaveConfigTofile(request)
|
||||
|
||||
if err != nil {
|
||||
utils.Error("SettingsUpdate: Error saving config to file", err)
|
||||
utils.HTTPError(w, "Error saving config to file",
|
||||
http.StatusInternalServerError, "CS001")
|
||||
return
|
||||
}
|
||||
// if err != nil {
|
||||
// utils.Error("SettingsUpdate: Error saving config to file", err)
|
||||
// utils.HTTPError(w, "Error saving config to file",
|
||||
// http.StatusInternalServerError, "CS001")
|
||||
// return
|
||||
// }
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK"
|
||||
"status": "OK",
|
||||
})
|
||||
} else {
|
||||
utils.Error("SettingsUpdate: Method not allowed" + req.Method, nil)
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"net/http"
|
||||
"./utils"
|
||||
"./user"
|
||||
"./configapi"
|
||||
"./proxy"
|
||||
"github.com/gorilla/mux"
|
||||
"strings"
|
||||
|
@ -159,6 +160,10 @@ func StartServer() {
|
|||
router.Use(tokenMiddleware)
|
||||
router.Use(utils.SetSecurityHeaders)
|
||||
|
||||
router.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/ui", http.StatusMovedPermanently)
|
||||
}))
|
||||
|
||||
srapi := router.PathPrefix("/cosmos").Subrouter()
|
||||
|
||||
srapi.HandleFunc("/api/login", user.UserLogin)
|
||||
|
@ -166,6 +171,8 @@ func StartServer() {
|
|||
srapi.HandleFunc("/api/register", user.UserRegister)
|
||||
srapi.HandleFunc("/api/invite", user.UserResendInviteLink)
|
||||
srapi.HandleFunc("/api/me", user.Me)
|
||||
srapi.HandleFunc("/api/config", configapi.ConfigRoute)
|
||||
srapi.HandleFunc("/api/restart", configapi.ConfigApiRestart)
|
||||
|
||||
srapi.HandleFunc("/api/users/{nickname}", user.UsersIdRoute)
|
||||
srapi.HandleFunc("/api/users", user.UsersRoute)
|
||||
|
@ -188,8 +195,9 @@ func StartServer() {
|
|||
if _, err := os.Stat(pwd + "/static"); os.IsNotExist(err) {
|
||||
utils.Fatal("Static folder not found at " + pwd + "/static", err)
|
||||
}
|
||||
|
||||
fs := spa.SpaHandler(pwd + "/static", "index.html")
|
||||
router.PathPrefix("/").Handler(fs)
|
||||
router.PathPrefix("/ui").Handler(http.StripPrefix("/ui", fs))
|
||||
|
||||
router = proxy.BuildFromConfig(router, config.ProxyConfig)
|
||||
|
||||
|
|
|
@ -13,11 +13,9 @@ func BuildFromConfig(router *mux.Router, config utils.ProxyConfig) *mux.Router {
|
|||
w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
router.PathPrefix("/_static").Handler(http.StripPrefix("/_static", http.FileServer(http.Dir("static"))))
|
||||
|
||||
for i := len(config.Routes)-1; i >= 0; i-- {
|
||||
routeConfig := config.Routes[i]
|
||||
RouterGen(routeConfig.Routing, router, RouteTo(routeConfig.Target))
|
||||
RouterGen(routeConfig, router, RouteTo(routeConfig.Target))
|
||||
}
|
||||
|
||||
return router
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/go-chi/httprate"
|
||||
)
|
||||
|
||||
func RouterGen(route utils.Route, router *mux.Router, destination *httputil.ReverseProxy) *mux.Route {
|
||||
func RouterGen(route utils.ProxyRouteConfig, router *mux.Router, destination *httputil.ReverseProxy) *mux.Route {
|
||||
var realDestination http.Handler
|
||||
realDestination = destination
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ type CreateRequestJSON struct {
|
|||
}
|
||||
|
||||
func UserCreate(w http.ResponseWriter, req *http.Request) {
|
||||
if AdminOnly(w, req) != nil {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ func UserDelete(w http.ResponseWriter, req *http.Request) {
|
|||
vars := mux.Vars(req)
|
||||
nickname := vars["nickname"]
|
||||
|
||||
if AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
if utils.AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ func UserEdit(w http.ResponseWriter, req *http.Request) {
|
|||
vars := mux.Vars(req)
|
||||
nickname := vars["nickname"]
|
||||
|
||||
if AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
if utils.AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ func UserEdit(w http.ResponseWriter, req *http.Request) {
|
|||
toSet := map[string]interface{}{}
|
||||
if request.Email != "" {
|
||||
|
||||
if AdminOnly(w, req) != nil {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ func UserGet(w http.ResponseWriter, req *http.Request) {
|
|||
nickname = req.Header.Get("x-cosmos-user")
|
||||
}
|
||||
|
||||
if AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
if utils.AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
var maxLimit = 100
|
||||
|
||||
func UserList(w http.ResponseWriter, req *http.Request) {
|
||||
if AdminOnly(w, req) != nil {
|
||||
if utils.AdminOnly(w, req) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ func UserResendInviteLink(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
nickname := utils.Sanitize(request.Nickname)
|
||||
|
||||
if AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
if utils.AdminOrItselfOnly(w, req, nickname) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func RefreshUserToken(w http.ResponseWriter, req *http.Request) (utils.User, error) {
|
||||
|
@ -106,7 +105,7 @@ func logOutUser(w http.ResponseWriter) {
|
|||
}
|
||||
|
||||
func redirectToReLogin(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, "/login?invalid=1&redirect=" + req.URL.Path, http.StatusFound)
|
||||
http.Redirect(w, req, "/ui/login?invalid=1&redirect=" + req.URL.Path, http.StatusFound)
|
||||
}
|
||||
|
||||
func SendUserToken(w http.ResponseWriter, user utils.User) {
|
||||
|
@ -157,61 +156,3 @@ func SendUserToken(w http.ResponseWriter, user utils.User) {
|
|||
http.SetCookie(w, &cookie)
|
||||
// http.SetCookie(w, &cookie2)
|
||||
}
|
||||
|
||||
func loggedInOnly(w http.ResponseWriter, req *http.Request) error {
|
||||
userNickname := req.Header.Get("x-cosmos-user")
|
||||
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
||||
isUserLoggedIn := role > 0
|
||||
|
||||
if !isUserLoggedIn || userNickname == "" {
|
||||
utils.Error("LoggedInOnly: User is not logged in", nil)
|
||||
//http.Redirect(w, req, "/login?notlogged=1&redirect=" + req.URL.Path, http.StatusFound)
|
||||
utils.HTTPError(w, "User not logged in", http.StatusUnauthorized, "HTTP004")
|
||||
return errors.New("User not logged in")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AdminOnly(w http.ResponseWriter, req *http.Request) error {
|
||||
userNickname := req.Header.Get("x-cosmos-user")
|
||||
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
||||
isUserLoggedIn := role > 0
|
||||
isUserAdmin := role > 1
|
||||
|
||||
if !isUserLoggedIn || userNickname == "" {
|
||||
utils.Error("AdminOnly: User is not logged in", nil)
|
||||
//http.Redirect(w, req, "/login?notlogged=1&redirect=" + req.URL.Path, http.StatusFound)
|
||||
utils.HTTPError(w, "User not logged in", http.StatusUnauthorized, "HTTP004")
|
||||
return errors.New("User not logged in")
|
||||
}
|
||||
|
||||
if isUserLoggedIn && !isUserAdmin {
|
||||
utils.Error("AdminOnly: User is not admin", nil)
|
||||
utils.HTTPError(w, "User unauthorized", http.StatusUnauthorized, "HTTP005")
|
||||
return errors.New("User not Admin")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AdminOrItselfOnly(w http.ResponseWriter, req *http.Request, nickname string) error {
|
||||
userNickname := req.Header.Get("x-cosmos-user")
|
||||
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
||||
isUserLoggedIn := role > 0
|
||||
isUserAdmin := role > 1
|
||||
|
||||
if !isUserLoggedIn || userNickname == "" {
|
||||
utils.Error("AdminOrItselfOnly: User is not logged in", nil)
|
||||
utils.HTTPError(w, "User not logged in", http.StatusUnauthorized, "HTTP004")
|
||||
return errors.New("User not logged in")
|
||||
}
|
||||
|
||||
if nickname != userNickname && !isUserAdmin {
|
||||
utils.Error("AdminOrItselfOnly: User is not admin", nil)
|
||||
utils.HTTPError(w, "User unauthorized", http.StatusUnauthorized, "HTTP005")
|
||||
return errors.New("User not Admin")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
)
|
||||
|
||||
type Role int
|
||||
type ProxyMode string
|
||||
type LoggingLevel string
|
||||
|
||||
const (
|
||||
|
@ -29,6 +30,12 @@ var LoggingLevelLabels = map[LoggingLevel]int{
|
|||
"ERROR": ERROR,
|
||||
}
|
||||
|
||||
var ProxyModeList = map[string]string{
|
||||
"PROXY": "PROXY",
|
||||
"SPA": "SPA",
|
||||
"STATIC": "STATIC",
|
||||
}
|
||||
|
||||
type FileStats struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
|
@ -78,19 +85,16 @@ type ProxyConfig struct {
|
|||
}
|
||||
|
||||
type ProxyRouteConfig struct {
|
||||
Routing Route `validate:"required"`
|
||||
Target string `validate:"required"`
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
Name string
|
||||
Description string
|
||||
UseHost bool
|
||||
Host string
|
||||
UsePathPrefix bool
|
||||
PathPrefix string
|
||||
Timeout time.Duration
|
||||
ThrottlePerMinute int
|
||||
SPAMode bool
|
||||
CORSOrigin string
|
||||
StripPathPrefix bool
|
||||
Static bool
|
||||
Target string `validate:"required"`
|
||||
Mode ProxyMode
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"math/rand"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var BaseMainConfig Config
|
||||
|
@ -175,7 +176,7 @@ func SaveConfigTofile(config Config) {
|
|||
configFile := GetConfigFileName()
|
||||
CreateDefaultConfigFileIfNecessary()
|
||||
|
||||
file, err := os.OpenFile(configFile, os.O_WRONLY, os.ModePerm)
|
||||
file, err := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm)
|
||||
if err != nil {
|
||||
Fatal("Opening Config File", err)
|
||||
}
|
||||
|
@ -195,3 +196,62 @@ func RestartServer() {
|
|||
Log("Restarting server...")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
|
||||
func loggedInOnly(w http.ResponseWriter, req *http.Request) error {
|
||||
userNickname := req.Header.Get("x-cosmos-user")
|
||||
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
||||
isUserLoggedIn := role > 0
|
||||
|
||||
if !isUserLoggedIn || userNickname == "" {
|
||||
Error("LoggedInOnly: User is not logged in", nil)
|
||||
//http.Redirect(w, req, "/login?notlogged=1&redirect=" + req.URL.Path, http.StatusFound)
|
||||
HTTPError(w, "User not logged in", http.StatusUnauthorized, "HTTP004")
|
||||
return errors.New("User not logged in")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AdminOnly(w http.ResponseWriter, req *http.Request) error {
|
||||
userNickname := req.Header.Get("x-cosmos-user")
|
||||
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
||||
isUserLoggedIn := role > 0
|
||||
isUserAdmin := role > 1
|
||||
|
||||
if !isUserLoggedIn || userNickname == "" {
|
||||
Error("AdminOnly: User is not logged in", nil)
|
||||
//http.Redirect(w, req, "/login?notlogged=1&redirect=" + req.URL.Path, http.StatusFound)
|
||||
HTTPError(w, "User not logged in", http.StatusUnauthorized, "HTTP004")
|
||||
return errors.New("User not logged in")
|
||||
}
|
||||
|
||||
if isUserLoggedIn && !isUserAdmin {
|
||||
Error("AdminOnly: User is not admin", nil)
|
||||
HTTPError(w, "User unauthorized", http.StatusUnauthorized, "HTTP005")
|
||||
return errors.New("User not Admin")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AdminOrItselfOnly(w http.ResponseWriter, req *http.Request, nickname string) error {
|
||||
userNickname := req.Header.Get("x-cosmos-user")
|
||||
role, _ := strconv.Atoi(req.Header.Get("x-cosmos-role"))
|
||||
isUserLoggedIn := role > 0
|
||||
isUserAdmin := role > 1
|
||||
|
||||
if !isUserLoggedIn || userNickname == "" {
|
||||
Error("AdminOrItselfOnly: User is not logged in", nil)
|
||||
HTTPError(w, "User not logged in", http.StatusUnauthorized, "HTTP004")
|
||||
return errors.New("User not logged in")
|
||||
}
|
||||
|
||||
if nickname != userNickname && !isUserAdmin {
|
||||
Error("AdminOrItselfOnly: User is not admin", nil)
|
||||
HTTPError(w, "User unauthorized", http.StatusUnauthorized, "HTTP005")
|
||||
return errors.New("User not Admin")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -8,6 +8,7 @@ export default defineConfig({
|
|||
build: {
|
||||
outDir: '../static',
|
||||
},
|
||||
// base: '/ui',
|
||||
server: {
|
||||
proxy: {
|
||||
'/cosmos/api': {
|
||||
|
|
Loading…
Reference in a new issue