diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d3951eb..a5e343f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: branches: ["main"] env: - VERSION: "1.5.7" + VERSION: "1.5.8" jobs: docker: diff --git a/cmd/server/main.go b/cmd/server/main.go index 51330e7..a67e589 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -12,6 +12,7 @@ func main() { os.Getenv("DATA_PATH"), os.Getenv("LOG_LEVEL"), os.Getenv("SSL_ENABLED"), + os.Getenv("STALENESS_CHECK"), ) s.Run(os.Getenv("BIND_ADDRESS")) } \ No newline at end of file diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 43d9802..5deb70a 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -21,12 +21,15 @@ var ( logLevel string wsUrl string token string + stalenessCheck string = "ON" ) func Main() { parseArgs() setLogLevel(logLevel) - go dockerapi.ContainerScheduleRefreshStaleStatus() + if stalenessCheck != "OFF" { + go dockerapi.ContainerScheduleRefreshStaleStatus() + } listen() } @@ -34,6 +37,7 @@ func parseArgs() { logLevel = os.Getenv("LOG_LEVEL") serverUrl := os.Getenv("SERVER_URL") token = os.Getenv("TOKEN") + stalenessCheck = os.Getenv("STALENESS_CHECK") serverScheme := "ws" if strings.HasPrefix(serverUrl, "https") { diff --git a/pkg/common/common.go b/pkg/common/common.go index 8bd56fb..7987f4d 100644 --- a/pkg/common/common.go +++ b/pkg/common/common.go @@ -1,3 +1,3 @@ package common -const Version = "1.5.7" \ No newline at end of file +const Version = "1.5.8" \ No newline at end of file diff --git a/pkg/dockerapi/compose.go b/pkg/dockerapi/compose.go index b544678..2efb5ce 100644 --- a/pkg/dockerapi/compose.go +++ b/pkg/dockerapi/compose.go @@ -195,27 +195,43 @@ func ComposeLogs(req *DockerComposeLogs, ws *websocket.Conn) error { return nil } -func createTempComposeFile(projectName string, definition string) (string, string, error) { +func createTempComposeFile(projectName string, definition string, variables map[string]store.VariableValue) (string, string, string, error) { dir, err := os.MkdirTemp("", projectName) if err != nil { log.Error().Err(err).Msg("Error while creating temp directory for compose") - return "", "", err + return "", "", "", err } - filename := filepath.Join(dir, "compose.yaml") - composeFile, err := os.Create(filename) + composeFilename := filepath.Join(dir, "compose.yaml") + composeFile, err := os.Create(composeFilename) if err != nil { log.Error().Err(err).Msg("Error while creating temp compose file") - return "", "", err + return "", "", "", err } _ , err = composeFile.WriteString(definition) if err != nil { log.Error().Err(err).Msg("Error while writing to temp compose file") - return "", "", err + return "", "", "", err } - return dir, filename, nil + envFilename := filepath.Join(dir, ".env") + envFile, err := os.Create(envFilename) + if err != nil { + log.Error().Err(err).Msg("Error while creating temp compose file") + return "", "", "", err + } + + envVars := toEnvFormat(variables) + for _, v := range envVars { + _ , err = envFile.WriteString(v + "\r\n") + if err != nil { + log.Error().Err(err).Msg("Error while writing to temp .env file") + return "", "", "", err + } + } + + return dir, composeFilename, envFilename, nil } func toEnvFormat(variables map[string]store.VariableValue) ([]string) { @@ -230,8 +246,7 @@ func toEnvFormat(variables map[string]store.VariableValue) ([]string) { return ret } -func processVars(cmd *exec.Cmd, variables map[string]store.VariableValue, ws *websocket.Conn, print bool) { - cmd.Env = os.Environ() +func logVars(cmd *exec.Cmd, variables map[string]store.VariableValue, ws *websocket.Conn, print bool) { if print { ws.WriteMessage(websocket.TextMessage, []byte("*** SETTING BELOW VARIABLES: ***\n\n")) } @@ -251,35 +266,31 @@ func processVars(cmd *exec.Cmd, variables map[string]store.VariableValue, ws *we ws.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("%s=%s\n", k, val))) } } - - for _, v := range toEnvFormat(variables) { - cmd.Env = append(cmd.Env, v) - } } func performComposeAction(action string, projectName string, definition string, variables map[string]store.VariableValue, ws *websocket.Conn, printVars bool) error { - dir, file, err := createTempComposeFile(projectName, definition) - log.Debug().Str("fileName", file).Msg("Created temporary compose file") + dir, composefile, envfile, err := createTempComposeFile(projectName, definition, variables) + log.Debug().Str("composeFileName", composefile).Str("envFileName", envfile).Msg("Created temporary compose file and .env file") if err != nil { return err } defer func() { - log.Debug().Str("fileName", file).Msg("Deleting temporary compose file") + log.Debug().Str("fileName", composefile).Msg("Deleting temporary compose file and .env file") os.RemoveAll(dir) }() var cmd *exec.Cmd switch action { case "up": - cmd = exec.Command("docker-compose", "-p", projectName, "-f", file, action, "-d") + cmd = exec.Command("docker-compose", "-p", projectName, "--env-file", envfile, "-f", composefile, action, "-d") case "down": - cmd = exec.Command("docker-compose", "-p", projectName, action) + cmd = exec.Command("docker-compose", "-p", projectName, "--env-file", envfile, action) case "pull": - cmd = exec.Command("docker-compose", "-p", projectName, "-f", file, action) + cmd = exec.Command("docker-compose", "-p", projectName, "--env-file", envfile, "-f", composefile, action) default: panic(fmt.Errorf("unknown compose action %s", action)) } - processVars(cmd, variables, ws, printVars) + logVars(cmd, variables, ws, printVars) ws.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("\n*** STARTING ACTION: %s ***\n\n", action))) f, err := pty.Start(cmd) diff --git a/pkg/dockerapi/container_stale_check.go b/pkg/dockerapi/container_stale_check.go index 6fa248e..9e9fc57 100644 --- a/pkg/dockerapi/container_stale_check.go +++ b/pkg/dockerapi/container_stale_check.go @@ -43,7 +43,7 @@ func ContainerScheduleRefreshStaleStatus() { for { log.Info().Msg("Refreshing container stale status") ContainerRefreshStaleStatus() - time.Sleep(1 * time.Hour) + time.Sleep(24 * time.Hour) } } diff --git a/pkg/dockerapi/image.go b/pkg/dockerapi/image.go index 86d0d54..f0d4131 100644 --- a/pkg/dockerapi/image.go +++ b/pkg/dockerapi/image.go @@ -18,7 +18,7 @@ func ImageList(req *DockerImageList) (*DockerImageListResponse, error) { return nil, err } - dcontainers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: req.All}) + dcontainers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true}) if err != nil { return nil, err } diff --git a/pkg/dockerapi/models.go b/pkg/dockerapi/models.go index 82af2d6..95e5945 100644 --- a/pkg/dockerapi/models.go +++ b/pkg/dockerapi/models.go @@ -102,8 +102,9 @@ type DockerVolumeList struct { } type Volume struct { - Driver string `json:"driver"` - Name string `json:"name"` + Driver string `json:"driver"` + Name string `json:"name"` + InUse bool `json:"inUse"` } type DockerVolumeListResponse struct { @@ -131,6 +132,7 @@ type Network struct { Name string `json:"name"` Driver string `json:"driver"` Scope string `json:"scope"` + InUse bool `json:"inUse"` } type DockerNetworkListResponse struct { diff --git a/pkg/dockerapi/network.go b/pkg/dockerapi/network.go index c34d279..67fa3f7 100644 --- a/pkg/dockerapi/network.go +++ b/pkg/dockerapi/network.go @@ -15,6 +15,20 @@ func NetworkList(req *DockerNetworkList) (*DockerNetworkListResponse, error) { return nil, err } + dcontainers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true}) + if err != nil { + return nil, err + } + + usedNetworks := make(map[string]interface{}, 0) + for _, c := range dcontainers { + if c.NetworkSettings != nil { + for _, n := range c.NetworkSettings.Networks { + usedNetworks[n.NetworkID] = nil + } + } + } + dnetworks, err := cli.NetworkList(context.Background(), types.NetworkListOptions{}) if err != nil { return nil, err @@ -22,11 +36,13 @@ func NetworkList(req *DockerNetworkList) (*DockerNetworkListResponse, error) { networks := make([]Network, len(dnetworks)) for i, item := range dnetworks { + _, inUse := usedNetworks[item.ID] networks[i] = Network{ Id: item.ID, Name: item.Name, Driver: item.Driver, Scope: item.Scope, + InUse: inUse, } } diff --git a/pkg/dockerapi/volume.go b/pkg/dockerapi/volume.go index 4f291bf..7cc14c8 100644 --- a/pkg/dockerapi/volume.go +++ b/pkg/dockerapi/volume.go @@ -4,7 +4,9 @@ import ( "context" "sort" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/volume" "github.com/docker/docker/client" ) @@ -15,6 +17,20 @@ func VolumeList(req *DockerVolumeList) (*DockerVolumeListResponse, error) { return nil, err } + dcontainers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true}) + if err != nil { + return nil, err + } + + usedVolumes := make(map[string]interface{}, 0) + for _, c := range dcontainers { + for _, m := range c.Mounts { + if m.Type == mount.TypeVolume { + usedVolumes[m.Name] = nil + } + } + } + dvolumes, err := cli.VolumeList(context.Background(), volume.ListOptions{}) if err != nil { return nil, err @@ -22,9 +38,11 @@ func VolumeList(req *DockerVolumeList) (*DockerVolumeListResponse, error) { volumes := make([]Volume, len(dvolumes.Volumes)) for i, item := range dvolumes.Volumes { + _, inUse := usedVolumes[item.Name] volumes[i] = Volume{ Driver: item.Driver, Name: item.Name, + InUse: inUse, } } @@ -56,7 +74,7 @@ func VolumesPrune(req *DockerVolumesPrune) (*DockerVolumesPruneResponse, error) } all := "true" - if req.All { + if !req.All { all = "false" } allFilter := filters.KeyValuePair{Key: "all", Value: all} diff --git a/pkg/server/handler/request_docker.go b/pkg/server/handler/request_docker.go index 28ce692..3273e4b 100644 --- a/pkg/server/handler/request_docker.go +++ b/pkg/server/handler/request_docker.go @@ -78,7 +78,7 @@ func (r *dockerContainerRemoveRequest) bind(c echo.Context, m *dockerapi.DockerC type dockerImageRemoveRequest struct { Id string `json:"id" validate:"required,max=100"` - Force bool `json:"force" validate:"required"` + Force bool `json:"force"` } func (r *dockerImageRemoveRequest) bind(c echo.Context, m *dockerapi.DockerImageRemove) error { diff --git a/pkg/server/server.go b/pkg/server/server.go index 63ae934..2124d2b 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -35,7 +35,7 @@ type Server struct { sslEnabled bool } -func NewServer(dbConnectionString string, dataPath string, logLevel string, sslEnabled string) (*Server) { +func NewServer(dbConnectionString string, dataPath string, logLevel string, sslEnabled string, stalenessCheck string) (*Server) { s := Server{} setLogLevel(logLevel) @@ -82,7 +82,9 @@ func NewServer(dbConnectionString string, dataPath string, logLevel string, sslE log.Error().Err(err).Msg("Error while updating old version data") } - go dockerapi.ContainerScheduleRefreshStaleStatus() + if stalenessCheck != "OFF" { + go dockerapi.ContainerScheduleRefreshStaleStatus() + } // Web Server s.handler = h diff --git a/web/src/app/images/image-list.tsx b/web/src/app/images/image-list.tsx index 1232b27..5676ebf 100644 --- a/web/src/app/images/image-list.tsx +++ b/web/src/app/images/image-list.tsx @@ -52,7 +52,7 @@ export default function ImageList() { { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ id: image?.id, force: true }), + body: JSON.stringify({ id: image?.id, force: false }), } ) if (!response.ok) { @@ -162,12 +162,14 @@ export default function ImageList() { {item.inUse ? "In use" : "Unused"} {convertByteToMb(item.size)} - { - e.stopPropagation() - handleDeleteImageConfirmation(item) - }} - /> + {!item.inUse && ( + { + e.stopPropagation() + handleDeleteImageConfirmation(item) + }} + /> + )} ))} diff --git a/web/src/app/networks/network-list.tsx b/web/src/app/networks/network-list.tsx index d5fdeba..b2a123f 100644 --- a/web/src/app/networks/network-list.tsx +++ b/web/src/app/networks/network-list.tsx @@ -28,6 +28,15 @@ import { toastFailed, toastSuccess } from "@/lib/utils" import apiBaseUrl from "@/lib/api-base-url" import DeleteDialog from "@/components/delete-dialog" +const systemNetwoks = [ + "none", + "bridge", + "host", + "ingress", + "docker_gwbridge", + "docker_volumes-backup-extension-desktop-extension_default", +] + export default function NetworkList() { const { nodeId } = useParams() const { nodeHead } = useNodeHead(nodeId!) @@ -137,6 +146,7 @@ export default function NetworkList() { Name Driver Scope + Status Actions @@ -151,13 +161,16 @@ export default function NetworkList() { {item.name} {item.driver} {item.scope} + {item.inUse ? "In use" : "Unused"} - { - e.stopPropagation() - handleDeleteNetworkConfirmation(item) - }} - /> + {!systemNetwoks.includes(item.name) && !item.inUse && ( + { + e.stopPropagation() + handleDeleteNetworkConfirmation(item) + }} + /> + )} ))} diff --git a/web/src/app/volumes/volume-list.tsx b/web/src/app/volumes/volume-list.tsx index b675170..c9d09d0 100644 --- a/web/src/app/volumes/volume-list.tsx +++ b/web/src/app/volumes/volume-list.tsx @@ -139,6 +139,7 @@ export default function VolumeList() { Driver Name + Status Actions @@ -151,13 +152,16 @@ export default function VolumeList() { {item.driver} {item.name} + {item.inUse ? "In use" : "Unused"} - { - e.stopPropagation() - handleDeleteVolumeConfirmation(item) - }} - /> + {!item.inUse && ( + { + e.stopPropagation() + handleDeleteVolumeConfirmation(item) + }} + /> + )} ))} diff --git a/web/src/lib/api-models.ts b/web/src/lib/api-models.ts index 0025c90..0c4bfb8 100644 --- a/web/src/lib/api-models.ts +++ b/web/src/lib/api-models.ts @@ -106,6 +106,7 @@ export interface IImage { export interface IVolume { driver: string name: string + inUse: boolean } export interface INetwork { @@ -113,6 +114,7 @@ export interface INetwork { name: string driver: string scope: string + inUse: boolean } export interface IComposeLibraryItemHead { diff --git a/web/src/lib/version.ts b/web/src/lib/version.ts index 0ff6f20..215c988 100644 --- a/web/src/lib/version.ts +++ b/web/src/lib/version.ts @@ -1 +1 @@ -export const VERSION = "1.5.7" +export const VERSION = "1.5.8"