diff --git a/changelog.md b/changelog.md
index 26965e6..8c6fd95 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,7 @@
## Version 0.4.0
- Protect server against direct IP access
+ - Stop / Start / Restart / Remove / Kill containers
+ -
## Version 0.3.0
- Implement 2 FA
diff --git a/client/src/api/docker.demo.jsx b/client/src/api/docker.demo.jsx
index 1c67705..6316d5b 100644
--- a/client/src/api/docker.demo.jsx
+++ b/client/src/api/docker.demo.jsx
@@ -22,8 +22,17 @@ const newDB = () => {
});
}
+const manageContainer = () => {
+ return new Promise((resolve, reject) => {
+ resolve({
+ "status": "ok",
+ })
+ });
+}
+
export {
list,
newDB,
- secure
+ secure,
+ manageContainer
};
\ No newline at end of file
diff --git a/client/src/api/docker.jsx b/client/src/api/docker.jsx
index 4df500f..563e755 100644
--- a/client/src/api/docker.jsx
+++ b/client/src/api/docker.jsx
@@ -26,9 +26,19 @@ const newDB = () => {
}
}))
}
+
+const manageContainer = (id, action) => {
+ return wrap(fetch('/cosmos/api/servapps/' + id + '/manage/' + action, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }))
+}
export {
list,
newDB,
- secure
+ secure,
+ manageContainer
};
\ No newline at end of file
diff --git a/client/src/pages/servapps/servapps.jsx b/client/src/pages/servapps/servapps.jsx
index fd2fe28..57c0d87 100644
--- a/client/src/pages/servapps/servapps.jsx
+++ b/client/src/pages/servapps/servapps.jsx
@@ -1,6 +1,6 @@
// material-ui
-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 { AppstoreAddOutlined, CloseSquareOutlined, DeleteOutlined, PauseCircleOutlined, PlaySquareOutlined, PlusCircleOutlined, ReloadOutlined, RollbackOutlined, SearchOutlined, SettingOutlined, StopOutlined, UpCircleOutlined, UpSquareFilled } from '@ant-design/icons';
+import { Alert, Badge, Button, Card, Checkbox, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, IconButton, Input, InputAdornment, TextField, Tooltip, Typography } from '@mui/material';
import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
import { Stack } from '@mui/system';
import { useEffect, useState } from 'react';
@@ -126,6 +126,88 @@ const ServeApps = () => {
}
}
+ const getActions = (app) => {
+ const doTo = (action) => {
+ setIsUpdatingId(app.Id, true);
+ API.docker.manageContainer(app.Id, action).then((res) => {
+ refreshServeApps();
+ });
+ };
+
+ let actions = [
+ {
+ t: 'Update Available',
+ if: ['update_available'],
+ e: {doTo('update')}} size='large'>
+
+
+ },
+ {
+ t: 'Start',
+ if: ['exited'],
+ e: {doTo('start')}} size='large'>
+
+
+ },
+ {
+ t: 'Unpause',
+ if: ['paused'],
+ e: {doTo('unpause')}} size='large'>
+
+
+ },
+ {
+ t: 'Pause',
+ if: ['running'],
+ e: {doTo('pause')}} size='large'>
+
+
+ },
+ {
+ t: 'Stop',
+ if: ['created', 'paused', 'restarting', 'running'],
+ e: {doTo('stop')}} size='large' variant="outlined">
+
+
+ },
+ {
+ t: 'Restart',
+ if: ['exited', 'running', 'paused', 'created', 'restarting'],
+ e: doTo('restart')} size='large'>
+
+
+ },
+ {
+ t: 'Re-create',
+ if: ['exited', 'running', 'paused', 'created', 'restarting'],
+ e: doTo('recreate')} color="error" size='large'>
+
+
+ },
+ {
+ t: 'Delete',
+ if: ['exited'],
+ e: {doTo('remove')}} color="error" size='large'>
+
+
+ },
+ {
+ t: 'Kill',
+ if: ['running', 'paused', 'created', 'restarting'],
+ e: doTo('kill')} color="error" size='large'>
+
+
+ }
+ ];
+
+ return actions.filter((action) => {
+ let updateAvailable = false;
+ return action.if.includes(app.State) ?? (updateAvailable && action.if.includes('update_available'));
+ }).map((action) => {
+ return {action.e}
+ });
+ }
+
return
@@ -229,31 +311,40 @@ const ServeApps = () => {
return
-
}>
-
-
- {
- ({
- "created": ,
- "restarting": ,
- "running": ,
- "removing": ,
- "paused": ,
- "exited": ,
- "dead": ,
- })[app.State]
- }
-
-
-
-
-
- {app.Names[0].replace('/', '')}
-
-
- {app.Image}
-
+
+
+
+ {
+ ({
+ "created": ,
+ "restarting": ,
+ "running": ,
+ "removing": ,
+ "paused": ,
+ "exited": ,
+ "dead": ,
+ })[app.State]
+ }
+
+
+
+
+
+ {app.Names[0].replace('/', '')}
+
+
+ {app.Image}
+
+
+
+ {/* */}
+
+ {getActions(app)}
+
diff --git a/package.json b/package.json
index e5d974c..4e99b4e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cosmos-server",
- "version": "0.4.0-unstable",
+ "version": "0.4.0-unstable2",
"description": "",
"main": "test-server.js",
"bugs": {
diff --git a/src/docker/api_managecont.go b/src/docker/api_managecont.go
new file mode 100644
index 0000000..35cdd28
--- /dev/null
+++ b/src/docker/api_managecont.go
@@ -0,0 +1,94 @@
+package docker
+
+import (
+ "net/http"
+ "encoding/json"
+ "io"
+ "os"
+
+ "github.com/azukaar/cosmos-server/src/utils"
+
+ "github.com/gorilla/mux"
+ // "github.com/docker/docker/client"
+ contstuff "github.com/docker/docker/api/types/container"
+ doctype "github.com/docker/docker/api/types"
+)
+
+func ManageContainerRoute(w http.ResponseWriter, req *http.Request) {
+ if utils.AdminOnly(w, req) != nil {
+ return
+ }
+
+ errD := Connect()
+ if errD != nil {
+ utils.Error("ManageContainer", errD)
+ utils.HTTPError(w, "Internal server error: " + errD.Error(), http.StatusInternalServerError, "DS002")
+ return
+ }
+
+ vars := mux.Vars(req)
+ containerName := utils.Sanitize(vars["containerId"])
+ // stop, start, restart, kill, remove, pause, unpause, recreate
+ action := utils.Sanitize(vars["action"])
+
+ utils.Log("ManageContainer " + containerName)
+
+ if req.Method == "GET" {
+ container, err := DockerClient.ContainerInspect(DockerContext, containerName)
+ if err != nil {
+ utils.Error("ManageContainer", err)
+ utils.HTTPError(w, "Internal server error: " + err.Error(), http.StatusInternalServerError, "DS002")
+ return
+ }
+
+ imagename := container.Config.Image
+
+ utils.Log("API: " + action + " " + containerName)
+
+ // switch action
+ switch action {
+ case "stop":
+ err = DockerClient.ContainerStop(DockerContext, container.ID, contstuff.StopOptions{})
+ case "start":
+ err = DockerClient.ContainerStart(DockerContext, container.ID, doctype.ContainerStartOptions{})
+ case "restart":
+ err = DockerClient.ContainerRestart(DockerContext, container.ID, contstuff.StopOptions{})
+ case "kill":
+ err = DockerClient.ContainerKill(DockerContext, container.ID, "")
+ case "remove":
+ err = DockerClient.ContainerRemove(DockerContext, container.ID, doctype.ContainerRemoveOptions{})
+ case "pause":
+ err = DockerClient.ContainerPause(DockerContext, container.ID)
+ case "unpause":
+ err = DockerClient.ContainerUnpause(DockerContext, container.ID)
+ case "recreate":
+ _, err = EditContainer(container.ID, container)
+ case "update":
+ pull, errPull := DockerClient.ImagePull(DockerContext, imagename, doctype.ImagePullOptions{})
+ if errPull != nil {
+ utils.Error("Docker Pull", errPull)
+ utils.HTTPError(w, "Cannot pull new image", http.StatusBadRequest, "DS004")
+ return
+ }
+ io.Copy(os.Stdout, pull)
+ _, err = EditContainer(container.ID, container)
+ default:
+ utils.HTTPError(w, "Invalid action", http.StatusBadRequest, "DS003")
+ return
+ }
+
+ if err != nil {
+ utils.Error("ManageContainer: " + action, err)
+ utils.HTTPError(w, "Internal server error: " + err.Error(), http.StatusInternalServerError, "DS004")
+ return
+ }
+
+ json.NewEncoder(w).Encode(map[string]interface{}{
+ "status": "OK",
+ })
+ } else {
+ utils.Error("ManageContainer: Method not allowed" + req.Method, nil)
+ utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001")
+ return
+ }
+}
\ No newline at end of file
diff --git a/src/docker/docker.go b/src/docker/docker.go
index 739b708..3be69dd 100644
--- a/src/docker/docker.go
+++ b/src/docker/docker.go
@@ -190,24 +190,6 @@ func ListContainers() ([]types.Container, error) {
return nil, err
}
- // for _, container := range containers {
- // fmt.Println("ID - ", container.ID)
- // fmt.Println("ID - ", container.Names)
- // fmt.Println("ID - ", container.Image)
- // fmt.Println("ID - ", container.Command)
- // fmt.Println("ID - ", container.State)
- // fmt.Println("Ports - ", container.Ports)
- // fmt.Println("HostConfig - ", container.HostConfig)
- // fmt.Println("ID - ", container.Labels)
- // fmt.Println("NetworkSettings - ", container.NetworkSettings)
- // if(container.NetworkSettings.Networks["cosmos-network"] != nil) {
- // fmt.Println("IP COSMOS - ", container.NetworkSettings.Networks["cosmos-network"].IPAddress);
- // }
- // if(container.NetworkSettings.Networks["bridge"] != nil) {
- // fmt.Println("IP bridge - ", container.NetworkSettings.Networks["bridge"].IPAddress);
- // }
- // }
-
return containers, nil
}
diff --git a/src/httpServer.go b/src/httpServer.go
index 10c5a16..09acf7b 100644
--- a/src/httpServer.go
+++ b/src/httpServer.go
@@ -220,10 +220,14 @@ func StartServer() {
srapi.HandleFunc("/api/users/{nickname}", user.UsersIdRoute)
srapi.HandleFunc("/api/users", user.UsersRoute)
+ srapi.HandleFunc("/api/servapps/{containerId}/manage/{action}", docker.ManageContainerRoute)
srapi.HandleFunc("/api/servapps/{containerId}/secure/{status}", docker.SecureContainerRoute)
srapi.HandleFunc("/api/servapps", docker.ContainersRoute)
- srapi.Use(utils.EnsureHostname(serverHostname))
+ // if(!config.HTTPConfig.AcceptAllInsecureHostname) {
+ // srapi.Use(utils.EnsureHostname(serverHostname))
+ // }
+
srapi.Use(tokenMiddleware)
srapi.Use(proxy.SmartShieldMiddleware(
utils.SmartShieldPolicy{
@@ -251,8 +255,14 @@ func StartServer() {
utils.Fatal("Static folder not found at " + pwd + "/static", err)
}
+
fs := spa.SpaHandler(pwd + "/static", "index.html")
- router.PathPrefix("/ui").Handler(utils.EnsureHostname(serverHostname)(http.StripPrefix("/ui", fs)))
+
+ // if(!config.HTTPConfig.AcceptAllInsecureHostname) {
+ // fs = utils.EnsureHostname(serverHostname)(fs)
+ // }
+
+ router.PathPrefix("/ui").Handler(http.StripPrefix("/ui", fs))
router = proxy.BuildFromConfig(router, HTTPConfig.ProxyConfig)
diff --git a/src/index.go b/src/index.go
index de1ac3b..4accfb7 100644
--- a/src/index.go
+++ b/src/index.go
@@ -10,14 +10,11 @@ import (
func main() {
utils.Log("Starting...")
- // utils.Log("Smart Shield estimates the capacity at " + strconv.Itoa((int)(proxy.MaxUsers)) + " concurrent users")
rand.Seed(time.Now().UnixNano())
LoadConfig()
- checkVersion()
-
go CRON()
docker.Test()
diff --git a/src/utils/types.go b/src/utils/types.go
index 7476bc6..628f429 100644
--- a/src/utils/types.go
+++ b/src/utils/types.go
@@ -101,6 +101,7 @@ type HTTPConfig struct {
ProxyConfig ProxyConfig
Hostname string `validate:"required,excludesall=0x2C/ "`
SSLEmail string `validate:"omitempty,email"`
+ AcceptAllInsecureHostname bool
}
const (