* 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:
Lucas Bernalte 2022-09-20 16:53:17 +02:00 коммит произвёл GitHub
Родитель 7929fa5648
Коммит 766998e3d2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 301 добавлений и 204 удалений

5
.github/workflows/build-scan-push.yaml поставляемый
Просмотреть файл

@ -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="[ \

Просмотреть файл

@ -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:

Просмотреть файл

@ -1,3 +1,4 @@
PUBLIC_URL=.
BROWSER=none
REACT_APP_BUGSNAG_API_KEY=
REACT_APP_BUGSNAG_API_KEY=
REACT_APP_MUI_LICENSE_KEY=

255
ui/package-lock.json сгенерированный
Просмотреть файл

@ -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",

Просмотреть файл

@ -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) => {