[release] version 0.5.0-unstable3
This commit is contained in:
parent
c670456d47
commit
3cbd88f4a6
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
42
client/src/utils/docker.js
Normal file
42
client/src/utils/docker.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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": {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,4 +292,4 @@ func Test() error {
|
||||||
// fmt.Println(jellyfin.NetworkSettings)
|
// fmt.Println(jellyfin.NetworkSettings)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue