[release] v0.7.0-unstable3

This commit is contained in:
Yann Stepienik 2023-06-13 02:03:18 +01:00
parent 8d6329f8d5
commit 6bc9e02e28
33 changed files with 736 additions and 323 deletions

View file

@ -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}
/>

View file

@ -1,7 +1,7 @@
// ==============================|| THEME CONFIG ||============================== //
const config = {
defaultPath: '/ui',
defaultPath: '/cosmos-ui',
fontFamily: `'Public Sans', sans-serif`,
i18n: 'en',
miniDrawer: false,

View file

@ -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;
}
}
})

View file

@ -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 /> */}

View file

@ -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
},

View file

@ -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,
}
]

View file

@ -17,7 +17,7 @@ const Logout = () => {
API.auth.logout()
.then(() => {
setTimeout(() => {
window.location.href = '/ui/login';
window.location.href = '/cosmos-ui/login';
}, 2000);
});
},[]);

View file

@ -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>

View file

@ -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">

View file

@ -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 });

View file

@ -109,7 +109,7 @@ const ForgotPassword = () => {
variant="contained"
color="primary"
component={Link}
to="/ui/login"
to="/cosmos-ui/login"
>
Back to login
</Button>

View file

@ -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>

View file

@ -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: '',

View file

@ -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);
});

View file

@ -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>
</>

View file

@ -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)

View file

@ -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,57 +83,85 @@ 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([]);
useEffect(() => {
const text = fetch(dockerComposeInit)
.then((res) => res.text())
.then((text) => {
setDockerCompose(text);
});
}, [dockerComposeInit]);
const [hostnames, setHostnames] = useState({});
const [overrides, setOverrides] = useState({});
const [context, setContext] = useState({});
const [installer, setInstaller] = useState(installerInit);
useEffect(() => {
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 : ''))
if(typeof doc['cosmos-installer'] === 'object') {
setInstaller(true);
}
hostnames.push(newRoute);
}
});
}
});
setHostnames(hostnames);
}
setService(doc);
} catch (e) {
setYmlError(e.message);
@ -290,34 +326,123 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installer, defaultNam
}, [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)}>
@ -364,23 +489,115 @@ const DockerComposeImport = ({ refresh, dockerComposeInit, installer, defaultNam
<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) => {
{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={index} label="Hostname" value={hostname.host} onChange={(e) => {
const newHostnames = [...hostnames];
newHostnames[index].host = e.target.value;
setHostnames(newHostnames);
<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
{(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={() => setOpenModal(true)}
variant={(installer ? "contained" : "outlined")}
startIcon={(installer ? <ArrowDownOutlined /> : <ArrowUpOutlined />)}
onClick={() => {
openModalFunc();
}}
variant={(installerInit ? "contained" : "outlined")}
startIcon={(installerInit ? <ArrowDownOutlined /> : <ArrowUpOutlined />)}
>
{installer ? 'Install' : 'Import Compose File'}
{installerInit ? 'Install' : 'Import Compose File'}
</ResponsiveButton>
}
</>;
};

View file

@ -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();
}, []);

View file

@ -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 && (

View file

@ -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>

View file

@ -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 />,

View file

@ -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>

View file

@ -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 />
},
]

View file

@ -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 />
}
]

View file

@ -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
View file

@ -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",

View file

@ -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": {

View file

@ -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/>

View file

@ -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"},
})

View file

@ -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 {

View file

@ -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)
}))

View file

@ -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) {

View file

@ -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")
}