feat(): add ux improvements (#80)
* feat(): add ux improvements * fix(): remove unused line * Recalculate volume size after emptying volume * Add volume name to empty and delete dialog confirmation * Set license key in index.tsx * Fix linter issues * Fix secret expression in workflow * Disable cache in workflow temporarily * Update .github/workflows/build-scan-push.yaml * Disable cache in workflow temporarily Co-authored-by: felipecruz91 <felipecruz91@hotmail.es> Co-authored-by: Felipe Cruz Martinez <15997951+felipecruz91@users.noreply.github.com>
This commit is contained in:
Родитель
7929fa5648
Коммит
766998e3d2
|
@ -91,14 +91,15 @@ jobs:
|
|||
labels: |
|
||||
org.opencontainers.image.revision=${{ github.event.pull_request.head.sha || github.event.after || github.event.release.tag_name }}
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
# cache-from: type=gha
|
||||
# cache-to: type=gha,mode=max
|
||||
platforms: linux/amd64,linux/arm64
|
||||
build-args: |
|
||||
BUGSNAG_RELEASE_STAGE=production
|
||||
BUGSNAG_APP_VERSION=${{ github.event.release.tag_name }}
|
||||
secrets: |
|
||||
BUGSNAG_API_KEY=${{ secrets.BUGSNAG_API_KEY }}
|
||||
REACT_APP_MUI_LICENSE_KEY=${{ secrets.REACT_APP_MUI_LICENSE_KEY }}
|
||||
|
||||
# If PR, put image tags in the PR comments
|
||||
# from https://github.com/marketplace/actions/create-or-update-comment
|
||||
|
|
|
@ -23,6 +23,9 @@ COPY ui /ui
|
|||
RUN --mount=type=secret,id=BUGSNAG_API_KEY \
|
||||
REACT_APP_BUGSNAG_API_KEY=$(cat /run/secrets/BUGSNAG_API_KEY) \
|
||||
npm run build
|
||||
RUN --mount=type=secret,id=REACT_APP_MUI_LICENSE_KEY \
|
||||
REACT_APP_MUI_LICENSE_KEY=$(cat /run/secrets/REACT_APP_MUI_LICENSE_KEY) \
|
||||
yarn build
|
||||
|
||||
FROM alpine:3.16@sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad as base
|
||||
ARG CLI_VERSION=20.10.17
|
||||
|
@ -51,7 +54,7 @@ ENV BUGSNAG_RELEASE_STAGE=$BUGSNAG_RELEASE_STAGE
|
|||
ENV BUGSNAG_APP_VERSION=$BUGSNAG_APP_VERSION
|
||||
|
||||
LABEL org.opencontainers.image.title="Volumes Backup & Share" \
|
||||
org.opencontainers.image.description="Back up, clone, restore, and share Docker volumes effortlessly." \
|
||||
org.opencontainers.image.description="Backup, clone, restore, and share Docker volumes effortlessly." \
|
||||
org.opencontainers.image.vendor="Docker Inc." \
|
||||
com.docker.desktop.extension.api.version=">= 0.2.3" \
|
||||
com.docker.extension.screenshots="[ \
|
||||
|
|
4
Makefile
4
Makefile
|
@ -1,6 +1,7 @@
|
|||
IMAGE?=docker/volumes-backup-extension
|
||||
TAG?=latest
|
||||
BUILDER=buildx-multi-arch
|
||||
REACT_APP_MUI_LICENSE_KEY?=UNKNOWN_KEY
|
||||
|
||||
export BUGSNAG_API_KEY?=
|
||||
export BUGSNAG_RELEASE_STAGE?=local
|
||||
|
@ -11,6 +12,7 @@ NO_COLOR = \033[m
|
|||
build-extension: ## Build service image to be deployed as a desktop extension
|
||||
docker buildx build \
|
||||
--secret id=BUGSNAG_API_KEY \
|
||||
--secret id=REACT_APP_MUI_LICENSE_KEY \
|
||||
--build-arg BUGSNAG_RELEASE_STAGE=$(BUGSNAG_RELEASE_STAGE) \
|
||||
--build-arg BUGSNAG_APP_VERSION=$(TAG) \
|
||||
--load \
|
||||
|
@ -34,7 +36,7 @@ prepare-buildx: ## Create buildx builder for multi-arch build, if not exists
|
|||
docker buildx inspect $(BUILDER) || docker buildx create --name=$(BUILDER) --driver=docker-container --driver-opt=network=host
|
||||
|
||||
push-extension: prepare-buildx ## Build & Upload extension image to hub. Do not push if tag already exists: make push-extension tag=0.1
|
||||
docker pull $(IMAGE):$(TAG) && echo "Failure: Tag already exists" || docker buildx build --secret id=BUGSNAG_API_KEY --build-arg BUGSNAG_RELEASE_STAGE=$(BUGSNAG_RELEASE_STAGE) --build-arg BUGSNAG_APP_VERSION=$(TAG) --push --builder=$(BUILDER) --platform=linux/amd64,linux/arm64 --build-arg TAG=$(TAG) --tag=$(IMAGE):$(TAG) .
|
||||
docker pull $(IMAGE):$(TAG) && echo "Failure: Tag already exists" || docker buildx build --secret id=BUGSNAG_API_KEY --secret id=REACT_APP_MUI_LICENSE_KEY --build-arg BUGSNAG_RELEASE_STAGE=$(BUGSNAG_RELEASE_STAGE) --build-arg BUGSNAG_APP_VERSION=$(TAG) --push --builder=$(BUILDER) --platform=linux/amd64,linux/arm64 --build-arg TAG=$(TAG) --tag=$(IMAGE):$(TAG) .
|
||||
|
||||
help: ## Show this help
|
||||
@echo Please specify a build target. The choices are:
|
||||
|
|
3
ui/.env
3
ui/.env
|
@ -1,3 +1,4 @@
|
|||
PUBLIC_URL=.
|
||||
BROWSER=none
|
||||
REACT_APP_BUGSNAG_API_KEY=
|
||||
REACT_APP_BUGSNAG_API_KEY=
|
||||
REACT_APP_MUI_LICENSE_KEY=
|
|
@ -14,7 +14,7 @@
|
|||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/material": "^5.6.1",
|
||||
"@mui/x-data-grid": "^5.12.1",
|
||||
"@mui/x-data-grid-pro": "^5.10.0",
|
||||
"cra-template": "1.1.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
|
@ -1766,9 +1766,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.18.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz",
|
||||
"integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==",
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz",
|
||||
"integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
},
|
||||
|
@ -3149,28 +3149,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/@mui/utils": {
|
||||
"version": "5.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.4.tgz",
|
||||
"integrity": "sha512-BHYErfrjqqh76KaDAm8wZlhEip1Uj7Cmco65NcsF3BWrAl3FWngACpaPZeEbTgmaEwyWAQEE6LZhsmy43hfyqQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react-is": "^16.7.1 || ^17.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/react-transition-group": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
|
||||
|
@ -3281,28 +3259,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system/node_modules/@mui/utils": {
|
||||
"version": "5.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.4.tgz",
|
||||
"integrity": "sha512-BHYErfrjqqh76KaDAm8wZlhEip1Uj7Cmco65NcsF3BWrAl3FWngACpaPZeEbTgmaEwyWAQEE6LZhsmy43hfyqQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react-is": "^16.7.1 || ^17.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/types": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.4.tgz",
|
||||
|
@ -3316,16 +3272,43 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "5.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-5.12.1.tgz",
|
||||
"integrity": "sha512-kOhmCqaI87c411uUHnojsaSgebvzkMbQXtw0lVGmNDcxWTugsxH0n7FC7+KVOc/EKyGRifvgIpiKKB17BI61pg==",
|
||||
"node_modules/@mui/utils": {
|
||||
"version": "5.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.10.3.tgz",
|
||||
"integrity": "sha512-4jXMDPfx6bpMVuheLaOpKTjpzw39ogAZLeaLj5+RJec3E37/hAZMYjURfblLfTWMMoGoqkY03mNsZaEwNobBow==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@mui/utils": "^5.4.1",
|
||||
"clsx": "^1.1.1",
|
||||
"@babel/runtime": "^7.18.9",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react-is": "^16.7.1 || ^17.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^4.1.5"
|
||||
"react-is": "^18.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/utils/node_modules/react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "5.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-5.17.2.tgz",
|
||||
"integrity": "sha512-zAwX57HHAanmPem1UrfsYNUX4cazrVpXZvgbfkrjDFjsmCuWz9vTuk67YgEx5VuDG1U/suFB1E9UO/LpYb7n4A==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.9",
|
||||
"@mui/utils": "^5.9.3",
|
||||
"clsx": "^1.2.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^4.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
|
@ -3341,26 +3324,43 @@
|
|||
"react-dom": "^17.0.2 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid/node_modules/@mui/utils": {
|
||||
"version": "5.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.4.tgz",
|
||||
"integrity": "sha512-BHYErfrjqqh76KaDAm8wZlhEip1Uj7Cmco65NcsF3BWrAl3FWngACpaPZeEbTgmaEwyWAQEE6LZhsmy43hfyqQ==",
|
||||
"node_modules/@mui/x-data-grid-pro": {
|
||||
"version": "5.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid-pro/-/x-data-grid-pro-5.17.2.tgz",
|
||||
"integrity": "sha512-SlilmsinExc7J6w/1cumG0u9RYY0+czA4mv4Q0aZG4r4JlbZg464DRYwps0QpxQkHUA4OKGLWRsOxMe+iYtB1Q==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react-is": "^16.7.1 || ^17.0.0",
|
||||
"@babel/runtime": "^7.18.9",
|
||||
"@mui/utils": "^5.9.3",
|
||||
"@mui/x-data-grid": "5.17.2",
|
||||
"@mui/x-license-pro": "5.17.0",
|
||||
"@types/format-util": "^1.0.2",
|
||||
"clsx": "^1.2.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^17.0.2"
|
||||
"reselect": "^4.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui"
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^5.4.1",
|
||||
"@mui/system": "^5.4.1",
|
||||
"react": "^17.0.2 || ^18.0.0",
|
||||
"react-dom": "^17.0.2 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-license-pro": {
|
||||
"version": "5.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-license-pro/-/x-license-pro-5.17.0.tgz",
|
||||
"integrity": "sha512-SVn0E1sUSjuvT79mvfFnW1BtwfiQ+yg6hZpTNwS13a9hJLRJYDI+GiEypArsXRHHvs5XL5PVMmfmHs5voQF9Iw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.9",
|
||||
"@mui/utils": "^5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0"
|
||||
"react": "^17.0.2 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
|
@ -3940,6 +3940,11 @@
|
|||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/format-util": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/format-util/-/format-util-1.0.2.tgz",
|
||||
"integrity": "sha512-9SrLCpgzWo2yHHhiMOX0WwgDh37nSbDbWUsRc1ss++o8O97E3tB6SJiyUQM21UeUsKvZNuhDCmkRaINZ4uJAfg=="
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
||||
|
@ -5994,9 +5999,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
|
||||
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
|
@ -18291,9 +18296,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.18.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz",
|
||||
"integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==",
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz",
|
||||
"integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
|
@ -19240,18 +19245,6 @@
|
|||
"react-is": "^17.0.2"
|
||||
}
|
||||
},
|
||||
"@mui/utils": {
|
||||
"version": "5.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.4.tgz",
|
||||
"integrity": "sha512-BHYErfrjqqh76KaDAm8wZlhEip1Uj7Cmco65NcsF3BWrAl3FWngACpaPZeEbTgmaEwyWAQEE6LZhsmy43hfyqQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react-is": "^16.7.1 || ^17.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^17.0.2"
|
||||
}
|
||||
},
|
||||
"react-transition-group": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
|
||||
|
@ -19299,18 +19292,6 @@
|
|||
"@emotion/cache": "^11.7.1",
|
||||
"prop-types": "^15.8.1"
|
||||
}
|
||||
},
|
||||
"@mui/utils": {
|
||||
"version": "5.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.4.tgz",
|
||||
"integrity": "sha512-BHYErfrjqqh76KaDAm8wZlhEip1Uj7Cmco65NcsF3BWrAl3FWngACpaPZeEbTgmaEwyWAQEE6LZhsmy43hfyqQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react-is": "^16.7.1 || ^17.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^17.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -19320,32 +19301,61 @@
|
|||
"integrity": "sha512-uveM3byMbthO+6tXZ1n2zm0W3uJCQYtwt/v5zV5I77v2v18u0ITkb8xwhsDD2i3V2Kye7SaNR6FFJ6lMuY/WqQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"@mui/x-data-grid": {
|
||||
"version": "5.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-5.12.1.tgz",
|
||||
"integrity": "sha512-kOhmCqaI87c411uUHnojsaSgebvzkMbQXtw0lVGmNDcxWTugsxH0n7FC7+KVOc/EKyGRifvgIpiKKB17BI61pg==",
|
||||
"@mui/utils": {
|
||||
"version": "5.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.10.3.tgz",
|
||||
"integrity": "sha512-4jXMDPfx6bpMVuheLaOpKTjpzw39ogAZLeaLj5+RJec3E37/hAZMYjURfblLfTWMMoGoqkY03mNsZaEwNobBow==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@mui/utils": "^5.4.1",
|
||||
"clsx": "^1.1.1",
|
||||
"@babel/runtime": "^7.18.9",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react-is": "^16.7.1 || ^17.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^4.1.5"
|
||||
"react-is": "^18.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mui/utils": {
|
||||
"version": "5.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.4.tgz",
|
||||
"integrity": "sha512-BHYErfrjqqh76KaDAm8wZlhEip1Uj7Cmco65NcsF3BWrAl3FWngACpaPZeEbTgmaEwyWAQEE6LZhsmy43hfyqQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@types/prop-types": "^15.7.5",
|
||||
"@types/react-is": "^16.7.1 || ^17.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^17.0.2"
|
||||
}
|
||||
"react-is": {
|
||||
"version": "18.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@mui/x-data-grid": {
|
||||
"version": "5.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-5.17.2.tgz",
|
||||
"integrity": "sha512-zAwX57HHAanmPem1UrfsYNUX4cazrVpXZvgbfkrjDFjsmCuWz9vTuk67YgEx5VuDG1U/suFB1E9UO/LpYb7n4A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.18.9",
|
||||
"@mui/utils": "^5.9.3",
|
||||
"clsx": "^1.2.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"@mui/x-data-grid-pro": {
|
||||
"version": "5.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid-pro/-/x-data-grid-pro-5.17.2.tgz",
|
||||
"integrity": "sha512-SlilmsinExc7J6w/1cumG0u9RYY0+czA4mv4Q0aZG4r4JlbZg464DRYwps0QpxQkHUA4OKGLWRsOxMe+iYtB1Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.18.9",
|
||||
"@mui/utils": "^5.9.3",
|
||||
"@mui/x-data-grid": "5.17.2",
|
||||
"@mui/x-license-pro": "5.17.0",
|
||||
"@types/format-util": "^1.0.2",
|
||||
"clsx": "^1.2.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"@mui/x-license-pro": {
|
||||
"version": "5.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-license-pro/-/x-license-pro-5.17.0.tgz",
|
||||
"integrity": "sha512-SVn0E1sUSjuvT79mvfFnW1BtwfiQ+yg6hZpTNwS13a9hJLRJYDI+GiEypArsXRHHvs5XL5PVMmfmHs5voQF9Iw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.18.9",
|
||||
"@mui/utils": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -19744,6 +19754,11 @@
|
|||
"@types/range-parser": "*"
|
||||
}
|
||||
},
|
||||
"@types/format-util": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/format-util/-/format-util-1.0.2.tgz",
|
||||
"integrity": "sha512-9SrLCpgzWo2yHHhiMOX0WwgDh37nSbDbWUsRc1ss++o8O97E3tB6SJiyUQM21UeUsKvZNuhDCmkRaINZ4uJAfg=="
|
||||
},
|
||||
"@types/graceful-fs": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
||||
|
@ -21253,9 +21268,9 @@
|
|||
}
|
||||
},
|
||||
"clsx": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
|
||||
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
|
||||
},
|
||||
"co": {
|
||||
"version": "4.6.0",
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/icons-material": "^5.8.4",
|
||||
"@mui/material": "^5.6.1",
|
||||
"@mui/x-data-grid": "^5.12.1",
|
||||
"@mui/x-data-grid-pro": "^5.10.0",
|
||||
"cra-template": "1.1.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
|
|
119
ui/src/App.tsx
119
ui/src/App.tsx
|
@ -1,13 +1,13 @@
|
|||
import React, { useContext, useEffect } from "react";
|
||||
import {
|
||||
DataGrid,
|
||||
DataGridPro,
|
||||
GridActionsCellItem,
|
||||
GridCellParams,
|
||||
GridToolbarColumnsButton,
|
||||
GridToolbarContainer,
|
||||
GridToolbarDensitySelector,
|
||||
GridToolbarFilterButton,
|
||||
} from "@mui/x-data-grid";
|
||||
} from "@mui/x-data-grid-pro";
|
||||
import { createDockerDesktopClient } from "@docker/extension-api-client";
|
||||
import {
|
||||
Box,
|
||||
|
@ -35,40 +35,27 @@ import CloneDialog from "./components/CloneDialog";
|
|||
import TransferDialog from "./components/TransferDialog";
|
||||
import DeleteForeverDialog from "./components/DeleteForeverDialog";
|
||||
import { MyContext } from ".";
|
||||
import { isError } from "./common/isError";
|
||||
import ImportDialog from "./components/ImportDialog";
|
||||
import { useGetVolumes } from "./hooks/useGetVolumes";
|
||||
import { Header } from "./components/Header";
|
||||
import { track } from "./common/track";
|
||||
import EmptyConfirmationDialog from "./components/EmptyConfirmationDialog";
|
||||
|
||||
const ddClient = createDockerDesktopClient();
|
||||
|
||||
interface ICustomToolbar {
|
||||
openDialog(): void;
|
||||
}
|
||||
|
||||
function CustomToolbar({ openDialog }: ICustomToolbar) {
|
||||
function CustomToolbar() {
|
||||
return (
|
||||
<GridToolbarContainer>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Grid item>
|
||||
<GridToolbarColumnsButton />
|
||||
<GridToolbarFilterButton />
|
||||
<GridToolbarDensitySelector />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
track({ action: "ImportNewVolumePopup" });
|
||||
openDialog();
|
||||
}}
|
||||
endIcon={<DownloadIcon />}
|
||||
>
|
||||
Import into new volume
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<GridToolbarContainer
|
||||
sx={{
|
||||
"& .MuiButton-root": {
|
||||
color: (theme) => theme.palette.docker.grey[500],
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<GridToolbarColumnsButton />
|
||||
<GridToolbarFilterButton />
|
||||
<GridToolbarDensitySelector />
|
||||
</GridToolbarContainer>
|
||||
);
|
||||
}
|
||||
|
@ -89,6 +76,8 @@ export function App() {
|
|||
React.useState<boolean>(false);
|
||||
const [openDeleteForeverDialog, setOpenDeleteForeverDialog] =
|
||||
React.useState<boolean>(false);
|
||||
const [openEmptyConfirmationDialog, setOpenEmptyConfirmationDialog] =
|
||||
React.useState<boolean>(false);
|
||||
|
||||
const [actionsInProgress, setActionsInProgress] = React.useState({});
|
||||
|
||||
|
@ -236,7 +225,7 @@ export function App() {
|
|||
</Tooltip>
|
||||
}
|
||||
label="Empty volume"
|
||||
onClick={handleEmpty(params.row)}
|
||||
onClick={() => handleEmpty(params.row)}
|
||||
showInMenu
|
||||
disabled={params.row.volumeSize === "0 B"}
|
||||
/>,
|
||||
|
@ -286,10 +275,14 @@ export function App() {
|
|||
context.actions.setVolume(row);
|
||||
};
|
||||
|
||||
const handleEmpty = (row) => async () => {
|
||||
track({ action: "EmptyVolume" });
|
||||
await emptyVolume(row.volumeName);
|
||||
await calculateVolumeSize(row.volumeName);
|
||||
const handleEmpty = (row) => {
|
||||
setOpenEmptyConfirmationDialog(true);
|
||||
context.actions.setVolume(row);
|
||||
};
|
||||
|
||||
const handleConfirmateEmpty = async () => {
|
||||
const volumeName = context.store.volume.volumeName;
|
||||
await calculateVolumeSize(volumeName);
|
||||
};
|
||||
|
||||
const handleDelete = (row) => async () => {
|
||||
|
@ -365,34 +358,6 @@ export function App() {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const emptyVolume = (volumeName: string) => {
|
||||
return ddClient.docker.cli
|
||||
.exec("run", [
|
||||
"--rm",
|
||||
"--label com.volumes-backup-extension.trigger-ui-refresh=true",
|
||||
"--label com.docker.compose.project=docker_volumes-backup-extension-desktop-extension",
|
||||
`-v=${volumeName}:/vackup-volume `,
|
||||
"busybox",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
'"rm -rf /vackup-volume/..?* /vackup-volume/.[!.]* /vackup-volume/*"', // hidden and not-hidden files and folders: .[!.]* matches all dot files except . and files whose name begins with .., and ..?* matches all dot-dot files except ..
|
||||
])
|
||||
.then((output) => {
|
||||
if (isError(output.stderr)) {
|
||||
sendNotification.error(output.stderr);
|
||||
return;
|
||||
}
|
||||
sendNotification.info(
|
||||
`The content of volume ${volumeName} has been removed`
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
sendNotification.error(
|
||||
`Failed to empty volume ${volumeName}: ${error.stderr} Exit code: ${error.code}`
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleExportDialogClose = () => {
|
||||
setOpenExportDialog(false);
|
||||
context.actions.setVolume(null);
|
||||
|
@ -457,6 +422,11 @@ export function App() {
|
|||
context.actions.setVolume(null);
|
||||
};
|
||||
|
||||
const handleEmptyConfirmationDialogClose = () => {
|
||||
setOpenEmptyConfirmationDialog(false);
|
||||
context.actions.setVolume(null);
|
||||
};
|
||||
|
||||
const handleDeleteForeverDialogCompletion = (
|
||||
actionSuccessfullyCompleted: boolean
|
||||
) => {
|
||||
|
@ -510,15 +480,23 @@ export function App() {
|
|||
<Stack direction="column" alignItems="start" spacing={2} sx={{ mt: 4 }}>
|
||||
<Grid container>
|
||||
<Grid item flex={1}>
|
||||
<DataGrid
|
||||
<Grid item sx={{ display: "flex", justifyContent: "flex-end" }}>
|
||||
<Button
|
||||
variant="text"
|
||||
onClick={() => {
|
||||
track({ action: "ImportNewVolumePopup" });
|
||||
setOpenImportIntoNewDialog(true);
|
||||
}}
|
||||
endIcon={<DownloadIcon />}
|
||||
>
|
||||
Import into new volume
|
||||
</Button>
|
||||
</Grid>
|
||||
<DataGridPro
|
||||
loading={isLoading}
|
||||
components={{
|
||||
LoadingOverlay: LinearProgress,
|
||||
Toolbar: () => (
|
||||
<CustomToolbar
|
||||
openDialog={() => setOpenImportIntoNewDialog(true)}
|
||||
/>
|
||||
),
|
||||
Toolbar: () => <CustomToolbar />,
|
||||
}}
|
||||
rows={rows || []}
|
||||
columns={columns}
|
||||
|
@ -590,6 +568,13 @@ export function App() {
|
|||
onCompletion={handleDeleteForeverDialogCompletion}
|
||||
/>
|
||||
)}
|
||||
{openEmptyConfirmationDialog && (
|
||||
<EmptyConfirmationDialog
|
||||
open={openEmptyConfirmationDialog}
|
||||
onClose={handleEmptyConfirmationDialogClose}
|
||||
onCompletion={handleConfirmateEmpty}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</Stack>
|
||||
</>
|
||||
|
|
|
@ -47,8 +47,8 @@ export default function DeleteForeverDialog({ ...props }: Props) {
|
|||
<DialogTitle>Delete a volume permanently</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
The volume will be deleted permanently. This action cannot be undone.
|
||||
Are you sure?
|
||||
The volume <strong>{context.store.volume.volumeName}</strong> will be
|
||||
deleted permanently. This action cannot be undone. Are you sure?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
|
@ -61,7 +61,7 @@ export default function DeleteForeverDialog({ ...props }: Props) {
|
|||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" onClick={deleteVolume}>
|
||||
<Button variant="contained" color="error" onClick={deleteVolume}>
|
||||
Delete forever
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import { useContext } from "react";
|
||||
import { Button } from "@mui/material";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import { createDockerDesktopClient } from "@docker/extension-api-client";
|
||||
|
||||
import { MyContext } from "../index";
|
||||
import { useNotificationContext } from "../NotificationContext";
|
||||
import { track } from "../common/track";
|
||||
import { isError } from "../common/isError";
|
||||
|
||||
const ddClient = createDockerDesktopClient();
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
onClose(): void;
|
||||
onCompletion(v?: boolean): void;
|
||||
}
|
||||
|
||||
export default function EmptyConfirmationDialog({ ...props }: Props) {
|
||||
const context = useContext(MyContext);
|
||||
const { sendNotification } = useNotificationContext();
|
||||
|
||||
const emptyVolume = () => {
|
||||
track({ action: "EmptyVolume" });
|
||||
ddClient.docker.cli
|
||||
.exec("run", [
|
||||
"--rm",
|
||||
"--label com.volumes-backup-extension.trigger-ui-refresh=true",
|
||||
"--label com.docker.compose.project=docker_volumes-backup-extension-desktop-extension",
|
||||
`-v=${context.store.volume.volumeName}:/vackup-volume `,
|
||||
"busybox",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
'"rm -rf /vackup-volume/..?* /vackup-volume/.[!.]* /vackup-volume/*"', // hidden and not-hidden files and folders: .[!.]* matches all dot files except . and files whose name begins with .., and ..?* matches all dot-dot files except ..
|
||||
])
|
||||
.then((output) => {
|
||||
if (isError(output.stderr)) {
|
||||
sendNotification.error(output.stderr);
|
||||
props.onCompletion(false);
|
||||
return;
|
||||
}
|
||||
sendNotification.info(
|
||||
`The content of volume ${context.store.volume.volumeName} has been removed`
|
||||
);
|
||||
props.onCompletion(true);
|
||||
})
|
||||
.catch((error) => {
|
||||
sendNotification.error(
|
||||
`Failed to empty volume ${context.store.volume.volumeName}: ${error.stderr} Exit code: ${error.code}`
|
||||
);
|
||||
props.onCompletion(false);
|
||||
});
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={props.open} onClose={props.onClose}>
|
||||
<DialogTitle>Empty a volume</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
The volume <strong>{context.store.volume.volumeName}</strong> will be
|
||||
emptied. This action cannot be undone. Are you sure?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
track({ action: "DeleteVolumeCancel" });
|
||||
props.onClose();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" onClick={emptyVolume}>
|
||||
Empty
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
|
@ -232,7 +232,7 @@ export default function ExportDialog({ open, onClose }: Props) {
|
|||
<DialogTitle>Export content</DialogTitle>
|
||||
<DialogContent>
|
||||
<FormControl>
|
||||
<FormLabel id="from-label">
|
||||
<FormLabel id="from-label" focused={false}>
|
||||
<Typography variant="h3" my={1}>
|
||||
From:
|
||||
</Typography>
|
||||
|
@ -240,7 +240,7 @@ export default function ExportDialog({ open, onClose }: Props) {
|
|||
<VolumeOrInput />
|
||||
</FormControl>
|
||||
<FormControl sx={{ width: "100%" }}>
|
||||
<FormLabel id="to-label">
|
||||
<FormLabel id="to-label" focused={false}>
|
||||
<Typography variant="h3" mt={3} mb={1}>
|
||||
To:
|
||||
</Typography>
|
||||
|
|
|
@ -30,7 +30,7 @@ export const Header = () => (
|
|||
</Link>
|
||||
</Grid>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mt: 2 }}>
|
||||
Back up, clone, restore, and share Docker volumes effortlessly.
|
||||
Backup, clone, restore, and share Docker volumes effortlessly.
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -222,14 +222,16 @@ export default function ImportDialog({
|
|||
<Stack>
|
||||
{selectedVolumeName && (
|
||||
<Alert
|
||||
sx={(theme) => ({ marginBottom: theme.spacing(2) })}
|
||||
sx={(theme) => ({
|
||||
marginBottom: theme.spacing(2),
|
||||
})}
|
||||
severity="warning"
|
||||
>
|
||||
Any existing data inside the volume will be replaced.
|
||||
</Alert>
|
||||
)}
|
||||
<FormControl>
|
||||
<FormLabel id="from-label">
|
||||
<FormLabel id="from-label" focused={false}>
|
||||
<Typography variant="h3" mb={1}>
|
||||
From:
|
||||
</Typography>
|
||||
|
@ -248,7 +250,7 @@ export default function ImportDialog({
|
|||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel id="to-label">
|
||||
<FormLabel id="to-label" focused={false}>
|
||||
<Typography variant="h3" mt={3} mb={1}>
|
||||
To:
|
||||
</Typography>
|
||||
|
|
|
@ -157,7 +157,7 @@ export default function TransferDialog({ ...props }: Props) {
|
|||
Any existing data inside the destination volume will be replaced.
|
||||
</Alert>
|
||||
<FormControl>
|
||||
<FormLabel id="from-label">
|
||||
<FormLabel id="from-label" focused={false}>
|
||||
<Typography variant="h3" my={1}>
|
||||
From:
|
||||
</Typography>
|
||||
|
@ -165,7 +165,7 @@ export default function TransferDialog({ ...props }: Props) {
|
|||
<VolumeOrInput />
|
||||
</FormControl>
|
||||
<FormControl sx={{ width: "100%" }}>
|
||||
<FormLabel id="to-label">
|
||||
<FormLabel id="to-label" focused={false}>
|
||||
<Typography variant="h3" mt={3} mb={1}>
|
||||
To:
|
||||
</Typography>
|
||||
|
|
|
@ -6,6 +6,7 @@ import { DockerMuiThemeProvider } from "@docker/docker-mui-theme";
|
|||
import { App } from "./App";
|
||||
import type { IVolumeRow } from "./hooks/useGetVolumes";
|
||||
import { NotificationProvider } from "./NotificationContext";
|
||||
import { LicenseInfo } from "@mui/x-data-grid-pro";
|
||||
|
||||
interface IAppContext {
|
||||
store: {
|
||||
|
@ -16,6 +17,8 @@ interface IAppContext {
|
|||
};
|
||||
}
|
||||
|
||||
LicenseInfo.setLicenseKey(process.env["REACT_APP_MUI_LICENSE_KEY"]);
|
||||
|
||||
export const MyContext = React.createContext<IAppContext>(null);
|
||||
|
||||
const AppProvider: React.FC = (props) => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче