git.iko.soy/typeable/octopod
on master help sign in
153 files changed+6311−3987
since splitsnapshots
unifiedsplit
.github/actions/common_setup/action.yaml+40 .github/actions/push_docker_images/action.yaml+26 .github/workflows/build.yaml+54−84 .github/workflows/check_documentation.yaml+1−1 .nova/Configuration.json+5 .vscode/settings.json+6−1 Development_guide.md+5−12 Makefile+3 Release_checklist.md+5−18 build.sh−60 cabal.project+6 charts/octopod/Chart.yaml+2−2 charts/octopod/README.md+15−8 charts/octopod/index.yaml+37 charts/octopod/templates/ingress-app.yaml+11−5 charts/octopod/templates/ingress-powerapp.yaml+11−5 charts/octopod/templates/ingress-ui.yaml+11−5 charts/octopod/templates/ingress-ws.yaml+11−5 charts/octopod/values.yaml+1−1 default.nix+11−4 dev/default.nix+51−7 docs/diagrams/images/technical-architecture-archive-via-cli.png docs/diagrams/images/technical-architecture-archive-via-ui.png docs/diagrams/images/technical-architecture-archive.png docs/diagrams/images/technical-architecture-cleanup-via-cli.png docs/diagrams/images/technical-architecture-cleanup-via-ui.png docs/diagrams/images/technical-architecture-cleanup.png docs/diagrams/images/technical-architecture-create-via-cli.png docs/diagrams/images/technical-architecture-create-via-ui.png docs/diagrams/images/technical-architecture-create.png docs/diagrams/images/technical-architecture-restore-via-cli.png docs/diagrams/images/technical-architecture-restore-via-ui.png docs/diagrams/images/technical-architecture-restore.png docs/diagrams/images/technical-architecture-update-via-cli.png docs/diagrams/images/technical-architecture-update-via-ui.png docs/diagrams/images/technical-architecture-update.png docs/diagrams/src/technical-architecture-archive-via-cli.mmd−37 docs/diagrams/src/technical-architecture-archive-via-ui.mmd−36 docs/diagrams/src/technical-architecture-archive.mmd+31 docs/diagrams/src/technical-architecture-cleanup-via-cli.mmd−20 docs/diagrams/src/technical-architecture-cleanup-via-ui.mmd−19 docs/diagrams/src/technical-architecture-cleanup.mmd+14 docs/diagrams/src/technical-architecture-create-via-cli.mmd−41 docs/diagrams/src/technical-architecture-create-via-ui.mmd−40 docs/diagrams/src/technical-architecture-create.mmd+41 docs/diagrams/src/technical-architecture-restore-via-cli.mmd−41 docs/diagrams/src/technical-architecture-restore-via-ui.mmd−40 docs/diagrams/src/technical-architecture-restore.mmd+42 docs/diagrams/src/technical-architecture-update-via-cli.mmd−41 docs/diagrams/src/technical-architecture-update-via-ui.mmd−40 docs/diagrams/src/technical-architecture-update.mmd+46 docs/en/Octopod_deployment_guide.md+39−6 docs/en/Overview.md+2−2 docs/en/Technical_architecture.md+8−135 helm-control-scripts/Cargo.lock+459−168 helm-control-scripts/Cargo.toml+6−2 helm-control-scripts/README.md+20 helm-control-scripts/src/archive.rs+3−3 helm-control-scripts/src/archive_check.rs+2−2 helm-control-scripts/src/check.rs+2−2 helm-control-scripts/src/cleanup.rs+2−13 helm-control-scripts/src/config_check.rs+9−4 helm-control-scripts/src/info.rs+2−2 helm-control-scripts/src/lib.rs+183−22 helm-control-scripts/src/unarchive.rs+3−3 img/release_workflow.png nix/default.nix+1−1 nix/sources.json+11−11 octo-cli/octo-cli.cabal+1−1 octo-cli/src/Octopod/CLI.hs+102−74 octo-cli/src/Octopod/CLI/Args.hs+19−15 octopod-api/src/Octopod/API/WebSocket.hs+1 octopod-api/src/Octopod/PowerAPI.hs+5 octopod-backend/octopod-backend.cabal+3−3 octopod-backend/src/Control/CacheMap.hs+43−47 octopod-backend/src/Control/Octopod/DeploymentLock.hs+13−19 octopod-backend/src/Octopod/Server.hs+287−262 octopod-backend/src/Octopod/Server/Args.hs+2−1 octopod-backend/src/Octopod/Server/ControlScriptUtils.hs+17−19 octopod-backend/src/Octopod/Server/Logger.hs−42 octopod-backend/src/Octopod/Server/Logging.hs+121 octopod-backend/src/Octopod/Server/Posix.hs+21−13 octopod-common/octopod-common.cabal+6−1 octopod-common/src/Common/Orphans.hs+20 octopod-common/src/Common/Types.hs+54−55 octopod-common/src/Control/Searchable.hs+20−1 octopod-common/src/Data/ConfigTree.hs+165 octopod-common/src/Data/Map/Ordered/Strict/Extra.hs−15 octopod-css/default.nix+2 octopod-css/development/layouts/index.html+931−907 octopod-css/development/layouts/staging.html+1159−668 octopod-css/development/markups/_action.html+2−1 octopod-css/development/markups/_bar.html+2−1 octopod-css/development/markups/_break.html+2−1 octopod-css/development/markups/_button.html+2−1 octopod-css/development/markups/_collapse.html+277 octopod-css/development/markups/_dash.html+2−1 octopod-css/development/markups/_drop.html+2−1 octopod-css/development/markups/_expander.html+2−1 octopod-css/development/markups/_external.html+2−1 octopod-css/development/markups/_input.html+36−1 octopod-css/development/markups/_keys-and-values.html+373 octopod-css/development/markups/_listing.html+2−1 octopod-css/development/markups/_loading.html+2−1 octopod-css/development/markups/_notification.html+2−1 octopod-css/development/markups/_null.html+2−1 octopod-css/development/markups/_spot.html+2−1 octopod-css/development/markups/_status.html+2−1 octopod-css/development/markups/_title.html+2−1 octopod-css/development/scripts/_collapse.js+18 octopod-css/development/styles/_collapse.css+95 octopod-css/development/styles/_deployment.css+3 octopod-css/development/styles/_editable-row.css+49 octopod-css/development/styles/_has-changes.css+3 octopod-css/development/styles/_input.css+30−2 octopod-css/development/styles/_key-custom-edited.css+4 octopod-css/development/styles/_key-default-edited.css+5 octopod-css/development/styles/_key-default-pristine.css+4 octopod-css/development/styles/_key-deleted.css+6 octopod-css/development/styles/_overrides.css+7−77 octopod-css/development/styles/_padded.css+3 octopod-css/development/styles/_root.css+2 octopod-css/development/styles/_row.css+3 octopod-css/development/styles/_table.css+5−1 octopod-css/development/styles/_value-deleted.css+5 octopod-css/development/styles/_value-edited.css+4 octopod-css/development/styles/_value-pristine.css+3 octopod-css/development/styles/_value-unknown.css+12 octopod-css/development/styles/style.css+15 octopod-css/development/vectors/collapse-project.svg+3 octopod-frontend/octopod-frontend.cabal+12−1 octopod-frontend/src/Data/Text/Search.hs+54−61 octopod-frontend/src/Data/WorkingOverrides.hs+104−76 octopod-frontend/src/Frontend/UIKit.hs+343−135 octopod-frontend/src/Frontend/UIKit/Button/Action.hs+3−3 octopod-frontend/src/Frontend/UIKit/Button/Common.hs+64−12 octopod-frontend/src/Frontend/UIKit/Button/Dash.hs+2−2 octopod-frontend/src/Frontend/UIKit/Button/Expander.hs+3−2 octopod-frontend/src/Frontend/UIKit/Button/Large.hs+3−3 octopod-frontend/src/Frontend/UIKit/Button/Sort.hs+2−2 octopod-frontend/src/Frontend/UIKit/Button/Static.hs+25−12 octopod-frontend/src/Frontend/UIKit/Button/Tree.hs+84 octopod-frontend/src/Frontend/Utils.hs+173−332 octopod-frontend/src/Page/ClassicPopup.hs+3−2 octopod-frontend/src/Page/Deployment.hs+39−34 octopod-frontend/src/Page/Deployments.hs+45−77 octopod-frontend/src/Page/Elements/Links.hs+20−11 octopod-frontend/src/Page/Popup/EditDeployment.hs+3−3 octopod-frontend/src/Page/Popup/NewDeployment.hs+30−27 octopod-frontend/src/Reflex/Dom/AsyncEvent.hs+30 octopod-frontend/src/Reflex/Dom/Renderable.hs+20−2 octopod_local_install.sh+21−1 stack.yaml+3−1
.github/actions/common_setup/action.yamlnew file+40
@@ -0,0 +1,40 @@
1+name: "Common setup"
2+description: "Common setup steps"
3+inputs:
4+ authToken:
5+ description: "Cachix auth token"
6+ required: true
7+runs:
8+ using: "composite"
9+ steps:
10+ - name: Install skopeo
11+ shell: bash
12+ run: |
13+ sudo apt-get -y update
14+ sudo apt-get -y install skopeo
15+
16+ - name: Increase swap
17+ shell: bash
18+ run: |
19+ free -h
20+
21+ sudo swapoff /mnt/swapfile
22+ sudo fallocate -l 12G /mnt/swapfile
23+ sudo mkswap /mnt/swapfile
24+ sudo swapon /mnt/swapfile
25+
26+ free -h
27+
28+ - name: Install Nix
29+ uses: cachix/install-nix-action@v16
30+ with:
31+ extra_nix_config: |
32+ trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
33+ substituters = https://hydra.iohk.io https://cache.nixos.org/
34+ system-features = kvm
35+
36+ - name: Login to Cachix
37+ uses: cachix/cachix-action@v10
38+ with:
39+ name: typeable
40+ authToken: ${{ inputs.authToken }}
.github/actions/push_docker_images/action.yamlnew file+26
@@ -0,0 +1,26 @@
1+name: "Push Docker Images"
2+description: "Push Docker Images"
3+inputs:
4+ tag:
5+ description: "The docker image tag to push."
6+ required: true
7+ docker_username:
8+ description: "The Docker Hub username"
9+ required: true
10+ docker_password:
11+ description: "The Docker Hub password"
12+ required: true
13+runs:
14+ using: "composite"
15+ steps:
16+ - name: Login to DockerHub
17+ uses: docker/login-action@v1.10.0
18+ with:
19+ username: ${{ inputs.docker_username }}
20+ password: ${{ inputs.docker_password }}
21+
22+ - name: Push Docker Images to DockerHub
23+ shell: bash
24+ run: |
25+ skopeo copy docker-archive:$(nix-build ./nix -j auto -A octopod-server-container) docker://typeable/octopod:${{ inputs.tag }}
26+ skopeo copy docker-archive:$(nix-build ./nix -j auto -A octo-cli-container) docker://typeable/octo:${{ inputs.tag }}
.github/workflows/build.yaml+54−84
@@ -1,4 +1,4 @@
1−name: Build
1+name: Build and release
22on:
33 push:
44 branches:
@@ -10,6 +10,15 @@ on:
1010 - master
1111 - develop
1212 - staging
13+ workflow_dispatch:
14+ inputs:
15+ release:
16+ description: Release the new version?
17+ required: false
18+ default: "true"
19+ version:
20+ description: The version to release
21+ required: true
1322
1423jobs:
1524 check-formatting:
@@ -20,7 +29,7 @@ jobs:
2029 - uses: cachix/install-nix-action@v12
2130 - name: Run fourmolu
2231 run: |
23− nix-shell nix/ci.nix -j auto --run "git ls-files '*.hs' | xargs fourmolu -m inplace --ghc-opt -XRecursiveDo --ghc-opt -XTypeApplications"
32+ nix-shell nix/ci.nix -j auto --run "git ls-files '*.hs' | xargs fourmolu -m inplace --ghc-opt -XRecursiveDo --ghc-opt -XTypeApplications --ghc-opt -XPatternSynonyms"
2433 git diff --exit-code
2534
2635 octo-macOS:
@@ -48,15 +57,10 @@ jobs:
4857 timeout-minutes: 600
4958 steps:
5059 - uses: actions/checkout@v2
51− - uses: cachix/install-nix-action@v12
52− with:
53− extra_nix_config: |
54− trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
55− substituters = https://hydra.iohk.io https://cache.nixos.org/
56− - uses: cachix/cachix-action@v8
60+ - name: Common setup
61+ uses: ./.github/actions/common_setup
5762 with:
58− name: octopod
59− signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}"
63+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
6064 - name: Build
6165 run: nix-build -A projectCross.musl64.hsPkgs.octo-cli.components.exes.octo --arg prod true -j auto
6266 - uses: actions/upload-artifact@v2
@@ -68,77 +72,28 @@ jobs:
6872 runs-on: ubuntu-latest
6973 timeout-minutes: 600
7074 steps:
71− - name: Increase swap
72− run: |
73− free -h
74−
75− sudo swapoff /mnt/swapfile
76− sudo fallocate -l 12G /mnt/swapfile
77− sudo mkswap /mnt/swapfile
78− sudo swapon /mnt/swapfile
79−
80− free -h - uses: actions/checkout@v2
81−
8275 - uses: actions/checkout@v2
8376
84− - name: Install Nix
85− uses: cachix/install-nix-action@v12
86− with:
87− extra_nix_config: |
88− trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
89− substituters = https://hydra.iohk.io https://cache.nixos.org/
90−
91− - name: Login to Cachix
92− uses: cachix/cachix-action@v8
77+ - name: Common setup
78+ uses: ./.github/actions/common_setup
9379 with:
94− name: octopod
95− signingKey: ${{ secrets.CACHIX_SIGNING_KEY }}
80+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
9681
9782 - name: Build Docker Images
98− run: |
99− # enable required features (see https://github.com/cachix/install-nix-action/issues/19)
100− mkdir -p ~/.config/nix
101− echo "system-features = kvm" >> ~/.config/nix/nix.conf
102−
103− # build docker images
104− ./build.sh build
105−
106− - name: Login to DockerHub
107− id: login-docker-hub
108− if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/staging' || github.ref == 'refs/heads/develop'
109− uses: docker/login-action@v1
110− with:
111− username: ${{ secrets.DOCKERHUB_USERNAME }}
112− password: ${{ secrets.DOCKERHUB_TOKEN }}
113−
114− - name: Push Docker Images to DockerHub
115− if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/staging'
116− run: |
117− # push docker images to DockerHub
118− image_name=`cat octo-docker | docker load | awk '{print $3}'`
119− docker tag $image_name typeable/octo:latest
120− docker push typeable/octo:latest
121−
122− image_name=`cat octopod-server-docker | docker load | awk '{print $3}'`
123− docker tag $image_name typeable/octopod:latest
124− docker push typeable/octopod:latest
83+ run: nix-build ./nix -j auto
12584
12685 - name: Push Nightly Docker Images to DockerHub
127− if: github.ref == 'refs/heads/develop'
128− run: |
129− # push docker images to DockerHub
130− image_name=`cat octo-docker | docker load | awk '{print $3}'`
131− docker tag $image_name typeable/octo:nightly
132− docker push typeable/octo:nightly
133−
134− image_name=`cat octopod-server-docker | docker load | awk '{print $3}'`
135− docker tag $image_name typeable/octopod:nightly
136− docker push typeable/octopod:nightly
86+ if: github.ref == 'refs/heads/master' && github.event_name == 'push'
87+ uses: ./.github/actions/push_docker_images
88+ with:
89+ tag: nightly
90+ docker_username: ${{ secrets.DOCKERHUB_USERNAME }}
91+ docker_password: ${{ secrets.DOCKERHUB_TOKEN }}
13792
13893 - name: Rename Docker image archives
13994 run: |
140− mv octo-docker octo-cli-image.tar.gz
141− mv octopod-server-docker octopod-server-image.tar.gz
95+ cp $(nix-build ./nix -j auto -A octo-cli-container) octo-cli-image.tar.gz
96+ cp $(nix-build ./nix -j auto -A octopod-server-container) octopod-server-image.tar.gz
14297
14398 - name: Upload octo CLI Image
14499 uses: actions/upload-artifact@v2
@@ -151,32 +106,47 @@ jobs:
151106 with:
152107 name: octopod-server-image
153108 path: octopod-server-image.tar.gz
154−
155− - name: Clean up
156− if: always()
157− continue-on-error: true
158− run: |
159− docker logout ${{ steps.login-docker-hub.outputs.registry }}
160−
161109 release:
162− name: "Release"
163− if: github.ref == 'refs/heads/master'
164− runs-on: "ubuntu-latest"
110+ name: Release
111+ if: github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'true'
165112 needs: [octo-macOS, octo-linux, docker-images]
113+ runs-on: "ubuntu-latest"
166114 steps:
115+ - uses: actions/checkout@v2
116+ - name: Common setup
117+ uses: ./.github/actions/common_setup
118+ with:
119+ authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
120+ - name: Push Docker Release Images to DockerHub
121+ uses: ./.github/actions/push_docker_images
122+ with:
123+ tag: ${{ github.event.inputs.version }}
124+ docker_username: ${{ secrets.DOCKERHUB_USERNAME }}
125+ docker_password: ${{ secrets.DOCKERHUB_TOKEN }}
126+
127+ - name: Push Docker latest Images to DockerHub
128+ uses: ./.github/actions/push_docker_images
129+ with:
130+ tag: latest
131+ docker_username: ${{ secrets.DOCKERHUB_USERNAME }}
132+ docker_password: ${{ secrets.DOCKERHUB_TOKEN }}
133+
167134 - uses: actions/download-artifact@v2
168− - name: Zip
135+ - name: Zip and move
169136 run: |
170137 chmod +x octo-cli-macos/octo
171138 zip octo-cli-macos octo-cli-macos/octo
172139 chmod +x octo-cli-linux/octo
173140 zip octo-cli-linux octo-cli-linux/octo
141+
142+ mv octo-cli-image/* .
143+ mv octopod-server-image/* .
174144 - uses: "marvinpinto/action-automatic-releases@latest"
175145 with:
176146 repo_token: "${{ secrets.GITHUB_TOKEN }}"
177147 automatic_release_tag: "latest"
178− prerelease: true
179− title: "Pre-Release"
148+ prerelease: false
149+ title: "${{ github.event.inputs.version }}"
180150 files: |
181151 *.zip
182152 *.tar.gz
.github/workflows/check_documentation.yaml+1−1
@@ -21,4 +21,4 @@ jobs:
2121
2222 - name: Run linter
2323 run: |
24− yarn run remark -f -u validate-links -u remark-lint-mdash-style -u remark-lint-final-newline -u remark-lint-list-item-bullet-indent -u remark-lint-no-auto-link-without-protocol -u remark-lint-no-blockquote-without-marker -u remark-lint-ordered-list-marker-style -u remark-lint-no-literal-urls -u remark-lint-hard-break-spaces -u remark-lint-no-duplicate-definitions -u remark-lint-no-heading-content-indent -u remark-lint-no-inline-padding -u remark-lint-no-shortcut-reference-image -u remark-lint-no-shortcut-reference-link -u remark-lint-no-undefined-references -u remark-lint-no-unused-definitions -u remark-lint-no-dead-urls docs README.md charts
24+ yarn run remark -f -u validate-links -u remark-lint-mdash-style -u remark-lint-final-newline -u remark-lint-list-item-bullet-indent -u remark-lint-no-blockquote-without-marker -u remark-lint-ordered-list-marker-style -u remark-lint-no-literal-urls -u remark-lint-hard-break-spaces -u remark-lint-no-duplicate-definitions -u remark-lint-no-heading-content-indent -u remark-lint-no-inline-padding -u remark-lint-no-shortcut-reference-image -u remark-lint-no-shortcut-reference-link -u remark-lint-no-undefined-references -u remark-lint-no-unused-definitions -u remark-lint-no-dead-urls docs README.md charts
.nova/Configuration.jsonnew file+5
@@ -0,0 +1,5 @@
1+{
2+ "env-generator" : "nix-shell --run env",
3+ "haskell.checkProject" : false,
4+ "haskell.formattingProvider" : "fourmolu"
5+}
.vscode/settings.json+6−1
@@ -1,4 +1,9 @@
11{
22 "nixEnvSelector.nixFile": "${workspaceRoot}/shell.nix",
3− "haskell.formattingProvider": "fourmolu"
3+ "haskell.formattingProvider": "fourmolu",
4+ "haskell.plugin.hlint.diagnosticsOn": false,
5+ "haskell.hlint.logLevel": "none",
6+ "haskell.hlint.run": "never",
7+ "haskell.serverExecutablePath": "haskell-language-server",
8+ "editor.formatOnSave": true
49}
Development_guide.md+5−12
@@ -2,14 +2,7 @@
22
33## Git flow
44
5−`master` contains the latest "release version" only.
6−
7−All development should be done in the `develop` branch.
8−
9−Feature PRs are created to the `develop` branch and merged with all commits **squashed**. This leads to us having every commit in the `develop` branch corresponds to exactly one feature or bug fix.
10−
11−When a release is ready, the `develop` branch is merged into the `master` branch using **rebase and merge**. This makes the `master` branch have every commit be a feature or bug fix. Merging to master triggers a CI script that collects all commits since the last merge and creates a new release with a change log of all commits.
12−
5+Feature branches are merged into `master`. There is no `develop` branch.
136## Building
147
158### Nix Installation
@@ -22,13 +15,13 @@ curl https://nixos.org/nix/install | sh
2215
2316### Nix cache
2417
25−#### Reflex platform cache
18+#### Haskell.nix cache
2619
27−To speedup initial project builds you will want to set up the Reflex Platform binary nix cache – append the following to `/etc/nix/nix.conf`:
20+To speedup initial project builds you will want to set up the haskell.nix binary nix cache – append the following to `/etc/nix/nix.conf`:
2821
2922```
30−binary-caches = https://cache.nixos.org https://nixcache.reflex-frp.org
31−binary-cache-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=
23+binary-caches = https://hydra.iohk.io https://cache.nixos.org/
24+binary-cache-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=
3225binary-caches-parallel-connections = 40
3326```
3427
Makefile+3
@@ -36,5 +36,8 @@ run-backend-dev:
3636run-frontend-dev:
3737 `nix-build dev -A frontend -j auto`
3838
39+run-frontend-prod:
40+ `nix-build dev -A frontend --arg prod true -j auto`
41+
3942run-caddy-for-ghcid:
4043 `nix-build dev -A caddyForGhcid -j auto`
Release_checklist.md+5−18
@@ -1,21 +1,8 @@
11# Release checklist
22
331. Update the referenced tags in the documentation
4−2. If there were changes to the examples:
5− 1. Build and push the new containers:
6− 1. octopod-web-app-example
7− 2. octopod-helm-example
8− 2. Create a new tag incrementing the integer version number of the tag:
9− 1. Build the updated images
10− 2. Tag it with the new `<integer>` (`docker tag`)
11− 3. Push the new tag (`docker push`)
12− 3. Update docs where the tags are referenced.
13−3. Merge the `develop` branch into the `staging` branch.
14−4. CI will automatically update the `latest` tag for both `octo` and `octopod`. Wait for CI to complete.
15−5. Deploy everything in our private cluser and make sure everything works.
16−6. Merge the `staging` branch into `master` using **Rebase and Merge**.
17−7. CI will automatically create a new release in GitHub with _octo CLI_ and update the `latest` tag for both `octo` and `octopod`. Wait for CI to complete.
18−8. Edit the created release in GitHub to match the version you are releasing.
19− 1. Change the release name to the version being released.
20− 2. Uncheck "This is a pre-release"
21−9. Push the new release of `octo` and `octopod`. To do this run `./release.sh <version>`.
4+2. If there were changes to control scripts:
5+ 1. _**\<TODO\>**_
6+3. [Dispatch the "Build and release" workflow](https://github.com/typeable/octopod/actions/workflows/build.yaml), specifying the version you are releasing:
7+
8+ ![](./img/release_workflow.png)
build.shdeleted−60
@@ -1,60 +0,0 @@
1−#!/usr/bin/env bash
2−
3−set -e
4−
5−build_octo_cli_docker_image() {
6− nix-build ./nix -A octo-cli-container -o "$1"
7−}
8−
9−build_octopod_server_docker_image() {
10− nix-build ./nix -A octopod-server-container --arg migrations "$1" -o "$2"
11−}
12−
13−push_docker_images() {
14− outfile=latest-octopod-server-docker
15− for image_name in $octo_cli_docker $octopod_server_docker; do
16− image_type=$(echo "$image_name" | cut -d- -f1)
17− image=$(ls -ls "$image_name" | awk '{print $12}')
18− echo "size: $(du -sh $image)"
19− docker load --input "$image" | tee "$outfile"
20− nixcontainer=$(awk '{print $3}' $outfile)
21− docker tag "$nixcontainer" "typeable/${image_type}:$1"
22− docker push "typeable/${image_type}:$1"
23− echo "Published: ${image_type}:$1"
24− done
25− rm $outfile
26−}
27−
28−build_docker_images() {
29− build_octo_cli_docker_image "$octo_cli_docker"
30− build_octopod_server_docker_image "$migrations" "$octopod_server_docker"
31−}
32−
33−export tag=$(git rev-parse HEAD)
34−export migrations="./migrations"
35−export octo_cli_docker="octo-docker"
36−export octopod_server_docker="octopod-server-docker"
37−
38−case "$1" in
39−build-and-push)
40− echo "$1 mode"
41−
42− if test -z "$2"; then
43− echo "Please provide a tag to upload to"
44− exit 1
45− fi
46−
47− build_docker_images
48− push_docker_images $2
49− ;;
50−build)
51− echo "$1 mode"
52− build_docker_images
53− ;;
54−*)
55− echo "usage:"
56− echo " $0 build Builds the docker images."
57− echo " $0 build-and-push <tag> Builds the docker images and uploads it to Docker Hub under the tag <tag>."
58− exit 1
59− ;;
60−esac
cabal.project+6
@@ -31,5 +31,11 @@ source-repository-package
3131 subdir: lib/executable-config/lookup
3232 --sha256: 0dlk8y6rxc87crw7764zq2py7nqn38lw496ca1y893m9gdq8qdkz
3333
34+source-repository-package
35+ type: git
36+ location: https://github.com/typeable/ordered-containers.git
37+ tag: 3eb05fb2f44fe482e9092aff447e9c68fbd6a7f6
38+ --sha256: 0q3cp3nar41bqh2ldl1ykzaycf0bjxh0n58za5g19x4g7ab8h9gm
39+
3440package reflex-dom
3541 flags: +use-warp
charts/octopod/Chart.yaml+2−2
@@ -2,8 +2,8 @@ apiVersion: v2
22name: octopod
33description: An opensource self-hosted solution for managing multiple deployments in a Kubernetes cluster.
44type: application
5−version: 0.6.1
6−appVersion: 1.4
5+version: 0.6.11
6+appVersion: 1.5.2
77keywords:
88 - kubernetes
99 - octopod
charts/octopod/README.md+15−8
@@ -6,20 +6,27 @@
66```console
77$ helm repo add typeable https://typeable.github.io/octopod/
88$ helm repo update
9−$ kubectl create ns octopod-deployment
9+$ kubectl create namespace octopod-deployment
1010$ helm install octopod typeable/octopod --set octopod.baseDomain="your-domain.com"
1111```
1212
1313## Introduction
1414
15−This chart bootstraps an Octopod deployment in a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
15+This chart bootstraps Octopod deployment in a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager.
1616
1717## Prerequisites
1818
19−- Kubernetes 1.12+
20−- Helm 3.1.0
21−- PV support (for postgresql persistense)
22−- nginx-ingress controller
19+### Mandatory
20+
21+- Kubernetes version >= 1.12.0 <= 1.22.0
22+- PVC support (for PostgreSQL persistence)
23+- [NGINX Ingress](https://kubernetes.github.io/ingress-nginx/) contoller version <= 0.49.3 installed. NGINX Ingress controller v1.x.x is not currently supported
24+
25+### Optional
26+
27+- Cert Manager ([cert-manager](https://cert-manager.io/docs/installation/)) installed, if you want to get SSL certificates from Let's Encrypt automatically.
28+- Cluster issuer ([ACME Issuer](https://cert-manager.io/docs/configuration/acme/#creating-a-basic-acme-issuer)) created. By default we assume that it would be named `letsencrypt`. You can change it by setting `ingress.tls.clusterIssuer` parameter.
29+
2330
2431## Installing the Chart
2532This chart will not create or delete any namespaces for you.
@@ -53,10 +60,10 @@ To uninstall/delete the `my-release` deployment:
5360$ helm -n octopod delete my-release
5461```
5562
56−The command removes all the Kubernetes components but PVC's associated with the postgres chart and deletes the release.
63+The command removes all the Kubernetes components except PVCs associated with the postgres chart, and deletes the release.
5764
5865## Note about generated values
59−Some values (such as passwords) in this chart (and its dependencies) are generated automatically, but due to [a limitation in helm](https://github.com/helm/charts/issues/5167) the values are changing on every upgrade. To prevent this you must fix these values by providing them via `--set` flags or in the [values file](https://helm.sh/docs/chart_template_guide/values_files/).
66+Some values (such as passwords) in this chart (and its dependencies) are generated automatically, but due to [the limitation in Helm](https://github.com/helm/charts/issues/5167) the values are changing on every upgrade. To prevent this you must set these values explicitly by providing them via `--set` flags or in the [values file](https://helm.sh/docs/chart_template_guide/values_files/).
6067
6168These values are:
6269- `postgresql.postgresqlPassword` ― main db password
charts/octopod/index.yamlnew file+37
@@ -0,0 +1,37 @@
1+apiVersion: v1
2+entries:
3+ postgresql:
4+ - annotations:
5+ category: Database
6+ apiVersion: v2
7+ appVersion: 11.12.0
8+ created: "2022-08-23T14:43:53.959078+03:00"
9+ dependencies:
10+ - name: common
11+ repository: https://charts.bitnami.com/bitnami
12+ version: 1.x.x
13+ description: Chart for PostgreSQL, an object-relational database management system
14+ (ORDBMS) with an emphasis on extensibility and on standards-compliance.
15+ digest: afece60469d73b9cc4b578fff9b7627dd6ae609be50a04dabac5ad0be5105f33
16+ home: https://github.com/bitnami/charts/tree/master/bitnami/postgresql
17+ icon: https://bitnami.com/assets/stacks/postgresql/img/postgresql-stack-220x234.png
18+ keywords:
19+ - postgresql
20+ - postgres
21+ - database
22+ - sql
23+ - replication
24+ - cluster
25+ maintainers:
26+ - email: containers@bitnami.com
27+ name: Bitnami
28+ - email: cedric@desaintmartin.fr
29+ name: desaintmartin
30+ name: postgresql
31+ sources:
32+ - https://github.com/bitnami/bitnami-docker-postgresql
33+ - https://www.postgresql.org/
34+ urls:
35+ - charts/postgresql-10.5.3.tgz
36+ version: 10.5.3
37+generated: "2022-08-23T14:43:53.955624+03:00"
charts/octopod/templates/ingress-app.yaml+11−5
@@ -1,10 +1,13 @@
11{{- if .Values.ingress.enabled -}}
22{{- $fullName := include "octopod.fullname" . -}}
3−{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
3+{{- $kubeVersion := .Capabilities.KubeVersion.Version }}
4+{{ if semverCompare ">= 1.19.0-0" $kubeVersion }}
5+apiVersion: networking.k8s.io/v1
6+{{ else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" }}
47apiVersion: networking.k8s.io/v1beta1
5−{{- else -}}
8+{{ else }}
69apiVersion: extensions/v1beta1
7−{{- end }}
10+{{ end }}
811kind: Ingress
912metadata:
1013 name: {{ $fullName }}-app
@@ -41,7 +44,10 @@ spec:
4144 http:
4245 paths:
4346 - path: /
47+ pathType: ImplementationSpecific
4448 backend:
45− serviceName: {{ $fullName }}
46− servicePort: {{ .Values.service.ports.app }}
49+ service:
50+ name: {{ $fullName }}
51+ port:
52+ number: {{ .Values.service.ports.app }}
4753{{- end }}
charts/octopod/templates/ingress-powerapp.yaml+11−5
@@ -1,10 +1,13 @@
11{{- if .Values.ingress.enabled -}}
22{{- $fullName := include "octopod.fullname" . -}}
3−{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
3+{{- $kubeVersion := .Capabilities.KubeVersion.Version }}
4+{{ if semverCompare ">= 1.19.0-0" $kubeVersion }}
5+apiVersion: networking.k8s.io/v1
6+{{ else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" }}
47apiVersion: networking.k8s.io/v1beta1
5−{{- else -}}
8+{{ else }}
69apiVersion: extensions/v1beta1
7−{{- end }}
10+{{ end }}
811kind: Ingress
912metadata:
1013 name: {{ $fullName }}-powerapp
@@ -35,7 +38,10 @@ spec:
3538 http:
3639 paths:
3740 - path: /
41+ pathType: ImplementationSpecific
3842 backend:
39− serviceName: {{ $fullName }}
40− servicePort: {{ .Values.service.ports.powerApp }}
43+ service:
44+ name: {{ $fullName }}
45+ port:
46+ number: {{ .Values.service.ports.powerApp }}
4147{{- end }}
charts/octopod/templates/ingress-ui.yaml+11−5
@@ -1,10 +1,13 @@
11{{- if .Values.ingress.enabled -}}
22{{- $fullName := include "octopod.fullname" . -}}
3−{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
3+{{- $kubeVersion := .Capabilities.KubeVersion.Version }}
4+{{ if semverCompare ">= 1.19.0-0" $kubeVersion }}
5+apiVersion: networking.k8s.io/v1
6+{{ else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" }}
47apiVersion: networking.k8s.io/v1beta1
5−{{- else -}}
8+{{ else }}
69apiVersion: extensions/v1beta1
7−{{- end }}
10+{{ end }}
811kind: Ingress
912metadata:
1013 name: {{ $fullName }}-ui
@@ -35,7 +38,10 @@ spec:
3538 http:
3639 paths:
3740 - path: /
41+ pathType: ImplementationSpecific
3842 backend:
39− serviceName: {{ $fullName }}
40− servicePort: {{ .Values.service.ports.ui }}
43+ service:
44+ name: {{ $fullName }}
45+ port:
46+ number: {{ .Values.service.ports.ui }}
4147{{- end }}
charts/octopod/templates/ingress-ws.yaml+11−5
@@ -1,10 +1,13 @@
11{{- if .Values.ingress.enabled -}}
22{{- $fullName := include "octopod.fullname" . -}}
3−{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
3+{{- $kubeVersion := .Capabilities.KubeVersion.Version }}
4+{{ if semverCompare ">= 1.19.0-0" $kubeVersion }}
5+apiVersion: networking.k8s.io/v1
6+{{ else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" }}
47apiVersion: networking.k8s.io/v1beta1
5−{{- else -}}
8+{{ else }}
69apiVersion: extensions/v1beta1
7−{{- end }}
10+{{ end }}
811kind: Ingress
912metadata:
1013 name: {{ $fullName }}-ws
@@ -35,7 +38,10 @@ spec:
3538 http:
3639 paths:
3740 - path: /
41+ pathType: ImplementationSpecific
3842 backend:
39− serviceName: {{ $fullName }}
40− servicePort: {{ .Values.service.ports.ws }}
43+ service:
44+ name: {{ $fullName }}
45+ port:
46+ number: {{ .Values.service.ports.ws }}
4147{{- end }}
charts/octopod/values.yaml+1−1
@@ -99,7 +99,7 @@ controlScripts:
9999 image:
100100 repository: typeable/octopod-helm-control-scripts
101101 pullPolicy: IfNotPresent
102− tag: 0.2.0
102+ tag: 0.2.4
103103sqitch:
104104 image:
105105 repository: typeable/sqitch
default.nix+11−4
@@ -8,8 +8,15 @@
88}:
99let
1010 octopod-css = import ./octopod-css { inherit pkgsSrc; };
11+ insaneOptimizationFlags = [
12+ "-O2"
13+ "-fexpose-all-unfoldings"
14+ "-fspecialise-aggressively"
15+ ];
1116 addLocalOptions = x:
12− if prod then x // { ghcOptions = [ "-Werror" ]; }
17+ if prod then x // {
18+ ghcOptions = [ "-Werror" ] ++ insaneOptimizationFlags;
19+ }
1320 else x // { ghcOptions = [ "-O0" ]; };
1421
1522 hsPkgs = pkgs.haskell-nix.cabalProject {
@@ -28,7 +35,7 @@ let
2835
2936 modules = [
3037 {
31− ghcOptions = [ "-O2" ];
38+ ghcOptions = insaneOptimizationFlags;
3239 dontStrip = false;
3340 dontPatchELF = false;
3441 enableDeadCodeElimination = true;
@@ -56,8 +63,8 @@ let
5663 }
5764 ];
5865
59− index-state = "2021-08-04T00:00:00Z";
60− compiler-nix-name = "ghc8105";
66+ index-state = "2021-11-22T00:00:00Z";
67+ compiler-nix-name = "ghc8107";
6168 };
6269in
6370hsPkgs // {
dev/default.nix+51−7
@@ -1,10 +1,12 @@
11{ pkgs ? hsPkgs.pkgs
22, sources ? import ../nix/sources.nix
33, nix-filter ? import sources.nix-filter
4−, hsPkgs ? import ./.. { }
4+, hsPkgs ? import ./.. { inherit prod system; }
55, migrations ? ../migrations
66, octopod-css ? import ../octopod-css { inherit pkgsSrc; }
77, pkgsSrc ? hsPkgs.pkgsSrc
8+, prod ? false
9+, system ? builtins.currentSystem
810}:
911let frontendConfig = pkgs.writeTextDir "config.json" ''
1012 {
@@ -23,6 +25,8 @@ in
2325 caddyConfig = pkgs.writeText "caddy-config" ''
2426 http://localhost:8000
2527
28+ header Cache-Control no-cache
29+
2630 reverse_proxy /api/* localhost:3002
2731
2832 file_server /config.json {
@@ -52,6 +56,8 @@ in
5256 caddyConfig = pkgs.writeText "caddy-config" ''
5357 http://localhost:8000
5458
59+ header Cache-Control no-cache
60+
5561 reverse_proxy /api/* localhost:3002
5662
5763 @notStatic {
@@ -98,6 +104,7 @@ in
98104 #!${pkgs.bash}/bin/bash
99105
100106 echo "You did something wrong :("
107+ echo "You did something wrong twice :("
101108
102109 1>&2 echo "You did something wrong, but you shouldn't see this."
103110
@@ -109,8 +116,32 @@ in
109116
110117 sleep 4
111118
112− echo "key,value"
113− echo "key2,value2"
119+ echo "foo.vat1.fjij.key1,"
120+
121+ for i in {1..10}
122+ do
123+ for j in {1..10}
124+ do
125+ echo "foo.vat$i.fjij.key$j,value"
126+ done
127+ done
128+
129+ for i in {1..10}
130+ do
131+ echo "fjij.key$i,value"
132+ done
133+
134+ exit 0
135+ '';
136+ smallInfoScript = pkgs.writeScript "info.sh" ''
137+ #!${pkgs.bash}/bin/bash
138+
139+ sleep 4
140+
141+ for i in {1..10}
142+ do
143+ echo "fjij.key$i,value"
144+ done
114145
115146 exit 0
116147 '';
@@ -120,8 +151,19 @@ in
120151
121152 sleep 4
122153
123− echo "key"
124− echo "key2"
154+ for i in {1..10}
155+ do
156+ echo "foo.vat.fkijf.fjij.key$i"
157+ done
158+ for i in {1..10}
159+ do
160+ echo "foo.vat.fjij.key$i"
161+ done
162+
163+ for i in {1..10}
164+ do
165+ echo "fjij.key$i"
166+ done
125167
126168 exit 0
127169 '';
@@ -152,8 +194,8 @@ in
152194 export CHECKING_COMMAND=${echoScript}
153195 export CLEANUP_COMMAND=${echoScript}
154196 export ARCHIVE_CHECKING_COMMAND=${echoScript}
155− export CONFIG_CHECKING_COMMAND=${echoScript}
156− export INFO_COMMAND=${infoScript}
197+ export CONFIG_CHECKING_COMMAND=${failScript}
198+ export INFO_COMMAND=${smallInfoScript}
157199 export NOTIFICATION_COMMAND=${writeScript}
158200 export DEPLOYMENT_CONFIG_COMMAND=${infoScript}
159201 export DEPLOYMENT_KEYS_COMMAND=${keysScript}
@@ -164,6 +206,8 @@ in
164206 export CACHE_INVALIDATION_TIME="60"
165207 export CACHE_UPDATE_TIME="20"
166208 export CONTROL_SCRIPT_TIMEOUT="10"
209+ export DEBUG=true
210+ export PROD_LOGS=false
167211 ${hsPkgs.octopod-backend.components.exes.octopod-exe}/bin/octopod-exe \
168212 --port 4443 \
169213 --ui-port 3002 \
docs/diagrams/images/technical-architecture-archive-via-cli.pngdeleted

Binary file not shown.

docs/diagrams/images/technical-architecture-archive-via-ui.pngdeleted

Binary file not shown.

docs/diagrams/images/technical-architecture-archive.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-cleanup-via-cli.pngdeleted

Binary file not shown.

docs/diagrams/images/technical-architecture-cleanup-via-ui.pngdeleted

Binary file not shown.

docs/diagrams/images/technical-architecture-cleanup.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-create-via-cli.pngdeleted

Binary file not shown.

docs/diagrams/images/technical-architecture-create-via-ui.pngdeleted

Binary file not shown.

docs/diagrams/images/technical-architecture-create.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-restore-via-cli.pngdeleted

Binary file not shown.

docs/diagrams/images/technical-architecture-restore-via-ui.pngdeleted

Binary file not shown.

docs/diagrams/images/technical-architecture-restore.pngnew file

Binary file not shown.

docs/diagrams/images/technical-architecture-update-via-cli.pngdeleted

Binary file not shown.

docs/diagrams/images/technical-architecture-update-via-ui.pngdeleted

Binary file not shown.

docs/diagrams/images/technical-architecture-update.pngnew file

Binary file not shown.

docs/diagrams/src/technical-architecture-archive-via-cli.mmddeleted−37
@@ -1,37 +0,0 @@
1−sequenceDiagram
2− participant octo CLI
3− participant Octopod Server
4− participant PostgreSQL
5− participant UI
6− participant Octopod Server/BgWorker
7− participant Octopod Server/StatusUpdater
8− participant ControlScripts
9− participant KubeAPI
10− octo CLI->>Octopod Server: archive(name)
11− Octopod Server->>PostgreSQL: status=ArchivePending
12− alt name not found
13− PostgreSQL-->>Octopod Server: error: name not found
14− Octopod Server-->>octo CLI: error: name not found
15− else
16− PostgreSQL-->>Octopod Server: ok
17− Octopod Server-xUI: event FrontendPleaseUpdateEverything
18− UI->>Octopod Server: get deployments info
19− Octopod Server-->>UI: deployments info
20− Octopod Server->>Octopod Server/BgWorker: archive
21− Octopod Server-->>octo CLI: done
22− Octopod Server/BgWorker->>ControlScripts: archive
23− ControlScripts->>KubeAPI: archive deployment
24− KubeAPI-->>ControlScripts: done
25− ControlScripts-->>Octopod Server/BgWorker: done
26− Octopod Server/BgWorker->>PostgreSQL: write logs
27− Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
28− UI->>Octopod Server: get deployments info
29− Octopod Server-->>UI: deployments info
30− Note over Octopod Server/StatusUpdater: wait 5 minutes
31− loop check deployment status every 30 seconds
32− Octopod Server/StatusUpdater->>PostgreSQL: status=Archived/ArchivePending
33− Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
34− UI->>Octopod Server: get deployments info
35− Octopod Server-->>UI: deployments info
36− end
37− end
docs/diagrams/src/technical-architecture-archive-via-ui.mmddeleted−36
@@ -1,36 +0,0 @@
1−sequenceDiagram
2− participant UI
3− participant Octopod Server
4− participant PostgreSQL
5− participant Octopod Server/BgWorker
6− participant Octopod Server/StatusUpdater
7− participant ControlScripts
8− participant KubeAPI
9− UI->>Octopod Server: archive(name)
10− Octopod Server->>PostgreSQL: status=ArchivePending
11− alt name not found
12− PostgreSQL-->>Octopod Server: error: name not found
13− Octopod Server-->>UI: error: name not found
14− else
15− PostgreSQL-->>Octopod Server: ok
16− Octopod Server-xUI: event FrontendPleaseUpdateEverything
17− UI->>Octopod Server: get deployments info
18− Octopod Server-->>UI: deployments info
19− Octopod Server->>Octopod Server/BgWorker: archive
20− Octopod Server-->>UI: done
21− Octopod Server/BgWorker->>ControlScripts: archive
22− ControlScripts->>KubeAPI: archive deployment
23− KubeAPI-->>ControlScripts: done
24− ControlScripts-->>Octopod Server/BgWorker: done
25− Octopod Server/BgWorker->>PostgreSQL: write logs
26− Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
27− UI->>Octopod Server: get deployments info
28− Octopod Server-->>UI: deployments info
29− Note over Octopod Server/StatusUpdater: wait 5 minutes
30− loop check deployment status every 30 seconds
31− Octopod Server/StatusUpdater->>PostgreSQL: status=Archived/ArchivePending
32− Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
33− UI->>Octopod Server: get deployments info
34− Octopod Server-->>UI: deployments info
35− end
36− end
docs/diagrams/src/technical-architecture-archive.mmdnew file+31
@@ -0,0 +1,31 @@
1+sequenceDiagram
2+ participant User
3+ participant Octopod Frontend
4+ participant Octopod Backend
5+ participant Control Scipts
6+ participant Octopod DB
7+
8+ User ->>+ Octopod Frontend: Archive deployment
9+ activate Octopod Frontend
10+
11+ Octopod Frontend ->>+ Octopod Backend: Archive deployment
12+
13+
14+ Octopod Backend ->>+ Control Scipts: archive – Archive the deployment
15+ Control Scipts -->>- Octopod Backend: Deployment archive started
16+
17+ Octopod Backend ->>+ Octopod DB: Set deployment state to ArchivePending
18+ Octopod DB -->>- Octopod Backend: Deployment status set
19+
20+ loop
21+ Octopod Backend ->>+ Control Scipts: archive_check – Is the deployment archived?
22+ Control Scipts -->>- Octopod Backend: deployment is not archived
23+ end
24+
25+ Octopod Backend ->>+ Control Scipts: archive_check – Is the deployment archived?
26+ Control Scipts -->>- Octopod Backend: deployment is archived!
27+
28+ Octopod Backend ->>+ Octopod DB: Set deployment state to Archived
29+ Octopod DB -->>- Octopod Backend: Deployment status set
30+
31+ deactivate Octopod Backend
docs/diagrams/src/technical-architecture-cleanup-via-cli.mmddeleted−20
@@ -1,20 +0,0 @@
1−sequenceDiagram
2− participant octo CLI
3− participant Octopod Server
4− participant PostgreSQL
5− participant UI
6− participant Octopod Server/BgWorker
7− participant Octopod Server/StatusUpdater
8− participant ControlScripts
9− participant KubeAPI
10− octo CLI->>Octopod Server: cleanup(name)
11− Octopod Server->>Octopod Server/BgWorker: cleanup
12− Octopod Server-->>octo CLI: done
13− Octopod Server/BgWorker->>ControlScripts: cleanup
14− ControlScripts->>KubeAPI: cleanup deployment
15− KubeAPI-->>ControlScripts: done
16− ControlScripts-->>Octopod Server/BgWorker: done
17− Octopod Server->>PostgreSQL: delete config and logs
18− Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
19− UI->>Octopod Server: get deployments info
20− Octopod Server-->>UI: deployments info
docs/diagrams/src/technical-architecture-cleanup-via-ui.mmddeleted−19
@@ -1,19 +0,0 @@
1−sequenceDiagram
2− participant UI
3− participant Octopod Server
4− participant PostgreSQL
5− participant Octopod Server/BgWorker
6− participant Octopod Server/StatusUpdater
7− participant ControlScripts
8− participant KubeAPI
9− UI->>Octopod Server: cleanup(name)
10− Octopod Server->>Octopod Server/BgWorker: cleanup
11− Octopod Server-->>UI: done
12− Octopod Server/BgWorker->>ControlScripts: cleanup
13− ControlScripts->>KubeAPI: cleanup deployment resources
14− KubeAPI-->>ControlScripts: done
15− ControlScripts-->>Octopod Server/BgWorker: done
16− Octopod Server->>PostgreSQL: delete config and logs
17− Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
18− UI->>Octopod Server: get deployments info
19− Octopod Server-->>UI: deployments info
docs/diagrams/src/technical-architecture-cleanup.mmdnew file+14
@@ -0,0 +1,14 @@
1+sequenceDiagram
2+ participant Octopod Backend
3+ participant Control Scipts
4+ participant Octopod DB
5+
6+ activate Octopod Backend
7+
8+ Octopod Backend ->>+ Control Scipts: cleanup – Clean up deployment resouces
9+ Control Scipts -->>- Octopod Backend: Deployment resouces cleaned up
10+
11+ Octopod Backend ->>+ Octopod DB: Delete everything related to the deployment
12+ Octopod DB -->>- Octopod Backend: Everything deleted
13+
14+ deactivate Octopod Backend
docs/diagrams/src/technical-architecture-create-via-cli.mmddeleted−41
@@ -1,41 +0,0 @@
1−sequenceDiagram
2− participant octo CLI
3− participant Octopod Server
4− participant PostgreSQL
5− participant UI
6− participant Octopod Server/BgWorker
7− participant Octopod Server/StatusUpdater
8− participant ControlScripts
9− participant KubeAPI
10− octo CLI->>Octopod Server: create(name, tag, [override])
11− Octopod Server->>PostgreSQL: store config, status=CreatePending
12− alt name already exists
13− PostgreSQL->>Octopod Server: error: deployment already exists
14− Octopod Server-->>octo CLI: error: deployment already exists
15− else
16− alt tag not found
17− Octopod Server-->>octo CLI: error: tag not found
18− else
19− PostgreSQL-->>Octopod Server: ok
20− Octopod Server-xUI: event FrontendPleaseUpdateEverything
21− UI->>Octopod Server: get deployments info
22− Octopod Server-->>UI: deployments info
23− Octopod Server->>Octopod Server/BgWorker: create
24− Octopod Server-->>octo CLI: done
25− Octopod Server/BgWorker->>ControlScripts: create
26− ControlScripts->>KubeAPI: create deployment
27− KubeAPI-->>ControlScripts: done
28− ControlScripts-->>Octopod Server/BgWorker: done
29− Octopod Server/BgWorker->>PostgreSQL: write logs
30− Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
31− UI->>Octopod Server: get deployments info
32− Octopod Server-->>UI: deployments info
33− Note over Octopod Server/StatusUpdater: wait 5 minutes
34− loop check deployment status every 30 seconds
35− Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
36− Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
37− UI->>Octopod Server: get deployments info
38− Octopod Server-->>UI: deployments info
39− end
40− end
41− end
docs/diagrams/src/technical-architecture-create-via-ui.mmddeleted−40
@@ -1,40 +0,0 @@
1−sequenceDiagram
2− participant UI
3− participant Octopod Server
4− participant PostgreSQL
5− participant Octopod Server/BgWorker
6− participant Octopod Server/StatusUpdater
7− participant ControlScripts
8− participant KubeAPI
9− UI->>Octopod Server: create(name, tag, [override])
10− Octopod Server->>PostgreSQL: store config, status=CreatePending
11− alt name already exists
12− PostgreSQL-->>Octopod Server: error: deployment already exists
13− Octopod Server-->>UI: error: deployment already exists
14− else
15− alt tag not found
16− Octopod Server-->>UI: error: tag not found
17− else
18− PostgreSQL-->>Octopod Server: ok
19− Octopod Server-xUI: event FrontendPleaseUpdateEverything
20− UI->>Octopod Server: get deployments info
21− Octopod Server-->>UI: deployments info
22− Octopod Server->>Octopod Server/BgWorker: create
23− Octopod Server-->>UI: done
24− Octopod Server/BgWorker->>ControlScripts: create
25− ControlScripts->>KubeAPI: create deployment
26− KubeAPI-->>ControlScripts: done
27− ControlScripts-->>Octopod Server/BgWorker: done
28− Octopod Server/BgWorker->>PostgreSQL: write logs
29− Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
30− UI->>Octopod Server: get deployments info
31− Octopod Server-->>UI: deployments info
32− Note over Octopod Server/StatusUpdater: wait 5 minutes
33− loop check deployment status every 30 seconds
34− Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
35− Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
36− UI->>Octopod Server: get deployments info
37− Octopod Server-->>UI: deployments info
38− end
39− end
40− end
docs/diagrams/src/technical-architecture-create.mmdnew file+41
@@ -0,0 +1,41 @@
1+sequenceDiagram
2+ participant User
3+ participant Octopod Frontend
4+ participant Octopod Backend
5+ participant Control Scipts
6+ participant Octopod DB
7+
8+ User ->>+ Octopod Frontend: Create deployment
9+ activate Octopod Frontend
10+
11+ Octopod Frontend ->>+ Octopod Backend: Create deployment
12+ Octopod Backend ->>+ Control Scipts: config_check – Check deployment config
13+ Control Scipts ->>- Octopod Backend: Deployment config OK
14+ Octopod Backend ->>+ Octopod DB: Create deployment
15+ Octopod DB -->>- Octopod Backend: Deployment Created
16+
17+ Octopod Backend -->> Octopod Frontend: Delpoyment created
18+ Octopod Frontend -->>- User: Close deployment popup
19+
20+ Octopod Backend ->>+ Control Scipts: info – Get deployment metadata
21+ Control Scipts -->>- Octopod Backend: Deployment metadata
22+
23+ Octopod Backend ->>+ Octopod DB: Set deployment metadata
24+ Octopod DB -->>- Octopod Backend: Deployment metadata set
25+
26+
27+ Octopod Backend ->>+ Control Scipts: create – Create the delpoyment
28+ Control Scipts -->>- Octopod Backend: Deployment creation started
29+
30+ loop
31+ Octopod Backend ->>+ Control Scipts: check – Is the deployment up?
32+ Control Scipts -->>- Octopod Backend: deployment is not up
33+ end
34+
35+ Octopod Backend ->>+ Control Scipts: check – Is the deployment up?
36+ Control Scipts -->>- Octopod Backend: deployment is up!
37+
38+ Octopod Backend ->>+ Octopod DB: Set deployment state to Running
39+ Octopod DB -->>- Octopod Backend: Deployment status set
40+
41+ deactivate Octopod Backend
docs/diagrams/src/technical-architecture-restore-via-cli.mmddeleted−41
@@ -1,41 +0,0 @@
1−sequenceDiagram
2− participant octo CLI
3− participant Octopod Server
4− participant PostgreSQL
5− participant UI
6− participant Octopod Server/BgWorker
7− participant Octopod Server/StatusUpdater
8− participant ControlScripts
9− participant KubeAPI
10− octo CLI->>Octopod Server: restore(name)
11− Octopod Server->>PostgreSQL: status=CreatePending
12− alt name not found
13− PostgreSQL-->>Octopod Server: error: name not found
14− Octopod Server-->>octo CLI: error: name not found
15− else
16− alt tag not found
17− Octopod Server-->>octo CLI: error: tag not found
18− else
19− PostgreSQL-->>Octopod Server: ok
20− Octopod Server-xUI: event FrontendPleaseUpdateEverything
21− UI->>Octopod Server: get deployments info
22− Octopod Server-->>UI: deployments info
23− Octopod Server->>Octopod Server/BgWorker: create
24− Octopod Server-->>octo CLI: done
25− Octopod Server/BgWorker->>ControlScripts: create
26− ControlScripts->>KubeAPI: restore deployment
27− KubeAPI-->>ControlScripts: done
28− ControlScripts-->>Octopod Server/BgWorker: done
29− Octopod Server/BgWorker->>PostgreSQL: write logs
30− Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
31− UI->>Octopod Server: get deployments info
32− Octopod Server-->>UI: deployments info
33− Note over Octopod Server/StatusUpdater: wait 5 minutes
34− loop check deployment status every 30 seconds
35− Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
36− Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
37− UI->>Octopod Server: get deployments info
38− Octopod Server-->>UI: deployments info
39− end
40− end
41− end
docs/diagrams/src/technical-architecture-restore-via-ui.mmddeleted−40
@@ -1,40 +0,0 @@
1−sequenceDiagram
2− participant UI
3− participant Octopod Server
4− participant PostgreSQL
5− participant Octopod Server/BgWorker
6− participant Octopod Server/StatusUpdater
7− participant ControlScripts
8− participant KubeAPI
9− UI->>Octopod Server: restore(name)
10− Octopod Server->>PostgreSQL: status=CreatePending
11− alt name not found
12− PostgreSQL-->>Octopod Server: error: name not found
13− Octopod Server-->>UI: error: name not found
14− else
15− alt tag not found
16− Octopod Server-->>UI: error: tag not found
17− else
18− PostgreSQL-->>Octopod Server: ok
19− Octopod Server-xUI: event FrontendPleaseUpdateEverything
20− UI->>Octopod Server: get deployments info
21− Octopod Server-->>UI: deployments info
22− Octopod Server->>Octopod Server/BgWorker: create
23− Octopod Server-->>UI: done
24− Octopod Server/BgWorker->>ControlScripts: create
25− ControlScripts->>KubeAPI: restore deployment
26− KubeAPI-->>ControlScripts: done
27− ControlScripts-->>Octopod Server/BgWorker: done
28− Octopod Server/BgWorker->>PostgreSQL: write logs
29− Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
30− UI->>Octopod Server: get deployments info
31− Octopod Server-->>UI: deployments info
32− Note over Octopod Server/StatusUpdater: wait 5 minutes
33− loop check deployment status every 30 seconds
34− Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
35− Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
36− UI->>Octopod Server: get deployments info
37− Octopod Server-->>UI: deployments info
38− end
39− end
40− end
docs/diagrams/src/technical-architecture-restore.mmdnew file+42
@@ -0,0 +1,42 @@
1+sequenceDiagram
2+ participant User
3+ participant Octopod Frontend
4+ participant Octopod Backend
5+ participant Control Scipts
6+ participant Octopod DB
7+
8+ User ->>+ Octopod Frontend: Restore deployment
9+ activate Octopod Frontend
10+
11+ Octopod Frontend ->>+ Octopod Backend: Restore deployment
12+ Octopod Backend ->>+ Control Scipts: config_check – Check deployment config
13+ Control Scipts ->>- Octopod Backend: Deployment config OK
14+
15+ Octopod Backend -->> Octopod Frontend: Delpoyment restore started
16+ Octopod Frontend -->>- User: Deployment restore started
17+
18+
19+ Octopod Backend ->>+ Control Scipts: info – Get deployment metadata
20+ Control Scipts -->>- Octopod Backend: Deployment metadata
21+
22+ Octopod Backend ->>+ Octopod DB: Set deployment metadata
23+ Octopod DB -->>- Octopod Backend: Deployment metadata set
24+
25+ Octopod Backend ->>+ Control Scipts: unarchive – Unarchive the delpoyment
26+ Control Scipts -->>- Octopod Backend: Deployment unarchiving started
27+
28+ Octopod Backend ->>+ Octopod DB: Set deployment state to CreatePending
29+ Octopod DB -->>- Octopod Backend: Deployment status set
30+
31+ loop
32+ Octopod Backend ->>+ Control Scipts: check – Is the deployment up?
33+ Control Scipts -->>- Octopod Backend: deployment is not up
34+ end
35+
36+ Octopod Backend ->>+ Control Scipts: check – Is the deployment up?
37+ Control Scipts -->>- Octopod Backend: deployment is up!
38+
39+ Octopod Backend ->>+ Octopod DB: Set deployment state to Running
40+ Octopod DB -->>- Octopod Backend: Deployment status set
41+
42+ deactivate Octopod Backend
docs/diagrams/src/technical-architecture-update-via-cli.mmddeleted−41
@@ -1,41 +0,0 @@
1−sequenceDiagram
2− participant octo CLI
3− participant Octopod Server
4− participant PostgreSQL
5− participant UI
6− participant Octopod Server/BgWorker
7− participant Octopod Server/StatusUpdater
8− participant ControlScripts
9− participant KubeAPI
10− octo CLI->>Octopod Server: create(name, tag, [override])
11− Octopod Server->>PostgreSQL: store config, status=UpdatePending
12− alt name not found
13− PostgreSQL-->>Octopod Server: error: name not found
14− Octopod Server-->>octo CLI: error: name not found
15− else
16− alt tag not found
17− Octopod Server-->>octo CLI: error: tag not found
18− else
19− PostgreSQL-->>Octopod Server: ok
20− Octopod Server-xUI: event FrontendPleaseUpdateEverything
21− UI->>Octopod Server: get deployments info
22− Octopod Server-->UI: deployments info
23− Octopod Server->>Octopod Server/BgWorker: update
24− Octopod Server-->>octo CLI: done
25− Octopod Server/BgWorker->>ControlScripts: update
26− ControlScripts->>KubeAPI: upgrade deployment
27− KubeAPI-->>ControlScripts: done
28− ControlScripts-->>Octopod Server/BgWorker: done
29− Octopod Server/BgWorker->>PostgreSQL: write logs
30− Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
31− UI->>Octopod Server: get deployments info
32− Octopod Server-->UI: deployments info
33− Note over Octopod Server/StatusUpdater: wait 5 minutes
34− loop check deployment status every 30 seconds
35− Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
36− Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
37− UI->>Octopod Server: get deployments info
38− Octopod Server-->UI: deployments info
39− end
40− end
41− end
docs/diagrams/src/technical-architecture-update-via-ui.mmddeleted−40
@@ -1,40 +0,0 @@
1−sequenceDiagram
2− participant UI
3− participant Octopod Server
4− participant PostgreSQL
5− participant Octopod Server/BgWorker
6− participant Octopod Server/StatusUpdater
7− participant ControlScripts
8− participant KubeAPI
9− UI->>Octopod Server: create(name, tag, [override])
10− Octopod Server->>PostgreSQL: store config, status=UpdatePending
11− alt name not found
12− PostgreSQL-->>Octopod Server: error: name not found
13− Octopod Server-->>UI: error: name not found
14− else
15− alt tag not found
16− Octopod Server-->>UI: error: tag not found
17− else
18− PostgreSQL-->>Octopod Server: ok
19− Octopod Server-xUI: event FrontendPleaseUpdateEverything
20− UI->>Octopod Server: get deployments info
21− Octopod Server-->>UI: deployments info
22− Octopod Server->>Octopod Server/BgWorker: update
23− Octopod Server-->>UI: done
24− Octopod Server/BgWorker->>ControlScripts: update
25− ControlScripts->>KubeAPI: upgrade deployment
26− KubeAPI-->>ControlScripts: done
27− ControlScripts-->>Octopod Server/BgWorker: done
28− Octopod Server/BgWorker->>PostgreSQL: write logs
29− Octopod Server/BgWorker-xUI: event FrontendPleaseUpdateEverything
30− UI->>Octopod Server: get deployments info
31− Octopod Server-->>UI: deployments info
32− Note over Octopod Server/StatusUpdater: wait 5 minutes
33− loop check deployment status every 30 seconds
34− Octopod Server/StatusUpdater->>PostgreSQL: status=Running/Failure
35− Octopod Server/StatusUpdater-xUI: event FrontendPleaseUpdateEverything
36− UI->>Octopod Server: get deployments info
37− Octopod Server-->>UI: deployments info
38− end
39− end
40− end
docs/diagrams/src/technical-architecture-update.mmdnew file+46
@@ -0,0 +1,46 @@
1+sequenceDiagram
2+ participant User
3+ participant Octopod Frontend
4+ participant Octopod Backend
5+ participant Control Scipts
6+ participant Octopod DB
7+
8+ User ->>+ Octopod Frontend: Update deployment
9+ activate Octopod Frontend
10+
11+ Octopod Frontend ->>+ Octopod Backend: Update deployment
12+ Octopod Backend ->>+ Control Scipts: config_check – Check deployment config
13+ Control Scipts ->>- Octopod Backend: Deployment config OK
14+
15+ Octopod Backend -->> Octopod Frontend: Delpoyment update started
16+ Octopod Frontend -->>- User: Close deployment popup
17+
18+
19+ Octopod Backend ->>+ Octopod DB: Update deployment config
20+ Octopod DB -->>- Octopod Backend: Deployment config updated
21+
22+
23+ Octopod Backend ->>+ Control Scipts: info – Get deployment metadata
24+ Control Scipts -->>- Octopod Backend: Deployment metadata
25+
26+ Octopod Backend ->>+ Octopod DB: Set deployment metadata
27+ Octopod DB -->>- Octopod Backend: Deployment metadata set
28+
29+ Octopod Backend ->>+ Control Scipts: update – Update the delpoyment
30+ Control Scipts -->>- Octopod Backend: Deployment update started
31+
32+ Octopod Backend ->>+ Octopod DB: Set deployment state to UpdatePending
33+ Octopod DB -->>- Octopod Backend: Deployment status set
34+
35+ loop
36+ Octopod Backend ->>+ Control Scipts: check – Is the deployment up?
37+ Control Scipts -->>- Octopod Backend: deployment is not up
38+ end
39+
40+ Octopod Backend ->>+ Control Scipts: check – Is the deployment up?
41+ Control Scipts -->>- Octopod Backend: deployment is up!
42+
43+ Octopod Backend ->>+ Octopod DB: Set deployment state to Running
44+ Octopod DB -->>- Octopod Backend: Deployment status set
45+
46+ deactivate Octopod Backend
docs/en/Octopod_deployment_guide.md+39−6
@@ -7,12 +7,40 @@ There are several options to install Octopod depending on your needs.
77You can install Octopod in any Kubernetes cluster using our [Helm chart](../../charts/octopod).
88
99
10−Your cluster must satisfy following requirements:
11−- PVC support
12−- Ingress contoller ([ingress-nginx](https://kubernetes.github.io/ingress-nginx/)) installed
13−- Kubernetes version >= 1.19.0
10+Your cluster must satisfy the following requirements:
11+
12+#### Mandatory requirements
13+
14+- Kubernetes version >= 1.12.0 <= 1.22.0
15+- PVC support (for PostgreSQL persistence)
16+- [NGINX Ingress](https://kubernetes.github.io/ingress-nginx/) contoller version <= 0.49.3 installed. NGINX Ingress controller v1.x.x is not currently supported
17+
18+#### Optional requirements
19+
20+- Cert Manager ([cert-manager](https://cert-manager.io/docs/installation/)) installed, if you want to get SSL certificates from Let's Encrypt automatically.
21+
22+After ensuring that your cluster satisfies the requirements you can follow [the Helm installation instruction](../../charts/octopod/README.md) provided with our Helm chart.
23+
24+### Running Octopod in production considerations.
25+
26+You must consider several things before running Octopod in production and onboarding your team.
27+
28+#### DNS records
29+
30+Octopod will create a lot of on-demand environments and they must be reachable for your team. Usually it implies creating a lot of DNS records, pointing to Octopod managed environments. This process must be automated.
31+
32+We highly recommend you to use [ExternalDNS](https://github.com/kubernetes-sigs/external-dns) as a very versatile solution for DNS records automation.
33+In certain cases, however, it isn't possible to use ExternalDNS. In this case you can use a wildcard DNS record, pointing to the service endpoint of your ingress controller. We do not recommend using wildcard DNS records. Even though they are much easier to implement, they could lead to hard-to-trace errors and they are also implementation-dependent.
34+
35+#### Certificates
36+
37+If you want to request SSL certificates dynamically from Let's Encrypt you must be aware of their [limits](https://letsencrypt.org/docs/rate-limits/). Because of these limits we recommend you to use a wildcard certificate for all Octopod deployments. The wildcard certificate has its limitations, like being valid only for one subdomain. This implies a need for planning your deployments DNS naming.
38+
39+#### Resources
40+
41+This is the most obvious one of the three, but you need to plan your cluster capacity based on your team needs. Setting [resource limits](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for your workloads is one of the best practices of using Kubernetes, and you probably have done this already.
42+Also consider using autoscaling since it really helps to reduce costs in highly dynamic environments.
1443
15−After ensuring that your cluster satisfies the requirements you can follow [the Helm installation instruction](../../charts/octopod/README.md) provided with our helm chart.
1644
1745### If you want to try it locally
1846
@@ -24,6 +52,11 @@ Before running the script make sure that you have following tools installed:
2452- [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)
2553- [helm 3](https://helm.sh/docs/intro/quickstart/#install-helm)
2654
55+> **ℹ️ NOTE:** using Octopod involves deploying many instances of some service (many instances of Wordpress in our example). This means, it will use up a lot of resources. Many local installations of Docker and Kubenetes have resource limits set quite low by default. If you experience performance issues you should try increasing the resource limits. Here are some useful links:
56+> - Docker resource limits in Docker Desktop for Mac – [docs.docker.com/desktop/mac/#resources](https://docs.docker.com/desktop/mac/#resources)
57+> - Docker resource limits in Docker Desktop for Windows – [docs.docker.com/desktop/windows/#resources](https://docs.docker.com/desktop/windows/#resources)
58+> - General Docker resource constraint documentation – [docs.docker.com/config/containers/resource_constraints](https://docs.docker.com/config/containers/resource_constraints/)
59+
2760After you have all of the necessary tools installed, you can run the script:
2861
2962```bash
@@ -36,7 +69,7 @@ Script will expose octopod ui at `octopod.lvh.me`.
3669
3770## What next?
3871
39−Now you may want to check how to [install helm charts](Helm-based_deployment_guide.md) with Octopod.
72+Now you may want to check how to [install Helm charts](Helm-based_deployment_guide.md) with Octopod.
4073
4174<br />
4275
docs/en/Overview.md+2−2
@@ -21,7 +21,7 @@ Octopod aims to extract the mentioned similarity between deploying different sta
2121
2222### 💽 The server
2323
24−You are developing a server, which is accessed through [_nginx_](https://www.nginx.com), and the server needs access to a [*Postgres*](https://www.postgresql.org) database and a [*Redis*](https://redis.io) database
24+You are developing a server, which is accessed through _nginx_, and the server needs access to a [*Postgres*](https://www.postgresql.org) database and a [*Redis*](https://redis.io) database
2525
2626![](../diagrams/images/sample_architecture.png)
2727
@@ -33,7 +33,7 @@ Now imagine that two different developers each completed one of the tasks, and y
3333
3434A way to mitigate this situation is to test each feature separately, in its own staging deployment, and verify which change made the page background pink, and ideally, you would check each feature before merging them into the final product (merging it into the `master` branch, for example).
3535
36−To check each feature before merging would require every developer to build the new version of the server, and set up all required services: [_nginx_](https://www.nginx.com), [*Postgres*](https://www.postgresql.org), [*Redis*](https://redis.io). Developers would also have to manage access to the set up environments ― set up SSL certificates, set up subdomains, make sure databases are not exposed, and make sure the connection between every component is secure and authenticated.
36+To check each feature before merging would require every developer to build the new version of the server, and set up all required services: _nginx_, [*Postgres*](https://www.postgresql.org), [*Redis*](https://redis.io). Developers would also have to manage access to the set up environments ― set up SSL certificates, set up subdomains, make sure databases are not exposed, and make sure the connection between every component is secure and authenticated.
3737
3838This is **a lot** of overhead just to test the color of a button. Note that most of the described work would be identical across the vast majority of features ― changing the deployment architecture is a relatively rare task in most projects. Databases, load balancing, caching, and proxying would be set up in much the same way for the majority of feature-specific stagings. The server itself is probably also compiled in exactly the same way for most features.
3939
docs/en/Technical_architecture.md+8−135
@@ -68,156 +68,29 @@ A _Docker Image_ with [_control scripts_](#-control-scripts) should be provided
6868
6969## Process view
7070
71−Here we provide sequence diagrams for every basic operation that can be performed in _Octopod_. These operations call [_control scripts_](#-control-scripts). On the diagrams, they are labeled as _ControlScripts_.
71+Here we provide sequence diagrams for every basic operation that can be performed in _Octopod_. These operations call [_control scripts_](#-control-scripts). On the diagrams, they are labeled as _Control Scripts_.
7272
73−### ✨ Create
73+### ✨ Deployment creation
7474
75−_Create_ – creates a new deployment. The main inputs include the name of the deployment, the _Docker Image tag_ and optional configurations. A more detailed description can be found in the [control scripts documentation](Control_scripts.md#-create).
75+![Create](../diagrams/images/technical-architecture-create.png)
7676
77−The arguments are forwarded to the [_create_](Control_scripts.md#-create) script which in turn creates the deployment in the _Kubernetes cluster_. It might call something like:
7877
79−```bash
80−helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
81− --set "global.project-name=$project_name" \
82− --set "global.base-domain=$base-domain" \
83− --set "app.tag=$tag" \
84− --set "app.env.foo=$app_env_configuration_1" \
85− --set "app.bar=$deployment_configuration_1" \
86− --wait \
87− --timeout 300
88−```
78+### 🔧 Deployment update
8979
90−<details>
91− <summary>Create via CLI sequence diagram</summary>
92−
93−![Create](../diagrams/images/technical-architecture-create-via-cli.png)
94−
95−</details>
96−
97−<details>
98− <summary>Create via UI sequence diagram</summary>
99−
100−![Create](../diagrams/images/technical-architecture-create-via-ui.png)
101−
102−</details>
103−
104−### 🔧 Update
105−
106−_Update_ – updates an existing deployment. The main inputs include the name of the deployment, the _Docker Image tag_ and optional configurations. A more detailed description can be found in the [control scripts documentation](Control_scripts.md#-update).
107−
108−_configurations_ are read from the database and merged with the new changes. All arguments are forwarded to the [_update_](Control_scripts.md#-update) script which in turn updates the specified deployment with the new parameters in the _Kubernetes cluster_. It might call something like:
109−
110−```bash
111−helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
112− --set "global.project-name=$project_name" \
113− --set "global.base-domain=$base-domain" \
114− --set "app.tag=$tag" \
115− --set "app.env.foo=$app_env_configuration_1" \
116− --set "app.bar=$deployment_configuration_1" \
117− --wait \
118− --timeout 300
119−```
120−
121−
122−<details>
123− <summary>Update via CLI sequence diagram</summary>
124−
125−![Update](../diagrams/images/technical-architecture-update-via-cli.png)
126−
127−</details>
128−
129−<details>
130− <summary>Update via UI sequence diagram</summary>
131−
132−![Update](../diagrams/images/technical-architecture-update-via-ui.png)
133−
134−</details>
80+![Update](../diagrams/images/technical-architecture-update.png)
13581
13682### 🗃 Archive
13783
138−_Delete_ – archives a deployment. It should only free the computational resources (_Pods_). _Persistent Volumes_ should not be deleted ― they are cleared in the [_cleanup_](#-cleanup) process. This operation can be undone with the [_restore_](#-restore) command.
139−
140−The main argument is the name that identifies the deployment. A more detailed description can be found in the [control scripts documentation](Control_scripts.md#-archive).
141−
142−The arguments are forwarded to the [_delete_](Control_scripts.md#-archive) script which in turn frees the computational resources. It might call something like:
143−
144−```bash
145−helm delete "$name" --purge
146−```
147−
148−<details>
149− <summary>Archive via CLI sequence diagram</summary>
150−
151−![Archive](../diagrams/images/technical-architecture-archive-via-cli.png)
152−
153−</details>
154−
155−<details>
156− <summary>Archive via UI sequence diagram</summary>
157−
158−![Archive](../diagrams/images/technical-architecture-archive-via-ui.png)
159−
160−</details>
84+![Archive](../diagrams/images/technical-architecture-archive.png)
16185
16286### 🚮 Cleanup
16387
164−_Cleanup_ – releases **all** resources captured by the deployment.
165−
166−The main argument is the name that identifies the deployment. A more detailed description can be found in the [control scripts documentation](Control_scripts.md#-cleanup). It can only be called after [_archive_](#-archive) has been executed.
167−
168−The arguments are forwarded to the [_cleanup_](Control_scripts.md#-cleanup) script which in turn frees all resources captured by the given deployment. It might call something like:
169−
170−```bash
171−kubectl delete pvc -n "$namespace" "$name-postgres-pvc"
172−kubectl delete certificate -n "$namespace" "$name-postgres-cert"
173−```
174−
175−<details>
176− <summary>Cleanup via CLI sequence diagram</summary>
177−
178−![Cleanup](../diagrams/images/technical-architecture-cleanup-via-cli.png)
179−
180−</details>
181−
182−<details>
183− <summary>Cleanup via UI sequence diagram</summary>
18488
185−![Cleanup](../diagrams/images/technical-architecture-cleanup-via-ui.png)
186−
187−</details>
89+![Cleanup](../diagrams/images/technical-architecture-cleanup.png)
18890
18991### 🔁 Restore
19092
191−_restore_ – restores an archived deployment in the state it was last in. Calls the same _script_ that is called in [_create_](#-create).
192−
193−The main argument is the name that identifies the deployment. A more detailed description can be found in the [control scripts documentation](Control_scripts.md#-create). It can only be called after [_archive_](#-archive) has been executed.
194−
195−All necessary setup information is read from the database: _configurations_ and the _Docker Image tag_. The arguments are forwarded to the [_create_](Control_scripts.md#-create) script which in turn recreates the deployment. It might call something like:
196−
197−```bash
198−helm upgrade --install --namespace "$namespace" "$name" "$deployment_chart" \
199− --set "global.project-name=$project_name" \
200− --set "global.base-domain=$base-domain" \
201− --set "app.tag=$tag" \
202− --set "app.env.foo=$app_env_configuration_1" \
203− --set "app.bar=$deployment_configuration_1" \
204− --wait \
205− --timeout 300
206−```
207−
208−<details>
209− <summary>Restore via CLI sequence diagram</summary>
210−
211−![Restore](../diagrams/images/technical-architecture-restore-via-cli.png)
212−
213−</details>
214−
215−<details>
216− <summary>Restore via UI sequence diagram</summary>
217−
218−![Restore](../diagrams/images/technical-architecture-restore-via-ui.png)
219−
220−</details>
93+![Restore](../diagrams/images/technical-architecture-restore.png)
22194
22295## 👨‍💻👩‍💻 How we use it
22396
helm-control-scripts/Cargo.lock+459−168
@@ -1,7 +1,5 @@
11# This file is automatically @generated by Cargo.
22# It is not intended for manual editing.
3−version = 3
4−
53[[package]]
64name = "adler32"
75version = "1.2.0"
@@ -47,6 +45,17 @@ dependencies = [
4745 "syn",
4846]
4947
48+[[package]]
49+name = "async-trait"
50+version = "0.1.51"
51+source = "registry+https://github.com/rust-lang/crates.io-index"
52+checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
53+dependencies = [
54+ "proc-macro2",
55+ "quote",
56+ "syn",
57+]
58+
5059[[package]]
5160name = "atty"
5261version = "0.2.14"
@@ -72,9 +81,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
7281
7382[[package]]
7483name = "bitflags"
75−version = "1.2.1"
84+version = "1.3.2"
7685source = "registry+https://github.com/rust-lang/crates.io-index"
77−checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
86+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
7887
7988[[package]]
8089name = "block-buffer"
@@ -87,21 +96,21 @@ dependencies = [
8796
8897[[package]]
8998name = "bumpalo"
90−version = "3.7.0"
99+version = "3.8.0"
91100source = "registry+https://github.com/rust-lang/crates.io-index"
92−checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
101+checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
93102
94103[[package]]
95104name = "bytes"
96−version = "1.0.1"
105+version = "1.1.0"
97106source = "registry+https://github.com/rust-lang/crates.io-index"
98−checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
107+checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
99108
100109[[package]]
101110name = "cc"
102−version = "1.0.69"
111+version = "1.0.71"
103112source = "registry+https://github.com/rust-lang/crates.io-index"
104−checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
113+checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
105114
106115[[package]]
107116name = "cfg-if"
@@ -132,7 +141,7 @@ dependencies = [
132141 "ansi_term",
133142 "atty",
134143 "bitflags",
135− "strsim",
144+ "strsim 0.8.0",
136145 "textwrap",
137146 "unicode-width",
138147 "vec_map",
@@ -140,9 +149,9 @@ dependencies = [
140149
141150[[package]]
142151name = "core-foundation"
143−version = "0.9.1"
152+version = "0.9.2"
144153source = "registry+https://github.com/rust-lang/crates.io-index"
145−checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
154+checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
146155dependencies = [
147156 "core-foundation-sys",
148157 "libc",
@@ -150,15 +159,15 @@ dependencies = [
150159
151160[[package]]
152161name = "core-foundation-sys"
153−version = "0.8.2"
162+version = "0.8.3"
154163source = "registry+https://github.com/rust-lang/crates.io-index"
155−checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
164+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
156165
157166[[package]]
158167name = "cpufeatures"
159−version = "0.1.5"
168+version = "0.2.1"
160169source = "registry+https://github.com/rust-lang/crates.io-index"
161−checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
170+checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
162171dependencies = [
163172 "libc",
164173]
@@ -172,6 +181,51 @@ dependencies = [
172181 "cfg-if",
173182]
174183
184+[[package]]
185+name = "crypto-mac"
186+version = "0.11.1"
187+source = "registry+https://github.com/rust-lang/crates.io-index"
188+checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
189+dependencies = [
190+ "generic-array",
191+ "subtle",
192+]
193+
194+[[package]]
195+name = "darling"
196+version = "0.12.4"
197+source = "registry+https://github.com/rust-lang/crates.io-index"
198+checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
199+dependencies = [
200+ "darling_core",
201+ "darling_macro",
202+]
203+
204+[[package]]
205+name = "darling_core"
206+version = "0.12.4"
207+source = "registry+https://github.com/rust-lang/crates.io-index"
208+checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
209+dependencies = [
210+ "fnv",
211+ "ident_case",
212+ "proc-macro2",
213+ "quote",
214+ "strsim 0.10.0",
215+ "syn",
216+]
217+
218+[[package]]
219+name = "darling_macro"
220+version = "0.12.4"
221+source = "registry+https://github.com/rust-lang/crates.io-index"
222+checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
223+dependencies = [
224+ "darling_core",
225+ "quote",
226+ "syn",
227+]
228+
175229[[package]]
176230name = "digest"
177231version = "0.9.0"
@@ -234,6 +288,12 @@ version = "0.4.8"
234288source = "registry+https://github.com/rust-lang/crates.io-index"
235289checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
236290
291+[[package]]
292+name = "dyn-clone"
293+version = "1.0.4"
294+source = "registry+https://github.com/rust-lang/crates.io-index"
295+checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
296+
237297[[package]]
238298name = "either"
239299version = "1.6.1"
@@ -242,9 +302,9 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
242302
243303[[package]]
244304name = "encoding_rs"
245−version = "0.8.28"
305+version = "0.8.29"
246306source = "registry+https://github.com/rust-lang/crates.io-index"
247−checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065"
307+checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746"
248308dependencies = [
249309 "cfg-if",
250310]
@@ -316,9 +376,9 @@ dependencies = [
316376
317377[[package]]
318378name = "futures"
319−version = "0.3.16"
379+version = "0.3.17"
320380source = "registry+https://github.com/rust-lang/crates.io-index"
321−checksum = "1adc00f486adfc9ce99f77d717836f0c5aa84965eb0b4f051f4e83f7cab53f8b"
381+checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca"
322382dependencies = [
323383 "futures-channel",
324384 "futures-core",
@@ -331,9 +391,9 @@ dependencies = [
331391
332392[[package]]
333393name = "futures-channel"
334−version = "0.3.16"
394+version = "0.3.17"
335395source = "registry+https://github.com/rust-lang/crates.io-index"
336−checksum = "74ed2411805f6e4e3d9bc904c95d5d423b89b3b25dc0250aa74729de20629ff9"
396+checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
337397dependencies = [
338398 "futures-core",
339399 "futures-sink",
@@ -341,15 +401,15 @@ dependencies = [
341401
342402[[package]]
343403name = "futures-core"
344−version = "0.3.16"
404+version = "0.3.17"
345405source = "registry+https://github.com/rust-lang/crates.io-index"
346−checksum = "af51b1b4a7fdff033703db39de8802c673eb91855f2e0d47dcf3bf2c0ef01f99"
406+checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
347407
348408[[package]]
349409name = "futures-executor"
350−version = "0.3.16"
410+version = "0.3.17"
351411source = "registry+https://github.com/rust-lang/crates.io-index"
352−checksum = "4d0d535a57b87e1ae31437b892713aee90cd2d7b0ee48727cd11fc72ef54761c"
412+checksum = "45025be030969d763025784f7f355043dc6bc74093e4ecc5000ca4dc50d8745c"
353413dependencies = [
354414 "futures-core",
355415 "futures-task",
@@ -358,15 +418,15 @@ dependencies = [
358418
359419[[package]]
360420name = "futures-io"
361−version = "0.3.16"
421+version = "0.3.17"
362422source = "registry+https://github.com/rust-lang/crates.io-index"
363−checksum = "0b0e06c393068f3a6ef246c75cdca793d6a46347e75286933e5e75fd2fd11582"
423+checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
364424
365425[[package]]
366426name = "futures-macro"
367−version = "0.3.16"
427+version = "0.3.17"
368428source = "registry+https://github.com/rust-lang/crates.io-index"
369−checksum = "c54913bae956fb8df7f4dc6fc90362aa72e69148e3f39041fbe8742d21e0ac57"
429+checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb"
370430dependencies = [
371431 "autocfg",
372432 "proc-macro-hack",
@@ -377,21 +437,21 @@ dependencies = [
377437
378438[[package]]
379439name = "futures-sink"
380−version = "0.3.16"
440+version = "0.3.17"
381441source = "registry+https://github.com/rust-lang/crates.io-index"
382−checksum = "c0f30aaa67363d119812743aa5f33c201a7a66329f97d1a887022971feea4b53"
442+checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
383443
384444[[package]]
385445name = "futures-task"
386−version = "0.3.16"
446+version = "0.3.17"
387447source = "registry+https://github.com/rust-lang/crates.io-index"
388−checksum = "bbe54a98670017f3be909561f6ad13e810d9a51f3f061b902062ca3da80799f2"
448+checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
389449
390450[[package]]
391451name = "futures-util"
392−version = "0.3.16"
452+version = "0.3.17"
393453source = "registry+https://github.com/rust-lang/crates.io-index"
394−checksum = "67eb846bfd58e44a8481a00049e82c43e0ccb5d61f8dc071057cb19249dd4d78"
454+checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
395455dependencies = [
396456 "autocfg",
397457 "futures-channel",
@@ -431,9 +491,9 @@ dependencies = [
431491
432492[[package]]
433493name = "h2"
434−version = "0.3.3"
494+version = "0.3.6"
435495source = "registry+https://github.com/rust-lang/crates.io-index"
436−checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726"
496+checksum = "6c06815895acec637cd6ed6e9662c935b866d20a106f8361892893a7d9234964"
437497dependencies = [
438498 "bytes",
439499 "fnv",
@@ -465,15 +525,19 @@ dependencies = [
465525
466526[[package]]
467527name = "helm-control-scripts"
468−version = "0.2.0"
528+version = "0.2.3"
469529dependencies = [
470530 "dkregistry",
471531 "env_logger",
472532 "envy",
533+ "json-patch",
473534 "k8s-openapi",
474535 "kube",
475536 "log",
476537 "regex",
538+ "rusoto_core",
539+ "rusoto_ecr",
540+ "schemars",
477541 "serde",
478542 "serde_json",
479543 "serde_yaml",
@@ -484,18 +548,34 @@ dependencies = [
484548
485549[[package]]
486550name = "hermit-abi"
487−version = "0.1.18"
551+version = "0.1.19"
488552source = "registry+https://github.com/rust-lang/crates.io-index"
489−checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
553+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
490554dependencies = [
491555 "libc",
492556]
493557
558+[[package]]
559+name = "hex"
560+version = "0.4.3"
561+source = "registry+https://github.com/rust-lang/crates.io-index"
562+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
563+
564+[[package]]
565+name = "hmac"
566+version = "0.11.0"
567+source = "registry+https://github.com/rust-lang/crates.io-index"
568+checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
569+dependencies = [
570+ "crypto-mac",
571+ "digest",
572+]
573+
494574[[package]]
495575name = "http"
496−version = "0.2.4"
576+version = "0.2.5"
497577source = "registry+https://github.com/rust-lang/crates.io-index"
498−checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
578+checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
499579dependencies = [
500580 "bytes",
501581 "fnv",
@@ -504,9 +584,9 @@ dependencies = [
504584
505585[[package]]
506586name = "http-body"
507−version = "0.4.2"
587+version = "0.4.3"
508588source = "registry+https://github.com/rust-lang/crates.io-index"
509−checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9"
589+checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5"
510590dependencies = [
511591 "bytes",
512592 "http",
@@ -515,9 +595,9 @@ dependencies = [
515595
516596[[package]]
517597name = "httparse"
518−version = "1.4.1"
598+version = "1.5.1"
519599source = "registry+https://github.com/rust-lang/crates.io-index"
520−checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68"
600+checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
521601
522602[[package]]
523603name = "httpdate"
@@ -533,9 +613,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
533613
534614[[package]]
535615name = "hyper"
536−version = "0.14.11"
616+version = "0.14.13"
537617source = "registry+https://github.com/rust-lang/crates.io-index"
538−checksum = "0b61cf2d1aebcf6e6352c97b81dc2244ca29194be1b276f5d8ad5c6330fffb11"
618+checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593"
539619dependencies = [
540620 "bytes",
541621 "futures-channel",
@@ -580,6 +660,12 @@ dependencies = [
580660 "tokio-native-tls",
581661]
582662
663+[[package]]
664+name = "ident_case"
665+version = "1.0.1"
666+source = "registry+https://github.com/rust-lang/crates.io-index"
667+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
668+
583669[[package]]
584670name = "idna"
585671version = "0.2.3"
@@ -603,9 +689,9 @@ dependencies = [
603689
604690[[package]]
605691name = "instant"
606−version = "0.1.10"
692+version = "0.1.12"
607693source = "registry+https://github.com/rust-lang/crates.io-index"
608−checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
694+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
609695dependencies = [
610696 "cfg-if",
611697]
@@ -618,19 +704,30 @@ checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
618704
619705[[package]]
620706name = "itoa"
621−version = "0.4.7"
707+version = "0.4.8"
622708source = "registry+https://github.com/rust-lang/crates.io-index"
623−checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
709+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
624710
625711[[package]]
626712name = "js-sys"
627−version = "0.3.52"
713+version = "0.3.55"
628714source = "registry+https://github.com/rust-lang/crates.io-index"
629−checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752"
715+checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
630716dependencies = [
631717 "wasm-bindgen",
632718]
633719
720+[[package]]
721+name = "json-patch"
722+version = "0.2.6"
723+source = "registry+https://github.com/rust-lang/crates.io-index"
724+checksum = "f995a3c8f2bc3dd52a18a583e90f9ec109c047fa1603a853e46bcda14d2e279d"
725+dependencies = [
726+ "serde",
727+ "serde_json",
728+ "treediff",
729+]
730+
634731[[package]]
635732name = "jsonpath_lib"
636733version = "0.3.0"
@@ -679,6 +776,7 @@ dependencies = [
679776 "jsonpath_lib",
680777 "k8s-openapi",
681778 "kube-core",
779+ "kube-derive",
682780 "openssl",
683781 "pem",
684782 "pin-project",
@@ -702,12 +800,26 @@ checksum = "ffbce0d890efc42abb31e974419e25a643a97ab7c6b3b9498bda686d10b55f8d"
702800dependencies = [
703801 "form_urlencoded",
704802 "http",
803+ "json-patch",
705804 "k8s-openapi",
706805 "serde",
707806 "serde_json",
708807 "thiserror",
709808]
710809
810+[[package]]
811+name = "kube-derive"
812+version = "0.58.1"
813+source = "registry+https://github.com/rust-lang/crates.io-index"
814+checksum = "95a28be5ca4f233b5dd872f426fdc75d6765c34576b590c44564982c05fdb400"
815+dependencies = [
816+ "darling",
817+ "proc-macro2",
818+ "quote",
819+ "serde_json",
820+ "syn",
821+]
822+
711823[[package]]
712824name = "lazy_static"
713825version = "1.4.0"
@@ -716,15 +828,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
716828
717829[[package]]
718830name = "libc"
719−version = "0.2.97"
831+version = "0.2.104"
720832source = "registry+https://github.com/rust-lang/crates.io-index"
721−checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
833+checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce"
722834
723835[[package]]
724836name = "libflate"
725−version = "1.1.0"
837+version = "1.1.1"
726838source = "registry+https://github.com/rust-lang/crates.io-index"
727−checksum = "6d87eae36b3f680f7f01645121b782798b56ef33c53f83d1c66ba3a22b60bfe3"
839+checksum = "16364af76ebb39b5869bb32c81fa93573267cd8c62bb3474e28d78fac3fb141e"
728840dependencies = [
729841 "adler32",
730842 "crc32fast",
@@ -748,9 +860,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
748860
749861[[package]]
750862name = "lock_api"
751−version = "0.4.4"
863+version = "0.4.5"
752864source = "registry+https://github.com/rust-lang/crates.io-index"
753−checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
865+checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
754866dependencies = [
755867 "scopeguard",
756868]
@@ -766,15 +878,26 @@ dependencies = [
766878
767879[[package]]
768880name = "matches"
769−version = "0.1.8"
881+version = "0.1.9"
882+source = "registry+https://github.com/rust-lang/crates.io-index"
883+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
884+
885+[[package]]
886+name = "md-5"
887+version = "0.9.1"
770888source = "registry+https://github.com/rust-lang/crates.io-index"
771−checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
889+checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
890+dependencies = [
891+ "block-buffer",
892+ "digest",
893+ "opaque-debug",
894+]
772895
773896[[package]]
774897name = "memchr"
775−version = "2.4.0"
898+version = "2.4.1"
776899source = "registry+https://github.com/rust-lang/crates.io-index"
777−checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
900+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
778901
779902[[package]]
780903name = "mime"
@@ -784,9 +907,9 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
784907
785908[[package]]
786909name = "mio"
787−version = "0.7.13"
910+version = "0.7.14"
788911source = "registry+https://github.com/rust-lang/crates.io-index"
789−checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16"
912+checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
790913dependencies = [
791914 "libc",
792915 "log",
@@ -806,9 +929,9 @@ dependencies = [
806929
807930[[package]]
808931name = "native-tls"
809−version = "0.2.7"
932+version = "0.2.8"
810933source = "registry+https://github.com/rust-lang/crates.io-index"
811−checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4"
934+checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
812935dependencies = [
813936 "lazy_static",
814937 "libc",
@@ -874,9 +997,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
874997
875998[[package]]
876999name = "openssl"
877−version = "0.10.35"
1000+version = "0.10.36"
8781001source = "registry+https://github.com/rust-lang/crates.io-index"
879−checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885"
1002+checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a"
8801003dependencies = [
8811004 "bitflags",
8821005 "cfg-if",
@@ -894,9 +1017,9 @@ checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
8941017
8951018[[package]]
8961019name = "openssl-sys"
897−version = "0.9.65"
1020+version = "0.9.67"
8981021source = "registry+https://github.com/rust-lang/crates.io-index"
899−checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d"
1022+checksum = "69df2d8dfc6ce3aaf44b40dec6f487d5a886516cf6879c49e98e0710f310a058"
9001023dependencies = [
9011024 "autocfg",
9021025 "cc",
@@ -907,18 +1030,18 @@ dependencies = [
9071030
9081031[[package]]
9091032name = "ordered-float"
910−version = "2.7.0"
1033+version = "2.8.0"
9111034source = "registry+https://github.com/rust-lang/crates.io-index"
912−checksum = "039f02eb0f69271f26abe3202189275d7aa2258b903cb0281b5de710a2570ff3"
1035+checksum = "97c9d06878b3a851e8026ef94bf7fef9ba93062cd412601da4d9cf369b1cc62d"
9131036dependencies = [
9141037 "num-traits",
9151038]
9161039
9171040[[package]]
9181041name = "parking_lot"
919−version = "0.11.1"
1042+version = "0.11.2"
9201043source = "registry+https://github.com/rust-lang/crates.io-index"
921−checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
1044+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
9221045dependencies = [
9231046 "instant",
9241047 "lock_api",
@@ -927,9 +1050,9 @@ dependencies = [
9271050
9281051[[package]]
9291052name = "parking_lot_core"
930−version = "0.8.3"
1053+version = "0.8.5"
9311054source = "registry+https://github.com/rust-lang/crates.io-index"
932−checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
1055+checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
9331056dependencies = [
9341057 "cfg-if",
9351058 "instant",
@@ -990,15 +1113,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
9901113
9911114[[package]]
9921115name = "pkg-config"
993−version = "0.3.19"
1116+version = "0.3.20"
9941117source = "registry+https://github.com/rust-lang/crates.io-index"
995−checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
1118+checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb"
9961119
9971120[[package]]
9981121name = "ppv-lite86"
999−version = "0.2.10"
1122+version = "0.2.14"
10001123source = "registry+https://github.com/rust-lang/crates.io-index"
1001−checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
1124+checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741"
10021125
10031126[[package]]
10041127name = "proc-macro-error"
@@ -1038,18 +1161,18 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
10381161
10391162[[package]]
10401163name = "proc-macro2"
1041−version = "1.0.27"
1164+version = "1.0.30"
10421165source = "registry+https://github.com/rust-lang/crates.io-index"
1043−checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
1166+checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70"
10441167dependencies = [
10451168 "unicode-xid",
10461169]
10471170
10481171[[package]]
10491172name = "quote"
1050−version = "1.0.9"
1173+version = "1.0.10"
10511174source = "registry+https://github.com/rust-lang/crates.io-index"
1052−checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
1175+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
10531176dependencies = [
10541177 "proc-macro2",
10551178]
@@ -1096,9 +1219,9 @@ dependencies = [
10961219
10971220[[package]]
10981221name = "redox_syscall"
1099−version = "0.2.9"
1222+version = "0.2.10"
11001223source = "registry+https://github.com/rust-lang/crates.io-index"
1101−checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
1224+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
11021225dependencies = [
11031226 "bitflags",
11041227]
@@ -1141,9 +1264,9 @@ dependencies = [
11411264
11421265[[package]]
11431266name = "reqwest"
1144−version = "0.11.4"
1267+version = "0.11.6"
11451268source = "registry+https://github.com/rust-lang/crates.io-index"
1146−checksum = "246e9f61b9bb77df069a947682be06e31ac43ea37862e244a69f177694ea6d22"
1269+checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280"
11471270dependencies = [
11481271 "base64",
11491272 "bytes",
@@ -1180,6 +1303,98 @@ version = "1.0.1"
11801303source = "registry+https://github.com/rust-lang/crates.io-index"
11811304checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac"
11821305
1306+[[package]]
1307+name = "rusoto_core"
1308+version = "0.47.0"
1309+source = "registry+https://github.com/rust-lang/crates.io-index"
1310+checksum = "5b4f000e8934c1b4f70adde180056812e7ea6b1a247952db8ee98c94cd3116cc"
1311+dependencies = [
1312+ "async-trait",
1313+ "base64",
1314+ "bytes",
1315+ "crc32fast",
1316+ "futures",
1317+ "http",
1318+ "hyper",
1319+ "hyper-tls",
1320+ "lazy_static",
1321+ "log",
1322+ "rusoto_credential",
1323+ "rusoto_signature",
1324+ "rustc_version",
1325+ "serde",
1326+ "serde_json",
1327+ "tokio",
1328+ "xml-rs",
1329+]
1330+
1331+[[package]]
1332+name = "rusoto_credential"
1333+version = "0.47.0"
1334+source = "registry+https://github.com/rust-lang/crates.io-index"
1335+checksum = "6a46b67db7bb66f5541e44db22b0a02fed59c9603e146db3a9e633272d3bac2f"
1336+dependencies = [
1337+ "async-trait",
1338+ "chrono",
1339+ "dirs-next",
1340+ "futures",
1341+ "hyper",
1342+ "serde",
1343+ "serde_json",
1344+ "shlex",
1345+ "tokio",
1346+ "zeroize",
1347+]
1348+
1349+[[package]]
1350+name = "rusoto_ecr"
1351+version = "0.47.0"
1352+source = "registry+https://github.com/rust-lang/crates.io-index"
1353+checksum = "93ec4a28e4fb276307c2129abb16cf7d5573da4ec24d9e3895cc5c8a8bc21a4d"
1354+dependencies = [
1355+ "async-trait",
1356+ "bytes",
1357+ "futures",
1358+ "rusoto_core",
1359+ "serde",
1360+ "serde_json",
1361+]
1362+
1363+[[package]]
1364+name = "rusoto_signature"
1365+version = "0.47.0"
1366+source = "registry+https://github.com/rust-lang/crates.io-index"
1367+checksum = "6264e93384b90a747758bcc82079711eacf2e755c3a8b5091687b5349d870bcc"
1368+dependencies = [
1369+ "base64",
1370+ "bytes",
1371+ "chrono",
1372+ "digest",
1373+ "futures",
1374+ "hex",
1375+ "hmac",
1376+ "http",
1377+ "hyper",
1378+ "log",
1379+ "md-5",
1380+ "percent-encoding",
1381+ "pin-project-lite",
1382+ "rusoto_credential",
1383+ "rustc_version",
1384+ "serde",
1385+ "sha2",
1386+ "tokio",
1387+]
1388+
1389+[[package]]
1390+name = "rustc_version"
1391+version = "0.4.0"
1392+source = "registry+https://github.com/rust-lang/crates.io-index"
1393+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
1394+dependencies = [
1395+ "semver",
1396+]
1397+
11831398[[package]]
11841399name = "ryu"
11851400version = "1.0.5"
@@ -1196,6 +1411,30 @@ dependencies = [
11961411 "winapi",
11971412]
11981413
1414+[[package]]
1415+name = "schemars"
1416+version = "0.8.6"
1417+source = "registry+https://github.com/rust-lang/crates.io-index"
1418+checksum = "d7a48d098c2a7fdf5740b19deb1181b4fb8a9e68e03ae517c14cde04b5725409"
1419+dependencies = [
1420+ "dyn-clone",
1421+ "schemars_derive",
1422+ "serde",
1423+ "serde_json",
1424+]
1425+
1426+[[package]]
1427+name = "schemars_derive"
1428+version = "0.8.6"
1429+source = "registry+https://github.com/rust-lang/crates.io-index"
1430+checksum = "4a9ea2a613fe4cd7118b2bb101a25d8ae6192e1975179b67b2f17afd11e70ac8"
1431+dependencies = [
1432+ "proc-macro2",
1433+ "quote",
1434+ "serde_derive_internals",
1435+ "syn",
1436+]
1437+
11991438[[package]]
12001439name = "scopeguard"
12011440version = "1.1.0"
@@ -1204,9 +1443,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
12041443
12051444[[package]]
12061445name = "security-framework"
1207−version = "2.3.1"
1446+version = "2.4.2"
12081447source = "registry+https://github.com/rust-lang/crates.io-index"
1209−checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467"
1448+checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
12101449dependencies = [
12111450 "bitflags",
12121451 "core-foundation",
@@ -1217,19 +1456,25 @@ dependencies = [
12171456
12181457[[package]]
12191458name = "security-framework-sys"
1220−version = "2.3.0"
1459+version = "2.4.2"
12211460source = "registry+https://github.com/rust-lang/crates.io-index"
1222−checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284"
1461+checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
12231462dependencies = [
12241463 "core-foundation-sys",
12251464 "libc",
12261465]
12271466
1467+[[package]]
1468+name = "semver"
1469+version = "1.0.4"
1470+source = "registry+https://github.com/rust-lang/crates.io-index"
1471+checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012"
1472+
12281473[[package]]
12291474name = "serde"
1230−version = "1.0.126"
1475+version = "1.0.130"
12311476source = "registry+https://github.com/rust-lang/crates.io-index"
1232−checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
1477+checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
12331478dependencies = [
12341479 "serde_derive",
12351480]
@@ -1246,9 +1491,20 @@ dependencies = [
12461491
12471492[[package]]
12481493name = "serde_derive"
1249−version = "1.0.126"
1494+version = "1.0.130"
12501495source = "registry+https://github.com/rust-lang/crates.io-index"
1251−checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
1496+checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
1497+dependencies = [
1498+ "proc-macro2",
1499+ "quote",
1500+ "syn",
1501+]
1502+
1503+[[package]]
1504+name = "serde_derive_internals"
1505+version = "0.25.0"
1506+source = "registry+https://github.com/rust-lang/crates.io-index"
1507+checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
12521508dependencies = [
12531509 "proc-macro2",
12541510 "quote",
@@ -1266,9 +1522,9 @@ dependencies = [
12661522
12671523[[package]]
12681524name = "serde_json"
1269−version = "1.0.64"
1525+version = "1.0.68"
12701526source = "registry+https://github.com/rust-lang/crates.io-index"
1271−checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
1527+checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
12721528dependencies = [
12731529 "indexmap",
12741530 "itoa",
@@ -1290,21 +1546,21 @@ dependencies = [
12901546
12911547[[package]]
12921548name = "serde_yaml"
1293−version = "0.8.17"
1549+version = "0.8.21"
12941550source = "registry+https://github.com/rust-lang/crates.io-index"
1295−checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23"
1551+checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af"
12961552dependencies = [
12971553 "dtoa",
1298− "linked-hash-map",
1554+ "indexmap",
12991555 "serde",
13001556 "yaml-rust",
13011557]
13021558
13031559[[package]]
13041560name = "sha2"
1305−version = "0.9.5"
1561+version = "0.9.8"
13061562source = "registry+https://github.com/rust-lang/crates.io-index"
1307−checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
1563+checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
13081564dependencies = [
13091565 "block-buffer",
13101566 "cfg-if",
@@ -1313,6 +1569,12 @@ dependencies = [
13131569 "opaque-debug",
13141570]
13151571
1572+[[package]]
1573+name = "shlex"
1574+version = "1.1.0"
1575+source = "registry+https://github.com/rust-lang/crates.io-index"
1576+checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
1577+
13161578[[package]]
13171579name = "signal-hook-registry"
13181580version = "1.4.0"
@@ -1324,21 +1586,21 @@ dependencies = [
13241586
13251587[[package]]
13261588name = "slab"
1327−version = "0.4.3"
1589+version = "0.4.5"
13281590source = "registry+https://github.com/rust-lang/crates.io-index"
1329−checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
1591+checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
13301592
13311593[[package]]
13321594name = "smallvec"
1333−version = "1.6.1"
1595+version = "1.7.0"
13341596source = "registry+https://github.com/rust-lang/crates.io-index"
1335−checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
1597+checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
13361598
13371599[[package]]
13381600name = "socket2"
1339−version = "0.4.1"
1601+version = "0.4.2"
13401602source = "registry+https://github.com/rust-lang/crates.io-index"
1341−checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad"
1603+checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
13421604dependencies = [
13431605 "libc",
13441606 "winapi",
@@ -1350,11 +1612,17 @@ version = "0.8.0"
13501612source = "registry+https://github.com/rust-lang/crates.io-index"
13511613checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
13521614
1615+[[package]]
1616+name = "strsim"
1617+version = "0.10.0"
1618+source = "registry+https://github.com/rust-lang/crates.io-index"
1619+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
1620+
13531621[[package]]
13541622name = "structopt"
1355−version = "0.3.21"
1623+version = "0.3.25"
13561624source = "registry+https://github.com/rust-lang/crates.io-index"
1357−checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c"
1625+checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c"
13581626dependencies = [
13591627 "clap",
13601628 "lazy_static",
@@ -1363,9 +1631,9 @@ dependencies = [
13631631
13641632[[package]]
13651633name = "structopt-derive"
1366−version = "0.4.14"
1634+version = "0.4.18"
13671635source = "registry+https://github.com/rust-lang/crates.io-index"
1368−checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90"
1636+checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
13691637dependencies = [
13701638 "heck",
13711639 "proc-macro-error",
@@ -1392,11 +1660,17 @@ dependencies = [
13921660 "syn",
13931661]
13941662
1663+[[package]]
1664+name = "subtle"
1665+version = "2.4.1"
1666+source = "registry+https://github.com/rust-lang/crates.io-index"
1667+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
1668+
13951669[[package]]
13961670name = "syn"
1397−version = "1.0.73"
1671+version = "1.0.80"
13981672source = "registry+https://github.com/rust-lang/crates.io-index"
1399−checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
1673+checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194"
14001674dependencies = [
14011675 "proc-macro2",
14021676 "quote",
@@ -1405,9 +1679,9 @@ dependencies = [
14051679
14061680[[package]]
14071681name = "tar"
1408−version = "0.4.35"
1682+version = "0.4.37"
14091683source = "registry+https://github.com/rust-lang/crates.io-index"
1410−checksum = "7d779dc6aeff029314570f666ec83f19df7280bb36ef338442cfa8c604021b80"
1684+checksum = "d6f5515d3add52e0bbdcad7b83c388bb36ba7b754dda3b5f5bc2d38640cdba5c"
14111685dependencies = [
14121686 "filetime",
14131687 "libc",
@@ -1448,18 +1722,18 @@ dependencies = [
14481722
14491723[[package]]
14501724name = "thiserror"
1451−version = "1.0.26"
1725+version = "1.0.30"
14521726source = "registry+https://github.com/rust-lang/crates.io-index"
1453−checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2"
1727+checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
14541728dependencies = [
14551729 "thiserror-impl",
14561730]
14571731
14581732[[package]]
14591733name = "thiserror-impl"
1460−version = "1.0.26"
1734+version = "1.0.30"
14611735source = "registry+https://github.com/rust-lang/crates.io-index"
1462−checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745"
1736+checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
14631737dependencies = [
14641738 "proc-macro2",
14651739 "quote",
@@ -1479,9 +1753,9 @@ dependencies = [
14791753
14801754[[package]]
14811755name = "tinyvec"
1482−version = "1.3.1"
1756+version = "1.5.0"
14831757source = "registry+https://github.com/rust-lang/crates.io-index"
1484−checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338"
1758+checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7"
14851759dependencies = [
14861760 "tinyvec_macros",
14871761]
@@ -1494,9 +1768,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
14941768
14951769[[package]]
14961770name = "tokio"
1497−version = "1.7.1"
1771+version = "1.12.0"
14981772source = "registry+https://github.com/rust-lang/crates.io-index"
1499−checksum = "5fb2ed024293bb19f7a5dc54fe83bf86532a44c12a2bb8ba40d64a4509395ca2"
1773+checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc"
15001774dependencies = [
15011775 "autocfg",
15021776 "bytes",
@@ -1524,9 +1798,9 @@ dependencies = [
15241798
15251799[[package]]
15261800name = "tokio-macros"
1527−version = "1.3.0"
1801+version = "1.5.0"
15281802source = "registry+https://github.com/rust-lang/crates.io-index"
1529−checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110"
1803+checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd"
15301804dependencies = [
15311805 "proc-macro2",
15321806 "quote",
@@ -1545,9 +1819,9 @@ dependencies = [
15451819
15461820[[package]]
15471821name = "tokio-util"
1548−version = "0.6.7"
1822+version = "0.6.8"
15491823source = "registry+https://github.com/rust-lang/crates.io-index"
1550−checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592"
1824+checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd"
15511825dependencies = [
15521826 "bytes",
15531827 "futures-core",
@@ -1559,13 +1833,14 @@ dependencies = [
15591833
15601834[[package]]
15611835name = "tower"
1562−version = "0.4.8"
1836+version = "0.4.10"
15631837source = "registry+https://github.com/rust-lang/crates.io-index"
1564−checksum = "f60422bc7fefa2f3ec70359b8ff1caff59d785877eb70595904605bcc412470f"
1838+checksum = "c00e500fff5fa1131c866b246041a6bf96da9c965f8fe4128cb1421f23e93c00"
15651839dependencies = [
15661840 "futures-core",
15671841 "futures-util",
15681842 "pin-project",
1843+ "pin-project-lite",
15691844 "tokio",
15701845 "tokio-util",
15711846 "tower-layer",
@@ -1605,9 +1880,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
16051880
16061881[[package]]
16071882name = "tracing"
1608−version = "0.1.26"
1883+version = "0.1.29"
16091884source = "registry+https://github.com/rust-lang/crates.io-index"
1610−checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
1885+checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
16111886dependencies = [
16121887 "cfg-if",
16131888 "log",
@@ -1618,9 +1893,9 @@ dependencies = [
16181893
16191894[[package]]
16201895name = "tracing-attributes"
1621−version = "0.1.15"
1896+version = "0.1.18"
16221897source = "registry+https://github.com/rust-lang/crates.io-index"
1623−checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
1898+checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
16241899dependencies = [
16251900 "proc-macro2",
16261901 "quote",
@@ -1629,13 +1904,22 @@ dependencies = [
16291904
16301905[[package]]
16311906name = "tracing-core"
1632−version = "0.1.18"
1907+version = "0.1.21"
16331908source = "registry+https://github.com/rust-lang/crates.io-index"
1634−checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052"
1909+checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
16351910dependencies = [
16361911 "lazy_static",
16371912]
16381913
1914+[[package]]
1915+name = "treediff"
1916+version = "3.0.2"
1917+source = "registry+https://github.com/rust-lang/crates.io-index"
1918+checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff"
1919+dependencies = [
1920+ "serde_json",
1921+]
1922+
16391923[[package]]
16401924name = "try-lock"
16411925version = "0.2.3"
@@ -1644,18 +1928,15 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
16441928
16451929[[package]]
16461930name = "typenum"
1647−version = "1.13.0"
1931+version = "1.14.0"
16481932source = "registry+https://github.com/rust-lang/crates.io-index"
1649−checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
1933+checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
16501934
16511935[[package]]
16521936name = "unicode-bidi"
1653−version = "0.3.5"
1937+version = "0.3.7"
16541938source = "registry+https://github.com/rust-lang/crates.io-index"
1655−checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
1656−dependencies = [
1657− "matches",
1658−]
1939+checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
16591940
16601941[[package]]
16611942name = "unicode-normalization"
@@ -1668,15 +1949,15 @@ dependencies = [
16681949
16691950[[package]]
16701951name = "unicode-segmentation"
1671−version = "1.7.1"
1952+version = "1.8.0"
16721953source = "registry+https://github.com/rust-lang/crates.io-index"
1673−checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
1954+checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
16741955
16751956[[package]]
16761957name = "unicode-width"
1677−version = "0.1.8"
1958+version = "0.1.9"
16781959source = "registry+https://github.com/rust-lang/crates.io-index"
1679−checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
1960+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
16801961
16811962[[package]]
16821963name = "unicode-xid"
@@ -1732,21 +2013,19 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
17322013
17332014[[package]]
17342015name = "wasm-bindgen"
1735−version = "0.2.75"
2016+version = "0.2.78"
17362017source = "registry+https://github.com/rust-lang/crates.io-index"
1737−checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586"
2018+checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
17382019dependencies = [
17392020 "cfg-if",
1740− "serde",
1741− "serde_json",
17422021 "wasm-bindgen-macro",
17432022]
17442023
17452024[[package]]
17462025name = "wasm-bindgen-backend"
1747−version = "0.2.75"
2026+version = "0.2.78"
17482027source = "registry+https://github.com/rust-lang/crates.io-index"
1749−checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f"
2028+checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
17502029dependencies = [
17512030 "bumpalo",
17522031 "lazy_static",
@@ -1759,9 +2038,9 @@ dependencies = [
17592038
17602039[[package]]
17612040name = "wasm-bindgen-futures"
1762−version = "0.4.25"
2041+version = "0.4.28"
17632042source = "registry+https://github.com/rust-lang/crates.io-index"
1764−checksum = "16646b21c3add8e13fdb8f20172f8a28c3dbf62f45406bcff0233188226cfe0c"
2043+checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
17652044dependencies = [
17662045 "cfg-if",
17672046 "js-sys",
@@ -1771,9 +2050,9 @@ dependencies = [
17712050
17722051[[package]]
17732052name = "wasm-bindgen-macro"
1774−version = "0.2.75"
2053+version = "0.2.78"
17752054source = "registry+https://github.com/rust-lang/crates.io-index"
1776−checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c"
2055+checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
17772056dependencies = [
17782057 "quote",
17792058 "wasm-bindgen-macro-support",
@@ -1781,9 +2060,9 @@ dependencies = [
17812060
17822061[[package]]
17832062name = "wasm-bindgen-macro-support"
1784−version = "0.2.75"
2063+version = "0.2.78"
17852064source = "registry+https://github.com/rust-lang/crates.io-index"
1786−checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f"
2065+checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
17872066dependencies = [
17882067 "proc-macro2",
17892068 "quote",
@@ -1794,15 +2073,15 @@ dependencies = [
17942073
17952074[[package]]
17962075name = "wasm-bindgen-shared"
1797−version = "0.2.75"
2076+version = "0.2.78"
17982077source = "registry+https://github.com/rust-lang/crates.io-index"
1799−checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2"
2078+checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
18002079
18012080[[package]]
18022081name = "web-sys"
1803−version = "0.3.52"
2082+version = "0.3.55"
18042083source = "registry+https://github.com/rust-lang/crates.io-index"
1805−checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696"
2084+checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
18062085dependencies = [
18072086 "js-sys",
18082087 "wasm-bindgen",
@@ -1857,6 +2136,12 @@ dependencies = [
18572136 "libc",
18582137]
18592138
2139+[[package]]
2140+name = "xml-rs"
2141+version = "0.8.4"
2142+source = "registry+https://github.com/rust-lang/crates.io-index"
2143+checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
2144+
18602145[[package]]
18612146name = "yaml-rust"
18622147version = "0.4.5"
@@ -1865,3 +2150,9 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
18652150dependencies = [
18662151 "linked-hash-map",
18672152]
2153+
2154+[[package]]
2155+name = "zeroize"
2156+version = "1.4.2"
2157+source = "registry+https://github.com/rust-lang/crates.io-index"
2158+checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970"
helm-control-scripts/Cargo.toml+6−2
@@ -1,6 +1,6 @@
11[package]
22name = "helm-control-scripts"
3−version = "0.2.0"
3+version = "0.2.3"
44authors = ["Aleksei Sizov <a.sizov@typeable.io>"]
55edition = "2018"
66
@@ -15,11 +15,15 @@ serde_json = "1.0"
1515serde_yaml = "0.8"
1616envy = "0.4"
1717yaml-rust = "0.4"
18−kube = "0.58.1"
18+kube = { version = "0.58.1", features = ["derive", "jsonpatch"]}
1919k8s-openapi = { version = "0.12.0", features = ["v1_19"]}
2020tokio = { version = "1.7.0", features = ["full"] }
2121dkregistry = { git = "https://github.com/camallo/dkregistry-rs", rev = "854d0da53bef5dd85b5e901123e85d43af97c74e"} # Version in crates uses old tokio
2222regex = "1"
23+rusoto_core = "0.47.0"
24+rusoto_ecr = "0.47.0"
25+schemars = "0.8.6"
26+json-patch = "0.2.6"
2327
2428[[bin]]
2529name = "create"
helm-control-scripts/README.md+20
@@ -41,6 +41,26 @@ Also, several environment variables are used to parametrize the default behavior
4141
4242These parameters, if set up in the `DEFAULTS` variable, will be passed to every deployment unless overridden in the "deployment overrides" section of an Octopod deployment configuration.
4343
44+### Authorization notes
45+
46+Most of the control scripts require that credentials for various services be passed to them to function properly. Some credentials, like the Helm ones, are quite obvious, but others are not.
47+
48+`config_check`, in addition to the Helm credentials, requires access to your docker registry to check the availability of the docker image tags.
49+
50+Right now only plain docker registries and ECR are supported. ECR clients need to have valid AWS credentials set via [environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html). You can provide any environment variables to Octopod by adding them to [env map](../charts/octopod/values.yaml#L86) in the Octopod helm chart.
51+
52+Note that if you are using EKS you don't need to add any additional configuration.
53+
54+Private registries, like Harbor with authorization haven't been tested with this set of scripts. If you are using a private registry and have troubles with the `config_check` script, please file an issue.
55+
56+#### Note about CRD support
57+
58+Right now these control scripts support `Posgresql` CRD from [postgres operator](https://github.com/zalando/postgres-operator) and `Kafka` CRD from [Strimzi](https://strimzi.io/). Explicit support is needed for the proper scaling of the resources, created by those CRDs. For example: just scaling stateful sets created by Kafka CRD won't work because the controller will keep scaling them back again.
59+
60+Explicit support for every CRD is necessary because the configurations are not standardized, and every CRD is implemented slightly differently.
61+
62+We'll keep supporting those CRDs, because we're using them in-house. If you want to support another CRD, you can file an issue or even a PR.
63+
4464<br />
4565
4666<p align="center">
helm-control-scripts/src/archive.rs+3−3
@@ -33,11 +33,11 @@ fn main() {
3333 info!("Generated Helm args: {:?}", &helm_template.args());
3434 match helm_template.run_stdout() {
3535 Ok(status) => {
36− let (deployments, statefulsets, _ingresses, _old_ingresses) = match parse_to_k8s(status) {
37− Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses),
36+ let (deployments, statefulsets, _ingresses, _old_ingresses, postgresqls, kafkas) = match parse_to_k8s(status) {
37+ Ok((deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas)) => (deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas),
3838 Err(err) => panic!("{}", err)
3939 };
40− match scale(deployments, statefulsets, namespace, 0) {
40+ match scale(deployments, statefulsets, postgresqls, kafkas, namespace, 0) {
4141 Ok(_status) => info!("Success!"),
4242 Err(status) => {
4343 error!("Error checking statuses");
helm-control-scripts/src/archive_check.rs+2−2
@@ -33,8 +33,8 @@ fn main() {
3333 info!("Generated Helm args: {:?}", &helm_template.args());
3434 match helm_template.run_stdout() {
3535 Ok(status) => {
36− let (deployments, statefulsets, _ingresses, _old_ingresses) = match parse_to_k8s(status) {
37− Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses),
36+ let (deployments, statefulsets, _ingresses, _old_ingresses, _postgresqls, _kafkas) = match parse_to_k8s(status) {
37+ Ok((deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas)) => (deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas),
3838 Err(err) => panic!("{}", err)
3939 };
4040 match check_all(deployments, statefulsets, namespace) {
helm-control-scripts/src/check.rs+2−2
@@ -33,8 +33,8 @@ fn main() {
3333 info!("Generated Helm args: {:?}", &helm_template.args());
3434 match helm_template.run_stdout() {
3535 Ok(status) => {
36− let (deployments, statefulsets, _ingresses, _old_ingresses) = match parse_to_k8s(status) {
37− Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses),
36+ let (deployments, statefulsets, _ingresses, _old_ingresses, _postgresqls, _kafkas) = match parse_to_k8s(status) {
37+ Ok((deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas)) => (deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas),
3838 Err(err) => panic!("{}", err)
3939 };
4040 match check_all(deployments, statefulsets, namespace) {
helm-control-scripts/src/cleanup.rs+2−13
@@ -42,8 +42,8 @@ fn main() {
4242 info!("Generated Helm args: {:?}", &helm_cmd.args());
4343 match helm_template.run_stdout() {
4444 Ok(status) => {
45− let (_deployments, _statefulsets, ingresses, old_ingresses) = match parse_to_k8s(status) {
46− Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses),
45+ let (_deployments, _statefulsets, ingresses, old_ingresses, _postgresqls, _kafkas) = match parse_to_k8s(status) {
46+ Ok((deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas)) => (deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas),
4747 Err(err) => panic!("{}", err)
4848 };
4949 match helm_cmd.run() {
@@ -55,17 +55,6 @@ fn main() {
5555 panic!("{:?}", err);
5656 }
5757 }
58− match ingresses_to_secrets(ingresses, old_ingresses) {
59− Some(secrets) => {
60− for secret in secrets {
61− match delete_secret(&namespace, &secret) {
62− Ok(_status) => info!("Successfully deleted secret {}", &secret),
63− Err(error) => error!("Can't delete secret {}\n {}", &secret, error)
64− }
65− }
66− },
67− None => info!("No secrets to delete")
68− }
6958 info!("Success!");
7059 },
7160 Err(status) => {
helm-control-scripts/src/config_check.rs+9−4
@@ -30,11 +30,15 @@ fn main() {
3030 deployment_parameters: deployment_parameters,
3131 overrides: overrides,
3232 };
33+ info!("Generated Helm args: {:?}", &helm_template.args());
3334 match helm_template.run_stdout() {
3435 Ok(status) => {
35− let (deployments, statefulsets, _ingresses, _old_ingresses) = match parse_to_k8s(status) {
36− Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses),
37− Err(err) => panic!("{}", err)
36+ let (deployments, statefulsets, _ingresses, _old_ingresses, _postgresqls, _kafkas) = match parse_to_k8s(status) {
37+ Ok((deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas)) => (deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas),
38+ Err(err) => {
39+ println!("Something went wrong: {}", err);
40+ panic!("{}", err);
41+ }
3842 };
3943 match deployments_statefulsets_to_images(deployments, statefulsets) {
4044 Some(images) => {
@@ -52,7 +56,8 @@ fn main() {
5256 }
5357 }
5458 }
55− Err(status) => {
59+ Err((status, err)) => {
60+ println!("{:#}", err);
5661 error!("Error during helm execution");
5762 panic!("{:?}", status);
5863 }
helm-control-scripts/src/info.rs+2−2
@@ -33,8 +33,8 @@ fn main() {
3333 };
3434 match helm_template.run_stdout() {
3535 Ok(status) => {
36− let (_deployments, _statefulsets, ingresses, old_ingresses) = match parse_to_k8s(status) {
37− Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses),
36+ let (_deployments, _statefulsets, ingresses, old_ingresses, _postgresqls, _kafkas) = match parse_to_k8s(status) {
37+ Ok((deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas)) => (deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas),
3838 Err(err) => panic!("{}", err)
3939 };
4040 print!("{}", print_kv(ingresses_to_hosts(ingresses, old_ingresses, default_name)));
helm-control-scripts/src/lib.rs+183−22
@@ -17,15 +17,19 @@ pub mod lib {
1717 core::v1::{PersistentVolumeClaim, Secret}
1818 };
1919 pub use kube::{
20− api::{Api, ListParams, ResourceExt, Patch, PatchParams, DeleteParams},
21− Client,
20+ api::{Api, ListParams, ResourceExt, Patch, PatchParams, DeleteParams, PostParams},
21+ Client,CustomResource
2222 };
2323 pub use dkregistry::{
2424 v2::Client as RegClient,
2525 errors::Error as RegError,
2626 };
2727 pub use regex::Regex;
28−
28+ pub use rusoto_core::{Region, RusotoError};
29+ pub use rusoto_ecr::{Ecr, EcrClient, DescribeImagesRequest, ImageIdentifier, DescribeImagesError};
30+ pub use schemars::JsonSchema;
31+ pub use tokio::task;
32+
2933 #[derive(Debug,StructOpt)]
3034 #[structopt(rename_all = "kebab-case")]
3135 pub struct CliOpts {
@@ -250,7 +254,7 @@ pub mod lib {
250254 Err(helm.status)
251255 }
252256 }
253− pub fn run_stdout(&self) -> Result<String, ExitStatus> {
257+ pub fn run_stdout(&self) -> Result<String, (ExitStatus, String)> {
254258 let helm = Command::new(&self.name)
255259 .args(&self.args())
256260 .output()
@@ -258,9 +262,11 @@ pub mod lib {
258262 if helm.status.success() {
259263 Ok(String::from_utf8(helm.stdout).unwrap())
260264 } else {
261− info!("helm stdout:\n {}", String::from_utf8(helm.stdout).unwrap());
262− info!("helm stderr:\n {}", String::from_utf8(helm.stderr).unwrap());
263− Err(helm.status)
265+ let stdout = String::from_utf8(helm.stdout).unwrap();
266+ let stderr = String::from_utf8(helm.stderr).unwrap();
267+ info!("helm stdout:\n {}", stdout);
268+ info!("helm stderr:\n {}", stderr);
269+ Err((helm.status, stderr))
264270 }
265271 }
266272 }
@@ -273,13 +279,30 @@ pub mod lib {
273279 RepoList,
274280 ShowValues,
275281 }
276−
282+ #[derive(CustomResource, Debug, Serialize, Deserialize, Default, Clone, JsonSchema)]
283+ #[kube(group = "acid.zalan.do", version = "v1", kind = "Postgresql", namespaced)]
284+ pub struct PosgresqlSpec {
285+ numberOfInstances: i32
286+ }
287+ #[derive(CustomResource, Debug, Serialize, Deserialize, Default, Clone, JsonSchema)]
288+ #[kube(group = "kafka.strimzi.io", version = "v1beta2", kind = "Kafka", namespaced)]
289+ pub struct KafkaSpec {
290+ kafka: KafkaConfig,
291+ zookeeper: ZookeeperConfig
292+ }
293+ #[derive(Debug, Serialize, Deserialize, Default, Clone, JsonSchema)]
294+ struct KafkaConfig {
295+ replicas: i32
296+ }
297+ #[derive(Debug, Serialize, Deserialize, Default, Clone, JsonSchema)]
298+ struct ZookeeperConfig {
299+ replicas: i32
300+ }
277301 fn check_value(value: String) -> Result<String, String> {
278− let split_value = value.split('=').collect::<Vec<_>>();
279− if split_value.len() == 2 {
280− Ok(value)
281− } else {
282− Err(format!("Override value {} is malformed", value))
302+ let re = Regex::new(r"^([^=]*)=(.*)$").unwrap();
303+ match re.captures(&value) {
304+ Some(_) => return Ok(value),
305+ None => return Err(format!("Override value {} is malformed", value)),
283306 }
284307 }
285308 pub fn overrides(cli_opts: &CliOpts, envs: &EnvVars) -> Option<Vec<String>> {
@@ -308,12 +331,14 @@ pub mod lib {
308331 None => panic!("No name argument provided")
309332 }
310333 }
311− pub fn parse_to_k8s(yaml: String) -> Result<(Vec<Deployment>, Vec<StatefulSet>, Vec<Ingress>, Vec<OldIngress>), serde_yaml::Error> {
334+ pub fn parse_to_k8s(yaml: String) -> Result<(Vec<Deployment>, Vec<StatefulSet>, Vec<Ingress>, Vec<OldIngress>, Vec<Postgresql>, Vec<Kafka>), serde_yaml::Error> {
312335 let docs = YamlLoader::load_from_str(&yaml).unwrap();
313336 let mut deployments: Vec<Deployment> = Vec::new();
314337 let mut statefulsets: Vec<StatefulSet> = Vec::new();
315338 let mut ingresses: Vec<Ingress> = Vec::new();
316339 let mut old_ingresses: Vec<OldIngress> = Vec::new();
340+ let mut postgresqls: Vec<Postgresql> = Vec::new();
341+ let mut kafkas: Vec<Kafka> = Vec::new();
317342 for doc in docs {
318343 match doc["kind"].as_str().unwrap() {
319344 "Deployment" => {
@@ -342,13 +367,27 @@ pub mod lib {
342367 ingresses.push(ingress);
343368 }
344369 },
370+ "postgresql" => {
371+ let mut postgresql_str = String::new();
372+ let mut emitter = YamlEmitter::new(&mut postgresql_str);
373+ emitter.dump(&doc).unwrap();
374+ let postgresql: Postgresql = serde_yaml::from_str(&postgresql_str)?;
375+ postgresqls.push(postgresql);
376+ },
377+ "Kafka" => {
378+ let mut kafka_str = String::new();
379+ let mut emitter = YamlEmitter::new(&mut kafka_str);
380+ emitter.dump(&doc).unwrap();
381+ let kafka: Kafka = serde_yaml::from_str(&kafka_str)?;
382+ kafkas.push(kafka);
383+ },
345384 _ => continue,
346385 }
347386 }
348387 if deployments.is_empty() && statefulsets.is_empty() {
349388 panic!("Got no deployment or stateful sets. Nothing to check");
350389 }
351− return Ok((deployments, statefulsets, ingresses, old_ingresses));
390+ return Ok((deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas));
352391 }
353392
354393 #[derive(Debug)]
@@ -610,6 +649,30 @@ pub mod lib {
610649 None => panic!("Image not found: {} {} {}", registry, repository, tag),
611650 }
612651 }
652+ #[tokio::main]
653+ pub async fn check_ecr_image(registry: &str, repository: &str, tag: &str) -> Result<(), DescribeImagesError>{
654+ let re = Regex::new(r"^[0-9]*.dkr.ecr.(.*).amazonaws.com$").unwrap();
655+ let captures = re.captures(&registry).unwrap();
656+ let region_string = String::from(&captures[1]);
657+ let region: Region = region_string.parse().unwrap();
658+ let client = EcrClient::new(region);
659+ let image_id = ImageIdentifier {
660+ image_tag: Some(String::from(tag)),
661+ image_digest: None,
662+ };
663+ let describe_request = DescribeImagesRequest {
664+ repository_name: String::from(repository),
665+ image_ids: Some(vec![image_id]),
666+ filter: None,
667+ max_results: None,
668+ next_token: None,
669+ registry_id: None,
670+ };
671+ match client.describe_images(describe_request).await {
672+ Ok(_) => return Ok(()),
673+ Err(err) => panic!("Image not found in ECR: {} {} {}\n {}", registry, repository, tag, err),
674+ }
675+ }
613676 fn parse_image_name(image_name: &str) -> (String, String, String) {
614677 let mut registry = String::new();
615678 let mut repository = String::new();
@@ -620,8 +683,8 @@ pub mod lib {
620683 registry = String::from("registry-1.docker.io");
621684 repository = format!("library/{}", &captures[1])
622685 } else if &captures[2] == "" {
623− registry = String::from("registry-1.docker.io");
624− repository = format!("{}/{}", &captures[1], &captures[3]);
686+ registry = String::from(&captures[1]);
687+ repository = String::from(&captures[3]);
625688 } else {
626689 if &captures[1] == "docker.io" {
627690 registry = String::from("registry-1.docker.io");
@@ -633,12 +696,19 @@ pub mod lib {
633696 info!("Checking image: {} {} {}", registry, repository, tag);
634697 (registry, repository, tag)
635698 }
636− pub fn check_images(images: Vec<String>) -> Result<(), RegError> {
699+ pub fn check_images(images: Vec<String>) -> Result<(), Box<dyn error::Error>> {
637700 for image in images {
638701 let (registry, repository, tag) = parse_image_name(&image);
639− match check_image(&registry, &repository, &tag) {
640− Ok(_) => (),
641− Err(err) => return Err(err),
702+ if registry.contains("dkr.ecr") {
703+ match check_ecr_image(&registry, &repository, &tag) {
704+ Ok(_) => (),
705+ Err(err) => return Err(Box::new(err)),
706+ }
707+ } else {
708+ match check_image(&registry, &repository, &tag) {
709+ Ok(_) => (),
710+ Err(err) => return Err(Box::new(err)),
711+ }
642712 }
643713 }
644714 Ok(())
@@ -663,6 +733,7 @@ pub mod lib {
663733 match helm_repo_add.run() {
664734 Ok(_status) => info!("Repo add success!"),
665735 Err(status) => {
736+ println!("Couldn't add repo.");
666737 error!("Error during helm execution");
667738 panic!("{:?}", status);
668739 }
@@ -670,6 +741,7 @@ pub mod lib {
670741 match helm_repo_update.run() {
671742 Ok(_status) => info!("Repo update success!"),
672743 Err(status) => {
744+ println!("Couldn't update repo.");
673745 error!("Error during helm execution");
674746 panic!("{:?}", status);
675747 }
@@ -804,7 +876,70 @@ pub mod lib {
804876 api.patch(&name, &patch_params, &patch).await?;
805877 Ok(())
806878 }
807− pub fn scale(deployments: Vec<Deployment>, statefulsets: Vec<StatefulSet>, namespace: String, replicas: i32) -> Result<(), Box<dyn error::Error>> {
879+ #[tokio::main]
880+ async fn scale_postgresql(namespace: &str, name: &str, replicas: i32) -> Result<(), kube::Error> {
881+ let client = Client::try_default().await?;
882+ let api: Api<Postgresql> = Api::namespaced(client, &namespace);
883+ let postgres = api.get(&name).await?;
884+ println!("{:#?}", postgres);
885+ let mut spec = postgres.spec;
886+ spec.numberOfInstances = replicas;
887+ let patched_postgres = Postgresql {
888+ api_version: postgres.api_version,
889+ kind: postgres.kind,
890+ metadata: postgres.metadata,
891+ spec: spec,
892+ };
893+ let patch = Patch::Merge(patched_postgres);
894+ let patch_params = PatchParams::apply("octopod");
895+ api.patch(&name, &patch_params, &patch).await?;
896+ Ok(())
897+ }
898+ #[tokio::main]
899+ async fn scale_kafka(namespace: &str, name: &str, replicas: i32) -> Result<(), kube::Error> {
900+ let client = Client::try_default().await?;
901+ let api: Api<Kafka> = Api::namespaced(client.clone(), &namespace);
902+ let kafka = api.get(&name).await?;
903+ let metadata = kafka.metadata;
904+ if replicas > 0 {
905+ if metadata.annotations.contains_key("strimzi.io/pause-reconciliation") {
906+ println!("deleting pause-reconciliation annotation");
907+ let remove_op = json_patch::RemoveOperation {
908+ path: String::from("/metadata/annotations/strimzi.io~1pause-reconciliation")
909+ };
910+ let patch_op = json_patch::PatchOperation::Remove(remove_op);
911+ let json_patch = json_patch::Patch(vec![patch_op]);
912+ let patch = Patch::Json::<()>(json_patch);
913+ let patch_params = PatchParams::apply("octopod");
914+ api.patch(&name, &patch_params, &patch).await?;
915+ }
916+ } else {
917+ if !metadata.annotations.contains_key("strimzi.io/pause-reconciliation") {
918+ println!("adding pause-reconciliation annotation");
919+ let add_op = json_patch::AddOperation {
920+ path: String::from("/metadata/annotations/strimzi.io~1pause-reconciliation"),
921+ value: serde_json::value::Value::String("true".to_string())
922+ };
923+ let patch_op = json_patch::PatchOperation::Add(add_op);
924+ let json_patch = json_patch::Patch(vec![patch_op]);
925+ let patch = Patch::Json::<()>(json_patch);
926+ let patch_params = PatchParams::apply("octopod");
927+ api.patch(&name, &patch_params, &patch).await?;
928+ }
929+ }
930+
931+ let kafka_sts: Api<StatefulSet> = Api::namespaced(client, &namespace);
932+ let sts_list = ListParams::default().labels(&format!("strimzi.io/cluster={}", name));
933+ for sts in kafka_sts.list(&sts_list).await? {
934+ let sts_namespace = String::from(namespace);
935+ let sts_replicas = replicas.clone();
936+ task::spawn_blocking(move || {
937+ scale_statefulset(&sts_namespace, &sts.name(), sts_replicas);
938+ }).await.expect("Failed to run thread")
939+ }
940+ Ok(())
941+ }
942+ pub fn scale(deployments: Vec<Deployment>, statefulsets: Vec<StatefulSet>, postgresqls: Vec<Postgresql>, kafkas: Vec<Kafka>, namespace: String, replicas: i32) -> Result<(), Box<dyn error::Error>> {
808943 if deployments.is_empty() {
809944 info!("No deployments to check");
810945 }else{
@@ -830,6 +965,32 @@ pub mod lib {
830965 }
831966 }
832967 }
968+
969+ if postgresqls.is_empty() {
970+ info!("No postgresqls to check");
971+ }else{
972+ for postgres in postgresqls {
973+ match scale_postgresql(&namespace, &postgres.name(), replicas) {
974+ Ok(status) => status,
975+ Err(err) => {
976+ return Err(Box::new(err));
977+ }
978+ }
979+ }
980+ }
981+
982+ if kafkas.is_empty() {
983+ info!("No kafkas to check");
984+ }else{
985+ for kafka in kafkas {
986+ match scale_kafka(&namespace, &kafka.name(), replicas) {
987+ Ok(status) => status,
988+ Err(err) => {
989+ return Err(Box::new(err));
990+ }
991+ }
992+ }
993+ }
833994 Ok(())
834995 }
835996 pub fn ingresses_to_secrets(new_ingresses: Vec<Ingress>, old_ingresses: Vec<OldIngress>) -> Option<Vec<String>> {
helm-control-scripts/src/unarchive.rs+3−3
@@ -33,11 +33,11 @@ fn main() {
3333 info!("Generated Helm args: {:?}", &helm_template.args());
3434 match helm_template.run_stdout() {
3535 Ok(status) => {
36− let (deployments, statefulsets, _ingresses, _old_ingresses) = match parse_to_k8s(status) {
37− Ok((deployments, statefulsets, ingresses, old_ingresses)) => (deployments, statefulsets, ingresses, old_ingresses),
36+ let (deployments, statefulsets, _ingresses, _old_ingresses, postgresqls, kafkas) = match parse_to_k8s(status) {
37+ Ok((deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas)) => (deployments, statefulsets, ingresses, old_ingresses, postgresqls, kafkas),
3838 Err(err) => panic!("{}", err)
3939 };
40− match scale(deployments, statefulsets, namespace, 1) {
40+ match scale(deployments, statefulsets, postgresqls, kafkas, namespace, 1) {
4141 Ok(_status) => info!("Success!"),
4242 Err(status) => {
4343 error!("Error checking statuses");
img/release_workflow.pngnew file

Binary file not shown.

nix/default.nix+1−1
@@ -1,5 +1,5 @@
11{ sources ? import ./sources.nix
2−, octopod-css ? import ../octopod-css { inherit pkgsSrc; }
2+, octopod-css ? import ../octopod-css { inherit pkgsSrc system; }
33, migrations ? ../migrations
44, system ? builtins.currentSystem
55, hsPkgs ? import ./.. { inherit system prod; }
nix/sources.json+11−11
@@ -1,14 +1,14 @@
11{
22 "haskellNix": {
3− "branch": "master",
3+ "branch": "iko/ghcjs-patch",
44 "description": "Alternative Haskell Infrastructure for Nixpkgs",
55 "homepage": "https://input-output-hk.github.io/haskell.nix",
6− "owner": "input-output-hk",
6+ "owner": "ilyakooo0",
77 "repo": "haskell.nix",
8− "rev": "62f8632b32933cbd2ccf2927ebb449de943b774d",
9− "sha256": "1pkh10lxry7czj3m6xnw6lvd69dspnsasm9ndiigrx7sfjzb4icg",
8+ "rev": "8cdc0f62f31be4f55bc7db88bdc0cec44bfd8ab9",
9+ "sha256": "0l0xg0s2vn0lpc2m1gavrlzk691sgifjf6l6k5sfsnhw2xq77g48",
1010 "type": "tarball",
11− "url": "https://github.com/input-output-hk/haskell.nix/archive/62f8632b32933cbd2ccf2927ebb449de943b774d.tar.gz",
11+ "url": "https://github.com/ilyakooo0/haskell.nix/archive/8cdc0f62f31be4f55bc7db88bdc0cec44bfd8ab9.tar.gz",
1212 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
1313 },
1414 "niv": {
@@ -17,10 +17,10 @@
1717 "homepage": "https://github.com/nmattia/niv",
1818 "owner": "nmattia",
1919 "repo": "niv",
20− "rev": "e0ca65c81a2d7a4d82a189f1e23a48d59ad42070",
21− "sha256": "1pq9nh1d8nn3xvbdny8fafzw87mj7gsmp6pxkdl65w2g18rmcmzx",
20+ "rev": "5830a4dd348d77e39a0f3c4c762ff2663b602d4c",
21+ "sha256": "1d3lsrqvci4qz2hwjrcnd8h5vfkg8aypq3sjd4g3izbc8frwz5sm",
2222 "type": "tarball",
23− "url": "https://github.com/nmattia/niv/archive/e0ca65c81a2d7a4d82a189f1e23a48d59ad42070.tar.gz",
23+ "url": "https://github.com/nmattia/niv/archive/5830a4dd348d77e39a0f3c4c762ff2663b602d4c.tar.gz",
2424 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
2525 },
2626 "nix-filter": {
@@ -41,10 +41,10 @@
4141 "homepage": "",
4242 "owner": "NixOS",
4343 "repo": "nixpkgs",
44− "rev": "da497034d8429b733111cda03b4790743e2272db",
45− "sha256": "0n6p3kd9bbqpl7byyc2ac316qgj465wq4sj0cvn9minxz5p3vqmd",
44+ "rev": "bb9bd465b625bfc971908c5d3d84ce517e1c0691",
45+ "sha256": "1q558hjfxhbcj6n6kbahswvii95kz6gs5bbzh7ky86cmc2b0l38x",
4646 "type": "tarball",
47− "url": "https://github.com/NixOS/nixpkgs/archive/da497034d8429b733111cda03b4790743e2272db.tar.gz",
47+ "url": "https://github.com/NixOS/nixpkgs/archive/bb9bd465b625bfc971908c5d3d84ce517e1c0691.tar.gz",
4848 "url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
4949 }
5050}
octo-cli/octo-cli.cabal+1−1
@@ -59,7 +59,7 @@ executable octo
5959 transformers,
6060 table-layout ^>= 0.9.0.0,
6161 servant-auth,
62− ordered-containers,
62+ http-types,
6363 hs-source-dirs: src
6464 default-language: Haskell2010
6565 ghc-options:
octo-cli/src/Octopod/CLI.hs+102−74
@@ -2,7 +2,7 @@
22
33module Octopod.CLI (runOcto) where
44
5−import Common.Types
5+import Common.Types hiding (stderr)
66import Common.Utils (dfiName)
77import Control.Exception
88import Control.Lens hiding (List)
@@ -10,11 +10,13 @@ import Control.Monad
1010import Control.Monad.IO.Class
1111import Control.Monad.Trans.Reader (ReaderT, ask, runReaderT)
1212import Data.Aeson (decode)
13+import qualified Data.ByteString.Char8 as BSC
1314import qualified Data.ByteString.Lazy.Char8 as LBSC
1415import Data.Coerce
16+import qualified Data.ConfigTree as CT
17+import Data.Foldable
1518import Data.Generics.Labels ()
1619import Data.Generics.Product
17−import qualified Data.Map.Ordered.Strict as OM
1820import Data.Maybe
1921import Data.Proxy
2022import Data.Text (Text)
@@ -24,26 +26,15 @@ import Data.Text.Lens
2426import Data.Time
2527import GHC.IO.Encoding
2628import Network.HTTP.Client.TLS
29+import Network.HTTP.Types.Status
2730import Octopod.CLI.Args
2831import Octopod.PowerAPI
2932import Octopod.PowerAPI.Auth.Client
3033import Servant.API
3134import Servant.Client
32− ( BaseUrl (..),
33− ClientEnv,
34− ClientError,
35− ClientM,
36− client,
37− mkClientEnv,
38− runClientM,
39− )
40−import Servant.Client.Core
41− ( ClientError (FailureResponse),
42− ResponseF (..),
43− parseBaseUrl,
44− )
4535import System.Environment (lookupEnv)
4636import System.Exit
37+import System.IO
4738import Text.Layout.Table
4839import Text.Layout.Table.Extras ()
4940import Prelude as P
@@ -60,8 +51,8 @@ runOcto = do
6051 >>= maybe (die "OCTO_AUTHORIZATION_HEADER is not set") pure
6152 args <- parseArgs
6253 env <- getBaseUrl
63− manager <- newTlsManager
64− let clientEnv = mkClientEnv manager env
54+ mngr <- newTlsManager
55+ let clientEnv = mkClientEnv mngr env
6556 flip runReaderT clientEnv $
6657 case args of
6758 Create tName tSetAp tSetDep -> do
@@ -82,6 +73,8 @@ runOcto = do
8273 Restore tName ->
8374 handleRestore auth . coerce $ tName
8475 GetActionLogs aId l -> handleGetActionInfo auth aId l
76+ Delete tName ->
77+ handleDelete auth . coerce $ tName
8578
8679-- | Returns BaseUrl from 'OCTOPOD_URL' environment variable
8780-- or exit with exit_code=1.
@@ -131,24 +124,10 @@ handleUpdate ::
131124handleUpdate auth dName dNewAppOvs removedAppOvs dNewDepOvs removedDepOvs = do
132125 clientEnv <- ask
133126 dep <- runClientM' (_fullInfoH auth dName) clientEnv
134− let removeAll :: Ord k => [k] -> OM.OMap k v -> Either k (OM.OMap k v)
135− removeAll [] m = Right m
136− removeAll (k : kk) m =
137− if k `OM.member` m
138− then removeAll kk $ OM.delete k m
139− else Left k
140− removeAllM :: MonadIO m => [Text] -> Overrides l -> m (Overrides l)
141− removeAllM ks (Overrides m) =
142− either
143− (\k -> dieT $ "Override " <> k <> " not present in deployment.")
144− (pure . Overrides)
145− $ removeAll ks m
146− appOverrides' <-
147− fmap (<> dNewAppOvs) $
148− removeAllM removedAppOvs $ dep ^. #deployment . #appOverrides
149− deploymentOverrides' <-
150− fmap (<> dNewDepOvs) $
151− removeAllM removedDepOvs $ dep ^. #deployment . #deploymentOverrides
127+ let removeAll :: [Text] -> Overrides l -> (Overrides l)
128+ removeAll ks (Overrides (CT.toFlatList -> m)) = Overrides $ CT.fromFlatList $ filter (flip elem ks . fst) m
129+ appOverrides' = dNewAppOvs <> (removeAll removedAppOvs $ dep ^. #deployment . #appOverrides)
130+ deploymentOverrides' = dNewDepOvs <> (removeAll removedDepOvs $ dep ^. #deployment . #deploymentOverrides)
152131 liftIO $ do
153132 let dUpdate =
154133 DeploymentUpdate
@@ -170,9 +149,9 @@ handleInfo auth dName = do
170149 case res of
171150 Right (i : _) -> printInfo i
172151 Right [] -> putStrLn notFoundMsg
173− Left err -> putStrLn $ "request failed, reason: " ++ show err
152+ other -> handleResponse (const $ pure ()) other
174153 where
175− notFoundMsg = "deployment " ++ T.unpack (coerce dName) ++ " not found"
154+ notFoundMsg = "Deployment " ++ T.unpack (coerce dName) ++ " not found"
176155
177156-- | Handles the 'cleanup' subcommand.
178157handleCleanup :: AuthContext AuthHeaderAuth -> DeploymentName -> ReaderT ClientEnv IO ()
@@ -181,6 +160,13 @@ handleCleanup auth dName = do
181160 liftIO $
182161 handleResponse (const $ pure ()) =<< runClientM (cleanupH auth dName) clientEnv
183162
163+-- | Handles the 'delete' subcommand.
164+handleDelete :: AuthContext AuthHeaderAuth -> DeploymentName -> ReaderT ClientEnv IO ()
165+handleDelete auth dName = do
166+ clientEnv <- ask
167+ liftIO $
168+ handleResponse (const $ pure ()) =<< runClientM (deleteH auth dName) clientEnv
169+
184170-- | Handles the 'restore' subcommand.
185171handleRestore :: AuthContext AuthHeaderAuth -> DeploymentName -> ReaderT ClientEnv IO ()
186172handleRestore auth dName = do
@@ -193,16 +179,14 @@ handleGetActionInfo :: AuthContext AuthHeaderAuth -> ActionId -> LogOutput -> Re
193179handleGetActionInfo auth aId l = do
194180 clientEnv <- ask
195181 liftIO $ do
196− runClientM (getActionInfoH auth aId) clientEnv >>= \case
197− Left err -> print err
198− Right x -> case l of
199− Out -> T.putStrLn . unStdout $ x ^. #stdout
200− Err -> T.putStrLn . unStderr $ x ^. #stderr
201− ErrOut -> do
202− T.putStrLn "\t\tstdout:\n"
203− T.putStrLn . unStdout $ x ^. #stdout
204− T.putStrLn "\t\tstderr:\n"
205− T.putStrLn . unStderr $ x ^. #stderr
182+ runClientM (getActionInfoH auth aId) clientEnv >>= handleResponse \x -> case l of
183+ Out -> T.putStrLn . unStdout $ x ^. #stdout
184+ Err -> T.putStrLn . unStderr $ x ^. #stderr
185+ ErrOut -> do
186+ T.putStrLn "\t\tstdout:\n"
187+ T.putStrLn . unStdout $ x ^. #stdout
188+ T.putStrLn "\t\tstderr:\n"
189+ T.putStrLn . unStderr $ x ^. #stderr
206190
207191listH :: AuthContext AuthHeaderAuth -> ClientM [DeploymentFullInfo]
208192createH :: AuthContext AuthHeaderAuth -> Deployment -> ClientM CommandResponse
@@ -214,6 +198,7 @@ _statusH :: AuthContext AuthHeaderAuth -> DeploymentName -> ClientM CurrentDeplo
214198cleanupH :: AuthContext AuthHeaderAuth -> DeploymentName -> ClientM CommandResponse
215199restoreH :: AuthContext AuthHeaderAuth -> DeploymentName -> ClientM CommandResponse
216200getActionInfoH :: AuthContext AuthHeaderAuth -> ActionId -> ClientM ActionInfo
201+deleteH :: AuthContext AuthHeaderAuth -> DeploymentName -> ClientM CommandResponse
217202( listH
218203 :<|> createH
219204 :<|> archiveH
@@ -223,6 +208,7 @@ getActionInfoH :: AuthContext AuthHeaderAuth -> ActionId -> ClientM ActionInfo
223208 :<|> _statusH
224209 :<|> cleanupH
225210 :<|> restoreH
211+ :<|> deleteH
226212 )
227213 :<|> getActionInfoH = pushArrowIntoServantAlt $ client (Proxy @PowerAPI)
228214
@@ -246,32 +232,57 @@ instance CanPushArrow (x -> y) ~ 'False => PushArrowIntoServantAlt' (x -> y) (x
246232-- | Handles response from Octopod Server.
247233handleResponse :: (a -> IO ()) -> Either ClientError a -> IO ()
248234handleResponse f (Right result) = f result
249−handleResponse _ (Left (FailureResponse _req res)) =
250− T.putStrLn . decodeError $ responseBody res
251−handleResponse _ (Left err) =
252− T.putStrLn $ "command failed due to unknown reason: " <> T.pack (show err)
235+handleResponse _ (Left cErr) = do
236+ case cErr of
237+ (ConnectionError exc) -> do
238+ hPutStrLn stderr "Connection error"
239+ hPutStrLn stderr $ displayException exc
240+ (FailureResponse _ res) -> do
241+ showResponseStatus res
242+ case decodeError $ responseBody res of
243+ Just errT -> do T.hPutStrLn stderr errT
244+ Nothing | not . LBSC.null $ responseBody res -> do
245+ LBSC.hPutStrLn stderr $ responseBody res
246+ pure ()
247+ Nothing -> pure ()
248+ (InvalidContentTypeHeader res) -> do
249+ showResponseStatus res
250+ hPutStrLn stderr "Invalid content type header received"
251+ (UnsupportedContentType ctype res) -> do
252+ showResponseStatus res
253+ hPutStrLn stderr $ "Unsupported content type header received: " <> show ctype
254+ (DecodeFailure err res) -> do
255+ showResponseStatus res
256+ hPutStrLn stderr $ "The response body could not be decoded: "
257+ T.hPutStrLn stderr err
258+ exitFailure
259+
260+showResponseStatus :: Response -> IO ()
261+showResponseStatus Response {responseStatusCode = (Status code body)} = do
262+ hPutStr stderr $ show code <> " "
263+ BSC.hPutStrLn stderr body
264+ pure ()
253265
254−decodeError :: LBSC.ByteString -> Text
266+decodeError :: LBSC.ByteString -> Maybe Text
255267decodeError body =
256− case decode body of
257− Just (ValidationError nameErrors) ->
268+ decode body <&> \case
269+ (ValidationError nameErrors) ->
258270 T.concat ((<> "\n") <$> nameErrors)
259− Just (AppError errorMsg) -> errorMsg
260− Just Success -> "ok"
261− _ -> "error: " <> (T.pack . LBSC.unpack $ body)
271+ (AppError errorMsg) -> errorMsg
272+ Success -> "ok"
262273
263274-- | Pretty-prints the 'info' subcommand result.
264275printInfo :: DeploymentInfo -> IO ()
265276printInfo (DeploymentInfo (Deployment _ dAppOvs dStOvs) (DeploymentMetadata dMeta) dLogs) = do
266277 T.putStrLn "Current settings:"
267− T.putStrLn $
268− "application config: "
269− <> formatOverrides dAppOvs
270− T.putStrLn $
271− "deployment config: "
272− <> formatOverrides dStOvs
273− T.putStrLn $ "metadata: "
274− forM_ dMeta $ \m ->
278+ T.putStrLn "Application config:"
279+ putStrLn $ unlines $ formatOverrides False dAppOvs
280+ T.putStrLn ""
281+ T.putStrLn "Deployment config: "
282+ putStrLn $ unlines $ formatOverrides False dStOvs
283+ T.putStrLn ""
284+ T.putStrLn $ "Metadata: "
285+ for_ dMeta $ \m ->
275286 T.putStrLn $
276287 " " <> m ^. #name <> ": " <> m ^. #link
277288 T.putStrLn ""
@@ -282,15 +293,14 @@ ppDeploymentLogs :: [DeploymentLog] -> IO ()
282293ppDeploymentLogs ds =
283294 putStrLn
284295 . tableString
285− [ column expand right noAlign def
296+ [ column expand center noAlign def
297+ , column expand center noAlign def
286298 , column expand center noAlign def
287299 , column expand center noAlign def
288300 , column expand center noAlign def
289− , column expand left (charAlign '=') def
290− , column expand left (charAlign '=') def
291301 , column expand center noAlign def
292302 ]
293− unicodeBoldHeaderS
303+ unicodeDoubleFrameS
294304 ( titlesH
295305 [ "Created at"
296306 , "Action id"
@@ -303,17 +313,35 @@ ppDeploymentLogs ds =
303313 $ ppDeploymentLogRow <$> ds
304314
305315-- | Pretty-prints the deployment log.
306−ppDeploymentLogRow :: DeploymentLog -> RowGroup Text
316+ppDeploymentLogRow :: DeploymentLog -> RowGroup String
307317ppDeploymentLogRow dLog =
308318 colsAllG
309319 top
310320 [
311− [ T.pack . formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S")) $
321+ [ formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S")) $
312322 dLog ^. field @"createdAt"
313323 ]
314324 , [dLog ^. field @"actionId" . to unActionId . re _Show . packed]
315− , [dLog ^. field @"action" . to actionToText]
316− , dLog ^. field @"deploymentAppOverrides" . to formatOverrides'
317− , dLog ^. field @"deploymentDepOverrides" . to formatOverrides'
325+ , [dLog ^. field @"action" . to (T.unpack . actionToText)]
326+ , dLog ^. field @"deploymentAppOverrides" . to (stripBorder . formatOverrides True)
327+ , dLog ^. field @"deploymentDepOverrides" . to (stripBorder . formatOverrides True)
318328 , [dLog ^. field @"exitCode" . re _Show . packed]
319329 ]
330+
331+stripBorder :: [String] -> [String]
332+stripBorder = fmap (init . tail) . init . tail
333+
334+formatOverrides :: Bool -> Overrides l -> [String]
335+formatOverrides splitlines (Overrides m) =
336+ tableLines
337+ [ column expand right noAlign def
338+ , column expand left noAlign def
339+ ]
340+ unicodeS
341+ def
342+ $ showOverride <$> (reverse . CT.toFlatList) m
343+ where
344+ showOverride (k, v) =
345+ colsAllG top $ [if splitlines then T.chunksOf 15 k else [k], showValue v]
346+ showValue (ValueAdded v) = if splitlines then T.chunksOf 25 v else [v]
347+ showValue ValueDeleted = ["<<REMOVED>>"]
octo-cli/src/Octopod/CLI/Args.hs+19−15
@@ -49,6 +49,9 @@ data Args
4949 name :: Text
5050 }
5151 | GetActionLogs ActionId LogOutput
52+ | Delete
53+ { name :: Text
54+ }
5255 deriving stock (Show)
5356
5457data LogOutput = Err | Out | ErrOut
@@ -65,7 +68,8 @@ readLogOutput _ = Nothing
6568
6669-- | Parses octo CLI subcommands and arguments.
6770parseArgs :: IO Args
68−parseArgs = execParser $ info (commandArgs <**> helper) fullDesc
71+parseArgs =
72+ customExecParser (prefs showHelpOnError) $ info (commandArgs <**> helper) fullDesc
6973
7074-- | Parses octo CLI subcommands.
7175commandArgs :: Parser Args
@@ -81,13 +85,14 @@ commandArgs =
8185 <> command
8286 "logs"
8387 (info actionLogsArgs (progDesc "get deployment logs of a given action"))
88+ <> command "delete" (info deleteArgs (progDesc "delete the deployment from Octopod. Does no cleanup."))
8489 )
8590
8691-- | Parses arguments of 'create' subcommand.
8792createArgs :: Parser Args
8893createArgs =
8994 Create
90− <$> strOption (long "name" <> short 'n' <> help "deployment name")
95+ <$> deploymentNameArgument
9196 <*> many
9297 ( strOption
9398 ( long "set-app-config"
@@ -110,15 +115,13 @@ listArgs =
110115
111116-- | Parses arguments of 'archive' subcommand.
112117archiveArgs :: Parser Args
113−archiveArgs =
114− Archive
115− <$> strOption (long "name" <> short 'n' <> help "deployment name")
118+archiveArgs = Archive <$> deploymentNameArgument
116119
117120-- | Parses arguments of 'update' subcommand.
118121updateArgs :: Parser Args
119122updateArgs =
120123 Update
121− <$> strOption (long "name" <> short 'n' <> help "deployment name")
124+ <$> deploymentNameArgument
122125 <*> many
123126 ( strOption
124127 ( long "set-app-config"
@@ -150,21 +153,19 @@ updateArgs =
150153
151154-- | Parses arguments of 'info' subcommand.
152155infoArgs :: Parser Args
153−infoArgs =
154− Info
155− <$> strOption (long "name" <> short 'n' <> help "deployment name")
156+infoArgs = Info <$> deploymentNameArgument
156157
157158-- | Parses arguments of 'cleanup' subcommand.
158159cleanupArgs :: Parser Args
159−cleanupArgs =
160− Cleanup
161− <$> strOption (long "name" <> short 'n' <> help "deployment name")
160+cleanupArgs = Cleanup <$> deploymentNameArgument
161+
162+-- | Parses arguments of 'delete' subcommand.
163+deleteArgs :: Parser Args
164+deleteArgs = Delete <$> deploymentNameArgument
162165
163166-- | Parses arguments of 'restore' subcommand.
164167restoreArgs :: Parser Args
165−restoreArgs =
166− Restore
167− <$> strOption (long "name" <> short 'n' <> help "deployment name")
168+restoreArgs = Restore <$> deploymentNameArgument
168169
169170actionLogsArgs :: Parser Args
170171actionLogsArgs =
@@ -178,3 +179,6 @@ actionLogsArgs =
178179 <> value ErrOut
179180 <> completeWith ["stdout", "stderr", "all"]
180181 )
182+
183+deploymentNameArgument :: Parser Text
184+deploymentNameArgument = strOption (long "name" <> short 'n' <> help "deployment name")
octopod-api/src/Octopod/API/WebSocket.hs+1
@@ -1,3 +1,4 @@
1+{- ORMOLU_DISABLE -}
12{-# LANGUAGE CPP #-}
23
34module Octopod.API.WebSocket
octopod-api/src/Octopod/PowerAPI.hs+5
@@ -36,6 +36,9 @@ type StatusEndpoint c = c :> "status" :> Get '[JSON] CurrentDeploymentStatus
3636type CleanupEndpoint c =
3737 c :> "cleanup" :> Delete '[JSON] CommandResponse
3838
39+type DeleteEndpoint c =
40+ c :> "delete" :> Delete '[JSON] CommandResponse
41+
3942type RestoreEndpoint c =
4043 c :> "restore" :> Patch '[JSON] CommandResponse
4144
@@ -63,6 +66,8 @@ type DeploymentAPI' c =
6366 -- endpoint to clean up resources of an archived deployment
6467 :<|> RestoreEndpoint c
6568 -- endpoint to restore an archived deployment
69+ :<|> DeleteEndpoint c
70+ -- endpoint to clean up resources of an archived deployment
6671 )
6772 :<|> GetActionInfoEndpoint
6873 -- endpoint to get action logs
octopod-backend/octopod-backend.cabal+3−3
@@ -63,7 +63,7 @@ library
6363 Octopod.Server
6464 Octopod.Server.Args
6565 Octopod.Server.ControlScriptUtils
66− Octopod.Server.Logger
66+ Octopod.Server.Logging
6767 Octopod.Server.Posix
6868 Orphans
6969 Types
@@ -81,7 +81,7 @@ library
8181 , cassava
8282 , conduit
8383 , octopod-common
84− , fast-logger
84+ , katip
8585 , generic-lens
8686 , http-api-data
8787 , lens
@@ -113,7 +113,7 @@ library
113113 , time
114114 , hasql
115115 , hasql-transaction
116− , ordered-containers
116+ , unordered-containers
117117 , vector
118118 , process
119119 default-language: Haskell2010
octopod-backend/src/Control/CacheMap.hs+43−47
@@ -2,42 +2,41 @@ module Control.CacheMap
22 ( CacheMap,
33 initCacheMap,
44 lookupBlocking,
5− ntCacheMap,
65 )
76where
87
8+import Control.Arrow
99import Control.Concurrent.Lifted
10−import Control.Concurrent.STM
1110import Control.Monad
1211import Control.Monad.Base
1312import Control.Monad.Except
1413import Control.Monad.Trans.Control
1514import Data.Fixed
15+import Data.Foldable
16+import Data.IORef.Lifted
1617import Data.Map.Strict (Map)
1718import qualified Data.Map.Strict as M
1819import Data.Time
1920import System.Mem.Weak
2021
21−data CacheMap e m k v = CacheMap !(InternalCacheMap e k v) !(k -> m v) !NominalDiffTime
22+data CacheMap e c k v = CacheMap !(InternalCacheMap e k v) (forall m. c m => k -> m v) !NominalDiffTime
2223
23−type InternalCacheMap e k v = TVar (Map k (UTCTime, TMVar (Either e v)))
24−
25−ntCacheMap :: (forall x. m x -> n x) -> CacheMap e m k v -> CacheMap e n k v
26−ntCacheMap f (CacheMap m g t) = CacheMap m (f <$> g) t
24+type InternalCacheMap e k v = IORef (Map k (UTCTime, MVar (Either e v)))
2725
2826initCacheMap ::
29− -- | Time after which a value is considered invalid. Not exact.
27+ forall c e k v m.
28+ MonadBase IO m =>
29+ -- | Time after which a value is considered "invalid" (removed from cache). Not exact.
3030 NominalDiffTime ->
31− -- | Time after which the value should be updated.
32− -- (A value can still be valid and need updating – the caller will get the
33− -- old valid value in that case.)
31+ -- | Time after which the value should be updated: upon query the old value would be returned, but
32+ -- a background computation for the new value would be started.
3433 NominalDiffTime ->
35− (k -> m v) ->
36− IO (CacheMap e m k v)
34+ (forall n. c n => k -> n v) ->
35+ m (CacheMap e c k v)
3736initCacheMap invalid update createValue = do
38− ref <- newTVarIO M.empty
39− wRef <- mkWeakTVar ref (pure ())
40− void $ fork $ removeInvalidCaches invalid wRef
37+ ref <- newIORef M.empty
38+ wRef <- liftBase $ mkWeak ref ref Nothing
39+ void $ liftBase $ fork $ removeInvalidCaches invalid wRef
4140 pure $ CacheMap ref createValue update
4241
4342removeInvalidCaches ::
@@ -51,43 +50,40 @@ removeInvalidCaches invalid wRef = do
5150 deRefWeak wRef >>= \case
5251 Nothing -> pure ()
5352 Just ref -> do
54− atomically $ modifyTVar' ref $ M.filter (\(t, _) -> t > cutoff)
53+ atomicModifyIORef' ref (M.filter (\(t, _) -> t > cutoff) &&& const ())
5554 removeInvalidCaches invalid wRef
5655 where
5756 threadDelayMicro :: Micro -> IO ()
5857 threadDelayMicro (MkFixed i) = threadDelay (fromInteger i)
5958
6059lookupBlocking ::
61− (MonadBaseControl IO m, MonadError e m, Ord k) =>
62− CacheMap e m k v ->
60+ (MonadBaseControl IO m, MonadError e m, c m, Ord k) =>
61+ CacheMap e c k v ->
6362 k ->
6463 m v
65−lookupBlocking (CacheMap mTVar createValue update) k = do
64+lookupBlocking (CacheMap ref createValue update) k = do
6665 now <- liftBase getCurrentTime
67− (vM, recreate) <-
68− liftBase $
69− atomically $ do
70− m <- readTVar mTVar
71− case M.lookup k m of
72− Nothing -> do
73− blank <- newEmptyTMVar
74− writeTVar mTVar $ M.insert k (now, blank) m
75− pure (blank, True)
76− Just (timeCreated, vM) -> do
77− tryReadTMVar vM >>= \case
78− Just (Left _) -> do
79− void $ takeTMVar vM
80− pure (vM, True)
81− _ -> pure (vM, timeCreated < addUTCTime (negate update) now)
82− when recreate $
83− void $
84− fork $ do
85− v' <- (Right <$> createValue k) `catchError` (pure . Left)
86− replaceTMVar vM v'
87−
88− (liftBase . atomically . readTMVar) vM >>= liftEither
89−
90−replaceTMVar :: MonadBase IO m => TMVar a -> a -> m ()
91−replaceTMVar mv x = liftBase . atomically $ do
92− void $ tryTakeTMVar mv
93− putTMVar mv x
66+ blank <- newEmptyMVar
67+ -- Return the var with an error or result and a bool indicating whether it needs to be updated
68+ -- If there's no var or if needs to be updated, the update goes into 'blank'
69+ mVar <- atomicModifyIORef' ref $ \m -> case M.lookup k m of
70+ Nothing -> (M.insert k (now, blank) m, Nothing)
71+ Just (timeCreated, var) ->
72+ if timeCreated < addUTCTime (negate update) now
73+ then (M.insert k (now, blank) m, Just (var, True))
74+ else (m, Just (var, False))
75+ -- Return the var from where the *current* result should be read,
76+ -- and optionally the var where the recomputation should write to
77+ (rVar, mwVar) <- case mVar of
78+ Nothing -> pure (blank, Just blank)
79+ Just (var, recompute) ->
80+ takeMVar var >>= \case
81+ Left _ -> pure $ if recompute then (blank, Just blank) else (var, Just var)
82+ res@(Right _) -> do
83+ putMVar var res
84+ pure $ (var, if recompute then Just blank else Nothing)
85+ for_ mwVar $ \wVar -> do
86+ fork $ do
87+ v <- (Right <$> createValue k) `catchError` (pure . Left)
88+ putMVar wVar v
89+ readMVar rVar >>= liftEither
octopod-backend/src/Control/Octopod/DeploymentLock.hs+13−19
@@ -7,19 +7,20 @@ module Control.Octopod.DeploymentLock
77where
88
99import Common.Types
10−import Control.Concurrent.STM
10+import Control.Arrow
1111import Control.Exception.Lifted
1212import Control.Monad.Base
1313import Control.Monad.Reader
1414import Control.Monad.Trans.Control
1515import Data.Generics.Product.Typed
16+import Data.IORef.Lifted
1617import Data.Set (Set)
1718import qualified Data.Set as S
1819
19−newtype LockedDeployments = LockedDeployments (TVar (Set DeploymentName))
20+newtype LockedDeployments = LockedDeployments (IORef (Set DeploymentName))
2021
21−initLockedDeployments :: IO LockedDeployments
22−initLockedDeployments = LockedDeployments <$> newTVarIO S.empty
22+initLockedDeployments :: MonadBase IO m => m LockedDeployments
23+initLockedDeployments = LockedDeployments <$> newIORef S.empty
2324
2425withLockedDeployment ::
2526 ( MonadReader r m
@@ -33,24 +34,17 @@ withLockedDeployment ::
3334 -- | The actions to be performed when the deployment is locked.
3435 m a ->
3536 m a
36−withLockedDeployment dName conflictHandler m = do
37− (LockedDeployments tvar) <- asks getTyped
38− proceed <- liftBase . atomically $ do
39− s <- readTVar tvar
40− if S.member dName s
41− then return False -- Do not proceed
42− else do
43− writeTVar tvar (S.insert dName s)
44− return True -- Proceed
45− if proceed
46− then finally m $ liftBase . atomically $ modifyTVar tvar (S.delete dName)
47− else conflictHandler
37+withLockedDeployment dName conflict act = do
38+ LockedDeployments ref <- asks getTyped
39+ bracket
40+ (atomicModifyIORef' ref (S.insert dName &&& S.notMember dName))
41+ (\ok -> when ok $ atomicModifyIORef' ref (S.delete dName &&& const ()))
42+ (\ok -> if ok then act else conflict)
4843
4944isDeploymentLocked ::
5045 (MonadReader r m, HasType LockedDeployments r, MonadBase IO m) =>
5146 DeploymentName ->
5247 m Bool
5348isDeploymentLocked dName = do
54− (LockedDeployments tvar) <- asks getTyped
55− s <- liftBase $ readTVarIO tvar
56− return $ S.member dName s
49+ LockedDeployments ref <- asks getTyped
50+ S.member dName <$> readIORef ref
octopod-backend/src/Octopod/Server.hs+287−262
@@ -4,13 +4,10 @@ import Common.Validation
44import Control.Applicative
55import Control.CacheMap (CacheMap)
66import qualified Control.CacheMap as CM
7−import Control.Concurrent (threadDelay)
8−import Control.Concurrent.Async (race_)
9−import qualified Control.Concurrent.Lifted as L
10−import Control.Concurrent.MVar
7+import Control.Concurrent.Async (race)
8+import Control.Concurrent.Lifted hiding (yield)
119import Control.Concurrent.STM
12−import Control.Exception (Exception (displayException), throwIO)
13−import qualified Control.Exception.Lifted as L
10+import Control.Exception.Lifted hiding (Handler)
1411import Control.Lens hiding (Context, each, pre, (<.))
1512import Control.Lens.Extras
1613import Control.Monad
@@ -26,6 +23,7 @@ import qualified Data.ByteString.Lazy as BSL
2623import qualified Data.ByteString.Lazy.Char8 as BSLC
2724import Data.Coerce
2825import Data.Conduit (ConduitT, yield)
26+import qualified Data.ConfigTree as CT
2927import qualified Data.Csv as C
3028import Data.Fixed
3129import Data.Foldable
@@ -33,9 +31,8 @@ import Data.Functor
3331import Data.Functor.Contravariant
3432import Data.Generics.Labels ()
3533import Data.Generics.Product
36−import Data.IORef
34+import Data.IORef.Lifted
3735import Data.Int
38−import qualified Data.Map.Ordered.Strict as OM
3936import Data.Maybe
4037import Data.Pool
4138import Data.Text (pack)
@@ -44,11 +41,14 @@ import qualified Data.Text.Encoding as T
4441import Data.Time
4542import Data.Traversable
4643import qualified Data.Vector as V
44+import GHC.Exception
45+import GHC.Stack
4746import Hasql.Connection
4847import qualified Hasql.Session as HasQL
4948import Hasql.Statement
5049import Hasql.Transaction
5150import Hasql.Transaction.Sessions
51+import Katip hiding (Namespace)
5252import Network.Wai.Handler.Warp
5353import Octopod.API
5454import Octopod.PowerAPI
@@ -56,7 +56,7 @@ import Octopod.PowerAPI.Auth.Server
5656import Octopod.Schema
5757import Octopod.Server.Args
5858import Octopod.Server.ControlScriptUtils
59−import Octopod.Server.Logger
59+import Octopod.Server.Logging
6060import Octopod.Server.Posix
6161import Options.Generic
6262import Orphans ()
@@ -67,26 +67,20 @@ import Servant
6767import Servant.Auth.Server
6868import System.Environment (lookupEnv)
6969import System.Exit
70−import System.Log.FastLogger
7170import System.Posix.Signals (sigTERM)
7271import Text.Read (readMaybe)
7372import Types
7473import Prelude hiding (lines, log, unlines, unwords)
7574
76−type AppM = ReaderT AppState Handler
75+type AppM = ReaderT AppState (KatipContextT Handler)
7776
78−type AppMConstraints m = (MonadReader AppState m, MonadBaseControl IO m, MonadError ServerError m)
79−
80−-- | This is a polymorphic monad wrapper with the main constraints from 'AppM'.
81−-- Currently used only for CacheMap values.
82−newtype AppM' a = AppM' {runAppM' :: forall m. AppMConstraints m => m a}
77+class (KatipContext m, MonadReader AppState m, MonadBaseControl IO m, MonadError ServerError m) => AppMConstraints m
78+instance (KatipContext m, MonadReader AppState m, MonadBaseControl IO m, MonadError ServerError m) => AppMConstraints m
8379
8480-- | Octopod Server state definition.
8581data AppState = AppState
8682 { -- | postgres pool
8783 dbPool :: !(Pool Connection)
88− , -- | logger
89− logger :: !TimedFastLogger
9084 , -- | channel for WS events for the frontend
9185 eventSink :: !(TChan WSEvent)
9286 , -- | background workers counter
@@ -127,10 +121,10 @@ data AppState = AppState
127121 , -- | Deployments currently being processed which has not yet been
128122 -- recorded in the database.
129123 lockedDeployments :: !LockedDeployments
130− , depOverridesCache :: !(CacheMap ServerError AppM' () (DefaultConfig 'DeploymentLevel))
131− , depOverrideKeysCache :: !(CacheMap ServerError AppM' () [Text])
132− , appOverridesCache :: !(CacheMap ServerError AppM' (Config 'DeploymentLevel) (DefaultConfig 'ApplicationLevel))
133− , appOverrideKeysCache :: !(CacheMap ServerError AppM' (Config 'DeploymentLevel) [Text])
124+ , depOverridesCache :: !(CacheMap ServerError AppMConstraints () (DefaultConfig 'DeploymentLevel))
125+ , depOverrideKeysCache :: !(CacheMap ServerError AppMConstraints () [Text])
126+ , appOverridesCache :: !(CacheMap ServerError AppMConstraints (Config 'DeploymentLevel) (DefaultConfig 'ApplicationLevel))
127+ , appOverrideKeysCache :: !(CacheMap ServerError AppMConstraints (Config 'DeploymentLevel) [Text])
134128 , gitSha :: !Text
135129 , controlScriptTimeout :: !ControlScriptTimeout
136130 }
@@ -162,14 +156,9 @@ runOctopodServer ::
162156 Text ->
163157 IO ()
164158runOctopodServer sha = do
165− logger' <- newLogger
166− logInfo logger' "started"
167159 bgWorkersC <- newIORef 0
168160 gracefulShutdownAct <- newIORef False
169161 shutdownS <- newEmptyMVar
170− void $ do
171− let termHandler = terminationHandler bgWorkersC gracefulShutdownAct shutdownS
172− installShutdownHandler logger' [sigTERM] termHandler
173162 opts <- parseArgs
174163 let a ?! e = a >>= maybe (die e) pure
175164 getEnvOrDie eName = lookupEnv eName ?! (eName <> " is not set")
@@ -196,16 +185,18 @@ runOctopodServer sha = do
196185 powerAuthorizationHeader <- AuthHeader . BSC.pack <$> getEnvOrDie "POWER_AUTHORIZATION_HEADER"
197186 invalidationTime <- fromInteger . maybe (60 * 60 * 24) read <$> lookupEnv "CACHE_INVALIDATION_TIME"
198187 updateTime <- fromInteger . maybe (60 * 10) read <$> lookupEnv "CACHE_UPDATE_TIME"
199− let cacheMap :: forall a x. (forall m. AppMConstraints m => x -> m a) -> IO (CacheMap ServerError AppM' x a)
200− cacheMap f = CM.initCacheMap invalidationTime updateTime $ \x -> AppM' $ f x
188+ isDebug <- (== Just "true") <$> lookupEnv "DEBUG"
189+ isProdLogs <- (/= Just "false") <$> lookupEnv "PROD_LOGS"
190+ let cacheMap :: forall a x. (forall m. AppMConstraints m => x -> m a) -> IO (CacheMap ServerError AppMConstraints x a)
191+ cacheMap = CM.initCacheMap invalidationTime updateTime
201192 decodeCSVDefaultConfig :: BSL.ByteString -> Either String (DefaultConfig l)
202193 decodeCSVDefaultConfig bs = do
203194 x <- C.decode C.NoHeader bs
204− pure $ DefaultConfig . OM.fromList . V.toList $ x
205− either500S :: (MonadError ServerError m, MonadBase IO m) => Either String x -> m x
195+ pure $ DefaultConfig . CT.fromFlatList . V.toList $ x
196+ either500S :: (KatipContext m, MonadError ServerError m) => Either String x -> m x
206197 either500S (Right x) = pure x
207198 either500S (Left err) = do
208− liftBase . logWarning logger' . T.pack $ err
199+ logLocM ErrorS $ logStr err
209200 throwError err500 {errBody = BSLC.pack err}
210201 notificationCmd <-
211202 (fmap . fmap) (Command . pack) $
@@ -224,67 +215,93 @@ runOctopodServer sha = do
224215 appOverridesCache' <- cacheMap $ \cfg -> do
225216 (_, Stdout out, _, _) <- defaultApplicationOverridesArgs cfg >>= runCommandArgs applicationOverridesCommand
226217 either500S $ decodeCSVDefaultConfig . BSL.fromStrict . T.encodeUtf8 $ out
227− dbPool' <-
228− createPool
229− (fmap (either (error . show) id) . acquire $ unDBConnectionString $ octopodDB opts)
230− release
231− 1
232− 30
233− (unDBPoolSize $ octopodDBPoolSize opts)
234− channel <- liftIO . atomically $ newBroadcastTChan
235− lockedDs <- initLockedDeployments
236− let appSt =
237− AppState
238− { dbPool = dbPool'
239− , logger = logger'
240− , eventSink = channel
241− , bgWorkersCounter = bgWorkersC
242− , gracefulShutdownActivated = gracefulShutdownAct
243− , shutdownSem = shutdownS
244− , projectName = projName
245− , baseDomain = domain
246− , namespace = ns
247− , statusUpdateTimeout = stUpdateTimeout
248− , creationCommand = creationCmd
249− , updateCommand = updateCmd
250− , archiveCommand = archiveCmd
251− , checkingCommand = checkingCmd
252− , cleanupCommand = cleanupCmd
253− , archiveCheckingCommand = archiveCheckingCmd
254− , configCheckingCommand = tagCheckingCmd
255− , infoCommand = infoCmd
256− , notificationCommand = notificationCmd
257− , deploymentOverridesCommand = dOverridesCmd
258− , deploymentOverrideKeysCommand = dKeysCmd
259− , applicationOverridesCommand = aOverridesCmd
260− , applicationOverrideKeysCommand = aKeysCmd
261− , unarchiveCommand = unarchiveCmd
262− , lockedDeployments = lockedDs
263− , depOverridesCache = depOverridesCache'
264− , depOverrideKeysCache = depOverrideKeysCache'
265− , appOverridesCache = appOverridesCache'
266− , appOverrideKeysCache = appOverrideKeysCache'
267− , gitSha = sha
268− , controlScriptTimeout = scriptTimeout
218+ let logConfig =
219+ LogConfig
220+ { project = projName
221+ , debug = isDebug
222+ , prodLogs = isProdLogs
269223 }
270−
271− app' = app appSt
272− wsApp' = wsApp channel
273− serverPort = octopodPort opts
274− uiServerPort = unServerPort $ octopodUIPort opts
275− powerServerPort = unServerPort serverPort
276− wsServerPort = unServerPort $ octopodWSPort opts
277− void . L.fork $ flip runReaderT appSt $ runArchiveCleanup archRetention
278− powerApp' <- powerApp powerAuthorizationHeader appSt
279− (run uiServerPort app')
280− `race_` (run powerServerPort powerApp')
281− `race_` (run wsServerPort wsApp')
282− `race_` (runStatusUpdater appSt)
283− `race_` (runShutdownHandler appSt)
224+ runLog logConfig $ do
225+ void $ do
226+ let termHandler = terminationHandler bgWorkersC gracefulShutdownAct shutdownS
227+ installShutdownHandler [sigTERM] termHandler
228+ dbPool' <-
229+ liftBase $
230+ createPool
231+ (fmap (either (error . show) id) . acquire $ unDBConnectionString $ octopodDB opts)
232+ release
233+ 1
234+ 30
235+ (unDBPoolSize $ octopodDBPoolSize opts)
236+ channel <- liftBase . atomically $ newBroadcastTChan
237+ lockedDs <- initLockedDeployments
238+ let appSt =
239+ AppState
240+ { dbPool = dbPool'
241+ , eventSink = channel
242+ , bgWorkersCounter = bgWorkersC
243+ , gracefulShutdownActivated = gracefulShutdownAct
244+ , shutdownSem = shutdownS
245+ , projectName = projName
246+ , baseDomain = domain
247+ , namespace = ns
248+ , statusUpdateTimeout = stUpdateTimeout
249+ , creationCommand = creationCmd
250+ , updateCommand = updateCmd
251+ , archiveCommand = archiveCmd
252+ , checkingCommand = checkingCmd
253+ , cleanupCommand = cleanupCmd
254+ , archiveCheckingCommand = archiveCheckingCmd
255+ , configCheckingCommand = tagCheckingCmd
256+ , infoCommand = infoCmd
257+ , notificationCommand = notificationCmd
258+ , deploymentOverridesCommand = dOverridesCmd
259+ , deploymentOverrideKeysCommand = dKeysCmd
260+ , applicationOverridesCommand = aOverridesCmd
261+ , applicationOverrideKeysCommand = aKeysCmd
262+ , unarchiveCommand = unarchiveCmd
263+ , lockedDeployments = lockedDs
264+ , depOverridesCache = depOverridesCache'
265+ , depOverrideKeysCache = depOverrideKeysCache'
266+ , appOverridesCache = appOverridesCache'
267+ , appOverrideKeysCache = appOverrideKeysCache'
268+ , gitSha = sha
269+ , controlScriptTimeout = scriptTimeout
270+ }
271+ serverPort = octopodPort opts
272+ powerServerPort = unServerPort serverPort
273+ uiServerPort = unServerPort $ octopodUIPort opts
274+ wsServerPort = unServerPort $ octopodWSPort opts
275+ (`runReaderT` appSt) $ do
276+ runServer uiServerPort
277+ `raceM_` runPowerServer powerServerPort powerAuthorizationHeader
278+ `raceM_` runWsServer wsServerPort channel
279+ `raceM_` runStatusUpdater
280+ `raceM_` runShutdownHandler
281+ `raceM_` runArchiveCleanup archRetention
282+
283+raceM_ :: MonadBaseControl IO m => m a -> m b -> m ()
284+raceM_ a b = liftBaseWith $ \runInBase ->
285+ race (runInBase a) (runInBase b) >>= \case
286+ Left x -> void $ restoreM x
287+ Right y -> void $ restoreM y
288+
289+runHasQL ::
290+ (KatipContext m, MonadReader AppState m, MonadBaseControl IO m) =>
291+ HasQL.Session a ->
292+ m a
293+runHasQL s = do
294+ p <- asks dbPool
295+ liftBaseOp (withResource p) $ \conn ->
296+ liftBase (HasQL.run s conn) >>= \case
297+ Right x -> pure x
298+ Left err -> do
299+ logLocM ErrorS $ logStr $ displayException err
300+ throwIO err
284301
285302runArchiveCleanup ::
286303 forall m.
287− (MonadReader AppState m, MonadBaseControl IO m) =>
304+ (KatipContext m, MonadReader AppState m, MonadBaseControl IO m) =>
288305 NominalDiffTime ->
289306 m ()
290307runArchiveCleanup retention = do
@@ -304,45 +321,45 @@ runArchiveCleanup retention = do
304321 runArchiveCleanup retention
305322 where
306323 threadDelayMicro :: Micro -> m ()
307− threadDelayMicro (MkFixed i) = liftBase $ threadDelay (fromInteger i)
324+ threadDelayMicro (MkFixed i) = threadDelay (fromInteger i)
308325
309326runTransaction ::
310− (MonadReader AppState m, MonadBaseControl IO m) =>
327+ (KatipContext m, MonadReader AppState m, MonadBaseControl IO m) =>
311328 Transaction a ->
312329 m a
313−runTransaction q = do
314− p <- asks dbPool
315− log <- asks logger
316− let logErr = logWarning log
317− handleException e = do
318− liftBase $ logErr . T.pack $ displayException e
319− throwIO e
320− withResource p $ \conn ->
321− liftBase $ HasQL.run (transaction Serializable Write q) conn >>= either handleException pure
330+runTransaction q = katipAddNamespace "transaction" $ runHasQL $ transaction Serializable Write q
322331
323332runStatement ::
324− (MonadReader AppState m, MonadBaseControl IO m) =>
333+ (KatipContext m, MonadReader AppState m, MonadBaseControl IO m) =>
325334 Statement () a ->
326335 m a
327−runStatement q = do
328− p <- asks dbPool
329− log <- asks logger
330− let logErr = logWarning log
331− handleException e = do
332− liftBase $ logErr . T.pack $ displayException e
333− throwIO e
334− withResource p $ \conn ->
335− liftBase $ HasQL.run (HasQL.statement () q) conn >>= either handleException pure
336−
337−-- | Helper to run the server.
338−nt :: AppState -> AppM a -> Handler a
339−nt s x = runReaderT x s
340−
341−-- | Application with the Web UI API.
342−app :: AppState -> Application
343−app s = serve api $ hoistServer api (nt s) server
344− where
345− api = Proxy @API
336+runStatement q = katipAddNamespace "statement" $ runHasQL $ HasQL.statement () q
337+
338+runApp ::
339+ forall api context m.
340+ ( HasServer api context
341+ , KatipContext m
342+ , MonadBase IO m
343+ , MonadReader AppState m
344+ , HasContextEntry (context .++ DefaultErrorFormatters) ErrorFormatters
345+ ) =>
346+ Port ->
347+ Proxy api ->
348+ Servant.Context context ->
349+ ServerT api AppM ->
350+ m ()
351+runApp portNum api ctx srv = do
352+ appstate <- ask
353+ logEnv <- getLogEnv
354+ logCtx <- getKatipContext
355+ ns <- getKatipNamespace
356+ let hoist :: AppM a -> Handler a
357+ hoist act = runKatipContextT logEnv logCtx ns $ runReaderT act appstate
358+ log req respStatus mSize = runKatipContextT logEnv logCtx ns $ do
359+ logLocM DebugS $ show' req <> " " <> show' respStatus <> " " <> show' mSize
360+ liftBase $
361+ runSettings (setPort portNum $ setLogger log defaultSettings) $
362+ serveWithContext api ctx $ hoistServerWithContext api (Proxy @context) hoist srv
346363
347364-- | Request handlers of the Web UI API application.
348365server :: ServerT API AppM
@@ -360,10 +377,10 @@ server =
360377 :<|> pingH
361378 :<|> projectNameH
362379
363−lookupCache :: (Ord x, AppMConstraints m) => (AppState -> CacheMap ServerError AppM' x y) -> x -> m y
380+lookupCache :: (Ord x, AppMConstraints m) => (AppState -> CacheMap ServerError AppMConstraints x y) -> x -> m y
364381lookupCache f x = do
365382 cm <- asks f
366− CM.lookupBlocking (CM.ntCacheMap runAppM' cm) x
383+ CM.lookupBlocking cm x
367384
368385defaultDeploymentKeysH :: AppMConstraints m => m [Text]
369386defaultDeploymentKeysH = lookupCache depOverrideKeysCache ()
@@ -377,22 +394,20 @@ defaultApplicationKeysH = lookupCache appOverrideKeysCache
377394defaultApplicationOverridesH :: AppMConstraints m => Config 'DeploymentLevel -> m (DefaultConfig 'ApplicationLevel)
378395defaultApplicationOverridesH = lookupCache appOverridesCache
379396
397+runServer :: (KatipContext m, MonadBase IO m, MonadReader AppState m) => Port -> m ()
398+runServer portNum = katipAddNamespace "app" $ runApp portNum (Proxy @API) EmptyContext server
399+
380400-- | Application with the octo CLI API.
381−powerApp :: AuthHeader -> AppState -> IO Application
382−powerApp h s = do
383− jwk' <- liftIO $ genJWK (RSAGenParam (4096 `div` 8))
401+runPowerServer :: (KatipContext m, MonadBase IO m, MonadReader AppState m) => Port -> AuthHeader -> m ()
402+runPowerServer portNum h = katipAddNamespace "powerApp" $ do
403+ jwk' <- liftBase $ genJWK (RSAGenParam (4096 `div` 8))
384404 let ctx :: Servant.Context Ctx
385405 ctx =
386406 h
387407 :. (defaultJWTSettings jwk' :: JWTSettings)
388408 :. (defaultCookieSettings :: CookieSettings)
389409 :. EmptyContext
390−
391− return $
392− serveWithContext api ctx $
393− hoistServerWithContext api (Proxy @Ctx) (nt s) powerServer
394− where
395− api = Proxy @PowerAPI
410+ runApp portNum (Proxy @PowerAPI) ctx powerServer
396411
397412type Ctx = '[AuthHeader, JWTSettings, CookieSettings]
398413
@@ -405,15 +420,16 @@ powerServer (Authenticated ()) =
405420 :<|> statusH
406421 :<|> cleanupH
407422 :<|> restoreH
423+ :<|> deleteH
408424 )
409425 :<|> getActionInfoH
410426powerServer _ = throwAll err401
411427
412428-- | Application with the WS API.
413−wsApp :: TChan WSEvent -> Application
414−wsApp channel = serve api $ wsServer channel
415− where
416− api = Proxy @WebSocketAPI
429+runWsServer :: (KatipContext m, MonadBase IO m, MonadReader AppState m) => Port -> TChan WSEvent -> m ()
430+runWsServer portNum channel =
431+ katipAddNamespace "wsApp" $
432+ runApp portNum (Proxy @WebSocketAPI) EmptyContext $ wsServer channel
417433
418434-- | Request handlers of the application with the WS API.
419435wsServer :: TChan WSEvent -> Server WebSocketAPI
@@ -437,7 +453,7 @@ powerListH = getFullInfo
437453
438454-- | Handles the 'full_info' request of the Web UI API.
439455fullInfoH ::
440− (MonadReader AppState m, MonadBaseControl IO m, MonadError ServerError m) =>
456+ (KatipContext m, MonadReader AppState m, MonadBaseControl IO m, MonadError ServerError m) =>
441457 DeploymentName ->
442458 m DeploymentFullInfo
443459fullInfoH dName = do
@@ -454,29 +470,27 @@ powerFullInfoH :: DeploymentName -> AppM DeploymentFullInfo
454470powerFullInfoH = fullInfoH
455471
456472getSingleFullInfo ::
457− (MonadReader AppState m, MonadBaseControl IO m) =>
473+ (KatipContext m, MonadReader AppState m, MonadBaseControl IO m) =>
458474 DeploymentName ->
459475 m (Maybe DeploymentFullInfo)
460476getSingleFullInfo dName = do
461− AppState {logger = l} <- ask
462477 deploymentsSchema <- runStatement . select $ do
463478 d <- each deploymentSchema
464479 where_ $ d ^. #name ==. litExpr dName
465480 pure d
466− deployments <- forM deploymentsSchema extractDeploymentFullInfo
467− liftBase . logInfo l $ "get deployments: " <> (pack . show $ deployments)
481+ deployments <- for deploymentsSchema extractDeploymentFullInfo
482+ logLocM DebugS $ "get deployments: " <> show' deployments
468483 return $ listToMaybe deployments
469484
470485getFullInfo ::
471− (MonadReader AppState m, MonadBaseControl IO m) =>
486+ (KatipContext m, MonadReader AppState m, MonadBaseControl IO m) =>
472487 m [DeploymentFullInfo]
473488getFullInfo = do
474− AppState {logger = l} <- ask
475489 deploymentsSchema <-
476490 runStatement . select . orderBy (view #updatedAt >$< desc) $
477491 each deploymentSchema
478− deployments <- forM deploymentsSchema extractDeploymentFullInfo
479− liftBase . logInfo l $ "get deployments: " <> (pack . show $ deployments)
492+ deployments <- for deploymentsSchema extractDeploymentFullInfo
493+ logLocM DebugS $ "get deployments: " <> show' deployments
480494 return deployments
481495
482496-- | Handles the 'create' request.
@@ -498,7 +512,7 @@ createH dep = do
498512 (dep ^. #name)
499513 ( do
500514 res <-
501− L.try $
515+ try $
502516 runStatement $
503517 insert
504518 Insert
@@ -534,38 +548,35 @@ createH dep = do
534548 throwError err409 {errBody = appError "Some database error"}
535549 )
536550 $ \() -> do
537− st <- ask
538− liftBase $ sendReloadEvent st
551+ sendReloadEvent
539552 updateDeploymentInfo (dep ^. #name)
540553 (ec, out, err, elTime) <- createDeployment dep
541554 -- calling it directly now is fine since there is no previous status.
542555 createDeploymentLog dep CreateAction ec elTime out err
543− liftBase $ sendReloadEvent st
544− liftBase $ handleExitCode ec
556+ sendReloadEvent
557+ handleExitCode ec
545558 pure Success
546559
547560-- | Updates deployment info.
548561updateDeploymentInfo ::
549− (MonadReader AppState m, MonadBaseControl IO m, MonadError ServerError m) =>
562+ (KatipContext m, MonadReader AppState m, MonadBaseControl IO m, MonadError ServerError m) =>
550563 DeploymentName ->
551564 m ()
552565updateDeploymentInfo dName = do
553− log <- asks (logWarning . logger)
554566 DeploymentFullInfo {deployment = dep} <- getDeploymentS dName
555567 cfg <- getDeploymentConfig dep
556568 (ec, out, err, _) <- runCommandArgs infoCommand =<< infoCommandArgs cfg dep
557569 case ec of
558570 ExitSuccess -> case parseDeploymentMetadata (unStdout out) of
559571 Right dMeta -> upsertDeploymentMetadatum dName dMeta
560− Left err' -> liftBase $ log $ "could not get deployment info, could not parse CSV: " <> T.pack err'
572+ Left err' -> logLocM ErrorS $ "could not get deployment info, could not parse CSV: " <> logStr err'
561573 ExitFailure _ ->
562− liftBase $
563− log $
564− "could not get deployment info, exit code: " <> (pack . show $ ec)
565− <> ", stdout: "
566− <> coerce out
567− <> "stderr: "
568− <> coerce err
574+ logLocM ErrorS $
575+ "could not get deployment info, exit code: " <> show' ec
576+ <> ", stdout: "
577+ <> logStr (unStdout out)
578+ <> "stderr: "
579+ <> logStr (unStderr err)
569580
570581getDeploymentConfig :: AppMConstraints m => Deployment -> m FullConfig
571582getDeploymentConfig dep = do
@@ -579,21 +590,18 @@ getDeploymentConfig dep = do
579590
580591-- | Helper to create a new deployment.
581592createDeployment ::
582− (MonadBaseControl IO m, MonadReader AppState m, MonadError ServerError m) =>
593+ (KatipContext m, MonadBaseControl IO m, MonadReader AppState m, MonadError ServerError m) =>
583594 Deployment ->
584595 m (ExitCode, Stdout, Stderr, Duration)
585596createDeployment dep = do
586− st <- ask
587597 cfg <- getDeploymentConfig dep
588− let log :: Text -> IO ()
589− log = logInfo (logger st)
590598 res <- runCommandArgs creationCommand =<< createCommandArgs cfg dep
591− liftBase . log $ "deployment created, deployment: " <> (pack . show $ dep)
599+ logLocM InfoS $ "deployment created, deployment: " <> show' dep
592600 pure res
593601
594602-- | Helper to get deployment logs.
595603selectDeploymentLogs ::
596− (MonadBaseControl IO m, MonadReader AppState m) =>
604+ (KatipContext m, MonadBaseControl IO m, MonadReader AppState m) =>
597605 DeploymentName ->
598606 m [DeploymentLog]
599607selectDeploymentLogs dName = (fmap . fmap) extractDeploymentLog . runStatement . select
@@ -648,7 +656,7 @@ processOutput (TransitionCleanupFailed x) = Just (x, CleanupAction)
648656processOutput TransitionFailure {} = Nothing
649657
650658transitionToStatusS ::
651− (MonadError ServerError m, MonadReader AppState m, MonadBaseControl IO m) =>
659+ (KatipContext m, MonadError ServerError m, MonadReader AppState m, MonadBaseControl IO m) =>
652660 DeploymentName ->
653661 DeploymentStatusTransition ->
654662 m ()
@@ -682,7 +690,7 @@ transitionErrorToServerError = \case
682690 show'' = BSLC.pack . show
683691
684692getDeploymentS ::
685− (MonadReader AppState m, MonadBaseControl IO m, MonadError ServerError m) =>
693+ (KatipContext m, MonadReader AppState m, MonadBaseControl IO m, MonadError ServerError m) =>
686694 DeploymentName ->
687695 m DeploymentFullInfo
688696getDeploymentS dName =
@@ -691,15 +699,13 @@ getDeploymentS dName =
691699 Nothing -> throwError err404 {errBody = "Deployment not found."}
692700
693701transitionToStatus ::
694− (MonadError ServerError m, MonadReader AppState m, MonadBaseControl IO m) =>
702+ (KatipContext m, MonadError ServerError m, MonadReader AppState m, MonadBaseControl IO m) =>
695703 DeploymentName ->
696704 DeploymentStatusTransition ->
697705 ExceptT StatusTransitionError m ()
698706transitionToStatus dName s = do
699− st <- ask
700− let log = liftBase . logInfo (logger st)
701− newS = transitionStatus s
702− log $ "Transitioning deployment " <> (show' . unDeploymentName) dName <> " " <> show' s
707+ let newS = transitionStatus s
708+ logLocM InfoS $ "Transitioning deployment " <> show' (unDeploymentName dName) <> " " <> show' s
703709 res <- runTransaction . runExceptT $ do
704710 oldS <-
705711 (lift . statement () . select)
@@ -733,7 +739,7 @@ transitionToStatus dName s = do
733739 (deps, oldS) <- either throwError pure res
734740 dep <- lift $ ensureOne deps
735741 lift $
736− forM_ (processOutput s) $ \(output, act) ->
742+ for_ (processOutput s) $ \(output, act) ->
737743 createDeploymentLog
738744 dep
739745 act
@@ -742,11 +748,11 @@ transitionToStatus dName s = do
742748 (output ^. #stdout)
743749 (output ^. #stderr)
744750 notificationCmd <- asks notificationCommand
745− forM_ notificationCmd $ \nCmd ->
751+ for_ notificationCmd $ \nCmd ->
746752 runBgWorker . void $
747753 runCommandArgs' nCmd
748754 =<< notificationCommandArgs dName oldS newS
749− liftBase $ sendReloadEvent st
755+ sendReloadEvent
750756
751757assertStatusTransitionPossible ::
752758 MonadError StatusTransitionError m =>
@@ -760,7 +766,7 @@ assertStatusTransitionPossible dName old new =
760766 (throwError $ InvalidStatusTransition dName old new)
761767
762768assertDeploymentTransitionPossible ::
763− (MonadError StatusTransitionError m, MonadReader AppState m, MonadBaseControl IO m) =>
769+ (KatipContext m, MonadError StatusTransitionError m, MonadReader AppState m, MonadBaseControl IO m) =>
764770 DeploymentName ->
765771 DeploymentStatus ->
766772 m ()
@@ -772,7 +778,7 @@ assertDeploymentTransitionPossible dName new = do
772778 assertStatusTransitionPossible dName (recordedStatus $ dep ^. #status) new
773779
774780assertDeploymentTransitionPossibleS ::
775− (MonadError ServerError m, MonadReader AppState m, MonadBaseControl IO m) =>
781+ (KatipContext m, MonadError ServerError m, MonadReader AppState m, MonadBaseControl IO m) =>
776782 DeploymentName ->
777783 DeploymentStatus ->
778784 m ()
@@ -819,8 +825,6 @@ archiveH dName = do
819825updateH :: DeploymentName -> DeploymentUpdate -> AppM CommandResponse
820826updateH dName dUpdate = do
821827 failIfGracefulShutdownActivated
822− st <- ask
823− let log = logInfo (logger st)
824828 olDep <- getDeploymentS dName <&> (^. #deployment)
825829 failIfImageNotFound
826830 ( olDep
@@ -846,12 +850,13 @@ updateH dName dUpdate = do
846850 >>= ensureOne
847851
848852 updateDeploymentInfo dName
849− liftBase $ sendReloadEvent st
853+ sendReloadEvent
850854 cfg <- getDeploymentConfig dep
851855 (ec, out, err, elTime) <- runCommandArgs updateCommand =<< updateCommandArgs cfg dep
852− liftBase . log $
853− "deployment updated, name: "
854− <> coerce dName
856+ logLocM DebugS $
857+ logStr $
858+ "deployment updated, name: "
859+ <> unDeploymentName dName
855860 transitionToStatusS dName $
856861 TransitionUpdatePending
857862 StatusTransitionProcessOutput
@@ -866,19 +871,15 @@ updateH dName dUpdate = do
866871-- | Handles the 'info' request of the Web UI API.
867872infoH :: DeploymentName -> AppM [DeploymentInfo]
868873infoH dName = do
869− st <- ask
870874 dInfo <- getInfo dName
871− liftIO . logInfo (logger st) $
872− "get deployment info: " <> (pack . show $ dInfo)
875+ logLocM DebugS $ "get deployment info: " <> show' dInfo
873876 pure [dInfo]
874877
875878-- | Handles the 'info' request of the octo CLI API.
876879powerInfoH :: DeploymentName -> AppM [DeploymentInfo]
877880powerInfoH dName = do
878− st <- ask
879881 dInfo <- getInfo dName
880− liftIO . logInfo (logger st) $
881− "get deployment info: " <> (pack . show $ dInfo)
882+ logLocM DebugS $ "get deployment info: " <> show' dInfo
882883 pure [dInfo]
883884
884885-- | Helper to get deployment info from the database.
@@ -921,23 +922,30 @@ cleanupH dName = do
921922 runDeploymentBgWorker Nothing dName (pure ()) $ \() -> cleanupDeployment dName
922923 pure Success
923924
925+-- | Deletes the deployment from the DB.
926+deleteH :: DeploymentName -> AppM CommandResponse
927+deleteH dName = do
928+ failIfGracefulShutdownActivated
929+ runDeploymentBgWorker Nothing dName (pure ()) $ \() -> deleteAllDeployment dName
930+ pure Success
931+
924932-- | Helper to cleanup deployment.
925933cleanupDeployment ::
926− (MonadBaseControl IO m, MonadReader AppState m, MonadError ServerError m) =>
934+ (KatipContext m, MonadBaseControl IO m, MonadReader AppState m, MonadError ServerError m) =>
927935 DeploymentName ->
928936 m ()
929937cleanupDeployment dName = do
930− st <- ask
931− let log = logInfo (logger st)
932938 (view #deployment -> dep) <- getDeploymentS dName
933939 cfg <- getDeploymentConfig dep
934940 (ec, out, err, elTime) <- runCommandArgs cleanupCommand =<< cleanupCommandArgs cfg dep
935− liftBase $ print out >> print err
941+ katipAddContext (FilePayload "stdout" $ T.encodeUtf8 $ unStdout out) $
942+ katipAddContext (FilePayload "stdout" $ T.encodeUtf8 $ unStderr err) $
943+ logLocM DebugS "deployment destroyed"
936944 case ec of
937945 ExitSuccess -> do
938946 deleteDeploymentLogs dName
939947 deleteDeployment dName
940− liftBase $ log $ "deployment destroyed, name: " <> coerce dName
948+ logLocM InfoS $ logStr $ "deployment destroyed, name: " <> unDeploymentName dName
941949 ExitFailure _ -> do
942950 transitionToStatusS dName $
943951 TransitionCleanupFailed
@@ -948,12 +956,21 @@ cleanupDeployment dName = do
948956 , stderr = err
949957 }
950958 pure ()
951− liftBase $ sendReloadEvent st
959+ sendReloadEvent
952960 handleExitCode ec
953961
962+deleteAllDeployment ::
963+ (KatipContext m, MonadBaseControl IO m, MonadReader AppState m) =>
964+ DeploymentName ->
965+ m ()
966+deleteAllDeployment dName = do
967+ deleteDeploymentLogs dName
968+ deleteDeployment dName
969+ logLocM InfoS $ logStr $ "deployment destroyed, name: " <> unDeploymentName dName
970+
954971-- | Helper to delete deployment logs.
955972deleteDeploymentLogs ::
956− (MonadBaseControl IO m, MonadReader AppState m) =>
973+ (KatipContext m, MonadBaseControl IO m, MonadReader AppState m) =>
957974 DeploymentName ->
958975 m ()
959976deleteDeploymentLogs dName =
@@ -968,7 +985,7 @@ deleteDeploymentLogs dName =
968985
969986-- | Helper to delete a deployment.
970987deleteDeployment ::
971− (MonadBaseControl IO m, MonadReader AppState m) =>
988+ (KatipContext m, MonadBaseControl IO m, MonadReader AppState m) =>
972989 DeploymentName ->
973990 m ()
974991deleteDeployment dName =
@@ -1018,13 +1035,13 @@ getActionInfoH aId = do
10181035 throwError err400 {errBody = appError "Action not found"}
10191036
10201037-- | Helper to handle exit code.
1021−handleExitCode :: MonadBaseControl IO m => ExitCode -> m ()
1038+handleExitCode :: MonadBase IO m => ExitCode -> m ()
10221039handleExitCode ExitSuccess = return ()
1023−handleExitCode (ExitFailure c) = liftBase . throwIO $ DeploymentFailed c
1040+handleExitCode (ExitFailure c) = throwIO $ DeploymentFailed c
10241041
10251042-- | Helper to log a deployment action.
10261043createDeploymentLog ::
1027− (MonadBaseControl IO m, MonadReader AppState m) =>
1044+ (KatipContext m, MonadBaseControl IO m, MonadReader AppState m) =>
10281045 Deployment ->
10291046 Action ->
10301047 ExitCode ->
@@ -1071,7 +1088,7 @@ ensureOne _ = throwError err500
10711088
10721089-- | Helper to insert or update deployment metadata.
10731090upsertDeploymentMetadatum ::
1074− (MonadBaseControl IO m, MonadReader AppState m) =>
1091+ (KatipContext m, MonadBaseControl IO m, MonadReader AppState m) =>
10751092 DeploymentName ->
10761093 DeploymentMetadata ->
10771094 m ()
@@ -1107,31 +1124,32 @@ validationError nameErrors =
11071124 encode $ ValidationError nameErrors
11081125
11091126-- | Helper to send an event to the WS event channel.
1110−sendReloadEvent :: AppState -> IO ()
1111−sendReloadEvent state =
1112− atomically $ writeTChan (eventSink state) FrontendPleaseUpdateEverything
1127+sendReloadEvent :: (MonadBase IO m, MonadReader AppState m) => m ()
1128+sendReloadEvent = do
1129+ sink <- asks eventSink
1130+ liftBase $ atomically $ writeTChan sink FrontendPleaseUpdateEverything
11131131
11141132-- | Runs the status updater.
1115−runStatusUpdater :: AppState -> IO ()
1116−runStatusUpdater state = do
1133+runStatusUpdater :: (KatipContext m, MonadBaseControl IO m, MonadReader AppState m) => m ()
1134+runStatusUpdater = do
11171135 let interval = 15 :: NominalDiffTime
1118− logErr :: MonadBase IO m => Text -> m ()
1119− logErr = liftBase . logWarning (logger state)
11201136
11211137 forever $ do
11221138 currentTime <- liftBase getCurrentTime
11231139 let cutoff = addUTCTime (negate interval) currentTime
1124− flip runReaderT state . (>>= logLeft) . runExceptT $ do
1140+ (>>= logLeft) . runExceptT $ do
11251141 rows' <- runStatement . select $ do
11261142 ds <- each deploymentSchema
11271143 where_ $ ds ^. #checkedAt <. litExpr cutoff
11281144 where_ $ not_ $ (ds ^. #status) `in_` (litExpr <$> [Archived, CleanupFailed])
11291145 pure (ds ^. #name, ds ^. #status, now `diffTime` (ds ^. #statusUpdatedAt))
11301146 checkResult <- for rows' $ \(dName, dStatus, Timestamp -> ts) -> do
1131− let timeout = statusUpdateTimeout state
1147+ timeout <- asks statusUpdateTimeout
11321148 mEc <-
11331149 getSingleFullInfo dName >>= \case
1134− Nothing -> liftBase (logErr $ "Couldn't find deployment: " <> coerce dName) $> Nothing
1150+ Nothing -> do
1151+ logLocM ErrorS $ logStr $ "Couldn't find deployment: " <> unDeploymentName dName
1152+ pure Nothing
11351153 Just (view #deployment -> dep) -> do
11361154 cfg <- getDeploymentConfig dep
11371155 case dStatus of
@@ -1160,16 +1178,13 @@ runStatusUpdater state = do
11601178 ($> True) $
11611179 (runExceptT . runExceptT) (transitionToStatus dName transition) >>= \case
11621180 Right (Right ()) -> updateCheckedAt (transitionStatus transition)
1163− Left e -> logErr $ show' e
1164− Right (Left e) -> logErr $ show' e
1165− when (Prelude.or updated) $ liftBase $ sendReloadEvent state
1166− liftBase $ threadDelay 2000000
1167−
1168−logLeft :: (MonadBase IO m, MonadReader AppState m, Exception e) => Either e () -> m ()
1169−logLeft (Left err) = do
1170− state <- ask
1171− let logErr = liftBase . logWarning (logger state)
1172− logErr . T.pack $ displayException err
1181+ Left e -> logLocM ErrorS $ show' e
1182+ Right (Left e) -> logLocM ErrorS $ show' e
1183+ when (Prelude.or updated) sendReloadEvent
1184+ threadDelay 2000000
1185+
1186+logLeft :: (HasCallStack, KatipContext m, Exception e) => Either e () -> m ()
1187+logLeft (Left err) = logLocM ErrorS $ logStr $ displayException err
11731188logLeft (Right ()) = pure ()
11741189
11751190-- | Returns the new deployment status.
@@ -1209,12 +1224,12 @@ failIfGracefulShutdownActivated = do
12091224isShuttingDown :: (MonadReader AppState m, MonadBase IO m) => m Bool
12101225isShuttingDown = do
12111226 gracefulShutdownAct <- gracefulShutdownActivated <$> ask
1212− liftBase . readIORef $ gracefulShutdownAct
1227+ readIORef $ gracefulShutdownAct
12131228
12141229-- | Handles the graceful shutdown signal.
12151230-- Sends a signal to the 'shutdownSem' semaphore
12161231-- if the background worker counter is 0.
1217−terminationHandler :: IORef Int -> IORef Bool -> MVar () -> IO ()
1232+terminationHandler :: MonadBase IO m => IORef Int -> IORef Bool -> MVar () -> m ()
12181233terminationHandler bgWorkersC gracefulShudownAct shutdownS = do
12191234 atomicWriteIORef gracefulShudownAct True
12201235 c <- readIORef bgWorkersC
@@ -1224,8 +1239,10 @@ terminationHandler bgWorkersC gracefulShudownAct shutdownS = do
12241239
12251240-- | Waits on the 'shutdownSem' semaphore to determine when the
12261241-- server can shut down.
1227−runShutdownHandler :: AppState -> IO ()
1228−runShutdownHandler = takeMVar . shutdownSem
1242+runShutdownHandler :: (MonadBase IO m, MonadReader AppState m) => m ()
1243+runShutdownHandler = do
1244+ sem <- asks shutdownSem
1245+ takeMVar sem
12291246
12301247-- | Runs background the worker and increases the background worker counter.
12311248-- Decreases background worker counter
@@ -1234,24 +1251,45 @@ runShutdownHandler = takeMVar . shutdownSem
12341251-- if the background worker counter is 0
12351252-- and graceful shutdown is activated.
12361253runBgWorker :: (MonadBaseControl IO m, MonadReader AppState m) => m () -> m ()
1237−runBgWorker act = void $ L.forkFinally act' cleanup
1254+runBgWorker act = void $ forkFinally act' cleanup
12381255 where
12391256 act' = do
12401257 state <- ask
1241− liftBase (atomicModifyIORef' (bgWorkersCounter state) (\c -> (c + 1, c))) >> act
1258+ atomicModifyIORef' (bgWorkersCounter state) $ \c -> (c + 1, ())
1259+ act
12421260 cleanup _ = do
12431261 state <- ask
1244− liftBase $ do
1245− c <- atomicModifyIORef' (bgWorkersCounter state) (\c -> (c - 1, c - 1))
1246− gracefulShutdown <- readIORef (gracefulShutdownActivated state)
1247− if gracefulShutdown && c == 0
1248− then putMVar (shutdownSem state) ()
1249− else pure ()
1262+ c <- atomicModifyIORef' (bgWorkersCounter state) $ \c -> (c - 1, c - 1)
1263+ isShutdown <- readIORef (gracefulShutdownActivated state)
1264+ if isShutdown && c == 0
1265+ then putMVar (shutdownSem state) ()
1266+ else pure ()
1267+
1268+runBgWorkerSync ::
1269+ (MonadBaseControl IO m, MonadReader AppState m) =>
1270+ -- | The computation to run, takes a callback for returning a result (and monadic state)
1271+ ((a -> m ()) -> m ()) ->
1272+ m a
1273+runBgWorkerSync act = do
1274+ result <- newEmptyMVar
1275+ runBgWorker $ do
1276+ let respondSync res = do
1277+ state <- liftBaseWith $ \runInBase -> runInBase $ pure res
1278+ tryPutMVar result (Right state) >>= \case
1279+ True -> pure ()
1280+ False -> error "result already submitted by bg worker"
1281+ void $
1282+ try (liftBaseWith $ \runInBase -> runInBase $ act respondSync) >>= \case
1283+ Left e -> tryPutMVar result (Left e)
1284+ Right _ -> tryPutMVar result $ Left $ errorCallException "bg worker did not submit result"
1285+ takeMVar result >>= \case
1286+ Left exc -> throwIO exc
1287+ Right stm -> restoreM stm
12501288
12511289-- | Same as 'runBgWorker' but also locks the specified deployment.
12521290runDeploymentBgWorker ::
12531291 forall m a.
1254− (MonadBaseControl IO m, MonadReader AppState m, MonadError ServerError m) =>
1292+ (KatipContext m, MonadBaseControl IO m, MonadReader AppState m, MonadError ServerError m) =>
12551293 -- | Status to set after the first part has finished.
12561294 Maybe DeploymentStatus ->
12571295 DeploymentName ->
@@ -1260,35 +1298,22 @@ runDeploymentBgWorker ::
12601298 m a ->
12611299 -- | The computation to run asynchronously.
12621300 (a -> m ()) ->
1263− m ()
1301+ m a
12641302runDeploymentBgWorker newS dName pre post = do
1265− st <- ask
1266− mainThread <- L.myThreadId
1267− (err :: MVar (Either ServerError (StM m a))) <- L.newEmptyMVar
1268− runBgWorker $
1269− withLockedDeployment
1270− dName
1271− (L.putMVar err $ Left err409 {errBody = "The deployment is currently being processed."})
1272− ( do
1273− L.try (liftBaseWith \runInBase -> runInBase pre) >>= \case
1274− Left (e :: L.SomeException) -> L.throwTo mainThread e
1275− Right x -> do
1276− proceed <- flip L.finally (L.tryPutMVar err (Left err500)) $ case newS of
1277− Just newS' -> do
1278− runExceptT (assertDeploymentTransitionPossibleS dName newS') >>= \case
1279− Left e -> L.putMVar err (Left e) $> False
1280− Right () -> L.putMVar err (Right x) $> True
1281− Nothing -> L.putMVar err (Right x) $> True
1282− liftBase (sendReloadEvent st)
1283− when proceed $ restoreM x >>= post
1284− )
1285− L.readMVar err >>= \case
1286− Left e -> throwError e
1287− Right (stm :: StM m a) -> do
1288− -- This might be a bad idea in general, but with just ReaderT and ExceptT
1289− -- it should be fine.
1290− _ :: a <- restoreM stm
1291− return ()
1303+ liftEither =<< runBgWorkerSync worker
1304+ where
1305+ worker respondSync = do
1306+ withLockedDeployment
1307+ dName
1308+ (respondSync $ Left err409 {errBody = "The deployment is currently being processed."})
1309+ ( do
1310+ let preWithAssert = pre <* for newS (assertDeploymentTransitionPossibleS dName)
1311+ result <- catchError (Right <$> preWithAssert) (pure . Left)
1312+ respondSync result
1313+ liftEither result >>= \res -> do
1314+ sendReloadEvent
1315+ post res
1316+ )
12921317
12931318extractDeploymentFullInfo ::
12941319 (MonadReader AppState m, MonadBase IO m) =>
octopod-backend/src/Octopod/Server/Args.hs+2−1
@@ -8,6 +8,7 @@ module Octopod.Server.Args
88 )
99where
1010
11+import Control.Monad.IO.Class
1112import Data.ByteString (ByteString)
1213import Data.Coerce
1314import Options.Generic
@@ -48,7 +49,7 @@ data OctopodOpts = OctopodOpts
4849 deriving stock (Show)
4950
5051-- | Parses Octopod Server arguments.
51−parseArgs :: IO OctopodOpts
52+parseArgs :: MonadIO m => m OctopodOpts
5253parseArgs = do
5354 args <- getRecord "Octopod.Server"
5455 pure $
octopod-backend/src/Octopod/Server/ControlScriptUtils.hs+17−19
@@ -37,15 +37,14 @@ import Control.Monad.Base
3737import Control.Monad.Reader
3838import qualified Data.ByteString.Lazy as TL
3939import Data.Coerce
40+import qualified Data.ConfigTree as CT
4041import Data.Fixed
4142import Data.Generics.Product.Typed
42−import qualified Data.Map.Ordered.Strict as MO
4343import qualified Data.Text as T
4444import qualified Data.Text.Encoding as T
4545import Data.Time
46−import Octopod.Server.Logger
46+import Octopod.Server.Logging
4747import System.Exit
48−import System.Log.FastLogger
4948import System.Process (terminateProcess)
5049import System.Process.Typed
5150import Types
@@ -119,12 +118,12 @@ archiveCommandArgs = genericDeploymentCommandArgs
119118cleanupCommandArgs :: GenericDeploymentCommandArgs m r
120119cleanupCommandArgs = genericDeploymentCommandArgs
121120
122−archiveCheckArgs :: GenericDeploymentCommandArgs m r
123−archiveCheckArgs = genericDeploymentCommandArgs
124−
125121checkCommandArgs :: GenericDeploymentCommandArgs m r
126122checkCommandArgs = genericDeploymentCommandArgs
127123
124+archiveCheckArgs :: GenericDeploymentCommandArgs m r
125+archiveCheckArgs = genericDeploymentCommandArgs
126+
128127configCheckCommandArgs :: GenericDeploymentCommandArgs m r
129128configCheckCommandArgs = genericDeploymentCommandArgs
130129
@@ -179,9 +178,9 @@ notificationCommandArgs dName old new = do
179178 ]
180179
181180runCommandArgs ::
182− ( MonadReader r m
181+ ( KatipContext m
183182 , MonadBase IO m
184− , HasType TimedFastLogger r
183+ , MonadReader r m
185184 , HasType ControlScriptTimeout r
186185 ) =>
187186 (r -> Command) ->
@@ -192,8 +191,8 @@ runCommandArgs f args = do
192191 runCommandArgs' cmd args
193192
194193runCommandArgs' ::
195− ( MonadBase IO m
196− , HasType TimedFastLogger r
194+ ( KatipContext m
195+ , MonadBase IO m
197196 , MonadReader r m
198197 , HasType ControlScriptTimeout r
199198 ) =>
@@ -202,14 +201,7 @@ runCommandArgs' ::
202201 m (ExitCode, Stdout, Stderr, Duration)
203202runCommandArgs' (Command cmd) (ControlScriptArgs args) = do
204203 t1 <- liftBase getCurrentTime
205−
206− logger <- asks (getTyped @TimedFastLogger)
207− let logText = T.unwords (cmd : fmap T.pack args)
208− liftBase $ logInfo logger $ "calling: " <> logText
209204 (ec, out, err) <- runCommand (T.unpack cmd) args
210− liftBase $
211− logInfo logger $
212− "calling `" <> logText <> "` exited with: " <> show' ec
213205 t2 <- liftBase getCurrentTime
214206 let elTime = elapsedTime t2 t1
215207 return (ec, out, err, elTime)
@@ -220,7 +212,8 @@ elapsedTime t1 t2 = Duration . calendarTimeTime $ Prelude.abs $ t2 `diffUTCTime`
220212
221213-- | Helper to run command with pipes.
222214runCommand ::
223− ( MonadBase IO m
215+ ( KatipContext m
216+ , MonadBase IO m
224217 , MonadReader r m
225218 , HasType ControlScriptTimeout r
226219 ) =>
@@ -228,6 +221,7 @@ runCommand ::
228221 [String] ->
229222 m (ExitCode, Stdout, Stderr)
230223runCommand cmd args = do
224+ logLocM DebugS $ "Calling control script: " <> logStr cmd <> " " <> show' args
231225 ControlScriptTimeout timeout <- asks getTyped
232226 let pc =
233227 proc cmd args
@@ -243,6 +237,10 @@ runCommand cmd args = do
243237 <$> waitExitCodeSTM p
244238 <*> getStdout p
245239 <*> getStderr p
240+ katipAddContext (FilePayload "stdout" $ TL.toStrict out) $
241+ katipAddContext (FilePayload "stderr" $ TL.toStrict err) $
242+ logLocM (if ec /= ExitSuccess then WarningS else DebugS) $
243+ "Control script " <> logStr cmd <> " " <> show' args <> " exited with: " <> show' ec
246244 pure
247245 ( ec
248246 , Stdout . T.decodeUtf8 . TL.toStrict $ out
@@ -259,7 +257,7 @@ overridesArgs :: forall l. KnownOverrideLevel l => Config l -> ControlScriptArgs
259257overridesArgs (Config cc) =
260258 ControlScriptArgs
261259 . concatMap (\(T.unpack -> k, T.unpack -> v) -> [argumentName, k <> "=" <> v])
262− . MO.assocs
260+ . CT.toFlatList
263261 $ cc
264262 where
265263 argumentName = case knownOverrideLevel @l of
octopod-backend/src/Octopod/Server/Logger.hsdeleted−42
@@ -1,42 +0,0 @@
1−-- |
2−--Module : Octopod.Server.Logger
3−--Description : Octopod Server logger.
4−module Octopod.Server.Logger
5− ( newLogger,
6− logInfo,
7− logWarning,
8− logWithSeverity,
9− show',
10− )
11−where
12−
13−import Data.ByteString
14−import Data.Text
15−import qualified Data.Text as T
16−import System.Log.FastLogger
17−
18−-- | Creates new logger.
19−newLogger :: IO TimedFastLogger
20−newLogger = do
21− timeCache <- newTimeCache "%Y-%m-%d %T%z"
22− (logger, _) <- newTimedFastLogger timeCache (LogStdout defaultBufSize)
23− pure logger
24−
25−-- | Logs a message with the 'INFO' severity.
26−logInfo :: TimedFastLogger -> Text -> IO ()
27−logInfo l = logWithSeverity l "INFO"
28−
29−-- | Logs a message with the 'WARN' severity.
30−logWarning :: TimedFastLogger -> Text -> IO ()
31−logWarning l = logWithSeverity l "WARN"
32−
33−-- | Logs a message with the specified severity.
34−logWithSeverity :: ToLogStr msg => TimedFastLogger -> ByteString -> msg -> IO ()
35−logWithSeverity l severity msg = l $ \ft -> metadata ft <> message
36− where
37− metadata :: ByteString -> LogStr
38− metadata ft = foldMap toLogStr ["[", ft, " ", severity, "] "]
39− message = toLogStr msg <> toLogStr ("\n" :: ByteString)
40−
41−show' :: Show a => a -> Text
42−show' = T.pack . show
octopod-backend/src/Octopod/Server/Logging.hsnew file+121
@@ -0,0 +1,121 @@
1+-- |
2+--Module : Octopod.Server.Logging
3+--Description : Octopod Server logging setup
4+module Octopod.Server.Logging
5+ ( runLog,
6+ LogConfig (..),
7+ show',
8+ logLocM,
9+ logStr,
10+ katipAddContext,
11+ katipAddNamespace,
12+ Severity (..),
13+ KatipContext,
14+ KatipContextT,
15+ LogItem (..),
16+ ToObject (..),
17+ PayloadSelection (..),
18+ FilePayload (..),
19+ )
20+where
21+
22+import qualified Control.Exception.Lifted as L
23+import Control.Lens hiding (scribe, (.=))
24+import Control.Monad
25+import Control.Monad.Base
26+import Control.Monad.Trans.Control
27+import Data.Aeson
28+import Data.ByteString (ByteString)
29+import qualified Data.HashMap.Strict as HM
30+import Data.Text (Text)
31+import qualified Data.Text as T
32+import qualified Data.Text.Encoding as T
33+import Data.Text.Lazy.Builder
34+import GHC.Generics
35+import Katip
36+import Katip.Format.Time
37+import Katip.Scribes.Handle
38+import System.IO
39+import Types hiding (stdout)
40+
41+data LogConfig = LogConfig
42+ { project :: ProjectName
43+ , debug :: Bool
44+ , prodLogs :: Bool
45+ }
46+ deriving stock (Generic)
47+
48+newtype SingleLineify a = SingleLineify a
49+ deriving newtype (LogItem)
50+
51+instance ToObject a => ToObject (SingleLineify a) where
52+ toObject (SingleLineify x) = fmap go $ toObject x
53+ where
54+ go (Object obj) = Object $ fmap go obj
55+ go (Array v) = Array $ fmap go v
56+ go (String s) = String $ T.concat $ escape s
57+ go value = value
58+ escape s = case T.break (`elem` ("\a\b\f\n\r\t\v\\" :: String)) s of
59+ (xs, ys) ->
60+ xs : case T.uncons ys of
61+ Nothing -> []
62+ Just (c, zs) -> case c of
63+ '\a' -> "\\a" : escape zs
64+ '\b' -> "\\b" : escape zs
65+ '\f' -> "\\f" : escape zs
66+ '\n' -> "\\n" : escape zs
67+ '\r' -> "\\r" : escape zs
68+ '\t' -> "\\t" : escape zs
69+ '\v' -> "\\v" : escape zs
70+ '\\' -> "\\\\" : escape zs
71+ _ -> escape ys
72+
73+makeLogEnv :: LogConfig -> IO LogEnv
74+makeLogEnv conf = do
75+ scribe <- mkHandleScribeWithFormatter formatter ColorIfTerminal stdout (permitItem severity) verbosity
76+ env <- initLogEnv "octopod" $ Environment $ uProjectName $ conf ^. #project
77+ env' <- registerScribe "stdout" scribe defaultScribeSettings env
78+ pure env'
79+ where
80+ verbosity = if conf ^. #prodLogs then V3 else V0
81+ severity = if conf ^. #debug then DebugS else InfoS
82+ formatter :: LogItem a => ItemFormatter a
83+ formatter =
84+ if conf ^. #prodLogs
85+ then \color verb item -> bracketFormat color verb item {_itemPayload = SingleLineify $ _itemPayload item}
86+ else \color verb item ->
87+ brackets (fromText $ formatAsLogTime $ _itemTime item)
88+ <> brackets (fromText $ colorBySeverity color (_itemSeverity item) $ renderSeverity $ _itemSeverity item)
89+ <> mconcat (map brackets $ getKeys verb $ _itemPayload item)
90+ <> " "
91+ <> unLogStr (_itemMessage item)
92+ <> mconcat (("\n" <>) <$> renderFiles (toObject $ _itemPayload item))
93+ renderFiles :: Object -> [Builder]
94+ renderFiles obj =
95+ HM.toList obj >>= \(k, v) -> case (T.stripPrefix "file-" k, v) of
96+ (Just fn, String text) | not $ T.null text -> pure $ fromText fn <> ":\n" <> fromText (tabulate text)
97+ _ -> []
98+ tabulate = ("\t" <>) . T.replace "\n" "\n\t"
99+
100+destroyLogEnv :: LogEnv -> IO ()
101+destroyLogEnv = void . closeScribes
102+
103+runLog :: MonadBaseControl IO m => LogConfig -> KatipContextT m a -> m a
104+runLog conf act = L.bracket (liftBase $ makeLogEnv conf) (liftBase . destroyLogEnv) $
105+ \env -> runKatipContextT env () mempty act
106+
107+show' :: Show a => a -> LogStr
108+show' = logStr . show
109+
110+data FilePayload = FilePayload
111+ { filename :: Text
112+ , contents :: ByteString
113+ }
114+ deriving stock (Generic)
115+
116+instance ToObject FilePayload where
117+ toObject fp = ("file-" <> fp ^. #filename) .= T.decodeUtf8 (fp ^. #contents)
118+
119+instance LogItem FilePayload where
120+ payloadKeys V3 _ = AllKeys
121+ payloadKeys _ _ = SomeKeys []
octopod-backend/src/Octopod/Server/Posix.hs+21−13
@@ -1,22 +1,30 @@
11module Octopod.Server.Posix (installShutdownHandler) where
22
3−import Control.Monad
4−import Control.Monad.Base (liftBase)
5−import Data.Text
6−import System.Log.FastLogger
3+import Control.Monad.Trans.Control
4+import Data.Aeson
5+import Data.Traversable
6+import Octopod.Server.Logging
77import System.Posix.Signals
88
9−import Octopod.Server.Logger
9+newtype SigContext = SigContext Signal
10+
11+instance ToObject SigContext where
12+ toObject (SigContext signal) = "signal" .= show signal
13+
14+instance LogItem SigContext where
15+ payloadKeys _ _ = AllKeys
1016
1117-- | Installs the given shutdown handler for the specified signals.
1218installShutdownHandler ::
13− TimedFastLogger ->
19+ (KatipContext m, MonadBaseControl IO m, StM m () ~ ()) =>
1420 [Signal] ->
15− IO () ->
16− IO [Handler]
17−installShutdownHandler logger signals action =
18− forM signals $ \signal -> installHandler signal (handler signal) Nothing
21+ m () ->
22+ m [Handler]
23+installShutdownHandler signals action =
24+ for signals $ \signal -> liftBaseWith $ \run ->
25+ installHandler signal (Catch $ run (handler signal)) Nothing
1926 where
20− handler signal = Catch $ do
21− logInfo logger $ "Shutdown initiated by signal " <> (pack . show $ signal)
22− liftBase action
27+ handler signal = katipAddNamespace "signal handler" $
28+ katipAddContext (SigContext signal) $ do
29+ logLocM InfoS "Shutdown initiated by signal"
30+ action
octopod-common/octopod-common.cabal+6−1
@@ -18,8 +18,9 @@ library
1818 exposed-modules: Common.Types
1919 , Common.Utils
2020 , Common.Validation
21− , Data.Map.Ordered.Strict.Extra
21+ , Data.ConfigTree
2222 , Control.Searchable
23+ , Common.Orphans
2324 -- other-modules:
2425 -- other-extensions:
2526 default-extensions: BlockArguments
@@ -54,6 +55,8 @@ library
5455 , ApplicativeDo
5556 , UndecidableInstances
5657 , FunctionalDependencies
58+ , QuantifiedConstraints
59+ , PatternSynonyms
5760 build-depends: base
5861 , aeson
5962 , cassava
@@ -66,6 +69,8 @@ library
6669 , containers
6770 , time
6871 , ordered-containers
72+ , deepseq
73+ , these
6974 hs-source-dirs: src
7075 default-language: Haskell2010
7176 ghc-options:
octopod-common/src/Common/Orphans.hsnew file+20
@@ -0,0 +1,20 @@
1+{-# OPTIONS_GHC -Wno-orphans #-}
2+
3+module Common.Orphans () where
4+
5+import Control.DeepSeq
6+import Data.Aeson
7+import Data.Map.Internal
8+import Data.Map.Ordered.Internal
9+import qualified Data.Map.Ordered.Strict as OM
10+import GHC.Generics (Generic)
11+
12+deriving stock instance Generic (Map k v)
13+
14+deriving stock instance Generic (OMap k v)
15+deriving anyclass instance (NFData k, NFData v) => NFData (OMap k v)
16+instance (ToJSON k, ToJSON v) => ToJSON (OMap k v) where
17+ toJSON = toJSON . OM.assocs
18+
19+instance (FromJSON k, FromJSON v, Ord k) => FromJSON (OMap k v) where
20+ parseJSON = fmap OM.fromList . parseJSON
octopod-common/src/Common/Types.hs+54−55
@@ -7,15 +7,17 @@
77-- This module contains common types between the backend and the frontend.
88module Common.Types where
99
10+import Common.Orphans ()
11+import Control.DeepSeq (NFData)
1012import Control.Lens
1113import Control.Searchable
1214import Data.Aeson hiding (Result)
15+import Data.ConfigTree (ConfigTree)
16+import qualified Data.ConfigTree as CT
1317import Data.Csv
1418import Data.Generics.Labels ()
1519import Data.Int
16−import Data.Map.Ordered.Strict (OMap, (<>|))
17−import qualified Data.Map.Ordered.Strict as OM
18−import Data.Map.Ordered.Strict.Extra ()
20+import Data.List.NonEmpty (NonEmpty)
1921import Data.Maybe
2022import Data.Text (Text)
2123import qualified Data.Text as T
@@ -46,26 +48,27 @@ instance Searchable needle t => Searchable needle (OverrideValue' t) where
4648data OverrideValue' t = ValueAdded t | ValueDeleted
4749 deriving (ToJSON, FromJSON) via Snake (OverrideValue' t)
4850 deriving stock (Eq, Ord, Show, Generic)
51+ deriving anyclass (NFData)
4952
5053type OverrideValue = OverrideValue' Text
5154
5255type DefaultConfig = DefaultConfig' Text
5356
54−newtype DefaultConfig' te (l :: OverrideLevel) = DefaultConfig (OMap te te)
55− deriving newtype (Eq, Ord, Show, ToJSON, FromJSON)
57+newtype DefaultConfig' te (l :: OverrideLevel) = DefaultConfig (ConfigTree te te)
58+ deriving newtype (Eq, Ord, Show)
59+
60+deriving newtype instance FromJSON (DefaultConfig' Text l)
61+deriving newtype instance ToJSON (DefaultConfig' Text l)
5662
5763instance Searchable t x => Searchable t (DefaultConfig' x l) where
5864 type SearchableConstraint t (DefaultConfig' x l) res = (Ord (Searched x res), SearchableConstraint t x res)
5965 type Searched (DefaultConfig' x l) res = DefaultConfig' (Searched x res) l
60− searchWith f (DefaultConfig oMap) = do
61− oMap' <- searchWith f . OM.assocs $ oMap
62− pure $ DefaultConfig $ OM.fromList oMap'
66+ searchWith f (DefaultConfig ct) = DefaultConfig <$> searchWith f ct
6367 {-# INLINE searchWith #-}
6468
65−lookupDefaultConfig :: DefaultConfig l -> Text -> Maybe Text
66−lookupDefaultConfig (DefaultConfig m) k = OM.lookup k m
69+type ConfigKey = [Text]
6770
68−newtype Config (l :: OverrideLevel) = Config {unConfig :: OMap Text Text}
71+newtype Config (l :: OverrideLevel) = Config {unConfig :: ConfigTree Text Text}
6972 deriving newtype (Eq, Ord, Show, ToJSON, FromJSON)
7073
7174data FullDefaultConfig = FullDefaultConfig
@@ -84,63 +87,58 @@ data FullConfig = FullConfig
8487
8588applyOverrides :: Overrides l -> DefaultConfig l -> Config l
8689applyOverrides (Overrides oo) (DefaultConfig dd) =
87− Config . extract $ oo <>| (ValueAdded <$> dd)
90+ Config . extract $ oo <> (ValueAdded <$> dd)
8891 where
89− extract :: OMap Text OverrideValue -> OMap Text Text
92+ extract :: ConfigTree Text OverrideValue -> ConfigTree Text Text
9093 extract =
9194 fmap
9295 ( \case
9396 ValueAdded v -> v
9497 ValueDeleted -> error "invariant"
9598 )
96− . OM.filter
97− ( \_ -> \case
99+ . CT.filter
100+ ( \case
98101 ValueAdded _ -> True
99102 ValueDeleted -> False
100103 )
101104
105+newtype Overrides' t (l :: OverrideLevel) = Overrides {unOverrides :: ConfigTree t (OverrideValue' t)}
106+ deriving newtype (Eq, Ord, Show, NFData, Semigroup, Monoid)
107+
108+deriving newtype instance FromJSON (Overrides' Text l)
109+deriving newtype instance ToJSON (Overrides' Text l)
110+
102111instance Searchable t x => Searchable t (Overrides' x l) where
103112 type SearchableConstraint t (Overrides' x l) res = (Ord (Searched x res), SearchableConstraint t x res)
104113 type Searched (Overrides' x l) res = Overrides' (Searched x res) l
105− searchWith f (Overrides oMap) = do
106− oMap' <- searchWith f . OM.assocs $ oMap
107− pure $ Overrides $ OM.fromList oMap'
114+ searchWith f (Overrides ct) = Overrides <$> searchWith f ct
108115 {-# INLINE searchWith #-}
109116
110−newtype Overrides' t (l :: OverrideLevel) = Overrides {unOverrides :: OMap t (OverrideValue' t)}
111− deriving newtype (Eq, Ord, Show, ToJSON, FromJSON)
112−
113117type Overrides = Overrides' Text
114118
115119extractOverrides :: DefaultConfig l -> Config l -> Overrides l
116120extractOverrides (DefaultConfig dCfg) (Config cfg) =
117− Overrides . OM.fromList $ removed <> present
121+ Overrides . CT.fromList $ removed <> present
118122 where
119− present :: [(Text, OverrideValue)]
120− present = mapMaybe processPresent . OM.assocs $ cfg
123+ present :: [(NonEmpty Text, OverrideValue)]
124+ present = mapMaybe processPresent . CT.toList $ cfg
121125
122− processPresent :: (Text, Text) -> Maybe (Text, OverrideValue)
123− processPresent (k, v) = case OM.lookup k dCfg of
126+ processPresent :: (NonEmpty Text, Text) -> Maybe (NonEmpty Text, OverrideValue)
127+ processPresent (k, v) = case CT.lookup k dCfg of
124128 Just v' | v == v' -> Nothing
125129 _ -> Just (k, ValueAdded v)
126130
127− processRemoved :: (Text, Text) -> Maybe (Text, OverrideValue)
131+ processRemoved :: (NonEmpty Text, Text) -> Maybe (NonEmpty Text, OverrideValue)
128132 processRemoved (k, _) =
129− if OM.member k cfg
133+ if CT.member k cfg
130134 then Nothing
131135 else Just (k, ValueDeleted)
132136
133− removed :: [(Text, OverrideValue)]
134− removed = mapMaybe processRemoved . OM.assocs $ dCfg
135−
136−ov :: Text -> OverrideValue -> Overrides l
137−ov k v = Overrides $ OM.singleton (k, v)
137+ removed :: [(NonEmpty Text, OverrideValue)]
138+ removed = mapMaybe processRemoved . CT.toList $ dCfg
138139
139−instance Semigroup (Overrides l) where
140− (Overrides lhs) <> (Overrides rhs) = Overrides $ rhs <>| lhs
141−
142−instance Monoid (Overrides l) where
143− mempty = Overrides OM.empty
140+ov :: NonEmpty Text -> OverrideValue -> Overrides l
141+ov k v = Overrides $ CT.singleton k v
144142
145143newtype DeploymentId = DeploymentId {unDeploymentId :: Int64}
146144 deriving stock (Show)
@@ -153,7 +151,7 @@ instance Searchable needle t => Searchable needle (DeploymentName' t) where
153151
154152newtype DeploymentName' t = DeploymentName {unDeploymentName :: t}
155153 deriving newtype
156− (Show, Read, FromJSON, ToJSON, ToHttpApiData, FromHttpApiData, Eq, Ord)
154+ (Show, Read, FromJSON, ToJSON, ToHttpApiData, FromHttpApiData, Eq, Ord, NFData)
157155
158156type DeploymentName = DeploymentName' Text
159157
@@ -210,6 +208,7 @@ data DeploymentStatus
210208 | CleanupFailed
211209 deriving stock (Generic, Read, Show, Eq)
212210 deriving (FromJSON, ToJSON) via Snake DeploymentStatus
211+ deriving anyclass (NFData)
213212
214213data FailureType
215214 = GenericFailure
@@ -217,6 +216,7 @@ data FailureType
217216 | PartialAvailability
218217 deriving stock (Generic, Read, Show, Eq)
219218 deriving (FromJSON, ToJSON) via Snake FailureType
219+ deriving anyclass (NFData)
220220
221221data PreciseDeploymentStatus
222222 = -- | The deployment is currently being processed by the server
@@ -224,6 +224,7 @@ data PreciseDeploymentStatus
224224 | DeploymentNotPending {recordedStatus :: DeploymentStatus}
225225 deriving stock (Generic, Read, Show, Eq)
226226 deriving (FromJSON, ToJSON) via Snake PreciseDeploymentStatus
227+ deriving anyclass (NFData)
227228
228229archivedStatuses :: [DeploymentStatus]
229230archivedStatuses = [ArchivePending, Archived]
@@ -237,7 +238,10 @@ data Deployment' t = Deployment
237238 , deploymentOverrides :: Overrides' t 'DeploymentLevel
238239 }
239240 deriving stock (Generic, Show, Eq)
240− deriving (FromJSON, ToJSON) via Snake (Deployment' t)
241+ deriving anyclass (NFData)
242+
243+deriving via Snake (Deployment' Text) instance FromJSON (Deployment' Text)
244+deriving via Snake (Deployment' Text) instance ToJSON (Deployment' Text)
241245
242246instance (Searchable needle t) => Searchable needle (Deployment' t) where
243247 type
@@ -271,7 +275,7 @@ data DeploymentLog = DeploymentLog
271275 deriving (ToJSON, FromJSON) via Snake DeploymentLog
272276
273277newtype DeploymentMetadata = DeploymentMetadata {unDeploymentMetadata :: [DeploymentMetadatum]}
274− deriving newtype (Eq, Show, Ord, FromJSON, ToJSON)
278+ deriving newtype (Eq, Show, Ord, FromJSON, ToJSON, NFData)
275279
276280data DeploymentMetadatum = DeploymentMetadatum
277281 { -- | The name of the link
@@ -282,6 +286,7 @@ data DeploymentMetadatum = DeploymentMetadatum
282286 deriving stock (Generic, Show, Eq, Ord)
283287 deriving (FromJSON, ToJSON) via Snake DeploymentMetadatum
284288 deriving anyclass (FromRecord)
289+ deriving anyclass (NFData)
285290
286291data DeploymentInfo = DeploymentInfo
287292 { deployment :: Deployment
@@ -309,7 +314,10 @@ data DeploymentFullInfo' t = DeploymentFullInfo
309314 , updatedAt :: UTCTime
310315 }
311316 deriving stock (Generic, Show, Eq)
312− deriving (FromJSON, ToJSON) via Snake (DeploymentFullInfo' t)
317+ deriving anyclass (NFData)
318+
319+deriving via Snake (DeploymentFullInfo' Text) instance FromJSON (DeploymentFullInfo' Text)
320+deriving via Snake (DeploymentFullInfo' Text) instance ToJSON (DeploymentFullInfo' Text)
313321
314322type DeploymentFullInfo = DeploymentFullInfo' Text
315323
@@ -377,23 +385,14 @@ parseSetOverrides texts = do
377385 Just x -> Right x
378386 Nothing ->
379387 Left $ "Malformed override key-value pair " <> text <> ", should be similar to FOO=bar"
380− return . Overrides $ OM.fromList pairs'
388+ return . Overrides $ CT.fromList pairs'
381389 where
382− parseSingleOverride :: Text -> Maybe (Text, OverrideValue)
390+ parseSingleOverride :: Text -> Maybe (NonEmpty Text, OverrideValue)
383391 parseSingleOverride t
384392 | Just i <- T.findIndex (== '=') t =
385− let (key, value) = bimap T.strip (T.tail . T.strip) $ T.splitAt i t
393+ let (CT.deconstructConfigKey -> key, value) = bimap T.strip (T.tail . T.strip) $ T.splitAt i t
386394 in Just (key, ValueAdded value)
387395 parseSingleOverride _ = Nothing
388396
389397parseUnsetOverrides :: [Text] -> Overrides l
390−parseUnsetOverrides = Overrides . OM.fromList . fmap (,ValueDeleted)
391−
392−formatOverrides :: Overrides l -> Text
393−formatOverrides = T.unlines . formatOverrides'
394−
395−formatOverrides' :: Overrides l -> [Text]
396−formatOverrides' (Overrides m) = fmap (\(k, v) -> k <> "=" <> showValue v) . OM.assocs $ m
397− where
398− showValue (ValueAdded v) = v
399− showValue ValueDeleted = "<removed>"
398+parseUnsetOverrides = Overrides . CT.fromList . fmap ((,ValueDeleted) . CT.deconstructConfigKey)
octopod-common/src/Control/Searchable.hs+20−1
@@ -2,11 +2,14 @@
22
33module Control.Searchable
44 ( Searchable (..),
5+ traverseOMap,
56 )
67where
78
89import Control.Lens
910import Data.Kind
11+import Data.Map.Ordered (OMap)
12+import qualified Data.Map.Ordered.Strict as OM
1013import Data.Text (Text)
1114
1215-- | This says that you can search 'needle's in a 'haystack'. What precisely
@@ -38,10 +41,26 @@ instance Searchable needle x => Searchable needle [x] where
3841 {-# INLINE searchWith #-}
3942
4043instance (Searchable needle a, Searchable needle b) => Searchable needle (a, b) where
41− type SearchableConstraint needle (a, b) res = (SearchableConstraint needle a res, SearchableConstraint needle b res)
44+ type
45+ SearchableConstraint needle (a, b) res =
46+ (SearchableConstraint needle a res, SearchableConstraint needle b res)
4247 type Searched (a, b) res = (Searched a res, Searched b res)
4348 searchWith f (a, b) = do
4449 a' <- searchWith f a
4550 b' <- searchWith f b
4651 pure (a', b')
4752 {-# INLINE searchWith #-}
53+
54+instance Searchable needle haystack => Searchable needle (Maybe haystack) where
55+ type SearchableConstraint needle (Maybe haystack) res = SearchableConstraint needle haystack res
56+ type Searched (Maybe haystack) res = Maybe (Searched haystack res)
57+ searchWith _ Nothing = pure Nothing
58+ searchWith f (Just h) = Just <$> searchWith f h
59+ {-# INLINE searchWith #-}
60+
61+traverseOMap ::
62+ forall h k x y.
63+ Ord k =>
64+ (forall f. Applicative f => (h -> f k) -> (x -> f y) -> OMap h x -> f (OMap k y))
65+traverseOMap kf vf (OM.assocs -> l) =
66+ fmap OM.fromList $ (\(h, x) -> (,) <$> kf h <*> vf x) `traverse` l
octopod-common/src/Data/ConfigTree.hsnew file+165
@@ -0,0 +1,165 @@
1+module Data.ConfigTree
2+ ( ConfigTree (..),
3+ null,
4+ filter,
5+ toList,
6+ singleton,
7+ fromList,
8+ lookup,
9+ member,
10+ deconstructConfigKey,
11+ reconstructConfigKey,
12+ toFlatList,
13+ fromFlatList,
14+ Data.ConfigTree.zip,
15+ catMaybes,
16+ insert,
17+ markValues,
18+ )
19+where
20+
21+import Common.Orphans ()
22+import Control.Applicative
23+import Control.DeepSeq (NFData)
24+import Control.Searchable
25+import Data.Aeson
26+import qualified Data.Bifunctor as Bi
27+import Data.Foldable (fold)
28+import qualified Data.List as L
29+import Data.List.NonEmpty (NonEmpty)
30+import qualified Data.List.NonEmpty as NE
31+import Data.Map.Ordered.Strict (OMap)
32+import qualified Data.Map.Ordered.Strict as OM
33+import Data.Maybe (fromJust, fromMaybe, isJust)
34+import Data.String
35+import Data.Text (Text)
36+import qualified Data.Text as T
37+import Data.These
38+import GHC.Generics (Generic)
39+import Prelude hiding (filter, lookup, null)
40+
41+zip :: Ord k => ConfigTree k v -> ConfigTree k h -> ConfigTree k (These v h)
42+zip (ConfigTree m) (ConfigTree w) =
43+ let intersection =
44+ OM.intersectionWith
45+ ( \_ (mv, vMap) (mh, hMap) ->
46+ ( case (mv, mh) of
47+ (Nothing, Nothing) -> Nothing
48+ (Just v, Nothing) -> Just $ This v
49+ (Nothing, Just h) -> Just $ That h
50+ (Just v, Just h) -> Just $ These v h
51+ , Data.ConfigTree.zip vMap hMap
52+ )
53+ )
54+ m
55+ w
56+ vCT = ConfigTree $ m OM.\\ intersection
57+ hCT = ConfigTree $ w OM.\\ intersection
58+ in fmap This vCT <> ConfigTree intersection <> fmap That hCT
59+
60+newtype ConfigTree k v = ConfigTree (OMap k (Maybe v, ConfigTree k v))
61+ deriving newtype (Eq, Ord, Show, NFData)
62+ deriving stock (Functor, Generic)
63+
64+instance ToJSON v => ToJSON (ConfigTree Text v) where
65+ toJSON = toJSON . toFlatList
66+
67+instance FromJSON v => FromJSON (ConfigTree Text v) where
68+ parseJSON = fmap fromFlatList . parseJSON
69+
70+markValues :: (IsString t, Monoid t, Ord t) => ConfigTree t c -> ConfigTree t (t, c)
71+markValues = fromList . fmap (\(k, v) -> (k, (reconstructConfigKey k, v))) . toList
72+{-# INLINE markValues #-}
73+
74+toFlatList :: (IsString t, Monoid t) => ConfigTree t c -> [(t, c)]
75+toFlatList = fmap (Bi.first reconstructConfigKey) . toList
76+{-# INLINE toFlatList #-}
77+
78+fromFlatList :: [(Text, v)] -> ConfigTree Text v
79+fromFlatList = fromList . fmap (Bi.first deconstructConfigKey)
80+{-# INLINE fromFlatList #-}
81+
82+toList :: ConfigTree k v -> [(NonEmpty k, v)]
83+toList (ConfigTree m) =
84+ OM.assocs m >>= \(k, (mv, s)) ->
85+ maybe id (\v -> (:) (pure k, v)) mv $ Bi.first (NE.cons k) <$> toList s
86+{-# INLINE toList #-}
87+
88+fromList :: Ord k => [(NonEmpty k, v)] -> ConfigTree k v
89+fromList = foldMap $ uncurry singleton
90+{-# INLINE fromList #-}
91+
92+singleton :: Ord k => NonEmpty k -> v -> ConfigTree k v
93+singleton (NE k) v = ConfigTree $ OM.singleton (k, (Just v, mempty))
94+singleton (NECons k ks) v = ConfigTree $ OM.singleton (k, (Nothing, singleton ks v))
95+{-# INLINE singleton #-}
96+
97+lookup :: Ord k => NonEmpty k -> ConfigTree k v -> Maybe v
98+lookup (NE k) (ConfigTree m) = OM.lookup k m >>= fst
99+lookup (NECons k ks) (ConfigTree m) = OM.lookup k m >>= lookup ks . snd
100+{-# INLINE lookup #-}
101+
102+member :: Ord k => NonEmpty k -> ConfigTree k v -> Bool
103+member (NE k) (ConfigTree m) = OM.member k m
104+member (NECons k ks) (ConfigTree m) = maybe False (member ks . snd) $ OM.lookup k m
105+{-# INLINE member #-}
106+
107+null :: ConfigTree l v -> Bool
108+null (ConfigTree m) = OM.null m
109+{-# INLINE null #-}
110+
111+filter :: Ord k => (v -> Bool) -> ConfigTree k v -> ConfigTree k v
112+filter f (ConfigTree m) =
113+ ConfigTree $
114+ OM.filter (\_ (mv, ConfigTree m') -> isJust mv || not (OM.null m'))
115+ . fmap (\(mv, ct) -> (mv >>= (\v -> if f v then Just v else Nothing), filter f ct))
116+ $ m
117+{-# INLINE filter #-}
118+
119+instance Ord k => Semigroup (ConfigTree k v) where
120+ (ConfigTree lMap) <> (ConfigTree rMap) =
121+ ConfigTree $ OM.unionWithR (\_ (lvm, lm) (rvm, rm) -> (lvm <|> rvm, lm <> rm)) lMap rMap
122+ {-# INLINE (<>) #-}
123+
124+instance Ord k => Monoid (ConfigTree k v) where
125+ mempty = ConfigTree OM.empty
126+
127+instance (Searchable needle k, Searchable needle v) => Searchable needle (ConfigTree k v) where
128+ type
129+ SearchableConstraint needle (ConfigTree k v) res =
130+ (SearchableConstraint needle k res, SearchableConstraint needle v res, Ord (Searched k res))
131+ type Searched (ConfigTree k v) res = ConfigTree (Searched k res) (Searched v res)
132+ searchWith f (ConfigTree oMap) = do
133+ oMap' <- traverseOMap (searchWith f) (searchWith f) oMap
134+ pure $ ConfigTree oMap'
135+ {-# INLINE searchWith #-}
136+
137+deconstructConfigKey :: Text -> NonEmpty Text
138+deconstructConfigKey =
139+ fromMaybe (pure "") . NE.nonEmpty . L.filter (not . T.null) . fmap T.strip . T.splitOn "."
140+{-# INLINE deconstructConfigKey #-}
141+
142+reconstructConfigKey :: (IsString t, Monoid t) => NonEmpty t -> t
143+reconstructConfigKey = fold . NE.intersperse "."
144+{-# INLINE reconstructConfigKey #-}
145+
146+pattern NE :: a -> NonEmpty a
147+pattern NE x = x NE.:| []
148+pattern NECons :: a -> NonEmpty a -> NonEmpty a
149+pattern NECons x y <- (NE.uncons -> (x, Just y))
150+{-# COMPLETE NE, NECons #-}
151+
152+catMaybes :: Ord k => ConfigTree k (Maybe v) -> ConfigTree k v
153+catMaybes = fmap fromJust . filter isJust
154+{-# INLINE catMaybes #-}
155+
156+insert :: Ord k => NonEmpty k -> v -> ConfigTree k v -> ConfigTree k v
157+insert (NE k) v (ConfigTree m) = ConfigTree $ (k, (Just v, subCfg)) OM.<| m
158+ where
159+ subCfg = case OM.lookup k m of
160+ Nothing -> mempty
161+ Just (_, m') -> m'
162+insert (NECons k ks) v (ConfigTree m) = ConfigTree $ case OM.lookup k m of
163+ Nothing -> (k, (Nothing, singleton ks v)) OM.<| m
164+ Just (v', m') -> (k, (v', insert ks v m')) OM.<| m
165+{-# INLINE insert #-}
octopod-common/src/Data/Map/Ordered/Strict/Extra.hsdeleted−15
@@ -1,15 +0,0 @@
1−{-# OPTIONS_GHC -Wno-orphans #-}
2−
3−module Data.Map.Ordered.Strict.Extra
4− (
5− )
6−where
7−
8−import Data.Aeson
9−import Data.Map.Ordered.Strict
10−
11−instance (ToJSON k, ToJSON v) => ToJSON (OMap k v) where
12− toJSON = toJSON . assocs
13−
14−instance (FromJSON k, FromJSON v, Ord k) => FromJSON (OMap k v) where
15− parseJSON = fmap fromList . parseJSON
octopod-css/default.nix+2
@@ -1,10 +1,12 @@
11{ pkgsSrc ? (import ./.. { }).pkgsSrc
22, sources ? import ../nix/sources.nix
33, nix-filter ? import sources.nix-filter
4+, system ? builtins.currentSystem
45}:
56let
67 pkgs = pkgsSrc {
78 overlays = [ (self: super: { nodejs = self.nodejs-10_x; }) ];
9+ inherit system;
810 };
911 production-css = pkgs.mkYarnPackage {
1012 name = "octopod-css";
octopod-css/development/layouts/index.html+931−907
@@ -1,1008 +1,1032 @@
11<!DOCTYPE html>
22<html lang="en">
33
4−<head>
5− <meta charset="utf-8">
6− <meta http-equiv="x-ua-compatible" content="ie=edge">
7− <title>Index</title>
8− <meta name="viewport" content="width=device-width, initial-scale=1">
9− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
10− <link rel="stylesheet" href="../styles/style.css">
11−</head>
4+ <head>
5+ <meta charset="utf-8">
6+ <meta http-equiv="x-ua-compatible" content="ie=edge">
7+ <title>Index</title>
8+ <meta name="viewport" content="width=device-width, initial-scale=1">
9+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
10+ <link rel="stylesheet" href="../styles/style.css">
11+ </head>
1212
13−<body>
13+ <body>
1414
15− <!--[if IE]>
16− <div class="browserupgrade">
17− You are using an <strong>outdated</strong> browser.
18− Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
19− </div>
15+ <!--[if IE]>
16+ <div class="browserupgrade">
17+ You are using an <strong>outdated</strong> browser.
18+ Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
19+ </div>
2020 <![endif]-->
2121
22− <header class="header">
23− <div class="header__wrap container">
24− <b class="header__logo">
22+ <header class="header">
23+ <div class="header__wrap container">
24+ <b class="header__logo">
2525 Deployment Manager
2626 </b>
27− <div class="header__project">
28− Project name
29− </div>
30− </div>
31− </header>
27+ <div class="header__project">
28+ Project name
29+ </div>
30+ </div>
31+ </header>
3232
33− <div class="page">
34− <div class="page__wrap container">
35− <div class="page__output notification notification--danger">
36− <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
37− <button class="notification__close" type="button"></button>
38− </div>
39− <div class="page__head">
40− <h1 class="page__heading title">
41− All deployments
42− </h1>
43− <div class="page__note">
44− Updated 5 mins ago
45− </div>
46− <div class="page__action page__action--search input input--search input--has-clear-type">
47− <input class="input__widget" type="text" placeholder="Search for deployments">
48− <button class="input__clear-type spot spot--cancel">Delete</button>
49− </div>
50− <a class="page__action button button--add popup-handler" href="#deployment">
33+ <div class="page">
34+ <div class="page__wrap container">
35+ <div class="page__output notification notification--danger">
36+ <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
37+ <button class="notification__close" type="button"></button>
38+ </div>
39+ <div class="page__head">
40+ <h1 class="page__heading title">
41+ All deployments
42+ </h1>
43+ <div class="page__note">
44+ Updated 5 mins ago
45+ </div>
46+ <div class="page__action page__action--search input input--search input--has-clear-type">
47+ <input class="input__widget" type="text" placeholder="Search for deployments">
48+ <button class="input__clear-type spot spot--cancel">Delete</button>
49+ </div>
50+ <a class="page__action button button--add popup-handler" href="#deployment">
5151 New deployment
5252 </a>
53− </div>
54− <div class="page__body">
55− <div class="data">
56− <div class="data__primary">
57− <div class="table table--deployments table--clickable table--double-click">
58− <table>
59− <thead>
60− <tr>
61− <th>
62− <button class="sort" type="button">Name</button>
63− </th>
64− <th>
65− Links
66− </th>
67− <th>
68− App configuration
69− </th>
70− <th>
71− <button class="sort" type="button">Deployment configuration</button>
72− </th>
73− <th>
74− <button class="sort sort--active sort--asc" type="button">Created</button>
75− </th>
76− <th>
77− <button class="sort" type="button">Changed</button>
78− </th>
79− <th>
80− <span class="visuallyhidden">Menu</span>
81− </th>
82− </tr>
83− </thead>
84− <tbody>
85− <tr class="popup-handler" data-href="#deployment">
86− <td>
87− Feature name
88− <div class="status status--success">
89− Success
90− </div>
91− </td>
92− <td>
93− <div class="listing">
94− <a class="listing__item external bar" href="#">
95− <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
96− </a>
97− <a class="listing__item external bar" href="#">
98− <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
99− </a>
100− </div>
101− </td>
102− <td>
103− <div class="listing listing--for-text">
104− <div class="listing__item">
105− <span class="listing__key">ENVIRONMENT:</span>
106− <span class="listing__value">production</span>
107− </div>
108− <div class="listing__item">
109− <span class="listing__key">app_api_url:</span>
110− <span class="listing__value">http://my-api.my-domain.com</span>
111− </div>
112− <div class="listing__item">
113− <span class="listing__key default">SECRET_CODE:</span>
114− <span class="listing__value">asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen</span>
115− </div>
116− <div class="listing__item">
117− <span class="listing__key default">INFO:</span>
118− <span class="listing__value default">New Project Staging</span>
119− </div>
120− <button class="listing__more expander" type="button">
53+ </div>
54+ <div class="page__body">
55+ <div class="data">
56+ <div class="data__primary">
57+ <div class="table table--deployments table--clickable table--double-click">
58+ <table>
59+ <thead>
60+ <tr>
61+ <th>
62+ <button class="sort" type="button">Name</button>
63+ </th>
64+ <th>
65+ Links
66+ </th>
67+ <th>
68+ App configuration
69+ </th>
70+ <th>
71+ <button class="sort" type="button">Deployment configuration</button>
72+ </th>
73+ <th>
74+ <button class="sort sort--active sort--asc" type="button">Created</button>
75+ </th>
76+ <th>
77+ <button class="sort" type="button">Changed</button>
78+ </th>
79+ <th>
80+ <span class="visuallyhidden">Menu</span>
81+ </th>
82+ </tr>
83+ </thead>
84+ <tbody>
85+ <tr class="popup-handler" data-href="#deployment">
86+ <td>
87+ Feature name
88+ <div class="status status--success">
89+ Success
90+ </div>
91+ </td>
92+ <td>
93+ <div class="listing">
94+ <a class="listing__item external bar" href="#">
95+ <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
96+ </a>
97+ <a class="listing__item external bar" href="#">
98+ <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
99+ </a>
100+ </div>
101+ </td>
102+ <td>
103+ <div class="listing listing--for-text">
104+ <div class="listing__item">
105+ <span class="listing__key">ENVIRONMENT:</span>
106+ <span class="listing__value">production</span>
107+ </div>
108+ <div class="listing__item">
109+ <span class="listing__key">app_api_url:</span>
110+ <span class="listing__value">http://my-api.my-domain.com</span>
111+ </div>
112+ <div class="listing__item">
113+ <span class="listing__key default">SECRET_CODE:</span>
114+ <span class="listing__value">asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen</span>
115+ </div>
116+ <div class="listing__item">
117+ <span class="listing__key default">INFO:</span>
118+ <span class="listing__value default">New Project Staging</span>
119+ </div>
120+ <button class="listing__more expander" type="button">
121121 Show all (5)
122122 </button>
123− </div>
124− </td>
125− <td>
126− <div class="listing listing--for-text">
127− <div class="listing__item">
128− <b>ENVIRONENT:</b> production
129− </div>
130− <div class="listing__item">
131− <b>app_api_url:</b> http://my-api.my-domain.com
132− </div>
133− <div class="listing__item">
134− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
135− </div>
136− <button class="listing__more expander" type="button">
123+ </div>
124+ </td>
125+ <td>
126+ <div class="listing listing--for-text">
127+ <div class="listing__item">
128+ <b>ENVIRONENT:</b> production
129+ </div>
130+ <div class="listing__item">
131+ <b>app_api_url:</b> http://my-api.my-domain.com
132+ </div>
133+ <div class="listing__item">
134+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
135+ </div>
136+ <button class="listing__more expander" type="button">
137137 Show all (5)
138138 </button>
139− </div>
140− </td>
141− <td>
142− 15.05.2020
143− </td>
144− <td>
145− 01.06.2020
146− </td>
147− <td>
148− <div class="drop drop--actions">
149− <button class="drop__handler" type="button">
139+ </div>
140+ </td>
141+ <td>
142+ 15.05.2020
143+ </td>
144+ <td>
145+ 01.06.2020
146+ </td>
147+ <td>
148+ <div class="drop drop--actions">
149+ <button class="drop__handler" type="button">
150150 Actions
151151 </button>
152− <div class="drop__dropdown">
153− <button class="action action--edit" type="button">
152+ <div class="drop__dropdown">
153+ <button class="action action--edit" type="button">
154154 Edit
155155 </button>
156− <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
156+ <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
157157 Move to archive
158158 </button>
159− </div>
160− </div>
161− </td>
162− </tr>
163− <tr class="popup-handler" data-href="#deployment">
164− <td>
165− Feature name
166− <div class="loading loading--status-alike">
167− Loading...
168− </div>
169− </td>
170− <td>
171− <div class="listing">
172− <a class="listing__item external bar" href="#">
173− <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
174− </a>
175− <a class="listing__item external bar" href="#">
176− <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
177− </a>
178− </div>
179− </td>
180− <td>
181− <div class="listing listing--for-text">
182− <div class="listing__item">
183− <b>ENVIRONENT:</b> production
184− </div>
185− <div class="listing__item">
186− <b>app_api_url:</b> http://my-api.my-domain.com
187− </div>
188− <div class="listing__item">
189− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
190− </div>
191− </div>
192− </td>
193− <td>
194− <div class="listing listing--for-text">
195− <div class="listing__item">
196− <b>ENVIRONENT:</b> production
197− </div>
198− <div class="listing__item">
199− <b>app_api_url:</b> http://my-api.my-domain.com
200− </div>
201− <div class="listing__item">
202− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
203− </div>
204− </div>
205− </td>
206− <td>
207− 15.05.2020
208− </td>
209− <td>
210− 01.06.2020
211− </td>
212− <td>
213− <div class="drop drop--actions">
214− <button class="drop__handler" type="button">
159+ </div>
160+ </div>
161+ </td>
162+ </tr>
163+ <tr class="popup-handler" data-href="#deployment">
164+ <td>
165+ Feature name
166+ <div class="loading loading--status-alike">
167+ Loading...
168+ </div>
169+ </td>
170+ <td>
171+ <div class="listing">
172+ <a class="listing__item external bar" href="#">
173+ <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
174+ </a>
175+ <a class="listing__item external bar" href="#">
176+ <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
177+ </a>
178+ </div>
179+ </td>
180+ <td>
181+ <div class="listing listing--for-text">
182+ <div class="listing__item">
183+ <b>ENVIRONENT:</b> production
184+ </div>
185+ <div class="listing__item">
186+ <b>app_api_url:</b> http://my-api.my-domain.com
187+ </div>
188+ <div class="listing__item">
189+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
190+ </div>
191+ </div>
192+ </td>
193+ <td>
194+ <div class="listing listing--for-text">
195+ <div class="listing__item">
196+ <b>ENVIRONENT:</b> production
197+ </div>
198+ <div class="listing__item">
199+ <b>app_api_url:</b> http://my-api.my-domain.com
200+ </div>
201+ <div class="listing__item">
202+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
203+ </div>
204+ </div>
205+ </td>
206+ <td>
207+ 15.05.2020
208+ </td>
209+ <td>
210+ 01.06.2020
211+ </td>
212+ <td>
213+ <div class="drop drop--actions">
214+ <button class="drop__handler" type="button">
215215 Actions
216216 </button>
217− <div class="drop__dropdown">
218− <button class="action action--edit" type="button">
217+ <div class="drop__dropdown">
218+ <button class="action action--edit" type="button">
219219 Edit
220220 </button>
221− <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
221+ <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
222222 Move to archive
223223 </button>
224− </div>
225− </div>
226− </td>
227− </tr>
228− <tr class="popup-handler" data-href="#deployment">
229− <td>
230− Feature name
231− <div class="status status--pending">
232− Pending
233− </div>
234− </td>
235− <td>
236− <div class="listing">
237− <a class="listing__item external bar" href="#">
238− <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
239− </a>
240− <a class="listing__item external bar" href="#">
241− <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
242− </a>
243− </div>
244− </td>
245− <td>
246− <div class="listing listing--for-text">
247− <div class="listing__item">
248− <b>ENVIRONENT:</b> production
249− </div>
250− <div class="listing__item">
251− <b>app_api_url:</b> http://my-api.my-domain.com
252− </div>
253− <div class="listing__item">
254− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
255− </div>
256− </div>
257− </td>
258− <td>
259− <div class="listing listing--for-text">
260− <div class="listing__item">
261− <b>ENVIRONENT:</b> production
262− </div>
263− <div class="listing__item">
264− <b>app_api_url:</b> http://my-api.my-domain.com
265− </div>
266− <div class="listing__item">
267− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
268− </div>
269− <button class="listing__more expander" type="button">
224+ </div>
225+ </div>
226+ </td>
227+ </tr>
228+ <tr class="popup-handler" data-href="#deployment">
229+ <td>
230+ Feature name
231+ <div class="status status--pending">
232+ Pending
233+ </div>
234+ </td>
235+ <td>
236+ <div class="listing">
237+ <a class="listing__item external bar" href="#">
238+ <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
239+ </a>
240+ <a class="listing__item external bar" href="#">
241+ <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
242+ </a>
243+ </div>
244+ </td>
245+ <td>
246+ <div class="listing listing--for-text">
247+ <div class="listing__item">
248+ <b>ENVIRONENT:</b> production
249+ </div>
250+ <div class="listing__item">
251+ <b>app_api_url:</b> http://my-api.my-domain.com
252+ </div>
253+ <div class="listing__item">
254+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
255+ </div>
256+ </div>
257+ </td>
258+ <td>
259+ <div class="listing listing--for-text">
260+ <div class="listing__item">
261+ <b>ENVIRONENT:</b> production
262+ </div>
263+ <div class="listing__item">
264+ <b>app_api_url:</b> http://my-api.my-domain.com
265+ </div>
266+ <div class="listing__item">
267+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
268+ </div>
269+ <button class="listing__more expander" type="button">
270270 Show all (5)
271271 </button>
272− </div>
273− </td>
274− <td>
275− 15.05.2020
276− </td>
277− <td>
278− 01.06.2020
279− </td>
280− <td>
281− <div class="drop drop--actions">
282− <button class="drop__handler" type="button">
272+ </div>
273+ </td>
274+ <td>
275+ 15.05.2020
276+ </td>
277+ <td>
278+ 01.06.2020
279+ </td>
280+ <td>
281+ <div class="drop drop--actions">
282+ <button class="drop__handler" type="button">
283283 Actions
284284 </button>
285− <div class="drop__dropdown">
286− <button class="action action--edit" type="button">
285+ <div class="drop__dropdown">
286+ <button class="action action--edit" type="button">
287287 Edit
288288 </button>
289− <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
289+ <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
290290 Move to archive
291291 </button>
292− </div>
293− </div>
294− </td>
295− </tr>
296− <tr class="popup-handler" data-href="#deployment">
297− <td>
298− Feature name
299− <div class="status status--failure">
300− Failure
301− </div>
302− </td>
303− <td>
304− <div class="listing">
305− <a class="listing__item external bar" href="#">
306− <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
307− </a>
308− <a class="listing__item external bar" href="#">
309− <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
310− </a>
311− </div>
312− </td>
313− <td>
314− <div class="listing listing--for-text">
315− <div class="listing__item">
316− <b>ENVIRONENT:</b> production
317− </div>
318− <div class="listing__item">
319− <b>app_api_url:</b> http://my-api.my-domain.com
320− </div>
321− <div class="listing__item">
322− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
323− </div>
324− </div>
325− </td>
326− <td>
327− <div class="listing listing--for-text">
328− <div class="listing__item">
329− <b>ENVIRONENT:</b> production
330− </div>
331− <div class="listing__item">
332− <b>app_api_url:</b> http://my-api.my-domain.com
333− </div>
334− <div class="listing__item">
335− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
336− </div>
337− </div>
338− </td>
339− <td>
340− 15.05.2020
341− </td>
342− <td>
343− 01.06.2020
344− </td>
345− <td>
346− <div class="drop drop--actions">
347− <button class="drop__handler" type="button">
292+ </div>
293+ </div>
294+ </td>
295+ </tr>
296+ <tr class="popup-handler" data-href="#deployment">
297+ <td>
298+ Feature name
299+ <div class="status status--failure">
300+ Failure
301+ </div>
302+ </td>
303+ <td>
304+ <div class="listing">
305+ <a class="listing__item external bar" href="#">
306+ <b>app</b>.feature-name.project.name.com lorem ipsume dolor sit amen
307+ </a>
308+ <a class="listing__item external bar" href="#">
309+ <b>admin</b>.feature-name.project.name.com lorem ipsume dolor sit amen
310+ </a>
311+ </div>
312+ </td>
313+ <td>
314+ <div class="listing listing--for-text">
315+ <div class="listing__item">
316+ <b>ENVIRONENT:</b> production
317+ </div>
318+ <div class="listing__item">
319+ <b>app_api_url:</b> http://my-api.my-domain.com
320+ </div>
321+ <div class="listing__item">
322+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
323+ </div>
324+ </div>
325+ </td>
326+ <td>
327+ <div class="listing listing--for-text">
328+ <div class="listing__item">
329+ <b>ENVIRONENT:</b> production
330+ </div>
331+ <div class="listing__item">
332+ <b>app_api_url:</b> http://my-api.my-domain.com
333+ </div>
334+ <div class="listing__item">
335+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
336+ </div>
337+ </div>
338+ </td>
339+ <td>
340+ 15.05.2020
341+ </td>
342+ <td>
343+ 01.06.2020
344+ </td>
345+ <td>
346+ <div class="drop drop--actions">
347+ <button class="drop__handler" type="button">
348348 Actions
349349 </button>
350− <div class="drop__dropdown">
351− <button class="action action--edit" type="button">
350+ <div class="drop__dropdown">
351+ <button class="action action--edit" type="button">
352352 Edit
353353 </button>
354− <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
354+ <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">
355355 Move to archive
356356 </button>
357+ </div>
358+ </div>
359+ </td>
360+ </tr>
361+ <tr class="no-table">
362+ <td colspan="8">
363+ <div class="null null--search">
364+ <b class="null__heading">No results found</b>
365+ <div class="null__message">It seems we can’t find any results
366+ <br>based on your search
367+ </div>
368+ </div>
369+ <!-- temp -->
370+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
371+ <!--/temp -->
372+ <div class="null null--data">
373+ <b class="null__heading">Cannot retrieve the data</b>
374+ <div class="null__message">Try to reload page</div>
375+ </div>
376+ <!-- temp -->
377+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
378+ <!--/temp -->
379+ <div class="loading loading--enlarged loading--alternate">
380+ Loading...
381+ </div>
382+ </td>
383+ </tr>
384+ </tbody>
385+ </table>
386+ </div>
357387 </div>
358− </div>
359− </td>
360− </tr>
361− <tr class="no-table">
362− <td colspan="8">
363− <div class="null null--search">
364− <b class="null__heading">No results found</b>
365− <div class="null__message">It seems we can’t find any results <br>based on your search</div>
366− </div>
367− <!-- temp -->
368− &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
369− <!--/temp -->
370− <div class="null null--data">
371− <b class="null__heading">Cannot retrieve the data</b>
372− <div class="null__message">Try to reload page</div>
373− </div>
374− <!-- temp -->
375− &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
376− <!--/temp -->
377− <div class="loading loading--enlarged loading--alternate">
378− Loading...
379− </div>
380− </td>
381− </tr>
382− </tbody>
383− </table>
384− </div>
385− </div>
386− <button class="data__show-archive expander expander--stand-alone" type="button">
388+ <button class="data__show-archive expander expander--stand-alone" type="button">
387389 Show Archived deployments (2)
388390 </button>
389− <div class="data__archive">
390− <div class="table table--clickable table--deployments">
391− <table>
392− <thead>
393− <tr class="popup-handler" data-href="#deployment">
394− <th>
395− Name
396− </th>
397− <th>
398− Links
399− </th>
400− <th>
401− Tag
402− </th>
403− <th>
404− App configuration
405− </th>
406− <th>
407− Deployment configuration
408− </th>
409− <th>
410− Created
411− </th>
412− <th>
413− Changed
414− </th>
415− <th>
416− <span class="visuallyhidden">Menu</span>
417− </th>
418− </tr>
419− </thead>
420− <tbody>
421− <tr class="popup-handler" data-href="#deployment">
422− <td>
423− Feature name
424− <div class="status status--archived">
425− Archived
426− </div>
427− </td>
428− <td>
429− ...
430− </td>
431− <td>
432− <div class="listing listing--for-text">
433− <div class="listing__item">
434− <b>ENVIRONENT:</b> production
435− </div>
436− <div class="listing__item">
437− <b>app_api_url:</b> http://my-api.my-domain.com
438− </div>
439− <div class="listing__item">
440− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
441− </div>
442− </div>
443− </td>
444− <td>
445− <div class="listing listing--for-text">
446− <div class="listing__item">
447− <b>ENVIRONENT:</b> production
448− </div>
449− <div class="listing__item">
450− <b>app_api_url:</b> http://my-api.my-domain.com
451− </div>
452− <div class="listing__item">
453− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
454− </div>
455− </div>
456− </td>
457− <td>
458− 15.05.2020
459− </td>
460− <td>
461− 01.06.2020
462− </td>
463− <td>
464− <div class="drop drop--actions">
465− <button class="drop__handler" type="button">
391+ <div class="data__archive">
392+ <div class="table table--clickable table--deployments">
393+ <table>
394+ <thead>
395+ <tr class="popup-handler" data-href="#deployment">
396+ <th>
397+ Name
398+ </th>
399+ <th>
400+ Links
401+ </th>
402+ <th>
403+ Tag
404+ </th>
405+ <th>
406+ App configuration
407+ </th>
408+ <th>
409+ Deployment configuration
410+ </th>
411+ <th>
412+ Created
413+ </th>
414+ <th>
415+ Changed
416+ </th>
417+ <th>
418+ <span class="visuallyhidden">Menu</span>
419+ </th>
420+ </tr>
421+ </thead>
422+ <tbody>
423+ <tr class="popup-handler" data-href="#deployment">
424+ <td>
425+ Feature name
426+ <div class="status status--archived">
427+ Archived
428+ </div>
429+ </td>
430+ <td>
431+ ...
432+ </td>
433+ <td>
434+ <div class="listing listing--for-text">
435+ <div class="listing__item">
436+ <b>ENVIRONENT:</b> production
437+ </div>
438+ <div class="listing__item">
439+ <b>app_api_url:</b> http://my-api.my-domain.com
440+ </div>
441+ <div class="listing__item">
442+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
443+ </div>
444+ </div>
445+ </td>
446+ <td>
447+ <div class="listing listing--for-text">
448+ <div class="listing__item">
449+ <b>ENVIRONENT:</b> production
450+ </div>
451+ <div class="listing__item">
452+ <b>app_api_url:</b> http://my-api.my-domain.com
453+ </div>
454+ <div class="listing__item">
455+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
456+ </div>
457+ </div>
458+ </td>
459+ <td>
460+ 15.05.2020
461+ </td>
462+ <td>
463+ 01.06.2020
464+ </td>
465+ <td>
466+ <div class="drop drop--actions">
467+ <button class="drop__handler" type="button">
466468 Actions
467469 </button>
468− <div class="drop__dropdown">
469− <button class="action action--restore" type="button">
470+ <div class="drop__dropdown">
471+ <button class="action action--restore" type="button">
470472 Restore from archive
471473 </button>
472− </div>
473− </div>
474− </td>
475− </tr>
476− <tr>
477− <td>
478− Feature name
479− <div class="status status--archived">
480− Archived
481− </div>
482− </td>
483− <td>
484− ...
485− </td>
486− <td>
487− <div class="listing listing--for-text">
488− <div class="listing__item">
489− <span class="listing__key">
474+ </div>
475+ </div>
476+ </td>
477+ </tr>
478+ <tr>
479+ <td>
480+ Feature name
481+ <div class="status status--archived">
482+ Archived
483+ </div>
484+ </td>
485+ <td>
486+ ...
487+ </td>
488+ <td>
489+ <div class="listing listing--for-text">
490+ <div class="listing__item">
491+ <span class="listing__key">
490492 ENVIRONMENT:
491493 </span>
492− <span class="listing__value">
494+ <span class="listing__value">
493495 production
494496 </span>
495− </div>
496− <div class="listing__item">
497− <span class="listing__key">
497+ </div>
498+ <div class="listing__item">
499+ <span class="listing__key">
498500 app_api_url:
499501 </span>
500− <span class="listing__value">
502+ <span class="listing__value">
501503 http://my-api.my-domain.com
502504 </span>
503− </div>
504− <div class="listing__item">
505− <span class="listing__key item-default">
505+ </div>
506+ <div class="listing__item">
507+ <span class="listing__key item-default">
506508 SECRET_CODE:
507509 </span>
508− <span class="listing__value item-default">
510+ <span class="listing__value item-default">
509511 asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
510512 </span>
511− </div>
512− </div>
513− </td>
514− <td>
515− <div class="listing listing--for-text">
516− <div class="listing__item">
517− <b>ENVIRONENT:</b> production
518− </div>
519− <div class="listing__item">
520− <b>app_api_url:</b> http://my-api.my-domain.com
521− </div>
522− <div class="listing__item">
523− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
524− </div>
525− </div>
526− </td>
527− <td>
528− 15.05.2020
529− </td>
530− <td>
531− 01.06.2020
532− </td>
533− <td>
534− <div class="drop drop--actions">
535− <button class="drop__handler" type="button">
513+ </div>
514+ </div>
515+ </td>
516+ <td>
517+ <div class="listing listing--for-text">
518+ <div class="listing__item">
519+ <b>ENVIRONENT:</b> production
520+ </div>
521+ <div class="listing__item">
522+ <b>app_api_url:</b> http://my-api.my-domain.com
523+ </div>
524+ <div class="listing__item">
525+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
526+ </div>
527+ </div>
528+ </td>
529+ <td>
530+ 15.05.2020
531+ </td>
532+ <td>
533+ 01.06.2020
534+ </td>
535+ <td>
536+ <div class="drop drop--actions">
537+ <button class="drop__handler" type="button">
536538 Actions
537539 </button>
538− <div class="drop__dropdown">
539− <button class="action action--restore" type="button">
540+ <div class="drop__dropdown">
541+ <button class="action action--restore" type="button">
540542 Restore from archive
541543 </button>
542− </div>
543− </div>
544− </td>
545− </tr>
546− </tbody>
547− </table>
548− </div>
549− </div>
550− </div>
551− <!-- или:
552− <div class="no-data">
553− <div class="null null--data">
554− <b class="null__heading">Cannot retrieve the data</b>
555− <div class="null__message">Try to reload page</div>
556− </div>
557− <div class="loading loading--enlarged loading--alternate">
558− Loading...
544+ </div>
545+ </div>
546+ </td>
547+ </tr>
548+ </tbody>
549+ </table>
550+ </div>
559551 </div>
560552 </div>
561− -->
562− </div>
563− </div>
564− </div>
565−
566− <!-- или:
567− <div class="no-page">
568− <div class="no-page__inner">
569− <div class="null null--data">
570− <b class="null__heading">Cannot retrieve the data</b>
571− <div class="null__message">Try to reload page</div>
572− </div>
573− или:
574− <div class="loading loading--enlarged loading--alternate">
575− Loading...
553+ <!-- или:
554+ <div class="no-data">
555+ <div class="null null--data">
556+ <b class="null__heading">Cannot retrieve the data</b>
557+ <div class="null__message">Try to reload page</div>
558+ </div>
559+ <div class="loading loading--enlarged loading--alternate">
560+ Loading...
561+ </div>
562+ </div>
563+ -->
576564 </div>
577565 </div>
578566 </div>
579− -->
580567
581− <div class="popup" id="deployment">
582− <div class="popup__overlay" aria-hidden="true">
583− Close popup
584− </div>
585− <div class="popup__body">
586− <div class="popup__head">
587− <button class="popup__close" type="button">
568+ <!-- или:
569+ <div class="no-page">
570+ <div class="no-page__inner">
571+ <div class="null null--data">
572+ <b class="null__heading">Cannot retrieve the data</b>
573+ <div class="null__message">Try to reload page</div>
574+ </div>
575+ или:
576+ <div class="loading loading--enlarged loading--alternate">
577+ Loading...
578+ </div>
579+ </div>
580+ </div>
581+ -->
582+
583+ <div class="popup" id="deployment">
584+ <div class="popup__overlay" aria-hidden="true">
585+ Close popup
586+ </div>
587+ <div class="popup__body">
588+ <div class="popup__head">
589+ <button class="popup__close" type="button">
588590 Close popup
589591 </button>
590− <h2 class="popup__project">
591− Project name
592− </h2>
593− <div class="popup__operations">
594− <button class="popup__action button button--save" type="button">Save</button>
595− <button class="popup__action button button--save-loading" type="button" disabled>Save</button>
596− <button class="popup__action button button--secondary" type="button">Cancel</button>
597− </div>
598− <div class="popup__menu drop drop--actions">
599− <button class="drop__handler" type="button">
592+ <h2 class="popup__project">
593+ Project name
594+ </h2>
595+ <div class="popup__operations">
596+ <button class="popup__action button button--save" type="button">Save</button>
597+ <button class="popup__action button button--save-loading" type="button" disabled>Save</button>
598+ <button class="popup__action button button--secondary" type="button">Cancel</button>
599+ </div>
600+ <div class="popup__menu drop drop--actions">
601+ <button class="drop__handler" type="button">
600602 Actions
601603 </button>
602− <div class="drop__dropdown">
603− <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">Move to archive</button>
604− <button class="action action--restore classic-popup-handler" type="button" data-href="#dialog">Restore from archive</button>
605− </div>
606− </div>
607− </div>
608− <div class="popup__content">
604+ <div class="drop__dropdown">
605+ <button class="action action--archive classic-popup-handler" type="button" data-href="#dialog">Move to archive</button>
606+ <button class="action action--restore classic-popup-handler" type="button" data-href="#dialog">Restore from archive</button>
607+ </div>
608+ </div>
609+ </div>
610+ <div class="popup__content">
609611
610− <div class="deployment">
611− <div class="deployment__output notification notification--danger">
612− <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
613− <button class="notification__close" type="button"></button>
614− </div>
615− <section class="deployment__section">
616− <h3 class="deployment__sub-heading">
617− Name
618− </h3>
619− <div class="deployment__widget">
620− <div class="tag input">
621− <input class="input__widget" type="text" placeholder="Project name">
622− </div>
623− </div>
624− </section>
625− <section class="deployment__section">
626− <h3 class="deployment__sub-heading">
627− Tag
628− </h3>
629− <div class="deployment__widget">
630− <div class="tag input">
631− <input class="input__widget" type="text" placeholder="key" value="fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b ">
632− </div>
633− </div>
634− </section>
635− <section class="deployment__section">
636− <h3 class="deployment__sub-heading">
637− App configuration
638− </h3>
639− <div class="deployment__widget">
640− <div class="overrides">
641− <button class="overrides__add dash dash--add" type="button">
612+ <div class="deployment">
613+ <div class="deployment__output notification notification--danger">
614+ <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
615+ <button class="notification__close" type="button"></button>
616+ </div>
617+ <section class="deployment__section">
618+ <h3 class="deployment__sub-heading">
619+ Name
620+ </h3>
621+ <div class="deployment__widget">
622+ <div class="tag input">
623+ <input class="input__widget" type="text" placeholder="Project name">
624+ </div>
625+ </div>
626+ </section>
627+ <section class="deployment__section">
628+ <h3 class="deployment__sub-heading">
629+ Tag
630+ </h3>
631+ <div class="deployment__widget">
632+ <div class="tag input">
633+ <input class="input__widget" type="text" placeholder="key" value="fa3a5bd4cc1134ae259c34d4cf530be0f0996a0b ">
634+ </div>
635+ </div>
636+ </section>
637+ <section class="deployment__section">
638+ <h3 class="deployment__sub-heading">
639+ App configuration
640+ </h3>
641+ <div class="deployment__widget">
642+ <div class="overrides">
643+ <button class="overrides__add dash dash--add" type="button">
642644 Add an override
643645 </button>
644− <div class="overrides__item">
645− <div class="overrides__key input">
646− <input class="input__widget" type="text" placeholder="key">
647− </div>
648− <div class="overrides__value input">
649− <input class="input__widget" type="text" placeholder="value">
650− </div>
651− <button class="overrides__delete spot spot--cancel">
652− Delete
653− </button>
654− </div>
655− <div class="overrides__item">
656− <div class="overrides__key input">
657− <input class="input__widget" type="text" placeholder="key" value="ENVIRONMENT">
658− </div>
659− <div class="overrides__value input">
660− <input class="input__widget" type="text" placeholder="value" value="production">
661− </div>
662− <button class="overrides__delete spot spot--cancel">
663− Delete
664− </button>
665− </div>
666− <div class="overrides__item">
667− <div class="overrides__key input">
668− <input class="input__widget" type="text" placeholder="key" value="app_api_url">
669− </div>
670− <div class="overrides__value input">
671− <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com">
672− </div>
673− <button class="overrides__delete spot spot--cancel">
674− Delete
675− </button>
676− </div>
677− <div class="overrides__item">
678− <div class="overrides__key input input--default">
679− <input class="input__widget" type="text" placeholder="key" value="SECRET_CODE">
680− </div>
681− <div class="overrides__value input input--default">
682− <input class="input__widget" type="text" placeholder="value" value="production">
683− </div>
684− <button class="overrides__delete spot spot--cancel">
685− Delete
686− </button>
687− </div>
688− <div class="overrides__item">
689− <div class="overrides__key input input--deleted">
690− <input class="input__widget" type="text" placeholder="key" value="APP_URL" disabled>
691− </div>
692− <div class="overrides__value input input--deleted">
693− <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com" disabled>
694− </div>
695− <button class="overrides__delete spot spot--undo">
696− Delete
697− </button>
698− </div>
699− <div class="overrides__item">
700− <div class="overrides__key input input--disable">
701− <input class="input__widget" type="text" placeholder="key" disabled>
702− </div>
703− <div class="overrides__value input input--disable">
704− <input class="input__widget" type="text" placeholder="value" value="production" disabled>
705− </div>
706− </div>
707− <div class="overrides__item">
708− <div class="overrides__key input">
709− <input class="input__widget" type="text" placeholder="key" value="LiveSearch">
710− <ul class="overrides__search">
711− <li class="overrides__search-item"><b>S</b>ECRET_PASSWORD</li>
712− <li class="overrides__search-item"><b>S</b>ECRET_INFO</li>
713− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
714− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
715− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
716− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
717− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
718− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
719− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
720− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
721− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
722− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
723− <li class="overrides__search-item"><b>S</b>oviet Socialist Republics</li>
724− </ul>
725− </div>
726− <div class="overrides__value input">
727− <input class="input__widget" type="text" placeholder="value" value="Value">
728− </div>
729− <button class="overrides__delete spot spot--cancel">
730− Delete
731− </button>
732− </div>
733− </div>
734− </div>
735− </section>
736− <section class="deployment__section">
737− <h3 class="deployment__sub-heading">
738− Deployment configuration
739− </h3>
740− <div class="deployment__widget">
741− <div class="overrides">
742− <button class="overrides__add dash dash--add" type="button">
646+ <div class="overrides__list">
647+ <div class="editable-row">
648+ <div class="editable-row__key input">
649+ <input class="input__widget" type="text" placeholder="key">
650+ </div>
651+ <div class="editable-row__value input">
652+ <input class="input__widget" type="text" placeholder="value">
653+ </div>
654+ <button class="editable-row__delete spot spot--cancel">
655+ Delete
656+ </button>
657+ </div>
658+ <div class="editable-row">
659+ <div class="editable-row__key input">
660+ <input class="input__widget" type="text" placeholder="key" value="ENVIRONMENT">
661+ </div>
662+ <div class="editable-row__value input">
663+ <input class="input__widget" type="text" placeholder="value" value="production">
664+ </div>
665+ <button class="editable-row__delete spot spot--cancel">
666+ Delete
667+ </button>
668+ </div>
669+ <div class="editable-row">
670+ <div class="editable-row__key input">
671+ <input class="input__widget" type="text" placeholder="key" value="app_api_url">
672+ </div>
673+ <div class="editable-row__value input">
674+ <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com">
675+ </div>
676+ <button class="editable-row__delete spot spot--cancel">
677+ Delete
678+ </button>
679+ </div>
680+ <div class="editable-row">
681+ <div class="editable-row__key input input--default">
682+ <input class="input__widget" type="text" placeholder="key" value="SECRET_CODE">
683+ </div>
684+ <div class="editable-row__value input input--default">
685+ <input class="input__widget" type="text" placeholder="value" value="production">
686+ </div>
687+ <button class="editable-row__delete spot spot--cancel">
688+ Delete
689+ </button>
690+ </div>
691+ <div class="editable-row">
692+ <div class="editable-row__key input input--deleted">
693+ <input class="input__widget" type="text" placeholder="key" value="APP_URL" disabled>
694+ </div>
695+ <div class="editable-row__value input input--deleted">
696+ <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com" disabled>
697+ </div>
698+ <button class="editable-row__delete spot spot--undo">
699+ Delete
700+ </button>
701+ </div>
702+ <div class="editable-row">
703+ <div class="editable-row__key input input--disable">
704+ <input class="input__widget" type="text" placeholder="key" disabled>
705+ </div>
706+ <div class="editable-row__value input input--disable">
707+ <input class="input__widget" type="text" placeholder="value" value="production" disabled>
708+ </div>
709+ </div>
710+ <div class="editable-row">
711+ <div class="editable-row__key input">
712+ <input class="input__widget" type="text" placeholder="key" value="LiveSearch">
713+ <ul class="input__dropdown">
714+ <li class="input__suggest"><b>S</b>ECRET_PASSWORD</li>
715+ <li class="input__suggest"><b>S</b>ECRET_INFO</li>
716+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
717+ </li>
718+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
719+ </li>
720+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
721+ </li>
722+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
723+ </li>
724+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
725+ </li>
726+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
727+ </li>
728+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
729+ </li>
730+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
731+ </li>
732+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
733+ </li>
734+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
735+ </li>
736+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
737+ </li>
738+ </ul>
739+ </div>
740+ <div class="editable-row__value input">
741+ <input class="input__widget" type="text" placeholder="value" value="Value">
742+ </div>
743+ <button class="editable-row__delete spot spot--cancel">
744+ Delete
745+ </button>
746+ </div>
747+ </div>
748+ </div>
749+ </div>
750+ </section>
751+ <section class="deployment__section">
752+ <h3 class="deployment__sub-heading">
753+ Deployment configuration
754+ </h3>
755+ <div class="deployment__widget">
756+ <div class="overrides">
757+ <button class="overrides__add dash dash--add" type="button">
743758 Add an override
744759 </button>
745− <div class="overrides__item">
746− <div class="overrides__key input">
747− <input class="input__widget" type="text" placeholder="key">
748− </div>
749− <div class="overrides__value input">
750− <input class="input__widget" type="text" placeholder="value">
751− </div>
752− <button class="overrides__delete spot spot--cancel">
753− Delete
754− </button>
755− </div>
756− <div class="overrides__item">
757− <div class="overrides__key input">
758− <input class="input__widget" type="text" placeholder="key" value="ENVIRONENT">
759− </div>
760− <div class="overrides__value input">
761− <input class="input__widget" type="text" placeholder="value" value="production">
762− </div>
763− <button class="overrides__delete spot spot--cancel">
764− Delete
765− </button>
766− </div>
767− <div class="overrides__item">
768− <div class="overrides__key input">
769− <input class="input__widget" type="text" placeholder="key" value="app_api_url">
770− </div>
771− <div class="overrides__value input">
772− <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com">
773− </div>
774− <button class="overrides__delete spot spot--cancel">
775− Delete
776− </button>
777− </div>
778− <div class="overrides__item">
779− <div class="overrides__key input">
780− <input class="input__widget input--default" type="text" placeholder="key" value="SECRET_CODE">
781− </div>
782− <div class="overrides__value input">
783− <input class="input__widget" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
784− </div>
785− <button class="overrides__delete spot spot--cancel">
786− Delete
787− </button>
788− </div>
789− <div class="overrides__item loader">
790− <div class="overrides__placeholder"></div>
791− <div class="overrides__placeholder"></div>
792− <div class="overrides__delete spot spot--loader"></div>
793− </div>
794− <div class="overrides__item loader">
795− <div class="overrides__placeholder"></div>
796− <div class="overrides__placeholder"></div>
797− <div class="overrides__delete spot spot--loader"></div>
798− </div>
799− <div class="overrides__item loader">
800− <div class="overrides__placeholder"></div>
801− <div class="overrides__placeholder"></div>
802− <div class="overrides__delete spot spot--loader"></div>
803− </div>
804− <div class="overrides__item">
805− <div class="overrides__key input input--error">
806− <input class="input__widget input--default" type="text" placeholder="key" value="SECRET_CODE">
807− <div class="input__output">Duplicate key values</div>
808− </div>
809− <div class="overrides__value input">
810− <input class="input__widget" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
811− </div>
812− <button class="overrides__delete spot spot--cancel">
813− Delete
814− </button>
815− </div>
816− <div class="overrides__item">
817− <div class="overrides__key input input--error">
818− <input class="input__widget" type="text" placeholder="key" value="ENVIRONMENT">
819− <div class="input__output">Duplicate key values</div>
820− </div>
821− <div class="overrides__value input">
822− <input class="input__widget" type="text" placeholder="value" value="production">
823− </div>
824− <button class="overrides__delete spot spot--cancel">
825− Delete
826− </button>
827− </div>
828− <div class="overrides__item">
829− <div class="overrides__key input input--error">
830− <input class="input__widget" type="text" placeholder="key" value="">
831− <div class="input__output">Empty key value</div>
832− </div>
833− <div class="overrides__value input">
834− <input class="input__widget" type="text" placeholder="value" value="production">
835− </div>
836− <button class="overrides__delete spot spot--cancel">
837− Delete
838− </button>
760+ <div class="overrides__list">
761+ <div class="editable-row">
762+ <div class="editable-row__key input">
763+ <input class="input__widget" type="text" placeholder="key">
764+ </div>
765+ <div class="editable-row__value input">
766+ <input class="input__widget" type="text" placeholder="value">
767+ </div>
768+ <button class="editable-row__delete spot spot--cancel">
769+ Delete
770+ </button>
771+ </div>
772+ <div class="editable-row">
773+ <div class="editable-row__key input">
774+ <input class="input__widget" type="text" placeholder="key" value="ENVIRONENT">
775+ </div>
776+ <div class="editable-row__value input">
777+ <input class="input__widget" type="text" placeholder="value" value="production">
778+ </div>
779+ <button class="editable-row__delete spot spot--cancel">
780+ Delete
781+ </button>
782+ </div>
783+ <div class="editable-row">
784+ <div class="editable-row__key input">
785+ <input class="input__widget" type="text" placeholder="key" value="app_api_url">
786+ </div>
787+ <div class="editable-row__value input">
788+ <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com">
789+ </div>
790+ <button class="editable-row__delete spot spot--cancel">
791+ Delete
792+ </button>
793+ </div>
794+ <div class="editable-row">
795+ <div class="editable-row__key input">
796+ <input class="input__widget input--default" type="text" placeholder="key" value="SECRET_CODE">
797+ </div>
798+ <div class="editable-row__value input">
799+ <input class="input__widget" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
800+ </div>
801+ <button class="editable-row__delete spot spot--cancel">
802+ Delete
803+ </button>
804+ </div>
805+ <div class="editable-row loader">
806+ <div class="editable-row__placeholder"></div>
807+ <div class="editable-row__placeholder"></div>
808+ <div class="editable-row__delete spot spot--loader"></div>
809+ </div>
810+ <div class="editable-row loader">
811+ <div class="editable-row__placeholder"></div>
812+ <div class="editable-row__placeholder"></div>
813+ <div class="editable-row__delete spot spot--loader"></div>
814+ </div>
815+ <div class="editable-row loader">
816+ <div class="editable-row__placeholder"></div>
817+ <div class="editable-row__placeholder"></div>
818+ <div class="editable-row__delete spot spot--loader"></div>
819+ </div>
820+ <div class="editable-row">
821+ <div class="editable-row__key input input--error">
822+ <input class="input__widget input--default" type="text" placeholder="key" value="SECRET_CODE">
823+ <div class="input__output">Duplicate key values</div>
824+ </div>
825+ <div class="editable-row__value input">
826+ <input class="input__widget" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
827+ </div>
828+ <button class="editable-row__delete spot spot--cancel">
829+ Delete
830+ </button>
831+ </div>
832+ <div class="editable-row">
833+ <div class="editable-row__key input input--error">
834+ <input class="input__widget" type="text" placeholder="key" value="ENVIRONMENT">
835+ <div class="input__output">Duplicate key values</div>
836+ </div>
837+ <div class="editable-row__value input">
838+ <input class="input__widget" type="text" placeholder="value" value="production">
839+ </div>
840+ <button class="editable-row__delete spot spot--cancel">
841+ Delete
842+ </button>
843+ </div>
844+ <div class="editable-row">
845+ <div class="editable-row__key input input--error">
846+ <input class="input__widget" type="text" placeholder="key" value="">
847+ <div class="input__output">Empty key value</div>
848+ </div>
849+ <div class="editable-row__value input">
850+ <input class="input__widget" type="text" placeholder="value" value="production">
851+ </div>
852+ <button class="editable-row__delete spot spot--cancel">
853+ Delete
854+ </button>
855+ </div>
856+ </div>
857+ </div>
858+ </div>
859+ </section>
860+ </div>
861+ <!-- temp -->
862+ или:
863+ <!--/temp -->
864+ <div class="no-deployment">
865+ <div class="null null--data">
866+ <div class="null__content">
867+ <b class="null__heading">Cannot retrieve the data</b>
868+ <div class="null__message">Try to reload page</div>
869+ </div>
870+ </div>
871+ <!-- temp -->
872+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
873+ <!--/temp -->
874+ <div class="loading loading--enlarged loading--alternate">
875+ Loading...
876+ </div>
877+ </div>
839878 </div>
840− </div>
841879 </div>
842− </section>
843880 </div>
844− <!-- temp -->
845− или:
846− <!--/temp -->
847− <div class="no-deployment">
848− <div class="null null--data">
849− <div class="null__content">
850− <b class="null__heading">Cannot retrieve the data</b>
851− <div class="null__message">Try to reload page</div>
852− </div>
853− </div>
854− <!-- temp -->
855− &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
856− <!--/temp -->
857− <div class="loading loading--enlarged loading--alternate">
858− Loading...
859− </div>
860− </div>
861− </div>
862− </div>
863− </div>
864881
865882
866− <div class="popup" id="log">
867− <div class="popup__overlay" aria-hidden="true">
868− Close popup
869− </div>
870− <div class="popup__body">
871− <div class="popup__head">
872− <button class="popup__close" type="button">
883+ <div class="popup" id="log">
884+ <div class="popup__overlay" aria-hidden="true">
885+ Close popup
886+ </div>
887+ <div class="popup__body">
888+ <div class="popup__head">
889+ <button class="popup__close" type="button">
873890 Close popup
874891 </button>
875− <h2 class="popup__project">
876− Project name, logs
877− </h2>
878− </div>
879− <div class="popup__sub-head">
880− <div class="nav">
881− <button class="nav__link nav__link--current" type="button">stdout</button>
882− <button class="nav__link" type="button">stderr</button>
883− </div>
884− </div>
885− <div class="popup__content">
886− <div class="log">
887− DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/libiserv/dist-install/build:/Users/dr/workspace/ghc/libraries/ghci/dist-install/build:/Users/dr/workspace/ghc/libraries/transformers/dist-install/build:/Users/dr/workspace/ghc/libraries/template-haskell/dist-install/build:/Users/dr/workspace/ghc/libraries/pretty/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-heap/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot-th/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/binary/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
888− >> inplace/lib/bin/ghc-iserv-dyn
889− <br>"rm" -f inplace/bin/runghc
890− <br>echo 'exec "$executablename" ${1+"$@"}' >> inplace/lib/bin/ghc-iserv-dyn
891− <br>echo '#!/bin/sh' >> inplace/bin/runghc
892− <br>chmod +x inplace/lib/bin/ghc-iserv-dyn
893− <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/runghc"' >> inplace/bin/runghc
894− <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
895− <br>/usr/local/bin/ginstall -c -m 755 utils/hpc/dist-install/build/tmp/hpc inplace/lib/bin/hpc
896− <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/runghc
897− <br>"rm" -f inplace/bin/hpc
898− <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
899− <br>echo 'pgmgcc="cc"' >> inplace/bin/runghc
900− <br>echo '#!/bin/sh' >> inplace/bin/hpc
901− <br>echo 'ghcprog="ghc-stage2"' >> "inplace/bin/runghc"
902− <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/hpc"' >> inplace/bin/hpc
903− <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/process/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
904− >> inplace/bin/runghc
905− <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
906− <br>cat utils/runghc/runghc.wrapper >> inplace/bin/runghc
907− <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/hpc
908− <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
909− <br>chmod +x inplace/bin/runghc
910− <br>echo 'pgmgcc="cc"' >> inplace/bin/hpc
911− <br>"rm" -f utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
912− <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/hpc/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
913− >> inplace/bin/hpc
914− <br>echo '#!/bin/sh' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
915− <br>cat utils/hpc/hpc.wrapper >> inplace/bin/hpc
916− <br>echo 'export DYLD_LIBRARY_PATH="libraries/Cabal/Cabal/dist-install/build:libraries/process/dist-install/build:libraries/parsec/dist-install/build:libraries/text/dist-install/build:libraries/template-haskell/dist-install/build:libraries/pretty/dist-install/build:libraries/ghc-boot-th/dist-install/build:libraries/mtl/dist-install/build:libraries/transformers/dist-install/build:libraries/directory/dist-install/build:libraries/unix/dist-install/build:libraries/time/dist-install/build:libraries/filepath/dist-install/build:libraries/binary/dist-install/build:libraries/containers/containers/dist-install/build:libraries/bytestring/dist-install/build:libraries/deepseq/dist-install/build:libraries/array/dist-install/build:libraries/base/dist-install/build:libraries/ghc-bignum/dist-install/build:libraries/ghc-prim/dist-install/build:rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
917− >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
918− <br>chmod +x inplace/bin/hpc
919− <br>echo 'exec "utils/ghc-cabal/dist-install/build/tmp/ghc-cabal" ${1+"$@"}' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
920− <br>chmod +x utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
921− <br>
922− <<ghc: 2787765536 bytes, 171 GCs, 20282449/67935792 avg/max bytes residency (8 samples), 144M in use, 0.000 INIT (0.003 elapsed), 1.738 MUT (2.286 elapsed), 0.567 GC (0.611 elapsed) :ghc>>
923− <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages -i
924− -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build
925− -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0 -package-id
926− binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619 -package-id
927− ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id time-1.9.3
928− -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build
929− -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/HsToCore.hs -o compiler/stage2/build/GHC/HsToCore.o
930− -dyno compiler/stage2/build/GHC/HsToCore.dyn_o
931− <br>
932− <<ghc: 766891168 bytes, 91 GCs, 14722329/42245856 avg/max bytes residency (6 samples), 91M in use, 0.000 INIT (0.003 elapsed), 0.361 MUT (0.510 elapsed), 0.236 GC (0.258 elapsed) :ghc>>
933− <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages
934− -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build
935− -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0
936− -package-id binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619
937− -package-id ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id
938− time-1.9.3 -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build
939− -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/Iface/Ext/Ast.hs -o compiler/stage2/build/GHC/Iface/Ext/Ast.o
940− -dyno compiler/stage2/build/GHC/Iface/Ext/Ast.dyn_o
941− <br>
942− <<ghc: 3067019736 bytes, 347 GCs, 25353490/71392248 avg/max bytes residency (8 samples), 184M in use, 0.000 INIT (0.002 elapsed), 1.416 MUT (1.823 elapsed), 0.554 GC (0.595 elapsed) :ghc>>
943− <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages
944− -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompil...
945− </div>
946− <!-- temp -->
947− или:
948− <!--/temp -->
949− <div class="no-log">
950− <div class="null null--data">
951− <div class="null__content">
952− <b class="null__heading">Cannot retrieve the data</b>
953− <div class="null__message">Try to reload page</div>
892+ <h2 class="popup__project">
893+ Project name, logs
894+ </h2>
895+ </div>
896+ <div class="popup__sub-head">
897+ <div class="nav">
898+ <button class="nav__link nav__link--current" type="button">stdout</button>
899+ <button class="nav__link" type="button">stderr</button>
900+ </div>
901+ </div>
902+ <div class="popup__content">
903+ <div class="log">
904+ DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/libiserv/dist-install/build:/Users/dr/workspace/ghc/libraries/ghci/dist-install/build:/Users/dr/workspace/ghc/libraries/transformers/dist-install/build:/Users/dr/workspace/ghc/libraries/template-haskell/dist-install/build:/Users/dr/workspace/ghc/libraries/pretty/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-heap/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot-th/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/binary/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
905+ >> inplace/lib/bin/ghc-iserv-dyn
906+ <br>"rm" -f inplace/bin/runghc
907+ <br>echo 'exec "$executablename" ${1+"$@"}' >> inplace/lib/bin/ghc-iserv-dyn
908+ <br>echo '#!/bin/sh' >> inplace/bin/runghc
909+ <br>chmod +x inplace/lib/bin/ghc-iserv-dyn
910+ <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/runghc"' >> inplace/bin/runghc
911+ <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
912+ <br>/usr/local/bin/ginstall -c -m 755 utils/hpc/dist-install/build/tmp/hpc inplace/lib/bin/hpc
913+ <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/runghc
914+ <br>"rm" -f inplace/bin/hpc
915+ <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
916+ <br>echo 'pgmgcc="cc"' >> inplace/bin/runghc
917+ <br>echo '#!/bin/sh' >> inplace/bin/hpc
918+ <br>echo 'ghcprog="ghc-stage2"' >> "inplace/bin/runghc"
919+ <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/hpc"' >> inplace/bin/hpc
920+ <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/process/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
921+ >> inplace/bin/runghc
922+ <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
923+ <br>cat utils/runghc/runghc.wrapper >> inplace/bin/runghc
924+ <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/hpc
925+ <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
926+ <br>chmod +x inplace/bin/runghc
927+ <br>echo 'pgmgcc="cc"' >> inplace/bin/hpc
928+ <br>"rm" -f utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
929+ <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/hpc/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
930+ >> inplace/bin/hpc
931+ <br>echo '#!/bin/sh' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
932+ <br>cat utils/hpc/hpc.wrapper >> inplace/bin/hpc
933+ <br>echo 'export DYLD_LIBRARY_PATH="libraries/Cabal/Cabal/dist-install/build:libraries/process/dist-install/build:libraries/parsec/dist-install/build:libraries/text/dist-install/build:libraries/template-haskell/dist-install/build:libraries/pretty/dist-install/build:libraries/ghc-boot-th/dist-install/build:libraries/mtl/dist-install/build:libraries/transformers/dist-install/build:libraries/directory/dist-install/build:libraries/unix/dist-install/build:libraries/time/dist-install/build:libraries/filepath/dist-install/build:libraries/binary/dist-install/build:libraries/containers/containers/dist-install/build:libraries/bytestring/dist-install/build:libraries/deepseq/dist-install/build:libraries/array/dist-install/build:libraries/base/dist-install/build:libraries/ghc-bignum/dist-install/build:libraries/ghc-prim/dist-install/build:rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
934+ >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
935+ <br>chmod +x inplace/bin/hpc
936+ <br>echo 'exec "utils/ghc-cabal/dist-install/build/tmp/ghc-cabal" ${1+"$@"}' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
937+ <br>chmod +x utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
938+ <br>
939+ <
940+ <ghc: 2787765536 bytes, 171 GCs, 20282449
941+ /67935792 avg/max bytes residency (8 samples), 144M in use, 0.000 INIT (0.003 elapsed), 1.738 MUT (2.286 elapsed), 0.567 GC (0.611 elapsed) :ghc>>
942+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages -i
943+ -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build
944+ -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0 -package-id
945+ binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619 -package-id
946+ ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id time-1.9.3
947+ -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build
948+ -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/HsToCore.hs -o compiler/stage2/build/GHC/HsToCore.o
949+ -dyno compiler/stage2/build/GHC/HsToCore.dyn_o
950+ <br>
951+ <
952+ <ghc: 766891168 bytes, 91 GCs, 14722329
953+ /42245856 avg/max bytes residency (6 samples), 91M in use, 0.000 INIT (0.003 elapsed), 0.361 MUT (0.510 elapsed), 0.236 GC (0.258 elapsed) :ghc>>
954+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages
955+ -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build
956+ -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0
957+ -package-id binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619
958+ -package-id ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id
959+ time-1.9.3 -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build
960+ -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/Iface/Ext/Ast.hs -o compiler/stage2/build/GHC/Iface/Ext/Ast.o
961+ -dyno compiler/stage2/build/GHC/Iface/Ext/Ast.dyn_o
962+ <br>
963+ <
964+ <ghc: 3067019736 bytes, 347 GCs, 25353490
965+ /71392248 avg/max bytes residency (8 samples), 184M in use, 0.000 INIT (0.002 elapsed), 1.416 MUT (1.823 elapsed), 0.554 GC (0.595 elapsed) :ghc>>
966+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages
967+ -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompil...
968+ </div>
969+ <!-- temp -->
970+ или:
971+ <!--/temp -->
972+ <div class="no-log">
973+ <div class="null null--data">
974+ <div class="null__content">
975+ <b class="null__heading">Cannot retrieve the data</b>
976+ <div class="null__message">Try to reload page</div>
977+ </div>
978+ </div>
979+ <!-- temp -->
980+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
981+ <!--/temp -->
982+ <div class="loading loading--enlarged loading--alternate">
983+ Loading...
984+ </div>
985+ </div>
986+ </div>
954987 </div>
955− </div>
956− <!-- temp -->
957− &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
958− <!--/temp -->
959− <div class="loading loading--enlarged loading--alternate">
960− Loading...
961− </div>
962988 </div>
963− </div>
964− </div>
965− </div>
966989
967− <div class="classic-popup" id="dialog">
968− <div class="classic-popup__container">
969− <div class="classic-popup__viewport">
970− <div class="classic-popup__slot">
990+ <div class="classic-popup" id="dialog">
991+ <div class="classic-popup__container">
992+ <div class="classic-popup__viewport">
993+ <div class="classic-popup__slot">
971994
972− <div class="dialog dialog--archive">
973− <div class="dialog__content">
974− Are you sure you want to archive the
975− <br>‘Feature name’ deployment?
976− </div>
977− <div class="dialog__footer">
978− <button class="dialog__action button" type="submit">Archive</button>
979− <button class="dialog__action button--secondary button" type="button">Cancel</button>
980− </div>
981− </div>
995+ <div class="dialog dialog--archive">
996+ <div class="dialog__content">
997+ Are you sure you want to archive the
998+ <br>‘Feature name’ deployment?
999+ </div>
1000+ <div class="dialog__footer">
1001+ <button class="dialog__action button" type="submit">Archive</button>
1002+ <button class="dialog__action button--secondary button" type="button">Cancel</button>
1003+ </div>
1004+ </div>
9821005
983− <button class="classic-popup__close" type="button">
1006+ <button class="classic-popup__close" type="button">
9841007 Close
9851008 </button>
1009+ </div>
1010+ </div>
1011+ </div>
9861012 </div>
987− </div>
988− </div>
989− </div>
9901013
9911014
992− <!-- jQuery -->
993− <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
1015+ <!-- jQuery -->
1016+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
9941017
995− <!-- Outline.js -->
996− <script src="../vendors/outline/outline.js"></script>
1018+ <!-- Outline.js -->
1019+ <script src="../vendors/outline/outline.js"></script>
9971020
998− <!-- Temp -->
999− <script src="../scripts/_expander.js"></script>
1000− <script src="../scripts/_drop.js"></script>
1001− <script src="../scripts/_sort.js"></script>
1002− <script src="../scripts/_data.js"></script>
1003− <script src="../scripts/_popup.js"></script>
1004− <script src="../scripts/_classic-popup.js"></script>
1021+ <!-- Temp -->
1022+ <script src="../scripts/_expander.js"></script>
1023+ <script src="../scripts/_drop.js"></script>
1024+ <script src="../scripts/_sort.js"></script>
1025+ <script src="../scripts/_data.js"></script>
1026+ <script src="../scripts/_popup.js"></script>
1027+ <script src="../scripts/_classic-popup.js"></script>
1028+ <script src="../scripts/_collapse.js"></script>
10051029
1006−</body>
1030+ </body>
10071031
1008−</html>
1032+</html>
octopod-css/development/layouts/staging.html+1159−668
@@ -1,738 +1,1229 @@
11<!DOCTYPE html>
22<html lang="en">
33
4−<head>
5− <meta charset="utf-8">
6− <meta http-equiv="x-ua-compatible" content="ie=edge">
7− <title>Deployment</title>
8− <meta name="viewport" content="width=device-width, initial-scale=1">
9− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
10− <link rel="stylesheet" href="../styles/style.css">
11−</head>
4+ <head>
5+ <meta charset="utf-8">
6+ <meta http-equiv="x-ua-compatible" content="ie=edge">
7+ <title>Deployment</title>
8+ <meta name="viewport" content="width=device-width, initial-scale=1">
9+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
10+ <link rel="stylesheet" href="../styles/style.css">
11+ </head>
1212
13−<body>
13+ <body>
1414
15− <!--[if IE]>
16− <div class="browserupgrade">
17− You are using an <strong>outdated</strong> browser.
18− Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
19− </div>
15+ <!--[if IE]>
16+ <div class="browserupgrade">
17+ You are using an <strong>outdated</strong> browser.
18+ Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
19+ </div>
2020 <![endif]-->
2121
22− <header class="header">
23− <div class="header__wrap container">
24− <b class="header__logo">
22+ <header class="header">
23+ <div class="header__wrap container">
24+ <b class="header__logo">
2525 Deployment Manager
2626 </b>
27− <div class="header__project">
28− Project name
29− </div>
30− </div>
31− </header>
27+ <div class="header__project">
28+ Project name
29+ </div>
30+ </div>
31+ </header>
3232
33− <div class="page">
34− <div class="page__wrap container">
35− <div class="page__output notification notification--success">
36− <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
37− <button class="notification__close" type="button"></button>
38− </div>
39− <a class="page__back dash dash--back dash--smaller" href="index.html">
33+ <div class="page">
34+ <div class="page__wrap container">
35+ <div class="page__output notification notification--success">
36+ <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
37+ <button class="notification__close" type="button"></button>
38+ </div>
39+ <a class="page__back dash dash--back dash--smaller" href="index.html">
4040 All deployments
4141 </a>
42− <div class="page__head">
43− <h1 class="page__heading title">
44− Project name
45− </h1>
46− <a class="page__action button button--edit popup-handler" href="#deployment">
42+ <div class="page__head">
43+ <h1 class="page__heading title">
44+ Project name
45+ </h1>
46+ <a class="page__action button button--edit popup-handler" href="#deployment">
4747 Edit deployment
4848 </a>
49− <a class="page__action button button--secondary button--archive classic-popup-handler" href="#dialog">
49+ <a class="page__action button button--secondary button--archive classic-popup-handler" href="#dialog">
5050 Move to archive
5151 </a>
52− <!-- temp -->
53− &nbsp;&nbsp;&nbsp;или для уже архивных:
54− <!--/temp -->
55− <a class="page__action button button--secondary button--restore classic-popup-handler" href="#dialog">
52+ <!-- temp -->
53+ &nbsp;&nbsp;&nbsp;или для уже архивных:
54+ <!--/temp -->
55+ <a class="page__action button button--secondary button--restore classic-popup-handler" href="#dialog">
5656 Recover from archive
5757 </a>
58− </div>
59− <div class="page__body">
60− <div class="deployment">
61− <div class="deployment__output notification notification--danger">
62− <b>Project successfully updated</b>
63− <button class="notification__close" type="button"></button>
64− </div>
65− <div class="deployment__summary">
66− <div class="deployment__stat">
67− <b class="deployment__param">
58+ </div>
59+ <div class="page__body">
60+ <div class="deployment">
61+ <div class="deployment__output notification notification--danger">
62+ <b>Project successfully updated</b>
63+ <button class="notification__close" type="button"></button>
64+ </div>
65+ <div class="deployment__summary">
66+ <div class="deployment__stat">
67+ <b class="deployment__param">
6868 Status
6969 </b>
70− <div class="deployment__value">
71− <div class="status status--success">Success</div>
72− </div>
73− </div>
74− <div class="deployment__stat">
75− <b class="deployment__param">
70+ <div class="deployment__value">
71+ <div class="status status--success">Success</div>
72+ </div>
73+ </div>
74+ <div class="deployment__stat">
75+ <b class="deployment__param">
7676 Created
7777 </b>
78− <div class="deployment__value">
79− 15.05.2020
80− </div>
81− </div>
82− <div class="deployment__stat">
83− <b class="deployment__param">
78+ <div class="deployment__value">
79+ 15.05.2020
80+ </div>
81+ </div>
82+ <div class="deployment__stat">
83+ <b class="deployment__param">
8484 Changed
8585 </b>
86− <div class="deployment__value">
87− 01.06.2020
88− </div>
89− </div>
90− </div>
91− <section class="deployment__section">
92− <h3 class="deployment__sub-heading">
93− Tag
94− </h3>
95− <div class="deployment__widget">
96− H97HF384GKERP39740223
97− </div>
98− </section>
99− <section class="deployment__section">
100− <h3 class="deployment__sub-heading">
101− Links
102− </h3>
103− <div class="deployment__widget">
104− <div class="listing">
105− <a class="listing__item external bar bar--larger" href="#">
86+ <div class="deployment__value">
87+ 01.06.2020
88+ </div>
89+ </div>
90+ </div>
91+ <section class="deployment__section">
92+ <h3 class="deployment__sub-heading">
93+ Tag
94+ </h3>
95+ <div class="deployment__widget">
96+ H97HF384GKERP39740223
97+ </div>
98+ </section>
99+ <section class="deployment__section">
100+ <h3 class="deployment__sub-heading">
101+ Links
102+ </h3>
103+ <div class="deployment__widget">
104+ <div class="listing">
105+ <a class="listing__item external bar bar--larger" href="#">
106106 app.feature-name.project.name.com
107107 </a>
108− <a class="listing__item external bar bar--larger" href="#">
108+ <a class="listing__item external bar bar--larger" href="#">
109109 admin.feature-name.project.name.com
110110 </a>
111− </div>
112− </div>
113− </section>
114− <section class="deployment__section">
115− <h3 class="deployment__sub-heading">
116− App configuration
117− </h3>
118− <div class="deployment__widget">
119− <div class="listing listing--for-text listing--larger">
120− <div class="listing__item">
121− <span class="listing__key">ENVIRONMENT:</span>
122− <span class="listing__value">production</span>
123− </div>
124− <div class="listing__item">
125− <span class="listing__key">app_api_url:</span>
126− <span class="listing__value">http://my-api.my-domain.com</span>
127− </div>
128− <div class="listing__item">
129− <span class="listing__key default">SECRET_CODE:</span>
130− <span class="listing__value">asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen</span>
131− </div>
132− <div class="listing__item deleted">
133− <span class="listing__key default">SECRET_CODE:</span>
134− <span class="listing__value default">asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen</span>
135− </div>
136− <div class="listing__item deleted">
137− <span class="listing__key default">SECRET_CODE:</span>
138− <div class="listing__placeholder"></div>
139− <div class="listing__spinner"></div>
140− </div>
141− <div class="listing__item">
142− <span class="listing__key default">INFO:</span>
143− <span class="listing__value default">New Project Staging</span>
144− </div>
145− </div>
146− </div>
147− </section>
148− <section class="deployment__section">
149− <h3 class="deployment__sub-heading">
150− Deployment configuration
151− </h3>
152− <div class="deployment__widget">
153− <div class="listing listing--for-text listing--larger">
154− <div class="listing__item">
155− <b>ENVIRONENT:</b> production
156− </div>
157− <div class="listing__item">
158− <b>app_api_url:</b> http: //my-api.my-domain.com
159− </div>
160− <div class="listing__item">
161− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij
162− </div>
163− </div>
164− </div>
165− </section>
166− <section class="deployment__section">
167− <h3 class="deployment__sub-heading">
168− Actions
169− </h3>
170− <div class="deployment__widget">
171− <div class="table table--actions">
172− <table>
173− <thead>
174− <tr>
175− <th>
176− Action type
177− </th>
178− <th>
179− App configuration
180− </th>
181− <th>
182− Deployment configuration
183− </th>
184− <th>
185− Exit code
186− </th>
187− <th>
188− <button class="sort sort--active sort--asc" type="button">Created</button>
189− </th>
190− <th>
191− Deployment duration
192− </th>
193− </tr>
194− </thead>
195− <tbody>
196− <tr>
197− <td>
198− Create
199− </td>
200− <td>
201− <div class="listing listing--for-text">
202− <div class="listing__item">
203− <b>ENVIRONENT:</b>
204− <div class="listing__placeholder listing__placeholder__value"></div>
205− <div class="listing__spinner"></div>
206− </div>
207− <div class="listing__item">
208− <b>app_api_url:</b> http://my-api.my-domain.com
209− </div>
210− <div class="listing__item">
211− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
212− </div>
213− <button class="listing__more expander" type="button">
111+ </div>
112+ </div>
113+ </section>
114+ <section class="deployment__section">
115+ <h3 class="deployment__sub-heading">
116+ App overrides
117+ </h3>
118+ <div class="deployment__widget">
119+ <div class="padded">
120+ <div class="row">
121+ <span class="key-custom-edited">ENVIRONENT: </span><span class="value-edited">production</span>
122+ </div>
123+ <div class="row">
124+ <span class="key-custom-edited">Secret_word: </span><span class="value-edited">Hello</span>
125+ </div>
126+ <div class="row">
127+ <span class="key-default-pristine">SECRET_CODE: </span><span class="value-edited">asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij</span>
128+ </div>
129+ <div class="row">
130+ <span class="key-deleted">sso.vaultEnv.hotels-sso.mapping[0].APP_URL </span><span class="value-deleted">http://my-api.my-domain.com</span>
131+ </div>
132+ <div class="row">
133+ <span class="key-deleted">air.vaultEnv.air-sso:</span> <span class="value-unknown">unknown</span>
134+ </div>
135+ <div class="collapse collapse--project">
136+ <button class="collapse__head" type="button">
137+ air
138+ </button>
139+ <div class="collapse__body">
140+ <div class="collapse__inner">
141+ <div class="row">
142+ <span class="key-default-pristine">vaultEngineName: </span><span class="value-pristine">staging</span>
143+ </div>
144+ <div class="collapse collapse--project">
145+ <button class="collapse__head" type="button">
146+ vaultEnv
147+ </button>
148+ <div class="collapse__body">
149+ <div class="collapse__inner">
150+ <div class="row">
151+ <span class="key-default-pristine">air-sso.engineOverride: </span><span class="value-pristine">staging</span>
152+ </div>
153+ <div class="collapse collapse--project">
154+ <button class="collapse__head" type="button">
155+ air-sso.mapping[0]
156+ </button>
157+ <div class="collapse__body">
158+ <div class="collapse__inner">
159+ <div class="row">
160+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
161+ </div>
162+ <div class="row">
163+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
164+ </div>
165+ </div>
166+ <div class="row">
167+ <span class="key-default-edited">air-sso.mapping[1].name: </span><span class="value-edited">SSO_CLIENT_SECRET</span>
168+ </div>
169+ </div>
170+ </div>
171+ <div class="collapse collapse--project">
172+ <button class="collapse__head" type="button">
173+ travolutionary.mapping[0]
174+ </button>
175+ <div class="collapse__body">
176+ <div class="collapse__inner">
177+ <div class="row">
178+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
179+ </div>
180+ <div class="row">
181+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
182+ </div>
183+ </div>
184+ </div>
185+ </div>
186+ <div class="collapse collapse--project">
187+ <button class="collapse__head" type="button">
188+ travolutionary.mapping[1]
189+ </button>
190+ <div class="collapse__body">
191+ <div class="collapse__inner">
192+ <div class="row">
193+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
194+ </div>
195+ <div class="row">
196+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
197+ </div>
198+ </div>
199+ </div>
200+ </div>
201+ <div class="row">
202+ <span class="key-default-edited">air-sso.mapping[1].name: </span><span class="value-edited">SSO_CLIENT_SECRET</span>
203+ </div>
204+ </div>
205+ </div>
206+ </div>
207+ </div>
208+ <div class="row">
209+ <span class="key-custom-edited">app_api_url: </span><span class="value-edited">http: //my-api.my-domain.com</span>
210+ </div>
211+ </div>
212+ </div>
213+ <div class="collapse collapse--project">
214+ <button class="collapse__head" type="button">
215+ sso
216+ </button>
217+ <div class="collapse__body">
218+ <div class="collapse__inner">
219+ <div class="row">
220+ <span class="key-default-pristine">vaultEngineName: </span><span class="value-pristine">staging</span>
221+ </div>
222+ <div class="collapse collapse--project">
223+ <button class="collapse__head" type="button">
224+ vaultEnv
225+ </button>
226+ <div class="collapse__body">
227+ <div class="collapse__inner">
228+ <div class="row">
229+ <span class="key-default-pristine">air-sso.engineOverride: </span><span class="value-pristine">staging</span>
230+ </div>
231+ <div class="collapse collapse--project">
232+ <button class="collapse__head" type="button">
233+ air-sso.mapping[0]
234+ </button>
235+ <div class="collapse__body">
236+ <div class="collapse__inner">
237+ <div class="row">
238+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
239+ </div>
240+ <div class="row">
241+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
242+ </div>
243+ </div>
244+ <div class="row">
245+ <span class="key-default-edited">air-sso.mapping[1].name: </span><span class="value-edited">SSO_CLIENT_SECRET</span>
246+ </div>
247+ </div>
248+ </div>
249+ <div class="collapse collapse--project">
250+ <button class="collapse__head" type="button">
251+ travolutionary.mapping[0]
252+ </button>
253+ <div class="collapse__body">
254+ <div class="collapse__inner">
255+ <div class="row">
256+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
257+ </div>
258+ <div class="row">
259+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
260+ </div>
261+ </div>
262+ </div>
263+ </div>
264+ <div class="collapse collapse--project">
265+ <button class="collapse__head" type="button">
266+ travolutionary.mapping[1]
267+ </button>
268+ <div class="collapse__body">
269+ <div class="collapse__inner">
270+ <div class="row">
271+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
272+ </div>
273+ <div class="row">
274+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
275+ </div>
276+ </div>
277+ </div>
278+ </div>
279+ <div class="row">
280+ <span class="key-default-edited">air-sso.mapping[1].name: </span><span class="value-edited">SSO_CLIENT_SECRET</span>
281+ </div>
282+ </div>
283+ </div>
284+ </div>
285+ </div>
286+ <div class="row">
287+ <span class="key-custom-edited">app_api_url: </span><span class="value-edited">http: //my-api.my-domain.com</span>
288+ </div>
289+ </div>
290+ </div>
291+ </div>
292+ </div>
293+ </section>
294+ <section class="deployment__section">
295+ <h3 class="deployment__sub-heading">
296+ Staging overrides
297+ </h3>
298+ <div class="deployment__widget">
299+ <div class="padded">
300+ <div class="row">
301+ <span class="key-custom-edited">ENVIRONENT: </span><span class="value-edited">http://my-api.my-domain.com</span>
302+ </div>
303+ <div class="row">
304+ <span class="key-default-pristine">SECRET_CODE: </span><span class="value-edited">http://my-api.my-domain.com</span>
305+ </div>
306+ <div class="row">
307+ <span class="key-default-pristine">ENVIRONENT: </span><span class="value-pristine">production</span>
308+ </div>
309+ </div>
310+ </div>
311+ </section>
312+ <section class="deployment__section">
313+ <h3 class="deployment__sub-heading">
314+ Actions
315+ </h3>
316+ <div class="deployment__widget">
317+ <div class="table table--actions">
318+ <table>
319+ <thead>
320+ <tr>
321+ <th>
322+ Action type
323+ </th>
324+ <th>
325+ App configuration
326+ </th>
327+ <th>
328+ Deployment configuration
329+ </th>
330+ <th>
331+ Exit code
332+ </th>
333+ <th>
334+ <button class="sort sort--active sort--asc" type="button">Created</button>
335+ </th>
336+ <th>
337+ Deployment duration
338+ </th>
339+ </tr>
340+ </thead>
341+ <tbody>
342+ <tr>
343+ <td>
344+ Create
345+ </td>
346+ <td>
347+ <div class="listing listing--for-text">
348+ <div class="listing__item">
349+ <b>ENVIRONENT:</b>
350+ <div class="listing__placeholder listing__placeholder__value"></div>
351+ <div class="listing__spinner"></div>
352+ </div>
353+ <div class="listing__item">
354+ <b>app_api_url:</b> http://my-api.my-domain.com
355+ </div>
356+ <div class="listing__item">
357+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
358+ </div>
359+ <button class="listing__more expander" type="button">
214360 Show all (5)
215361 </button>
216− </div>
217− </td>
218− <td>
219− <div class="listing listing--for-text">
220− <div class="listing__item">
221− <b>ENVIRONENT:</b> production
222− </div>
223− <div class="listing__item">
224− <b>app_api_url:</b> http://my-api.my-domain.com
225− </div>
226− <div class="listing__item">
227− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
228− </div>
229− <button class="listing__more expander" type="button">
362+ </div>
363+ </td>
364+ <td>
365+ <div class="listing listing--for-text">
366+ <div class="listing__item">
367+ <b>ENVIRONENT:</b> production
368+ </div>
369+ <div class="listing__item">
370+ <b>app_api_url:</b> http://my-api.my-domain.com
371+ </div>
372+ <div class="listing__item">
373+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
374+ </div>
375+ <button class="listing__more expander" type="button">
230376 Show all (5)
231377 </button>
232− </div>
233− </td>
234− <td>
235− 0
236− </td>
237− <td>
238− 2020-05-13 08:06:00 313811+00
239− </td>
240− <td>
241− 1m 95s
242− </td>
243− </tr>
244− <tr>
245− <td>
246− Edit
247− </td>
248− <td>
249− <div class="listing listing--for-text">
250− <div class="listing__item">
251− <b>ENVIRONENT:</b> production
252− </div>
253− <div class="listing__item">
254− <b>app_api_url:</b> http://my-api.my-domain.com
255− </div>
256− <div class="listing__item">
257− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
258− </div>
259− </div>
260− </td>
261− <td>
262− <div class="listing listing--for-text">
263− <div class="listing__item">
264− <b>ENVIRONENT:</b> production
265− </div>
266− <div class="listing__item">
267− <b>app_api_url:</b> http://my-api.my-domain.com
268− </div>
269− <div class="listing__item">
270− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
271− </div>
272− </div>
273− </td>
274− <td>
275− 1
276− </td>
277− <td>
278− 2020-05-13 08:06:00 313811+00
279− </td>
280− <td>
281− 1m 95s
282− </td>
283− </tr>
284− <tr>
285− <td>
286− Update
287− </td>
288− <td>
289− <div class="listing listing--for-text">
290− <div class="listing__item">
291− <b>ENVIRONENT:</b> production
292− </div>
293− <div class="listing__item">
294− <b>app_api_url:</b> http://my-api.my-domain.com
295− </div>
296− <div class="listing__item">
297− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
298− </div>
299− </div>
300− </td>
301− <td>
302− <div class="listing listing--for-text">
303− <div class="listing__item">
304− <b>ENVIRONENT:</b> production
305− </div>
306− <div class="listing__item">
307− <b>app_api_url:</b> http://my-api.my-domain.com
308− </div>
309− <div class="listing__item">
310− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
311− </div>
312− </div>
313− </td>
314− <td>
315− 0
316− </td>
317− <td>
318− 2020-05-13 08:06:00 313811+00
319− </td>
320− <td>
321− 1m 95s
322− </td>
323− </tr>
324− <tr>
325− <td>
326− Delete
327− </td>
328− <td>
329− <div class="listing listing--for-text">
330− <div class="listing__item">
331− <b>ENVIRONENT:</b> production
332− </div>
333− <div class="listing__item">
334− <b>app_api_url:</b> http://my-api.my-domain.com
335− </div>
336− <div class="listing__item">
337− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
338− </div>
339− </div>
340− </td>
341− <td>
342− <div class="listing listing--for-text">
343− <div class="listing__item">
344− <b>ENVIRONENT:</b> production
345− </div>
346− <div class="listing__item">
347− <b>app_api_url:</b> http://my-api.my-domain.com
348− </div>
349− <div class="listing__item">
350− <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
351− </div>
352− </div>
353− </td>
354− <td>
355− 1
356− </td>
357− <td>
358− 2020-05-13 08:06:00 313811+00
359− </td>
360− <td>
361− 1m 95s
362− </td>
363− </tr>
364− <tr class="no-table">
365− <td colspan="7">
378+ </div>
379+ </td>
380+ <td>
381+ 0
382+ </td>
383+ <td>
384+ 2020-05-13 08:06:00 313811+00
385+ </td>
386+ <td>
387+ 1m 95s
388+ </td>
389+ </tr>
390+ <tr>
391+ <td>
392+ Edit
393+ </td>
394+ <td>
395+ <div class="listing listing--for-text">
396+ <div class="listing__item">
397+ <b>ENVIRONENT:</b> production
398+ </div>
399+ <div class="listing__item">
400+ <b>app_api_url:</b> http://my-api.my-domain.com
401+ </div>
402+ <div class="listing__item">
403+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
404+ </div>
405+ </div>
406+ </td>
407+ <td>
408+ <div class="listing listing--for-text">
409+ <div class="listing__item">
410+ <b>ENVIRONENT:</b> production
411+ </div>
412+ <div class="listing__item">
413+ <b>app_api_url:</b> http://my-api.my-domain.com
414+ </div>
415+ <div class="listing__item">
416+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
417+ </div>
418+ </div>
419+ </td>
420+ <td>
421+ 1
422+ </td>
423+ <td>
424+ 2020-05-13 08:06:00 313811+00
425+ </td>
426+ <td>
427+ 1m 95s
428+ </td>
429+ </tr>
430+ <tr>
431+ <td>
432+ Update
433+ </td>
434+ <td>
435+ <div class="listing listing--for-text">
436+ <div class="listing__item">
437+ <b>ENVIRONENT:</b> production
438+ </div>
439+ <div class="listing__item">
440+ <b>app_api_url:</b> http://my-api.my-domain.com
441+ </div>
442+ <div class="listing__item">
443+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
444+ </div>
445+ </div>
446+ </td>
447+ <td>
448+ <div class="listing listing--for-text">
449+ <div class="listing__item">
450+ <b>ENVIRONENT:</b> production
451+ </div>
452+ <div class="listing__item">
453+ <b>app_api_url:</b> http://my-api.my-domain.com
454+ </div>
455+ <div class="listing__item">
456+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
457+ </div>
458+ </div>
459+ </td>
460+ <td>
461+ 0
462+ </td>
463+ <td>
464+ 2020-05-13 08:06:00 313811+00
465+ </td>
466+ <td>
467+ 1m 95s
468+ </td>
469+ </tr>
470+ <tr>
471+ <td>
472+ Delete
473+ </td>
474+ <td>
475+ <div class="listing listing--for-text">
476+ <div class="listing__item">
477+ <b>ENVIRONENT:</b> production
478+ </div>
479+ <div class="listing__item">
480+ <b>app_api_url:</b> http://my-api.my-domain.com
481+ </div>
482+ <div class="listing__item">
483+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
484+ </div>
485+ </div>
486+ </td>
487+ <td>
488+ <div class="listing listing--for-text">
489+ <div class="listing__item">
490+ <b>ENVIRONENT:</b> production
491+ </div>
492+ <div class="listing__item">
493+ <b>app_api_url:</b> http://my-api.my-domain.com
494+ </div>
495+ <div class="listing__item">
496+ <b>SECRET_CODE:</b> asdfaisdjri235868ear7%lorem-ipsum-dolor-sit-amen
497+ </div>
498+ </div>
499+ </td>
500+ <td>
501+ 1
502+ </td>
503+ <td>
504+ 2020-05-13 08:06:00 313811+00
505+ </td>
506+ <td>
507+ 1m 95s
508+ </td>
509+ </tr>
510+ <tr class="no-table">
511+ <td colspan="7">
512+ <div class="null null--data">
513+ <b class="null__heading">Cannot retrieve the data</b>
514+ <div class="null__message">Try to reload page</div>
515+ </div>
516+ <!-- temp -->
517+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
518+ <!--/temp -->
519+ <div class="loading loading--enlarged loading--alternate">
520+ Loading...
521+ </div>
522+ </td>
523+ </tr>
524+ </tbody>
525+ </table>
526+ </div>
527+ </div>
528+ </section>
529+ </div>
530+ <!-- temp -->
531+ или:
532+ <!--/temp -->
533+ <div class="no-deployment">
366534 <div class="null null--data">
367− <b class="null__heading">Cannot retrieve the data</b>
368− <div class="null__message">Try to reload page</div>
535+ <div class="null__content">
536+ <b class="null__heading">Cannot retrieve the data</b>
537+ <div class="null__message">Try to reload page</div>
538+ </div>
369539 </div>
370− <!-- temp -->
371− &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
372− <!--/temp -->
540+ или:
373541 <div class="loading loading--enlarged loading--alternate">
374− Loading...
542+ Loading...
375543 </div>
376− </td>
377− </tr>
378− </tbody>
379− </table>
380− </div>
381− </div>
382− </section>
383− </div>
384− <!-- temp -->
385− или:
386− <!--/temp -->
387− <div class="no-deployment">
388− <div class="null null--data">
389− <div class="null__content">
390− <b class="null__heading">Cannot retrieve the data</b>
391− <div class="null__message">Try to reload page</div>
392− </div>
393− </div>
394− или:
395− <div class="loading loading--enlarged loading--alternate">
396− Loading...
397− </div>
398− </div>
399− </div>
400− </div>
401− </div>
402−
403− <!-- или:
404− <div class="no-page">
405− <div class="no-page__inner">
406− <div class="null null--data">
407− <b class="null__heading">Cannot retrieve the data</b>
408− <div class="null__message">Try to reload page</div>
409− </div>
410− или:
411− <div class="loading loading--enlarged loading--alternate">
412− Loading...
544+ </div>
413545 </div>
414546 </div>
415547 </div>
416− -->
417548
418− <div class="popup" id="deployment">
419− <div class="popup__overlay" aria-hidden="true">
420− Close popup
421− </div>
422− <div class="popup__body">
423− <div class="popup__head">
424− <button class="popup__close" type="button">
549+ <!-- или:
550+ <div class="no-page">
551+ <div class="no-page__inner">
552+ <div class="null null--data">
553+ <b class="null__heading">Cannot retrieve the data</b>
554+ <div class="null__message">Try to reload page</div>
555+ </div>
556+ или:
557+ <div class="loading loading--enlarged loading--alternate">
558+ Loading...
559+ </div>
560+ </div>
561+ </div>
562+ -->
563+
564+ <div class="popup" id="deployment">
565+ <div class="popup__overlay" aria-hidden="true">
566+ Close popup
567+ </div>
568+ <div class="popup__body">
569+ <div class="popup__head">
570+ <button class="popup__close" type="button">
425571 Close popup
426572 </button>
427− <h2 class="popup__project">
428− Project name
429− </h2>
430− <div class="popup__operations">
431− <button class="popup__action button button--save" type="button">Save</button>
432− <button class="popup__action button button--secondary" type="button">Cancel</button>
433− </div>
434− <div class="popup__menu drop drop--actions">
435− <button class="drop__handler" type="button">
573+ <h2 class="popup__project">
574+ Project name
575+ </h2>
576+ <div class="popup__operations">
577+ <button class="popup__action button button--save" type="button">Save</button>
578+ <button class="popup__action button button--secondary" type="button">Cancel</button>
579+ </div>
580+ <div class="popup__menu drop drop--actions">
581+ <button class="drop__handler" type="button">
436582 Actions
437583 </button>
438− <div class="drop__dropdown">
439− <button class="action action--archive" type="button">Move to archive</button>
440− <button class="action action--restore" type="button">Restore from archive</button>
441− </div>
442− </div>
443− </div>
444− <div class="popup__content">
584+ <div class="drop__dropdown">
585+ <button class="action action--archive" type="button">Move to archive</button>
586+ <button class="action action--restore" type="button">Restore from archive</button>
587+ </div>
588+ </div>
589+ </div>
590+ <div class="popup__content">
445591
446− <div class="deployment">
447− <div class="deployment__output notification notification--danger">
448− <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
449− <button class="notification__close" type="button"></button>
450− </div>
451− <section class="deployment__section">
452− <h3 class="deployment__sub-heading">
453− Tag
454− </h3>
455− <div class="deployment__widget">
456− <div class="tag input">
457− <input class="input__widget" type="text" placeholder="key" value="H97HF384GKERP39740223">
458− </div>
459− </div>
460− </section>
461− <section class="deployment__section">
462− <h3 class="deployment__sub-heading">
463− App configuration
464− </h3>
465− <div class="deployment__widget">
466− <div class="overrides">
467− <button class="overrides__add dash dash--add" type="button">
592+ <div class="deployment">
593+ <div class="deployment__output notification notification--danger">
594+ <b>System error.</b> Сan not move the deployment to the archive right now. Please try later.
595+ <button class="notification__close" type="button"></button>
596+ </div>
597+ <section class="deployment__section">
598+ <h3 class="deployment__sub-heading">
599+ Tag
600+ </h3>
601+ <div class="deployment__widget">
602+ <div class="tag input">
603+ <input class="input__widget" type="text" placeholder="key" value="H97HF384GKERP39740223">
604+ </div>
605+ </div>
606+ </section>
607+ <section class="deployment__section">
608+ <h3 class="deployment__sub-heading">
609+ App configuration
610+ </h3>
611+ <div class="deployment__widget">
612+ <div class="overrides">
613+ <button class="overrides__add dash dash--add" type="button">
468614 Add an override
469615 </button>
470− <div class="overrides__item">
471− <div class="overrides__key input">
472− <input class="input__widget" type="text" placeholder="key">
473− </div>
474− <div class="overrides__value input">
475− <input class="input__widget" type="text" placeholder="value">
476− </div>
477− <button class="overrides__delete spot spot--cancel">
478− Delete
479− </button>
480− </div>
481− <div class="overrides__item">
482− <div class="overrides__key input">
483− <input class="input__widget" type="text" placeholder="key" value="ENVIRONENT">
484− </div>
485− <div class="overrides__value input">
486− <input class="input__widget" type="text" placeholder="value" value="production">
487− </div>
488− <button class="overrides__delete spot spot--cancel">
489− Delete
490− </button>
491− </div>
492− <div class="overrides__item">
493− <div class="overrides__key input">
494− <input class="input__widget" type="text" placeholder="key" value="app_api_url">
495− </div>
496− <div class="overrides__value input">
497− <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com">
498− </div>
499− <button class="overrides__delete spot spot--cancel">
500− Delete
501− </button>
502− </div>
503− <div class="overrides__item">
504− <div class="overrides__key input">
505− <input class="input__widget" type="text" placeholder="key" value="SECRET_CODE">
506− </div>
507− <div class="overrides__value input">
508− <input class="input__widget" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
509− </div>
510− <button class="overrides__delete spot spot--cancel">
511− Delete
512− </button>
513− </div>
514− </div>
515− </div>
516− </section>
517− <section class="deployment__section">
518− <h3 class="deployment__sub-heading">
519− Deployment configuration
520− </h3>
521− <div class="deployment__widget">
522− <div class="overrides">
523− <button class="overrides__add dash dash--add" type="button">
616+ <div class="overrides__list">
617+ <div class="editable-row">
618+ <div class="editable-row__key input">
619+ <input class="input__widget" type="text" placeholder="key">
620+ </div>
621+ <div class="editable-row__value input">
622+ <input class="input__widget" type="text" placeholder="value">
623+ </div>
624+ <button class="editable-row__delete spot spot--cancel">
625+ Delete
626+ </button>
627+ </div>
628+ <div class="editable-row">
629+ <div class="editable-row__key input">
630+ <input class="input__widget key-custom-edited" type="text" placeholder="key" value="ENVIRONENT">
631+ </div>
632+ <div class="editable-row__value input">
633+ <input class="input__widget value-edited" type="text" placeholder="value" value="production">
634+ </div>
635+ <button class="editable-row__delete spot spot--cancel">
636+ Delete
637+ </button>
638+ </div>
639+ <div class="editable-row">
640+ <div class="editable-row__key input">
641+ <input class="input__widget key-custom-edited" type="text" placeholder="key" value="app_api_url">
642+ </div>
643+ <div class="editable-row__value input">
644+ <input class="input__widget value-edited" type="text" placeholder="value" value="http://my-api.my-domain.com">
645+ </div>
646+ <button class="editable-row__delete spot spot--cancel">
647+ Delete
648+ </button>
649+ </div>
650+ <div class="editable-row">
651+ <div class="editable-row__key input">
652+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
653+ </div>
654+ <div class="editable-row__value input">
655+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
656+ </div>
657+ <button class="editable-row__delete spot spot--cancel">
658+ Delete
659+ </button>
660+ </div>
661+ </div>
662+ <div class="overrides__tree">
663+ <div class="collapse collapse--project">
664+ <button class="collapse__head" type="button">
665+ air
666+ </button>
667+ <div class="collapse__body">
668+ <div class="collapse__inner">
669+ <div class="editable-row">
670+ <div class="editable-row__key input">
671+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
672+ </div>
673+ <div class="editable-row__value input">
674+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
675+ </div>
676+ <button class="editable-row__delete spot spot--cancel">
677+ Delete
678+ </button>
679+ </div>
680+ <div class="collapse collapse--project">
681+ <button class="collapse__head" type="button">
682+ vaultEnv
683+ </button>
684+ <div class="collapse__body">
685+ <div class="collapse__inner">
686+ <div class="editable-row">
687+ <div class="editable-row__key input">
688+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
689+ </div>
690+ <div class="editable-row__value input">
691+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
692+ </div>
693+ <button class="editable-row__delete spot spot--cancel">
694+ Delete
695+ </button>
696+ </div>
697+ <div class="collapse collapse--project">
698+ <button class="collapse__head" type="button">
699+ air-sso.mapping[0]
700+ </button>
701+ <div class="collapse__body">
702+ <div class="collapse__inner">
703+ <div class="editable-row">
704+ <div class="editable-row__key input">
705+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
706+ </div>
707+ <div class="editable-row__value input">
708+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
709+ </div>
710+ <button class="editable-row__delete spot spot--cancel">
711+ Delete
712+ </button>
713+ </div>
714+ <div class="editable-row">
715+ <div class="editable-row__key input">
716+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
717+ </div>
718+ <div class="editable-row__value input">
719+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
720+ </div>
721+ <button class="editable-row__delete spot spot--cancel">
722+ Delete
723+ </button>
724+ </div>
725+ </div>
726+ <div class="editable-row">
727+ <div class="editable-row__key input">
728+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
729+ </div>
730+ <div class="editable-row__value input">
731+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
732+ </div>
733+ <button class="editable-row__delete spot spot--cancel">
734+ Delete
735+ </button>
736+ </div>
737+ </div>
738+ </div>
739+ <div class="collapse collapse--project">
740+ <button class="collapse__head" type="button">
741+ travolutionary.mapping[0]
742+ </button>
743+ <div class="collapse__body">
744+ <div class="collapse__inner">
745+ <div class="editable-row">
746+ <div class="editable-row__key input">
747+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
748+ </div>
749+ <div class="editable-row__value input">
750+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
751+ </div>
752+ <button class="editable-row__delete spot spot--cancel">
753+ Delete
754+ </button>
755+ </div>
756+ <div class="editable-row">
757+ <div class="editable-row__key input">
758+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
759+ </div>
760+ <div class="editable-row__value input">
761+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
762+ </div>
763+ <button class="editable-row__delete spot spot--cancel">
764+ Delete
765+ </button>
766+ </div>
767+ </div>
768+ </div>
769+ </div>
770+ <div class="collapse collapse--project">
771+ <button class="collapse__head" type="button">
772+ travolutionary.mapping[1]
773+ </button>
774+ <div class="collapse__body">
775+ <div class="collapse__inner">
776+ <div class="editable-row">
777+ <div class="editable-row__key input">
778+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
779+ </div>
780+ <div class="editable-row__value input">
781+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
782+ </div>
783+ <button class="editable-row__delete spot spot--cancel">
784+ Delete
785+ </button>
786+ </div>
787+ <div class="editable-row">
788+ <div class="editable-row__key input">
789+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
790+ </div>
791+ <div class="editable-row__value input">
792+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
793+ </div>
794+ <button class="editable-row__delete spot spot--cancel">
795+ Delete
796+ </button>
797+ </div>
798+ </div>
799+ </div>
800+ </div>
801+ <div class="editable-row">
802+ <div class="editable-row__key input">
803+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
804+ </div>
805+ <div class="editable-row__value input">
806+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
807+ </div>
808+ <button class="editable-row__delete spot spot--cancel">
809+ Delete
810+ </button>
811+ </div>
812+ </div>
813+ </div>
814+ </div>
815+ </div>
816+ <div class="editable-row">
817+ <div class="editable-row__key input">
818+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
819+ </div>
820+ <div class="editable-row__value input">
821+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
822+ </div>
823+ <button class="editable-row__delete spot spot--cancel">
824+ Delete
825+ </button>
826+ </div>
827+ </div>
828+ </div>
829+ <div class="collapse collapse--project">
830+ <button class="collapse__head" type="button">
831+ sso
832+ </button>
833+ <div class="collapse__body">
834+ <div class="collapse__inner">
835+ <div class="editable-row">
836+ <div class="editable-row__key input">
837+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
838+ </div>
839+ <div class="editable-row__value input">
840+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
841+ </div>
842+ <button class="editable-row__delete spot spot--cancel">
843+ Delete
844+ </button>
845+ </div>
846+ <div class="collapse collapse--project">
847+ <button class="collapse__head" type="button">
848+ vaultEnv
849+ </button>
850+ <div class="collapse__body">
851+ <div class="collapse__inner">
852+ <div class="editable-row">
853+ <div class="editable-row__key input">
854+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
855+ </div>
856+ <div class="editable-row__value input">
857+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
858+ </div>
859+ <button class="editable-row__delete spot spot--cancel">
860+ Delete
861+ </button>
862+ </div>
863+ <div class="collapse collapse--project">
864+ <button class="collapse__head" type="button">
865+ air-sso.mapping[0]
866+ </button>
867+ <div class="collapse__body">
868+ <div class="collapse__inner">
869+ <div class="editable-row">
870+ <div class="editable-row__key input">
871+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
872+ </div>
873+ <div class="editable-row__value input">
874+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
875+ </div>
876+ <button class="editable-row__delete spot spot--cancel">
877+ Delete
878+ </button>
879+ </div>
880+ <div class="editable-row">
881+ <div class="editable-row__key input">
882+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
883+ </div>
884+ <div class="editable-row__value input">
885+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
886+ </div>
887+ <button class="editable-row__delete spot spot--cancel">
888+ Delete
889+ </button>
890+ </div>
891+ </div>
892+ <div class="editable-row">
893+ <div class="editable-row__key input">
894+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
895+ </div>
896+ <div class="editable-row__value input">
897+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
898+ </div>
899+ <button class="editable-row__delete spot spot--cancel">
900+ Delete
901+ </button>
902+ </div>
903+ </div>
904+ </div>
905+ <div class="collapse collapse--project">
906+ <button class="collapse__head" type="button">
907+ travolutionary.mapping[0]
908+ </button>
909+ <div class="collapse__body">
910+ <div class="collapse__inner">
911+ <div class="editable-row">
912+ <div class="editable-row__key input">
913+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
914+ </div>
915+ <div class="editable-row__value input">
916+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
917+ </div>
918+ <button class="editable-row__delete spot spot--cancel">
919+ Delete
920+ </button>
921+ </div>
922+ <div class="editable-row">
923+ <div class="editable-row__key input">
924+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
925+ </div>
926+ <div class="editable-row__value input">
927+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
928+ </div>
929+ <button class="editable-row__delete spot spot--cancel">
930+ Delete
931+ </button>
932+ </div>
933+ </div>
934+ </div>
935+ </div>
936+ <div class="collapse collapse--project">
937+ <button class="collapse__head" type="button">
938+ travolutionary.mapping[1]
939+ </button>
940+ <div class="collapse__body">
941+ <div class="collapse__inner">
942+ <div class="editable-row">
943+ <div class="editable-row__key input">
944+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
945+ </div>
946+ <div class="editable-row__value input">
947+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
948+ </div>
949+ <button class="editable-row__delete spot spot--cancel">
950+ Delete
951+ </button>
952+ </div>
953+ <div class="editable-row">
954+ <div class="editable-row__key input">
955+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
956+ </div>
957+ <div class="editable-row__value input">
958+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
959+ </div>
960+ <button class="editable-row__delete spot spot--cancel">
961+ Delete
962+ </button>
963+ </div>
964+ </div>
965+ </div>
966+ </div>
967+ <div class="editable-row">
968+ <div class="editable-row__key input">
969+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
970+ </div>
971+ <div class="editable-row__value input">
972+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
973+ </div>
974+ <button class="editable-row__delete spot spot--cancel">
975+ Delete
976+ </button>
977+ </div>
978+ </div>
979+ </div>
980+ </div>
981+ </div>
982+ <div class="editable-row">
983+ <div class="editable-row__key input">
984+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
985+ </div>
986+ <div class="editable-row__value input">
987+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
988+ </div>
989+ <button class="editable-row__delete spot spot--cancel">
990+ Delete
991+ </button>
992+ </div>
993+ </div>
994+ </div>
995+ </div>
996+ </div>
997+ </div>
998+ </section>
999+ <section class="deployment__section">
1000+ <h3 class="deployment__sub-heading">
1001+ Deployment configuration
1002+ </h3>
1003+ <div class="deployment__widget">
1004+ <div class="overrides">
1005+ <button class="overrides__add dash dash--add" type="button">
5241006 Add an override
5251007 </button>
526− <div class="overrides__item">
527− <div class="overrides__key input">
528− <input class="input__widget" type="text" placeholder="key">
529− </div>
530− <div class="overrides__value input">
531− <input class="input__widget" type="text" placeholder="value">
532− </div>
533− <button class="overrides__delete spot spot--cancel">
534− Delete
535− </button>
536− </div>
537− <div class="overrides__item">
538− <div class="overrides__key input">
539− <input class="input__widget" type="text" placeholder="key" value="ENVIRONENT">
540− </div>
541− <div class="overrides__value input">
542− <input class="input__widget" type="text" placeholder="value" value="production">
543− </div>
544− <button class="overrides__delete spot spot--cancel">
545− Delete
546− </button>
547− </div>
548− <div class="overrides__item">
549− <div class="overrides__key input">
550− <input class="input__widget" type="text" placeholder="key" value="app_api_url">
551− </div>
552− <div class="overrides__value input">
553− <input class="input__widget" type="text" placeholder="value" value="http://my-api.my-domain.com">
554− </div>
555− <button class="overrides__delete spot spot--cancel">
556− Delete
557− </button>
558− </div>
559− <div class="overrides__item">
560− <div class="overrides__key input">
561− <input class="input__widget" type="text" placeholder="key" value="SECRET_CODE">
562− </div>
563− <div class="overrides__value input">
564− <input class="input__widget" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
565− </div>
566− <button class="overrides__delete spot spot--cancel">
567− Delete
568− </button>
1008+ <div class="overrides__list">
1009+ <div class="editable-row">
1010+ <div class="editable-row__key input">
1011+ <input class="input__widget" type="text" placeholder="key">
1012+ </div>
1013+ <div class="editable-row__value input">
1014+ <input class="input__widget" type="text" placeholder="value">
1015+ </div>
1016+ <button class="editable-row__delete spot spot--cancel">
1017+ Delete
1018+ </button>
1019+ </div>
1020+ <div class="editable-row">
1021+ <div class="editable-row__key input">
1022+ <input class="input__widget key-custom-edited" type="text" placeholder="key" value="ENVIRONENT">
1023+ </div>
1024+ <div class="editable-row__value input">
1025+ <input class="input__widget value-edited" type="text" placeholder="value" value="production">
1026+ </div>
1027+ <button class="editable-row__delete spot spot--cancel">
1028+ Delete
1029+ </button>
1030+ </div>
1031+ <div class="editable-row">
1032+ <div class="editable-row__key input">
1033+ <input class="input__widget key-custom-edited" type="text" placeholder="key" value="app_api_url">
1034+ </div>
1035+ <div class="editable-row__value input">
1036+ <input class="input__widget value-edited" type="text" placeholder="value" value="http://my-api.my-domain.com">
1037+ </div>
1038+ <button class="editable-row__delete spot spot--cancel">
1039+ Delete
1040+ </button>
1041+ </div>
1042+ <div class="editable-row">
1043+ <div class="editable-row__key input">
1044+ <input class="input__widget key-default-pristine" type="text" placeholder="key" value="SECRET_CODE">
1045+ </div>
1046+ <div class="editable-row__value input">
1047+ <input class="input__widget value-edited" type="text" placeholder="value" value="asdfaisdjri235868ear7%^&IUAK$^%&asdfasdfij">
1048+ </div>
1049+ <button class="editable-row__delete spot spot--cancel">
1050+ Delete
1051+ </button>
1052+ </div>
1053+ </div>
1054+ </div>
1055+ </div>
1056+ </section>
1057+ </div>
1058+ <!-- temp -->
1059+ или:
1060+ <!--/temp -->
1061+ <div class="no-deployment">
1062+ <div class="null null--data">
1063+ <div class="null__content">
1064+ <b class="null__heading">Cannot retrieve the data</b>
1065+ <div class="null__message">Try to reload page</div>
1066+ </div>
1067+ </div>
1068+ <!-- temp -->
1069+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
1070+ <!--/temp -->
1071+ <div class="loading loading--enlarged loading--alternate">
1072+ Loading...
1073+ </div>
1074+ </div>
5691075 </div>
570− </div>
5711076 </div>
572− </section>
5731077 </div>
574− <!-- temp -->
575− или:
576− <!--/temp -->
577− <div class="no-deployment">
578− <div class="null null--data">
579− <div class="null__content">
580− <b class="null__heading">Cannot retrieve the data</b>
581− <div class="null__message">Try to reload page</div>
582− </div>
583− </div>
584− <!-- temp -->
585− &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
586− <!--/temp -->
587− <div class="loading loading--enlarged loading--alternate">
588− Loading...
589− </div>
590− </div>
591− </div>
592− </div>
593− </div>
5941078
5951079
596− <div class="popup" id="log">
597− <div class="popup__overlay" aria-hidden="true">
598− Close popup
599− </div>
600− <div class="popup__body">
601− <div class="popup__head">
602− <button class="popup__close" type="button">
1080+ <div class="popup" id="log">
1081+ <div class="popup__overlay" aria-hidden="true">
1082+ Close popup
1083+ </div>
1084+ <div class="popup__body">
1085+ <div class="popup__head">
1086+ <button class="popup__close" type="button">
6031087 Close popup
6041088 </button>
605− <h2 class="popup__project">
606− Project name, logs
607− </h2>
608− </div>
609− <div class="popup__sub-head">
610− <div class="nav">
611− <button class="nav__link nav__link--current" type="button">stdout</button>
612− <button class="nav__link" type="button">stderr</button>
613− </div>
614− </div>
615− <div class="popup__content">
616− <div class="log">
617− DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/libiserv/dist-install/build:/Users/dr/workspace/ghc/libraries/ghci/dist-install/build:/Users/dr/workspace/ghc/libraries/transformers/dist-install/build:/Users/dr/workspace/ghc/libraries/template-haskell/dist-install/build:/Users/dr/workspace/ghc/libraries/pretty/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-heap/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot-th/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/binary/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
618− >> inplace/lib/bin/ghc-iserv-dyn
619− <br>"rm" -f inplace/bin/runghc
620− <br>echo 'exec "$executablename" ${1+"$@"}' >> inplace/lib/bin/ghc-iserv-dyn
621− <br>echo '#!/bin/sh' >> inplace/bin/runghc
622− <br>chmod +x inplace/lib/bin/ghc-iserv-dyn
623− <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/runghc"' >> inplace/bin/runghc
624− <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
625− <br>/usr/local/bin/ginstall -c -m 755 utils/hpc/dist-install/build/tmp/hpc inplace/lib/bin/hpc
626− <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/runghc
627− <br>"rm" -f inplace/bin/hpc
628− <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
629− <br>echo 'pgmgcc="cc"' >> inplace/bin/runghc
630− <br>echo '#!/bin/sh' >> inplace/bin/hpc
631− <br>echo 'ghcprog="ghc-stage2"' >> "inplace/bin/runghc"
632− <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/hpc"' >> inplace/bin/hpc
633− <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/process/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
634− >> inplace/bin/runghc
635− <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
636− <br>cat utils/runghc/runghc.wrapper >> inplace/bin/runghc
637− <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/hpc
638− <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
639− <br>chmod +x inplace/bin/runghc
640− <br>echo 'pgmgcc="cc"' >> inplace/bin/hpc
641− <br>"rm" -f utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
642− <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/hpc/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
643− >> inplace/bin/hpc
644− <br>echo '#!/bin/sh' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
645− <br>cat utils/hpc/hpc.wrapper >> inplace/bin/hpc
646− <br>echo 'export DYLD_LIBRARY_PATH="libraries/Cabal/Cabal/dist-install/build:libraries/process/dist-install/build:libraries/parsec/dist-install/build:libraries/text/dist-install/build:libraries/template-haskell/dist-install/build:libraries/pretty/dist-install/build:libraries/ghc-boot-th/dist-install/build:libraries/mtl/dist-install/build:libraries/transformers/dist-install/build:libraries/directory/dist-install/build:libraries/unix/dist-install/build:libraries/time/dist-install/build:libraries/filepath/dist-install/build:libraries/binary/dist-install/build:libraries/containers/containers/dist-install/build:libraries/bytestring/dist-install/build:libraries/deepseq/dist-install/build:libraries/array/dist-install/build:libraries/base/dist-install/build:libraries/ghc-bignum/dist-install/build:libraries/ghc-prim/dist-install/build:rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
647− >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
648− <br>chmod +x inplace/bin/hpc
649− <br>echo 'exec "utils/ghc-cabal/dist-install/build/tmp/ghc-cabal" ${1+"$@"}' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
650− <br>chmod +x utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
651− <br>
652− <<ghc: 2787765536 bytes, 171 GCs, 20282449/67935792 avg/max bytes residency (8 samples), 144M in use, 0.000 INIT (0.003 elapsed), 1.738 MUT (2.286 elapsed), 0.567 GC (0.611 elapsed) :ghc>>
653− <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages -i
654− -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build
655− -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0 -package-id
656− binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619 -package-id
657− ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id time-1.9.3
658− -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build
659− -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/HsToCore.hs -o compiler/stage2/build/GHC/HsToCore.o
660− -dyno compiler/stage2/build/GHC/HsToCore.dyn_o
661− <br>
662− <<ghc: 766891168 bytes, 91 GCs, 14722329/42245856 avg/max bytes residency (6 samples), 91M in use, 0.000 INIT (0.003 elapsed), 0.361 MUT (0.510 elapsed), 0.236 GC (0.258 elapsed) :ghc>>
663− <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages
664− -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build
665− -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0
666− -package-id binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619
667− -package-id ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id
668− time-1.9.3 -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build
669− -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/Iface/Ext/Ast.hs -o compiler/stage2/build/GHC/Iface/Ext/Ast.o
670− -dyno compiler/stage2/build/GHC/Iface/Ext/Ast.dyn_o
671− <br>
672− <<ghc: 3067019736 bytes, 347 GCs, 25353490/71392248 avg/max bytes residency (8 samples), 184M in use, 0.000 INIT (0.002 elapsed), 1.416 MUT (1.823 elapsed), 0.554 GC (0.595 elapsed) :ghc>>
673− <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages
674− -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompil...
675− </div>
676− <!-- temp -->
677− или:
678− <!--/temp -->
679− <div class="no-log">
680− <div class="null null--data">
681− <div class="null__content">
682− <b class="null__heading">Cannot retrieve the data</b>
683− <div class="null__message">Try to reload page</div>
1089+ <h2 class="popup__project">
1090+ Project name, logs
1091+ </h2>
1092+ </div>
1093+ <div class="popup__sub-head">
1094+ <div class="nav">
1095+ <button class="nav__link nav__link--current" type="button">stdout</button>
1096+ <button class="nav__link" type="button">stderr</button>
1097+ </div>
1098+ </div>
1099+ <div class="popup__content">
1100+ <div class="log">
1101+ DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/libiserv/dist-install/build:/Users/dr/workspace/ghc/libraries/ghci/dist-install/build:/Users/dr/workspace/ghc/libraries/transformers/dist-install/build:/Users/dr/workspace/ghc/libraries/template-haskell/dist-install/build:/Users/dr/workspace/ghc/libraries/pretty/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-heap/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-boot-th/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/binary/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
1102+ >> inplace/lib/bin/ghc-iserv-dyn
1103+ <br>"rm" -f inplace/bin/runghc
1104+ <br>echo 'exec "$executablename" ${1+"$@"}' >> inplace/lib/bin/ghc-iserv-dyn
1105+ <br>echo '#!/bin/sh' >> inplace/bin/runghc
1106+ <br>chmod +x inplace/lib/bin/ghc-iserv-dyn
1107+ <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/runghc"' >> inplace/bin/runghc
1108+ <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
1109+ <br>/usr/local/bin/ginstall -c -m 755 utils/hpc/dist-install/build/tmp/hpc inplace/lib/bin/hpc
1110+ <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/runghc
1111+ <br>"rm" -f inplace/bin/hpc
1112+ <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/runghc
1113+ <br>echo 'pgmgcc="cc"' >> inplace/bin/runghc
1114+ <br>echo '#!/bin/sh' >> inplace/bin/hpc
1115+ <br>echo 'ghcprog="ghc-stage2"' >> "inplace/bin/runghc"
1116+ <br>echo 'executablename="/Users/dr/workspace/ghc/inplace/lib/bin/hpc"' >> inplace/bin/hpc
1117+ <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/process/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
1118+ >> inplace/bin/runghc
1119+ <br>echo 'datadir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
1120+ <br>cat utils/runghc/runghc.wrapper >> inplace/bin/runghc
1121+ <br>echo 'bindir="/Users/dr/workspace/ghc/inplace/bin"' >> inplace/bin/hpc
1122+ <br>echo 'topdir="/Users/dr/workspace/ghc/inplace/lib"' >> inplace/bin/hpc
1123+ <br>chmod +x inplace/bin/runghc
1124+ <br>echo 'pgmgcc="cc"' >> inplace/bin/hpc
1125+ <br>"rm" -f utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
1126+ <br>echo 'export DYLD_LIBRARY_PATH="/Users/dr/workspace/ghc/libraries/hpc/dist-install/build:/Users/dr/workspace/ghc/libraries/directory/dist-install/build:/Users/dr/workspace/ghc/libraries/unix/dist-install/build:/Users/dr/workspace/ghc/libraries/time/dist-install/build:/Users/dr/workspace/ghc/libraries/filepath/dist-install/build:/Users/dr/workspace/ghc/libraries/containers/containers/dist-install/build:/Users/dr/workspace/ghc/libraries/bytestring/dist-install/build:/Users/dr/workspace/ghc/libraries/deepseq/dist-install/build:/Users/dr/workspace/ghc/libraries/array/dist-install/build:/Users/dr/workspace/ghc/libraries/base/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-bignum/dist-install/build:/Users/dr/workspace/ghc/libraries/ghc-prim/dist-install/build:/Users/dr/workspace/ghc/rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
1127+ >> inplace/bin/hpc
1128+ <br>echo '#!/bin/sh' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
1129+ <br>cat utils/hpc/hpc.wrapper >> inplace/bin/hpc
1130+ <br>echo 'export DYLD_LIBRARY_PATH="libraries/Cabal/Cabal/dist-install/build:libraries/process/dist-install/build:libraries/parsec/dist-install/build:libraries/text/dist-install/build:libraries/template-haskell/dist-install/build:libraries/pretty/dist-install/build:libraries/ghc-boot-th/dist-install/build:libraries/mtl/dist-install/build:libraries/transformers/dist-install/build:libraries/directory/dist-install/build:libraries/unix/dist-install/build:libraries/time/dist-install/build:libraries/filepath/dist-install/build:libraries/binary/dist-install/build:libraries/containers/containers/dist-install/build:libraries/bytestring/dist-install/build:libraries/deepseq/dist-install/build:libraries/array/dist-install/build:libraries/base/dist-install/build:libraries/ghc-bignum/dist-install/build:libraries/ghc-prim/dist-install/build:rts/dist/build${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"'
1131+ >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
1132+ <br>chmod +x inplace/bin/hpc
1133+ <br>echo 'exec "utils/ghc-cabal/dist-install/build/tmp/ghc-cabal" ${1+"$@"}' >> utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
1134+ <br>chmod +x utils/ghc-cabal/dist-install/build/tmp/ghc-cabal-bindist
1135+ <br>
1136+ <
1137+ <ghc: 2787765536 bytes, 171 GCs, 20282449
1138+ /67935792 avg/max bytes residency (8 samples), 144M in use, 0.000 INIT (0.003 elapsed), 1.738 MUT (2.286 elapsed), 0.567 GC (0.611 elapsed) :ghc>>
1139+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages -i
1140+ -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build
1141+ -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0 -package-id
1142+ binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619 -package-id
1143+ ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id time-1.9.3
1144+ -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build
1145+ -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/HsToCore.hs -o compiler/stage2/build/GHC/HsToCore.o
1146+ -dyno compiler/stage2/build/GHC/HsToCore.dyn_o
1147+ <br>
1148+ <
1149+ <ghc: 766891168 bytes, 91 GCs, 14722329
1150+ /42245856 avg/max bytes residency (6 samples), 91M in use, 0.000 INIT (0.003 elapsed), 0.361 MUT (0.510 elapsed), 0.236 GC (0.258 elapsed) :ghc>>
1151+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages
1152+ -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompiler/stage2/build/../rts/dist/build
1153+ -Icompiler/stage2/build/stage2 -optP-DHAVE_INTERNAL_INTERPRETER -optP-DCAN_LOAD_DLL -optP-Iincludes/dist-install/build -optP-include -optPcompiler/stage2/build/./autogen/cabal_macros.h -package-id array-0.5.4.0 -package-id base-4.15.0.0
1154+ -package-id binary-0.8.7.0 -package-id bytestring-0.10.10.0 -package-id containers-0.6.2.1 -package-id deepseq-1.4.4.0 -package-id directory-1.3.6.1 -package-id exceptions-0.10.4 -package-id filepath-1.4.2.1 -package-id ghc-boot-8.11.0.20200619
1155+ -package-id ghc-boot-th-8.11.0.20200619 -package-id ghc-heap-8.11.0.20200619 -package-id ghci-8.11.0.20200619 -package-id hpc-0.6.1.0 -package-id process-1.6.8.2 -package-id template-haskell-2.17.0.0 -package-id terminfo-0.4.1.4 -package-id
1156+ time-1.9.3 -package-id transformers-0.5.6.2 -package-id unix-2.7.2.2 -Wall -Wno-name-shadowing -Wnoncanonical-monad-instances -Wnoncanonical-monoid-instances -this-unit-id ghc -XHaskell2010 -XNoImplicitPrelude -optc-DTHREADED_RTS -Iincludes/dist-install/build
1157+ -Rghc-timing -O0 -Wcpp-undef -Wincomplete-uni-patterns -Wincomplete-record-updates -no-user-package-db -rtsopts -Wnoncanonical-monad-instances -outputdir compiler/stage2/build -dynamic-too -c compiler/./GHC/Iface/Ext/Ast.hs -o compiler/stage2/build/GHC/Iface/Ext/Ast.o
1158+ -dyno compiler/stage2/build/GHC/Iface/Ext/Ast.dyn_o
1159+ <br>
1160+ <
1161+ <ghc: 3067019736 bytes, 347 GCs, 25353490
1162+ /71392248 avg/max bytes residency (8 samples), 184M in use, 0.000 INIT (0.002 elapsed), 1.416 MUT (1.823 elapsed), 0.554 GC (0.595 elapsed) :ghc>>
1163+ <br>"inplace/bin/ghc-stage1" -hisuf hi -osuf o -hcsuf hc -static -O0 -H64m -Wall -Iincludes -Iincludes/dist -Iincludes/dist-derivedconstants/header -Iincludes/dist-ghcconstants/header -this-unit-id ghc-8.11.0.20200619 -hide-all-packages
1164+ -i -icompiler/. -icompiler/stage2/build -Icompiler/stage2/build -icompiler/stage2/build/./autogen -Icompiler/stage2/build/./autogen -Icompiler/. -Icompiler/../rts/dist/build -Icompiler/stage2 -Icompiler/stage2/build/. -Icompil...
1165+ </div>
1166+ <!-- temp -->
1167+ или:
1168+ <!--/temp -->
1169+ <div class="no-log">
1170+ <div class="null null--data">
1171+ <div class="null__content">
1172+ <b class="null__heading">Cannot retrieve the data</b>
1173+ <div class="null__message">Try to reload page</div>
1174+ </div>
1175+ </div>
1176+ <!-- temp -->
1177+ &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
1178+ <!--/temp -->
1179+ <div class="loading loading--enlarged loading--alternate">
1180+ Loading...
1181+ </div>
1182+ </div>
1183+ </div>
6841184 </div>
685− </div>
686− <!-- temp -->
687− &nbsp;&nbsp;&nbsp;&nbsp;или&nbsp;&nbsp;&nbsp;&nbsp;
688− <!--/temp -->
689− <div class="loading loading--enlarged loading--alternate">
690− Loading...
691− </div>
6921185 </div>
693− </div>
694− </div>
695− </div>
6961186
697− <div class="classic-popup" id="dialog">
698− <div class="classic-popup__container">
699− <div class="classic-popup__viewport">
700− <div class="classic-popup__slot">
1187+ <div class="classic-popup" id="dialog">
1188+ <div class="classic-popup__container">
1189+ <div class="classic-popup__viewport">
1190+ <div class="classic-popup__slot">
7011191
702− <div class="dialog dialog--archive">
703− <div class="dialog__content">
704− Are you sure you want to archive the
705− <br>‘Feature name’ deployment?
706− </div>
707− <div class="dialog__footer">
708− <button class="dialog__action button" type="submit">Archive</button>
709− <button class="dialog__action button--secondary button" type="button">Cancel</button>
710− </div>
711− </div>
1192+ <div class="dialog dialog--archive">
1193+ <div class="dialog__content">
1194+ Are you sure you want to archive the
1195+ <br>‘Feature name’ deployment?
1196+ </div>
1197+ <div class="dialog__footer">
1198+ <button class="dialog__action button" type="submit">Archive</button>
1199+ <button class="dialog__action button--secondary button" type="button">Cancel</button>
1200+ </div>
1201+ </div>
7121202
713− <button class="classic-popup__close" type="button">
1203+ <button class="classic-popup__close" type="button">
7141204 Close
7151205 </button>
1206+ </div>
1207+ </div>
1208+ </div>
7161209 </div>
717− </div>
718− </div>
719− </div>
7201210
7211211
722− <!-- jQuery -->
723− <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
1212+ <!-- jQuery -->
1213+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
7241214
725− <!-- Outline.js -->
726− <script src="../vendors/outline/outline.js"></script>
1215+ <!-- Outline.js -->
1216+ <script src="../vendors/outline/outline.js"></script>
7271217
728− <!-- Temp -->
729− <script src="../scripts/_expander.js"></script>
730− <script src="../scripts/_drop.js"></script>
731− <script src="../scripts/_sort.js"></script>
732− <script src="../scripts/_data.js"></script>
733− <script src="../scripts/_popup.js"></script>
734− <script src="../scripts/_classic-popup.js"></script>
1218+ <!-- Temp -->
1219+ <script src="../scripts/_expander.js"></script>
1220+ <script src="../scripts/_drop.js"></script>
1221+ <script src="../scripts/_sort.js"></script>
1222+ <script src="../scripts/_data.js"></script>
1223+ <script src="../scripts/_popup.js"></script>
1224+ <script src="../scripts/_classic-popup.js"></script>
1225+ <script src="../scripts/_collapse.js"></script>
7351226
736−</body>
1227+ </body>
7371228
738−</html>
1229+</html>
octopod-css/development/markups/_action.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.action</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -104,6 +104,7 @@
104104 <script src="../scripts/_data.js"></script>
105105 <script src="../scripts/_popup.js"></script>
106106 <script src="../scripts/_classic-popup.js"></script>
107+ <script src="../scripts/_collapse.js"></script>
107108
108109 </body>
109110</html>
octopod-css/development/markups/_bar.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.bar</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -53,6 +53,7 @@
5353 <script src="../scripts/_data.js"></script>
5454 <script src="../scripts/_popup.js"></script>
5555 <script src="../scripts/_classic-popup.js"></script>
56+ <script src="../scripts/_collapse.js"></script>
5657
5758 </body>
5859</html>
octopod-css/development/markups/_break.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.break</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111
@@ -76,6 +76,7 @@
7676 <script src="../scripts/_data.js"></script>
7777 <script src="../scripts/_popup.js"></script>
7878 <script src="../scripts/_classic-popup.js"></script>
79+ <script src="../scripts/_collapse.js"></script>
7980
8081
8182 </body>
octopod-css/development/markups/_button.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.button</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111
@@ -128,6 +128,7 @@
128128 <script src="../scripts/_data.js"></script>
129129 <script src="../scripts/_popup.js"></script>
130130 <script src="../scripts/_classic-popup.js"></script>
131+ <script src="../scripts/_collapse.js"></script>
131132
132133
133134 </body>
octopod-css/development/markups/_collapse.htmlnew file+277
@@ -0,0 +1,277 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>.collapse</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 600px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>.collapse</h1>
21+
22+ <p>
23+ Это функциональный блок. Кликаем в <b>.collapse__head</b>
24+ -- раскрывается <b>.collapse__body</b>.
25+ </p>
26+ <p>
27+ Дефолтная версия выглядит никак:
28+ </p>
29+ <br>
30+ <br>
31+
32+
33+ <div class="collapse">
34+ <button class="collapse__head" type="button">
35+ Шапка
36+ </button>
37+ <div class="collapse__body">
38+ Тело
39+ </div>
40+ </div>
41+
42+ <br>
43+ <br>
44+ <br>
45+ <p>
46+ Оформление добавляем модификаторами-скинами (см. ниже).
47+ </p>
48+
49+ <p>
50+ Раскрываем <b>.collapse__body</b> вручную и так же добавляем <b>.collapse--expanded</b>.
51+ </p>
52+
53+
54+
55+
56+
57+ <br>
58+ <br>
59+ <br>
60+ <br>
61+ <h2>.collapse--project</h2>
62+ <p>
63+ Внутри обычно используется <b>.row</b>
64+ </p>
65+
66+ <div class="collapse collapse--project">
67+ <button class="collapse__head" type="button">
68+ travolutionary.mapping[1]
69+ </button>
70+ <div class="collapse__body">
71+ <div class="collapse__inner">
72+ <div class="row">
73+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
74+ </div>
75+ <div class="row">
76+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
77+ </div>
78+ </div>
79+ </div>
80+ </div>
81+
82+
83+ <p>
84+ Можно вкладывать любое количество <b>.collapse</b> и <b>.row</b> друг в друга,
85+ в любом порядке, в любых комбинациях. Ещё можно добавлять<br> класс <b>.collapse__head--has-changes</b>
86+ </p>
87+
88+ <div class="collapse collapse--project">
89+ <button class="collapse__head collapse__head--has-changes" type="button">
90+ air
91+ </button>
92+ <div class="collapse__body">
93+ <div class="collapse__inner">
94+ <div class="row">
95+ <span class="key-default-pristine">vaultEngineName: </span><span class="value-pristine">staging</span>
96+ </div>
97+ <div class="collapse collapse--project">
98+ <button class="collapse__head collapse__head--has-changes" type="button">
99+ vaultEnv
100+ </button>
101+ <div class="collapse__body">
102+ <div class="collapse__inner">
103+ <div class="row">
104+ <span class="key-default-pristine">air-sso.engineOverride: </span><span class="value-pristine">staging</span>
105+ </div>
106+ <div class="collapse collapse--project">
107+ <button class="collapse__head" type="button">
108+ air-sso.mapping[0]
109+ </button>
110+ <div class="collapse__body">
111+ <div class="collapse__inner">
112+ <div class="row">
113+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
114+ </div>
115+ <div class="row">
116+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
117+ </div>
118+ </div>
119+ <div class="row">
120+ <span class="key-default-edited">air-sso.mapping[1].name: </span><span class="value-edited">SSO_CLIENT_SECRET</span>
121+ </div>
122+ </div>
123+ </div>
124+ <div class="collapse collapse--project">
125+ <button class="collapse__head collapse__head--has-changes" type="button">
126+ travolutionary.mapping[0]
127+ </button>
128+ <div class="collapse__body">
129+ <div class="collapse__inner">
130+ <div class="row">
131+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
132+ </div>
133+ <div class="row">
134+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
135+ </div>
136+ </div>
137+ </div>
138+ </div>
139+ <div class="collapse collapse--project">
140+ <button class="collapse__head collapse__head--has-changes" type="button">
141+ travolutionary.mapping[1]
142+ </button>
143+ <div class="collapse__body">
144+ <div class="collapse__inner">
145+ <div class="row">
146+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
147+ </div>
148+ <div class="row">
149+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
150+ </div>
151+ </div>
152+ </div>
153+ </div>
154+ <div class="row">
155+ <span class="key-default-edited">air-sso.mapping[1].name: </span><span class="value-edited">SSO_CLIENT_SECRET</span>
156+ </div>
157+ </div>
158+ </div>
159+ </div>
160+ </div>
161+ <div class="row">
162+ <span class="key-custom-edited">app_api_url: </span><span class="value-edited">http: //my-api.my-domain.com</span>
163+ </div>
164+ </div>
165+ </div>
166+ <div class="collapse collapse--project">
167+ <button class="collapse__head" type="button">
168+ sso
169+ </button>
170+ <div class="collapse__body">
171+ <div class="collapse__inner">
172+ <div class="row">
173+ <span class="key-default-pristine">vaultEngineName: </span><span class="value-pristine">staging</span>
174+ </div>
175+ <div class="collapse collapse--project">
176+ <button class="collapse__head" type="button">
177+ vaultEnv
178+ </button>
179+ <div class="collapse__body">
180+ <div class="collapse__inner">
181+ <div class="row">
182+ <span class="key-default-pristine">air-sso.engineOverride: </span><span class="value-pristine">staging</span>
183+ </div>
184+ <div class="collapse collapse--project">
185+ <button class="collapse__head" type="button">
186+ air-sso.mapping[0]
187+ </button>
188+ <div class="collapse__body">
189+ <div class="collapse__inner">
190+ <div class="row">
191+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
192+ </div>
193+ <div class="row">
194+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
195+ </div>
196+ </div>
197+ <div class="row">
198+ <span class="key-default-edited">air-sso.mapping[1].name: </span><span class="value-edited">SSO_CLIENT_SECRET</span>
199+ </div>
200+ </div>
201+ </div>
202+ <div class="collapse collapse--project">
203+ <button class="collapse__head" type="button">
204+ travolutionary.mapping[0]
205+ </button>
206+ <div class="collapse__body">
207+ <div class="collapse__inner">
208+ <div class="row">
209+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
210+ </div>
211+ <div class="row">
212+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
213+ </div>
214+ </div>
215+ </div>
216+ </div>
217+ <div class="collapse collapse--project">
218+ <button class="collapse__head" type="button">
219+ travolutionary.mapping[1]
220+ </button>
221+ <div class="collapse__body">
222+ <div class="collapse__inner">
223+ <div class="row">
224+ <span class="key-default-edited">name: </span><span class="value-edited">SSO_CLIENT_ID</span>
225+ </div>
226+ <div class="row">
227+ <span class="key-default-pristine">key: </span><span class="value-pristine">clientId</span>
228+ </div>
229+ </div>
230+ </div>
231+ </div>
232+ <div class="row">
233+ <span class="key-default-edited">air-sso.mapping[1].name: </span><span class="value-edited">SSO_CLIENT_SECRET</span>
234+ </div>
235+ </div>
236+ </div>
237+ </div>
238+ </div>
239+ <div class="row">
240+ <span class="key-custom-edited">app_api_url: </span><span class="value-edited">http: //my-api.my-domain.com</span>
241+ </div>
242+ </div>
243+ </div>
244+
245+
246+ <br>
247+ <br>
248+ <br>
249+ <br>
250+ <br>
251+ <br>
252+ <br>
253+ <br>
254+ <br>
255+ <br>
256+ <br>
257+ </div>
258+
259+
260+
261+ <!-- jQuery -->
262+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
263+
264+ <!-- Outline.js -->
265+ <script src="../vendors/outline/outline.js"></script>
266+
267+ <!-- Temp -->
268+ <script src="../scripts/_expander.js"></script>
269+ <script src="../scripts/_drop.js"></script>
270+ <script src="../scripts/_sort.js"></script>
271+ <script src="../scripts/_data.js"></script>
272+ <script src="../scripts/_popup.js"></script>
273+ <script src="../scripts/_classic-popup.js"></script>
274+ <script src="../scripts/_collapse.js"></script>
275+
276+ </body>
277+</html>
octopod-css/development/markups/_dash.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.dash</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111
@@ -121,6 +121,7 @@
121121 <script src="../scripts/_data.js"></script>
122122 <script src="../scripts/_popup.js"></script>
123123 <script src="../scripts/_classic-popup.js"></script>
124+ <script src="../scripts/_collapse.js"></script>
124125
125126
126127 </body>
octopod-css/development/markups/_drop.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.drop</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -104,6 +104,7 @@
104104 <script src="../scripts/_data.js"></script>
105105 <script src="../scripts/_popup.js"></script>
106106 <script src="../scripts/_classic-popup.js"></script>
107+ <script src="../scripts/_collapse.js"></script>
107108
108109 </body>
109110</html>
octopod-css/development/markups/_expander.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.expander</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -88,6 +88,7 @@
8888 <script src="../scripts/_data.js"></script>
8989 <script src="../scripts/_popup.js"></script>
9090 <script src="../scripts/_classic-popup.js"></script>
91+ <script src="../scripts/_collapse.js"></script>
9192
9293 </body>
9394</html>
octopod-css/development/markups/_external.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.external</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -53,6 +53,7 @@
5353 <script src="../scripts/_data.js"></script>
5454 <script src="../scripts/_popup.js"></script>
5555 <script src="../scripts/_classic-popup.js"></script>
56+ <script src="../scripts/_collapse.js"></script>
5657
5758 </body>
5859</html>
octopod-css/development/markups/_input.html+36−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.input</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111
@@ -72,6 +72,40 @@
7272 </div>
7373
7474
75+ <br>
76+ <br>
77+ <br>
78+ <h2>Выпадайка</h2>
79+
80+ <div class="input">
81+ <input class="input__widget" type="text" placeholder="key" value="LiveSearch">
82+ <ul class="input__dropdown">
83+ <li class="input__suggest"><b>S</b>ECRET_PASSWORD</li>
84+ <li class="input__suggest"><b>S</b>ECRET_INFO</li>
85+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
86+ </li>
87+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
88+ </li>
89+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
90+ </li>
91+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
92+ </li>
93+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
94+ </li>
95+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
96+ </li>
97+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
98+ </li>
99+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
100+ </li>
101+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
102+ </li>
103+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
104+ </li>
105+ <li class="input__suggest"><b>S</b>oviet Socialist Republics
106+ </li>
107+ </ul>
108+ </div>
75109
76110 <br>
77111 <br>
@@ -102,6 +136,7 @@
102136 <script src="../scripts/_data.js"></script>
103137 <script src="../scripts/_popup.js"></script>
104138 <script src="../scripts/_classic-popup.js"></script>
139+ <script src="../scripts/_collapse.js"></script>
105140
106141
107142 </body>
octopod-css/development/markups/_keys-and-values.htmlnew file+373
@@ -0,0 +1,373 @@
1+<!DOCTYPE html>
2+<html lang="en">
3+ <head>
4+ <meta charset="utf-8">
5+ <meta http-equiv="x-ua-compatible" content="ie=edge">
6+ <title>Keys and values</title>
7+ <meta name="viewport" content="width=device-width, initial-scale=1">
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
9+ <link rel="stylesheet" href="../styles/style.css">
10+ </head>
11+ <body>
12+
13+
14+
15+ <div style="width: 800px; margin: 0 auto;">
16+ <br>
17+ <br>
18+ <br>
19+ <br>
20+ <h1>Keys and values</h1>
21+
22+
23+ <span class="key-default-pristine">
24+ air.vaultEngineName:
25+ </span>
26+
27+
28+ <br>
29+ <br>
30+ <br>
31+ <h2>Классы</h2>
32+
33+ <div class="table">
34+ <table>
35+ <tbody>
36+ <tr>
37+ <td>
38+ Ключ по умолчанию, значение не изменено:
39+ </td>
40+ <td>
41+ <span class="key-default-pristine">key-default-pristine</span>
42+ </td>
43+ <td>
44+ <span class="value-pristine">value-pristine</span>
45+ </td>
46+ </tr>
47+ <tr>
48+ <td>
49+ Ключ по умолчанию, значение изменено:
50+ </td>
51+ <td>
52+ <span class="key-default-edited">key-default-edited</span>
53+ </td>
54+ <td>
55+ <span class="value-edited">value-edited</span>
56+ </td>
57+ </tr>
58+ <tr>
59+ <td>
60+ Ключ кастомный, значение изменено:
61+ </td>
62+ <td>
63+ <span class="key-custom-edited">key-custom-edited</span>
64+ </td>
65+ <td>
66+ <span class="value-edited">value-edited</span>
67+ </td>
68+ </tr>
69+ <tr>
70+ <td>
71+ Ключ и значение удалены:
72+ </td>
73+ <td>
74+ <span class="key-deleted">key-deleted</span>
75+ </td>
76+ <td>
77+ <span class="value-deleted">value-deleted</span>
78+ </td>
79+ </tr>
80+ <tr>
81+ <td>
82+ Ключ удалён, значение неизвестно:
83+ </td>
84+ <td>
85+ <span class="key-deleted">key-deleted</span>
86+ </td>
87+ <td>
88+ <span class="value-unknown">unknown</span> (value-unknown)
89+ </td>
90+ </tr>
91+ </tbody>
92+ </table>
93+ </div>
94+
95+
96+ <br>
97+ <br>
98+ <h2>Примеры</h2>
99+
100+ <div class="table">
101+ <table>
102+ <tbody>
103+ <tr>
104+ <td>
105+ Ключ по умолчанию, значение не изменено:
106+ </td>
107+ <td>
108+ <span class="key-default-pristine">air.vaultEngineName:</span>
109+ </td>
110+ <td>
111+ <span class="value-pristine">staging</span>
112+ </td>
113+ </tr>
114+ <tr>
115+ <td>
116+ Ключ по умолчанию, значение изменено:
117+ </td>
118+ <td>
119+ <span class="key-default-edited">air.vaultEngineName:</span>
120+ </td>
121+ <td>
122+ <span class="value-edited">staging</span>
123+ </td>
124+ </tr>
125+ <tr>
126+ <td>
127+ Ключ кастомный, значение изменено:
128+ </td>
129+ <td>
130+ <span class="key-custom-edited">app_api_url:</span>
131+ </td>
132+ <td>
133+ <span class="value-edited">http://my-api.my-domain.com</span>
134+ </td>
135+ </tr>
136+ <tr>
137+ <td>
138+ Ключ и значение удалены:
139+ </td>
140+ <td>
141+ <span class="key-deleted">sso.vaultEnv.hotels-sso.mapping[0].APP_URL</span>
142+ </td>
143+ <td>
144+ <span class="value-deleted">http://my-api.my-domain.com</span>
145+ </td>
146+ </tr>
147+ <tr>
148+ <td>
149+ Ключ удалён, значение неизвестно:
150+ </td>
151+ <td>
152+ <span class="key-deleted">air.vaultEnv.air-sso:</span>
153+ </td>
154+ <td>
155+ <span class="value-unknown">unknown</span>
156+ </td>
157+ </tr>
158+ </tbody>
159+ </table>
160+ </div>
161+
162+ <br>
163+ <br>
164+ <p>
165+ Для экономии узлов в DOM-дереве можно не создавать отдельную обёртку &lt;span&gt;...&lt;/span&gt;
166+ а вешать эти классы прям на ячейки таблицы, или на любого другого родителя.
167+ Работает со всеми классами кроме <b>.value-unknown</b> -- это отдельный компонент.
168+ </p>
169+
170+ <div class="table">
171+ <table>
172+ <tbody>
173+ <tr>
174+ <td>
175+ Ключ по умолчанию, значение не изменено:
176+ </td>
177+ <td class="key-default-pristine">
178+ air.vaultEngineName:
179+ </td>
180+ <td class="value-pristine">
181+ staging
182+ </td>
183+ </tr>
184+ <tr>
185+ <td>
186+ Ключ по умолчанию, значение изменено:
187+ </td>
188+ <td class="key-default-edited">
189+ air.vaultEngineName:
190+ </td>
191+ <td class="value-edited">
192+ staging
193+ </td>
194+ </tr>
195+ <tr>
196+ <td>
197+ Ключ кастомный, значение изменено:
198+ </td>
199+ <td class="key-custom-edited">
200+ app_api_url:
201+ </td>
202+ <td class="value-edited">
203+ http://my-api.my-domain.com
204+ </td>
205+ </tr>
206+ <tr>
207+ <td>
208+ Ключ и значение удалены:
209+ </td>
210+ <td class="key-deleted">
211+ sso.vaultEnv.hotels-sso.mapping[0].APP_URL
212+ </td>
213+ <td class="value-deleted">
214+ http://my-api.my-domain.com
215+ </td>
216+ </tr>
217+ <tr>
218+ <td>
219+ Ключ удалён, значение неизвестно:
220+ </td>
221+ <td class="key-deleted">
222+ air.vaultEnv.air-sso:
223+ </td>
224+ <td>
225+ <span class="value-unknown">unknown</span>
226+ </td>
227+ </tr>
228+ </tbody>
229+ </table>
230+ </div>
231+
232+
233+ <br>
234+ <br>
235+ <h2>
236+ Запись в строку
237+ </h2>
238+ <p>
239+ Прост пишем одно за другим, разделяем пробелом:
240+ </p>
241+
242+ <div class="table">
243+ <table>
244+ <tbody>
245+ <tr>
246+ <td>
247+ Ключ по умолчанию, значение не изменено:
248+ </td>
249+ <td>
250+ <span class="key-default-pristine">air.vaultEngineName:</span> <span class="value-pristine">staging</span>
251+ </td>
252+ </tr>
253+ <tr>
254+ <td>
255+ Ключ по умолчанию, значение изменено:
256+ </td>
257+ <td>
258+ <span class="key-default-edited">air.vaultEngineName:</span> <span class="value-edited">staging</span>
259+ </td>
260+ </tr>
261+ <tr>
262+ <td>
263+ Ключ кастомный, значение изменено:
264+ </td>
265+ <td>
266+ <span class="key-custom-edited">app_api_url:</span> <span class="value-edited">http://my-api.my-domain.com</span>
267+ </td>
268+ </tr>
269+ <tr>
270+ <td>
271+ Ключ и значение удалены:
272+ </td>
273+ <td>
274+ <span class="key-deleted">sso.vaultEnv.hotels-sso.mapping[0].APP_URL</span> <span class="value-deleted">http://my-api.my-domain.com</span>
275+ </td>
276+ </tr>
277+ <tr>
278+ <td>
279+ Ключ удалён, значение неизвестно:
280+ </td>
281+ <td>
282+ <span class="key-deleted">air.vaultEnv.air-sso:</span> <span class="value-unknown">unknown</span>
283+ </td>
284+ </tr>
285+ </tbody>
286+ </table>
287+ </div>
288+
289+ <br>
290+ <br>
291+ <p>
292+ Пробел можно положить не между тегами, а внутрь ключа, и тогда подчёркивания сомкнуться
293+ </p>
294+
295+ <div class="table">
296+ <table>
297+ <tbody>
298+ <tr>
299+ <td>
300+ Ключ по умолчанию, значение не изменено:
301+ </td>
302+ <td>
303+ <span class="key-default-pristine">air.vaultEngineName: </span><span class="value-pristine">staging</span>
304+ </td>
305+ </tr>
306+ <tr>
307+ <td>
308+ Ключ по умолчанию, значение изменено:
309+ </td>
310+ <td>
311+ <span class="key-default-edited">air.vaultEngineName: </span><span class="value-edited">staging</span>
312+ </td>
313+ </tr>
314+ <tr>
315+ <td>
316+ Ключ кастомный, значение изменено:
317+ </td>
318+ <td>
319+ <span class="key-custom-edited">app_api_url: </span><span class="value-edited">http://my-api.my-domain.com</span>
320+ </td>
321+ </tr>
322+ <tr>
323+ <td>
324+ Ключ и значение удалены:
325+ </td>
326+ <td>
327+ <span class="key-deleted">sso.vaultEnv.hotels-sso.mapping[0].APP_URL </span><span class="value-deleted">http://my-api.my-domain.com</span>
328+ </td>
329+ </tr>
330+ <tr>
331+ <td>
332+ Ключ удалён, значение неизвестно:
333+ </td>
334+ <td>
335+ <span class="key-deleted">air.vaultEnv.air-sso: </span><span class="value-unknown">unknown</span>
336+ </td>
337+ </tr>
338+ </tbody>
339+ </table>
340+ </div>
341+
342+ <br>
343+ <br>
344+ <br>
345+ <br>
346+ <br>
347+ <br>
348+ <br>
349+ <br>
350+ <br>
351+ <br>
352+ <br>
353+ </div>
354+
355+
356+
357+ <!-- jQuery -->
358+ <script src="../vendors/jquery/jquery-3.2.1.min.js"></script>
359+
360+ <!-- Outline.js -->
361+ <script src="../vendors/outline/outline.js"></script>
362+
363+ <!-- Temp -->
364+ <script src="../scripts/_expander.js"></script>
365+ <script src="../scripts/_drop.js"></script>
366+ <script src="../scripts/_sort.js"></script>
367+ <script src="../scripts/_data.js"></script>
368+ <script src="../scripts/_popup.js"></script>
369+ <script src="../scripts/_classic-popup.js"></script>
370+ <script src="../scripts/_collapse.js"></script>
371+
372+ </body>
373+</html>
octopod-css/development/markups/_listing.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.listing</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -92,6 +92,7 @@
9292 <script src="../scripts/_data.js"></script>
9393 <script src="../scripts/_popup.js"></script>
9494 <script src="../scripts/_classic-popup.js"></script>
95+ <script src="../scripts/_collapse.js"></script>
9596
9697 </body>
9798</html>
octopod-css/development/markups/_loading.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.loading</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -128,6 +128,7 @@
128128 <script src="../scripts/_data.js"></script>
129129 <script src="../scripts/_popup.js"></script>
130130 <script src="../scripts/_classic-popup.js"></script>
131+ <script src="../scripts/_collapse.js"></script>
131132
132133 </body>
133134</html>
octopod-css/development/markups/_notification.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.notification</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -80,6 +80,7 @@
8080 <script src="../scripts/_data.js"></script>
8181 <script src="../scripts/_popup.js"></script>
8282 <script src="../scripts/_classic-popup.js"></script>
83+ <script src="../scripts/_collapse.js"></script>
8384
8485 </body>
8586</html>
octopod-css/development/markups/_null.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.null</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -73,6 +73,7 @@
7373 <script src="../scripts/_data.js"></script>
7474 <script src="../scripts/_popup.js"></script>
7575 <script src="../scripts/_classic-popup.js"></script>
76+ <script src="../scripts/_collapse.js"></script>
7677
7778 </body>
7879</html>
octopod-css/development/markups/_spot.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.spot</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -53,6 +53,7 @@
5353 <script src="../scripts/_data.js"></script>
5454 <script src="../scripts/_popup.js"></script>
5555 <script src="../scripts/_classic-popup.js"></script>
56+ <script src="../scripts/_collapse.js"></script>
5657
5758 </body>
5859</html>
octopod-css/development/markups/_status.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.status</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -75,6 +75,7 @@
7575 <script src="../scripts/_data.js"></script>
7676 <script src="../scripts/_popup.js"></script>
7777 <script src="../scripts/_classic-popup.js"></script>
78+ <script src="../scripts/_collapse.js"></script>
7879
7980 </body>
8081</html>
octopod-css/development/markups/_title.html+2−1
@@ -5,7 +5,7 @@
55 <meta http-equiv="x-ua-compatible" content="ie=edge">
66 <title>.title</title>
77 <meta name="viewport" content="width=device-width, initial-scale=1">
8− <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">-->
8+ <!--<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">-->
99 <link rel="stylesheet" href="../styles/style.css">
1010 </head>
1111 <body>
@@ -52,6 +52,7 @@
5252 <script src="../scripts/_data.js"></script>
5353 <script src="../scripts/_popup.js"></script>
5454 <script src="../scripts/_classic-popup.js"></script>
55+ <script src="../scripts/_collapse.js"></script>
5556
5657 </body>
5758</html>
octopod-css/development/scripts/_collapse.jsnew file+18
@@ -0,0 +1,18 @@
1+(function($) {
2+
3+ var animationTime = 300;
4+
5+ $('.collapse__head').on('click', function () {
6+ var $collapse = $(this).parent('.collapse');
7+
8+ if( ! $collapse.hasClass('collapse--expanded') ) {
9+ $collapse.addClass('collapse--expanded');
10+ $collapse.children('.collapse__body').slideDown(animationTime);
11+ } else {
12+ $collapse.children('.collapse__body').slideUp(animationTime);
13+ $collapse.removeClass('collapse--expanded');
14+ }
15+
16+ });
17+
18+})(jQuery);
octopod-css/development/styles/_collapse.cssnew file+95
@@ -0,0 +1,95 @@
1+.collapse {
2+ position: relative; /* для .collapse__clear */
3+}
4+ .collapse__head {
5+ position: relative;
6+ padding: 0;
7+ background-color: transparent;
8+ border: none;
9+ font-family: inherit;
10+ text-align: left;
11+ cursor: pointer;
12+ -webkit-touch-callout: none;
13+ -webkit-user-select: none;
14+ -khtml-user-select: none;
15+ -moz-user-select: none;
16+ -ms-user-select: none;
17+ user-select: none;
18+ }
19+
20+ .collapse__body {
21+ display: none;
22+ }
23+
24+
25+
26+
27+/* Модификаторы: */
28+
29+
30+.collapse--project {
31+
32+}
33+
34+.collapse--project {
35+ margin-top: 8px;
36+}
37+ .collapse--project > .collapse__head {
38+ position: relative;
39+ display: inline-block;
40+ vertical-align: top;
41+ margin-left: -24px;
42+ padding: 0 9px 0 24px;
43+ background-color: #F3F3F3;
44+ font-weight: 300;
45+ font-size: 16px;
46+ line-height: 24px;
47+ color: var(--dark-grey);
48+ }
49+
50+ .collapse--project > .collapse__head--has-changes {
51+ text-decoration: underline;
52+ }
53+
54+ .collapse--project > .collapse__head:hover {
55+ background-color: #E2E2E2;
56+ color: var(--primary);
57+ }
58+ .collapse--project > .collapse__head::after {
59+ content: "";
60+ flex-shrink: 0;
61+ position: absolute;
62+ left: 5px;
63+ top: 4px;
64+ display: block;
65+ width: 16px;
66+ height: 16px;
67+ background-image: url("../vectors/collapse-project.svg");
68+ background-size: 16px 16px;
69+ background-repeat: no-repeat;
70+ background-position: center center;
71+ transition: transform 0.3s ease;
72+ }
73+
74+ .collapse--project > .collapse__head:hover {
75+
76+ }
77+ .collapse--project > .collapse__head:hover::after {
78+
79+ }
80+
81+ .collapse--project.collapse--expanded > .collapse__head {
82+
83+ }
84+ .collapse--project.collapse--expanded > .collapse__head::after {
85+ transform: rotate(90deg);
86+ }
87+
88+ .collapse--project > .collapse__body {
89+
90+ }
91+ .collapse--project .collapse__inner {
92+ margin-top: 8px;
93+ padding-left: 32px;
94+ border-left: 1px solid #C4C4C4;
95+ }
octopod-css/development/styles/_deployment.css+3
@@ -58,3 +58,6 @@
5858 font-size: 16px;
5959 line-height: 24px;
6060 }
61+ .deployment__widget .row:first-child {
62+ margin-top: 0;
63+ }
octopod-css/development/styles/_editable-row.cssnew file+49
@@ -0,0 +1,49 @@
1+.editable-row {
2+ position: relative;
3+ margin-top: 8px;
4+ display: flex;
5+ align-self: flex-start;
6+}
7+
8+.editable-row:first-child {
9+ margin-top: 0;
10+}
11+ .editable-row__key {
12+ width: 400px;
13+ }
14+
15+ .editable-row__value {
16+ margin-left: 16px;
17+ flex-grow: 1;
18+ }
19+
20+ .editable-row__delete {
21+ margin-left: 2px;
22+ }
23+
24+
25+ .editable-row__placeholder {
26+ position: relative;
27+ width: 400px;
28+ height: 40px;
29+ padding: 14px 0;
30+ box-shadow: inset 0 -1px 0 rgba(176, 176, 176, 0.2);
31+ }
32+
33+ .editable-row__placeholder::before {
34+ position: relative;
35+ content: "";
36+ display: block;
37+ height: 12px;
38+ width: 350px;
39+ border-radius: 2px;
40+ background: rgba(238, 238, 238, 0.5);
41+ }
42+
43+ .editable-row__placeholder:first-child {
44+ margin-right: 16px;
45+ }
46+
47+ .editable-row__placeholder:first-child::before {
48+ width: 200px;
49+ }
octopod-css/development/styles/_has-changes.cssnew file+3
@@ -0,0 +1,3 @@
1+.has-changes {
2+ text-decoration: underline;
3+}
octopod-css/development/styles/_input.css+30−2
@@ -18,8 +18,6 @@
1818 color: rgba(0, 0, 0, 0.87);
1919 }
2020
21−
22−
2321 .input__widget:hover {
2422 box-shadow: inset 0 -1px #326EE6;
2523 }
@@ -43,6 +41,36 @@
4341 /* stylelint-enable */
4442
4543
44+ .input__dropdown {
45+ z-index: 2;
46+ position: absolute;
47+ top: 30px;
48+ background: #FFFFFF;
49+ padding: 8px 0;
50+ list-style: none;
51+ border-radius: 2px;
52+ box-shadow: 0 8px 8px 0 #0000003D, 0 0 8px 0 #0000001F;
53+ max-height: 274px;
54+ overflow-y: auto;
55+ }
56+
57+ .input__suggest {
58+ font-size: 16px;
59+ line-height: 24px;
60+ padding: 12px 16px;
61+ cursor: pointer;
62+ color: rgba(0, 0, 0, 0.87);
63+ font-weight: 400;
64+ }
65+
66+ .input__suggest b {
67+ font-weight: 700;
68+ }
69+
70+ .input__suggest:hover {
71+ background: #F8F8F8;
72+ }
73+
4674
4775.input--has-clear-type {
4876
octopod-css/development/styles/_key-custom-edited.cssnew file+4
@@ -0,0 +1,4 @@
1+.key-custom-edited {
2+ font-weight: 500 !important;
3+ text-decoration: underline !important;
4+}
octopod-css/development/styles/_key-default-edited.cssnew file+5
@@ -0,0 +1,5 @@
1+.key-default-edited {
2+ font-weight: 300 !important;
3+ font-style: italic !important;
4+ text-decoration: underline !important;
5+}
octopod-css/development/styles/_key-default-pristine.cssnew file+4
@@ -0,0 +1,4 @@
1+.key-default-pristine {
2+ font-weight: 300 !important;
3+ font-style: italic !important;
4+}
octopod-css/development/styles/_key-deleted.cssnew file+6
@@ -0,0 +1,6 @@
1+.key-deleted {
2+ font-weight: 300 !important;
3+ font-style: italic !important;
4+ text-decoration: line-through;
5+ color: #D3D3D3;
6+}
octopod-css/development/styles/_overrides.css+7−77
@@ -1,85 +1,15 @@
11.overrides {
22
33}
4− .overrides__item {
5− position: relative;
6− margin-bottom: 8px;
7− display: flex;
8− align-self: flex-start;
9− }
10−
11− .overrides__item:last-child {
12− margin-bottom: 0;
4+ .overrides__add {
5+ padding: 8px 0 8px 24px !important;
136 }
14− .overrides__key {
15− width: 400px;
16− }
17−
18− .overrides__value {
19− margin-left: 16px;
20− width: 400px;
21− }
22−
23− .overrides__delete {
24− margin-left: 2px;
25− }
26−
27− .overrides__search {
28− z-index: 2;
29− position: absolute;
30− top: 30px;
31− background: #FFFFFF;
32− padding: 8px 0;
33− list-style: none;
34− border-radius: 2px;
35− box-shadow: 0 8px 8px 0 #0000003D, 0 0 8px 0 #0000001F;
36− max-height: 274px;
37− overflow-y: auto;
38− }
39−
40− .overrides__search-item {
41− font-size: 16px;
42− line-height: 24px;
43− padding: 12px 16px;
44− cursor: pointer;
45− color: rgba(0, 0, 0, 0.87);
46− font-weight: 400;
47− }
487
49− .overrides__search-item b {
50− font-weight: 700;
51− }
8+ .overrides__list {
529
53− .overrides__search-item:hover {
54− background: #F8F8F8;
55− }
56−
57− .overrides__placeholder {
58− position: relative;
59− width: 400px;
60− height: 40px;
61− padding: 14px 0;
62− box-shadow: inset 0 -1px 0 rgba(176, 176, 176, 0.2);
63− }
64−
65− .overrides__placeholder::before {
66− position: relative;
67− content: "";
68− display: block;
69− height: 12px;
70− width: 350px;
71− border-radius: 2px;
72− background: rgba(238, 238, 238, 0.5);
73− }
74−
75− .overrides__placeholder:first-child {
76− margin-right: 16px;
77− }
78−
79− .overrides__placeholder:first-child::before {
80− width: 200px;
81− }
10+ }
8211
83− .overrides__add {
84− padding: 8px 0 8px 24px !important;
12+ .overrides__tree {
13+ margin-top: 16px;
14+ margin-left: 16px;
8515 }
octopod-css/development/styles/_padded.cssnew file+3
@@ -0,0 +1,3 @@
1+.padded {
2+ padding-left: 24px;
3+}
octopod-css/development/styles/_root.css+2
@@ -17,6 +17,8 @@
1717 --red: #D42A2A;
1818 --green: #0F7F12;
1919
20+ --black: #212121;
21+
2022 --shadow_1: 0 2px 2px rgba(0, 0, 0, 0.24),
2123 0 0 2px rgba(0, 0, 0, 0.12);
2224
octopod-css/development/styles/_row.cssnew file+3
@@ -0,0 +1,3 @@
1+.row {
2+ margin-top: 8px;
3+}
octopod-css/development/styles/_table.css+5−1
@@ -23,6 +23,10 @@
2323 .table tbody tr {
2424 border-top: 1px solid #E0E0E0;
2525 }
26+
27+ .table tbody tr:first-child {
28+ border-top: none;
29+ }
2630 .table th {
2731 vertical-align: middle;
2832 height: 48px;
@@ -57,7 +61,7 @@
5761
5862}
5963 .table--clickable tbody tr {
60− cursor: default;
64+ cursor: pointer;
6165 }
6266
6367 .table--clickable tbody tr:hover {
octopod-css/development/styles/_value-deleted.cssnew file+5
@@ -0,0 +1,5 @@
1+.value-deleted {
2+ font-weight: 300 !important;
3+ text-decoration: line-through;
4+ color: #D3D3D3;
5+}
octopod-css/development/styles/_value-edited.cssnew file+4
@@ -0,0 +1,4 @@
1+.value-edited {
2+ font-weight: 500 !important;
3+ text-decoration: underline !important;
4+}
octopod-css/development/styles/_value-pristine.cssnew file+3
@@ -0,0 +1,3 @@
1+.value-pristine {
2+ font-weight: 300 !important;
3+}
octopod-css/development/styles/_value-unknown.cssnew file+12
@@ -0,0 +1,12 @@
1+.value-unknown {
2+ display: inline-block;
3+ vertical-align: baseline;
4+ width: 92px;
5+ height: 0.5715em;
6+ background-color: #F3F3F3;
7+ border-radius: 2px;
8+ margin-right: 4px;
9+ text-indent: 200%;
10+ white-space: nowrap;
11+ overflow: hidden;
12+}
octopod-css/development/styles/style.css+15
@@ -40,3 +40,18 @@
4040@import url("_no-log.css");
4141@import url("_nav.css");
4242@import url("_break.css");
43+@import url("_key-default-pristine.css");
44+@import url("_key-default-edited.css");
45+@import url("_key-custom-edited.css");
46+@import url("_key-deleted.css");
47+@import url("_value-pristine.css");
48+@import url("_value-edited.css");
49+@import url("_value-deleted.css");
50+@import url("_value-pristine.css");
51+@import url("_value-edited.css");
52+@import url("_collapse.css");
53+@import url("_row.css");
54+@import url("_padded.css");
55+@import url("_has-changes.css");
56+@import url("_editable-row.css");
57+@import url("_value-unknown.css");
octopod-css/development/vectors/collapse-project.svgnew file+3
@@ -0,0 +1,3 @@
1+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2+<path d="M6.66678 11.3332L10.0001 7.99989L6.66678 4.66655L6.66678 11.3332Z" fill="#212121"/>
3+</svg>
octopod-frontend/octopod-frontend.cabal+12−1
@@ -23,6 +23,8 @@ flag development
2323executable frontend
2424 if impl(ghcjs >= 0.2.1)
2525 ghcjs-options: -dedupe
26+ ghcjs-options: -DGHCJS_BUSY_YIELD=20
27+ -DGHCJS_GC_INTERVAL=60000
2628 if flag(development)
2729 cpp-options: -DDEVELOPMENT
2830 main-is: Main.hs
@@ -50,7 +52,9 @@ executable frontend
5052 , Frontend.UIKit.Button.Static
5153 , Frontend.UIKit.Button.Action
5254 , Frontend.UIKit.Button.Sort
55+ , Frontend.UIKit.Button.Tree
5356 , Reflex.Dom.Renderable
57+ , Reflex.Dom.AsyncEvent
5458 ghc-options:
5559 -Weverything
5660 -Wno-implicit-prelude
@@ -99,6 +103,8 @@ executable frontend
99103 , ViewPatterns
100104 , ApplicativeDo
101105 , PartialTypeSignatures
106+ , DeriveAnyClass
107+ , MultiWayIf
102108 build-depends: aeson
103109 , base
104110 , bytestring
@@ -125,8 +131,13 @@ executable frontend
125131 , reflex
126132 , data-default
127133 , free
128− , parallel
129134 , generic-data
130135 , monoidal-containers
136+ , fuzzyfind
137+ , deepseq
138+ , patch
139+ , reflex-dom-core
140+ , these
141+ , ref-tf
131142 hs-source-dirs: src
132143 default-language: Haskell2010
octopod-frontend/src/Data/Text/Search.hs+54−61
@@ -1,4 +1,4 @@
1−{-# OPTIONS_GHC -Wno-redundant-constraints #-}
1+{-# OPTIONS_GHC -Wno-redundant-constraints -Wno-orphans #-}
22
33module Data.Text.Search
44 ( fuzzySearch,
@@ -12,75 +12,43 @@ module Data.Text.Search
1212 )
1313where
1414
15−import Control.Applicative
1615import Control.Applicative.Free.Fast
16+import Control.DeepSeq (NFData)
1717import Control.Lens
18−import Control.Parallel.Strategies
1918import Control.Searchable
20−import Data.Bifunctor
21−import Data.Char
22−import Data.Function
2319import qualified Data.List as L
24−import Data.Maybe (catMaybes)
20+import Data.Maybe (listToMaybe)
2521import Data.Ord
2622import Data.Semigroup
23+import Data.Sequence (Seq (..))
24+import Data.String
2725import Data.Text (Text)
2826import qualified Data.Text as T
27+import GHC.Generics (Generic)
2928import Reflex.Dom
3029import Reflex.Dom.Renderable
30+import Text.FuzzyFind
3131
32−type Needle = Text
32+type Needle = [String]
3333type Haystack = Text
34−type Running = Int
35−type Penalty = Int
36−
37−fuzzySearch' :: Needle -> Haystack -> Running -> Penalty -> [([FuzzySearchStringChunk String], Int)]
38−fuzzySearch' needle haystack i p = case (T.uncons needle, T.uncons haystack) of
39− (Just _, Nothing) -> []
40− (Just (n, eedle), Just (h, aystack)) ->
41− bimap (prependNotMatched h) (+ (i - p)) <$> fuzzySearch' needle aystack 0 p
42− <|> if toLower n == toLower h
43− then first (prependMatched h) <$> fuzzySearch' eedle aystack (i * 2 + 1) 1
44− else empty
45− (Nothing, Nothing) -> [([], i)]
46− (Nothing, Just _) -> [([NotMatched $ T.unpack haystack], i)]
47−
48−prependMatched :: Char -> [FuzzySearchStringChunk String] -> [FuzzySearchStringChunk String]
49−prependMatched c xs@(NotMatched _ : _) = Matched [c] : xs
50−prependMatched c (Matched cs : xs) = Matched (c : cs) : xs
51−prependMatched c [] = [Matched [c]]
52−
53−prependNotMatched :: Char -> [FuzzySearchStringChunk String] -> [FuzzySearchStringChunk String]
54−prependNotMatched c xs@(Matched _ : _) = NotMatched [c] : xs
55−prependNotMatched c (NotMatched cs : xs) = NotMatched (c : cs) : xs
56−prependNotMatched c [] = [NotMatched [c]]
57−
58−fuzzySearch :: Needle -> Haystack -> Maybe ([FuzzySearchStringChunk Text], Int)
59−fuzzySearch "" h = Just ([NotMatched h], 0)
60−fuzzySearch n h = case fuzzySearch' n h 0 0 of
61− [] -> Nothing
62− xs@(_ : _) -> Just . (first . fmap . fmap) T.pack $ L.maximumBy (compare `on` snd) xs
63−
64−fuzzySearchMany :: Needle -> [Haystack] -> [(Haystack, [FuzzySearchStringChunk Text])]
65−fuzzySearchMany needle haystacks =
66− fmap fst . L.sortOn (Down . snd) $
67− mapMaybe
68− ( \haystack ->
69− fuzzySearch needle haystack
70− <&> \(res, score) -> ((haystack, res), score)
71− )
72− haystacks
34+
35+fuzzySearch :: Needle -> Haystack -> Maybe Alignment
36+fuzzySearch [] h = Just (Alignment 0 $ Result $ pure $ Gap $ T.unpack h)
37+fuzzySearch n h = listToMaybe $ fuzzyFind n [T.unpack h]
38+
39+fuzzySearchMany :: Needle -> [Haystack] -> [(Seq FuzzySearchStringChunk, Haystack)]
40+fuzzySearchMany [] ts = ts <&> \t -> (pure $ NotMatched t, t)
41+fuzzySearchMany n h =
42+ (\(unAlignment -> (res, _), initial) -> (res, T.pack initial))
43+ <$> L.sortOn (Down . score . fst) (fuzzyFindOn id n (T.unpack <$> h))
7344
7445searchMany ::
7546 (Searchable Text x, SearchableConstraint Text x SearchResult) =>
76− Text ->
47+ [String] ->
7748 [x] ->
7849 [Searched x SearchResult]
79−searchMany "" = fmap wrapResult
80−searchMany t =
81− fmap snd . L.sortOn (Down . fst) . catMaybes
82− . withStrategy (parListChunk 3 rpar)
83− . fmap (search t)
50+searchMany [] = fmap wrapResult
51+searchMany t = fmap snd . L.sortOn (Down . fst) . mapMaybe (search t)
8452{-# INLINE searchMany #-}
8553
8654-- | Extract initial structure from search result.
@@ -100,7 +68,7 @@ wrapResult =
10068
10169search ::
10270 (Searchable Text x, SearchableConstraint Text x SearchResult) =>
103− Text ->
71+ Needle ->
10472 x ->
10573 Maybe (Int, Searched x SearchResult)
10674search needle x = runSearchApplicative $ searchWith searchSingle x
@@ -108,14 +76,35 @@ search needle x = runSearchApplicative $ searchWith searchSingle x
10876 searchSingle :: Text -> SearchApplicative SearchResult
10977 searchSingle t = case fuzzySearch needle t of
11078 Nothing -> pure $ SearchResult t Nothing
111− Just (res, score) -> liftAp $ TextSearch t res score
79+ Just (unAlignment -> (res, score)) -> liftAp $ TextSearch t res score
11280{-# INLINE search #-}
11381
82+unAlignment :: Alignment -> (Seq FuzzySearchStringChunk, Score)
83+unAlignment (Alignment score (Result res)) = (unResultSegment <$> res, score)
84+
85+unResultSegment :: ResultSegment -> FuzzySearchStringChunk
86+unResultSegment (Gap x) = NotMatched $ T.pack x
87+unResultSegment (Match x) = Matched $ T.pack x
88+
11489data SearchResult = SearchResult
11590 { initialSearchText :: !Text
116− , searchResult :: !(Maybe [FuzzySearchStringChunk Text])
91+ , searchResult :: !(Maybe (Seq FuzzySearchStringChunk))
11792 }
118− deriving stock (Eq, Ord)
93+ deriving stock (Eq, Ord, Generic)
94+ deriving anyclass (NFData)
95+
96+instance IsString SearchResult where
97+ fromString s = SearchResult (T.pack s) Nothing
98+
99+instance Semigroup SearchResult where
100+ SearchResult a aRes <> SearchResult b bRes = SearchResult (a <> b) $ case (aRes, bRes) of
101+ (Nothing, Nothing) -> Nothing
102+ (Just a', Nothing) -> Just $ a' :|> NotMatched b
103+ (Nothing, Just b') -> Just $ NotMatched a :<| b'
104+ (Just a', Just b') -> Just $ a' <> b'
105+
106+instance Monoid SearchResult where
107+ mempty = SearchResult mempty Nothing
119108
120109instance Searchable SearchResult SearchResult where
121110 type Searched SearchResult res = res
@@ -125,9 +114,10 @@ instance Searchable SearchResult SearchResult where
125114instance Renderable SearchResult where
126115 rndr (SearchResult _ (Just cs)) = rndr cs
127116 rndr (SearchResult t Nothing) = rndr t
117+ {-# INLINE rndr #-}
128118
129119data SearchF x where
130− TextSearch :: Text -> [FuzzySearchStringChunk Text] -> !Int -> SearchF SearchResult
120+ TextSearch :: Text -> Seq FuzzySearchStringChunk -> !Int -> SearchF SearchResult
131121
132122type SearchApplicative = Ap SearchF
133123
@@ -145,10 +135,13 @@ runSearchApplicative x = case findMaxResult x of
145135 where
146136 findMaxResult :: SearchApplicative a -> Maybe Int
147137 findMaxResult = fmap getMax . runAp_ (\(TextSearch _ _ i) -> Just $ Max i)
138+{-# INLINE runSearchApplicative #-}
148139
149−data FuzzySearchStringChunk a = NotMatched !a | Matched !a
150− deriving stock (Show, Eq, Ord, Functor)
140+data FuzzySearchStringChunk = NotMatched !Text | Matched !Text
141+ deriving stock (Show, Eq, Ord, Generic)
142+ deriving anyclass (NFData)
151143
152−instance Renderable a => Renderable (FuzzySearchStringChunk a) where
144+instance Renderable FuzzySearchStringChunk where
153145 rndr (NotMatched a) = rndr a
154146 rndr (Matched a) = elAttr "span" ("style" =: "text-decoration: underline;") (rndr a)
147+ {-# INLINE rndr #-}
octopod-frontend/src/Data/WorkingOverrides.hs+104−76
@@ -1,32 +1,104 @@
11module Data.WorkingOverrides
22 ( WorkingOverrides,
3− WorkingOverride,
4− WorkingOverride',
53 WorkingOverrideKey' (..),
4+ getConfigValueText,
65 WorkingOverrideKey,
76 WorkingOverrideKeyType (..),
8− WorkingOverrideValue' (..),
9− WorkingOverrideValue,
107 destructWorkingOverrides,
118 constructWorkingOverrides,
12− newWorkingOverride,
9+ ConfigValue (..),
10+ CustomKey (..),
11+ CustomConfigValue (..),
12+ WorkingConfigTree,
13+ WorkingOverride,
14+ WorkingOverride',
15+ configTreeHasLeaf,
16+ destructWorkingOverridesDyn,
1317 )
1418where
1519
20+import Common.Orphans ()
1621import Common.Types
17−import qualified Data.List as L
18−import qualified Data.Map.Ordered.Strict as OM
19−import Data.Maybe
22+import Control.Monad.Reader
23+import Control.Monad.Ref
24+import Data.ConfigTree (ConfigTree)
25+import qualified Data.ConfigTree as CT
26+import Data.Foldable
27+import Data.Functor
28+import Data.Map (Map)
29+import qualified Data.Map as M
30+import Data.Maybe (catMaybes)
31+import qualified Data.Maybe as M
2032import Data.Text (Text)
21−import Data.UniqMap
33+import Data.These
34+import GHC.Generics (Generic)
35+import Reflex
36+
37+type WorkingConfigTree k v = ConfigTree k (ConfigValue v)
38+
39+data ConfigValue te
40+ = DefaultConfigValue !te
41+ | CustomConfigValue !(Either (CustomKey te) (CustomConfigValue te))
42+ deriving stock (Show, Generic, Eq, Ord)
43+
44+newtype CustomKey v = CustomKey v
45+ deriving stock (Show, Generic, Eq, Ord)
46+
47+data CustomConfigValue te
48+ = CustomValue !te
49+ | DeletedValue !(Maybe te)
50+ deriving stock (Show, Generic, Eq, Ord)
2251
52+getConfigValueText :: ConfigValue te -> Maybe te
53+getConfigValueText (DefaultConfigValue t) = Just t
54+getConfigValueText (CustomConfigValue (Left (CustomKey t))) = Just t
55+getConfigValueText (CustomConfigValue (Right (CustomValue t))) = Just t
56+getConfigValueText (CustomConfigValue (Right (DeletedValue t))) = t
57+
58+-- Super inefficient but i dont care
59+configTreeHasLeaf ::
60+ forall m kv te.
61+ ( MonadReader (Ref m (Map (Text, ConfigTree kv te) Bool)) m
62+ , MonadRef m
63+ , Ord kv
64+ , Ord te
65+ ) =>
66+ -- | The id of the predicate used for caching
67+ Text ->
68+ (te -> Bool) ->
69+ ConfigTree kv te ->
70+ m Bool
71+configTreeHasLeaf name f = memo (\(_, CT.ConfigTree x) -> go $ toList x) . (name,)
72+ where
73+ go :: [(Maybe te, ConfigTree kv te)] -> m Bool
74+ go [] = pure False
75+ go ((Just y, _) : _) | f y = pure True
76+ go ((_, ct) : rest) =
77+ configTreeHasLeaf name f ct >>= \case
78+ True -> pure True
79+ False -> go rest
80+
81+memo ::
82+ (MonadReader (Ref m (Map x y)) m, Ord x, MonadRef m) =>
83+ (x -> m y) ->
84+ x ->
85+ m y
86+memo f x = do
87+ memoRef <- ask
88+ m <- readRef memoRef
89+ case M.lookup x m of
90+ Just y -> pure y
91+ Nothing -> do
92+ y <- f x
93+ modifyRef memoRef $ M.insert x y
94+ pure y
2395type WorkingOverrides = WorkingOverrides' Text
2496
25−type WorkingOverrides' te = UniqKeyMap (WorkingOverride' te)
97+type WorkingOverrides' te = ConfigTree te (ConfigValue te)
2698
2799type WorkingOverride = WorkingOverride' Text
28100
29−type WorkingOverride' te = (WorkingOverrideKey' te, WorkingOverrideValue' te)
101+type WorkingOverride' te = (te, ConfigValue te)
30102
31103type WorkingOverrideKey = WorkingOverrideKey' Text
32104
@@ -36,75 +108,31 @@ data WorkingOverrideKey' te = WorkingOverrideKey !WorkingOverrideKeyType !te
36108data WorkingOverrideKeyType = CustomWorkingOverrideKey | DefaultWorkingOverrideKey
37109 deriving stock (Show, Eq)
38110
39−type WorkingOverrideValue = WorkingOverrideValue' Text
111+destructWorkingOverridesDyn :: Reflex t => [Dynamic t WorkingOverride] -> Dynamic t (Overrides l)
112+destructWorkingOverridesDyn =
113+ fmap (Overrides . CT.fromFlatList . catMaybes) . sequenceA . (fmap . fmap) (\(k, v) -> (k,) <$> unConfigValue v)
40114
41−data WorkingOverrideValue' te
42− = WorkingCustomValue !te
43− | WorkingDefaultValue !te
44− | WorkingDeletedValue !(Maybe te)
45− deriving stock (Show)
46−
47−destructWorkingOverrides :: WorkingOverrides -> Overrides l
115+destructWorkingOverrides :: [WorkingOverride] -> Overrides l
48116destructWorkingOverrides =
49− Overrides
50− . foldr
51− ( \pair@(k, v) m -> case OM.lookup k m of
52− Just _ | v == ValueDeleted -> m
53− _ -> m OM.|> pair
54− )
55− OM.empty
56− . mapMaybe
57− ( \case
58− (WorkingOverrideKey CustomWorkingOverrideKey k, getWorkingOverrideValue -> v) -> Just (k, v)
59− (WorkingOverrideKey _ k, WorkingCustomValue v) -> Just (k, ValueAdded v)
60− (WorkingOverrideKey _ k, WorkingDeletedValue _) -> Just (k, ValueDeleted)
61− (WorkingOverrideKey DefaultWorkingOverrideKey _, WorkingDefaultValue _) -> Nothing
62− )
63− . elemsUniq
64− where
65− getWorkingOverrideValue :: WorkingOverrideValue -> OverrideValue
66− getWorkingOverrideValue (WorkingCustomValue x) = ValueAdded x
67− getWorkingOverrideValue (WorkingDefaultValue x) = ValueAdded x
68− getWorkingOverrideValue (WorkingDeletedValue _) = ValueDeleted
117+ Overrides . CT.fromFlatList . M.catMaybes . fmap (\(k, v) -> (k,) <$> unConfigValue v)
118+
119+unConfigValue :: ConfigValue te -> Maybe (OverrideValue' te)
120+unConfigValue (DefaultConfigValue _) = Nothing
121+unConfigValue (CustomConfigValue (Left (CustomKey te))) = Just $ ValueAdded te
122+unConfigValue (CustomConfigValue (Right (CustomValue te))) = Just $ ValueAdded te
123+unConfigValue (CustomConfigValue (Right (DeletedValue _))) = Just ValueDeleted
69124
70125constructWorkingOverrides ::
71126 Ord te =>
72− Maybe (DefaultConfig' te l) ->
127+ DefaultConfig' te l ->
73128 Overrides' te l ->
74129 WorkingOverrides' te
75−constructWorkingOverrides (Just (DefaultConfig dCfg)) (Overrides ovsM) =
76− let custom =
77− uniqMapFromList
78− . mapMaybe
79− ( \(k, v) ->
80− let k' = WorkingOverrideKey (if OM.member k dCfg then DefaultWorkingOverrideKey else CustomWorkingOverrideKey) k
81− in case v of
82− ValueAdded x -> Just (k', WorkingCustomValue x)
83− ValueDeleted -> (k',) . WorkingDeletedValue . Just <$> OM.lookup k dCfg
84− )
85− . OM.assocs
86− $ ovsM
87− in L.foldl'
88− ( \m (k, v) ->
89− if OM.member k ovsM
90− then m
91− else
92− fst $
93− insertUniqEnd (WorkingOverrideKey DefaultWorkingOverrideKey k, WorkingDefaultValue v) m
94− )
95− custom
96− (OM.assocs dCfg)
97−constructWorkingOverrides Nothing (Overrides ovsM) =
98− uniqMapFromList
99− . fmap
100− ( \(k, v) ->
101− let k' = WorkingOverrideKey CustomWorkingOverrideKey k
102− in case v of
103− ValueAdded x -> (k', WorkingCustomValue x)
104− ValueDeleted -> (k', WorkingDeletedValue Nothing)
105− )
106− . OM.assocs
107− $ ovsM
108−
109−newWorkingOverride :: WorkingOverride
110−newWorkingOverride = (WorkingOverrideKey CustomWorkingOverrideKey "", WorkingCustomValue "")
130+constructWorkingOverrides (DefaultConfig defCT) (Overrides newCT) =
131+ CT.catMaybes $
132+ CT.zip newCT defCT <&> \case
133+ That x -> Just $ DefaultConfigValue x
134+ This x -> case x of
135+ ValueDeleted -> Nothing
136+ ValueAdded v -> Just $ CustomConfigValue $ Left $ CustomKey v
137+ These ValueDeleted v -> Just $ CustomConfigValue $ Right $ DeletedValue $ Just v
138+ These (ValueAdded v) _ -> Just $ CustomConfigValue $ Right $ CustomValue v
octopod-frontend/src/Frontend/UIKit.hs+343−135
@@ -7,44 +7,66 @@ module Frontend.UIKit
77 octopodTextInputDyn,
88 loadingOverride,
99 loadingOverrides,
10− overrideField,
11− OverrideField (..),
10+ configField,
1211 popupOverlay,
1312 Default (..),
1413 module X,
1514 (.~~),
1615 (?~~),
17− OverrideFieldType (..),
1816 deletedOverride,
19− showNonEditableWorkingOverride,
20− NonEditableWorkingOverrideStyle (..),
17+ untilReadyEv',
18+ untilReady',
19+ runWithReplace',
20+ joinEvM,
21+ showNonEditableWorkingOverrideTree,
22+ showFlatConfig,
23+ ConfigFieldButtonAction (..),
24+ showOverrideTree,
25+ nonEditableLoading,
2126 )
2227where
2328
29+import Common.Types
2430import Control.Lens
31+import Control.Lens.Extras (is)
2532import Control.Monad
26−import Control.Monad.IO.Class
33+import Control.Monad.Reader
34+import Control.Monad.Ref
2735import Data.Align
36+import Data.ConfigTree (ConfigTree (ConfigTree))
37+import qualified Data.ConfigTree as CT
2838import Data.Default
39+import Data.Either
40+import Data.Foldable
2941import Data.Functor
3042import Data.Generics.Labels ()
43+import Data.Generics.Sum
3144import Data.List.NonEmpty
3245import qualified Data.List.NonEmpty as NE
46+import Data.Map (Map)
3347import qualified Data.Map as M
34−import Data.Maybe (isNothing, maybeToList)
48+import qualified Data.Map.Ordered.Strict as OM
49+import Data.Maybe (fromMaybe, isNothing, maybeToList)
50+import Data.Monoid (Endo (..))
3551import Data.Text (Text)
52+import qualified Data.Text as T
3653import Data.Text.Search
3754import Data.These
55+import Data.Traversable
56+import Data.Witherable
3857import Data.WorkingOverrides
3958import Frontend.Classes as X
4059import Frontend.UIKit.Button.Action as X
60+import Frontend.UIKit.Button.Common
4161import Frontend.UIKit.Button.Dash as X
4262import Frontend.UIKit.Button.Expander as X
4363import Frontend.UIKit.Button.Large as X
4464import Frontend.UIKit.Button.Sort as X
4565import Frontend.UIKit.Button.Static as X
66+import Frontend.UIKit.Button.Tree
4667import GHC.Generics (Generic)
4768import Reflex.Dom
69+import Reflex.Dom.AsyncEvent
4870import Reflex.Dom.Renderable
4971import Reflex.Network
5072
@@ -73,64 +95,84 @@ errorCommonWidget =
7395 elClass "b" "null__heading" $ text "Cannot retrieve the data"
7496 divClass "null__message" $ text "Try to reload the page"
7597
76−data OverrideField t = OverrideField
98+data ConfigField t = ConfigField
7799 { fieldValue :: Dynamic t Text
78100 , fieldError :: Dynamic t (Maybe (NonEmpty Text))
79− , fieldDisabled :: Dynamic t Bool
80− , fieldType :: Dynamic t OverrideFieldType
101+ , fieldType :: Dynamic t ConfigFieldType
81102 }
82103 deriving stock (Generic)
83104
84−data OverrideFieldType
85− = DefaultOverrideFieldType
86− | EditedOverrideFieldType
105+data ConfigFieldType
106+ = DefaultConfigFieldType
107+ | CustomConfigFieldType
87108 deriving stock (Show)
88109
89−overrideFieldTypeClasses :: OverrideFieldType -> Classes
90−overrideFieldTypeClasses DefaultOverrideFieldType = "input--default"
91−overrideFieldTypeClasses EditedOverrideFieldType = mempty
92−
93−overrideField :: MonadWidget t m => Dynamic t [Text] -> OverrideField t -> OverrideField t -> m (Dynamic t Text, Dynamic t Text, Event t ())
94−overrideField overrideKeyValues keyDyn valueDyn = do
95− elDiv "overrides__item" $ do
96− keyInp <-
110+configField ::
111+ MonadWidget t m =>
112+ Dynamic t [Text] ->
113+ Dynamic t WorkingOverride ->
114+ m (Dynamic t Text, Dynamic t Text, Event t ConfigFieldButtonAction)
115+configField overrideKeyValues (splitDynPure -> (k, v')) = do
116+ let (splitDynPure -> (keyClass, valueClass)) =
117+ v' <&> \case
118+ DefaultConfigValue _ -> ("key-default-pristine", "value-pristine")
119+ CustomConfigValue (Right (CustomValue _)) -> ("key-default-edited", "value-edited")
120+ CustomConfigValue (Right (DeletedValue Just {})) -> ("key-deleted", "value-deleted")
121+ CustomConfigValue (Right (DeletedValue Nothing)) -> ("key-deleted", "value-unknown")
122+ CustomConfigValue (Left _) -> ("key-custom-edited", "value-edited")
123+ valDyn =
124+ v' <&> \case
125+ DefaultConfigValue v -> v
126+ CustomConfigValue (Right (CustomValue v)) -> v
127+ CustomConfigValue (Right (DeletedValue v)) -> fromMaybe "UNKNOWN" v
128+ CustomConfigValue (Left (CustomKey v)) -> v
129+ deletedDyn' = (is (_Ctor' @"CustomConfigValue" . _Right . _Ctor' @"DeletedValue")) <$> v'
130+ deletedDyn <- holdUniqDyn deletedDyn'
131+ let extraDivClasses =
132+ deletedDyn <&> \case
133+ True -> "input--deleted"
134+ False -> mempty
135+ elDiv "editable-row" $ mdo
136+ (value -> keyTextDyn) <-
97137 octopodTextInputDyn
98138 overrideKeyValues
99− (keyDyn ^. #fieldDisabled)
100− ( do
101− t <- keyDyn ^. #fieldType
102− pure $ "overrides__key" <> overrideFieldTypeClasses t
103− )
139+ deletedDyn
140+ (mappend "editable-row__key" <$> extraDivClasses)
141+ keyClass
104142 "key"
105− (keyDyn ^. #fieldValue)
106− (keyDyn ^. #fieldError)
107− let keyTextDyn = value keyInp
143+ k
144+ (pure Nothing)
108145 (value -> valTextDyn) <-
109146 octopodTextInputDyn
110147 (pure [])
111− (valueDyn ^. #fieldDisabled)
112− ( do
113− t <- valueDyn ^. #fieldType
114− pure $ "overrides__value" <> overrideFieldTypeClasses t
115− )
148+ deletedDyn
149+ (mappend "editable-row__value" <$> extraDivClasses)
150+ valueClass
116151 "value"
117− (valueDyn ^. #fieldValue)
118− (valueDyn ^. #fieldError)
119− closeEv <- deleteOverrideButton
152+ valDyn
153+ (valTextDyn <&> \t -> if (T.null . T.strip) t then Just (pure "Value can not be empty") else Nothing)
154+ closeEv <-
155+ networkView >=> switchHold never $
156+ deletedDyn <&> \case
157+ True -> fmap (const ConfigFieldButtonRecover) <$> undoOverrideButton
158+ False -> fmap (const ConfigFieldButtonClear) <$> deleteOverrideButton
120159 pure (keyTextDyn, valTextDyn, closeEv)
121160
161+data ConfigFieldButtonAction = ConfigFieldButtonClear | ConfigFieldButtonRecover
162+ deriving stock (Show)
163+
122164deletedOverride :: MonadWidget t m => Text -> Maybe Text -> m (Event t ())
123165deletedOverride k vM = do
124− elDiv "overrides__item" $ do
125− elDiv "overrides__key input input--deleted" $ field k
166+ elDiv "editable-row" $ do
167+ elDiv "editable-row__key input input--deleted" $ field k
126168 case vM of
127169 Nothing -> do
128− elDiv "overrides__placeholder overrides__value" blank
170+ elDiv "editable-row__placeholder editable-row__value" blank
129171 loadingOverrideSpinner
130172 pure never
131173 Just v -> do
132− elDiv "overrides__value input input--deleted" $ field $ v
133− undoOverrideButton
174+ elDiv "editable-row__value input input--deleted" $ field $ v
175+ (($> ()) <$> undoOverrideButton)
134176 where
135177 field t =
136178 void $
@@ -140,20 +182,20 @@ deletedOverride k vM = do
140182 & initialAttributes .~ ("type" =: "text" <> "class" =: "input__widget")
141183
142184loadingOverrideField :: MonadWidget t m => m ()
143−loadingOverrideField = elDiv "overrides__placeholder" blank
185+loadingOverrideField = elDiv "editable-row__placeholder" blank
144186
145187loadingOverrideSpinner :: MonadWidget t m => m ()
146188loadingOverrideSpinner = elDiv "overrides__delete spot spot--loader" blank
147189
148190loadingOverride :: MonadWidget t m => m ()
149191loadingOverride = do
150− elDiv "overrides__item loader" $ do
192+ elDiv "editable-row loader" $ do
151193 loadingOverrideField
152194 loadingOverrideField
153195 loadingOverrideSpinner
154196
155197loadingOverrides :: MonadWidget t m => m ()
156−loadingOverrides = do
198+loadingOverrides = divClass "padded" $ do
157199 loadingOverride
158200 loadingOverride
159201 loadingOverride
@@ -169,6 +211,8 @@ octopodTextInput' ::
169211 (Dynamic t [Text]) ->
170212 -- | Disabled?
171213 Dynamic t Bool ->
214+ -- | Div classes.
215+ Dynamic t Classes ->
172216 -- | Input field classes.
173217 Dynamic t Classes ->
174218 -- | Placeholder for input field.
@@ -178,14 +222,14 @@ octopodTextInput' ::
178222 -- | Event carrying the error message.
179223 Event t Text ->
180224 m (InputElement EventResult GhcjsDomSpace t, Dynamic t Bool)
181−octopodTextInput' valuesDyn disabledDyn clssDyn placeholder inValDyn' errEv = mdo
225+octopodTextInput' valuesDyn disabledDyn inpClassesDyn clssDyn placeholder inValDyn' errEv = mdo
182226 errDyn <-
183227 holdDyn Nothing $
184228 leftmost
185229 [ Just . pure <$> errEv
186230 , Nothing <$ updated (value inp)
187231 ]
188− inp <- octopodTextInputDyn valuesDyn disabledDyn clssDyn placeholder inValDyn' errDyn
232+ inp <- octopodTextInputDyn valuesDyn disabledDyn inpClassesDyn clssDyn placeholder inValDyn' errDyn
189233 pure (inp, isNothing <$> errDyn)
190234
191235-- | The only text input field that is used in project forms. This input
@@ -193,19 +237,21 @@ octopodTextInput' valuesDyn disabledDyn clssDyn placeholder inValDyn' errEv = md
193237octopodTextInputDyn ::
194238 MonadWidget t m =>
195239 -- | Value to suggest
196− (Dynamic t [Text]) ->
240+ Dynamic t [Text] ->
197241 -- | Disabled?
198242 Dynamic t Bool ->
243+ -- | Div classes.
244+ Dynamic t Classes ->
199245 -- | Input field classes.
200246 Dynamic t Classes ->
201247 -- | Placeholder for input field.
202248 Text ->
203249 -- | Init value.
204− (Dynamic t Text) ->
250+ Dynamic t Text ->
205251 -- | Event carrying the error message.
206252 Dynamic t (Maybe (NonEmpty Text)) ->
207253 m (InputElement EventResult GhcjsDomSpace t)
208−octopodTextInputDyn valuesDyn disabledDyn clssDyn placeholder inValDyn' errDyn = mdo
254+octopodTextInputDyn valuesDyn disabledDyn divClasses inpClassesDyn placeholder inValDyn' errDyn = mdo
209255 inValDyn <- holdUniqDyn inValDyn'
210256 let valDyn = value inp
211257 inValEv =
@@ -225,19 +271,24 @@ octopodTextInputDyn valuesDyn disabledDyn clssDyn placeholder inValDyn' errDyn =
225271
226272 let classDyn = do
227273 errClasses <- errorClassesDyn
228− additionalClasses <- clssDyn
274+ additionalClasses <- divClasses
229275 pure . destructClasses $ "input" <> errClasses <> additionalClasses
230276
231277 inVal <- sample . current $ inValDyn
232278 disabled <- sample . current $ disabledDyn
279+ let actualInputClassesDyn = do
280+ inpClass <- inpClassesDyn
281+ pure $ destructClasses $ "input__widget" <> inpClass
282+ clss <- sample . current $ actualInputClassesDyn
233283 (inp, selectedValue) <- elDynClass "div" classDyn $ do
234284 inp' <-
235285 inputElement $
236286 def
237287 & initialAttributes
238288 .~ ( "type" =: "text"
239− <> "class" =: "input__widget"
289+ <> "class" =: clss
240290 <> "placeholder" =: placeholder
291+ <> "spellcheck" =: "false"
241292 )
242293 & inputElementConfig_setValue .~ inValEv
243294 & inputElementConfig_initialValue .~ inVal
@@ -247,101 +298,258 @@ octopodTextInputDyn valuesDyn disabledDyn clssDyn placeholder inValDyn' errDyn =
247298 <>~ updated
248299 ( do
249300 disabled' <- disabledDyn
301+ classes <- actualInputClassesDyn
250302 pure $
251− M.singleton "disabled" $
252− if disabled' then Just "disabled" else Nothing
303+ ( "disabled" =: (if disabled' then Just "disabled" else Nothing)
304+ <> "class" =: Just classes
305+ )
253306 )
254307 void $
255308 simpleList ((maybeToList >=> NE.toList) <$> errDyn) $ \err ->
256309 divClass "input__output" $ dynText err
257310
258− delayedFalseFocus <- delayFalse $ _inputElement_hasFocus inp'
259− selectedValue' <- networkView >=> switchHoldPromptly never $ do
260− hasFocus <- delayedFalseFocus
261− values <- valuesDyn
262− case hasFocus of
263− True | (_ : _) <- values -> do
311+ let searchValuesEv = catMaybes . updated $ do
312+ hasFocus <- _inputElement_hasFocus inp'
313+ values <- valuesDyn
264314 currVal <- valDyn
265− case fuzzySearchMany currVal values of
266− [] -> pure $ pure never
267− ress ->
268− pure $ do
269− elClass "ul" "overrides__search" $ do
270− fmap leftmost $
271− forM ress $ \(initialText, res) -> do
272− (resEl, ()) <- elClass' "li" "overrides__search-item" $
273− forM_ res $ \case
274− Matched t -> elAttr "span" ("style" =: "font-weight: bold;") $ text t
275− NotMatched t -> text t
276− pure $ domEvent Click resEl $> initialText
277− _ -> pure $ pure never
278− pure (inp', selectedValue')
279− pure inp
315+ pure $ case hasFocus of
316+ True | (_ : _) <- values -> Just (values, currVal)
317+ _ -> Nothing
318+ searchResultEv <- asyncEventLast searchValuesEv $ \(values, currVal) ->
319+ fuzzySearchMany [T.unpack currVal] values
320+ selectedValueEv <-
321+ (>>= joinEvM) $
322+ runWithReplace' (pure ()) $
323+ updated (_inputElement_hasFocus inp') <&> \case
324+ False -> pure never
325+ True ->
326+ (>>= joinEvM) $
327+ runWithReplace' (pure ()) $
328+ searchResultEv <&> \case
329+ [] -> pure never
330+ searchResult -> do
331+ elClass "ul" "input__dropdown" $ do
332+ fmap leftmost $
333+ for searchResult $ \(res, initialText) -> do
334+ (resEl, ()) <- elClass' "li" "input__suggest" $
335+ for_ res $ \case
336+ Matched t -> elAttr "span" ("style" =: "font-weight: bold;") $ text t
337+ NotMatched t -> text t
338+ -- Because 'Click' would fire after the text field looses
339+ -- focus and the popup disappears.
340+ pure $ domEvent Mousedown resEl $> initialText
280341
281−delayFalse :: (MonadHold t m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m)) => Dynamic t Bool -> m (Dynamic t Bool)
282−delayFalse x = do
283− initVal <- sample . current $ x
284− let trueEv = ffilter id $ updated x
285− falseEv = ffilter not $ updated x
286− delayedFalseEv <- delay 0.1 falseEv
287− holdDyn initVal $ leftmost [trueEv, delayedFalseEv]
342+ pure (inp', selectedValueEv)
343+ pure inp
288344
289345-- | Dark unclickable background for opened sidebar.
290346popupOverlay :: DomBuilder t m => m ()
291347popupOverlay =
292348 elAttr "div" ("class" =: "popup__overlay" <> "aria-hidden" =: "true") blank
293349
294−data NonEditableWorkingOverrideStyle
295− = RegularNonEditableWorkingOverrideStyle
296− | LargeNonEditableWorkingOverrideStyle
297−
298−nonEditableWorkingOverrideStyleClasses :: NonEditableWorkingOverrideStyle -> Classes
299−nonEditableWorkingOverrideStyleClasses RegularNonEditableWorkingOverrideStyle = mempty
300−nonEditableWorkingOverrideStyleClasses LargeNonEditableWorkingOverrideStyle = "listing--larger"
301−
302−-- | Widget that shows overrides list. It does not depend on their type.
303−showNonEditableWorkingOverride ::
304− (MonadWidget t m, Renderable te) =>
305− -- | Loading?
306− Bool ->
307− -- | Is it fully loaded?
308− Bool ->
309− NonEditableWorkingOverrideStyle ->
310− -- | Overrides list.
311− [WorkingOverride' te] ->
350+showFlatConfig ::
351+ (MonadWidget t m, Renderable tk, Renderable tv) =>
352+ [(tk, OverrideValue' tv)] ->
353+ m ()
354+showFlatConfig l =
355+ for_ l $ \(k, v) ->
356+ let keyClasses = if is (_Ctor' @"ValueDeleted") v then "key-deleted" else "key-default-pristine"
357+ in divClass "row" $ do
358+ elClass "span" (destructClasses keyClasses) $ do
359+ rndr k
360+ text ": "
361+ pure ()
362+ case v of
363+ ValueAdded t -> elClass "span" "value-edited" $ rndr t
364+ ValueDeleted -> elClass "div" "listing__placeholder listing__placeholder__value" $ pure ()
365+ pure ()
366+
367+showNonEditableWorkingOverrideTree ::
368+ (MonadWidget t m, Renderable tk, Renderable tv, Ord tk, Ord (ConfigValue tv)) =>
369+ Dynamic t (WorkingConfigTree tk tv) ->
312370 m ()
313−showNonEditableWorkingOverride loading loaded style cfg =
314− divClass
315− ( destructClasses $
316− "listing" <> "listing--for-text" <> nonEditableWorkingOverrideStyleClasses style
317− )
318− $ do
319− case cfg of
320− [] ->
321− divClass "listing__item" $
322− elClass "span" "listing--info-text" $
323− text $
324− if loaded then "no configuration" else "no custom configuration"
325− _ -> forM_ cfg $ \(WorkingOverrideKey keyType key, val) -> do
326− let wrapper = case val of
327− WorkingDeletedValue _ -> divClass "listing__item deleted"
328− _ -> divClass "listing__item"
329− wrapper $ do
330− let keyWrapper = case keyType of
331− CustomWorkingOverrideKey -> elClass "span" "listing__key"
332− DefaultWorkingOverrideKey -> elClass "span" "listing__key default"
333− keyWrapper $ do
334− rndr key
335− text ": "
336−
337− case val of
338− WorkingCustomValue txt -> elClass "span" "listing__value" $ rndr txt
339− WorkingDefaultValue txt -> elClass "span" "listing__value default" $ rndr txt
340− WorkingDeletedValue (Just txt) -> elClass "span" "listing__value default" $ rndr txt
341− WorkingDeletedValue Nothing -> do
342− elClass "div" "listing__placeholder listing__placeholder__value" $ pure ()
343− when loading $ elClass "div" "listing__spinner" $ pure ()
344− when loading $
345− divClass "listing__item" $ do
346− elClass "div" "listing__placeholder" $ pure ()
347− elClass "div" "listing__spinner" $ pure ()
371+showNonEditableWorkingOverrideTree treeDyn = do
372+ memoRef <- newRef mempty
373+ flip runReaderT memoRef $
374+ untilReady (lift nonEditableLoading) $
375+ networkView $
376+ treeDyn <&> \tree ->
377+ untilReady (lift nonEditableLoading) $
378+ divClass "padded" $
379+ showWorkingOverrideTree'
380+ (const False)
381+ (is (_Ctor' @"CustomConfigValue"))
382+ (\_ _ -> ())
383+ (\_ k v -> renderRow k v $> pure ())
384+ tree
385+ (pure True)
386+ never
387+ pure ()
388+
389+nonEditableLoading :: DomBuilder t m => m ()
390+nonEditableLoading = for_ [1 :: Int .. 3] $ \_ ->
391+ divClass "padded" $
392+ divClass "row" $ do
393+ elClass "div" "listing__placeholder" $ pure ()
394+ elClass "div" "listing__spinner" $ pure ()
395+
396+showOverrideTree ::
397+ forall m t tv tk x.
398+ ( MonadWidget t m
399+ , Renderable tk
400+ , Show x
401+ , Ord tk
402+ , Ord tv
403+ ) =>
404+ -- | Value should be shown by default
405+ (tv -> Bool) ->
406+ -- | Value is modified
407+ (tv -> Bool) ->
408+ -- | Prepare the fixpoint data
409+ (tk -> tv -> x) ->
410+ -- | Render row
411+ (x -> tk -> tv -> m (Dynamic t x)) ->
412+ ConfigTree tk tv ->
413+ -- | Should we render anything
414+ m [Dynamic t x]
415+showOverrideTree isShown isModified prepareData renderValue ct = do
416+ memoRef <- newRef mempty
417+ flip runReaderT memoRef $
418+ showWorkingOverrideTree' isShown isModified prepareData ((fmap . fmap . fmap) lift renderValue) ct (pure True) never
419+
420+showWorkingOverrideTree' ::
421+ ( MonadWidget t m
422+ , Renderable tk
423+ , Show x
424+ , MonadReader (Ref m (Map (Text, ConfigTree tk tv) Bool)) m
425+ , Ord tv
426+ , Ord tk
427+ ) =>
428+ -- | Value should be shown by default
429+ (tv -> Bool) ->
430+ -- | Value is modified
431+ (tv -> Bool) ->
432+ -- | Prepare the fixpoint data
433+ (tk -> tv -> x) ->
434+ -- | Render row
435+ (x -> tk -> tv -> m (Dynamic t x)) ->
436+ ConfigTree tk tv ->
437+ -- | Parent is open
438+ Dynamic t Bool ->
439+ -- | Force open/close all subtrees
440+ Event t Bool ->
441+ m [Dynamic t x]
442+showWorkingOverrideTree' isShown isModified prepareData renderValue (ConfigTree m) shouldRenderDyn' forceSubtreesEv = do
443+ shouldRenderDyn <- holdUniqDyn shouldRenderDyn'
444+ (appEndo . fold -> f) <- for (OM.assocs m) $ \(k, (mv, _)) -> do
445+ xMaybe <- for mv $ \v -> mdo
446+ let initialData = prepareData k v
447+ x <-
448+ networkView $
449+ shouldRenderDyn <&> \render -> do
450+ lastX <- sample $ current x'
451+ if render
452+ then elClass "div" "row" $ renderValue lastX k v
453+ else pure (pure lastX)
454+ x' <- (join <$> (holdDyn (pure initialData) x))
455+ pure $ x'
456+ pure $ Endo $ maybe id (:) xMaybe
457+ fmap (f . concat) $
458+ for (OM.assocs m) $ \case
459+ (_, (_, subtree)) | CT.null subtree -> pure []
460+ (k, (_, subtree)) -> mdo
461+ let wrapperClass = do
462+ open <- openDyn
463+ pure $
464+ destructClasses $
465+ "collapse--project" <> "collapse"
466+ <> if open
467+ then "collapse--expanded"
468+ else mempty
469+ (ovs, openDyn) <- elDynClass "div" wrapperClass $ do
470+ modified <- configTreeHasLeaf "isModified" isModified subtree
471+ shown <- configTreeHasLeaf "isShown" isShown subtree
472+ (traceEvent "clickEv" -> clickEv) <-
473+ treeButton
474+ TreeButtonConfig
475+ { buttonText = TextBuilder $ rndr k
476+ , subtreeHasChanges = modified
477+ , visible = shouldRenderDyn
478+ , forceState = forceSubtreesEv
479+ }
480+ buttonIsOpen <- holdDyn shown $ leftmost [forceSubtreesEv, fst <$> clickEv]
481+ let selfForceSubtrees = fst <$> ffilter (isRight . snd) clickEv
482+ ovs' <-
483+ divClass "collapse__inner" $
484+ showWorkingOverrideTree'
485+ isShown
486+ isModified
487+ prepareData
488+ renderValue
489+ subtree
490+ ((&&) <$> buttonIsOpen <*> shouldRenderDyn)
491+ (leftmost [selfForceSubtrees, forceSubtreesEv])
492+ pure (ovs', buttonIsOpen)
493+ pure ovs
494+
495+renderRow ::
496+ (DomBuilder t m, Renderable te, Renderable k) =>
497+ k ->
498+ ConfigValue te ->
499+ m ()
500+renderRow k v = do
501+ case v of
502+ DefaultConfigValue v' -> do
503+ elClass "span" "key-default-pristine" $ do
504+ rndr k
505+ text ": "
506+ pure ()
507+ elClass "span" "value-pristine" $ rndr v'
508+ pure ()
509+ CustomConfigValue (Left (CustomKey v')) -> do
510+ elClass "span" "key-custom-edited" $ do
511+ rndr k
512+ text ": "
513+ pure ()
514+ elClass "span" "value-edited" $ rndr v'
515+ pure ()
516+ CustomConfigValue (Right (CustomValue v')) -> do
517+ elClass "span" "key-default-edited" $ do
518+ rndr k
519+ text ": "
520+ pure ()
521+ elClass "span" "value-edited" $ rndr v'
522+ pure ()
523+ CustomConfigValue (Right (DeletedValue mv)) -> do
524+ elClass "span" "key-deleted" $ do
525+ rndr k
526+ text ": "
527+ pure ()
528+ elClass "span" "value-deleted" $ rndr `traverse_` mv
529+ pure ()
530+
531+untilReadyEv' ::
532+ (Adjustable t m, PostBuild t m, MonadHold t m) =>
533+ m a ->
534+ m (Event t b) ->
535+ m (Event t b)
536+untilReadyEv' m m' = do
537+ (_, bEvEv) <- untilReady m m'
538+ switchHold never bEvEv
539+
540+untilReady' ::
541+ (Adjustable t m, PostBuild t m) =>
542+ m a ->
543+ m b ->
544+ m (Event t b)
545+untilReady' m m' = do
546+ (_, bEv) <- untilReady m m'
547+ pure bEv
548+
549+runWithReplace' :: Adjustable t m => m a -> Event t (m b) -> m (Event t b)
550+runWithReplace' ma mbEv = do
551+ (_, bEv) <- runWithReplace ma mbEv
552+ pure bEv
553+
554+joinEvM :: (MonadHold t m, Reflex t) => Event t (Event t a) -> m (Event t a)
555+joinEvM = switchHold never
octopod-frontend/src/Frontend/UIKit/Button/Action.hs+3−3
@@ -18,7 +18,7 @@ data ActionButtonConfig t = ActionButtonConfig
1818 { buttonText :: Text
1919 , buttonEnabled :: Dynamic t Bool
2020 , buttonType :: Maybe ActionButtonType
21− , buttonBaseTag :: BaseButtonTag
21+ , buttonBaseTag :: BaseButtonTag t
2222 }
2323 deriving stock (Generic)
2424
@@ -47,7 +47,7 @@ buttonTypeClasses = \case
4747actionButton ::
4848 (DomBuilder t m, PostBuild t m) =>
4949 ActionButtonConfig t ->
50− m (Event t ())
50+ m (Event t (Either () ()))
5151actionButton cfg =
5252 buttonEl
5353 CommonButtonConfig
@@ -58,6 +58,6 @@ actionButton cfg =
5858 , enabledClasses = mempty
5959 , disabledClasses = "action--disabled"
6060 , buttonEnabled = cfg ^. #buttonEnabled
61− , buttonText = pure $ cfg ^. #buttonText
61+ , buttonText = textBuilder $ cfg ^. #buttonText
6262 , buttonBaseTag = cfg ^. #buttonBaseTag
6363 }
octopod-frontend/src/Frontend/UIKit/Button/Common.hs+64−12
@@ -2,48 +2,100 @@ module Frontend.UIKit.Button.Common
22 ( CommonButtonConfig (..),
33 BaseButtonTag (..),
44 buttonEl,
5+ TextBuilder (..),
6+ textBuilder,
57 )
68where
79
8−import Control.Lens
10+import Control.Lens hiding (element)
911import Data.Generics.Labels ()
1012import Data.Map (Map)
13+import Data.Proxy
14+import Data.String
1115import Data.Text (Text)
16+import qualified Data.Text as T
1217import Frontend.Classes
1318import GHC.Generics (Generic)
1419import Reflex.Dom
1520
21+textBuilder :: Text -> TextBuilder t
22+textBuilder t = TextBuilder $ text t
23+
24+newtype TextBuilder t = TextBuilder
25+ { unTextBuilder ::
26+ forall m.
27+ ( DomBuilder t m
28+ , PostBuild t m
29+ ) =>
30+ m ()
31+ }
32+
33+instance IsString (TextBuilder t) where
34+ fromString t = TextBuilder (text . T.pack $ t)
35+
1636data CommonButtonConfig t = CommonButtonConfig
1737 { constantClasses :: Dynamic t Classes
1838 , enabledClasses :: Classes
1939 , disabledClasses :: Classes
2040 , buttonEnabled :: Dynamic t Bool
21− , buttonText :: Dynamic t Text
22− , buttonBaseTag :: BaseButtonTag
41+ , buttonText :: TextBuilder t
42+ , buttonBaseTag :: BaseButtonTag t
2343 }
2444 deriving stock (Generic)
2545
26−data BaseButtonTag = ButtonTag | ATag Text
46+data BaseButtonTag t = ButtonTag | ATag (Dynamic t Text)
2747
28−baseTag :: BaseButtonTag -> (Text, Map Text Text)
29−baseTag ButtonTag = ("button", "type" =: "button")
30−baseTag (ATag url) = ("a", "href" =: url <> "target" =: "_blank")
48+baseTag :: Reflex t => BaseButtonTag t -> (Text, Dynamic t (Map Text Text))
49+baseTag ButtonTag = ("button", pure $ "type" =: "button")
50+baseTag (ATag urlDyn) =
51+ ( "a"
52+ , urlDyn <&> \url -> "href" =: url <> "target" =: "_blank"
53+ )
3154
3255buttonEl ::
56+ forall m t.
3357 (DomBuilder t m, PostBuild t m) =>
3458 CommonButtonConfig t ->
35− m (Event t ())
59+ m (Event t (Either () ()))
3660buttonEl cfg = do
37− let (t, staticAttrs) = baseTag (cfg ^. #buttonBaseTag)
61+ let (t, tagAttrsDyn) = baseTag (cfg ^. #buttonBaseTag)
3862 attrsDyn = do
3963 enabled <- cfg ^. #buttonEnabled
4064 let (enabledClasses, enabledAttrs) = case enabled of
4165 True -> (cfg ^. #enabledClasses, mempty)
4266 False -> (cfg ^. #disabledClasses, "disabled" =: "")
4367 cs <- cfg ^. #constantClasses
68+ tagAttrs <- tagAttrsDyn
4469 pure $
45− staticAttrs
70+ tagAttrs
4671 <> "class" =: destructClasses (enabledClasses <> cs)
4772 <> enabledAttrs
48− (bEl, _) <- elDynAttr' t attrsDyn $ dynText $ cfg ^. #buttonText
49− pure $ domEvent Click bEl
73+ modAttrs <- dynamicAttributesToModifyAttributes attrsDyn
74+ let elCfg =
75+ (def @(ElementConfig EventResult t (DomBuilderSpace m)))
76+ & elementConfig_modifyAttributes .~ fmapCheap mapKeysToAttributeName modAttrs
77+ & elementConfig_eventSpec
78+ %~~ addEventSpecFlags
79+ (Proxy @(DomBuilderSpace m))
80+ Contextmenu
81+ ( const
82+ EventFlags
83+ { _eventFlags_propagation = Propagation_StopImmediate
84+ , _eventFlags_preventDefault = False
85+ }
86+ )
87+ & elementConfig_eventSpec
88+ %~~ addEventSpecFlags
89+ (Proxy @(DomBuilderSpace m))
90+ Click
91+ ( const
92+ EventFlags
93+ { _eventFlags_propagation = Propagation_StopImmediate
94+ , _eventFlags_preventDefault = False
95+ }
96+ )
97+ (bEl, _) <- element t elCfg (unTextBuilder (cfg ^. #buttonText))
98+ pure $ leftmost [Left <$> domEvent Click bEl, Right <$> domEvent Contextmenu bEl]
99+
100+(%~~) :: ASetter' s a -> (a -> a) -> s -> s
101+(%~~) = (%~)
octopod-frontend/src/Frontend/UIKit/Button/Dash.hs+2−2
@@ -54,7 +54,7 @@ buttonTypeClasses = \case
5454dashButton ::
5555 (DomBuilder t m, PostBuild t m) =>
5656 DashButtonConfig t ->
57− m (Event t ())
57+ m (Event t (Either () ()))
5858dashButton cfg =
5959 buttonEl
6060 CommonButtonConfig
@@ -66,6 +66,6 @@ dashButton cfg =
6666 , enabledClasses = mempty
6767 , disabledClasses = "dash--disabled"
6868 , buttonEnabled = cfg ^. #buttonEnabled
69− , buttonText = pure $ cfg ^. #buttonText
69+ , buttonText = textBuilder $ cfg ^. #buttonText
7070 , buttonBaseTag = ButtonTag
7171 }
octopod-frontend/src/Frontend/UIKit/Button/Expander.hs+3−2
@@ -10,6 +10,7 @@ where
1010import Control.Lens
1111import Control.Monad.Fix
1212import Data.Default
13+import Data.Functor
1314import Data.Generics.Labels ()
1415import Data.Text (Text)
1516import Frontend.Classes
@@ -65,7 +66,7 @@ expanderButton ::
6566 ExpanderButtonConfig t ->
6667 m (Dynamic t ExpanderState)
6768expanderButton cfg = mdo
68− stateDyn <- foldDyn (\() -> toggleState) (cfg ^. #buttonInitialState) toggleEv
69+ stateDyn <- foldDyn (\() -> toggleState) (cfg ^. #buttonInitialState) (toggleEv $> ())
6970 let constantClasses =
7071 "expander"
7172 <> maybe mempty buttonTypeClasses (cfg ^. #buttonType)
@@ -79,7 +80,7 @@ expanderButton cfg = mdo
7980 , enabledClasses = mempty
8081 , disabledClasses = mempty
8182 , buttonEnabled = pure True
82− , buttonText = cfg ^. #buttonText
83+ , buttonText = TextBuilder $ dynText $ cfg ^. #buttonText
8384 , buttonBaseTag = ButtonTag
8485 }
8586 pure stateDyn
octopod-frontend/src/Frontend/UIKit/Button/Large.hs+3−3
@@ -23,7 +23,7 @@ data LargeButtonConfig t = LargeButtonConfig
2323 , buttonType :: Dynamic t (Maybe LargeButtonType)
2424 , buttonPriority :: LargeButtonPriority
2525 , buttonStyle :: LargeButtonStyle
26− , buttonBaseTag :: BaseButtonTag
26+ , buttonBaseTag :: BaseButtonTag t
2727 }
2828 deriving stock (Generic)
2929
@@ -77,7 +77,7 @@ buttonTypeClasses LoadingLargeButtonType = "button--save-loading"
7777largeButton ::
7878 (DomBuilder t m, PostBuild t m) =>
7979 LargeButtonConfig t ->
80− m (Event t ())
80+ m (Event t (Either () ()))
8181largeButton cfg =
8282 buttonEl
8383 CommonButtonConfig
@@ -91,6 +91,6 @@ largeButton cfg =
9191 , enabledClasses = mempty
9292 , disabledClasses = "button--disabled"
9393 , buttonEnabled = cfg ^. #buttonEnabled
94− , buttonText = pure $ cfg ^. #buttonText
94+ , buttonText = textBuilder $ cfg ^. #buttonText
9595 , buttonBaseTag = cfg ^. #buttonBaseTag
9696 }
octopod-frontend/src/Frontend/UIKit/Button/Sort.hs+2−2
@@ -41,7 +41,7 @@ buttonSortStateClasses = \case
4141sortButton ::
4242 (DomBuilder t m, PostBuild t m) =>
4343 SortButtonConfig t ->
44− m (Event t ())
44+ m (Event t (Either () ()))
4545sortButton cfg =
4646 buttonEl
4747 CommonButtonConfig
@@ -53,6 +53,6 @@ sortButton cfg =
5353 , enabledClasses = mempty
5454 , disabledClasses = mempty
5555 , buttonEnabled = cfg ^. #buttonEnabled
56− , buttonText = pure $ cfg ^. #buttonText
56+ , buttonText = textBuilder $ cfg ^. #buttonText
5757 , buttonBaseTag = ButtonTag
5858 }
octopod-frontend/src/Frontend/UIKit/Button/Static.hs+25−12
@@ -4,13 +4,14 @@ module Frontend.UIKit.Button.Static
44 closePopupButton,
55 deleteOverrideButton,
66 undoOverrideButton,
7+ dropButton,
78 )
89where
910
1011import Frontend.UIKit.Button.Common
1112import Reflex.Dom
1213
13−closeNotificationButton :: (DomBuilder t m, PostBuild t m) => m (Event t ())
14+closeNotificationButton :: (DomBuilder t m, PostBuild t m) => m (Event t (Either () ()))
1415closeNotificationButton =
1516 buttonEl
1617 CommonButtonConfig
@@ -18,11 +19,11 @@ closeNotificationButton =
1819 , enabledClasses = mempty
1920 , disabledClasses = mempty
2021 , buttonEnabled = pure True
21− , buttonText = pure ""
22+ , buttonText = ""
2223 , buttonBaseTag = ButtonTag
2324 }
2425
25−closeClassicPopupButton :: (DomBuilder t m, PostBuild t m) => m (Event t ())
26+closeClassicPopupButton :: (DomBuilder t m, PostBuild t m) => m (Event t (Either () ()))
2627closeClassicPopupButton =
2728 buttonEl
2829 CommonButtonConfig
@@ -30,11 +31,11 @@ closeClassicPopupButton =
3031 , enabledClasses = mempty
3132 , disabledClasses = mempty
3233 , buttonEnabled = pure True
33− , buttonText = pure ""
34+ , buttonText = ""
3435 , buttonBaseTag = ButtonTag
3536 }
3637
37−closePopupButton :: (DomBuilder t m, PostBuild t m) => m (Event t ())
38+closePopupButton :: (DomBuilder t m, PostBuild t m) => m (Event t (Either () ()))
3839closePopupButton =
3940 buttonEl
4041 CommonButtonConfig
@@ -42,30 +43,42 @@ closePopupButton =
4243 , enabledClasses = mempty
4344 , disabledClasses = mempty
4445 , buttonEnabled = pure True
45− , buttonText = pure ""
46+ , buttonText = ""
4647 , buttonBaseTag = ButtonTag
4748 }
4849
49−deleteOverrideButton :: (DomBuilder t m, PostBuild t m) => m (Event t ())
50+deleteOverrideButton :: (DomBuilder t m, PostBuild t m) => m (Event t (Either () ()))
5051deleteOverrideButton =
5152 buttonEl
5253 CommonButtonConfig
53− { constantClasses = pure "overrides__delete spot spot--cancel"
54+ { constantClasses = pure "editable-row__delete spot spot--cancel"
5455 , enabledClasses = mempty
5556 , disabledClasses = mempty
5657 , buttonEnabled = pure True
57− , buttonText = pure ""
58+ , buttonText = ""
5859 , buttonBaseTag = ButtonTag
5960 }
6061
61−undoOverrideButton :: (DomBuilder t m, PostBuild t m) => m (Event t ())
62+undoOverrideButton :: (DomBuilder t m, PostBuild t m) => m (Event t (Either () ()))
6263undoOverrideButton =
6364 buttonEl
6465 CommonButtonConfig
65− { constantClasses = pure "overrides__delete spot spot--undo"
66+ { constantClasses = pure "editable-row__delete spot spot--undo"
6667 , enabledClasses = mempty
6768 , disabledClasses = mempty
6869 , buttonEnabled = pure True
69− , buttonText = pure ""
70+ , buttonText = ""
71+ , buttonBaseTag = ButtonTag
72+ }
73+
74+dropButton :: (DomBuilder t m, PostBuild t m) => m (Event t (Either () ()))
75+dropButton =
76+ buttonEl
77+ CommonButtonConfig
78+ { constantClasses = pure "drop__handler"
79+ , enabledClasses = mempty
80+ , disabledClasses = mempty
81+ , buttonEnabled = pure True
82+ , buttonText = "Actions"
7083 , buttonBaseTag = ButtonTag
7184 }
octopod-frontend/src/Frontend/UIKit/Button/Tree.hsnew file+84
@@ -0,0 +1,84 @@
1+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
2+
3+{-# HLINT ignore "Use newtype instead of data" #-}
4+module Frontend.UIKit.Button.Tree
5+ ( treeButton,
6+ TreeButtonConfig (..),
7+ TextBuilder (..),
8+ TreeButtonStatus (..),
9+ TreeButtonApply (..),
10+ )
11+where
12+
13+import Control.Lens
14+import Control.Monad
15+import Control.Monad.Fix
16+import Data.Default
17+import Data.Functor
18+import Data.Generics.Labels ()
19+import Frontend.UIKit.Button.Common
20+import GHC.Generics (Generic)
21+import Reflex.Dom
22+import Reflex.Network (networkView)
23+
24+data TreeButtonConfig t = TreeButtonConfig
25+ { buttonText :: TextBuilder t
26+ , subtreeHasChanges :: Bool
27+ , visible :: Dynamic t Bool
28+ , -- | From outside
29+ forceState :: Event t Bool
30+ }
31+ deriving stock (Generic)
32+
33+instance Reflex t => Default (TreeButtonConfig t) where
34+ def =
35+ TreeButtonConfig
36+ { buttonText = ""
37+ , subtreeHasChanges = False
38+ , visible = pure True
39+ , forceState = never
40+ }
41+
42+treeButton ::
43+ (DomBuilder t m, PostBuild t m, MonadHold t m, MonadFix m) =>
44+ TreeButtonConfig t ->
45+ -- | (<Open?>, <Click>)
46+ m (Event t (Bool, Either () ()))
47+treeButton cfg = mdo
48+ (traceEvent "treeButton" -> clickedEv) <-
49+ networkView >=> switchHold never $
50+ (cfg ^. #visible) <&> \visible ->
51+ if visible
52+ then
53+ buttonEl
54+ CommonButtonConfig
55+ { constantClasses = do
56+ open <- openDyn
57+ pure $
58+ "collapse__head"
59+ <> (if cfg ^. #subtreeHasChanges then "collapse__head--has-changes" else mempty)
60+ <> (if open then mempty else "collapse--expanded")
61+ , buttonText = cfg ^. #buttonText
62+ , buttonBaseTag = ButtonTag
63+ , buttonEnabled = pure True
64+ , disabledClasses = mempty
65+ , enabledClasses = mempty
66+ }
67+ else pure never
68+ openDyn <-
69+ foldDyn
70+ ( \case
71+ Left () -> not
72+ Right x -> const x
73+ )
74+ False
75+ $ (leftmost [Right <$> cfg ^. #forceState, clickedEv $> Left ()])
76+ pure $ coincidence $ (\click -> (,click) <$> updated openDyn) <$> clickedEv
77+
78+data TreeButtonStatus
79+ = TreeButtonClosed
80+ | TreeButtonOpen
81+
82+data TreeButtonApply
83+ = TreeButtonApplyToSelf
84+ | TreeButtonApplyToSubtree
octopod-frontend/src/Frontend/Utils.hs+173−332
@@ -20,53 +20,45 @@ module Frontend.Utils
2020 DeploymentPageNotification (..),
2121 formatPosixToDateTime,
2222 dropdownWidget,
23− dropdownWidget',
2423 deploymentConfigProgressiveComponents,
25− deploymentConfigProgressive,
24+ -- deploymentConfigProgressive,
2625 holdClearingWith,
2726 unitEv,
2827 (<&&>),
29− ProgressiveFullConfig (..),
3028 RequestErrorHandler,
31− deploymentOverridesWidget,
32− deploymentOverridesWidgetSearched,
33− applicationOverridesWidget,
34− applicationOverridesWidgetSearched,
3529 debounceDyn,
30+ catchReturns,
31+ holdDynMaybe,
32+ constructWorkingOverridesEv,
3633 )
3734where
3835
3936import Common.Types as CT
37+import Control.Arrow
4038import Control.Lens
4139import Control.Monad
4240import Control.Monad.Reader
43−import Control.Monad.State.Strict
44−import qualified Data.Foldable as F
41+import qualified Data.ConfigTree as CT
4542import Data.Functor
4643import Data.Generics.Labels ()
47−import Data.List.NonEmpty (NonEmpty)
44+import Data.Generics.Sum
4845import qualified Data.Map as M
49−import Data.Map.Monoidal.Strict (MonoidalMap)
50−import qualified Data.Map.Monoidal.Strict as MM
51−import Data.Maybe (fromMaybe, isJust, isNothing)
52−import Data.Monoid
53−import Data.Text as T (Text, null, pack)
54−import Data.Text.Search
46+import Data.Maybe (fromMaybe)
47+import Data.String
48+import Data.Text as T (Text, null, pack, strip)
5549import Data.Time
5650import Data.UniqMap
5751import Data.Unique
5852import Data.Witherable
5953import Data.WorkingOverrides
54+import Debug.Trace
6055import Frontend.API
6156import Frontend.GHCJS
6257import Frontend.UIKit
63−import GHC.Generics (Generic)
6458import GHCJS.DOM
6559import GHCJS.DOM.Element as DOM
6660import GHCJS.DOM.EventM (on, target)
6761import GHCJS.DOM.GlobalEventHandlers as Events (click)
68−import GHCJS.DOM.Node as DOM
69−import Generic.Data (Generically (..))
7062import Reflex.Dom as R
7163import Reflex.Dom.Renderable
7264import Reflex.Network
@@ -83,49 +75,30 @@ elementClick = do
8375 doc <- currentDocumentUnchecked
8476 wrapDomEvent doc (`on` Events.click) $ ClickedElement <$> target
8577
86−-- | Dropdown widget which binds its own @document click@ event.
87−dropdownWidget ::
88− MonadWidget t m =>
89− -- | Button widget which opens the dropdown widget.
90− m () ->
91− -- | Widget with the dropdown list.
92− -- Returns an event carrying the user's selection.
93− m (Event t a) ->
94− m (Event t a)
95−dropdownWidget btn body = mdo
96− clickedEl <- elementClick
97− dropdownWidget' clickedEl btn body
98−
9978-- | Similar to 'dropdownWidget' but uses @document click@ event that may be
10079-- shared between other widgets.
101−dropdownWidget' ::
80+dropdownWidget ::
10281 MonadWidget t m =>
103− -- | Document click event that may be shared between other widgets.
104− Event t ClickedElement ->
105− -- | Button widget which opens the dropdown widget.
106− m () ->
10782 -- | Widget with the dropdown list.
10883 -- Returns an event carrying the user's selection.
10984 m (Event t a) ->
11085 m (Event t a)
111−dropdownWidget' clickedEl btn body = mdo
112− clickInsideEv <- performEvent $
113− ffor clickedEl $ \(ClickedElement clicked) ->
114− DOM.contains (_element_raw btnEl) clicked
115− openedDyn <- foldDyn switchState False $ clickInsideEv
116− let switchState ev cur = ev && not cur
117− wrapperClassDyn = ffor openedDyn $ \case
86+dropdownWidget body = mdo
87+ openedDyn <- toggle False $ btnEl
88+ let wrapperClassDyn = ffor openedDyn $ \case
11889 True -> "class" =: "drop drop--actions drop--expanded"
11990 False -> "class" =: "drop drop--actions"
120− (btnEl, (_, wEv)) <- elDynAttr'
91+ (btnEl, wEv) <- elDynAttr
12192 "div"
12293 wrapperClassDyn
12394 $ do
124− btn
125− elDynAttr'
126− "div"
127− (constDyn $ "class" =: "drop__dropdown")
128− body
95+ clickedEv <- dropButton
96+ aEv <-
97+ elDynAttr
98+ "div"
99+ (constDyn $ "class" =: "drop__dropdown")
100+ body
101+ pure (clickedEv, aEv)
129102 pure wEv
130103
131104showT :: Show a => a -> Text
@@ -207,90 +180,46 @@ octopodTextInput clss lbl placeholder val errEv =
207180 elClass "section" "deployment__section" $ do
208181 elClass "h3" "deployment__sub-heading" $ text lbl
209182 elClass "div" "deployment__widget" $ do
210− (value -> valDyn, validDyn) <- octopodTextInput' (pure []) (pure False) (pure clss) placeholder (pure . fromMaybe "" $ val) errEv
183+ (value -> valDyn, validDyn) <- octopodTextInput' (pure []) (pure False) (pure mempty) (pure clss) placeholder (pure . fromMaybe "" $ val) errEv
211184 pure (valDyn, validDyn)
212185
213186-- | Widget that can show and hide overrides if there are more than 3. This
214187-- widget is used in the deployments table and the deployment action table.
215188overridesWidget ::
216− (MonadWidget t m, Renderable te, Ord te) =>
189+ (MonadWidget t m, Renderable te, IsString te, Monoid te) =>
217190 -- | List of overrides.
218191 Overrides' te l ->
219− (Event t () -> m (Event t (DefaultConfig' te l))) ->
220192 m ()
221−overridesWidget ovs getDef = divClass "listing listing--for-text" $ mdo
222− defMDyn <- getDef firstExpand >>= holdDynMaybe
223− dyn_ $
224− expandState <&> \case
225− ExpandedState ->
226− void $
227− networkView $
228− do
229− defM <- defMDyn
230− pure $
231− showNonEditableWorkingOverride (isNothing defM) (isJust defM) RegularNonEditableWorkingOverrideStyle $
232− elemsUniq $ constructWorkingOverrides defM ovs
233− ContractedState ->
234− let ovsList = elemsUniq $ constructWorkingOverrides Nothing ovs
235− in showNonEditableWorkingOverride False False RegularNonEditableWorkingOverrideStyle $
236− take 3 ovsList
237−
238− expandState <-
239− expanderButton
240− ExpanderButtonConfig
241− { buttonText = do
242− s <- expandState
243− pure $ case s of
244− ExpandedState -> "Hide default configuration"
245− ContractedState -> "Show full configuration"
246− , buttonInitialState = ContractedState
247− , buttonType = Just ListingExpanderButton
248− , buttonStyle = RegularExpanderButtonStyle
249− }
250− firstExpand <- headE $ ($> ()) $ ffilter (== ExpandedState) $ updated expandState
193+overridesWidget (Overrides ovs) = mdo
194+ let l = CT.toFlatList ovs
195+ expandable = length l > 3
196+
197+ if expandable
198+ then mdo
199+ dyn_ $
200+ expandState <&> \s -> do
201+ let l' = case s of
202+ ExpandedState -> l
203+ ContractedState -> take 3 l
204+ showFlatConfig l'
205+ pure ()
206+
207+ expandState <-
208+ expanderButton
209+ ExpanderButtonConfig
210+ { buttonText = do
211+ s <- expandState
212+ pure $ case s of
213+ ExpandedState -> "Hide default configuration"
214+ ContractedState -> "Show full configuration"
215+ , buttonInitialState = ContractedState
216+ , buttonType = Just ListingExpanderButton
217+ , buttonStyle = RegularExpanderButtonStyle
218+ }
219+ pure ()
220+ else showFlatConfig l
251221 pure ()
252222
253−deploymentOverridesWidget ::
254− (MonadWidget t m) =>
255− RequestErrorHandler t m ->
256− Overrides 'DeploymentLevel ->
257− m ()
258−deploymentOverridesWidget hReq depOvs =
259− deploymentOverridesWidgetSearched hReq (wrapResult depOvs)
260−
261−deploymentOverridesWidgetSearched ::
262− (MonadWidget t m) =>
263− RequestErrorHandler t m ->
264− Overrides' SearchResult 'DeploymentLevel ->
265− m ()
266−deploymentOverridesWidgetSearched hReq depOvs =
267− overridesWidget depOvs $ (fmap . fmap . fmap) wrapResult $ defaultDeploymentOverrides >=> hReq
268−
269−applicationOverridesWidget ::
270− MonadWidget t m =>
271− RequestErrorHandler t m ->
272− Overrides 'DeploymentLevel ->
273− Overrides 'ApplicationLevel ->
274− m ()
275−applicationOverridesWidget hReq depOvs appOvs =
276− applicationOverridesWidgetSearched hReq (wrapResult depOvs) (wrapResult appOvs)
277−
278−applicationOverridesWidgetSearched ::
279− MonadWidget t m =>
280− RequestErrorHandler t m ->
281− Overrides' SearchResult 'DeploymentLevel ->
282− Overrides' SearchResult 'ApplicationLevel ->
283− m ()
284−applicationOverridesWidgetSearched hReq depOvs appOvs =
285− overridesWidget appOvs $ \fire -> do
286− depDefEv <- defaultDeploymentOverrides fire >>= hReq
287− fmap switchDyn $
288− (fmap . fmap . fmap) wrapResult $
289− networkHold (pure never) $
290− depDefEv <&> \depDef -> do
291− pb <- getPostBuild
292− defaultApplicationOverrides (pure $ Right $ applyOverrides (deSearch depOvs) depDef) pb >>= hReq
293−
294223-- | Type of notification at the top of pages.
295224data DeploymentPageNotification
296225 = -- | Message text for a \"success\" events.
@@ -344,9 +273,11 @@ deploymentPopupBody ::
344273 m (Dynamic t (Maybe DeploymentUpdate))
345274deploymentPopupBody hReq defAppOv defDepOv errEv = mdo
346275 pb <- getPostBuild
347− (defDepEv, defApp, depCfgEv) <- deploymentConfigProgressiveComponents hReq deploymentOvsDynDebounced
348− defAppM <- holdClearingWith defApp (unitEv deploymentOvsDyn)
349− defDep <- holdDynMaybe defDepEv
276+ (defDepEv, defAppEv, depCfgEv) <-
277+ deploymentConfigProgressiveComponents hReq deploymentOvsDynDebounced
278+
279+ defAppEvM <- holdClearingWith defAppEv (unitEv deploymentOvsDyn)
280+
350281 depKeys <- deploymentOverrideKeys pb >>= hReq
351282
352283 let commandResponseEv = fmapMaybe commandResponse errEv
@@ -354,43 +285,47 @@ deploymentPopupBody hReq defAppOv defDepOv errEv = mdo
354285
355286 let holdDCfg ::
356287 Dynamic t [Text] ->
357− Dynamic t (Maybe (DefaultConfig l)) ->
288+ (DefaultConfig l) ->
358289 Overrides l ->
359− m (Dynamic t (Overrides l), Dynamic t (Overrides l), Dynamic t Bool)
360− holdDCfg values dCfgDyn ovs = mdo
361− ovsDyn <- holdDyn ovs ovsEv
362− ovsDynDebounced <- holdDyn ovs ovsEvDebounced
363− x <- attachDyn (current ovsDyn) dCfgDyn
364− res <- dyn (x <&> \(ovs', dCfg) -> envVarsInput values dCfg ovs')
365− ovsEv <- switchHold never (fst <$> res)
366− ovsEvDebounced <- debounce 2 ovsEv
367− isValid <- join <$> holdDyn (pure True) (snd <$> res)
368− pure (ovsDyn, ovsDynDebounced, isValid)
290+ m (Dynamic t (Overrides l), Dynamic t (Overrides l))
291+ holdDCfg (traceDyn "values" -> values) cfgDyn@(DefaultConfig ct) ovs = mdo
292+ let ovs' = constructWorkingOverrides cfgDyn ovs
293+ lookupOverride k = CT.lookup (CT.deconstructConfigKey k) ct
294+ (resCfgDyn :: Dynamic t [Dynamic t WorkingOverride]) <- envVarsInput values lookupOverride ovs'
295+ (resEvDebounced :: Event t [Dynamic t WorkingOverride]) <- debounce 2 $ updated resCfgDyn
296+ let resDyn' = join . fmap destructWorkingOverridesDyn $ resCfgDyn
297+ debouncedResDyn <- fmap join . holdDyn (pure ovs) . fmap destructWorkingOverridesDyn $ resEvDebounced
298+ pure (resDyn', debouncedResDyn)
369299
370300 depKeysDyn <- holdDyn [] depKeys
371− (deploymentOvsDyn, deploymentOvsDynDebounced, depValidDyn) <-
372− deploymentSection "Deployment configuration" $ holdDCfg depKeysDyn defDep defDepOv
301+
302+ (join . fmap fst &&& join . fmap snd -> (deploymentOvsDyn, deploymentOvsDynDebounced)) <-
303+ deploymentSection "Deployment configuration" $
304+ networkHold (loadingOverrides $> (pure defDepOv, pure defDepOv)) $
305+ defDepEv <&> \defDep -> holdDCfg depKeysDyn defDep defDepOv
373306
374307 appKeys <- waitForValuePromptly depCfgEv $ \deploymentCfg -> do
375308 pb' <- getPostBuild
376309 applicationOverrideKeys (pure $ Right deploymentCfg) pb' >>= hReq >>= immediateNothing
310+
377311 appKeysDyn <- holdDyn [] $ catMaybes appKeys
378− (applicationOvsDyn, _, appValidDyn) <- deploymentSection "App configuration" $ holdDCfg appKeysDyn defAppM defAppOv
312+ (join -> applicationOvsMDyn) <-
313+ deploymentSection "App configuration" >=> holdDyn (pure Nothing) . fmap sequenceA $
314+ networkView $
315+ defAppEvM <&> \case
316+ Nothing -> loadingOverrides $> Nothing
317+ Just defApp -> Just . fst <$> holdDCfg appKeysDyn defApp defAppOv
379318
380319 pure $ do
381− depValid <- depValidDyn
382− appValid <- appValidDyn
383− if depValid && appValid
384− then do
385− depCfg <- deploymentOvsDyn
386− appOvs <- applicationOvsDyn
387− pure $
388− Just $
389− DeploymentUpdate
390− { appOverrides = appOvs
391− , deploymentOverrides = depCfg
392− }
393− else pure Nothing
320+ depCfg <- deploymentOvsDyn
321+ appOvsM <- applicationOvsMDyn
322+ pure $ do
323+ appOvs <- appOvsM
324+ Just $
325+ DeploymentUpdate
326+ { appOverrides = appOvs
327+ , deploymentOverrides = depCfg
328+ }
394329
395330deploymentConfigProgressiveComponents ::
396331 MonadWidget t m =>
@@ -417,36 +352,17 @@ deploymentConfigProgressiveComponents hReq depOvsDyn = do
417352 defaultApplicationOverrides (pure $ Right depCfg) pb' >>= hReq
418353 pure (defDepEv, defAppEv, depCfgEv)
419354
420−deploymentConfigProgressive ::
421− MonadWidget t m =>
422− RequestErrorHandler t m ->
423− Dynamic t (Overrides 'DeploymentLevel) ->
424− Dynamic t (Overrides 'ApplicationLevel) ->
425− m (Dynamic t ProgressiveFullConfig)
426−deploymentConfigProgressive hReq depOvsDyn appOvsDyn = do
427− (depCfgEv, defAppEv, _) <- deploymentConfigProgressiveComponents hReq depOvsDyn
428− defAppMDyn <- holdDynMaybe defAppEv
429− defCfgMDyn <- holdDynMaybe depCfgEv
430− pure $ do
431− defAppM <- defAppMDyn
432− appOvs <- appOvsDyn
433− defDepM <- defCfgMDyn
434− depOvs <- depOvsDyn
435− pure
436− ProgressiveFullConfig
437− { appConfig = constructWorkingOverrides defAppM appOvs
438− , appConfigLoading = isNothing defAppM
439− , depConfig = constructWorkingOverrides defDepM depOvs
440− , depConfigLoading = isNothing defDepM
441− }
442−
443−data ProgressiveFullConfig = ProgressiveFullConfig
444− { appConfig :: WorkingOverrides
445− , appConfigLoading :: Bool
446− , depConfig :: WorkingOverrides
447− , depConfigLoading :: Bool
448− }
449− deriving stock (Generic)
355+constructWorkingOverridesEv ::
356+ MonadWidget t m => Event t (DefaultConfig l) -> Dynamic t (Overrides l) -> m (Event t WorkingOverrides)
357+constructWorkingOverridesEv defCfgEv ovsDyn = do
358+ defCfgMDyn <- holdDynMaybe defCfgEv
359+ pure $
360+ catMaybes . updated $ do
361+ defCfgM <- defCfgMDyn
362+ ovs <- ovsDyn
363+ pure $ do
364+ defCfg <- defCfgM
365+ pure $ constructWorkingOverrides defCfg ovs
450366
451367waitForValuePromptly :: (MonadHold t m, Adjustable t m) => Event t x -> (x -> m (Event t y)) -> m (Event t y)
452368waitForValuePromptly ev f = fmap switchPromptlyDyn $ networkHold (pure never) $ f <$> ev
@@ -467,9 +383,14 @@ wrapRequestErrors f = mdo
467383 (x, ev :: Event t (PatchMap Unique Text)) <- runEventWriterT $
468384 f $ \reqEv -> do
469385 k <- liftIO newUnique
470− tellEvent $ fmapCheap (PatchMap . M.singleton k . reqErrorBody) reqEv
386+ tellEvent $ fmap (PatchMap . M.singleton k . fmap catchEmptyErrors . reqErrorBody) reqEv
471387 pure $ fmapMaybeCheap reqSuccess reqEv
472388 pure x
389+ where
390+ catchEmptyErrors t
391+ | (T.null . T.strip) t =
392+ "Something went wrong, but no explanation was provided."
393+ catchEmptyErrors t = t
473394
474395-- | The widget used to display errors.
475396errorHeader ::
@@ -477,24 +398,23 @@ errorHeader ::
477398 -- | Message text.
478399 Dynamic t Text ->
479400 m ()
480−errorHeader appErr = do
481− divClass "deployment__output notification notification--danger" $ do
401+errorHeader appErr =
402+ elClass "pre" "deployment__output notification notification--danger" $
482403 dynText appErr
483404
484405-- | Widget with override fields. This widget supports adding and
485406-- removing key-value pairs.
486407envVarsInput ::
487− forall l t m.
408+ forall t m.
488409 MonadWidget t m =>
489410 Dynamic t [Text] ->
490− Maybe (DefaultConfig l) ->
411+ (Text -> Maybe Text) ->
491412 -- | Initial deployment overrides.
492− Overrides l ->
493− -- | Updated deployment overrides.
494− m (Event t (Overrides l), Dynamic t Bool)
495−envVarsInput values dCfg ovs = mdo
496− envsDyn <- foldDyn appEndo (constructWorkingOverrides dCfg ovs) $ leftmost [addEv, updEv]
497− let addEv = clickEv $> Endo (fst . insertUniqStart newWorkingOverride)
413+ WorkingOverrides ->
414+ -- | Updated deployment overrides. [(<full deconstructed key>, <value>)]
415+ m (Dynamic t [Dynamic t WorkingOverride])
416+envVarsInput (traceDyn "values" -> values) lookupOverride ovs = divClass "padded" $ mdo
417+ let initialConfig = CT.markValues ovs
498418 clickEv <-
499419 dashButton
500420 DashButtonConfig
@@ -503,143 +423,65 @@ envVarsInput values dCfg ovs = mdo
503423 , buttonType = Just AddDashButtonType
504424 , buttonStyle = OverridesDashButtonStyle
505425 }
506− let workingOverridesWithErrors = validateWorkingOverrides . uniqMap <$> envsDyn
507− isValid = all ((== mempty) . snd) <$> workingOverridesWithErrors
508− updEv <-
509− switchDyn . fmap F.fold
510− <$> listWithKey
511− workingOverridesWithErrors
512− (\i x -> fmap (performUserOverrideAction (lookupDefaultConfig <$> dCfg) i) <$> envVarInput values x)
513− let addingIsEnabled = all (\(WorkingOverrideKey _ x, _) -> not . T.null $ x) . elemsUniq <$> envsDyn
514− case dCfg of
515− Just _ -> pure ()
516− Nothing -> loadingOverrides
517− pure (updated $ destructWorkingOverrides <$> envsDyn, isValid)
518−
519−validateWorkingOverrides ::
520− forall f.
521− Traversable f =>
522− f WorkingOverride ->
523− f (WorkingOverride, OverrideErrors)
524−validateWorkingOverrides overrides =
525− let (result, keyOccurrences :: MonoidalMap Text (Sum Int)) =
526− flip runState mempty $ forM overrides \override@(WorkingOverrideKey _ key, value') -> do
527− case value' of
528− WorkingDeletedValue _ -> pure ()
529− _ -> modify (<> MM.singleton key (Sum 1))
530− pure . (override,) . mconcat $
531− [ case MM.lookup key keyOccurrences of
532− Just (Sum n)
533− | n > 1 ->
534− overrideKeyErrors "You can not use the same key multiple times."
535− _ -> mempty
536− , if T.null key
537− then overrideKeyErrors "Keys can not be empty."
538− else mempty
539− , case value' of
540− WorkingCustomValue "" -> overrideValueErrors "Values can not be empty."
541− WorkingCustomValue _ -> mempty
542− WorkingDefaultValue _ -> mempty
543− WorkingDeletedValue _ -> mempty
544− ]
545− in result
546−
547−data OverrideErrors = OverrideErrors
548− { keyErrors :: Maybe (NonEmpty Text)
549− , valueErrors :: Maybe (NonEmpty Text)
550− }
551− deriving stock (Generic, Eq)
552− deriving (Semigroup, Monoid) via Generically OverrideErrors
553−
554−overrideKeyErrors :: Text -> OverrideErrors
555−overrideKeyErrors x = mempty {keyErrors = Just $ pure x}
556−
557−overrideValueErrors :: Text -> OverrideErrors
558−overrideValueErrors x = mempty {valueErrors = Just $ pure x}
559−
560−-- | Widget for entering a key-value pair. The updated overrides list is
561−-- written to the 'EventWriter'.
426+ let emptyItem = ("", CustomConfigValue $ Left $ CustomKey "")
427+ addedItemsCount <- foldDyn (const (fst . insertUniqStart ())) emptyUniqKeyMap clickEv
428+ addedOvsListDyn <- (fmap . fmap) (fmap snd . M.toList)
429+ <$> list (uniqMap <$> addedItemsCount)
430+ $ \_ ->
431+ envVarInput lookupOverride values emptyItem
432+ treeResCfg <-
433+ showOverrideTree
434+ ((== Just True) . fmap (T.null . T.strip) . getConfigValueText . snd)
435+ (isn't (_Ctor' @"DefaultConfigValue") . snd)
436+ (\_ v -> v)
437+ (\a _ _ -> envVarInput lookupOverride values (trace "a" a))
438+ initialConfig
439+ let resCfg :: Dynamic t [Dynamic t (Text, ConfigValue Text)]
440+ resCfg = (<> treeResCfg) <$> addedOvsListDyn
441+ addingIsEnabled = join $ fmap and . sequenceA . (fmap . fmap) (not . T.null . fst) <$> resCfg
442+ pure resCfg
443+
444+-- | Widget for entering a key-value pair.
562445envVarInput ::
563446 (MonadWidget t m) =>
447+ (Text -> Maybe Text) ->
564448 -- | The key values to suggest to the user
565449 Dynamic t [Text] ->
566450 -- | Current variable key and value.
567− Dynamic t (WorkingOverride, OverrideErrors) ->
568− m (Event t UserOverrideAction)
569−envVarInput values val = do
570− let kDyn = val <&> fst <&> \(WorkingOverrideKey _ x, _) -> x
571− -- Either <override present> <override deleted>
572− d <-
573− eitherDyn $
574− val <&> snd . fst <&> \case
575− WorkingCustomValue v -> Right (v, EditedOverrideFieldType)
576− WorkingDefaultValue v -> Right (v, DefaultOverrideFieldType)
577− WorkingDeletedValue v -> Left v
578− networkView >=> switchHold never $
579− d <&> \case
580− Right vtDyn -> do
581− let (vDyn, vTypeDyn) = splitDynPure vtDyn
582− (keyTextDyn, valTextDyn, closeEv) <-
583− overrideField
584− values
585− OverrideField
586− { fieldValue = kDyn
587− , fieldError = keyErrors . snd <$> val
588− , fieldDisabled =
589− val <&> fst <&> \(WorkingOverrideKey t _, _) -> t == DefaultWorkingOverrideKey
590− , fieldType =
591− val <&> fst <&> \(WorkingOverrideKey t _, _) -> case t of
592− CustomWorkingOverrideKey -> EditedOverrideFieldType
593− DefaultWorkingOverrideKey -> DefaultOverrideFieldType
594− }
595− OverrideField
596− { fieldValue = vDyn
597− , fieldError = valueErrors . snd <$> val
598− , fieldDisabled = pure False
599− , fieldType = vTypeDyn
600− }
451+ WorkingOverride ->
452+ m (Dynamic t WorkingOverride)
453+envVarInput f keys val = mdo
454+ value' <- holdDyn val (leftmost [buttonValue, updated inputValue])
455+ (keyTextDyn, valTextDyn, buttonPressEv) <- configField keys value'
456+ let inputValue = do
457+ v <- valTextDyn
458+ k <- keyTextDyn
601459 pure $
602− leftmost
603− [ UpdateKey <$> updated keyTextDyn
604− , UpdateValue <$> updated valTextDyn
605− , closeEv $> DeleteOverride
606− ]
607− Left vDyn -> do
608− restoreEv <-
609− networkView >=> switchHold never $ do
610− v <- vDyn
611− k <- kDyn
612− pure $ deletedOverride k v
613− pure $
614− flip push restoreEv $ \() -> do
615− v <- sample . current $ vDyn
616− pure $ UpdateValue <$> v
617−
618−data UserOverrideAction = UpdateKey !Text | UpdateValue !Text | DeleteOverride
619−
620−performUserOverrideAction ::
621− Maybe (Text -> Maybe Text) ->
622− Int ->
623− UserOverrideAction ->
624− Endo WorkingOverrides
625−performUserOverrideAction f i (UpdateValue v) = Endo $
626− updateUniq i $ \(k@(WorkingOverrideKey _ kt), _) ->
627− ( k
628− , case f >>= ($ kt) of
629− Just v' | v == v' -> WorkingDefaultValue v
630− _ -> WorkingCustomValue v
631− )
632−performUserOverrideAction _ i (UpdateKey k) = Endo $
633− updateUniq i $
634− \(_, v) -> (WorkingOverrideKey CustomWorkingOverrideKey k, v)
635−performUserOverrideAction f i DeleteOverride = Endo $ \m ->
636− case f of
637− Nothing -> updateUniq i (\(k, _) -> (k, WorkingDeletedValue Nothing)) m
638− Just _ -> case lookupUniq i m of
639− Nothing -> m
640− Just (WorkingOverrideKey DefaultWorkingOverrideKey k, _) ->
641− updateUniq i (const (WorkingOverrideKey DefaultWorkingOverrideKey k, WorkingDeletedValue (f >>= ($ k)))) m
642− Just (WorkingOverrideKey CustomWorkingOverrideKey _, _) -> deleteUniq i m
460+ ( k
461+ , case f k of
462+ Nothing -> CustomConfigValue $ Left $ CustomKey v
463+ Just x | x == v -> DefaultConfigValue v
464+ Just _ -> CustomConfigValue $ Right $ CustomValue v
465+ )
466+ buttonValue = flip push buttonPressEv $ \x ->
467+ Just <$> do
468+ k <- sample . current $ keyTextDyn
469+ (k,) <$> case x of
470+ ConfigFieldButtonClear -> do
471+ v <- case f k of
472+ Nothing -> sample . current $ valTextDyn
473+ Just v -> pure v
474+ pure $ CustomConfigValue $ Right $ DeletedValue $ Just v
475+ ConfigFieldButtonRecover -> do
476+ case f k of
477+ Nothing -> do
478+ -- the value is not in the default config
479+ v <- sample . current $ valTextDyn
480+ pure $ CustomConfigValue $ Left $ CustomKey v
481+ Just v -> do
482+ -- the value is in the default config
483+ pure $ DefaultConfigValue v
484+ pure $ value'
643485
644486holdDynMaybe :: (Reflex t, MonadHold t m) => Event t a -> m (Dynamic t (Maybe a))
645487holdDynMaybe ev = holdDyn Nothing $ fmapCheap Just ev
@@ -649,12 +491,6 @@ immediateNothing ev = do
649491 pb <- getPostBuild
650492 pure $ leftmost [pb $> Nothing, fmapCheap Just ev]
651493
652−attachDyn :: (Reflex t, MonadHold t m) => Behavior t a -> Dynamic t b -> m (Dynamic t (a, b))
653−attachDyn b d = do
654− currD <- sample . current $ d
655− currB <- sample b
656− holdDyn (currB, currD) (attach b $ updated d)
657−
658494holdClearingWith :: (Reflex t, MonadHold t m) => Event t a -> Event t () -> m (Dynamic t (Maybe a))
659495holdClearingWith aEv clear =
660496 holdDyn Nothing $ leftmost [fmapCheap Just aEv, fmapCheap (const Nothing) clear]
@@ -679,3 +515,8 @@ debounceDyn t d = do
679515 currD <- sample . current $ d
680516 ev <- debounce t (updated d)
681517 holdDyn currD ev
518+
519+catchReturns :: (DomBuilder t m, MonadFix m) => (Event t () -> m a) -> m a
520+catchReturns f = mdo
521+ (divEl, a) <- el' "div" $ f $ ($> ()) . ffilter (== 13) $ domEvent Keypress divEl
522+ pure a
octopod-frontend/src/Page/ClassicPopup.hs+3−2
@@ -12,6 +12,7 @@ where
1212
1313import Reflex.Dom
1414
15+import Data.Functor
1516import Data.Generics.Labels ()
1617import Frontend.UIKit
1718
@@ -53,7 +54,7 @@ popupWidget m =
5354 divClass "classic-popup__slot" $ do
5455 (okEv, cancelEv) <- m
5556 closeEv <- closeClassicPopupButton
56− pure $ (okEv, leftmost [cancelEv, closeEv])
57+ pure $ (okEv, leftmost [cancelEv, closeEv $> ()])
5758
5859-- | Popup that requires confirmation of deployment deletion.
5960confirmArchivePopup ::
@@ -77,5 +78,5 @@ confirmArchivePopup showEv txt = do
7778 & #buttonStyle .~~ DialogActionLargeButtonStyle
7879 & #buttonPriority .~~ SecondaryLargeButton
7980 & #buttonText .~~ "Cancel"
80− pure (okEv, cancelEv)
81+ pure (okEv $> (), cancelEv $> ())
8182 classicPopup showEv body
octopod-frontend/src/Page/Deployment.hs+39−34
@@ -5,31 +5,30 @@
55--This module contains the definition of a deployment page.
66module Page.Deployment (deploymentPage) where
77
8−import Control.Lens
9−import Control.Monad
10−import Data.Coerce
11−import Data.Generics.Product (field)
12−import Data.Text as T (Text, pack)
13−import Obelisk.Route.Frontend
14−import Reflex.Dom as R
15−import Servant.Reflex
16−
178import Common.Types as CT
189import Common.Utils
10+import Control.Lens
11+import Control.Monad
1912import Control.Monad.Reader
2013import Data.Align
14+import Data.Coerce
15+import Data.Functor
2116import Data.Generics.Labels ()
17+import Data.Generics.Product (field)
18+import Data.Text as T (Text, pack)
2219import Data.Time
23−import Data.UniqMap
2420import Frontend.API
2521import Frontend.GHCJS
2622import Frontend.Route
2723import Frontend.UIKit
2824import Frontend.Utils
25+import Obelisk.Route.Frontend
2926import Page.ClassicPopup
3027import Page.Elements.Links
3128import Page.Popup.EditDeployment
29+import Reflex.Dom as R
3230import Reflex.Network
31+import Servant.Reflex
3332import Servant.Reflex.Extra
3433
3534-- | The root widget of a deployment page. It requests the deployment data.
@@ -125,7 +124,7 @@ deploymentHead dfiDyn sentEv =
125124 & #buttonStyle .~~ PageActionLargeButtonStyle
126125 & #buttonText .~~ "Recover from archive"
127126 & #buttonEnabled .~~ btnEnabledDyn
128− void $ restoreEndpoint (Right . coerce <$> dname) btnEv
127+ void $ restoreEndpoint (Right . coerce <$> dname) $ btnEv $> ()
129128 pure (never, never)
130129 else mdo
131130 let btnState = not $ isPending . recordedStatus $ dfi ^. field @"status"
@@ -159,10 +158,10 @@ deploymentHead dfiDyn sentEv =
159158 , buttonType = pure $ Just LogsLargeButtonType
160159 , buttonPriority = SecondaryLargeButton
161160 , buttonStyle = PageActionLargeButtonStyle
162− , buttonBaseTag = ATag url
161+ , buttonBaseTag = ATag $ pure url
163162 }
164163 )
165− delEv <- confirmArchivePopup archEv $ do
164+ delEv <- confirmArchivePopup (archEv $> ()) $ do
166165 text "Are you sure you want to archive the"
167166 el "br" blank
168167 dynText dname
@@ -192,7 +191,7 @@ deploymentBody updEv dfiDyn = deploymentBodyWrapper $
192191 wrapRequestErrors $ \hReq -> do
193192 let nameDyn = dfiDyn <^.> dfiName
194193 depDyn = dfiDyn <^.> #deployment
195− cfgDyn <- deploymentConfigProgressive hReq (depDyn <^.> #deploymentOverrides) (depDyn <^.> #appOverrides)
194+ (defDepCfgEv, defAppCfgEv, _) <- deploymentConfigProgressiveComponents hReq (depDyn <^.> #deploymentOverrides)
196195 divClass "deployment__summary" $ do
197196 divClass "deployment__stat" $ do
198197 elClass "b" "deployment__param" $ text "Status"
@@ -213,18 +212,26 @@ deploymentBody updEv dfiDyn = deploymentBodyWrapper $
213212 divClass "deployment__widget" $
214213 divClass "listing" $
215214 void $ simpleList urlsDyn renderMetadataLink
216− void $
217− networkView $
218− cfgDyn <&> \cfg -> do
219− let showVars bL l =
220− divClass "deployment__widget" $
221− showNonEditableWorkingOverride (cfg ^. bL) (not $ cfg ^. bL) LargeNonEditableWorkingOverrideStyle $
222− elemsUniq (cfg ^. l)
223− deploymentSection "Deployment configuration" $ showVars #depConfigLoading #depConfig
224− deploymentSection "App configuration" $ showVars #appConfigLoading #appConfig
215+ deploymentSection "Deployment configuration" $ showVars defDepCfgEv $ depDyn <^.> #deploymentOverrides
216+ deploymentSection "App configuration" $ showVars defAppCfgEv $ depDyn <^.> #appOverrides
225217 deploymentSection "Actions" $
226218 divClass "table table--actions" $
227− actionsTable hReq updEv nameDyn
219+ actionsTable updEv nameDyn
220+
221+showVars ::
222+ MonadWidget t m =>
223+ Event t (DefaultConfig l) ->
224+ Dynamic t (Overrides l) ->
225+ m ()
226+showVars defCfgEv ovsDyn = do
227+ workingOvsEv <- constructWorkingOverridesEv defCfgEv ovsDyn
228+ _ <-
229+ divClass "deployment__widget" $
230+ networkHold nonEditableLoading $
231+ workingOvsEv <&> \x -> do
232+ showNonEditableWorkingOverrideTree (pure x)
233+ pure ()
234+ pure ()
228235
229236-- | Widget with a table of actions that can be performed on a deployment.
230237-- It requests deployment data.
@@ -232,12 +239,11 @@ deploymentBody updEv dfiDyn = deploymentBodyWrapper $
232239-- otherwise it calls 'actionsTableData', passing the received data.
233240actionsTable ::
234241 MonadWidget t m =>
235− RequestErrorHandler t m ->
236242 -- | Event notifying about the need to update data.
237243 Event t () ->
238244 Dynamic t DeploymentName ->
239245 m ()
240−actionsTable hReq updEv nameDyn = do
246+actionsTable updEv nameDyn = do
241247 pb <- getPostBuild
242248 respEv <- infoEndpoint (Right <$> nameDyn) pb
243249 let okEv = join . fmap logs <$> fmapMaybe reqSuccess respEv
@@ -247,7 +253,7 @@ actionsTable hReq updEv nameDyn = do
247253 widgetHold_ actionsTableLoading $
248254 leftmost
249255 [ actionsTableError <$ errEv
250− , actionsTableData hReq updEv nameDyn <$> okEv
256+ , actionsTableData updEv nameDyn <$> okEv
251257 ]
252258
253259-- | Header of the actions table.
@@ -285,25 +291,24 @@ actionsTableError = do
285291-- It updates data every time when the supplied event fires.
286292actionsTableData ::
287293 MonadWidget t m =>
288− RequestErrorHandler t m ->
289294 -- | Event notifying about the need to update data.
290295 Event t () ->
291296 Dynamic t DeploymentName ->
292297 -- | Initial logs.
293298 [DeploymentLog] ->
294299 m ()
295−actionsTableData hReq updEv nameDyn initLogs = do
300+actionsTableData updEv nameDyn initLogs = do
296301 respEv <- infoEndpoint (Right <$> nameDyn) updEv
297302 let okEv = (>>= logs) <$> fmapMaybe reqSuccess respEv
298303 logsDyn <- holdDyn initLogs okEv
299304 el "tbody" $
300305 void $
301306 simpleList logsDyn $ \logDyn -> do
302− dyn_ $ actinRow hReq <$> logDyn
307+ dyn_ $ actionRow <$> logDyn
303308
304309-- | Data row of the actions table.
305−actinRow :: RequestErrorHandler t m -> MonadWidget t m => DeploymentLog -> m ()
306−actinRow hReq DeploymentLog {..} = do
310+actionRow :: MonadWidget t m => DeploymentLog -> m ()
311+actionRow DeploymentLog {..} = do
307312 el "tr" $ do
308313 el "td" $ do
309314 text $ actionToText action
@@ -311,8 +316,8 @@ actinRow hReq DeploymentLog {..} = do
311316 "status "
312317 <> if exitCode == 0 then "status--success" else "status--failure"
313318 divClass statusClass blank
314− el "td" $ deploymentOverridesWidget hReq deploymentDepOverrides
315− el "td" $ applicationOverridesWidget hReq deploymentDepOverrides deploymentAppOverrides
319+ el "td" $ overridesWidget deploymentDepOverrides
320+ el "td" $ overridesWidget deploymentAppOverrides
316321 el "td" $ text $ showT $ exitCode
317322 el "td" $ text $ formatPosixToDateTime createdAt
318323 el "td" $ text $ formatDuration duration
octopod-frontend/src/Page/Deployments.hs+45−77
@@ -24,7 +24,7 @@ import Common.Utils
2424import Control.Applicative
2525import Control.Monad.Reader
2626import Data.Align
27−import Data.Char
27+import Data.Foldable
2828import Data.Functor
2929import qualified Data.Semigroup as S
3030import qualified Data.Text as T
@@ -39,6 +39,7 @@ import Page.ClassicPopup
3939import Page.Elements.Links
4040import Page.Popup.EditDeployment
4141import Page.Popup.NewDeployment
42+import Reflex.Dom.AsyncEvent
4243import Reflex.Dom.Renderable
4344import Reflex.MultiEventWriter.Class
4445import Servant.Reflex.Extra
@@ -79,21 +80,22 @@ deploymentsWidget ::
7980 [DeploymentFullInfo] ->
8081 m ()
8182deploymentsWidget updAllEv dfis = do
82− (showNewDeploymentEv, editEv) <- deploymentsWidgetWrapper $
83− wrapRequestErrors $ \hReq -> mdo
84− pageNotification $
85− leftmost
86− [ DPMError
87− "Deployment list update failed, deployment list\
88− \ may be slightly outdated."
89− <$ errUpdEv
90− , DPMClear <$ okUpdEv
91− ]
92− (showNewDeploymentEv', termDyn') <- deploymentsHeadWidget True okUpdEv
93− termDyn <- debounceDyn 0.3 termDyn'
94− (okUpdEv, errUpdEv, editEv) <- deploymentsListWidget hReq updAllEv termDyn dfis
95− pure (showNewDeploymentEv', deSearch <$> editEv)
96− void $ newDeploymentPopup showNewDeploymentEv never
83+ (showNewDeploymentEv, editEv) <- deploymentsWidgetWrapper $ mdo
84+ pageNotification $
85+ leftmost
86+ [ DPMError
87+ "Deployment list update failed, deployment list\
88+ \ may be slightly outdated."
89+ <$ errUpdEv
90+ , DPMClear <$ okUpdEv
91+ ]
92+ (showNewDeploymentEv', termDyn') <- deploymentsHeadWidget True okUpdEv
93+ termDyn <- debounceDyn 0.3 termDyn'
94+ (okUpdEv, errUpdEv, editEv) <- deploymentsListWidget updAllEv termDyn dfis
95+ pure (showNewDeploymentEv', deSearch <$> editEv)
96+ newDepEv <- newDeploymentPopup showNewDeploymentEv never
97+ setRoute $ newDepEv <&> \newDep -> DashboardRoute :/ Just (newDep ^. #name)
98+
9799 void $ editDeploymentPopup editEv never
98100
99101-- | Div wrappers.
@@ -176,26 +178,26 @@ deploymentsListWidget ::
176178 , SetRoute t (R Routes) m
177179 , MonadReader ProjectConfig m
178180 ) =>
179− RequestErrorHandler t m ->
180181 Event t () ->
181182 Dynamic t Text ->
182183 -- | Initial deployment data
183184 [DeploymentFullInfo] ->
184185 m (Event t (), Event t (), Event t SearchedDeploymentInfo)
185−deploymentsListWidget hReq updAllEv termDyn ds = dataWidgetWrapper $ mdo
186+deploymentsListWidget updAllEv termDyn ds = dataWidgetWrapper $ mdo
186187 retryEv <- delay 10 errUpdEv
187188 updRespEv <- listEndpoint $ leftmost [updAllEv, () <$ retryEv]
188189 let okUpdEv = fmapMaybe reqSuccess updRespEv
189190 errUpdEv = fmapMaybe reqErrorBody updRespEv
190191 dsDyn <- holdDyn ds okUpdEv
191− let searchedDyn = ffor2 termDyn dsDyn $ \term ds' ->
192− searchMany (T.filter (not . isSpace) term) ds'
193− (archivedDsDyn, activeDsDyn) =
192+ let searchInput = updated $ ffor2 termDyn dsDyn (,)
193+ searchedEv <- asyncEventLast searchInput $ \(term, ds') ->
194+ searchMany (T.unpack <$> T.words term) ds'
195+ searchedDyn <- holdDyn (wrapResult <$> ds) searchedEv
196+ let (archivedDsDyn, activeDsDyn) =
194197 splitDynPure $ L.partition isDeploymentArchived <$> searchedDyn
195198 searchSorting = termDyn $> Nothing
196− clickedEv <- elementClick
197− editEv <- activeDeploymentsWidget hReq searchSorting clickedEv activeDsDyn
198− archivedDeploymentsWidget hReq searchSorting clickedEv archivedDsDyn
199+ editEv <- activeDeploymentsWidget searchSorting activeDsDyn
200+ archivedDeploymentsWidget searchSorting archivedDsDyn
199201 pure (() <$ okUpdEv, () <$ errUpdEv, editEv)
200202
201203type SearchedDeploymentInfo = DeploymentFullInfo' SearchResult
@@ -206,16 +208,12 @@ activeDeploymentsWidget ::
206208 , SetRoute t (R Routes) m
207209 , MonadReader ProjectConfig m
208210 ) =>
209− RequestErrorHandler t m ->
210211 Dynamic t (Maybe (SortDir DeploymentFullInfo)) ->
211− -- | Event that carries the clicked DOM element. This event is required by
212− -- `dropdownWidget'`.
213− Event t ClickedElement ->
214212 Dynamic t [SearchedDeploymentInfo] ->
215213 -- | Returns an event carrying editable deployment
216214 -- to \"edit deployment\" sidebar.
217215 m (Event t SearchedDeploymentInfo)
218−activeDeploymentsWidget hReq searchSorting clickedEv dsDyn =
216+activeDeploymentsWidget searchSorting dsDyn =
219217 divClass "data__primary" $
220218 tableWrapper (updated searchSorting $> SortingChanged) $ \sortDyn -> do
221219 sorting <- holdDyn Nothing (mergeWith (<|>) [updated sortDyn, updated searchSorting])
@@ -226,7 +224,7 @@ activeDeploymentsWidget hReq searchSorting clickedEv dsDyn =
226224 dyn $
227225 emptyDyn <&> \case
228226 False -> do
229− editEvs <- simpleList dsSortedDyn (activeDeploymentWidget hReq clickedEv)
227+ editEvs <- simpleList dsSortedDyn activeDeploymentWidget
230228 pure $ switchDyn $ leftmost <$> editEvs
231229 True -> do
232230 emptyTableBody noDeploymentsWidget
@@ -246,15 +244,11 @@ activeDeploymentWidget ::
246244 , SetRoute t (R Routes) m
247245 , MonadReader ProjectConfig m
248246 ) =>
249− RequestErrorHandler t m ->
250− -- | Event that carries the clicked DOM element. This event is required by
251− -- `dropdownWidget'`.
252− Event t ClickedElement ->
253247 Dynamic t SearchedDeploymentInfo ->
254248 -- | Returns event carrying editable deployment
255249 -- that is required by \"edit deployment\" sidebar.
256250 m (Event t SearchedDeploymentInfo)
257−activeDeploymentWidget hReq clickedEv dDyn' = do
251+activeDeploymentWidget dDyn' = do
258252 dDyn <- holdUniqDyn dDyn'
259253 editEvEv <- dyn $
260254 ffor dDyn $ \d@DeploymentFullInfo {..} -> do
@@ -266,27 +260,17 @@ activeDeploymentWidget hReq clickedEv dDyn' = do
266260 statusWidget $ constDyn status
267261 el "td" $
268262 divClass "listing" $
269− forM_ (unDeploymentMetadata metadata) (renderMetadataLink . pure)
263+ for_ (unDeploymentMetadata metadata) (renderMetadataLink . pure)
270264 el "td" $
271− deploymentOverridesWidgetSearched hReq (deployment ^. field @"deploymentOverrides" . coerced)
265+ overridesWidget (deployment ^. field @"deploymentOverrides" . coerced)
272266 el "td" $
273− applicationOverridesWidgetSearched
274− hReq
275− (deployment ^. field @"deploymentOverrides" . coerced)
276− (deployment ^. field @"appOverrides" . coerced)
267+ overridesWidget (deployment ^. field @"appOverrides" . coerced)
277268 el "td" $
278269 text $ formatPosixToDate createdAt
279270 el "td" $
280271 text $ formatPosixToDate updatedAt
281272 el "td" $ do
282273 let enabled = not . isPending . recordedStatus $ status
283− btn =
284− elAttr
285− "button"
286− ( "class" =: "drop__handler"
287− <> "type" =: "button"
288− )
289− $ text "Actions"
290274 body = do
291275 btnEditEv <-
292276 actionButton $
@@ -311,13 +295,13 @@ activeDeploymentWidget hReq clickedEv dDyn' = do
311295 def
312296 { buttonText = "Details"
313297 , buttonType = Just LogsActionButtonType
314− , buttonBaseTag = ATag url
298+ , buttonBaseTag = ATag $ pure url
315299 }
316300 )
317301 pure $
318302 leftmost
319303 [ArchiveDeployment <$ btnArcEv, EditDeployment <$ btnEditEv]
320− dropdownWidget' clickedEv btn body
304+ dropdownWidget body
321305 let archEv = () <$ ffilter (is (_Ctor @"ArchiveDeployment")) dropdownEv
322306 editEv = d <$ ffilter (is (_Ctor @"EditDeployment")) dropdownEv
323307 delEv <- confirmArchivePopup archEv $ do
@@ -326,7 +310,7 @@ activeDeploymentWidget hReq clickedEv dDyn' = do
326310 text $ unDeploymentName dName <> " deployment?"
327311 void $ archiveEndpoint (constDyn . Right $ dName) delEv
328312 let route = DashboardRoute :/ Just dName
329− setRoute $ route <$ domEvent Dblclick linkEl
313+ setRoute $ route <$ domEvent Click linkEl
330314 pure editEv
331315 switchHold never editEvEv
332316
@@ -336,14 +320,10 @@ archivedDeploymentsWidget ::
336320 ( MonadWidget t m
337321 , SetRoute t (R Routes) m
338322 ) =>
339− RequestErrorHandler t m ->
340323 Dynamic t (Maybe (SortDir DeploymentFullInfo)) ->
341− -- | Event that carries the clicked DOM element. This event is required by
342− -- `dropdownWidget'`.
343− Event t ClickedElement ->
344324 Dynamic t [SearchedDeploymentInfo] ->
345325 m ()
346−archivedDeploymentsWidget hReq searchSorting clickedEv dsDyn = do
326+archivedDeploymentsWidget searchSorting dsDyn = do
347327 showDyn <- toggleButton
348328 let classDyn = ffor showDyn $ \case
349329 True -> "data__archive data__archive--open"
@@ -360,7 +340,7 @@ archivedDeploymentsWidget hReq searchSorting clickedEv dsDyn = do
360340 void $
361341 simpleList
362342 dsSortedDyn
363− (archivedDeploymentWidget hReq clickedEv)
343+ archivedDeploymentWidget
364344 True -> emptyTableBody noDeploymentsWidget
365345
366346-- | Row with archived deployment.
@@ -368,11 +348,9 @@ archivedDeploymentWidget ::
368348 ( MonadWidget t m
369349 , SetRoute t (R Routes) m
370350 ) =>
371− RequestErrorHandler t m ->
372− Event t ClickedElement ->
373351 Dynamic t SearchedDeploymentInfo ->
374352 m ()
375−archivedDeploymentWidget hReq clickedEv dDyn' = do
353+archivedDeploymentWidget dDyn' = do
376354 dDyn <- holdUniqDyn dDyn'
377355 dyn_ $
378356 ffor dDyn $ \d@DeploymentFullInfo {..} -> do
@@ -384,34 +362,24 @@ archivedDeploymentWidget hReq clickedEv dDyn' = do
384362 statusWidget (pure status)
385363 el "td" $ text "..."
386364 el "td" $
387− deploymentOverridesWidgetSearched hReq (deployment ^. field @"deploymentOverrides" . coerced)
365+ overridesWidget (deployment ^. field @"deploymentOverrides" . coerced)
388366 el "td" $
389− applicationOverridesWidgetSearched
390− hReq
391− (deployment ^. field @"deploymentOverrides" . coerced)
392− (deployment ^. field @"appOverrides" . coerced)
367+ overridesWidget (deployment ^. field @"appOverrides" . coerced)
393368 el "td" $
394369 text $ formatPosixToDate createdAt
395370 el "td" $
396371 text $ formatPosixToDate updatedAt
397372 el "td" $ do
398− let btn =
399− elAttr
400− "button"
401− ( "class" =: "drop__handler"
402− <> "type" =: "button"
403− )
404− $ text "Actions"
405− body =
373+ let body =
406374 actionButton
407375 def
408376 { buttonText = "Restore from archive"
409377 , buttonType = Just ArchiveActionButtonType
410378 }
411− btnEv <- dropdownWidget' clickedEv btn body
412− void $ restoreEndpoint (constDyn $ Right $ dName) btnEv
379+ btnEv <- dropdownWidget body
380+ void $ restoreEndpoint (constDyn $ Right $ dName) (btnEv $> ())
413381 let route = DashboardRoute :/ Just dName
414− setRoute $ route <$ domEvent Dblclick linkEl
382+ setRoute $ route <$ domEvent Click linkEl
415383
416384-- | Sort deployments by the supplied condition.
417385sortDeployments ::
@@ -557,7 +525,7 @@ tableWrapper ::
557525 (Dynamic t (Maybe (SortDir DeploymentFullInfo)) -> m a) ->
558526 m a
559527tableWrapper sChanged ma =
560− divClass "table table--deployments table--clickable table--double-click" $
528+ divClass "table table--deployments table--clickable" $
561529 el "table" $ mdo
562530 ((), sDyn') <- runSortableTableGroup sChanged tableHeader
563531 sDyn <- holdDyn Nothing $ Just <$> sDyn'
octopod-frontend/src/Page/Elements/Links.hs+20−11
@@ -4,8 +4,11 @@ module Page.Elements.Links
44where
55
66import Common.Types
7+import Common.Utils ((<^.>))
78import Control.Lens
9+import Control.Monad
810import qualified Data.Text as T
11+import Frontend.UIKit.Button.Common
912import Reflex.Dom
1013
1114renderMetadataLink ::
@@ -13,14 +16,20 @@ renderMetadataLink ::
1316 Dynamic t DeploymentMetadatum ->
1417 m ()
1518renderMetadataLink metadataD = do
16− let attrDyn =
17− metadataD <&> \metadata ->
18− "class" =: "listing__item external bar bar--larger"
19− <> "href" =: metadata ^. #link
20− <> "target" =: "_blank"
21− elDynAttr "a" attrDyn . dynText $
22− metadataD <&> \case
23− -- If the name is empty, then use the url
24− DeploymentMetadatum {name = name}
25− | (not . T.null . T.strip) name -> name
26− DeploymentMetadatum {link = url} -> url
19+ void $
20+ buttonEl
21+ CommonButtonConfig
22+ { constantClasses = pure $ "listing__item" <> "external" <> "bar" <> "bar--larger"
23+ , enabledClasses = mempty
24+ , disabledClasses = "button--disabled"
25+ , buttonEnabled = pure True
26+ , buttonText =
27+ TextBuilder $
28+ dynText $
29+ metadataD <&> \case
30+ -- If the name is empty, then use the url
31+ DeploymentMetadatum {name = name}
32+ | (not . T.null . T.strip) name -> name
33+ DeploymentMetadatum {link = url} -> url
34+ , buttonBaseTag = ATag $ metadataD <^.> #link
35+ }
octopod-frontend/src/Page/Popup/EditDeployment.hs+3−3
@@ -33,10 +33,10 @@ editDeploymentPopup ::
3333 Event t () ->
3434 -- | Event with a flag showing the current state of the request.
3535 m (Event t Bool)
36−editDeploymentPopup showEv hideEv = sidebar showEv hideEv $ \dfi -> mdo
36+editDeploymentPopup showEv hideEv = catchReturns $ \enterEv -> sidebar showEv hideEv $ \dfi -> mdo
3737 divClass "popup__body" $ mdo
3838 let dname = dfi ^. dfiName
39− (closeEv', saveEv) <- editDeploymentPopupHeader dname enabledDyn sentDyn
39+ (closeEv', (enterEv <>) -> saveEv) <- editDeploymentPopupHeader dname enabledDyn sentDyn
4040 deploymentMDyn <- editDeploymentPopupBody dfi respEv
4141 respEv <-
4242 holdDyn (pure never) >=> networkView >=> switchHold never $
@@ -88,7 +88,7 @@ editDeploymentPopupHeader dname validDyn loadingDyn =
8888 )
8989
9090 divClass "popup__menu drop drop--actions" blank
91− pure (closeEv, saveEv)
91+ pure (closeEv $> (), saveEv $> ())
9292
9393-- | The body of the sidebar containing the edit form. Contains a tag field and
9494-- an override field.
octopod-frontend/src/Page/Popup/NewDeployment.hs+30−27
@@ -8,7 +8,6 @@ module Page.Popup.NewDeployment (newDeploymentPopup) where
88import Control.Lens
99import Control.Monad
1010import Data.Functor
11−import Data.Generics.Sum
1211import Data.Monoid
1312import qualified Data.Text as T
1413import Reflex.Dom as R
@@ -30,31 +29,35 @@ newDeploymentPopup ::
3029 Event t () ->
3130 -- | \"Close\" event.
3231 Event t () ->
33− m ()
34−newDeploymentPopup showEv hideEv = void $
35− sidebar showEv hideEv $
36− const $ mdo
37− divClass "popup__body" $ mdo
38− (closeEv', saveEv) <- newDeploymentPopupHeader enabledDyn sentDyn
39− deploymentMDyn <- newDeploymentPopupBody respEv
40− respEv <-
41− holdDyn (pure never) >=> networkView >=> switchHold never $
42− tagMaybe (current deploymentMDyn) saveEv <&> \dep -> do
43− pb <- getPostBuild
44− createEndpoint
45− (pure $ Right dep)
46− pb
47− sentDyn <-
48− holdDyn False $
49− leftmost
50− [ True <$ saveEv
51− , False <$ respEv
52− ]
53− let successEv =
54− fmapMaybe (preview (_Ctor @"Success") <=< commandResponse) respEv
55− closeEv = leftmost [closeEv', successEv]
56− enabledDyn = zipDynWith (&&) (not <$> sentDyn) (isJust <$> deploymentMDyn)
57− pure (never, closeEv)
32+ m (Event t Deployment)
33+newDeploymentPopup showEv hideEv =
34+ catchReturns $ \enterEv ->
35+ sidebar showEv hideEv $
36+ const $ mdo
37+ divClass "popup__body" $ mdo
38+ (closeEv', (enterEv <>) -> saveEv) <- newDeploymentPopupHeader enabledDyn sentDyn
39+ deploymentMDyn <- newDeploymentPopupBody (snd <$> respEv)
40+ respEv <-
41+ holdDyn (pure never) >=> networkView >=> switchHold never $
42+ tagMaybe (current deploymentMDyn) saveEv <&> \dep -> do
43+ pb <- getPostBuild
44+ fmap (dep,)
45+ <$> createEndpoint
46+ (pure $ Right dep)
47+ pb
48+ sentDyn <-
49+ holdDyn False $
50+ leftmost
51+ [ True <$ saveEv
52+ , False <$ respEv
53+ ]
54+ let successEv =
55+ respEv `fforMaybe` \case
56+ (dep, commandResponse -> Just Success {}) -> Just dep
57+ _ -> Nothing
58+ closeEv = leftmost [closeEv', successEv $> ()]
59+ enabledDyn = zipDynWith (&&) (not <$> sentDyn) (isJust <$> deploymentMDyn)
60+ pure (successEv, closeEv)
5861
5962-- | The header of sidebar contains control buttons: \"Save\" and \"Close\".
6063newDeploymentPopupHeader ::
@@ -80,7 +83,7 @@ newDeploymentPopupHeader enabledDyn loadingDyn =
8083 True -> Just LoadingLargeButtonType
8184 )
8285 divClass "popup__menu drop drop--actions" blank
83− pure (closeEv, saveEv)
86+ pure (closeEv $> (), saveEv $> ())
8487
8588-- | The body of the sidebar contains the creation form. It contains: a name field,
8689-- a tag field and overrides fields. The name field is validated with the regexp:
octopod-frontend/src/Reflex/Dom/AsyncEvent.hsnew file+30
@@ -0,0 +1,30 @@
1+module Reflex.Dom.AsyncEvent
2+ ( NFData,
3+ asyncEventLast,
4+ )
5+where
6+
7+import Control.Concurrent
8+import Control.DeepSeq (NFData, force)
9+import Control.Exception (evaluate)
10+import Control.Monad.IO.Class
11+import Data.Functor
12+import Data.IORef
13+import Reflex
14+
15+asyncEventLast ::
16+ (TriggerEvent t m, PerformEvent t m, MonadIO (Performable m), MonadIO m, NFData b) =>
17+ Event t a ->
18+ (a -> b) ->
19+ m (Event t b)
20+asyncEventLast aEv f = do
21+ lastThreadIdRef <- liftIO $ newIORef Nothing
22+ performEventAsync $
23+ aEv <&> \a fire -> liftIO $ do
24+ readIORef lastThreadIdRef >>= \case
25+ Nothing -> pure ()
26+ Just lastThreadId -> killThread lastThreadId
27+ newThreadId <- forkIO $ do
28+ b <- evaluate $ force $ f a
29+ fire b
30+ writeIORef lastThreadIdRef (Just newThreadId)
octopod-frontend/src/Reflex/Dom/Renderable.hs+20−2
@@ -3,15 +3,33 @@ module Reflex.Dom.Renderable
33 )
44where
55
6+import Data.Sequence (Seq (..))
67import Data.Text (Text)
8+import qualified Data.Text as T
79import Reflex.Dom
810
911class Renderable a where
1012 rndr :: DomBuilder t m => a -> m ()
13+ rndrList :: DomBuilder t m => [a] -> m ()
14+ rndrList [] = pure ()
15+ rndrList (a : aa) = rndr a >> rndr aa
16+ {-# INLINE rndrList #-}
1117
1218instance Renderable Text where
1319 rndr = text
20+ {-# INLINE rndr #-}
1421
1522instance Renderable a => Renderable [a] where
16− rndr [] = pure ()
17− rndr (a : aa) = rndr a >> rndr aa
23+ rndr = rndrList
24+ {-# INLINE rndr #-}
25+
26+instance Renderable Char where
27+ rndr = text . T.singleton
28+ {-# INLINE rndr #-}
29+ rndrList = text . T.pack
30+ {-# INLINE rndrList #-}
31+
32+instance Renderable a => Renderable (Seq a) where
33+ rndr Empty = pure ()
34+ rndr (a :<| aa) = rndr a >> rndr aa
35+ {-# INLINE rndr #-}
octopod_local_install.sh+21−1
@@ -8,6 +8,9 @@ echo
88echo " 🔖 If you encounter any issues during the installation please open an issue:"
99echo " https://github.com/typeable/octopod/issues/new"
1010echo
11+echo " ℹ️ Please note that you might experience performance issues if you have low resouce limits:"
12+echo " https://github.com/typeable/octopod/blob/master/docs/en/Octopod_deployment_guide.md#if-you-want-to-try-it-locally"
13+echo
1114
1215for cmd in kind kubectl helm; do
1316 if ! command -v $cmd &> /dev/null; then
@@ -87,8 +90,25 @@ data:
8790EOF
8891message "Installing ingress-nginx controller"
8992kubectl --context=kind-octopod apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.45.0/deploy/static/provider/kind/deploy.yaml
93+has_error=0
9094while true; do
91− if [ $(kubectl --context=kind-octopod -n ingress-nginx get deploy | awk -F ' ' '$1 ~ /^ingress-nginx/ {print $4}') == '1' ]; then
95+ set +e
96+ kube_out=`kubectl --context=kind-octopod -n ingress-nginx get deploy 2>&1`
97+ err_code=$?
98+ set -e
99+ if [ $err_code != 0 ] && [ $has_error == 0 ]; then
100+ echo "kubectl returned: $kube_out"
101+ echo "Do you want to continue installation? [y/n] "
102+ read confirm
103+ if [ "$confirm" != 'y' ]; then
104+ message "Deleting cluster..."
105+ kind delete cluster --name octopod
106+ exit 0;
107+ else
108+ has_error=1
109+ fi
110+ fi
111+ if [ $err_code == 0 ] && [ $( echo -n "$kube_out" | awk -F ' ' '$1 ~ /^ingress-nginx/ {print $4}') == '1' ]; then
92112 message 'ingress-controller is ready'
93113 break;
94114 else
stack.yaml+3−1
@@ -1,4 +1,4 @@
1−resolver: lts-18.2
1+resolver: lts-18.28
22
33packages:
44 - octo-cli
@@ -10,3 +10,5 @@ extra-deps:
1010 - servant-websockets-2.0.0
1111 - table-layout-0.9.1.0
1212 - data-default-instances-base-0.1.0.1
13+ - git: https://github.com/typeable/ordered-containers.git
14+ commit: 3eb05fb2f44fe482e9092aff447e9c68fbd6a7f6