├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── dependabot.yml ├── pull_request_template.md ├── release-drafter.yml └── workflows │ ├── golangci-lint.yml │ ├── goreleaser.yml │ ├── publish-docker.yml │ ├── release-drafter.yml │ ├── size-label.yml │ └── test.yml ├── .gitignore ├── .golangci.yaml ├── .goreleaser.yaml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── render.go ├── root.go └── serve.go ├── go.mod ├── go.sum ├── images ├── demo.png ├── logo-large.png ├── logo.png ├── logo_small.png └── universe.png ├── main.go ├── pkg ├── renderer │ └── renderer.go └── types │ └── types.go ├── statik └── statik.go └── web ├── demo ├── data │ └── graph.json └── index.html ├── index.html └── js ├── 3d-force-graph.min.js ├── dat.gui.js ├── three-spritetext.min.js └── three.min.js /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 🐞 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior e.g. provide example action definition. 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | 18 | **Screenshots** 19 | If applicable, add screenshots to help explain your problem. 20 | 21 | **Additional context** 22 | Add any other context about the problem here. 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 💡 3 | about: Suggest a new idea for the project. 4 | labels: enhancement 5 | --- 6 | 7 | # Summary 8 | 9 | Brief explanation of the feature. 10 | 11 | ## Basic example 12 | 13 | If the proposal involves a new or changed API, include a basic code example. Omit this section if it's not applicable. 14 | 15 | ## Motivation 16 | 17 | Why are we doing this? What use cases does it support? What is the expected outcome? 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question ❓ 3 | about: Is something unclear? 4 | labels: question 5 | --- 6 | 7 | # Question? 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: gomod 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | open-pull-requests-limit: 10 9 | reviewers: 10 | - "afritzler" 11 | - package-ecosystem: docker 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | open-pull-requests-limit: 10 16 | reviewers: 17 | - "afritzler" 18 | - package-ecosystem: github-actions 19 | directory: "/" 20 | schedule: 21 | interval: "daily" 22 | open-pull-requests-limit: 10 23 | reviewers: 24 | - "afritzler" 25 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Proposed Changes 2 | 3 | - 4 | - 5 | - 6 | 7 | Fixes # -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - 'feature' 7 | - 'enhancement' 8 | - title: '🐛 Bug Fixes' 9 | labels: 10 | - 'fix' 11 | - 'bugfix' 12 | - 'bug' 13 | - title: '🧰 Maintenance' 14 | labels: 15 | - 'chore' 16 | - 'dependencies' 17 | - 'marketing' 18 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 19 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 20 | version-resolver: 21 | major: 22 | labels: 23 | - 'major' 24 | minor: 25 | labels: 26 | - 'minor' 27 | patch: 28 | labels: 29 | - 'patch' 30 | default: patch 31 | exclude-labels: 32 | - 'skip-changelog' 33 | autolabeler: 34 | - label: 'chore' 35 | files: 36 | - '*.md' 37 | branch: 38 | - '/docs{0,1}\/.+/' 39 | - label: 'bug' 40 | branch: 41 | - '/fix\/.+/' 42 | title: 43 | - '/fix/i' 44 | - label: 'enhancement' 45 | branch: 46 | - '/feature\/.+/' 47 | body: 48 | - '/JIRA-[0-9]{1,4}/' 49 | template: | 50 | ## Changes 51 | 52 | $CHANGES 53 | 54 | ## Usage 55 | 56 | To pull the image run 57 | 58 | ```bash 59 | docker pull ghcr.io/afritzler/kube-universe:v$RESOLVED_VERSION 60 | ``` 61 | 62 | Using the image as a base image in your Dockerfile 63 | 64 | ``` 65 | FROM ghcr.io/afritzler/kube-universe:v$RESOLVED_VERSION 66 | ``` 67 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint Golang Codebase 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - 'docs/**' 7 | - '**/*.md' 8 | jobs: 9 | golangci: 10 | name: lint 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-go@v5 15 | with: 16 | go-version-file: 'go.mod' 17 | - name: golangci-lint 18 | uses: golangci/golangci-lint-action@v6 19 | with: 20 | version: v1.55.2 21 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | goreleaser-publish: 10 | runs-on: ubuntu-latest 11 | if: github.event.repository.fork == false 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | - name: Unshallow 16 | run: git fetch --prune --unshallow 17 | - name: Set up Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: 1.19 21 | - name: Run GoReleaser 22 | uses: goreleaser/goreleaser-action@v6 23 | with: 24 | version: latest 25 | args: release --rm-dist 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | -------------------------------------------------------------------------------- /.github/workflows/publish-docker.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish Docker Image 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | push: 8 | branches: 9 | - master 10 | tags: 11 | - v* 12 | paths-ignore: 13 | - 'docs/**' 14 | - '**/*.md' 15 | pull_request: 16 | paths-ignore: 17 | - 'docs/**' 18 | - '**/*.md' 19 | 20 | jobs: 21 | buildAndPush: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: docker/metadata-action@v5 26 | id: meta 27 | with: 28 | images: | 29 | ghcr.io/${{ github.repository_owner }}/kube-universe 30 | tags: | 31 | type=semver,pattern={{version}} 32 | type=schedule 33 | type=ref,event=branch 34 | type=ref,event=tag 35 | type=ref,event=pr 36 | type=sha 37 | flavor: | 38 | latest=${{ github.ref == 'refs/heads/master' }} 39 | - name: Set up QEMU 40 | uses: docker/setup-qemu-action@v3 41 | with: 42 | platforms: all 43 | # workaround for self-hosted runner 44 | # https://github.com/mumoshu/actions-runner-controller-ci/commit/e91c8c0f6ca82aa7618010c6d2f417aa46c4a4bf 45 | - name: Set up Docker Context for Buildx 46 | id: buildx-context 47 | run: | 48 | docker context create builders 49 | - name: Set up Docker Buildx 50 | timeout-minutes: 5 51 | uses: docker/setup-buildx-action@v3 52 | with: 53 | version: latest 54 | - name: Login to GHCR 55 | if: github.event_name != 'pull_request' 56 | uses: docker/login-action@v3 57 | with: 58 | registry: ghcr.io 59 | username: ${{ github.actor }} 60 | password: ${{ secrets.GITHUB_TOKEN }} 61 | - name: Build and push 62 | timeout-minutes: 40 63 | uses: docker/build-push-action@v6 64 | with: 65 | context: . 66 | platforms: linux/amd64,linux/arm64 67 | push: ${{ github.event_name != 'pull_request' }} 68 | tags: ${{ steps.meta.outputs.tags }} 69 | labels: ${{ steps.meta.outputs.labels }} 70 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [ opened, reopened, synchronize ] 9 | 10 | jobs: 11 | update_release_draft: 12 | runs-on: ubuntu-latest 13 | steps: 14 | # Drafts your next Release notes as Pull Requests are merged into "main" 15 | - uses: release-drafter/release-drafter@v6 16 | with: 17 | disable-releaser: github.ref != 'refs/heads/master' 18 | config-name: release-drafter.yml 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /.github/workflows/size-label.yml: -------------------------------------------------------------------------------- 1 | name: Size Label 2 | 3 | on: 4 | pull_request_target: 5 | types: [ assigned, opened, synchronize, reopened ] 6 | 7 | jobs: 8 | size-label: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: size-label 12 | uses: pascalgn/size-label-action@v0.5.5 13 | env: 14 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Code test 2 | 3 | on: 4 | pull_request: 5 | types: [ assigned, opened, synchronize, reopened ] 6 | paths-ignore: 7 | - 'docs/**' 8 | - '**/*.md' 9 | 10 | jobs: 11 | checks: 12 | name: run 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | with: 17 | ref: ${{ github.event.pull_request.head.sha }} 18 | - uses: actions/setup-go@v5 19 | with: 20 | go-version: 1.19 21 | - run: make test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | web/*.json 3 | kube-universe 4 | .vscode/ 5 | bin/ -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 3m 3 | 4 | linters: 5 | enable: 6 | - revive 7 | - ineffassign 8 | - misspell 9 | - goimports 10 | 11 | severity: 12 | default-severity: error 13 | 14 | linters-settings: 15 | misspell: 16 | ignore-words: 17 | - strat 18 | revive: 19 | severity: error 20 | rules: 21 | - name: exported 22 | - name: if-return 23 | disabled: true 24 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | builds: 5 | - 6 | id: kube-universe 7 | dir: . 8 | main: main.go 9 | binary: kube-universe 10 | ldflags: 11 | - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser 12 | env: 13 | - CGO_ENABLED=0 14 | goos: 15 | - linux 16 | - darwin 17 | - windows 18 | goarch: 19 | - amd64 20 | - 386 21 | - arm 22 | - arm64 23 | goarm: 24 | - 6 25 | - 7 26 | ignore: 27 | - goos: darwin 28 | goarch: 386 29 | 30 | archives: 31 | - format: binary 32 | 33 | checksum: 34 | name_template: 'checksums.txt' 35 | snapshot: 36 | name_template: "{{ .Tag }}-next" 37 | changelog: 38 | skip: true -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM --platform=$BUILDPLATFORM golang:1.24.4 as builder 3 | 4 | ARG GOARCH='' 5 | ARG GITHUB_PAT='' 6 | 7 | WORKDIR /workspace 8 | RUN go install github.com/rakyll/statik@latest 9 | 10 | # Copy the Go Modules manifests 11 | COPY go.mod go.mod 12 | COPY go.sum go.sum 13 | 14 | # cache deps before building and copying source so that we don't need to re-download as much 15 | # and so that source changes don't invalidate our downloaded layer 16 | RUN --mount=type=cache,target=/root/.cache/go-build \ 17 | --mount=type=cache,target=/go/pkg \ 18 | go mod download 19 | 20 | # Copy the go source 21 | COPY cmd/ cmd/ 22 | COPY pkg/ pkg/ 23 | COPY statik/ statik/ 24 | COPY web/ web/ 25 | COPY main.go main.go 26 | 27 | ARG TARGETOS 28 | ARG TARGETARCH 29 | 30 | # Build 31 | RUN --mount=type=cache,target=/root/.cache/go-build \ 32 | --mount=type=cache,target=/go/pkg \ 33 | CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH GO111MODULE=on go build -ldflags="-s -w" -a -o kube-universe main.go 34 | 35 | FROM gcr.io/distroless/static:nonroot 36 | WORKDIR / 37 | COPY --from=builder /workspace/kube-universe . 38 | USER 65532:65532 39 | 40 | ENTRYPOINT ["./kube-universe"] 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOCMD=go 2 | GOBUILD=$(GOCMD) build 3 | GOCLEAN=$(GOCMD) clean 4 | GOTEST=$(GOCMD) test 5 | GOGET=$(GOCMD) get 6 | BINARY_NAME=kube-universe 7 | BINARY_UNIX=$(BINARY_NAME)_linux 8 | IMAGE=afritzler/kube-universe 9 | TAG=latest 10 | 11 | all: test build 12 | 13 | help: ## Display this help. 14 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 15 | 16 | ##@ Development 17 | 18 | build: deps 19 | statik -f -src=$(PWD)/web/ 20 | $(GOBUILD) -o $(BINARY_NAME) -v 21 | test: 22 | $(GOTEST) -v ./... 23 | clean: 24 | $(GOCLEAN) 25 | rm -f $(BINARY_NAME) 26 | rm -f $(BINARY_UNIX) 27 | run: 28 | $(GOBUILD) -o $(BINARY_NAME) -v ./... 29 | ./$(BINARY_NAME) 30 | deps: 31 | $(GOGET) -d github.com/rakyll/statik 32 | 33 | .PHONY: fmt 34 | fmt: goimports ## Run goimports against code. 35 | $(GOIMPORTS) -w . 36 | 37 | .PHONY: vet 38 | vet: ## Run go vet against code. 39 | go vet ./... 40 | 41 | .PHONY: lint 42 | lint: golangci-lint ## Run golangci-lint on the code. 43 | $(GOLANGCILINT) run ./... 44 | 45 | # Cross compilation 46 | build-linux: 47 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BINARY_UNIX) -v 48 | docker-build: build 49 | docker build -t $(IMAGE):$(TAG) . 50 | 51 | ##@ Tools 52 | 53 | ## Location to install dependencies to 54 | LOCALBIN ?= $(shell pwd)/bin 55 | $(LOCALBIN): 56 | mkdir -p $(LOCALBIN) 57 | 58 | ## Tool Binaries 59 | ADDLICENSE ?= $(LOCALBIN)/addlicense 60 | GOIMPORTS ?= $(LOCALBIN)/goimports 61 | GOLANGCILINT ?= $(LOCALBIN)/golangci-lint 62 | 63 | ## Tool Versions 64 | ADDLICENSE_VERSION ?= v1.1.1 65 | GOIMPORTS_VERSION ?= v0.14.0 66 | GOLANGCILINT_VERSION ?= v1.55.2 67 | 68 | .PHONY: addlicense 69 | addlicense: $(ADDLICENSE) ## Download addlicense locally if necessary. 70 | $(ADDLICENSE): $(LOCALBIN) 71 | test -s $(LOCALBIN)/addlicense || GOBIN=$(LOCALBIN) go install github.com/google/addlicense@$(ADDLICENSE_VERSION) 72 | 73 | .PHONY: goimports 74 | goimports: $(GOIMPORTS) ## Download goimports locally if necessary. 75 | $(GOIMPORTS): $(LOCALBIN) 76 | test -s $(LOCALBIN)/goimports || GOBIN=$(LOCALBIN) go install golang.org/x/tools/cmd/goimports@$(GOIMPORTS_VERSION) 77 | 78 | .PHONY: goimports 79 | golangci-lint: $(GOLANGCILINT) ## Download golangci-lint locally if necessary. 80 | $(GOLANGCILINT): $(LOCALBIN) 81 | test -s $(LOCALBIN)/golangci-lint || GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCILINT_VERSION) 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kube-universe 2 | 3 | ![kube universe logo](images/logo_small.png) 4 | 5 | --- 6 | 7 | ![demo](images/demo.png) 8 | 9 | ## Overview 10 | 11 | Kube-Universe renders a Kubernetes cluster into a dynamic 3D graph. An example landscape visualization can be found [here](images/universe.png). 12 | 13 | A __Live Demo Version__ is available [here](https://afritzler.github.io/kube-universe/web/demo/) 14 | 15 | ## Features 16 | 17 | * 3D cluster overview 18 | * Identify pods with errors 19 | 20 | ## Installation and Usage 21 | 22 | Before you start you need to install `statik` to vendor the web content into executable 23 | 24 | ```bash 25 | go get -u github.com/rakyll/statik 26 | ``` 27 | 28 | Get the `kube-universe` binary 29 | 30 | ```bash 31 | go get github.com/afritzler/kube-universe 32 | ``` 33 | 34 | Start `kube-universe` locally 35 | 36 | ```bash 37 | kube-universe serve --kubeconfig=PATH_TO_MY_KUBECONFIG 38 | ``` 39 | 40 | or just 41 | 42 | ```bash 43 | kube-universe serve 44 | ``` 45 | 46 | if you are using minikube or have the `KUBECONFIG` environment variable pointing to a corresponding cluster. 47 | 48 | The web UI can be accessed via http://localhost:3000 and the rendered graph under http://localhost:3000/graph. 49 | With the `--port` flag you can also specify under which port the kube universe server should be exposed (default is 3000). 50 | 51 | ## Development 52 | 53 | To build and run `kube-universe` from source 54 | 55 | ```bash 56 | git clone https://github.com/afritzler/kube-universe $GOPATH/src/github.com/afritzler/kube-universe 57 | cd $GOPATH/src/github.com/afritzler/kube-universe 58 | go run *.go serve --kubeconfig=PATH_TO_MY_KUBECONFIG 59 | ``` 60 | 61 | or to build and run it using the executable 62 | 63 | ```bash 64 | make 65 | ./kube-universe serve --kubeconfig=PATH_TO_MY_KUBECONFIG 66 | ``` 67 | 68 | To build the Docker image 69 | 70 | ```bash 71 | cd $GOPATH/src/github.com/afritzler/kube-universe 72 | make docker-build 73 | ``` 74 | 75 | ## Acknowledgements 76 | 77 | Kube universe is using [3d-force-graph](https://github.com/vasturiano/3d-force-graph) for rendering. 78 | -------------------------------------------------------------------------------- /cmd/render.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Andreas Fritzler 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | renderer "github.com/afritzler/kube-universe/pkg/renderer" 22 | "github.com/spf13/cobra" 23 | ) 24 | 25 | // renderCmd represents the render command 26 | var renderCmd = &cobra.Command{ 27 | Use: "render", 28 | Short: "Renders the cluster graph as JSON", 29 | Long: `Renders the cluster graph into JSON format and prints it out to stdout.`, 30 | Run: func(cmd *cobra.Command, args []string) { 31 | render() 32 | }, 33 | } 34 | 35 | func init() { 36 | rootCmd.AddCommand(renderCmd) 37 | } 38 | 39 | func render() { 40 | kubeconfig := rootCmd.Flag("kubeconfig").Value.String() 41 | data, err := renderer.GetGraph(kubeconfig) 42 | if err != nil { 43 | fmt.Printf("failed to render cluster graph: %s", err) 44 | os.Exit(1) 45 | } 46 | fmt.Printf("%s\n", data) 47 | } 48 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Andreas Fritzler 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "path/filepath" 21 | 22 | homedir "github.com/mitchellh/go-homedir" 23 | "github.com/spf13/cobra" 24 | "github.com/spf13/viper" 25 | ) 26 | 27 | var kubeconfig string 28 | var cfgFile string 29 | 30 | // rootCmd represents the base command when called without any subcommands 31 | var rootCmd = &cobra.Command{ 32 | Use: "kube-universe", 33 | Short: "3D representation of a Kubernetes cluster", 34 | Long: `3D representation of a Kubernetes cluster`, 35 | } 36 | 37 | // Execute runs the main command loop. 38 | func Execute() { 39 | if err := rootCmd.Execute(); err != nil { 40 | fmt.Println(err) 41 | os.Exit(1) 42 | } 43 | } 44 | 45 | func init() { 46 | cobra.OnInitialize(initConfig) 47 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.kube-universe.yaml)") 48 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 49 | if home := homeDir(); home != "" { 50 | rootCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") 51 | } else { 52 | rootCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "absolute path to the kubeconfig file") 53 | } 54 | if err := viper.BindPFlag("kubeconfig", rootCmd.PersistentFlags().Lookup("kubeconfig")); err != nil { 55 | panic(fmt.Sprintf("faild to bind kubeconfig flag: %s", err)) 56 | } 57 | } 58 | 59 | // initConfig reads in config file and ENV variables if set. 60 | func initConfig() { 61 | if cfgFile != "" { 62 | // Use config file from the flag. 63 | viper.SetConfigFile(cfgFile) 64 | } else { 65 | // Find home directory. 66 | home, err := homedir.Dir() 67 | if err != nil { 68 | fmt.Println(err) 69 | os.Exit(1) 70 | } 71 | // Search config in home directory with name ".kube-universe" (without extension). 72 | viper.AddConfigPath(home) 73 | viper.SetConfigName(".kube-universe") 74 | } 75 | viper.AutomaticEnv() // read in environment variables that match 76 | // If a config file is found, read it in. 77 | if err := viper.ReadInConfig(); err == nil { 78 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 79 | } 80 | } 81 | 82 | func homeDir() string { 83 | if h := os.Getenv("HOME"); h != "" { 84 | return h 85 | } 86 | return os.Getenv("USERPROFILE") // windows 87 | } 88 | -------------------------------------------------------------------------------- /cmd/serve.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Andreas Fritzler 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "net/http" 21 | "os" 22 | 23 | renderer "github.com/afritzler/kube-universe/pkg/renderer" 24 | "github.com/rakyll/statik/fs" 25 | "github.com/spf13/cobra" 26 | "github.com/spf13/viper" 27 | 28 | _ "github.com/afritzler/kube-universe/statik" 29 | ) 30 | 31 | var port string 32 | 33 | // serveCmd represents the serve command 34 | var serveCmd = &cobra.Command{ 35 | Use: "serve", 36 | Short: "Starts a webserver to serve the 3D landscape view", 37 | Long: `Starts a webserver to serve the 3D landscape view. 38 | 39 | By default, the website can be accessed on http://localhost:3000. The JSON representation of 40 | the landscape graph can be found under http://localhost:3000/graph.`, 41 | Run: func(cmd *cobra.Command, args []string) { 42 | serve() 43 | }, 44 | } 45 | 46 | func init() { 47 | rootCmd.AddCommand(serveCmd) 48 | serveCmd.PersistentFlags().StringVarP(&port, "port", "p", "3000", "Port on which the server should listen") 49 | if err := viper.BindPFlag("port", serveCmd.PersistentFlags().Lookup("port")); err != nil { 50 | panic(fmt.Sprintf("faild to bind port flag: %s", err)) 51 | } 52 | } 53 | 54 | func serve() { 55 | fmt.Printf("started server on http://localhost:%s\n", port) 56 | statikFS, err := fs.New() 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | http.Handle("/", http.FileServer(statikFS)) 61 | http.HandleFunc("/graph", func(writer http.ResponseWriter, request *http.Request) { 62 | config := os.Getenv("KUBECONFIG") 63 | if config == "" { 64 | config = rootCmd.Flag("kubeconfig").Value.String() 65 | } 66 | data, err := renderer.GetGraph(config) 67 | if err != nil { 68 | fmt.Printf("failed to render landscape graph: %s", err) 69 | http.Error(writer, err.Error(), http.StatusInternalServerError) 70 | fmt.Printf("failed to render graph: %s", err) 71 | } 72 | writer.Header().Set("Content-Type", "application/json") 73 | if _, err := writer.Write(data); err != nil { 74 | fmt.Printf("faild to write response data: %s", err) 75 | } 76 | }) 77 | if err := http.ListenAndServe(getPort(), nil); err != nil { 78 | panic(fmt.Sprintf("faild to start server: %s", err)) 79 | } 80 | } 81 | 82 | func getPort() string { 83 | return fmt.Sprintf(":%s", port) 84 | } 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/afritzler/kube-universe 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/mitchellh/go-homedir v1.1.0 7 | github.com/rakyll/statik v0.1.7 8 | github.com/spf13/cobra v1.9.1 9 | github.com/spf13/viper v1.19.0 10 | k8s.io/apimachinery v0.29.4 11 | k8s.io/client-go v0.29.4 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 16 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 17 | github.com/fsnotify/fsnotify v1.7.0 // indirect 18 | github.com/go-logr/logr v1.4.1 // indirect 19 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 20 | github.com/go-openapi/jsonreference v0.20.2 // indirect 21 | github.com/go-openapi/swag v0.22.3 // indirect 22 | github.com/gogo/protobuf v1.3.2 // indirect 23 | github.com/golang/protobuf v1.5.4 // indirect 24 | github.com/google/gnostic-models v0.6.8 // indirect 25 | github.com/google/gofuzz v1.2.0 // indirect 26 | github.com/google/uuid v1.4.0 // indirect 27 | github.com/hashicorp/hcl v1.0.0 // indirect 28 | github.com/imdario/mergo v0.3.11 // indirect 29 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 30 | github.com/josharian/intern v1.0.0 // indirect 31 | github.com/json-iterator/go v1.1.12 // indirect 32 | github.com/magiconair/properties v1.8.7 // indirect 33 | github.com/mailru/easyjson v0.7.7 // indirect 34 | github.com/mitchellh/mapstructure v1.5.0 // indirect 35 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 36 | github.com/modern-go/reflect2 v1.0.2 // indirect 37 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 38 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 39 | github.com/sagikazarmark/locafero v0.4.0 // indirect 40 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 41 | github.com/sourcegraph/conc v0.3.0 // indirect 42 | github.com/spf13/afero v1.11.0 // indirect 43 | github.com/spf13/cast v1.6.0 // indirect 44 | github.com/spf13/pflag v1.0.6 // indirect 45 | github.com/subosito/gotenv v1.6.0 // indirect 46 | go.uber.org/atomic v1.9.0 // indirect 47 | go.uber.org/multierr v1.9.0 // indirect 48 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 49 | golang.org/x/net v0.33.0 // indirect 50 | golang.org/x/oauth2 v0.18.0 // indirect 51 | golang.org/x/sys v0.28.0 // indirect 52 | golang.org/x/term v0.27.0 // indirect 53 | golang.org/x/text v0.21.0 // indirect 54 | golang.org/x/time v0.5.0 // indirect 55 | google.golang.org/appengine v1.6.8 // indirect 56 | google.golang.org/protobuf v1.33.0 // indirect 57 | gopkg.in/inf.v0 v0.9.1 // indirect 58 | gopkg.in/ini.v1 v1.67.0 // indirect 59 | gopkg.in/yaml.v2 v2.4.0 // indirect 60 | gopkg.in/yaml.v3 v3.0.1 // indirect 61 | k8s.io/api v0.29.4 // indirect 62 | k8s.io/klog/v2 v2.110.1 // indirect 63 | k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect 64 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect 65 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 66 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 67 | sigs.k8s.io/yaml v1.3.0 // indirect 68 | ) 69 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 2 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 6 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 8 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 9 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 10 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 11 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 12 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 13 | github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 14 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 15 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 16 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 17 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 18 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 19 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 20 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= 21 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 22 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 23 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 24 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 25 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 26 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 27 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 28 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 29 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 30 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 31 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 32 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 33 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 34 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 35 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 36 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 37 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 38 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 39 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 40 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 41 | github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= 42 | github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 43 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 44 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 45 | github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= 46 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 47 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 48 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 49 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 50 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 51 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 52 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 53 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 54 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 55 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 56 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 57 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 58 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 59 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 60 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 61 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 62 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 63 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 64 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 65 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 66 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 67 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 68 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 69 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 70 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 71 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 72 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 73 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 74 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 75 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 76 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 77 | github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= 78 | github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= 79 | github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= 80 | github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 81 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 82 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 83 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 84 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 85 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 86 | github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= 87 | github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= 88 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 89 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 90 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 91 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= 92 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= 93 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 94 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= 95 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 96 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 97 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 98 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 99 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= 100 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 101 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 102 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 103 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 104 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 105 | github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= 106 | github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= 107 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 108 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 109 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 110 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 111 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 112 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 113 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 114 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 115 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 116 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 117 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 118 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 119 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 120 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 121 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 122 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 123 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 124 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 125 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 126 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 127 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 128 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 129 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 130 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 131 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 132 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 133 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 134 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 135 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 136 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 137 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 138 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 139 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 140 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 141 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 142 | golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= 143 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 144 | golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= 145 | golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= 146 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 147 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 148 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 149 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 150 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 151 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 152 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 153 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 154 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 155 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 156 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 157 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 158 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 159 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 160 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 161 | golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= 162 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 163 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 164 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 165 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 166 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 167 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 168 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 169 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 170 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 171 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 172 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 173 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 174 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 175 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 176 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= 177 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 178 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 179 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 180 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 181 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 182 | google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= 183 | google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= 184 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 185 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 186 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 187 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 188 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 189 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 190 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 191 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 192 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 193 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 194 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 195 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 196 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 197 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 198 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 199 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 200 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 201 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 202 | k8s.io/api v0.29.4 h1:WEnF/XdxuCxdG3ayHNRR8yH3cI1B/llkWBma6bq4R3w= 203 | k8s.io/api v0.29.4/go.mod h1:DetSv0t4FBTcEpfA84NJV3g9a7+rSzlUHk5ADAYHUv0= 204 | k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q= 205 | k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y= 206 | k8s.io/client-go v0.29.4 h1:79ytIedxVfyXV8rpH3jCBW0u+un0fxHDwX5F9K8dPR8= 207 | k8s.io/client-go v0.29.4/go.mod h1:kC1thZQ4zQWYwldsfI088BbK6RkxK+aF5ebV8y9Q4tk= 208 | k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= 209 | k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo= 210 | k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= 211 | k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= 212 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= 213 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 214 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 215 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 216 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 217 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 218 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 219 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 220 | -------------------------------------------------------------------------------- /images/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afritzler/kube-universe/ec5fdfbbba6649018f3832acc3bdc30402804fdf/images/demo.png -------------------------------------------------------------------------------- /images/logo-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afritzler/kube-universe/ec5fdfbbba6649018f3832acc3bdc30402804fdf/images/logo-large.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afritzler/kube-universe/ec5fdfbbba6649018f3832acc3bdc30402804fdf/images/logo.png -------------------------------------------------------------------------------- /images/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afritzler/kube-universe/ec5fdfbbba6649018f3832acc3bdc30402804fdf/images/logo_small.png -------------------------------------------------------------------------------- /images/universe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afritzler/kube-universe/ec5fdfbbba6649018f3832acc3bdc30402804fdf/images/universe.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Andreas Fritzler 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import "github.com/afritzler/kube-universe/cmd" 18 | 19 | func main() { 20 | cmd.Execute() 21 | } 22 | -------------------------------------------------------------------------------- /pkg/renderer/renderer.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Andreas Fritzler 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pkg 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | 22 | kutype "github.com/afritzler/kube-universe/pkg/types" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/client-go/kubernetes" 25 | "k8s.io/client-go/tools/clientcmd" 26 | ) 27 | 28 | const ( 29 | namespaceType = "namespace" 30 | podType = "pod" 31 | nodeType = "node" 32 | ) 33 | 34 | // GetGraph returns the rendered dependency graph 35 | func GetGraph(kubeconfig string) ([]byte, error) { 36 | // use the current context in kubeconfig 37 | config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) 38 | ctx := context.Background() 39 | if err != nil { 40 | return nil, fmt.Errorf("failed to load kubeconfig: %s", err) 41 | } 42 | 43 | clientset, err := kubernetes.NewForConfig(config) 44 | if err != nil { 45 | return nil, fmt.Errorf("failed to create clientset for kubeconfig: %s", err) 46 | } 47 | 48 | nodes := make(map[string]*kutype.Node) 49 | links := make([]kutype.Link, 0) 50 | 51 | namespaces, err := clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) 52 | if err != nil { 53 | return nil, fmt.Errorf("failed to get namespaces: %s", err) 54 | } 55 | for _, n := range namespaces.Items { 56 | key := fmt.Sprintf("%s-%s", namespaceType, n.Name) 57 | nodes[key] = &kutype.Node{Id: key, Name: n.Name, Type: namespaceType, Namespace: n.Namespace} 58 | } 59 | 60 | pods, err := clientset.CoreV1().Pods("").List(ctx, metav1.ListOptions{}) 61 | if err != nil { 62 | return nil, fmt.Errorf("failed to get pods: %s", err) 63 | } 64 | for _, p := range pods.Items { 65 | podKey := fmt.Sprintf("%s-%s", p.Namespace, p.Name) 66 | namespaceKey := fmt.Sprintf("%s-%s", namespaceType, p.Namespace) 67 | nodeKey := fmt.Sprintf("%s-%s", nodeType, p.Spec.NodeName) 68 | nodes[podKey] = &kutype.Node{ 69 | Id: podKey, 70 | Name: p.Name, 71 | Type: podType, 72 | Namespace: p.Namespace, 73 | Status: string(p.Status.Phase), 74 | StatusMessage: p.Status.Message} 75 | links = append(links, kutype.Link{Source: namespaceKey, Target: podKey, Value: 0}) 76 | links = append(links, kutype.Link{Source: podKey, Target: nodeKey, Value: 0}) 77 | } 78 | 79 | clusterNodes, err := clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) 80 | if err != nil { 81 | return nil, fmt.Errorf("failed to get nodes: %s", err) 82 | } 83 | for _, n := range clusterNodes.Items { 84 | key := fmt.Sprintf("%s-%s", nodeType, n.Name) 85 | nodes[key] = &kutype.Node{Id: key, Name: n.Name, Type: nodeType, Namespace: n.Namespace, Status: string(n.Status.Phase)} 86 | } 87 | 88 | data, err := json.MarshalIndent(kutype.Graph{Nodes: values(nodes), Links: &links}, "", " ") 89 | if err != nil { 90 | return nil, fmt.Errorf("JSON marshaling failed: %s", err) 91 | } 92 | return data, nil 93 | } 94 | 95 | func values(nodes map[string]*kutype.Node) *[]kutype.Node { 96 | array := []kutype.Node{} 97 | for _, n := range nodes { 98 | array = append(array, *n) 99 | } 100 | return &array 101 | } 102 | -------------------------------------------------------------------------------- /pkg/types/types.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Andreas Fritzler 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package pkg 16 | 17 | type Graph struct { 18 | Nodes *[]Node `json:"nodes"` 19 | Links *[]Link `json:"links"` 20 | } 21 | 22 | type Node struct { 23 | Id string `json:"id"` 24 | Namespace string `json:"namespace"` 25 | Name string `json:"name"` 26 | Type string `json:"type"` 27 | Status string `json:"status,omitempty"` 28 | StatusMessage string `json:"statusmessage,omitempty"` 29 | } 30 | 31 | type Link struct { 32 | Source string `json:"source"` 33 | Target string `json:"target"` 34 | Value int `json:"value"` 35 | } 36 | -------------------------------------------------------------------------------- /web/demo/data/graph.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": [ 3 | { 4 | "id": "kube-system-pod-checkpointer-mgwn7", 5 | "namespace": "kube-system", 6 | "name": "pod-checkpointer-mgwn7", 7 | "type": "pod" 8 | }, 9 | { 10 | "id": "nginx-ingress-nginx-ingress-controller-dqwnw", 11 | "namespace": "nginx-ingress", 12 | "name": "nginx-ingress-controller-dqwnw", 13 | "type": "pod" 14 | }, 15 | { 16 | "id": "node-test-master-1", 17 | "namespace": "", 18 | "name": "test-master-1", 19 | "type": "node" 20 | }, 21 | { 22 | "id": "kube-system-kube-controller-manager-56f5ffc6d-snq5c", 23 | "namespace": "kube-system", 24 | "name": "kube-controller-manager-56f5ffc6d-snq5c", 25 | "type": "pod" 26 | }, 27 | { 28 | "id": "kube-system-kube-etcd-0001", 29 | "namespace": "kube-system", 30 | "name": "kube-etcd-0001", 31 | "type": "pod" 32 | }, 33 | { 34 | "id": "kube-system-kube-etcd-backup-sidecar-79f76685d-whg64", 35 | "namespace": "kube-system", 36 | "name": "kube-etcd-backup-sidecar-79f76685d-whg64", 37 | "type": "pod" 38 | }, 39 | { 40 | "id": "node-test-worker-2", 41 | "namespace": "", 42 | "name": "test-worker-2", 43 | "type": "node" 44 | }, 45 | { 46 | "id": "kube-system-heapster-c8fb4f746-9s7b8", 47 | "namespace": "kube-system", 48 | "name": "heapster-c8fb4f746-9s7b8", 49 | "type": "pod" 50 | }, 51 | { 52 | "id": "kube-system-kube-controller-manager-56f5ffc6d-nlg5x", 53 | "namespace": "kube-system", 54 | "name": "kube-controller-manager-56f5ffc6d-nlg5x", 55 | "type": "pod" 56 | }, 57 | { 58 | "id": "kube-system-kube-proxy-hjf9m", 59 | "namespace": "kube-system", 60 | "name": "kube-proxy-hjf9m", 61 | "type": "pod" 62 | }, 63 | { 64 | "id": "namespace-kube-system", 65 | "namespace": "", 66 | "name": "kube-system", 67 | "type": "namespace" 68 | }, 69 | { 70 | "id": "kube-system-kube-dns-52nns", 71 | "namespace": "kube-system", 72 | "name": "kube-dns-52nns", 73 | "type": "pod" 74 | }, 75 | { 76 | "id": "kube-system-kube-etcd-network-checkpointer-42qnw", 77 | "namespace": "kube-system", 78 | "name": "kube-etcd-network-checkpointer-42qnw", 79 | "type": "pod" 80 | }, 81 | { 82 | "id": "nginx-ingress-nginx-default-backend-74f5ccc879-47qrv", 83 | "namespace": "nginx-ingress", 84 | "name": "nginx-default-backend-74f5ccc879-47qrv", 85 | "type": "pod" 86 | }, 87 | { 88 | "id": "kube-system-kube-etcd-0002", 89 | "namespace": "kube-system", 90 | "name": "kube-etcd-0002", 91 | "type": "pod" 92 | }, 93 | { 94 | "id": "kube-system-kube-flannel-c27nw", 95 | "namespace": "kube-system", 96 | "name": "kube-flannel-c27nw", 97 | "type": "pod" 98 | }, 99 | { 100 | "id": "kube-system-kube-proxy-cg6j4", 101 | "namespace": "kube-system", 102 | "name": "kube-proxy-cg6j4", 103 | "type": "pod" 104 | }, 105 | { 106 | "id": "kube-system-kube-controller-manager-56f5ffc6d-p88kl", 107 | "namespace": "kube-system", 108 | "name": "kube-controller-manager-56f5ffc6d-p88kl", 109 | "type": "pod" 110 | }, 111 | { 112 | "id": "kube-system-pod-checkpointer-grkwm", 113 | "namespace": "kube-system", 114 | "name": "pod-checkpointer-grkwm", 115 | "type": "pod" 116 | }, 117 | { 118 | "id": "kube-system-kube-apiserver-vqhwr", 119 | "namespace": "kube-system", 120 | "name": "kube-apiserver-vqhwr", 121 | "type": "pod" 122 | }, 123 | { 124 | "id": "kube-system-kube-dns-lqns2", 125 | "namespace": "kube-system", 126 | "name": "kube-dns-lqns2", 127 | "type": "pod" 128 | }, 129 | { 130 | "id": "kube-system-kube-etcd-0000", 131 | "namespace": "kube-system", 132 | "name": "kube-etcd-0000", 133 | "type": "pod" 134 | }, 135 | { 136 | "id": "nginx-ingress-nginx-ingress-controller-7jw7d", 137 | "namespace": "nginx-ingress", 138 | "name": "nginx-ingress-controller-7jw7d", 139 | "type": "pod" 140 | }, 141 | { 142 | "id": "nginx-ingress-nginx-ingress-controller-hfl79", 143 | "namespace": "nginx-ingress", 144 | "name": "nginx-ingress-controller-hfl79", 145 | "type": "pod" 146 | }, 147 | { 148 | "id": "node-test-worker-1", 149 | "namespace": "", 150 | "name": "test-worker-1", 151 | "type": "node" 152 | }, 153 | { 154 | "id": "namespace-nginx-ingress", 155 | "namespace": "", 156 | "name": "nginx-ingress", 157 | "type": "namespace" 158 | }, 159 | { 160 | "id": "kube-system-kube-etcd-network-checkpointer-pj5zb", 161 | "namespace": "kube-system", 162 | "name": "kube-etcd-network-checkpointer-pj5zb", 163 | "type": "pod" 164 | }, 165 | { 166 | "id": "kube-system-kube-proxy-grfdp", 167 | "namespace": "kube-system", 168 | "name": "kube-proxy-grfdp", 169 | "type": "pod" 170 | }, 171 | { 172 | "id": "kube-system-kube-apiserver-rrh26", 173 | "namespace": "kube-system", 174 | "name": "kube-apiserver-rrh26", 175 | "type": "pod" 176 | }, 177 | { 178 | "id": "kube-system-pod-checkpointer-lfxt2", 179 | "namespace": "kube-system", 180 | "name": "pod-checkpointer-lfxt2", 181 | "type": "pod" 182 | }, 183 | { 184 | "id": "node-test-worker-0", 185 | "namespace": "", 186 | "name": "test-worker-0", 187 | "type": "node" 188 | }, 189 | { 190 | "id": "kube-system-kube-scheduler-7c96f44db8-2rdv9", 191 | "namespace": "kube-system", 192 | "name": "kube-scheduler-7c96f44db8-2rdv9", 193 | "type": "pod" 194 | }, 195 | { 196 | "id": "kube-system-kube-scheduler-7c96f44db8-cb67m", 197 | "namespace": "kube-system", 198 | "name": "kube-scheduler-7c96f44db8-cb67m", 199 | "type": "pod" 200 | }, 201 | { 202 | "id": "kube-system-pod-checkpointer-grkwm-test-master-0", 203 | "namespace": "kube-system", 204 | "name": "pod-checkpointer-grkwm-test-master-0", 205 | "type": "pod" 206 | }, 207 | { 208 | "id": "kube-system-pod-checkpointer-lfxt2-test-master-2", 209 | "namespace": "kube-system", 210 | "name": "pod-checkpointer-lfxt2-test-master-2", 211 | "type": "pod" 212 | }, 213 | { 214 | "id": "nginx-ingress-echoheaders-6cc4d8b98f-kbqlx", 215 | "namespace": "nginx-ingress", 216 | "name": "echoheaders-6cc4d8b98f-kbqlx", 217 | "type": "pod" 218 | }, 219 | { 220 | "id": "kube-system-etcd-operator-75dcfcf4f7-5jcml", 221 | "namespace": "kube-system", 222 | "name": "etcd-operator-75dcfcf4f7-5jcml", 223 | "type": "pod" 224 | }, 225 | { 226 | "id": "kube-system-kube-apiserver-prpxm", 227 | "namespace": "kube-system", 228 | "name": "kube-apiserver-prpxm", 229 | "type": "pod" 230 | }, 231 | { 232 | "id": "kube-system-kube-flannel-h9zgv", 233 | "namespace": "kube-system", 234 | "name": "kube-flannel-h9zgv", 235 | "type": "pod" 236 | }, 237 | { 238 | "id": "node-test-master-2", 239 | "namespace": "", 240 | "name": "test-master-2", 241 | "type": "node" 242 | }, 243 | { 244 | "id": "kube-system-alertmanager-0", 245 | "namespace": "kube-system", 246 | "name": "alertmanager-0", 247 | "type": "pod" 248 | }, 249 | { 250 | "id": "kube-system-kube-dns-t27h4", 251 | "namespace": "kube-system", 252 | "name": "kube-dns-t27h4", 253 | "type": "pod" 254 | }, 255 | { 256 | "id": "node-test-master-0", 257 | "namespace": "", 258 | "name": "test-master-0", 259 | "type": "node" 260 | }, 261 | { 262 | "id": "namespace-default", 263 | "namespace": "", 264 | "name": "default", 265 | "type": "namespace" 266 | }, 267 | { 268 | "id": "kube-system-kube-proxy-fjbt2", 269 | "namespace": "kube-system", 270 | "name": "kube-proxy-fjbt2", 271 | "type": "pod" 272 | }, 273 | { 274 | "id": "kube-system-prometheus-0", 275 | "namespace": "kube-system", 276 | "name": "prometheus-0", 277 | "type": "pod" 278 | }, 279 | { 280 | "id": "kube-system-pod-checkpointer-mgwn7-test-master-1", 281 | "namespace": "kube-system", 282 | "name": "pod-checkpointer-mgwn7-test-master-1", 283 | "type": "pod" 284 | }, 285 | { 286 | "id": "kube-system-kube-flannel-6jzqp", 287 | "namespace": "kube-system", 288 | "name": "kube-flannel-6jzqp", 289 | "type": "pod" 290 | }, 291 | { 292 | "id": "kube-system-kube-flannel-fz7vj", 293 | "namespace": "kube-system", 294 | "name": "kube-flannel-fz7vj", 295 | "type": "pod" 296 | }, 297 | { 298 | "id": "kube-system-kubernetes-dashboard-b4bfdc49f-tf5v2", 299 | "namespace": "kube-system", 300 | "name": "kubernetes-dashboard-b4bfdc49f-tf5v2", 301 | "type": "pod" 302 | }, 303 | { 304 | "id": "kube-system-kube-flannel-czqvd", 305 | "namespace": "kube-system", 306 | "name": "kube-flannel-czqvd", 307 | "type": "pod" 308 | }, 309 | { 310 | "id": "kube-system-kube-proxy-dwmkq", 311 | "namespace": "kube-system", 312 | "name": "kube-proxy-dwmkq", 313 | "type": "pod" 314 | }, 315 | { 316 | "id": "namespace-kube-public", 317 | "namespace": "", 318 | "name": "kube-public", 319 | "type": "namespace" 320 | }, 321 | { 322 | "id": "kube-system-grafana-8547fdf7cd-fpb6k", 323 | "namespace": "kube-system", 324 | "name": "grafana-8547fdf7cd-fpb6k", 325 | "type": "pod" 326 | }, 327 | { 328 | "id": "kube-system-kube-flannel-4sfwz", 329 | "namespace": "kube-system", 330 | "name": "kube-flannel-4sfwz", 331 | "type": "pod" 332 | }, 333 | { 334 | "id": "kube-system-kube-etcd-network-checkpointer-9n2pj", 335 | "namespace": "kube-system", 336 | "name": "kube-etcd-network-checkpointer-9n2pj", 337 | "type": "pod" 338 | }, 339 | { 340 | "id": "kube-system-kube-proxy-9wv6l", 341 | "namespace": "kube-system", 342 | "name": "kube-proxy-9wv6l", 343 | "type": "pod" 344 | }, 345 | { 346 | "id": "kube-system-kube-scheduler-7c96f44db8-zvpvr", 347 | "namespace": "kube-system", 348 | "name": "kube-scheduler-7c96f44db8-zvpvr", 349 | "type": "pod" 350 | } 351 | ], 352 | "links": [ 353 | { 354 | "source": "namespace-kube-system", 355 | "target": "kube-system-alertmanager-0", 356 | "value": 0 357 | }, 358 | { 359 | "source": "kube-system-alertmanager-0", 360 | "target": "node-test-worker-2", 361 | "value": 0 362 | }, 363 | { 364 | "source": "namespace-kube-system", 365 | "target": "kube-system-etcd-operator-75dcfcf4f7-5jcml", 366 | "value": 0 367 | }, 368 | { 369 | "source": "kube-system-etcd-operator-75dcfcf4f7-5jcml", 370 | "target": "node-test-master-0", 371 | "value": 0 372 | }, 373 | { 374 | "source": "namespace-kube-system", 375 | "target": "kube-system-grafana-8547fdf7cd-fpb6k", 376 | "value": 0 377 | }, 378 | { 379 | "source": "kube-system-grafana-8547fdf7cd-fpb6k", 380 | "target": "node-test-worker-1", 381 | "value": 0 382 | }, 383 | { 384 | "source": "namespace-kube-system", 385 | "target": "kube-system-heapster-c8fb4f746-9s7b8", 386 | "value": 0 387 | }, 388 | { 389 | "source": "kube-system-heapster-c8fb4f746-9s7b8", 390 | "target": "node-test-worker-0", 391 | "value": 0 392 | }, 393 | { 394 | "source": "namespace-kube-system", 395 | "target": "kube-system-kube-apiserver-prpxm", 396 | "value": 0 397 | }, 398 | { 399 | "source": "kube-system-kube-apiserver-prpxm", 400 | "target": "node-test-master-2", 401 | "value": 0 402 | }, 403 | { 404 | "source": "namespace-kube-system", 405 | "target": "kube-system-kube-apiserver-rrh26", 406 | "value": 0 407 | }, 408 | { 409 | "source": "kube-system-kube-apiserver-rrh26", 410 | "target": "node-test-master-0", 411 | "value": 0 412 | }, 413 | { 414 | "source": "namespace-kube-system", 415 | "target": "kube-system-kube-apiserver-vqhwr", 416 | "value": 0 417 | }, 418 | { 419 | "source": "kube-system-kube-apiserver-vqhwr", 420 | "target": "node-test-master-1", 421 | "value": 0 422 | }, 423 | { 424 | "source": "namespace-kube-system", 425 | "target": "kube-system-kube-controller-manager-56f5ffc6d-nlg5x", 426 | "value": 0 427 | }, 428 | { 429 | "source": "kube-system-kube-controller-manager-56f5ffc6d-nlg5x", 430 | "target": "node-test-master-0", 431 | "value": 0 432 | }, 433 | { 434 | "source": "namespace-kube-system", 435 | "target": "kube-system-kube-controller-manager-56f5ffc6d-p88kl", 436 | "value": 0 437 | }, 438 | { 439 | "source": "kube-system-kube-controller-manager-56f5ffc6d-p88kl", 440 | "target": "node-test-master-0", 441 | "value": 0 442 | }, 443 | { 444 | "source": "namespace-kube-system", 445 | "target": "kube-system-kube-controller-manager-56f5ffc6d-snq5c", 446 | "value": 0 447 | }, 448 | { 449 | "source": "kube-system-kube-controller-manager-56f5ffc6d-snq5c", 450 | "target": "node-test-master-0", 451 | "value": 0 452 | }, 453 | { 454 | "source": "namespace-kube-system", 455 | "target": "kube-system-kube-dns-52nns", 456 | "value": 0 457 | }, 458 | { 459 | "source": "kube-system-kube-dns-52nns", 460 | "target": "node-test-master-2", 461 | "value": 0 462 | }, 463 | { 464 | "source": "namespace-kube-system", 465 | "target": "kube-system-kube-dns-lqns2", 466 | "value": 0 467 | }, 468 | { 469 | "source": "kube-system-kube-dns-lqns2", 470 | "target": "node-test-master-1", 471 | "value": 0 472 | }, 473 | { 474 | "source": "namespace-kube-system", 475 | "target": "kube-system-kube-dns-t27h4", 476 | "value": 0 477 | }, 478 | { 479 | "source": "kube-system-kube-dns-t27h4", 480 | "target": "node-test-master-0", 481 | "value": 0 482 | }, 483 | { 484 | "source": "namespace-kube-system", 485 | "target": "kube-system-kube-etcd-0000", 486 | "value": 0 487 | }, 488 | { 489 | "source": "kube-system-kube-etcd-0000", 490 | "target": "node-test-master-2", 491 | "value": 0 492 | }, 493 | { 494 | "source": "namespace-kube-system", 495 | "target": "kube-system-kube-etcd-0001", 496 | "value": 0 497 | }, 498 | { 499 | "source": "kube-system-kube-etcd-0001", 500 | "target": "node-test-master-1", 501 | "value": 0 502 | }, 503 | { 504 | "source": "namespace-kube-system", 505 | "target": "kube-system-kube-etcd-0002", 506 | "value": 0 507 | }, 508 | { 509 | "source": "kube-system-kube-etcd-0002", 510 | "target": "node-test-master-0", 511 | "value": 0 512 | }, 513 | { 514 | "source": "namespace-kube-system", 515 | "target": "kube-system-kube-etcd-backup-sidecar-79f76685d-whg64", 516 | "value": 0 517 | }, 518 | { 519 | "source": "kube-system-kube-etcd-backup-sidecar-79f76685d-whg64", 520 | "target": "node-test-worker-1", 521 | "value": 0 522 | }, 523 | { 524 | "source": "namespace-kube-system", 525 | "target": "kube-system-kube-etcd-network-checkpointer-42qnw", 526 | "value": 0 527 | }, 528 | { 529 | "source": "kube-system-kube-etcd-network-checkpointer-42qnw", 530 | "target": "node-test-master-2", 531 | "value": 0 532 | }, 533 | { 534 | "source": "namespace-kube-system", 535 | "target": "kube-system-kube-etcd-network-checkpointer-9n2pj", 536 | "value": 0 537 | }, 538 | { 539 | "source": "kube-system-kube-etcd-network-checkpointer-9n2pj", 540 | "target": "node-test-master-0", 541 | "value": 0 542 | }, 543 | { 544 | "source": "namespace-kube-system", 545 | "target": "kube-system-kube-etcd-network-checkpointer-pj5zb", 546 | "value": 0 547 | }, 548 | { 549 | "source": "kube-system-kube-etcd-network-checkpointer-pj5zb", 550 | "target": "node-test-master-1", 551 | "value": 0 552 | }, 553 | { 554 | "source": "namespace-kube-system", 555 | "target": "kube-system-kube-flannel-4sfwz", 556 | "value": 0 557 | }, 558 | { 559 | "source": "kube-system-kube-flannel-4sfwz", 560 | "target": "node-test-master-2", 561 | "value": 0 562 | }, 563 | { 564 | "source": "namespace-kube-system", 565 | "target": "kube-system-kube-flannel-6jzqp", 566 | "value": 0 567 | }, 568 | { 569 | "source": "kube-system-kube-flannel-6jzqp", 570 | "target": "node-test-worker-0", 571 | "value": 0 572 | }, 573 | { 574 | "source": "namespace-kube-system", 575 | "target": "kube-system-kube-flannel-c27nw", 576 | "value": 0 577 | }, 578 | { 579 | "source": "kube-system-kube-flannel-c27nw", 580 | "target": "node-test-worker-1", 581 | "value": 0 582 | }, 583 | { 584 | "source": "namespace-kube-system", 585 | "target": "kube-system-kube-flannel-czqvd", 586 | "value": 0 587 | }, 588 | { 589 | "source": "kube-system-kube-flannel-czqvd", 590 | "target": "node-test-master-0", 591 | "value": 0 592 | }, 593 | { 594 | "source": "namespace-kube-system", 595 | "target": "kube-system-kube-flannel-fz7vj", 596 | "value": 0 597 | }, 598 | { 599 | "source": "kube-system-kube-flannel-fz7vj", 600 | "target": "node-test-master-1", 601 | "value": 0 602 | }, 603 | { 604 | "source": "namespace-kube-system", 605 | "target": "kube-system-kube-flannel-h9zgv", 606 | "value": 0 607 | }, 608 | { 609 | "source": "kube-system-kube-flannel-h9zgv", 610 | "target": "node-test-worker-2", 611 | "value": 0 612 | }, 613 | { 614 | "source": "namespace-kube-system", 615 | "target": "kube-system-kube-proxy-9wv6l", 616 | "value": 0 617 | }, 618 | { 619 | "source": "kube-system-kube-proxy-9wv6l", 620 | "target": "node-test-master-2", 621 | "value": 0 622 | }, 623 | { 624 | "source": "namespace-kube-system", 625 | "target": "kube-system-kube-proxy-cg6j4", 626 | "value": 0 627 | }, 628 | { 629 | "source": "kube-system-kube-proxy-cg6j4", 630 | "target": "node-test-worker-1", 631 | "value": 0 632 | }, 633 | { 634 | "source": "namespace-kube-system", 635 | "target": "kube-system-kube-proxy-dwmkq", 636 | "value": 0 637 | }, 638 | { 639 | "source": "kube-system-kube-proxy-dwmkq", 640 | "target": "node-test-worker-2", 641 | "value": 0 642 | }, 643 | { 644 | "source": "namespace-kube-system", 645 | "target": "kube-system-kube-proxy-fjbt2", 646 | "value": 0 647 | }, 648 | { 649 | "source": "kube-system-kube-proxy-fjbt2", 650 | "target": "node-test-master-1", 651 | "value": 0 652 | }, 653 | { 654 | "source": "namespace-kube-system", 655 | "target": "kube-system-kube-proxy-grfdp", 656 | "value": 0 657 | }, 658 | { 659 | "source": "kube-system-kube-proxy-grfdp", 660 | "target": "node-test-master-0", 661 | "value": 0 662 | }, 663 | { 664 | "source": "namespace-kube-system", 665 | "target": "kube-system-kube-proxy-hjf9m", 666 | "value": 0 667 | }, 668 | { 669 | "source": "kube-system-kube-proxy-hjf9m", 670 | "target": "node-test-worker-0", 671 | "value": 0 672 | }, 673 | { 674 | "source": "namespace-kube-system", 675 | "target": "kube-system-kube-scheduler-7c96f44db8-2rdv9", 676 | "value": 0 677 | }, 678 | { 679 | "source": "kube-system-kube-scheduler-7c96f44db8-2rdv9", 680 | "target": "node-test-master-0", 681 | "value": 0 682 | }, 683 | { 684 | "source": "namespace-kube-system", 685 | "target": "kube-system-kube-scheduler-7c96f44db8-cb67m", 686 | "value": 0 687 | }, 688 | { 689 | "source": "kube-system-kube-scheduler-7c96f44db8-cb67m", 690 | "target": "node-test-master-0", 691 | "value": 0 692 | }, 693 | { 694 | "source": "namespace-kube-system", 695 | "target": "kube-system-kube-scheduler-7c96f44db8-zvpvr", 696 | "value": 0 697 | }, 698 | { 699 | "source": "kube-system-kube-scheduler-7c96f44db8-zvpvr", 700 | "target": "node-test-master-0", 701 | "value": 0 702 | }, 703 | { 704 | "source": "namespace-kube-system", 705 | "target": "kube-system-kubernetes-dashboard-b4bfdc49f-tf5v2", 706 | "value": 0 707 | }, 708 | { 709 | "source": "kube-system-kubernetes-dashboard-b4bfdc49f-tf5v2", 710 | "target": "node-test-worker-0", 711 | "value": 0 712 | }, 713 | { 714 | "source": "namespace-kube-system", 715 | "target": "kube-system-pod-checkpointer-grkwm", 716 | "value": 0 717 | }, 718 | { 719 | "source": "kube-system-pod-checkpointer-grkwm", 720 | "target": "node-test-master-0", 721 | "value": 0 722 | }, 723 | { 724 | "source": "namespace-kube-system", 725 | "target": "kube-system-pod-checkpointer-grkwm-test-master-0", 726 | "value": 0 727 | }, 728 | { 729 | "source": "kube-system-pod-checkpointer-grkwm-test-master-0", 730 | "target": "node-test-master-0", 731 | "value": 0 732 | }, 733 | { 734 | "source": "namespace-kube-system", 735 | "target": "kube-system-pod-checkpointer-lfxt2", 736 | "value": 0 737 | }, 738 | { 739 | "source": "kube-system-pod-checkpointer-lfxt2", 740 | "target": "node-test-master-2", 741 | "value": 0 742 | }, 743 | { 744 | "source": "namespace-kube-system", 745 | "target": "kube-system-pod-checkpointer-lfxt2-test-master-2", 746 | "value": 0 747 | }, 748 | { 749 | "source": "kube-system-pod-checkpointer-lfxt2-test-master-2", 750 | "target": "node-test-master-2", 751 | "value": 0 752 | }, 753 | { 754 | "source": "namespace-kube-system", 755 | "target": "kube-system-pod-checkpointer-mgwn7", 756 | "value": 0 757 | }, 758 | { 759 | "source": "kube-system-pod-checkpointer-mgwn7", 760 | "target": "node-test-master-1", 761 | "value": 0 762 | }, 763 | { 764 | "source": "namespace-kube-system", 765 | "target": "kube-system-pod-checkpointer-mgwn7-test-master-1", 766 | "value": 0 767 | }, 768 | { 769 | "source": "kube-system-pod-checkpointer-mgwn7-test-master-1", 770 | "target": "node-test-master-1", 771 | "value": 0 772 | }, 773 | { 774 | "source": "namespace-kube-system", 775 | "target": "kube-system-prometheus-0", 776 | "value": 0 777 | }, 778 | { 779 | "source": "kube-system-prometheus-0", 780 | "target": "node-test-worker-2", 781 | "value": 0 782 | }, 783 | { 784 | "source": "namespace-nginx-ingress", 785 | "target": "nginx-ingress-echoheaders-6cc4d8b98f-kbqlx", 786 | "value": 0 787 | }, 788 | { 789 | "source": "nginx-ingress-echoheaders-6cc4d8b98f-kbqlx", 790 | "target": "node-test-worker-1", 791 | "value": 0 792 | }, 793 | { 794 | "source": "namespace-nginx-ingress", 795 | "target": "nginx-ingress-nginx-default-backend-74f5ccc879-47qrv", 796 | "value": 0 797 | }, 798 | { 799 | "source": "nginx-ingress-nginx-default-backend-74f5ccc879-47qrv", 800 | "target": "node-test-worker-0", 801 | "value": 0 802 | }, 803 | { 804 | "source": "namespace-nginx-ingress", 805 | "target": "nginx-ingress-nginx-ingress-controller-7jw7d", 806 | "value": 0 807 | }, 808 | { 809 | "source": "nginx-ingress-nginx-ingress-controller-7jw7d", 810 | "target": "node-test-worker-0", 811 | "value": 0 812 | }, 813 | { 814 | "source": "namespace-nginx-ingress", 815 | "target": "nginx-ingress-nginx-ingress-controller-dqwnw", 816 | "value": 0 817 | }, 818 | { 819 | "source": "nginx-ingress-nginx-ingress-controller-dqwnw", 820 | "target": "node-test-worker-2", 821 | "value": 0 822 | }, 823 | { 824 | "source": "namespace-nginx-ingress", 825 | "target": "nginx-ingress-nginx-ingress-controller-hfl79", 826 | "value": 0 827 | }, 828 | { 829 | "source": "nginx-ingress-nginx-ingress-controller-hfl79", 830 | "target": "node-test-worker-1", 831 | "value": 0 832 | } 833 | ] 834 | } 835 | -------------------------------------------------------------------------------- /web/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /web/js/dat.gui.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * https://github.com/dataarts/dat.gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(e.dat={})}(this,function(e){"use strict";function t(e,t){var n=e.__state.conversionName.toString(),o=Math.round(e.r),i=Math.round(e.g),r=Math.round(e.b),s=e.a,a=Math.round(e.h),l=e.s.toFixed(1),d=e.v.toFixed(1);if(t||"THREE_CHAR_HEX"===n||"SIX_CHAR_HEX"===n){for(var c=e.hex.toString(16);c.length<6;)c="0"+c;return"#"+c}return"CSS_RGB"===n?"rgb("+o+","+i+","+r+")":"CSS_RGBA"===n?"rgba("+o+","+i+","+r+","+s+")":"HEX"===n?"0x"+e.hex.toString(16):"RGB_ARRAY"===n?"["+o+","+i+","+r+"]":"RGBA_ARRAY"===n?"["+o+","+i+","+r+","+s+"]":"RGB_OBJ"===n?"{r:"+o+",g:"+i+",b:"+r+"}":"RGBA_OBJ"===n?"{r:"+o+",g:"+i+",b:"+r+",a:"+s+"}":"HSV_OBJ"===n?"{h:"+a+",s:"+l+",v:"+d+"}":"HSVA_OBJ"===n?"{h:"+a+",s:"+l+",v:"+d+",a:"+s+"}":"unknown format"}function n(e,t,n){Object.defineProperty(e,t,{get:function(){return"RGB"===this.__state.space?this.__state[t]:(I.recalculateRGB(this,t,n),this.__state[t])},set:function(e){"RGB"!==this.__state.space&&(I.recalculateRGB(this,t,n),this.__state.space="RGB"),this.__state[t]=e}})}function o(e,t){Object.defineProperty(e,t,{get:function(){return"HSV"===this.__state.space?this.__state[t]:(I.recalculateHSV(this),this.__state[t])},set:function(e){"HSV"!==this.__state.space&&(I.recalculateHSV(this),this.__state.space="HSV"),this.__state[t]=e}})}function i(e){if("0"===e||S.isUndefined(e))return 0;var t=e.match(U);return S.isNull(t)?0:parseFloat(t[1])}function r(e){var t=e.toString();return t.indexOf(".")>-1?t.length-t.indexOf(".")-1:0}function s(e,t){var n=Math.pow(10,t);return Math.round(e*n)/n}function a(e,t,n,o,i){return o+(e-t)/(n-t)*(i-o)}function l(e,t,n,o){e.style.background="",S.each(ee,function(i){e.style.cssText+="background: "+i+"linear-gradient("+t+", "+n+" 0%, "+o+" 100%); "})}function d(e){e.style.background="",e.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);",e.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);",e.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}function c(e,t,n){var o=document.createElement("li");return t&&o.appendChild(t),n?e.__ul.insertBefore(o,n):e.__ul.appendChild(o),e.onResize(),o}function u(e){X.unbind(window,"resize",e.__resizeHandler),e.saveToLocalStorageIfPossible&&X.unbind(window,"unload",e.saveToLocalStorageIfPossible)}function _(e,t){var n=e.__preset_select[e.__preset_select.selectedIndex];n.innerHTML=t?n.value+"*":n.value}function h(e,t,n){if(n.__li=t,n.__gui=e,S.extend(n,{options:function(t){if(arguments.length>1){var o=n.__li.nextElementSibling;return n.remove(),f(e,n.object,n.property,{before:o,factoryArgs:[S.toArray(arguments)]})}if(S.isArray(t)||S.isObject(t)){var i=n.__li.nextElementSibling;return n.remove(),f(e,n.object,n.property,{before:i,factoryArgs:[t]})}},name:function(e){return n.__li.firstElementChild.firstElementChild.innerHTML=e,n},listen:function(){return n.__gui.listen(n),n},remove:function(){return n.__gui.remove(n),n}}),n instanceof q){var o=new Q(n.object,n.property,{min:n.__min,max:n.__max,step:n.__step});S.each(["updateDisplay","onChange","onFinishChange","step","min","max"],function(e){var t=n[e],i=o[e];n[e]=o[e]=function(){var e=Array.prototype.slice.call(arguments);return i.apply(o,e),t.apply(n,e)}}),X.addClass(t,"has-slider"),n.domElement.insertBefore(o.domElement,n.domElement.firstElementChild)}else if(n instanceof Q){var i=function(t){if(S.isNumber(n.__min)&&S.isNumber(n.__max)){var o=n.__li.firstElementChild.firstElementChild.innerHTML,i=n.__gui.__listening.indexOf(n)>-1;n.remove();var r=f(e,n.object,n.property,{before:n.__li.nextElementSibling,factoryArgs:[n.__min,n.__max,n.__step]});return r.name(o),i&&r.listen(),r}return t};n.min=S.compose(i,n.min),n.max=S.compose(i,n.max)}else n instanceof K?(X.bind(t,"click",function(){X.fakeEvent(n.__checkbox,"click")}),X.bind(n.__checkbox,"click",function(e){e.stopPropagation()})):n instanceof Z?(X.bind(t,"click",function(){X.fakeEvent(n.__button,"click")}),X.bind(t,"mouseover",function(){X.addClass(n.__button,"hover")}),X.bind(t,"mouseout",function(){X.removeClass(n.__button,"hover")})):n instanceof $&&(X.addClass(t,"color"),n.updateDisplay=S.compose(function(e){return t.style.borderLeftColor=n.__color.toString(),e},n.updateDisplay),n.updateDisplay());n.setValue=S.compose(function(t){return e.getRoot().__preset_select&&n.isModified()&&_(e.getRoot(),!0),t},n.setValue)}function p(e,t){var n=e.getRoot(),o=n.__rememberedObjects.indexOf(t.object);if(-1!==o){var i=n.__rememberedObjectIndecesToControllers[o];if(void 0===i&&(i={},n.__rememberedObjectIndecesToControllers[o]=i),i[t.property]=t,n.load&&n.load.remembered){var r=n.load.remembered,s=void 0;if(r[e.preset])s=r[e.preset];else{if(!r[se])return;s=r[se]}if(s[o]&&void 0!==s[o][t.property]){var a=s[o][t.property];t.initialValue=a,t.setValue(a)}}}}function f(e,t,n,o){if(void 0===t[n])throw new Error('Object "'+t+'" has no property "'+n+'"');var i=void 0;if(o.color)i=new $(t,n);else{var r=[t,n].concat(o.factoryArgs);i=ne.apply(e,r)}o.before instanceof z&&(o.before=o.before.__li),p(e,i),X.addClass(i.domElement,"c");var s=document.createElement("span");X.addClass(s,"property-name"),s.innerHTML=i.property;var a=document.createElement("div");a.appendChild(s),a.appendChild(i.domElement);var l=c(e,a,o.before);return X.addClass(l,he.CLASS_CONTROLLER_ROW),i instanceof $?X.addClass(l,"color"):X.addClass(l,H(i.getValue())),h(e,l,i),e.__controllers.push(i),i}function m(e,t){return document.location.href+"."+t}function g(e,t,n){var o=document.createElement("option");o.innerHTML=t,o.value=t,e.__preset_select.appendChild(o),n&&(e.__preset_select.selectedIndex=e.__preset_select.length-1)}function b(e,t){t.style.display=e.useLocalStorage?"block":"none"}function v(e){var t=e.__save_row=document.createElement("li");X.addClass(e.domElement,"has-save"),e.__ul.insertBefore(t,e.__ul.firstChild),X.addClass(t,"save-row");var n=document.createElement("span");n.innerHTML=" ",X.addClass(n,"button gears");var o=document.createElement("span");o.innerHTML="Save",X.addClass(o,"button"),X.addClass(o,"save");var i=document.createElement("span");i.innerHTML="New",X.addClass(i,"button"),X.addClass(i,"save-as");var r=document.createElement("span");r.innerHTML="Revert",X.addClass(r,"button"),X.addClass(r,"revert");var s=e.__preset_select=document.createElement("select");if(e.load&&e.load.remembered?S.each(e.load.remembered,function(t,n){g(e,n,n===e.preset)}):g(e,se,!1),X.bind(s,"change",function(){for(var t=0;t=0;n--)t=[e[n].apply(this,t)];return t[0]}},each:function(e,t,n){if(e)if(A&&e.forEach&&e.forEach===A)e.forEach(t,n);else if(e.length===e.length+0){var o=void 0,i=void 0;for(o=0,i=e.length;o1?S.toArray(arguments):arguments[0];return S.each(O,function(t){if(t.litmus(e))return S.each(t.conversions,function(t,n){if(T=t.read(e),!1===L&&!1!==T)return L=T,T.conversionName=n,T.conversion=t,S.BREAK}),S.BREAK}),L},B=void 0,N={hsv_to_rgb:function(e,t,n){var o=Math.floor(e/60)%6,i=e/60-Math.floor(e/60),r=n*(1-t),s=n*(1-i*t),a=n*(1-(1-i)*t),l=[[n,a,r],[s,n,r],[r,n,a],[r,s,n],[a,r,n],[n,r,s]][o];return{r:255*l[0],g:255*l[1],b:255*l[2]}},rgb_to_hsv:function(e,t,n){var o=Math.min(e,t,n),i=Math.max(e,t,n),r=i-o,s=void 0,a=void 0;return 0===i?{h:NaN,s:0,v:0}:(a=r/i,s=e===i?(t-n)/r:t===i?2+(n-e)/r:4+(e-t)/r,(s/=6)<0&&(s+=1),{h:360*s,s:a,v:i/255})},rgb_to_hex:function(e,t,n){var o=this.hex_with_component(0,2,e);return o=this.hex_with_component(o,1,t),o=this.hex_with_component(o,0,n)},component_from_hex:function(e,t){return e>>8*t&255},hex_with_component:function(e,t,n){return n<<(B=8*t)|e&~(255<this.__max&&(n=this.__max),void 0!==this.__step&&n%this.__step!=0&&(n=Math.round(n/this.__step)*this.__step),D(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"setValue",this).call(this,n)}},{key:"min",value:function(e){return this.__min=e,this}},{key:"max",value:function(e){return this.__max=e,this}},{key:"step",value:function(e){return this.__step=e,this.__impliedStep=e,this.__precision=r(e),this}}]),t}(),Q=function(e){function t(e,n,o){function i(){l.__onFinishChange&&l.__onFinishChange.call(l,l.getValue())}function r(e){var t=d-e.clientY;l.setValue(l.getValue()+t*l.__impliedStep),d=e.clientY}function s(){X.unbind(window,"mousemove",r),X.unbind(window,"mouseup",s),i()}F(this,t);var a=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n,o));a.__truncationSuspended=!1;var l=a,d=void 0;return a.__input=document.createElement("input"),a.__input.setAttribute("type","text"),X.bind(a.__input,"change",function(){var e=parseFloat(l.__input.value);S.isNaN(e)||l.setValue(e)}),X.bind(a.__input,"blur",function(){i()}),X.bind(a.__input,"mousedown",function(e){X.bind(window,"mousemove",r),X.bind(window,"mouseup",s),d=e.clientY}),X.bind(a.__input,"keydown",function(e){13===e.keyCode&&(l.__truncationSuspended=!0,this.blur(),l.__truncationSuspended=!1,i())}),a.updateDisplay(),a.domElement.appendChild(a.__input),a}return j(t,W),P(t,[{key:"updateDisplay",value:function(){return this.__input.value=this.__truncationSuspended?this.getValue():s(this.getValue(),this.__precision),D(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"updateDisplay",this).call(this)}}]),t}(),q=function(e){function t(e,n,o,i,r){function s(e){e.preventDefault();var t=_.__background.getBoundingClientRect();return _.setValue(a(e.clientX,t.left,t.right,_.__min,_.__max)),!1}function l(){X.unbind(window,"mousemove",s),X.unbind(window,"mouseup",l),_.__onFinishChange&&_.__onFinishChange.call(_,_.getValue())}function d(e){var t=e.touches[0].clientX,n=_.__background.getBoundingClientRect();_.setValue(a(t,n.left,n.right,_.__min,_.__max))}function c(){X.unbind(window,"touchmove",d),X.unbind(window,"touchend",c),_.__onFinishChange&&_.__onFinishChange.call(_,_.getValue())}F(this,t);var u=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n,{min:o,max:i,step:r})),_=u;return u.__background=document.createElement("div"),u.__foreground=document.createElement("div"),X.bind(u.__background,"mousedown",function(e){document.activeElement.blur(),X.bind(window,"mousemove",s),X.bind(window,"mouseup",l),s(e)}),X.bind(u.__background,"touchstart",function(e){1===e.touches.length&&(X.bind(window,"touchmove",d),X.bind(window,"touchend",c),d(e))}),X.addClass(u.__background,"slider"),X.addClass(u.__foreground,"slider-fg"),u.updateDisplay(),u.__background.appendChild(u.__foreground),u.domElement.appendChild(u.__background),u}return j(t,W),P(t,[{key:"updateDisplay",value:function(){var e=(this.getValue()-this.__min)/(this.__max-this.__min);return this.__foreground.style.width=100*e+"%",D(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"updateDisplay",this).call(this)}}]),t}(),Z=function(e){function t(e,n,o){F(this,t);var i=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n)),r=i;return i.__button=document.createElement("div"),i.__button.innerHTML=void 0===o?"Fire":o,X.bind(i.__button,"click",function(e){return e.preventDefault(),r.fire(),!1}),X.addClass(i.__button,"button"),i.domElement.appendChild(i.__button),i}return j(t,z),P(t,[{key:"fire",value:function(){this.__onChange&&this.__onChange.call(this),this.getValue().call(this.object),this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}}]),t}(),$=function(e){function t(e,n){function o(e){u(e),X.bind(window,"mousemove",u),X.bind(window,"touchmove",u),X.bind(window,"mouseup",r),X.bind(window,"touchend",r)}function i(e){_(e),X.bind(window,"mousemove",_),X.bind(window,"touchmove",_),X.bind(window,"mouseup",s),X.bind(window,"touchend",s)}function r(){X.unbind(window,"mousemove",u),X.unbind(window,"touchmove",u),X.unbind(window,"mouseup",r),X.unbind(window,"touchend",r),c()}function s(){X.unbind(window,"mousemove",_),X.unbind(window,"touchmove",_),X.unbind(window,"mouseup",s),X.unbind(window,"touchend",s),c()}function a(){var e=R(this.value);!1!==e?(p.__color.__state=e,p.setValue(p.__color.toOriginal())):this.value=p.__color.toString()}function c(){p.__onFinishChange&&p.__onFinishChange.call(p,p.__color.toOriginal())}function u(e){-1===e.type.indexOf("touch")&&e.preventDefault();var t=p.__saturation_field.getBoundingClientRect(),n=e.touches&&e.touches[0]||e,o=n.clientX,i=n.clientY,r=(o-t.left)/(t.right-t.left),s=1-(i-t.top)/(t.bottom-t.top);return s>1?s=1:s<0&&(s=0),r>1?r=1:r<0&&(r=0),p.__color.v=s,p.__color.s=r,p.setValue(p.__color.toOriginal()),!1}function _(e){-1===e.type.indexOf("touch")&&e.preventDefault();var t=p.__hue_field.getBoundingClientRect(),n=1-((e.touches&&e.touches[0]||e).clientY-t.top)/(t.bottom-t.top);return n>1?n=1:n<0&&(n=0),p.__color.h=360*n,p.setValue(p.__color.toOriginal()),!1}F(this,t);var h=V(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n));h.__color=new I(h.getValue()),h.__temp=new I(0);var p=h;h.domElement=document.createElement("div"),X.makeSelectable(h.domElement,!1),h.__selector=document.createElement("div"),h.__selector.className="selector",h.__saturation_field=document.createElement("div"),h.__saturation_field.className="saturation-field",h.__field_knob=document.createElement("div"),h.__field_knob.className="field-knob",h.__field_knob_border="2px solid ",h.__hue_knob=document.createElement("div"),h.__hue_knob.className="hue-knob",h.__hue_field=document.createElement("div"),h.__hue_field.className="hue-field",h.__input=document.createElement("input"),h.__input.type="text",h.__input_textShadow="0 1px 1px ",X.bind(h.__input,"keydown",function(e){13===e.keyCode&&a.call(this)}),X.bind(h.__input,"blur",a),X.bind(h.__selector,"mousedown",function(){X.addClass(this,"drag").bind(window,"mouseup",function(){X.removeClass(p.__selector,"drag")})}),X.bind(h.__selector,"touchstart",function(){X.addClass(this,"drag").bind(window,"touchend",function(){X.removeClass(p.__selector,"drag")})});var f=document.createElement("div");return S.extend(h.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"}),S.extend(h.__field_knob.style,{position:"absolute",width:"12px",height:"12px",border:h.__field_knob_border+(h.__color.v<.5?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1}),S.extend(h.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1}),S.extend(h.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"}),S.extend(f.style,{width:"100%",height:"100%",background:"none"}),l(f,"top","rgba(0,0,0,0)","#000"),S.extend(h.__hue_field.style,{width:"15px",height:"100px",border:"1px solid #555",cursor:"ns-resize",position:"absolute",top:"3px",right:"3px"}),d(h.__hue_field),S.extend(h.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:h.__input_textShadow+"rgba(0,0,0,0.7)"}),X.bind(h.__saturation_field,"mousedown",o),X.bind(h.__saturation_field,"touchstart",o),X.bind(h.__field_knob,"mousedown",o),X.bind(h.__field_knob,"touchstart",o),X.bind(h.__hue_field,"mousedown",i),X.bind(h.__hue_field,"touchstart",i),h.__saturation_field.appendChild(f),h.__selector.appendChild(h.__field_knob),h.__selector.appendChild(h.__saturation_field),h.__selector.appendChild(h.__hue_field),h.__hue_field.appendChild(h.__hue_knob),h.domElement.appendChild(h.__input),h.domElement.appendChild(h.__selector),h.updateDisplay(),h}return j(t,z),P(t,[{key:"updateDisplay",value:function(){var e=R(this.getValue());if(!1!==e){var t=!1;S.each(I.COMPONENTS,function(n){if(!S.isUndefined(e[n])&&!S.isUndefined(this.__color.__state[n])&&e[n]!==this.__color.__state[n])return t=!0,{}},this),t&&S.extend(this.__color.__state,e)}S.extend(this.__temp.__state,this.__color.__state),this.__temp.a=1;var n=this.__color.v<.5||this.__color.s>.5?255:0,o=255-n;S.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toHexString(),border:this.__field_knob_border+"rgb("+n+","+n+","+n+")"}),this.__hue_knob.style.marginTop=100*(1-this.__color.h/360)+"px",this.__temp.s=1,this.__temp.v=1,l(this.__saturation_field,"left","#fff",this.__temp.toHexString()),this.__input.value=this.__color.toString(),S.extend(this.__input.style,{backgroundColor:this.__color.toHexString(),color:"rgb("+n+","+n+","+n+")",textShadow:this.__input_textShadow+"rgba("+o+","+o+","+o+",.7)"})}}]),t}(),ee=["-moz-","-o-","-webkit-","-ms-",""],te={load:function(e,t){var n=t||document,o=n.createElement("link");o.type="text/css",o.rel="stylesheet",o.href=e,n.getElementsByTagName("head")[0].appendChild(o)},inject:function(e,t){var n=t||document,o=document.createElement("style");o.type="text/css",o.innerHTML=e;var i=n.getElementsByTagName("head")[0];try{i.appendChild(o)}catch(e){}}},ne=function(e,t){var n=e[t];return S.isArray(arguments[2])||S.isObject(arguments[2])?new Y(e,t,arguments[2]):S.isNumber(n)?S.isNumber(arguments[2])&&S.isNumber(arguments[3])?S.isNumber(arguments[4])?new q(e,t,arguments[2],arguments[3],arguments[4]):new q(e,t,arguments[2],arguments[3]):S.isNumber(arguments[4])?new Q(e,t,{min:arguments[2],max:arguments[3],step:arguments[4]}):new Q(e,t,{min:arguments[2],max:arguments[3]}):S.isString(n)?new J(e,t):S.isFunction(n)?new Z(e,t,""):S.isBoolean(n)?new K(e,t):null},oe=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(e){setTimeout(e,1e3/60)},ie=function(){function e(){F(this,e),this.backgroundElement=document.createElement("div"),S.extend(this.backgroundElement.style,{backgroundColor:"rgba(0,0,0,0.8)",top:0,left:0,display:"none",zIndex:"1000",opacity:0,WebkitTransition:"opacity 0.2s linear",transition:"opacity 0.2s linear"}),X.makeFullscreen(this.backgroundElement),this.backgroundElement.style.position="fixed",this.domElement=document.createElement("div"),S.extend(this.domElement.style,{position:"fixed",display:"none",zIndex:"1001",opacity:0,WebkitTransition:"-webkit-transform 0.2s ease-out, opacity 0.2s linear",transition:"transform 0.2s ease-out, opacity 0.2s linear"}),document.body.appendChild(this.backgroundElement),document.body.appendChild(this.domElement);var t=this;X.bind(this.backgroundElement,"click",function(){t.hide()})}return P(e,[{key:"show",value:function(){var e=this;this.backgroundElement.style.display="block",this.domElement.style.display="block",this.domElement.style.opacity=0,this.domElement.style.webkitTransform="scale(1.1)",this.layout(),S.defer(function(){e.backgroundElement.style.opacity=1,e.domElement.style.opacity=1,e.domElement.style.webkitTransform="scale(1)"})}},{key:"hide",value:function(){var e=this,t=function t(){e.domElement.style.display="none",e.backgroundElement.style.display="none",X.unbind(e.domElement,"webkitTransitionEnd",t),X.unbind(e.domElement,"transitionend",t),X.unbind(e.domElement,"oTransitionEnd",t)};X.bind(this.domElement,"webkitTransitionEnd",t),X.bind(this.domElement,"transitionend",t),X.bind(this.domElement,"oTransitionEnd",t),this.backgroundElement.style.opacity=0,this.domElement.style.opacity=0,this.domElement.style.webkitTransform="scale(1.1)"}},{key:"layout",value:function(){this.domElement.style.left=window.innerWidth/2-X.getWidth(this.domElement)/2+"px",this.domElement.style.top=window.innerHeight/2-X.getHeight(this.domElement)/2+"px"}}]),e}(),re=function(e){if(e&&"undefined"!=typeof window){var t=document.createElement("style");return t.setAttribute("type","text/css"),t.innerHTML=e,document.head.appendChild(t),e}}(".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear;border:0;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button.close-top{position:relative}.dg.main .close-button.close-bottom{position:absolute}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-y:visible}.dg.a.has-save>ul.close-top{margin-top:0}.dg.a.has-save>ul.close-bottom{margin-top:27px}.dg.a.has-save>ul.closed{margin-top:0}.dg.a .save-row{top:0;z-index:1002}.dg.a .save-row.close-top{position:relative}.dg.a .save-row.close-bottom{position:fixed}.dg li{-webkit-transition:height .1s ease-out;-o-transition:height .1s ease-out;-moz-transition:height .1s ease-out;transition:height .1s ease-out;-webkit-transition:overflow .1s linear;-o-transition:overflow .1s linear;-moz-transition:overflow .1s linear;transition:overflow .1s linear}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li>*{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px;overflow:hidden}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .cr.function .property-name{width:100%}.dg .c{float:left;width:60%;position:relative}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:7px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .cr.color{overflow:visible}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.color{border-left:3px solid}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2FA1D6}.dg .cr.number input[type=text]{color:#2FA1D6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2FA1D6;max-width:100%}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n");te.inject(re);var se="Default",ae=function(){try{return!!window.localStorage}catch(e){return!1}}(),le=void 0,de=!0,ce=void 0,ue=!1,_e=[],he=function e(t){var n=this,o=t||{};this.domElement=document.createElement("div"),this.__ul=document.createElement("ul"),this.domElement.appendChild(this.__ul),X.addClass(this.domElement,"dg"),this.__folders={},this.__controllers=[],this.__rememberedObjects=[],this.__rememberedObjectIndecesToControllers=[],this.__listening=[],o=S.defaults(o,{closeOnTop:!1,autoPlace:!0,width:e.DEFAULT_WIDTH}),o=S.defaults(o,{resizable:o.autoPlace,hideable:o.autoPlace}),S.isUndefined(o.load)?o.load={preset:se}:o.preset&&(o.load.preset=o.preset),S.isUndefined(o.parent)&&o.hideable&&_e.push(this),o.resizable=S.isUndefined(o.parent)&&o.resizable,o.autoPlace&&S.isUndefined(o.scrollable)&&(o.scrollable=!0);var i=ae&&"true"===localStorage.getItem(m(this,"isLocal")),r=void 0,s=void 0;if(Object.defineProperties(this,{parent:{get:function(){return o.parent}},scrollable:{get:function(){return o.scrollable}},autoPlace:{get:function(){return o.autoPlace}},closeOnTop:{get:function(){return o.closeOnTop}},preset:{get:function(){return n.parent?n.getRoot().preset:o.load.preset},set:function(e){n.parent?n.getRoot().preset=e:o.load.preset=e,E(this),n.revert()}},width:{get:function(){return o.width},set:function(e){o.width=e,w(n,e)}},name:{get:function(){return o.name},set:function(e){o.name=e,s&&(s.innerHTML=o.name)}},closed:{get:function(){return o.closed},set:function(t){o.closed=t,o.closed?X.addClass(n.__ul,e.CLASS_CLOSED):X.removeClass(n.__ul,e.CLASS_CLOSED),this.onResize(),n.__closeButton&&(n.__closeButton.innerHTML=t?e.TEXT_OPEN:e.TEXT_CLOSED)}},load:{get:function(){return o.load}},useLocalStorage:{get:function(){return i},set:function(e){ae&&(i=e,e?X.bind(window,"unload",r):X.unbind(window,"unload",r),localStorage.setItem(m(n,"isLocal"),e))}}}),S.isUndefined(o.parent)){if(this.closed=o.closed||!1,X.addClass(this.domElement,e.CLASS_MAIN),X.makeSelectable(this.domElement,!1),ae&&i){n.useLocalStorage=!0;var a=localStorage.getItem(m(this,"gui"));a&&(o.load=JSON.parse(a))}this.__closeButton=document.createElement("div"),this.__closeButton.innerHTML=e.TEXT_CLOSED,X.addClass(this.__closeButton,e.CLASS_CLOSE_BUTTON),o.closeOnTop?(X.addClass(this.__closeButton,e.CLASS_CLOSE_TOP),this.domElement.insertBefore(this.__closeButton,this.domElement.childNodes[0])):(X.addClass(this.__closeButton,e.CLASS_CLOSE_BOTTOM),this.domElement.appendChild(this.__closeButton)),X.bind(this.__closeButton,"click",function(){n.closed=!n.closed})}else{void 0===o.closed&&(o.closed=!0);var l=document.createTextNode(o.name);X.addClass(l,"controller-name"),s=c(n,l);X.addClass(this.__ul,e.CLASS_CLOSED),X.addClass(s,"title"),X.bind(s,"click",function(e){return e.preventDefault(),n.closed=!n.closed,!1}),o.closed||(this.closed=!1)}o.autoPlace&&(S.isUndefined(o.parent)&&(de&&(ce=document.createElement("div"),X.addClass(ce,"dg"),X.addClass(ce,e.CLASS_AUTO_PLACE_CONTAINER),document.body.appendChild(ce),de=!1),ce.appendChild(this.domElement),X.addClass(this.domElement,e.CLASS_AUTO_PLACE)),this.parent||w(n,o.width)),this.__resizeHandler=function(){n.onResizeDebounced()},X.bind(window,"resize",this.__resizeHandler),X.bind(this.__ul,"webkitTransitionEnd",this.__resizeHandler),X.bind(this.__ul,"transitionend",this.__resizeHandler),X.bind(this.__ul,"oTransitionEnd",this.__resizeHandler),this.onResize(),o.resizable&&y(this),r=function(){ae&&"true"===localStorage.getItem(m(n,"isLocal"))&&localStorage.setItem(m(n,"gui"),JSON.stringify(n.getSaveObject()))},this.saveToLocalStorageIfPossible=r,o.parent||function(){var e=n.getRoot();e.width+=1,S.defer(function(){e.width-=1})}()};he.toggleHide=function(){ue=!ue,S.each(_e,function(e){e.domElement.style.display=ue?"none":""})},he.CLASS_AUTO_PLACE="a",he.CLASS_AUTO_PLACE_CONTAINER="ac",he.CLASS_MAIN="main",he.CLASS_CONTROLLER_ROW="cr",he.CLASS_TOO_TALL="taller-than-window",he.CLASS_CLOSED="closed",he.CLASS_CLOSE_BUTTON="close-button",he.CLASS_CLOSE_TOP="close-top",he.CLASS_CLOSE_BOTTOM="close-bottom",he.CLASS_DRAG="drag",he.DEFAULT_WIDTH=245,he.TEXT_CLOSED="Close Controls",he.TEXT_OPEN="Open Controls",he._keydownHandler=function(e){"text"===document.activeElement.type||72!==e.which&&72!==e.keyCode||he.toggleHide()},X.bind(window,"keydown",he._keydownHandler,!1),S.extend(he.prototype,{add:function(e,t){return f(this,e,t,{factoryArgs:Array.prototype.slice.call(arguments,2)})},addColor:function(e,t){return f(this,e,t,{color:!0})},remove:function(e){this.__ul.removeChild(e.__li),this.__controllers.splice(this.__controllers.indexOf(e),1);var t=this;S.defer(function(){t.onResize()})},destroy:function(){if(this.parent)throw new Error("Only the root GUI should be removed with .destroy(). For subfolders, use gui.removeFolder(folder) instead.");this.autoPlace&&ce.removeChild(this.domElement);var e=this;S.each(this.__folders,function(t){e.removeFolder(t)}),X.unbind(window,"keydown",he._keydownHandler,!1),u(this)},addFolder:function(e){if(void 0!==this.__folders[e])throw new Error('You already have a folder in this GUI by the name "'+e+'"');var t={name:e,parent:this};t.autoPlace=this.autoPlace,this.load&&this.load.folders&&this.load.folders[e]&&(t.closed=this.load.folders[e].closed,t.load=this.load.folders[e]);var n=new he(t);this.__folders[e]=n;var o=c(this,n.domElement);return X.addClass(o,"folder"),n},removeFolder:function(e){this.__ul.removeChild(e.domElement.parentElement),delete this.__folders[e.name],this.load&&this.load.folders&&this.load.folders[e.name]&&delete this.load.folders[e.name],u(e);var t=this;S.each(e.__folders,function(t){e.removeFolder(t)}),S.defer(function(){t.onResize()})},open:function(){this.closed=!1},close:function(){this.closed=!0},hide:function(){this.domElement.style.display="none"},show:function(){this.domElement.style.display=""},onResize:function(){var e=this.getRoot();if(e.scrollable){var t=X.getOffset(e.__ul).top,n=0;S.each(e.__ul.childNodes,function(t){e.autoPlace&&t===e.__save_row||(n+=X.getHeight(t))}),window.innerHeight-t-20GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n\n
\n\n
\n\n'),this.parent)throw new Error("You can only call remember on a top level GUI.");var e=this;S.each(Array.prototype.slice.call(arguments),function(t){0===e.__rememberedObjects.length&&v(e),-1===e.__rememberedObjects.indexOf(t)&&e.__rememberedObjects.push(t)}),this.autoPlace&&w(this,this.width)},getRoot:function(){for(var e=this;e.parent;)e=e.parent;return e},getSaveObject:function(){var e=this.load;return e.closed=this.closed,this.__rememberedObjects.length>0&&(e.preset=this.preset,e.remembered||(e.remembered={}),e.remembered[this.preset]=x(this)),e.folders={},S.each(this.__folders,function(t,n){e.folders[n]=t.getSaveObject()}),e},save:function(){this.load.remembered||(this.load.remembered={}),this.load.remembered[this.preset]=x(this),_(this,!1),this.saveToLocalStorageIfPossible()},saveAs:function(e){this.load.remembered||(this.load.remembered={},this.load.remembered[se]=x(this,!0)),this.load.remembered[e]=x(this),this.preset=e,g(this,e,!0),this.saveToLocalStorageIfPossible()},revert:function(e){S.each(this.__controllers,function(t){this.getRoot().load.remembered?p(e||this.getRoot(),t):t.setValue(t.initialValue),t.__onFinishChange&&t.__onFinishChange.call(t,t.getValue())},this),S.each(this.__folders,function(e){e.revert(e)}),e||_(this.getRoot(),!1)},listen:function(e){var t=0===this.__listening.length;this.__listening.push(e),t&&C(this.__listening)},updateDisplay:function(){S.each(this.__controllers,function(e){e.updateDisplay()}),S.each(this.__folders,function(e){e.updateDisplay()})}});var pe={Color:I,math:N,interpret:R},fe={Controller:z,BooleanController:K,OptionController:Y,StringController:J,NumberController:W,NumberControllerBox:Q,NumberControllerSlider:q,FunctionController:Z,ColorController:$},me={dom:X},ge={GUI:he},be=he,ve={color:pe,controllers:fe,dom:me,gui:ge,GUI:be};e.color=pe,e.controllers=fe,e.dom=me,e.gui=ge,e.GUI=be,e.default=ve,Object.defineProperty(e,"__esModule",{value:!0})}); -------------------------------------------------------------------------------- /web/js/three-spritetext.min.js: -------------------------------------------------------------------------------- 1 | // Version 1.6.5 three-spritetext - https://github.com/vasturiano/three-spritetext 2 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("three")):"function"==typeof define&&define.amd?define(["three"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).SpriteText=e(t.THREE)}(this,(function(t){"use strict";function e(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function r(t,e){for(var r=0;rt.length)&&(e=t.length);for(var r=0,i=new Array(e);r0&&void 0!==arguments[0]?arguments[0]:"",i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:10,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"rgba(255, 255, 255, 1)";return e(this,l),(t=c.call(this,new f.SpriteMaterial))._text="".concat(r),t._textHeight=i,t._color=n,t._backgroundColor=!1,t._padding=0,t._borderWidth=0,t._borderRadius=0,t._borderColor="white",t._strokeWidth=0,t._strokeColor="white",t._fontFace="Arial",t._fontSize=90,t._fontWeight="normal",t._canvas=document.createElement("canvas"),t._genCanvas(),t}return i=l,(o=[{key:"text",get:function(){return this._text},set:function(t){this._text=t,this._genCanvas()}},{key:"textHeight",get:function(){return this._textHeight},set:function(t){this._textHeight=t,this._genCanvas()}},{key:"color",get:function(){return this._color},set:function(t){this._color=t,this._genCanvas()}},{key:"backgroundColor",get:function(){return this._backgroundColor},set:function(t){this._backgroundColor=t,this._genCanvas()}},{key:"padding",get:function(){return this._padding},set:function(t){this._padding=t,this._genCanvas()}},{key:"borderWidth",get:function(){return this._borderWidth},set:function(t){this._borderWidth=t,this._genCanvas()}},{key:"borderRadius",get:function(){return this._borderRadius},set:function(t){this._borderRadius=t,this._genCanvas()}},{key:"borderColor",get:function(){return this._borderColor},set:function(t){this._borderColor=t,this._genCanvas()}},{key:"fontFace",get:function(){return this._fontFace},set:function(t){this._fontFace=t,this._genCanvas()}},{key:"fontSize",get:function(){return this._fontSize},set:function(t){this._fontSize=t,this._genCanvas()}},{key:"fontWeight",get:function(){return this._fontWeight},set:function(t){this._fontWeight=t,this._genCanvas()}},{key:"strokeWidth",get:function(){return this._strokeWidth},set:function(t){this._strokeWidth=t,this._genCanvas()}},{key:"strokeColor",get:function(){return this._strokeColor},set:function(t){this._strokeColor=t,this._genCanvas()}},{key:"_genCanvas",value:function(){var t=this,e=this._canvas,r=e.getContext("2d"),i=Array.isArray(this.borderWidth)?this.borderWidth:[this.borderWidth,this.borderWidth],n=i.map((function(e){return e*t.fontSize*.1})),o=(Array.isArray(this.borderRadius)?this.borderRadius:[this.borderRadius,this.borderRadius,this.borderRadius,this.borderRadius]).map((function(e){return e*t.fontSize*.1})),a=Array.isArray(this.padding)?this.padding:[this.padding,this.padding],u=a.map((function(e){return e*t.fontSize*.1})),c=this.text.split("\n"),l="".concat(this.fontWeight," ").concat(this.fontSize,"px ").concat(this.fontFace);r.font=l;var d=Math.max.apply(Math,s(c.map((function(t){return r.measureText(t).width})))),g=this.fontSize*c.length;if(e.width=d+2*n[0]+2*u[0],e.height=g+2*n[1]+2*u[1],this.borderWidth){if(r.strokeStyle=this.borderColor,n[0]){var p=n[0]/2;r.lineWidth=n[0],r.beginPath(),r.moveTo(p,o[0]),r.lineTo(p,e.height-o[3]),r.moveTo(e.width-p,o[1]),r.lineTo(e.width-p,e.height-o[2]),r.stroke()}if(n[1]){var y=n[1]/2;r.lineWidth=n[1],r.beginPath(),r.moveTo(Math.max(n[0],o[0]),y),r.lineTo(e.width-Math.max(n[0],o[1]),y),r.moveTo(Math.max(n[0],o[3]),e.height-y),r.lineTo(e.width-Math.max(n[0],o[2]),e.height-y),r.stroke()}if(this.borderRadius){var b=Math.max.apply(Math,s(n)),v=b/2;r.lineWidth=b,r.beginPath(),[!!o[0]&&[o[0],v,v,o[0]],!!o[1]&&[e.width-o[1],e.width-v,v,o[1]],!!o[2]&&[e.width-o[2],e.width-v,e.height-v,e.height-o[2]],!!o[3]&&[o[3],v,e.height-v,e.height-o[3]]].filter((function(t){return t})).forEach((function(t){var e=h(t,4),i=e[0],n=e[1],o=e[2],a=e[3];r.moveTo(i,o),r.quadraticCurveTo(n,o,n,a)})),r.stroke()}}this.backgroundColor&&(r.fillStyle=this.backgroundColor,this.borderRadius?(r.beginPath(),r.moveTo(n[0],o[0]),[[n[0],o[0],e.width-o[1],n[1],n[1],n[1]],[e.width-n[0],e.width-n[0],e.width-n[0],n[1],o[1],e.height-o[2]],[e.width-n[0],e.width-o[2],o[3],e.height-n[1],e.height-n[1],e.height-n[1]],[n[0],n[0],n[0],e.height-n[1],e.height-o[3],o[0]]].forEach((function(t){var e=h(t,6),i=e[0],n=e[1],o=e[2],a=e[3],s=e[4],u=e[5];r.quadraticCurveTo(i,a,n,s),r.lineTo(o,u)})),r.closePath(),r.fill()):r.fillRect(n[0],n[1],e.width-2*n[0],e.height-2*n[1])),r.translate.apply(r,s(n)),r.translate.apply(r,s(u)),r.font=l,r.fillStyle=this.color,r.textBaseline="bottom";var _=this.strokeWidth>0;_&&(r.lineWidth=this.strokeWidth*this.fontSize/10,r.strokeStyle=this.strokeColor),c.forEach((function(e,i){var n=(d-r.measureText(e).width)/2,o=(i+1)*t.fontSize;_&&r.strokeText(e,n,o),r.fillText(e,n,o)})),this.material.map&&this.material.map.dispose();var m=this.material.map=new f.Texture(e);m.minFilter=f.LinearFilter,m.needsUpdate=!0;var w=this.textHeight*c.length+2*i[1]+2*a[1];this.scale.set(w*e.width/e.height,w,0)}},{key:"clone",value:function(){return new this.constructor(this.text,this.textHeight,this.color).copy(this)}},{key:"copy",value:function(t){return f.Sprite.prototype.copy.call(this,t),this.color=t.color,this.backgroundColor=t.backgroundColor,this.padding=t.padding,this.borderWidth=t.borderWidth,this.borderColor=t.borderColor,this.fontFace=t.fontFace,this.fontSize=t.fontSize,this.fontWeight=t.fontWeight,this.strokeWidth=t.strokeWidth,this.strokeColor=t.strokeColor,this}}])&&r(i.prototype,o),u&&r(i,u),Object.defineProperty(i,"prototype",{writable:!1}),l}(f.Sprite);return l})); --------------------------------------------------------------------------------