[release] v0.7.0-unstable3
This commit is contained in:
parent
8d6329f8d5
commit
6bc9e02e28
|
@ -36,7 +36,7 @@ const HostChip = ({route, settings, style}) => {
|
|||
window.open(window.location.origin + route.PathPrefix, '_blank');
|
||||
}}
|
||||
onDelete={settings ? () => {
|
||||
window.open('/ui/config-url/'+route.Name, '_blank');
|
||||
window.open('/cosmos-ui/config-url/'+route.Name, '_blank');
|
||||
} : null}
|
||||
deleteIcon={settings ? <SettingOutlined /> : null}
|
||||
/>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// ==============================|| THEME CONFIG ||============================== //
|
||||
|
||||
const config = {
|
||||
defaultPath: '/ui',
|
||||
defaultPath: '/cosmos-ui',
|
||||
fontFamily: `'Public Sans', sans-serif`,
|
||||
i18n: 'en',
|
||||
miniDrawer: false,
|
||||
|
|
|
@ -10,13 +10,13 @@ const IsLoggedIn = () => useEffect(() => {
|
|||
API.auth.me().then((data) => {
|
||||
if(data.status != 'OK') {
|
||||
if(data.status == 'NEW_INSTALL') {
|
||||
window.location.href = '/ui/newInstall';
|
||||
window.location.href = '/cosmos-ui/newInstall';
|
||||
} else if (data.status == 'error' && data.code == "HTTP004") {
|
||||
window.location.href = '/ui/login?redirect=' + redirectTo;
|
||||
window.location.href = '/cosmos-ui/login?redirect=' + redirectTo;
|
||||
} else if (data.status == 'error' && data.code == "HTTP006") {
|
||||
window.location.href = '/ui/loginmfa?redirect=' + redirectTo;
|
||||
window.location.href = '/cosmos-ui/loginmfa?redirect=' + redirectTo;
|
||||
} else if (data.status == 'error' && data.code == "HTTP007") {
|
||||
window.location.href = '/ui/newmfa?redirect=' + redirectTo;
|
||||
window.location.href = '/cosmos-ui/newmfa?redirect=' + redirectTo;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ const HeaderContent = () => {
|
|||
{!matchesXs && <Search />}
|
||||
{matchesXs && <Box sx={{ width: '100%', ml: 1 }} />}
|
||||
|
||||
<Link href="/ui/logout" underline="none">
|
||||
<Link href="/cosmos-ui/logout" underline="none">
|
||||
<Chip label="Logout" />
|
||||
</Link>
|
||||
{/* <Notification /> */}
|
||||
|
|
|
@ -17,7 +17,7 @@ const dashboard = {
|
|||
id: 'home',
|
||||
title: 'Home',
|
||||
type: 'item',
|
||||
url: '/ui/',
|
||||
url: '/cosmos-ui/',
|
||||
icon: icons.HomeOutlined,
|
||||
breadcrumbs: false
|
||||
},
|
||||
|
@ -25,7 +25,7 @@ const dashboard = {
|
|||
id: 'dashboard',
|
||||
title: 'Dashboard',
|
||||
type: 'item',
|
||||
url: '/ui/dashboard',
|
||||
url: '/cosmos-ui/dashboard',
|
||||
icon: DashboardOutlined,
|
||||
breadcrumbs: false
|
||||
},
|
||||
|
@ -33,7 +33,7 @@ const dashboard = {
|
|||
id: 'market',
|
||||
title: 'Market',
|
||||
type: 'item',
|
||||
url: '/ui/market-listing',
|
||||
url: '/cosmos-ui/market-listing',
|
||||
icon: AppstoreAddOutlined,
|
||||
breadcrumbs: false
|
||||
},
|
||||
|
|
|
@ -19,35 +19,35 @@ const pages = {
|
|||
id: 'servapps',
|
||||
title: 'ServApps',
|
||||
type: 'item',
|
||||
url: '/ui/servapps',
|
||||
url: '/cosmos-ui/servapps',
|
||||
icon: AppstoreOutlined
|
||||
},
|
||||
{
|
||||
id: 'url',
|
||||
title: 'URLs',
|
||||
type: 'item',
|
||||
url: '/ui/config-url',
|
||||
url: '/cosmos-ui/config-url',
|
||||
icon: icons.NodeExpandOutlined,
|
||||
},
|
||||
{
|
||||
id: 'users',
|
||||
title: 'Users',
|
||||
type: 'item',
|
||||
url: '/ui/config-users',
|
||||
url: '/cosmos-ui/config-users',
|
||||
icon: icons.ProfileOutlined,
|
||||
},
|
||||
{
|
||||
id: 'openid',
|
||||
title: 'OpenID',
|
||||
type: 'item',
|
||||
url: '/ui/openid-manage',
|
||||
url: '/cosmos-ui/openid-manage',
|
||||
icon: PicLeftOutlined,
|
||||
},
|
||||
{
|
||||
id: 'config',
|
||||
title: 'Configuration',
|
||||
type: 'item',
|
||||
url: '/ui/config-general',
|
||||
url: '/cosmos-ui/config-general',
|
||||
icon: icons.SettingOutlined,
|
||||
}
|
||||
]
|
||||
|
|
|
@ -17,7 +17,7 @@ const Logout = () => {
|
|||
API.auth.logout()
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
window.location.href = '/ui/login';
|
||||
window.location.href = '/cosmos-ui/login';
|
||||
}, 2000);
|
||||
});
|
||||
},[]);
|
||||
|
|
|
@ -15,7 +15,7 @@ const Signup = () => (
|
|||
<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">
|
||||
<Typography component={Link} to="/cosmos-ui/login" variant="body1" sx={{ textDecoration: 'none' }} color="primary">
|
||||
Already have an account?
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
|
|
@ -53,14 +53,14 @@ const AuthLogin = () => {
|
|||
const notLogged = urlSearchParams.get('notlogged') == 1;
|
||||
const notLoggedAdmin = urlSearchParams.get('notlogged') == 2;
|
||||
const invalid = urlSearchParams.get('invalid') == 1;
|
||||
const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/ui';
|
||||
const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/cosmos-ui';
|
||||
|
||||
useEffect(() => {
|
||||
API.auth.me().then((data) => {
|
||||
if(data.status == 'OK') {
|
||||
window.location.href = redirectTo;
|
||||
} else if(data.status == 'NEW_INSTALL') {
|
||||
window.location.href = '/ui/newInstall';
|
||||
window.location.href = '/cosmos-ui/newInstall';
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -190,7 +190,7 @@ const AuthLogin = () => {
|
|||
}
|
||||
label={<Typography variant="h6">Keep me sign in</Typography>}
|
||||
/>*/}
|
||||
{showResetPassword && <Link variant="h6" component={RouterLink} to="/ui/forgot-password" color="primary">
|
||||
{showResetPassword && <Link variant="h6" component={RouterLink} to="/cosmos-ui/forgot-password" color="primary">
|
||||
Forgot Your Password?
|
||||
</Link>}
|
||||
{!showResetPassword && <Typography variant="h6">
|
||||
|
|
|
@ -85,7 +85,7 @@ const AuthRegister = ({nickname, isRegister, isInviteLink, regkey}) => {
|
|||
}).then((res) => {
|
||||
setStatus({ success: true });
|
||||
setSubmitting(false);
|
||||
window.location.href = '/ui/login';
|
||||
window.location.href = '/cosmos-ui/login';
|
||||
}).catch((err) => {
|
||||
setStatus({ success: false });
|
||||
setErrors({ submit: err.message });
|
||||
|
|
|
@ -109,7 +109,7 @@ const ForgotPassword = () => {
|
|||
variant="contained"
|
||||
color="primary"
|
||||
component={Link}
|
||||
to="/ui/login"
|
||||
to="/cosmos-ui/login"
|
||||
>
|
||||
Back to login
|
||||
</Button>
|
||||
|
|
|
@ -35,14 +35,14 @@ import { CosmosCollapse } from '../config/users/formShortcuts';
|
|||
|
||||
const MFALoginForm = () => {
|
||||
const urlSearchParams = new URLSearchParams(window.location.search);
|
||||
const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/ui';
|
||||
const redirectTo = urlSearchParams.get('redirect') ? urlSearchParams.get('redirect') : '/cosmos-ui';
|
||||
|
||||
useEffect(() => {
|
||||
API.auth.me().then((data) => {
|
||||
if(data.status == 'OK') {
|
||||
window.location.href = redirectTo;
|
||||
} else if(data.status == 'NEW_INSTALL') {
|
||||
window.location.href = '/ui/newInstall';
|
||||
window.location.href = '/cosmos-ui/newInstall';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -149,7 +149,7 @@ const MFASetup = () => {
|
|||
<MFALoginForm />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Link to="/ui/logout">
|
||||
<Link to="/cosmos-ui/logout">
|
||||
<Typography variant="h5">Logout</Typography>
|
||||
</Link>
|
||||
</Grid>
|
||||
|
|
|
@ -150,7 +150,7 @@ const ProxyManagement = () => {
|
|||
{routes && <PrettyTableView
|
||||
data={routes}
|
||||
getKey={(r) => r.Name + r.Target + r.Mode}
|
||||
linkTo={(r) => '/ui/config-url/' + r.Name}
|
||||
linkTo={(r) => '/cosmos-ui/config-url/' + r.Name}
|
||||
columns={[
|
||||
{
|
||||
title: '',
|
||||
|
|
|
@ -57,7 +57,7 @@ const UserManagement = () => {
|
|||
formType: ""+formType,
|
||||
})
|
||||
.then((values) => {
|
||||
let sendLink = window.location.origin + '/ui/register?t='+formType+'&nickname='+nickname+'&key=' + values.data.registerKey;
|
||||
let sendLink = window.location.origin + '/cosmos-ui/register?t='+formType+'&nickname='+nickname+'&key=' + values.data.registerKey;
|
||||
setToAction({...values.data, nickname, sendLink, formType, formAction: formType === 2 ? 'invite them to the server' : 'let them reset their password'});
|
||||
setOpenInviteForm(true);
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Box, Stack } from "@mui/material";
|
||||
import { Box, CircularProgress, Stack } from "@mui/material";
|
||||
import { HomeBackground, TransparentHeader } from "../home";
|
||||
import { useEffect, useState } from "react";
|
||||
import * as API from "../../api";
|
||||
|
@ -70,7 +70,7 @@ function ShowcasesItem({ isDark, item }) {
|
|||
<Button className="CheckButton" color="primary" variant="contained">
|
||||
Install
|
||||
</Button>
|
||||
<Link to={"/ui/market-listing/" + item.name} style={{
|
||||
<Link to={"/cosmos-ui/market-listing/" + item.name} style={{
|
||||
textDecoration: 'none',
|
||||
}}>
|
||||
<Button className="CheckButton" color="primary" variant="outlined">
|
||||
|
@ -133,7 +133,6 @@ const MarketPage = () => {
|
|||
return <>
|
||||
<HomeBackground />
|
||||
<TransparentHeader />
|
||||
|
||||
{openedApp && <Box style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
|
@ -143,7 +142,7 @@ const MarketPage = () => {
|
|||
zIndex: 1300,
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
}}>
|
||||
<Link to="/ui/market-listing" as={Box}
|
||||
<Link to="/cosmos-ui/market-listing" as={Box}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
|
@ -169,7 +168,7 @@ const MarketPage = () => {
|
|||
}
|
||||
}}>
|
||||
|
||||
<Link to="/ui/market-listing" style={{
|
||||
<Link to="/cosmos-ui/market-listing" style={{
|
||||
textDecoration: 'none',
|
||||
}}>
|
||||
<Button className="CheckButton" color="primary" variant="outlined">
|
||||
|
@ -199,7 +198,7 @@ const MarketPage = () => {
|
|||
}}></div>
|
||||
|
||||
<div>
|
||||
<DockerComposeImport installer defaultName={openedApp.name} dockerComposeInit={openedApp.compose} />
|
||||
<DockerComposeImport installerInit defaultName={openedApp.name} dockerComposeInit={openedApp.compose} />
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
@ -207,7 +206,19 @@ const MarketPage = () => {
|
|||
|
||||
<Stack style={{ position: 'relative' }} spacing={1}>
|
||||
<Stack style={{ height: '35vh' }} spacing={1}>
|
||||
<Showcases showcase={showcase} isDark={isDark}/>
|
||||
{(!showcase || !Object.keys(showcase).length) && <Box style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 1000,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<CircularProgress
|
||||
size={100}
|
||||
/>
|
||||
</Box>}
|
||||
{showcase && showcase.length && <Showcases showcase={showcase} isDark={isDark}/>}
|
||||
</Stack>
|
||||
|
||||
<Stack spacing={1} style={{
|
||||
|
@ -218,12 +229,26 @@ const MarketPage = () => {
|
|||
minHeight: 'calc(65vh - 80px)',
|
||||
padding: '24px',
|
||||
}}>
|
||||
<Grid2 container spacing={{ xs: 1, sm: 1, md: 2 }}>
|
||||
{apps && Object.keys(apps).length > 0 && apps[Object.keys(apps)[0]].map((app) => {
|
||||
{(!apps || !Object.keys(apps).length) && <Box style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 1000,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginTop: '150px',
|
||||
}}>
|
||||
<CircularProgress
|
||||
size={100}
|
||||
/>
|
||||
</Box>}
|
||||
|
||||
{apps && Object.keys(apps).length > 0 && <Grid2 container spacing={{ xs: 1, sm: 1, md: 2 }}>
|
||||
{apps[Object.keys(apps)[0]].map((app) => {
|
||||
return <Grid2 style={{
|
||||
...gridAnim,
|
||||
cursor: 'pointer',
|
||||
}} xs={12} sm={12} md={6} lg={4} xl={3} key={app.name} item><Link to={"/ui/market-listing/" + app.name} style={{
|
||||
}} xs={12} sm={12} md={6} lg={4} xl={3} key={app.name} item><Link to={"/cosmos-ui/market-listing/" + app.name} style={{
|
||||
textDecoration: 'none',
|
||||
}}>
|
||||
<div key={app.name} style={appCardStyle(theme)}>
|
||||
|
@ -248,7 +273,7 @@ const MarketPage = () => {
|
|||
</Link>
|
||||
</Grid2>
|
||||
})}
|
||||
</Grid2>
|
||||
</Grid2>}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</>
|
||||
|
|
|
@ -63,7 +63,7 @@ const NewInstall = () => {
|
|||
setStatus(res.data);
|
||||
} catch(error) {
|
||||
if(error.status == 401)
|
||||
window.location.href = "/ui/login";
|
||||
window.location.href = "/cosmos-ui/login";
|
||||
}
|
||||
if (typeof status !== 'undefined') {
|
||||
setTimeout(() => {
|
||||
|
@ -577,7 +577,7 @@ const NewInstall = () => {
|
|||
step: "5",
|
||||
})
|
||||
setTimeout(() => {
|
||||
window.location.href = hostname + "/ui/login";
|
||||
window.location.href = hostname + "/cosmos-ui/login";
|
||||
}, 500);
|
||||
} else
|
||||
setActiveStep(activeStep + 1)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// material-ui
|
||||
import * as React from 'react';
|
||||
import { Alert, Button, FormLabel, Stack, Typography } from '@mui/material';
|
||||
import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined, SyncOutlined, UserOutlined, KeyOutlined, ArrowUpOutlined, FileZipOutlined, ArrowDownOutlined } from '@ant-design/icons';
|
||||
import { Alert, Button, Checkbox, FormControlLabel, FormLabel, Stack, Typography } from '@mui/material';
|
||||
import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined, SyncOutlined, UserOutlined, KeyOutlined, ArrowUpOutlined, FileZipOutlined, ArrowDownOutlined, ConsoleSqlOutlined } from '@ant-design/icons';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
|
@ -26,6 +26,10 @@ import ResponsiveButton from '../../../components/responseiveButton';
|
|||
import UploadButtons from '../../../components/fileUpload';
|
||||
import NewDockerService from './newService';
|
||||
import yaml from 'js-yaml';
|
||||
import { CosmosCollapse, CosmosFormDivider, CosmosInputText } from '../../config/users/formShortcuts';
|
||||
import VolumeContainerSetup from './volumes';
|
||||
import DockerContainerSetup from './setup';
|
||||
import whiskers from 'whiskers';
|
||||
|
||||
function checkIsOnline() {
|
||||
API.isOnline().then((res) => {
|
||||
|
@ -63,11 +67,15 @@ const preStyle = {
|
|||
marginRight: '0',
|
||||
}
|
||||
|
||||
const isNewerVersion = (v1, v2) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
const getHostnameFromName = (name) => {
|
||||
return name.replace('/', '').replace(/_/g, '-').replace(/[^a-zA-Z0-9-]/g, '').toLowerCase().replace(/\s/g, '-') + '.' + window.location.origin.split('://')[1]
|
||||
}
|
||||
|
||||
const DockerComposeImport = ({ refresh, dockerComposeInit, installer, defaultName }) => {
|
||||
const DockerComposeImport = ({ refresh, dockerComposeInit, installerInit, defaultName }) => {
|
||||
const [step, setStep] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
|
@ -75,254 +83,371 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installer, defaultNam
|
|||
const [service, setService] = useState({});
|
||||
const [ymlError, setYmlError] = useState('');
|
||||
const [serviceName, setServiceName] = useState(defaultName || 'my-service');
|
||||
const [hostnames, setHostnames] = useState([]);
|
||||
const [hostnames, setHostnames] = useState({});
|
||||
const [overrides, setOverrides] = useState({});
|
||||
const [context, setContext] = useState({});
|
||||
const [installer, setInstaller] = useState(installerInit);
|
||||
|
||||
useEffect(() => {
|
||||
const text = fetch(dockerComposeInit)
|
||||
.then((res) => res.text())
|
||||
.then((text) => {
|
||||
setDockerCompose(text);
|
||||
});
|
||||
}, [dockerComposeInit]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!openModal) {
|
||||
if (!openModal) {
|
||||
return;
|
||||
}
|
||||
// fetch(dockerComposeInit)
|
||||
// .then((res) => res.text())
|
||||
// .then((text) => {
|
||||
// setDockerCompose(text);
|
||||
// });
|
||||
if(dockerComposeInit)
|
||||
setDockerCompose(`
|
||||
{
|
||||
"cosmos-installer": {
|
||||
"form": [
|
||||
{
|
||||
"name": "caca",
|
||||
"label": "Caca?",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
},
|
||||
"services": {
|
||||
"{ServiceName}": {
|
||||
"image": "lscr.io/linuxserver/jellyfin:latest",
|
||||
"container_name": "{ServiceName}",
|
||||
"restart": "unless-stopped",
|
||||
"environment": [
|
||||
"PUID=1000",
|
||||
"PGID=1000",
|
||||
"TZ=auto"
|
||||
],
|
||||
"labels": {
|
||||
"cosmos-force-network-secured": "true",
|
||||
"caca": "{Context.caca}"
|
||||
},
|
||||
"volumes": [{
|
||||
"source": "{ServiceName}-config",
|
||||
"target": "/config",
|
||||
"type": "volume"
|
||||
}],
|
||||
"routes": [
|
||||
{
|
||||
"name": "{ServiceName}",
|
||||
"description": "Expose {ServiceName} to the web",
|
||||
"useHost": true,
|
||||
"target": "http://{ServiceName}:8096",
|
||||
"mode": "SERVAPP"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
}, [openModal, dockerComposeInit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!openModal || installer) {
|
||||
return;
|
||||
}
|
||||
|
||||
setYmlError('');
|
||||
if (dockerCompose === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
let isJson = dockerCompose.trim().startsWith('{') && dockerCompose.trim().endsWith('}');
|
||||
let isJson = dockerCompose && dockerCompose.trim().startsWith('{') && dockerCompose.trim().endsWith('}');
|
||||
|
||||
// if Json
|
||||
if (isJson) {
|
||||
try {
|
||||
let doc = JSON.parse(dockerCompose);
|
||||
|
||||
if(installer) {
|
||||
doc = JSON.parse(dockerCompose.replace(/\{\{self\}\}/gi, serviceName));
|
||||
|
||||
// check hostnames for each service
|
||||
let hostnames = [];
|
||||
|
||||
if(doc.services)
|
||||
Object.keys(doc.services).forEach((key) => {
|
||||
if (doc.services[key].routes) {
|
||||
let routeId = 0;
|
||||
doc.services[key].routes.forEach((route) => {
|
||||
if (route.useHost) {
|
||||
let newRoute = Object.assign({}, route);
|
||||
if(route.useHost === true) {
|
||||
newRoute.host = getHostnameFromName(key + (routeId > 0 ? '-' + routeId : ''))
|
||||
}
|
||||
hostnames.push(newRoute);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setHostnames(hostnames);
|
||||
if(typeof doc['cosmos-installer'] === 'object') {
|
||||
setInstaller(true);
|
||||
}
|
||||
|
||||
setService(doc);
|
||||
} catch (e) {
|
||||
setYmlError(e.message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// if Yml
|
||||
let doc;
|
||||
let newService = {};
|
||||
try {
|
||||
doc = yaml.load(dockerCompose);
|
||||
// if Yml
|
||||
let doc;
|
||||
let newService = {};
|
||||
try {
|
||||
doc = yaml.load(dockerCompose);
|
||||
|
||||
if (typeof doc === 'object' && doc !== null && Object.keys(doc).length > 0 &&
|
||||
!doc.services && !doc.networks && !doc.volumes) {
|
||||
doc = {
|
||||
services: Object.assign({}, doc)
|
||||
if (typeof doc === 'object' && doc !== null && Object.keys(doc).length > 0 &&
|
||||
!doc.services && !doc.networks && !doc.volumes) {
|
||||
doc = {
|
||||
services: Object.assign({}, doc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert to the proper format
|
||||
if (doc.services) {
|
||||
Object.keys(doc.services).forEach((key) => {
|
||||
// convert to the proper format
|
||||
if (doc.services) {
|
||||
Object.keys(doc.services).forEach((key) => {
|
||||
|
||||
// convert volumes
|
||||
if (doc.services[key].volumes) {
|
||||
if(Array.isArray(doc.services[key].volumes)) {
|
||||
let volumes = [];
|
||||
doc.services[key].volumes.forEach((volume) => {
|
||||
if (typeof volume === 'object') {
|
||||
volumes.push(volume);
|
||||
} else {
|
||||
let volumeSplit = volume.split(':');
|
||||
let volumeObj = {
|
||||
Source: volumeSplit[0],
|
||||
Target: volumeSplit[1],
|
||||
Type: volume[0] === '/' ? 'bind' : 'volume',
|
||||
};
|
||||
volumes.push(volumeObj);
|
||||
}
|
||||
});
|
||||
doc.services[key].volumes = volumes;
|
||||
// convert volumes
|
||||
if (doc.services[key].volumes) {
|
||||
if (Array.isArray(doc.services[key].volumes)) {
|
||||
let volumes = [];
|
||||
doc.services[key].volumes.forEach((volume) => {
|
||||
if (typeof volume === 'object') {
|
||||
volumes.push(volume);
|
||||
} else {
|
||||
let volumeSplit = volume.split(':');
|
||||
let volumeObj = {
|
||||
Source: volumeSplit[0],
|
||||
Target: volumeSplit[1],
|
||||
Type: volume[0] === '/' ? 'bind' : 'volume',
|
||||
};
|
||||
volumes.push(volumeObj);
|
||||
}
|
||||
});
|
||||
doc.services[key].volumes = volumes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convert expose
|
||||
if (doc.services[key].expose) {
|
||||
doc.services[key].expose = doc.services[key].expose.map((port) => {
|
||||
return '' + port;
|
||||
})
|
||||
}
|
||||
|
||||
//convert user
|
||||
if (doc.services[key].user) {
|
||||
doc.services[key].user = '' + doc.services[key].user;
|
||||
}
|
||||
|
||||
// convert labels:
|
||||
if (doc.services[key].labels) {
|
||||
if (Array.isArray(doc.services[key].labels)) {
|
||||
let labels = {};
|
||||
doc.services[key].labels.forEach((label) => {
|
||||
const [key, value] = label.split('=');
|
||||
labels['' + key] = '' + value;
|
||||
});
|
||||
doc.services[key].labels = labels;
|
||||
// convert expose
|
||||
if (doc.services[key].expose) {
|
||||
doc.services[key].expose = doc.services[key].expose.map((port) => {
|
||||
return '' + port;
|
||||
})
|
||||
}
|
||||
if (typeof doc.services[key].labels == 'object') {
|
||||
let labels = {};
|
||||
Object.keys(doc.services[key].labels).forEach((keylabel) => {
|
||||
labels['' + keylabel] = '' + doc.services[key].labels[keylabel];
|
||||
});
|
||||
doc.services[key].labels = labels;
|
||||
}
|
||||
}
|
||||
|
||||
// convert environment
|
||||
if (doc.services[key].environment) {
|
||||
if (!Array.isArray(doc.services[key].environment)) {
|
||||
let environment = [];
|
||||
Object.keys(doc.services[key].environment).forEach((keyenv) => {
|
||||
environment.push(keyenv + '=' + doc.services[key].environment[keyenv]);
|
||||
});
|
||||
doc.services[key].environment = environment;
|
||||
//convert user
|
||||
if (doc.services[key].user) {
|
||||
doc.services[key].user = '' + doc.services[key].user;
|
||||
}
|
||||
}
|
||||
|
||||
// convert network
|
||||
if (doc.services[key].networks) {
|
||||
if (Array.isArray(doc.services[key].networks)) {
|
||||
let networks = {};
|
||||
doc.services[key].networks.forEach((network) => {
|
||||
if (typeof network === 'object') {
|
||||
networks['' + network.name] = network;
|
||||
}
|
||||
else
|
||||
networks['' + network] = {};
|
||||
});
|
||||
doc.services[key].networks = networks;
|
||||
// convert labels:
|
||||
if (doc.services[key].labels) {
|
||||
if (Array.isArray(doc.services[key].labels)) {
|
||||
let labels = {};
|
||||
doc.services[key].labels.forEach((label) => {
|
||||
const [key, value] = label.split('=');
|
||||
labels['' + key] = '' + value;
|
||||
});
|
||||
doc.services[key].labels = labels;
|
||||
}
|
||||
if (typeof doc.services[key].labels == 'object') {
|
||||
let labels = {};
|
||||
Object.keys(doc.services[key].labels).forEach((keylabel) => {
|
||||
labels['' + keylabel] = '' + doc.services[key].labels[keylabel];
|
||||
});
|
||||
doc.services[key].labels = labels;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ensure container_name
|
||||
if (!doc.services[key].container_name) {
|
||||
doc.services[key].container_name = key;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// convert networks
|
||||
if (doc.networks) {
|
||||
if (Array.isArray(doc.networks)) {
|
||||
let networks = {};
|
||||
doc.networks.forEach((network) => {
|
||||
if (typeof network === 'object') {
|
||||
networks['' + network.name] = network;
|
||||
// convert environment
|
||||
if (doc.services[key].environment) {
|
||||
if (!Array.isArray(doc.services[key].environment)) {
|
||||
let environment = [];
|
||||
Object.keys(doc.services[key].environment).forEach((keyenv) => {
|
||||
environment.push(keyenv + '=' + doc.services[key].environment[keyenv]);
|
||||
});
|
||||
doc.services[key].environment = environment;
|
||||
}
|
||||
}
|
||||
|
||||
// convert network
|
||||
if (doc.services[key].networks) {
|
||||
if (Array.isArray(doc.services[key].networks)) {
|
||||
let networks = {};
|
||||
doc.services[key].networks.forEach((network) => {
|
||||
if (typeof network === 'object') {
|
||||
networks['' + network.name] = network;
|
||||
}
|
||||
else
|
||||
networks['' + network] = {};
|
||||
});
|
||||
doc.services[key].networks = networks;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure container_name
|
||||
if (!doc.services[key].container_name) {
|
||||
doc.services[key].container_name = key;
|
||||
}
|
||||
else
|
||||
networks['' + network] = {};
|
||||
});
|
||||
doc.networks = networks;
|
||||
} else {
|
||||
let networks = {};
|
||||
Object.keys(doc.networks).forEach((key) => {
|
||||
networks['' + key] = doc.networks[key] || {};
|
||||
});
|
||||
doc.networks = networks;
|
||||
}
|
||||
}
|
||||
|
||||
// convert volumes
|
||||
if (doc.volumes) {
|
||||
if (Array.isArray(doc.volumes)) {
|
||||
let volumes = {};
|
||||
doc.volumes.forEach((volume) => {
|
||||
if(!volume) {
|
||||
volume = {};
|
||||
}
|
||||
if (typeof volume === 'object') {
|
||||
volumes['' + volume.name] = volume;
|
||||
}
|
||||
else
|
||||
volumes['' + volume] = {};
|
||||
});
|
||||
doc.volumes = volumes;
|
||||
} else {
|
||||
let volumes = {};
|
||||
Object.keys(doc.volumes).forEach((key) => {
|
||||
volumes['' + key] = doc.volumes[key] || {};
|
||||
});
|
||||
doc.volumes = volumes;
|
||||
// convert networks
|
||||
if (doc.networks) {
|
||||
if (Array.isArray(doc.networks)) {
|
||||
let networks = {};
|
||||
doc.networks.forEach((network) => {
|
||||
if (typeof network === 'object') {
|
||||
networks['' + network.name] = network;
|
||||
}
|
||||
else
|
||||
networks['' + network] = {};
|
||||
});
|
||||
doc.networks = networks;
|
||||
} else {
|
||||
let networks = {};
|
||||
Object.keys(doc.networks).forEach((key) => {
|
||||
networks['' + key] = doc.networks[key] || {};
|
||||
});
|
||||
doc.networks = networks;
|
||||
}
|
||||
}
|
||||
|
||||
// convert volumes
|
||||
if (doc.volumes) {
|
||||
if (Array.isArray(doc.volumes)) {
|
||||
let volumes = {};
|
||||
doc.volumes.forEach((volume) => {
|
||||
if (!volume) {
|
||||
volume = {};
|
||||
}
|
||||
if (typeof volume === 'object') {
|
||||
volumes['' + volume.name] = volume;
|
||||
}
|
||||
else
|
||||
volumes['' + volume] = {};
|
||||
});
|
||||
doc.volumes = volumes;
|
||||
} else {
|
||||
let volumes = {};
|
||||
Object.keys(doc.volumes).forEach((key) => {
|
||||
volumes['' + key] = doc.volumes[key] || {};
|
||||
});
|
||||
doc.volumes = volumes;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setYmlError(e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setYmlError(e.message);
|
||||
return;
|
||||
setService(doc);
|
||||
}
|
||||
|
||||
setService(doc);
|
||||
}
|
||||
}, [openModal, dockerCompose]);
|
||||
|
||||
useEffect(() => {
|
||||
if(!openModal) {
|
||||
setOverrides({});
|
||||
}, [serviceName, hostnames, context]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!openModal || dockerCompose === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (installer) {
|
||||
let doc = JSON.parse(dockerCompose.replace(/\{\{self\}\}/gi, serviceName));
|
||||
const rendered = whiskers.render(dockerCompose, {
|
||||
ServiceName: serviceName,
|
||||
Hostnames: hostnames,
|
||||
Context: context,
|
||||
});
|
||||
|
||||
if(doc.services)
|
||||
Object.keys(doc.services).forEach((key) => {
|
||||
if (doc.services[key].routes) {
|
||||
doc.services[key].routes.forEach((route, index) => {
|
||||
doc.services[key].routes[index] = {
|
||||
...hostnames[index],
|
||||
name: route.name,
|
||||
description: route.description,
|
||||
};
|
||||
const jsoned = JSON.parse(rendered);
|
||||
|
||||
if (jsoned.services) {
|
||||
// GENERATE HOSTNAMES FORM
|
||||
let newHostnames = {};
|
||||
Object.keys(jsoned.services).forEach((key) => {
|
||||
if (jsoned.services[key].routes) {
|
||||
let routeId = 0;
|
||||
jsoned.services[key].routes.forEach((route) => {
|
||||
if (route.useHost) {
|
||||
let newRoute = Object.assign({}, route);
|
||||
if (route.useHost === true) {
|
||||
newRoute.host = getHostnameFromName(key + (routeId > 0 ? '-' + routeId : ''))
|
||||
}
|
||||
|
||||
if(!newHostnames[key]) newHostnames[key] = {};
|
||||
|
||||
|
||||
if(!newHostnames[key][route.name]) {
|
||||
newHostnames[key][route.name] = newRoute;
|
||||
if(hostnames[key] && hostnames[key][route.name]) {
|
||||
newHostnames[key][route.name].host = hostnames[key][route.name].host;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setService(doc);
|
||||
if(JSON.stringify(newHostnames) !== JSON.stringify(hostnames))
|
||||
setHostnames({...newHostnames});
|
||||
|
||||
let newVolumes = [];
|
||||
|
||||
Object.keys(jsoned.services).forEach((key) => {
|
||||
// APPLY OVERRIDE
|
||||
if (overrides[key]) {
|
||||
// prevent customizing static volumes
|
||||
if (jsoned.services[key].volumes && jsoned['cosmos-installer'] && jsoned['cosmos-installer']['frozen-volumes']) {
|
||||
jsoned['cosmos-installer']['frozen-volumes'].forEach((volume) => {
|
||||
delete overrides[key].volumes;
|
||||
});
|
||||
}
|
||||
|
||||
jsoned.services[key] = {
|
||||
...jsoned.services[key],
|
||||
...overrides[key],
|
||||
};
|
||||
}
|
||||
|
||||
// APPLY HOSTNAMES
|
||||
if (hostnames[key]) {
|
||||
if (jsoned.services[key].routes) {
|
||||
jsoned.services[key].routes.forEach((route) => {
|
||||
if (hostnames[key][route.name]) {
|
||||
route.host = hostnames[key][route.name].host;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// CREATE NEW VOLUMES
|
||||
if (jsoned.services[key].volumes) {
|
||||
jsoned.services[key].volumes.forEach((volume) => {
|
||||
if (typeof volume === 'object' && !volume.existing) {
|
||||
newVolumes.push(volume);
|
||||
} else if (typeof volume === 'object' && volume.existing) {
|
||||
delete volume.existing;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (newVolumes.length > 0) {
|
||||
jsoned.volumes = jsoned.volumes || {};
|
||||
newVolumes.forEach((volume) => {
|
||||
jsoned.volumes[volume.source] = {
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setService(jsoned);
|
||||
}
|
||||
} catch (e) {
|
||||
setYmlError(e.message);
|
||||
return;
|
||||
}
|
||||
}, [openModal, installer, serviceName, hostnames]);
|
||||
}, [openModal, dockerCompose, serviceName, hostnames, overrides]);
|
||||
|
||||
const openModalFunc = () => {
|
||||
setOpenModal(true);
|
||||
setStep(0);
|
||||
setService({});
|
||||
setYmlError(null);
|
||||
setOverrides({});
|
||||
setHostnames({});
|
||||
setDockerCompose('');
|
||||
setInstaller(installerInit);
|
||||
setServiceName(defaultName || 'default-name');
|
||||
}
|
||||
|
||||
return <>
|
||||
<Dialog open={openModal} onClose={() => setOpenModal(false)} >
|
||||
<Dialog open={openModal} onClose={() => setOpenModal(false)}>
|
||||
<DialogTitle>{installer ? "Installation" : "Import Compose File"}</DialogTitle>
|
||||
<DialogContent style={{width: '800px', maxWidth: '100%'}}>
|
||||
<DialogContent style={{ width: '800px', maxWidth: '100%' }}>
|
||||
<DialogContentText>
|
||||
{step === 0 && !installer && <><Stack spacing={2}>
|
||||
<Alert severity="warning" icon={<WarningOutlined />}>
|
||||
|
@ -361,26 +486,118 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installer, defaultNam
|
|||
</Stack></>}
|
||||
|
||||
{step === 0 && installer && <><Stack spacing={2}>
|
||||
<div style={{ color: 'red' }}>
|
||||
{ymlError}
|
||||
</div>
|
||||
{!ymlError && (<><FormLabel>Choose your service name</FormLabel>
|
||||
<div style={{ color: 'red' }}>
|
||||
{ymlError}
|
||||
</div>
|
||||
|
||||
{!ymlError && (<><FormLabel>Choose your service name</FormLabel>
|
||||
|
||||
<TextField label="Service Name" value={serviceName} onChange={(e) => setServiceName(e.target.value)} />
|
||||
{hostnames.map((hostname, index) => {
|
||||
return <>
|
||||
<FormLabel>Choose URL for {hostname.name}</FormLabel>
|
||||
<div style={{ opacity: 0.9, fontSize: '0.8em', textDecoration: 'italic'}}
|
||||
>{hostname.description}</div>
|
||||
<TextField key={index} label="Hostname" value={hostname.host} onChange={(e) => {
|
||||
const newHostnames = [...hostnames];
|
||||
newHostnames[index].host = e.target.value;
|
||||
setHostnames(newHostnames);
|
||||
}} />
|
||||
</>
|
||||
|
||||
{service['cosmos-installer'] && service['cosmos-installer'].form.map((formElement) => {
|
||||
return formElement.type === 'checkbox' ? <FormControlLabel
|
||||
control={<Checkbox checked={context[formElement.name] || formElement.initialValue} onChange={(e) => {
|
||||
setContext({ ...context, [formElement.name]: e.target.checked });
|
||||
}
|
||||
} />}
|
||||
label={formElement.label}
|
||||
/> : <TextField
|
||||
label={formElement.label}
|
||||
value={context[formElement.name] || formElement.initialValue}
|
||||
onChange={(e) => {
|
||||
setContext({ ...context, [formElement.name]: e.target.value });
|
||||
}
|
||||
} />
|
||||
})}
|
||||
</>)}
|
||||
|
||||
{Object.keys(hostnames).map((serviceIndex) => {
|
||||
const service = hostnames[serviceIndex];
|
||||
return Object.keys(service).map((hostIndex) => {
|
||||
const hostname = service[hostIndex];
|
||||
return <>
|
||||
<FormLabel>Choose URL for {hostname.name}</FormLabel>
|
||||
<div style={{ opacity: 0.9, fontSize: '0.8em', textDecoration: 'italic' }}
|
||||
>{hostname.description}</div>
|
||||
<TextField key={serviceIndex + hostIndex} label="Hostname" value={hostname.host} onChange={(e) => {
|
||||
hostnames[serviceIndex][hostname.name].host = e.target.value;
|
||||
setHostnames({...hostnames});
|
||||
}} />
|
||||
</>
|
||||
})
|
||||
})}
|
||||
|
||||
{service && service.services && Object.values(service.services).map((value) => {
|
||||
return <CosmosCollapse title={`Customize ${value.container_name}`}>
|
||||
<Stack spacing={2}>
|
||||
<DockerContainerSetup
|
||||
newContainer
|
||||
containerInfo={{
|
||||
Name: '',
|
||||
Image: '',
|
||||
Config: {
|
||||
Env: value.environment || [],
|
||||
Labels: value.labels || {},
|
||||
},
|
||||
HostConfig: {
|
||||
RestartPolicy: {},
|
||||
}
|
||||
}}
|
||||
OnChange={(containerInfo) => {
|
||||
setOverrides({
|
||||
...overrides,
|
||||
[value.container_name]: {
|
||||
environment: containerInfo.envVars,
|
||||
labels: containerInfo.labels,
|
||||
}
|
||||
})
|
||||
}}
|
||||
noCard
|
||||
installer
|
||||
/>
|
||||
<CosmosFormDivider title="Volumes" />
|
||||
<VolumeContainerSetup
|
||||
newContainer
|
||||
frozenVolumes={service['cosmos-installer'] && service['cosmos-installer']['frozen-volumes'] || []}
|
||||
containerInfo={{
|
||||
HostConfig: {
|
||||
Binds: [],
|
||||
Mounts: Object.keys(value.volumes).map(k => {
|
||||
return {
|
||||
Type: value.volumes[k].type || (k.startsWith('/') ? 'bind' : 'volume'),
|
||||
Source: value.volumes[k].source || "",
|
||||
Target: value.volumes[k].target || "",
|
||||
}
|
||||
}) || [],
|
||||
}
|
||||
}}
|
||||
OnChange={(containerInfo, volumes) => {
|
||||
setOverrides({
|
||||
...overrides,
|
||||
[value.container_name]: {
|
||||
volumes: containerInfo.volumes.map((v, k) => {
|
||||
return {
|
||||
type: v.Type,
|
||||
source: v.Source,
|
||||
target: v.Target,
|
||||
existing: v.Type == 'volume' && volumes.find(v2 => v2.Source === v.Name),
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}}
|
||||
noCard
|
||||
/>
|
||||
</Stack>
|
||||
</CosmosCollapse>
|
||||
})}
|
||||
|
||||
</>)}
|
||||
</Stack></>}
|
||||
|
||||
{step === 0 && dockerComposeInit && dockerCompose == '' && <Stack spacing={2} alignItems={'center'} style={{paddingTop: '20px'}}>
|
||||
<CircularProgress />
|
||||
</Stack>}
|
||||
|
||||
{step === 1 && <Stack spacing={2}>
|
||||
<NewDockerService service={service} refresh={refresh} />
|
||||
</Stack>}
|
||||
|
@ -392,6 +609,7 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installer, defaultNam
|
|||
setStep(0);
|
||||
setDockerCompose('');
|
||||
setYmlError('');
|
||||
setInstaller(false);
|
||||
}}>Cancel</Button>
|
||||
<Button disabled={!dockerCompose || ymlError} onClick={() => {
|
||||
if (step === 0) {
|
||||
|
@ -406,14 +624,21 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installer, defaultNam
|
|||
</DialogActions>}
|
||||
</Dialog>
|
||||
|
||||
<ResponsiveButton
|
||||
color="primary"
|
||||
onClick={() => setOpenModal(true)}
|
||||
variant={(installer ? "contained" : "outlined")}
|
||||
startIcon={(installer ? <ArrowDownOutlined /> : <ArrowUpOutlined />)}
|
||||
>
|
||||
{installer ? 'Install' : 'Import Compose File'}
|
||||
</ResponsiveButton>
|
||||
{(installerInit && service.minVersion && isNewerVersion(service.minVersion)) ?
|
||||
<Alert severity="error" icon={<WarningOutlined />}>
|
||||
This service requires a newer version of Cosmos. Please update Cosmos to install this service.
|
||||
</Alert>
|
||||
: <ResponsiveButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
openModalFunc();
|
||||
}}
|
||||
variant={(installerInit ? "contained" : "outlined")}
|
||||
startIcon={(installerInit ? <ArrowDownOutlined /> : <ArrowUpOutlined />)}
|
||||
>
|
||||
{installerInit ? 'Install' : 'Import Compose File'}
|
||||
</ResponsiveButton>
|
||||
}
|
||||
</>;
|
||||
};
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ const NewDockerService = ({service, refresh}) => {
|
|||
const [openModal, setOpenModal] = React.useState(false);
|
||||
const preRef = React.useRef(null);
|
||||
|
||||
delete service['cosmos-installer'];
|
||||
|
||||
React.useEffect(() => {
|
||||
// refreshContainer();
|
||||
}, []);
|
||||
|
|
|
@ -8,6 +8,7 @@ import { DeleteOutlined, PlusCircleOutlined } from '@ant-design/icons';
|
|||
import * as API from '../../../api';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
import LogsInModal from '../../../components/logsInModal';
|
||||
import ResponsiveButton from '../../../components/responseiveButton';
|
||||
|
||||
const containerInfoFrom = (values) => {
|
||||
const labels = {};
|
||||
|
@ -27,7 +28,7 @@ const containerInfoFrom = (values) => {
|
|||
return realvalues;
|
||||
}
|
||||
|
||||
const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newContainer, OnForceSecure}) => {
|
||||
const DockerContainerSetup = ({noCard, containerInfo, installer, OnChange, refresh, newContainer, OnForceSecure}) => {
|
||||
const restartPolicies = [
|
||||
['no', 'No Restart'],
|
||||
['always', 'Always Restart'],
|
||||
|
@ -40,6 +41,13 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont
|
|||
const padding = isMobile ? '6px 4px' : '12px 10px';
|
||||
const [latestImage, setLatestImage] = React.useState(containerInfo.Config.Image);
|
||||
|
||||
const wrapCard = (children) => {
|
||||
if (noCard) return children;
|
||||
return <MainCard title="Docker Container Setup">
|
||||
{children}
|
||||
</MainCard>;
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ maxWidth: '1000px', width: '100%', margin: '', position: 'relative' }}>
|
||||
<Formik
|
||||
|
@ -56,6 +64,7 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont
|
|||
}),
|
||||
interactive: containerInfo.Config.Tty && containerInfo.Config.OpenStdin,
|
||||
}}
|
||||
enableReinitialize
|
||||
validate={(values) => {
|
||||
const errors = {};
|
||||
if (!values.image) {
|
||||
|
@ -115,13 +124,14 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont
|
|||
}}
|
||||
/>
|
||||
<Stack spacing={2}>
|
||||
<MainCard title={'Docker Container Setup'}>
|
||||
{wrapCard(<>
|
||||
{containerInfo.State && containerInfo.State.Status !== 'running' && (
|
||||
<Alert severity="warning" style={{ marginBottom: '15px' }}>
|
||||
This container is not running. Editing any settings will cause the container to start again.
|
||||
</Alert>
|
||||
)}
|
||||
<Grid container spacing={4}>
|
||||
{!installer && <>
|
||||
{newContainer && <CosmosInputText
|
||||
name="name"
|
||||
label="Name"
|
||||
|
@ -161,6 +171,7 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont
|
|||
}}
|
||||
/>
|
||||
</Grid>}
|
||||
</>}
|
||||
|
||||
<CosmosFormDivider title={'Environment Variables'} />
|
||||
<Grid item xs={12}>
|
||||
|
@ -206,8 +217,7 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont
|
|||
</Grid>
|
||||
</Grid>
|
||||
))}
|
||||
<IconButton
|
||||
fullWidth
|
||||
<ResponsiveButton
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
size='large'
|
||||
|
@ -216,9 +226,10 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont
|
|||
newEnvVars.push({ key: '', value: '' });
|
||||
formik.setFieldValue('envVars', newEnvVars);
|
||||
}}
|
||||
startIcon={<PlusCircleOutlined />}
|
||||
>
|
||||
<PlusCircleOutlined />
|
||||
</IconButton>
|
||||
Add
|
||||
</ResponsiveButton>
|
||||
</Grid>
|
||||
<CosmosFormDivider title={'Labels'} />
|
||||
<Grid item xs={12}>
|
||||
|
@ -264,8 +275,7 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont
|
|||
</Grid>
|
||||
</Grid>
|
||||
))}
|
||||
<IconButton
|
||||
fullWidth
|
||||
<ResponsiveButton
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
size='large'
|
||||
|
@ -274,12 +284,13 @@ const DockerContainerSetup = ({config, containerInfo, OnChange, refresh, newCont
|
|||
newLabels.push({ key: '', value: '' });
|
||||
formik.setFieldValue('labels', newLabels);
|
||||
}}
|
||||
startIcon={<PlusCircleOutlined />}
|
||||
>
|
||||
<PlusCircleOutlined />
|
||||
</IconButton>
|
||||
Add
|
||||
</ResponsiveButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</>)}
|
||||
{!newContainer && <MainCard>
|
||||
<Stack direction="column" spacing={2}>
|
||||
{formik.errors.submit && (
|
||||
|
|
|
@ -12,7 +12,7 @@ import { NetworksColumns } from '../networks';
|
|||
import NewNetworkButton from '../createNetwork';
|
||||
import ResponsiveButton from '../../../components/responseiveButton';
|
||||
|
||||
const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, OnChange }) => {
|
||||
const VolumeContainerSetup = ({ noCard, containerInfo, frozenVolumes = [], refresh, newContainer, OnChange }) => {
|
||||
const restartPolicies = [
|
||||
['no', 'No Restart'],
|
||||
['always', 'Always Restart'],
|
||||
|
@ -44,6 +44,13 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
});
|
||||
};
|
||||
|
||||
const wrapCard = (children) => {
|
||||
if (noCard) return children;
|
||||
return <MainCard title="Volume Mounts">
|
||||
{children}
|
||||
</MainCard>
|
||||
};
|
||||
|
||||
return (<Stack spacing={2}>
|
||||
<div style={{ maxWidth: '1000px', width: '100%', margin: '', position: 'relative' }}>
|
||||
<Formik
|
||||
|
@ -60,6 +67,7 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
})
|
||||
])
|
||||
}}
|
||||
enableReinitialize
|
||||
validate={(values) => {
|
||||
const errors = {};
|
||||
// check unique
|
||||
|
@ -70,7 +78,7 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
if (unique.length !== volumes.length) {
|
||||
errors.submit = 'Mounts must have unique targets';
|
||||
}
|
||||
OnChange && OnChange(values);
|
||||
OnChange && OnChange(values, volumes);
|
||||
return errors;
|
||||
}}
|
||||
onSubmit={async (values, { setErrors, setStatus, setSubmitting }) => {
|
||||
|
@ -95,8 +103,8 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
>
|
||||
{(formik) => (
|
||||
<form noValidate onSubmit={formik.handleSubmit}>
|
||||
<MainCard title={'Volume Mounts'}>
|
||||
{containerInfo.State && containerInfo.State.Status !== 'running' && (
|
||||
{wrapCard(<>
|
||||
{!newContainer && containerInfo.State && containerInfo.State.Status !== 'running' && (
|
||||
<Alert severity="warning" style={{ marginBottom: '15px' }}>
|
||||
This container is not running. Editing any settings will cause the container to start again.
|
||||
</Alert>
|
||||
|
@ -129,6 +137,7 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
<div style={{fontWeight: 'bold', wordSpace: 'nowrap', overflow:'hidden', textOverflow:'ellipsis', maxWidth: '100px'}}>
|
||||
<TextField
|
||||
className="px-2 my-2"
|
||||
disabled={frozenVolumes.includes(r.Source)}
|
||||
variant="outlined"
|
||||
name='Type'
|
||||
id='Type'
|
||||
|
@ -155,6 +164,7 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
variant="outlined"
|
||||
name='Source'
|
||||
id='Source'
|
||||
disabled={frozenVolumes.includes(r.Source)}
|
||||
style={{minWidth: '200px'}}
|
||||
value={r.Source}
|
||||
onChange={(e) => {
|
||||
|
@ -168,6 +178,7 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
variant="outlined"
|
||||
name='Source'
|
||||
id='Source'
|
||||
disabled={frozenVolumes.includes(r.Source)}
|
||||
select
|
||||
style={{minWidth: '200px'}}
|
||||
value={r.Source}
|
||||
|
@ -177,9 +188,9 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
formik.setFieldValue('volumes', newVolumes);
|
||||
}}
|
||||
>
|
||||
{volumes.map((volume) => (
|
||||
<MenuItem key={volume.Id} value={volume.Name}>
|
||||
{volume.Name}
|
||||
{[...volumes, r].map((volume) => (
|
||||
<MenuItem key={volume.Id || "last"} value={volume.Name || volume.Source}>
|
||||
{volume.Name || volume.Source}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
@ -195,6 +206,7 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
variant="outlined"
|
||||
name='Target'
|
||||
id='Target'
|
||||
disabled={frozenVolumes.includes(r.Source)}
|
||||
style={{minWidth: '200px'}}
|
||||
value={r.Target}
|
||||
onChange={(e) => {
|
||||
|
@ -211,6 +223,7 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
return (<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
disabled={frozenVolumes.includes(r.Source)}
|
||||
onClick={() => {
|
||||
const newVolumes = [...formik.values.volumes];
|
||||
newVolumes.splice(newVolumes.indexOf(r), 1);
|
||||
|
@ -252,7 +265,7 @@ const VolumeContainerSetup = ({ config, containerInfo, refresh, newContainer, On
|
|||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</>)}
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
|
|
|
@ -17,7 +17,7 @@ const ServappsIndex = () => {
|
|||
return <div>
|
||||
<IsLoggedIn />
|
||||
|
||||
<PrettyTabbedView path="/ui/servapps/:tab" tabs={[
|
||||
<PrettyTabbedView path="/cosmos-ui/servapps/:tab" tabs={[
|
||||
{
|
||||
title: 'Containers',
|
||||
children: <ServeApps />,
|
||||
|
|
|
@ -150,7 +150,7 @@ const ServeApps = () => {
|
|||
<ResponsiveButton variant="contained" startIcon={<ReloadOutlined />} onClick={() => {
|
||||
refreshServeApps();
|
||||
}}>Refresh</ResponsiveButton>
|
||||
<Link to="/ui/servapps/new-service">
|
||||
<Link to="/cosmos-ui/servapps/new-service">
|
||||
<ResponsiveButton
|
||||
variant="contained"
|
||||
startIcon={<AppstoreAddOutlined />}
|
||||
|
@ -303,7 +303,7 @@ const ServeApps = () => {
|
|||
</Stack>
|
||||
</Stack>
|
||||
<div>
|
||||
<Link to={`/ui/servapps/containers/${app.Names[0].replace('/', '')}`}>
|
||||
<Link to={`/cosmos-ui/servapps/containers/${app.Names[0].replace('/', '')}`}>
|
||||
<Button variant="outlined" color="primary" fullWidth>Details</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
@ -22,35 +22,35 @@ const LoginRoutes = {
|
|||
element: <MinimalLayout />,
|
||||
children: [
|
||||
{
|
||||
path: '/ui/login',
|
||||
path: '/cosmos-ui/login',
|
||||
element: <AuthLogin />
|
||||
},
|
||||
{
|
||||
path: '/ui/register',
|
||||
path: '/cosmos-ui/register',
|
||||
element: <AuthRegister />
|
||||
},
|
||||
{
|
||||
path: '/ui/logout',
|
||||
path: '/cosmos-ui/logout',
|
||||
element: <Logout />
|
||||
},
|
||||
{
|
||||
path: '/ui/newInstall',
|
||||
path: '/cosmos-ui/newInstall',
|
||||
element: <NewInstall />
|
||||
},
|
||||
{
|
||||
path: '/ui/newmfa',
|
||||
path: '/cosmos-ui/newmfa',
|
||||
element: <NewMFA />
|
||||
},
|
||||
{
|
||||
path: '/ui/openid',
|
||||
path: '/cosmos-ui/openid',
|
||||
element: <OpenID />
|
||||
},
|
||||
{
|
||||
path: '/ui/loginmfa',
|
||||
path: '/cosmos-ui/loginmfa',
|
||||
element: <MFALogin />
|
||||
},
|
||||
{
|
||||
path: '/ui/forgot-password',
|
||||
path: '/cosmos-ui/forgot-password',
|
||||
element: <ForgotPassword />
|
||||
},
|
||||
]
|
||||
|
|
|
@ -39,60 +39,60 @@ const MainRoutes = {
|
|||
children: [
|
||||
{
|
||||
path: '/',
|
||||
// redirect to /ui
|
||||
element: <Navigate to="/ui" />
|
||||
// redirect to /cosmos-ui
|
||||
element: <Navigate to="/cosmos-ui" />
|
||||
},
|
||||
{
|
||||
path: '/ui/logo',
|
||||
// redirect to /ui
|
||||
path: '/cosmos-ui/logo',
|
||||
// redirect to /cosmos-ui
|
||||
element: <Navigate to={logo} />
|
||||
},
|
||||
{
|
||||
path: '/ui',
|
||||
path: '/cosmos-ui',
|
||||
element: <HomePage />
|
||||
},
|
||||
{
|
||||
path: '/ui/dashboard',
|
||||
path: '/cosmos-ui/dashboard',
|
||||
element: <DashboardDefault />
|
||||
},
|
||||
{
|
||||
path: '/ui/servapps',
|
||||
path: '/cosmos-ui/servapps',
|
||||
element: <ServeAppsIndex />
|
||||
},
|
||||
{
|
||||
path: '/ui/config-users',
|
||||
path: '/cosmos-ui/config-users',
|
||||
element: <UserManagement />
|
||||
},
|
||||
{
|
||||
path: '/ui/config-general',
|
||||
path: '/cosmos-ui/config-general',
|
||||
element: <ConfigManagement />
|
||||
},
|
||||
{
|
||||
path: '/ui/servapps/new-service',
|
||||
path: '/cosmos-ui/servapps/new-service',
|
||||
element: <NewDockerServiceForm />
|
||||
},
|
||||
{
|
||||
path: '/ui/config-url',
|
||||
path: '/cosmos-ui/config-url',
|
||||
element: <ProxyManagement />
|
||||
},
|
||||
{
|
||||
path: '/ui/config-url/:routeName',
|
||||
path: '/cosmos-ui/config-url/:routeName',
|
||||
element: <RouteConfigPage />,
|
||||
},
|
||||
{
|
||||
path: '/ui/servapps/containers/:containerName',
|
||||
path: '/cosmos-ui/servapps/containers/:containerName',
|
||||
element: <ContainerIndex />,
|
||||
},
|
||||
{
|
||||
path: '/ui/openid-manage',
|
||||
path: '/cosmos-ui/openid-manage',
|
||||
element: <OpenIdList />,
|
||||
},
|
||||
{
|
||||
path: '/ui/market-listing/',
|
||||
path: '/cosmos-ui/market-listing/',
|
||||
element: <MarketPage />
|
||||
},
|
||||
{
|
||||
path: '/ui/market-listing/:appName',
|
||||
path: '/cosmos-ui/market-listing/:appName',
|
||||
element: <MarketPage />
|
||||
}
|
||||
]
|
||||
|
|
|
@ -26,3 +26,24 @@ export function isDomain(hostname) {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function debounce(func, wait, immediate) {
|
||||
var timeout;
|
||||
|
||||
return () => {
|
||||
var context = this, args = arguments;
|
||||
|
||||
var later = () => {
|
||||
timeout = null;
|
||||
if (!immediate) func.apply(context, args);
|
||||
};
|
||||
|
||||
var callNow = immediate && !timeout;
|
||||
|
||||
clearTimeout(timeout);
|
||||
|
||||
timeout = setTimeout(later, wait);
|
||||
|
||||
if (callNow) func.apply(context, args);
|
||||
};
|
||||
};
|
118
package-lock.json
generated
118
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.7.0-unstable",
|
||||
"version": "0.7.0-unstable2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cosmos-server",
|
||||
"version": "0.7.0-unstable",
|
||||
"version": "0.7.0-unstable2",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.7.0",
|
||||
|
@ -24,6 +24,7 @@
|
|||
"apexcharts": "^3.35.5",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"browserslist": "^4.21.7",
|
||||
"dot": "^1.1.3",
|
||||
"formik": "^2.2.9",
|
||||
"framer-motion": "^7.3.6",
|
||||
"history": "^5.3.0",
|
||||
|
@ -52,6 +53,8 @@
|
|||
"typescript": "4.8.3",
|
||||
"vite": "^4.2.0",
|
||||
"web-vitals": "^3.0.2",
|
||||
"whiskers": "^0.4.0",
|
||||
"whiskers.js": "^1.0.0",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -4391,6 +4394,11 @@
|
|||
"integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
|
||||
|
@ -4411,6 +4419,15 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.9",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||
|
@ -4749,6 +4766,17 @@
|
|||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/comma-separated-tokens": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
|
||||
|
@ -4997,6 +5025,14 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/diff-sequences": {
|
||||
"version": "29.4.3",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz",
|
||||
|
@ -5048,6 +5084,17 @@
|
|||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dot": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dot/-/dot-1.1.3.tgz",
|
||||
"integrity": "sha512-/nt74Rm+PcfnirXGEdhZleTwGC2LMnuKTeeTIlI82xb5loBBoXNYzr2ezCroPSMtilK8EZIfcNZwOcHN+ib1Lg==",
|
||||
"engines": [
|
||||
"node >=0.2.6"
|
||||
],
|
||||
"bin": {
|
||||
"dottojs": "bin/dot-packer"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.419",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.419.tgz",
|
||||
|
@ -6107,6 +6154,25 @@
|
|||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
|
@ -6115,6 +6181,19 @@
|
|||
"is-callable": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/format": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
|
||||
|
@ -7687,6 +7766,25 @@
|
|||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
|
@ -9923,6 +10021,22 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/whiskers": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/whiskers/-/whiskers-0.4.0.tgz",
|
||||
"integrity": "sha512-pTygA/fE6RIMOp3AwUy7E9jrdpqUEa4k5VCdJIBZ/64kNtiMuCTCYC6fzbiUhjxN32zX+qZQlZACMC/un5HS7A==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whiskers.js": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whiskers.js/-/whiskers.js-1.0.0.tgz",
|
||||
"integrity": "sha512-IzdNdA2jTPrTEnjQkySSOW90rtb++vJUjkHdwwsjqIeGndnizM3Mo+5DEVZ3iy7Su3itR92i9+EFtYamIT3Zxw==",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "cosmos-server",
|
||||
"version": "0.7.0-unstable2",
|
||||
"version": "0.7.0-unstable3",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
@ -24,6 +24,7 @@
|
|||
"apexcharts": "^3.35.5",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"browserslist": "^4.21.7",
|
||||
"dot": "^1.1.3",
|
||||
"formik": "^2.2.9",
|
||||
"framer-motion": "^7.3.6",
|
||||
"history": "^5.3.0",
|
||||
|
@ -52,11 +53,13 @@
|
|||
"typescript": "4.8.3",
|
||||
"vite": "^4.2.0",
|
||||
"web-vitals": "^3.0.2",
|
||||
"whiskers": "^0.4.0",
|
||||
"whiskers.js": "^1.0.0",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"scripts": {
|
||||
"client": "vite",
|
||||
"client-build": "vite build --base=/ui/",
|
||||
"client-build": "vite build --base=/cosmos-ui/",
|
||||
"start": "env CONFIG_FILE=./config_dev.json EZ=UTC build/cosmos",
|
||||
"build": "sh build.sh",
|
||||
"dev": "npm run build && npm run start",
|
||||
|
@ -64,7 +67,7 @@
|
|||
"dockerdevrun": "docker stop cosmos-dev; docker rm cosmos-dev; docker run -d -p 80:80 -p 443:443 -e DOCKER_HOST=tcp://host.docker.internal:2375 -e COSMOS_MONGODB=$MONGODB -e COSMOS_LOG_LEVEL=DEBUG -v /:/mnt/host --restart=unless-stopped -h cosmos-dev --name cosmos-dev cosmos-dev",
|
||||
"dockerdev": "npm run dockerdevbuild && npm run dockerdevrun",
|
||||
"dockerdevclient": "npm run client-build && npm run dockerdevbuild && npm run dockerdevrun",
|
||||
"demo": "vite build --base=/ui/ --mode demo",
|
||||
"demo": "vite build --base=/cosmos-ui/ --mode demo",
|
||||
"devdemo": "vite --mode demo"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<!-- sponsors -->
|
||||
<h3 align="center">Thanks to the sponsors:</h3></br>
|
||||
<p align="center"><a href="https://github.com/zarevskaya"><img src="https://avatars.githubusercontent.com/zarevskaya" style="border-radius:48px" width="48" height="48" alt="zarev" title="zarev" /></a>
|
||||
<a href="https://github.com/DrMxrcy"><img src="https://avatars.githubusercontent.com/DrMxrcy" style="border-radius:48px" width="48" height="48" alt="null" title="null" /></a>
|
||||
</p><!-- /sponsors -->
|
||||
|
||||
---
|
||||
|
@ -24,7 +23,7 @@ Cosmos is a self-hosted platform for running server applications securely and wi
|
|||
<a target="_blank" href="https://cosmos-cloud.io/doc">
|
||||
<img height="44px" src="https://raw.githubusercontent.com/azukaar/Cosmos-Server/master/icons/doc.png" />
|
||||
</a>
|
||||
<a target="_blank" href="https://cosmos-cloud.io/ui">
|
||||
<a target="_blank" href="https://cosmos-cloud.io/cosmos-ui">
|
||||
<img height="44px" src="https://raw.githubusercontent.com/azukaar/Cosmos-Server/master/icons/demo.png" />
|
||||
</a>
|
||||
<br/>
|
||||
|
|
|
@ -72,7 +72,7 @@ func discoverEndpoint(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
json.NewEncoder(rw).Encode(&oidcConfiguration{
|
||||
Issuer: hostname,
|
||||
AuthURL: realHostname + "/ui/openid",
|
||||
AuthURL: realHostname + "/cosmos-ui/openid",
|
||||
TokenURL: hostname + "/oauth2/token",
|
||||
JWKsURI: hostname + "/.well-known/jwks.json",
|
||||
RevocationEndpoint: hostname + "/oauth2/revoke",
|
||||
|
@ -96,7 +96,7 @@ func discoverEndpoint(rw http.ResponseWriter, req *http.Request) {
|
|||
BackChannelLogoutSessionSupported: true,
|
||||
FrontChannelLogoutSupported: true,
|
||||
FrontChannelLogoutSessionSupported: true,
|
||||
EndSessionEndpoint: hostname + "/ui/logout",
|
||||
EndSessionEndpoint: hostname + "/cosmos-ui/logout",
|
||||
RequestObjectSigningAlgValuesSupported: []string{"RS256"},
|
||||
CodeChallengeMethodsSupported: []string{"plain", "S256"},
|
||||
})
|
||||
|
|
|
@ -384,7 +384,7 @@ func CreateService(serviceRequest DockerServiceCreateRequest, OnLog func(string)
|
|||
if containerConfig.Env != nil {
|
||||
for i, env := range containerConfig.Env {
|
||||
if strings.HasPrefix(env, "TZ=") {
|
||||
if strings.TrimPrefix(env, "TZ=") == "" {
|
||||
if strings.TrimPrefix(env, "TZ=") == "auto" {
|
||||
if os.Getenv("TZ") != "" {
|
||||
containerConfig.Env[i] = "TZ=" + os.Getenv("TZ")
|
||||
} else {
|
||||
|
|
|
@ -302,12 +302,12 @@ func StartServer() {
|
|||
fs = utils.EnsureHostname(fs)
|
||||
}
|
||||
|
||||
router.PathPrefix("/ui").Handler(http.StripPrefix("/ui", fs))
|
||||
router.PathPrefix("/cosmos-ui").Handler(http.StripPrefix("/cosmos-ui", fs))
|
||||
|
||||
router = proxy.BuildFromConfig(router, HTTPConfig.ProxyConfig)
|
||||
|
||||
router.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/ui", http.StatusMovedPermanently)
|
||||
http.Redirect(w, r, "/cosmos-ui", http.StatusMovedPermanently)
|
||||
}))
|
||||
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ func RefreshUserToken(w http.ResponseWriter, req *http.Request) (utils.User, err
|
|||
}
|
||||
|
||||
requestURL := req.URL.Path
|
||||
isSettingMFA := strings.HasPrefix(requestURL, "/ui/loginmfa") || strings.HasPrefix(requestURL, "/ui/newmfa") || strings.HasPrefix(requestURL, "/api/mfa")
|
||||
isSettingMFA := strings.HasPrefix(requestURL, "/cosmos-ui/loginmfa") || strings.HasPrefix(requestURL, "/cosmos-ui/newmfa") || strings.HasPrefix(requestURL, "/api/mfa")
|
||||
|
||||
userInBase.MFAState = 0
|
||||
|
||||
|
@ -201,15 +201,15 @@ func logOutUser(w http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
|
||||
func redirectToReLogin(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, "/ui/login?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, req, "/cosmos-ui/login?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func redirectToLoginMFA(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, "/ui/loginmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, req, "/cosmos-ui/loginmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func redirectToNewMFA(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, "/ui/newmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, req, "/cosmos-ui/newmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func SendUserToken(w http.ResponseWriter, req *http.Request, user utils.User, mfaDone bool) {
|
||||
|
|
|
@ -15,15 +15,15 @@ func LoggedInOnlyWithRedirect(w http.ResponseWriter, req *http.Request) error {
|
|||
|
||||
if !isUserLoggedIn || userNickname == "" {
|
||||
Error("LoggedInOnlyWithRedirect: User is not logged in", nil)
|
||||
http.Redirect(w, req, "/ui/login?notlogged=1&redirect="+req.URL.Path, http.StatusFound)
|
||||
http.Redirect(w, req, "/cosmos-ui/login?notlogged=1&redirect="+req.URL.Path, http.StatusFound)
|
||||
return errors.New("User not logged in")
|
||||
}
|
||||
|
||||
if(mfa == 1) {
|
||||
http.Redirect(w, req, "/ui/loginmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, req, "/cosmos-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)
|
||||
http.Redirect(w, req, "/cosmos-ui/newmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
return errors.New("User requires MFA Setup")
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ func AdminOnlyWithRedirect(w http.ResponseWriter, req *http.Request) error {
|
|||
|
||||
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)
|
||||
http.Redirect(w, req, "/cosmos-ui/login?notlogged=1&redirect="+req.URL.Path, http.StatusFound)
|
||||
return errors.New("User is not logged")
|
||||
}
|
||||
|
||||
|
@ -50,10 +50,10 @@ func AdminOnlyWithRedirect(w http.ResponseWriter, req *http.Request) error {
|
|||
}
|
||||
|
||||
if(mfa == 1) {
|
||||
http.Redirect(w, req, "/ui/loginmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, req, "/cosmos-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)
|
||||
http.Redirect(w, req, "/cosmos-ui/newmfa?invalid=1&redirect=" + req.URL.Path + "&" + req.URL.RawQuery, http.StatusTemporaryRedirect)
|
||||
return errors.New("User requires MFA Setup")
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue