v0.1.15 improve Port selection, hostnames and ports now have default, other UI improvements

This commit is contained in:
Yann Stepienik 2023-04-10 17:53:16 +01:00
parent d8870191c6
commit a82a436187
9 changed files with 148 additions and 63 deletions

View file

@ -16,24 +16,24 @@ const pages = {
type: 'group', type: 'group',
children: [ children: [
{ {
id: 'proxy', id: 'url',
title: 'Proxy Routes', title: 'URLs',
type: 'item', type: 'item',
url: '/ui/config/proxy', url: '/ui/config-url',
icon: icons.NodeExpandOutlined, icon: icons.NodeExpandOutlined,
}, },
{ {
id: 'users', id: 'users',
title: 'Manage Users', title: 'Manage Users',
type: 'item', type: 'item',
url: '/ui/config/users', url: '/ui/config-users',
icon: icons.ProfileOutlined, icon: icons.ProfileOutlined,
}, },
{ {
id: 'config', id: 'config',
title: 'Configuration', title: 'Configuration',
type: 'item', type: 'item',
url: '/ui/config/general', url: '/ui/config-general',
icon: icons.SettingOutlined, icon: icons.SettingOutlined,
} }
] ]

View file

@ -29,6 +29,8 @@ import RestartModal from './restart';
import Autocomplete from '@mui/material/Autocomplete'; import Autocomplete from '@mui/material/Autocomplete';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import defaultport from '../../servapps/defaultport.json';
import * as API from '../../../api'; import * as API from '../../../api';
export function CosmosContainerPicker({formik, lockTarget, TargetContainer, onTargetChange}) { export function CosmosContainerPicker({formik, lockTarget, TargetContainer, onTargetChange}) {
@ -53,7 +55,7 @@ export function CosmosContainerPicker({formik, lockTarget, TargetContainer, onTa
if(preview) { if(preview) {
let protocols = preview.split("://") let protocols = preview.split("://")
let p1_ = protocols.length > 1 ? protocols[1] : protocols[0] let p1_ = protocols.length > 1 ? protocols[1] : protocols[0]
console.log("p1_", p1_)
targetResult = { targetResult = {
container: '/' + p1_.split(":")[0], container: '/' + p1_.split(":")[0],
port: p1_.split(":")[1], port: p1_.split(":")[1],
@ -76,6 +78,37 @@ export function CosmosContainerPicker({formik, lockTarget, TargetContainer, onTa
}) })
setPortsOptions(portsTemp) setPortsOptions(portsTemp)
targetResult.port = '80'
if(portsTemp.length > 0) {
// pick best default port
// set default to first port
targetResult.port = portsTemp[0]
// first, check if a manual override exists
let override = Object.keys(defaultport.overrides).find((key) => {
let keyMatch = new RegExp(key, "i");
return newContainer.Image.match(keyMatch) && portsTemp.includes(defaultport.overrides[key])
});
if(override) {
targetResult.port = defaultport.overrides[override]
} else {
// if not, check the default list of common ports
let priorityList = defaultport.priority;
priorityList.find((_portReg) => {
return portsTemp.find((portb) => {
let portReg = new RegExp(_portReg, "i");
if(portb.toString().match(portReg)) {
targetResult.port = portb
return true;
}
})
})
}
}
formik.setFieldValue(name, getTarget());
if(newContainer.NetworkSettings.Networks["bridge"]) { if(newContainer.NetworkSettings.Networks["bridge"]) {
setIsOnBridge(true); setIsOnBridge(true);
} }
@ -186,26 +219,27 @@ export function CosmosContainerPicker({formik, lockTarget, TargetContainer, onTa
{(portsOptions.length > 0) ? (<> {(portsOptions.length > 0) ? (<>
<InputLabel htmlFor={name + "-port"}>Container Port</InputLabel> <InputLabel htmlFor={name + "-port"}>Container Port</InputLabel>
<TextField <Autocomplete
className="px-2 my-2" className="px-2 my-2"
variant="outlined" variant="outlined"
name={name + "-port"} name={name + "-port"}
id={name + "-port"} id={name + "-port"}
value={targetResult.port} value={targetResult.port}
select options={portsOptions.map((option) => (option))}
placeholder='Select a port' placeholder='Select a port'
onChange={(event) => { freeSolo
targetResult.port = event.target.value filterOptions={(x) => x} // disable filtering
getOptionLabel={(option) => '' + option}
isOptionEqualToValue={(option, value) => {
return ('' + option) === value
}}
onChange={(event, newValue) => {
targetResult.port = newValue
formik.setFieldValue(name, getTarget()) formik.setFieldValue(name, getTarget())
}} }}
> renderInput={(params) => <TextField {...params} />}
{portsOptions.map((option) => ( />
<MenuItem key={option} value={option}> {targetResult.port == '' && targetResult.port == 0 && <FormHelperText error id="standard-weight-helper-text-name-login">
{option}
</MenuItem>
))}
</TextField>
{targetResult.port == '' && <FormHelperText error id="standard-weight-helper-text-name-login">
Please select a port Please select a port
</FormHelperText>} </FormHelperText>}
</>) : ''} </>) : ''}

View file

@ -122,8 +122,8 @@ const ProxyManagement = () => {
}}>Refresh</Button>&nbsp;&nbsp; }}>Refresh</Button>&nbsp;&nbsp;
<Button variant="contained" color="primary" startIcon={<PlusCircleOutlined />} onClick={() => { <Button variant="contained" color="primary" startIcon={<PlusCircleOutlined />} onClick={() => {
routes.unshift({ routes.unshift({
Name: 'New Route', Name: 'New URL',
Description: 'New Route', Description: 'New URL',
Mode: "SERVAPP", Mode: "SERVAPP",
UseHost: false, UseHost: false,
Host: '', Host: '',

View file

@ -100,7 +100,7 @@ const RouteManagement = ({ routeConfig, TargetContainer, noControls=false, lockT
{(formik) => ( {(formik) => (
<form ref={myRef} noValidate onSubmit={formik.handleSubmit}> <form ref={myRef} noValidate onSubmit={formik.handleSubmit}>
<MainCard name={routeConfig.Name} title={ <MainCard name={routeConfig.Name} title={
noControls ? 'New Route' : noControls ? 'New URL' :
<div>{routeConfig.Name} &nbsp; <div>{routeConfig.Name} &nbsp;
<Chip label={<UpOutlined />} onClick={() => up()}/> &nbsp; <Chip label={<UpOutlined />} onClick={() => up()}/> &nbsp;
<Chip label={<DownOutlined />} onClick={() => down()}/> &nbsp; <Chip label={<DownOutlined />} onClick={() => down()}/> &nbsp;

View file

@ -0,0 +1,59 @@
{
"priority": [
"80",
"3000",
"8080",
"8*",
"3*"
],
"overrides": {
"adguard": 80,
"airsonic": 4040,
"bazarr": 6767,
"bitwarden": 80,
"bookstack": 80,
"calibre(-web)?" : 8083,
"dokuwiki": 80,
"duplicati": 8200,
"emby": 8096,
"filebrowser": 80,
"ghost": 2368,
"git(lab|server)" : 80,
"gitea": 3000,
"grafana": 3000,
"heimdall": 80,
"homeassistant": 8123,
"homebridge": 8581,
"jackett": 9117,
"jdownloader": 5800,
"jellyfin": 8096,
"jitsi": 80,
"lidarr": 8686,
"letsencrypt": 80,
"ombi4k": 3579,
"moodle": 80,
"nzb360": 6789,
"nzbget": 6789,
"nzbhydra": 5076,
"ombi": 3579,
"openproject": 80,
"organizr": 80,
"pi-hole": 80,
"plex": 32400,
"portainer": 9000,
"prometheus": 9090,
"qbittorrent(vpn)?" : 8080,
"radarr": 7878,
"rocket.chat": 3000,
"sabnzbd": 8080,
"sick(chill|rage)": 8081,
"sonarr": 8989,
"synclounge": 8088,
"tautulli": 8181,
"transmission(-openvpn)?" : 9091,
"ubooquity": 2202,
"unifi-controller": 8443,
"watchtower": 8080,
"wordpress": 80
}
}

View file

@ -1,5 +1,5 @@
// material-ui // material-ui
import { AppstoreAddOutlined, ReloadOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons'; import { AppstoreAddOutlined, PlusCircleOutlined, ReloadOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons';
import { Alert, Badge, Button, Card, Checkbox, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, Input, InputAdornment, TextField, Tooltip, Typography } from '@mui/material'; import { Alert, Badge, Button, Card, Checkbox, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, Input, InputAdornment, TextField, Tooltip, Typography } from '@mui/material';
import Grid2 from '@mui/material/Unstable_Grid2/Grid2'; import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
import { Stack } from '@mui/system'; import { Stack } from '@mui/system';
@ -116,13 +116,13 @@ const ServeApps = () => {
return <div> return <div>
<RestartModal openModal={openRestartModal} setOpenModal={setOpenRestartModal} /> <RestartModal openModal={openRestartModal} setOpenModal={setOpenRestartModal} />
<Dialog open={openModal} onClose={() => setOpenModal(false)}> <Dialog open={openModal} onClose={() => setOpenModal(false)}>
<DialogTitle>Connect ServApp</DialogTitle> <DialogTitle>Expose ServApp</DialogTitle>
{openModal && <> {openModal && <>
<DialogContent> <DialogContent>
<DialogContentText> <DialogContentText>
<Stack spacing={2}> <Stack spacing={2}>
<div> <div>
Welcome to the Connect Wizard. This interface will help you expose your ServApp securely to the internet. Welcome to the URL Wizard. This interface will help you expose your ServApp securely to the internet by creating a new URL.
</div> </div>
<div> <div>
{openModal && !hasCosmosNetwork(openModal.Names[0]) && <Alert severity="warning">This ServApp does not appear to be connected to a Cosmos Network, so the hostname might not be accessible. The easiest way to fix this is to check the box "Force Secure Network" or manually create a sub-network in Docker.</Alert>} {openModal && !hasCosmosNetwork(openModal.Names[0]) && <Alert severity="warning">This ServApp does not appear to be connected to a Cosmos Network, so the hostname might not be accessible. The easiest way to fix this is to check the box "Force Secure Network" or manually create a sub-network in Docker.</Alert>}
@ -134,8 +134,8 @@ const ServeApps = () => {
Mode: "SERVAPP", Mode: "SERVAPP",
Name: openModal.Names[0].replace('/', ''), Name: openModal.Names[0].replace('/', ''),
Description: "Expose " + openModal.Names[0].replace('/', '') + " to the internet", Description: "Expose " + openModal.Names[0].replace('/', '') + " to the internet",
UseHost: false, UseHost: true,
Host: '', Host: openModal.Names[0].replace('/', '') + '.' + window.location.origin.split('://')[1],
UsePathPrefix: false, UsePathPrefix: false,
PathPrefix: '', PathPrefix: '',
Timeout: 30000, Timeout: 30000,
@ -177,7 +177,7 @@ const ServeApps = () => {
updateRoutes(); updateRoutes();
} }
}}>Connect</Button> }}>Confirm</Button>
</DialogActions> </DialogActions>
</>} </>}
</Dialog> </Dialog>
@ -278,13 +278,13 @@ const ServeApps = () => {
</Stack></Stack>} </Stack></Stack>}
<Stack margin={1} direction="column" spacing={1} alignItems="flex-start"> <Stack margin={1} direction="column" spacing={1} alignItems="flex-start">
<Typography variant="h6" color="text.secondary"> <Typography variant="h6" color="text.secondary">
Proxies URLs
</Typography> </Typography>
<Stack spacing={2} direction="row"> <Stack spacing={2} direction="row">
{getContainersRoutes(app.Names[0].replace('/', '')).map((route) => { {getContainersRoutes(app.Names[0].replace('/', '')).map((route) => {
return <><Chip return <><Chip
label={route.Host + route.PathPrefix} label={route.Host + route.PathPrefix}
color="primary" color="secondary"
style={{paddingRight: '4px'}} style={{paddingRight: '4px'}}
onClick={() => { onClick={() => {
if(route.UseHost) if(route.UseHost)
@ -293,21 +293,33 @@ const ServeApps = () => {
window.open(window.location.origin + route.PathPrefix, '_blank'); window.open(window.location.origin + route.PathPrefix, '_blank');
}} }}
onDelete={() => { onDelete={() => {
window.open('/ui/config/proxy#'+route.Name, '_blank'); window.open('/ui/config-url#'+route.Name, '_blank');
}} }}
deleteIcon={<SettingOutlined />} deleteIcon={<SettingOutlined />}
/> />
</> </>
})} })}
{getContainersRoutes(app.Names[0].replace('/', '')).length == 0 && {/* {getContainersRoutes(app.Names[0].replace('/', '')).length == 0 && */}
<Chip label="No Proxy Setup" />} <Chip
label="New"
color="primary"
style={{paddingRight: '4px'}}
deleteIcon={<PlusCircleOutlined />}
onClick={() => {
setOpenModal(app);
}}
onDelete={() => {
setOpenModal(app);
}}
/>
{/* } */}
</Stack> </Stack>
</Stack> </Stack>
<Stack> {/* <Stack>
<Button variant="contained" color="primary" onClick={() => { <Button variant="contained" color="primary" onClick={() => {
setOpenModal(app); setOpenModal(app);
}}>Connect</Button> }}>Connect</Button>
</Stack> </Stack> */}
</Stack> </Stack>
</Item></Grid2> </Item></Grid2>
}) })

View file

@ -41,15 +41,15 @@ const MainRoutes = {
element: <ServeApps /> element: <ServeApps />
}, },
{ {
path: '/ui/config/users', path: '/ui/config-users',
element: <UserManagement /> element: <UserManagement />
}, },
{ {
path: '/ui/config/general', path: '/ui/config-general',
element: <ConfigManagement /> element: <ConfigManagement />
}, },
{ {
path: '/ui/config/proxy', path: '/ui/config-url',
element: <ProxyManagement /> element: <ProxyManagement />
}, },
] ]

View file

@ -1,6 +1,9 @@
// ==============================|| PRESET THEME - THEME SELECTOR ||============================== // // ==============================|| PRESET THEME - THEME SELECTOR ||============================== //
import { purple, pink, deepPurple } from '@mui/material/colors';
const Theme = (colors) => { const Theme = (colors) => {
console.log(colors)
const { blue, red, gold, cyan, green, grey } = colors; const { blue, red, gold, cyan, green, grey } = colors;
const greyColors = { const greyColors = {
0: grey[0], 0: grey[0],
@ -25,33 +28,10 @@ const Theme = (colors) => {
return { return {
primary: { primary: {
lighter: '#C8A2C8', main: purple[400],
100: '#B785B7',
200: '#B785B7',
light: '#A668A6',
400: '#A668A6',
main: '#8D538D',
dark: '#704270',
700: '#533153',
darker:'#362036',
900: '#1A0E1A',
contrastText
}, },
secondary: { secondary: {
lighter: greyColors[100], main: deepPurple[100]
100: greyColors[100],
200: greyColors[200],
light: greyColors[300],
400: greyColors[400],
main: greyColors[500],
600: greyColors[600],
dark: greyColors[700],
800: greyColors[800],
darker: greyColors[900],
A100: greyColors[0],
A200: greyColors.A400,
A300: greyColors.A700,
contrastText: greyColors[0]
}, },
error: { error: {
lighter: red[0], lighter: red[0],

View file

@ -1,6 +1,6 @@
{ {
"name": "cosmos-server", "name": "cosmos-server",
"version": "0.1.14", "version": "0.1.15",
"description": "", "description": "",
"main": "test-server.js", "main": "test-server.js",
"bugs": { "bugs": {