[release] version 0.5.0-unstable3

This commit is contained in:
Yann Stepienik 2023-05-14 13:11:59 +01:00
parent c670456d47
commit 3cbd88f4a6
10 changed files with 171 additions and 22 deletions

View file

@ -1,3 +1,11 @@
## Version 0.5.0
- Add Terminal to containers
- Add Create Container
- Add support for importing Docker Compose
- Fixed 2 bugs with the smart shield, that made it too strict
- Added more infoon the shield when blocking someone
- Fixed home background image
## Version 0.4.3 ## Version 0.4.3
- Fix for exposing routes from the details page - Fix for exposing routes from the details page

View file

@ -61,10 +61,6 @@ const preStyle = {
marginTop: '10px', marginTop: '10px',
marginLeft: '0', marginLeft: '0',
marginRight: '0', marginRight: '0',
display: 'block',
textAlign: 'left',
verticalAlign: 'baseline',
opacity: '1',
} }
const DockerComposeImport = () => { const DockerComposeImport = () => {
@ -73,14 +69,22 @@ const DockerComposeImport = () => {
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
const [dockerCompose, setDockerCompose] = useState(''); const [dockerCompose, setDockerCompose] = useState('');
const [service, setService] = useState({}); const [service, setService] = useState({});
const [ymlError, setYmlError] = useState('');
useEffect(() => { useEffect(() => {
if(dockerCompose === '') { if(dockerCompose === '') {
return; return;
} }
let doc;
let newService = {}; let newService = {};
let doc = yaml.load(dockerCompose); try {
doc = yaml.load(dockerCompose);
} catch (e) {
console.log(e);
setYmlError(e.message);
return;
}
// convert to the proper format // convert to the proper format
if(doc.services) { if(doc.services) {
@ -110,7 +114,6 @@ const DockerComposeImport = () => {
} }
setService(doc); setService(doc);
console.log(doc);
}, [dockerCompose]); }, [dockerCompose]);
return <> return <>
@ -134,6 +137,10 @@ const DockerComposeImport = () => {
reader.readAsText(file); reader.readAsText(file);
}} }}
/> />
<div style={{color: 'red'}}>
{ymlError}
</div>
<TextField <TextField
multiline multiline
@ -141,7 +148,12 @@ const DockerComposeImport = () => {
fullWidth fullWidth
value={dockerCompose} value={dockerCompose}
onChange={(e) => setDockerCompose(e.target.value)} onChange={(e) => setDockerCompose(e.target.value)}
style={preStyle} sx={preStyle}
InputProps={{
sx: {
color: '#EEE',
}
}}
rows={20}></TextField> rows={20}></TextField>
</Stack>} </Stack>}
{step === 1 && <Stack spacing={2}> {step === 1 && <Stack spacing={2}>
@ -154,6 +166,7 @@ const DockerComposeImport = () => {
setOpenModal(false); setOpenModal(false);
setStep(0); setStep(0);
setDockerCompose(''); setDockerCompose('');
setYmlError('');
}}>Close</Button> }}>Close</Button>
<Button onClick={() => { <Button onClick={() => {
if(step === 0) { if(step === 0) {

View file

@ -18,6 +18,7 @@ import NetworkContainerSetup from './network';
import VolumeContainerSetup from './volumes'; import VolumeContainerSetup from './volumes';
import DockerTerminal from './terminal'; import DockerTerminal from './terminal';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { smartDockerLogConcat, tryParseProgressLog } from '../../../utils/docker';
const preStyle = { const preStyle = {
backgroundColor: '#000', backgroundColor: '#000',
@ -56,6 +57,7 @@ const NewDockerService = ({service}) => {
const [log, setLog] = React.useState([]); const [log, setLog] = React.useState([]);
const [isDone, setIsDone] = React.useState(false); const [isDone, setIsDone] = React.useState(false);
const [openModal, setOpenModal] = React.useState(false); const [openModal, setOpenModal] = React.useState(false);
const preRef = React.useRef(null);
React.useEffect(() => { React.useEffect(() => {
// refreshContainer(); // refreshContainer();
@ -64,7 +66,8 @@ const NewDockerService = ({service}) => {
const create = () => { const create = () => {
setLog([]) setLog([])
API.docker.createService(service, (newlog) => { API.docker.createService(service, (newlog) => {
setLog((old) => [...old, newlog]); setLog((old) => smartDockerLogConcat(old, newlog));
preRef.current.scrollTop = preRef.current.scrollHeight;
if (newlog.includes('[OPERATION SUCCEEDED]')) { if (newlog.includes('[OPERATION SUCCEEDED]')) {
setIsDone(true); setIsDone(true);
} }
@ -100,19 +103,11 @@ const NewDockerService = ({service}) => {
}} }}
>Restart</Button> >Restart</Button>
} }
<Link to={`/ui/servapps`}>
<Button
variant="outlined"
color="primary"
fullWidth
startIcon={<AppstoreOutlined />}
>Back to Servapps</Button>
</Link>
</Stack>} </Stack>}
<pre style={preStyle}> <pre style={preStyle} ref={preRef}>
{!log.length && JSON.stringify(service, false ,2)} {!log.length && JSON.stringify(service, false ,2)}
{log.map((l) => { {log.map((l) => {
return <div>{l}</div> return <div>{tryParseProgressLog(l)}</div>
})} })}
</pre> </pre>
</Stack> </Stack>

View file

@ -205,7 +205,7 @@ const NewDockerServiceForm = () => {
{ {
title: 'Storage', title: 'Storage',
disabled: maxTab < 2, disabled: maxTab < 2,
children: <Stack spacing={2}><VolumeContainerSetup ontainer containerInfo={containerInfo} OnChange={(values) => { children: <Stack spacing={2}><VolumeContainerSetup newContainer containerInfo={containerInfo} OnChange={(values) => {
console.log(values) console.log(values)
const newValues = { const newValues = {
...containerInfo, ...containerInfo,

View file

@ -12,6 +12,8 @@ const DockerTerminal = ({containerInfo, refresh}) => {
const isInteractive = Config.Tty; const isInteractive = Config.Tty;
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery((theme) => theme.breakpoints.down('sm')) const isMobile = useMediaQuery((theme) => theme.breakpoints.down('sm'))
const [history, setHistory] = useState([]);
const [historyCursor, setHistoryCursor] = useState(0);
const [message, setMessage] = useState(''); const [message, setMessage] = useState('');
const [output, setOutput] = useState([ const [output, setOutput] = useState([
@ -24,10 +26,31 @@ const DockerTerminal = ({containerInfo, refresh}) => {
const [isConnected, setIsConnected] = useState(false); const [isConnected, setIsConnected] = useState(false);
const handleKeyDown = (e) => { const handleKeyDown = (e) => {
if (e.key === 'Enter' && e.ctrlKey) { if (e.key === 'Enter') {
// shift + enter for new line
if (e.shiftKey) {
return;
}
e.preventDefault(); e.preventDefault();
sendMessage(); sendMessage();
} }
if (e.key === 'ArrowUp') {
e.preventDefault();
if (historyCursor > 0) {
setHistoryCursor(historyCursor - 1);
setMessage(history[historyCursor - 1]);
}
}
if (e.key === 'ArrowDown') {
e.preventDefault();
if (historyCursor < history.length - 1) {
setHistoryCursor(historyCursor + 1);
setMessage(history[historyCursor + 1]);
} else {
setHistoryCursor(history.length);
setMessage('');
}
}
}; };
const makeInteractive = () => { const makeInteractive = () => {
@ -85,7 +108,9 @@ const DockerTerminal = ({containerInfo, refresh}) => {
const sendMessage = () => { const sendMessage = () => {
if (ws.current) { if (ws.current) {
ws.current.send(message); ws.current.send(message);
setHistoryCursor(history.length + 1);
setMessage(''); setMessage('');
setHistory((prevHistory) => [...prevHistory, message]);
} }
}; };

View file

@ -0,0 +1,42 @@
export const smartDockerLogConcat = (log, newLogRaw) => {
const containsId = (log, id) => {
try {
const parsedLog = JSON.parse(log);
return parsedLog.id === id;
}
catch (e) {
return false;
}
}
try {
const newLog = JSON.parse(newLogRaw);
if (newLog.id) {
if (log.find((l) => containsId(l, newLog.id))) {
return log.map((l) => (containsId(l, newLog.id) ? newLogRaw : l));
} else {
return [...log, newLogRaw];
}
}
return [...log, newLogRaw];
} catch (e) {
console.log(e);
return [...log, newLogRaw];
}
}
export const tryParseProgressLog = (log) => {
try {
const parsedLog = JSON.parse(log);
if (parsedLog.status && parsedLog.progress) {
return `${parsedLog.id} ${parsedLog.status} ${parsedLog.progress}`
} else if (parsedLog.status && parsedLog.progressDetail && parsedLog.progressDetail.current) {
return `${parsedLog.id} ${parsedLog.status} ${parsedLog.progressDetail.current}/${parsedLog.progressDetail.total}`
} else if (parsedLog.status) {
return `${parsedLog.id} ${parsedLog.status} ${parsedLog.sha256 || ""}`
}
return log;
} catch (e) {
return log;
}
}

View file

@ -1,6 +1,6 @@
{ {
"name": "cosmos-server", "name": "cosmos-server",
"version": "0.5.0-unstable2", "version": "0.5.0-unstable3",
"description": "", "description": "",
"main": "test-server.js", "main": "test-server.js",
"bugs": { "bugs": {

View file

@ -7,6 +7,9 @@ import (
"strings" "strings"
"time" "time"
"bufio" "bufio"
"strconv"
"os"
"os/user"
"errors" "errors"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
@ -311,6 +314,7 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Image %s pulled\n", container.Image) fmt.Fprintf(w, "Image %s pulled\n", container.Image)
flusher.Flush() flusher.Flush()
} }
// Create containers // Create containers
for _, container := range serviceRequest.Services { for _, container := range serviceRequest.Services {
utils.Log(fmt.Sprintf("Creating container %s...", container.Name)) utils.Log(fmt.Sprintf("Creating container %s...", container.Name))
@ -362,6 +366,39 @@ func CreateServiceRoute(w http.ResponseWriter, req *http.Request) {
} }
} }
// Create missing folders for bind mounts
for _, newmount := range container.Volumes {
if newmount.Type == mount.TypeBind {
if _, err := os.Stat(newmount.Source); os.IsNotExist(err) {
err := os.MkdirAll(newmount.Source, 0755)
if err != nil {
utils.Error("CreateService: Unable to create directory for bind mount", err)
fmt.Fprintf(w, "[ERROR] Unable to create directory for bind mount: "+err.Error())
flusher.Flush()
Rollback(rollbackActions, w, flusher)
return
}
// Change the ownership of the directory to the container.User
userInfo, err := user.Lookup(container.User)
if err != nil {
utils.Error("CreateService: Unable to lookup user", err)
fmt.Fprintf(w, "[ERROR] Unable to lookup user: "+err.Error())
flusher.Flush()
} else {
uid, _ := strconv.Atoi(userInfo.Uid)
gid, _ := strconv.Atoi(userInfo.Gid)
err = os.Chown(newmount.Source, uid, gid)
if err != nil {
utils.Error("CreateService: Unable to change ownership of directory", err)
fmt.Fprintf(w, "[ERROR] Unable to change ownership of directory: "+err.Error())
flusher.Flush()
}
}
}
}
}
hostConfig := &conttype.HostConfig{ hostConfig := &conttype.HostConfig{
PortBindings: PortBindings, PortBindings: PortBindings,
Mounts: container.Volumes, Mounts: container.Volumes,

View file

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"os" "os"
"os/user"
"strconv"
"github.com/azukaar/cosmos-server/src/utils" "github.com/azukaar/cosmos-server/src/utils"
containerType "github.com/docker/docker/api/types/container" containerType "github.com/docker/docker/api/types/container"
@ -83,6 +85,33 @@ func UpdateContainerRoute(w http.ResponseWriter, req *http.Request) {
} }
} }
if(form.Volumes != nil) { if(form.Volumes != nil) {
// Create missing folders for bind mounts
for _, newmount := range form.Volumes {
if newmount.Type == mount.TypeBind {
if _, err := os.Stat(newmount.Source); os.IsNotExist(err) {
err := os.MkdirAll(newmount.Source, 0755)
if err != nil {
utils.Error("UpdateService: Unable to create directory for bind mount", err)
utils.HTTPError(w, "Unable to create directory for bind mount: "+err.Error(), http.StatusInternalServerError, "DS004")
return
}
// Change the ownership of the directory to the container.User
userInfo, err := user.Lookup(container.Config.User)
if err != nil {
utils.Error("UpdateService: Unable to lookup user", err)
} else {
uid, _ := strconv.Atoi(userInfo.Uid)
gid, _ := strconv.Atoi(userInfo.Gid)
err = os.Chown(newmount.Source, uid, gid)
if err != nil {
utils.Error("UpdateService: Unable to change ownership of directory", err)
}
}
}
}
}
container.HostConfig.Mounts = form.Volumes container.HostConfig.Mounts = form.Volumes
container.HostConfig.Binds = []string{} container.HostConfig.Binds = []string{}
} }

View file

@ -292,4 +292,4 @@ func Test() error {
// fmt.Println(jellyfin.NetworkSettings) // fmt.Println(jellyfin.NetworkSettings)
return nil return nil
} }