├── .github └── ISSUE_TEMPLATE │ ├── Bug_report.md │ ├── Feature_request.md │ └── Support_question.md ├── .gitignore ├── .travis.yml ├── .whitesource ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── apiscout.yml ├── build-docs.sh ├── docs ├── .DS_Store ├── archetypes │ └── default.md ├── config.toml ├── content │ ├── _index.md │ ├── building-apiscout │ │ └── _index.md │ ├── contributing │ │ └── _index.md │ ├── dependencies │ │ └── _index.md │ ├── deploying-apiscout │ │ └── _index.md │ ├── getting-started │ │ └── _index.md │ └── what-is-apiscout │ │ └── _index.md ├── layouts │ └── partials │ │ ├── header.html │ │ └── logo.html └── static │ ├── css │ └── theme.css │ └── images │ └── getting-started │ ├── Picture1.png │ ├── Picture2.png │ └── Picture3.png ├── nginx ├── default.conf └── start.sh ├── samples └── invoiceservice-go │ ├── .gitignore │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── invoice-go-svc.yml │ ├── main.go │ └── swagger.json ├── server ├── main.go ├── server │ ├── event.go │ ├── retry.go │ ├── server.go │ ├── service.go │ └── service_test.go └── util │ ├── apidoc.go │ ├── hugo.go │ └── os.go └── webapp ├── archetypes └── default.md ├── config.toml ├── content ├── _index.md └── apis │ └── _index.md ├── layouts ├── partials │ ├── header.html │ └── logo.html └── shortcodes │ └── oas3.html ├── static ├── css │ ├── reset.css │ └── theme-green.css ├── oas3 │ ├── LICENSE │ ├── oauth2-redirect.html │ ├── swagger-ui-bundle.js │ ├── swagger-ui-bundle.js.map │ ├── swagger-ui-standalone-preset.js │ ├── swagger-ui-standalone-preset.js.map │ ├── swagger-ui.css │ ├── swagger-ui.css.map │ ├── swagger-ui.js │ └── swagger-ui.js.map └── swaggerdocs │ └── .gitkeep └── themes └── hugo-theme-learn ├── .gitignore ├── LICENSE.md ├── README.md ├── archetypes ├── chapter.md └── default.md ├── i18n ├── en.toml ├── es.toml ├── fr.toml └── pt.toml ├── images ├── screenshot.png └── tn.png ├── layouts ├── 404.html ├── _default │ ├── list.html │ └── single.html ├── index.html ├── index.json ├── partials │ ├── custom-comments.html │ ├── custom-footer.html │ ├── custom-header.html │ ├── favicon.html │ ├── footer.html │ ├── header.html │ ├── logo.html │ ├── menu-footer.html │ ├── menu.html │ ├── meta.html │ ├── search.html │ └── toc.html └── shortcodes │ ├── attachments.html │ ├── button.html │ ├── children.html │ ├── expand.html │ ├── mermaid.html │ ├── notice.html │ ├── ref.html │ ├── relref.html │ └── siteparam.html ├── static ├── css │ ├── auto-complete.css │ ├── featherlight.min.css │ ├── font-awesome.min.css │ ├── hugo-theme.css │ ├── hybrid.css │ ├── nucleus.css │ ├── perfect-scrollbar.min.css │ ├── theme-blue.css │ ├── theme-green.css │ ├── theme-red.css │ └── theme.css ├── fonts │ ├── FontAwesome.otf │ ├── Inconsolata.eot │ ├── Inconsolata.svg │ ├── Inconsolata.ttf │ ├── Inconsolata.woff │ ├── Novecentosanswide-Normal-webfont.eot │ ├── Novecentosanswide-Normal-webfont.svg │ ├── Novecentosanswide-Normal-webfont.ttf │ ├── Novecentosanswide-Normal-webfont.woff │ ├── Novecentosanswide-Normal-webfont.woff2 │ ├── Novecentosanswide-UltraLight-webfont.eot │ ├── Novecentosanswide-UltraLight-webfont.svg │ ├── Novecentosanswide-UltraLight-webfont.ttf │ ├── Novecentosanswide-UltraLight-webfont.woff │ ├── Novecentosanswide-UltraLight-webfont.woff2 │ ├── Work_Sans_200.eot │ ├── Work_Sans_200.svg │ ├── Work_Sans_200.ttf │ ├── Work_Sans_200.woff │ ├── Work_Sans_200.woff2 │ ├── Work_Sans_300.eot │ ├── Work_Sans_300.svg │ ├── Work_Sans_300.ttf │ ├── Work_Sans_300.woff │ ├── Work_Sans_300.woff2 │ ├── Work_Sans_500.eot │ ├── Work_Sans_500.svg │ ├── Work_Sans_500.ttf │ ├── Work_Sans_500.woff │ ├── Work_Sans_500.woff2 │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── images │ ├── clippy.svg │ ├── favicon.png │ └── gopher-404.jpg ├── js │ ├── auto-complete.js │ ├── clipboard.min.js │ ├── featherlight.min.js │ ├── highlight.pack.js │ ├── html5shiv-printshiv.min.js │ ├── hugo-learn.js │ ├── jquery-3.x.min.js │ ├── jquery.sticky.js │ ├── learn.js │ ├── lunr.min.js │ ├── modernizr.custom.71422.js │ ├── perfect-scrollbar.jquery.min.js │ ├── perfect-scrollbar.min.js │ └── search.js └── mermaid │ ├── mermaid.css │ ├── mermaid.dark.css │ ├── mermaid.forest.css │ └── mermaid.js ├── theme.toml └── wercker.yml /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: If something isn't working as expected 🤔. 4 | 5 | --- 6 | 7 | **Current behavior (how does the issue manifest):** 8 | 9 | **Expected behavior:** 10 | 11 | **Minimal steps to reproduce the problem (not required if feature enhancement):** 12 | 13 | **Please tell us about your environment (Operating system, docker version, browser & web ui version, etc):** 14 | 15 | **Version (If unknown, leave empty or state unknown):** 0.X.X 16 | 17 | **Additional information you deem important (e.g. issue happens only occasionally):** 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature Request 3 | about: I have a suggestion (and may want to implement it 🙂)! 4 | 5 | --- 6 | 7 | **Current behavior:** 8 | 9 | **Expected behavior:** 10 | 11 | **What is the motivation / use case for changing the behavior?** 12 | 13 | **Additional information you deem important (e.g. I need this tomorrow):** -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Support_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🤗 Support Question 3 | about: If you have a question 💬, feel free to ask! 4 | 5 | --- 6 | 7 | **What is your question?** 8 | 9 | **Please tell us about your environment (Operating system, docker version, browser & web ui version, etc):** 10 | 11 | **Version (If unknown, leave empty or state unknown):** 0.X.X -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | webapp/public 3 | docs/public -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: go 4 | 5 | go: 6 | - master 7 | 8 | os: 9 | - linux 10 | 11 | branches: 12 | only: 13 | - master 14 | 15 | install: 16 | - chmod +x build-docs.sh 17 | 18 | script: 19 | - ./build-docs.sh prerequisites 20 | - ./build-docs.sh build 21 | 22 | deploy: 23 | provider: pages 24 | skip_cleanup: true 25 | local-dir: docs/public 26 | github_token: $GITHUB_TOKEN 27 | email: $EMAIL 28 | on: 29 | branch: master -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "generalSettings": { 3 | "shouldScanRepo": true 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure" 7 | } 8 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to API Scout 2 | 3 | Are you interested in contributing to API Scout? If so, this doc was created specifically for you! If you’re not ready to start contributing code, no problem, feel free to check out any of the issues and begin by helping to enhance any other part of the tool! 4 | 5 | ## How to make a contribution 6 | 7 | Never made an open source contribution before? Wondering how contributions work in our project? Here's a quick rundown! 8 | 9 | If you have any questions, feel free to post an issue and tag it as a question: 10 | 11 | * Find an issue that you are interested in addressing or a feature that you would like to add. Look for issues labeled `good first issue`, `kind/help-wanted` if you’re unsure where to begin. 12 | * Fork the repository associated with the issue to your local GitHub account. This means that you will have a copy of the repository under github-username/repository-name. 13 | * Clone the repository to your local machine using `git clone https://github.com/github-username/repository-name.git`. 14 | * Create a new branch for your fix using `git checkout -b branch-name-here`. 15 | * Make the appropriate changes for the issue you are trying to address or the feature that you want to add. 16 | * Use `git add insert-paths-of-changed-files-here` to add the file contents of the changed files to the "snapshot" git uses to manage the state of the project, also known as the index. 17 | * Use `git commit -m "Insert a short message of the changes made here"` to store the contents of the index with a descriptive message. 18 | * Push the changes to the remote repository using `git push origin branch-name-here`. 19 | * Submit a pull request to the upstream repository. 20 | * Title the pull request with a short description of the changes made and the issue or bug number associated with your change. For example, you can title an issue like: "Updating docs as outlined in #4352". 21 | * In the description of the pull request, explain the changes that you made, any issues you think exist with the pull request you made, and any questions you have for the maintainers. 22 | * Wait for the pull request to be reviewed by a maintainers. 23 | * Make changes to the pull request if the reviewing maintainer recommends them. 24 | * Congratulations, you’ve contributed to API Scout and a celebration is in order! 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.15-alpine 2 | ADD . /tmp 3 | RUN wget https://github.com/gohugoio/hugo/releases/download/v0.45.1/hugo_0.45.1_Linux-64bit.tar.gz \ 4 | && tar -xzvf hugo_0.45.1_Linux-64bit.tar.gz hugo -C /bin && rm hugo_0.45.1_Linux-64bit.tar.gz \ 5 | && rm -rf /usr/share/nginx/html \ 6 | && ln -s /tmp/public/ /usr/share/nginx/html \ 7 | && cd /tmp \ 8 | && hugo \ 9 | && cp ./nginx/default.conf /etc/nginx/conf.d/default.conf 10 | CMD ["/bin/sh", "/tmp/nginx/start.sh"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, TIBCO Software Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of TIBCO Software Inc. nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-all clean-docker clean-kube build-site build-server build-docker build-all run-server run-docker run-hugo run-kube 2 | 3 | #--- Variables --- 4 | CURRDIR = `pwd` 5 | EXTIP = `minikube ip` 6 | USER = `whoami` 7 | DOCKERREPO = `whoami` 8 | KUBEFILES = . 9 | 10 | #--- Help --- 11 | help: 12 | @echo 13 | @echo Makefile targets 14 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 15 | @echo 16 | 17 | #--- Clean up targets --- 18 | clean-all: ## Removes the dist directory 19 | rm -rf ./dist 20 | 21 | clean-docker: ## Stops and removes all containers and images for apiscout 22 | docker stop apiscout 23 | docker rm apiscout 24 | docker rmi retgits/apiscout 25 | 26 | clean-kube: ## Removes the apiscout service and deployment from Kubernetes 27 | kubectl delete svc apiscout-svc 28 | kubectl delete deployment apiscout 29 | 30 | #--- Get dependencies --- 31 | deps: ## Get dependencies to build the server 32 | go get -u github.com/TIBCOSoftware/apiscout/server 33 | 34 | #--- Build targets --- 35 | build-site: ## Builds the Hugo distribution in dist 36 | mkdir -p dist 37 | cp -r webapp/* ./dist 38 | 39 | build-server: ## Builds the server app in dist 40 | mkdir -p dist 41 | cd server && go generate && GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ../dist/server *.go 42 | 43 | build-docker: ## Builds a docker image from the dist directory 44 | cp Dockerfile ./dist/Dockerfile 45 | cp -R ./nginx/ ./dist/nginx 46 | eval $$(minikube docker-env) ;\ 47 | cd dist && docker image build . -t $(DOCKERREPO)/apiscout:latest 48 | 49 | build-all: clean-all build-site build-server build-docker ## Performs clean-all and executes all build targets 50 | 51 | #--- Run targets --- 52 | run-server: ## Builds the in the server directory and runs it with default settings 53 | cd server && go generate && GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ../dist/server *.go 54 | MODE=LOCAL HUGODIR=$(CURRDIR)/webapp HUGOSTORE=$(CURRDIR)/webapp/content/apis SWAGGERSTORE=$(CURRDIR)/webapp/static/swaggerdocs EXTERNALIP=$(EXTIP) ./dist/server 55 | 56 | run-docker: ## Runs a docker container with default settings 57 | docker run -it --rm -p 80:80 -v $(HOME)/.kube:/root/.kube -v $(HOME)/.minikube:/home/$(USER)/.minikube -e MODE=LOCAL -e HUGODIR="/tmp" -e EXTERNALIP=$(EXTIP) -e HUGOCMD="sh -c \"cd /tmp && hugo\"" --name=apiscout $(DOCKERREPO)/apiscout:latest 58 | 59 | run-hugo: ## Runs the embedded Hugo server on port 1313 60 | cd webapp && hugo server -D --disableFastRender 61 | 62 | run-docs: ## Runs the embedded Hugo server on port 1313 for the documentation 63 | cd docs && hugo server -D --disableFastRender --themesDir ../webapp/themes 64 | 65 | run-kube: ## Deploys apiscout to Kubernetes 66 | kubectl apply -f ${KUBEFILES}/apiscout.yml 67 | 68 | #--- Stop targets --- 69 | stop-docker: ## Stop and remove the running apiscout container 70 | docker stop apiscout && docker rm apiscout 71 | 72 | #--- Minikube targets --- 73 | minikube-install: ## Install Minikube on this machine 74 | curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube && sudo cp minikube /usr/local/bin/ && rm minikube 75 | minikube-start: ## Start Minikube with default configuration 76 | minikube start 77 | minikube-stop: ## Stop Minikube 78 | minikube stop 79 | minikube-delete: minikube-stop ## Delete the Minikube installation 80 | minikube delete 81 | minikube-show: ## Show the API Scout UI that is deployed to Minikube 82 | open `minikube service apiscout-svc --url` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API Scout :: Finding Your APIs In Kubernetes 2 | 3 | ## What is API Scout 4 | 5 | API Scout, helps you get up-to-date API docs to your developers by simply annotating your services in K8s. 6 | 7 | ## What are the components of API Scout 8 | 9 | The docker image that is deployed to Kubernetes has several components: 10 | 11 | * The container itself is based on [nginx:1.15-alpine](https://hub.docker.com/_/nginx/) 12 | * The webapp is a staticly generated site by [Hugo](https://github.com/gohugoio/hugo) using the [Learn](https://themes.gohugo.io/hugo-theme-learn/) theme and [Swagger UI](https://github.com/swagger-api/swagger-ui/releases) 13 | * A server app that connects to the Kubernetes cluster using a default role to watch for services that need to be indexed 14 | 15 | _Hugo is downloaded and embedded during the build of the container_ 16 | 17 | ## Build and run 18 | 19 | API Scout has a _Makefile_ that can be used for most of the operations. Make sure you have installed Go Programming Language, set GOPATH variable and added $GOPATH/bin in your PATH 20 | 21 | ```bash 22 | usage: make [target] 23 | ``` 24 | 25 | | Makefile targets | Description | 26 | |------------------|-----------------------------------------------------------------------| 27 | | build-all | Performs clean-all and executes all build targets | 28 | | build-docker | Builds a docker image from the dist directory | 29 | | build-server | Builds the server app in dist | 30 | | build-site | Builds the Hugo distribution in dist | 31 | | clean-all | Removes the dist directory | 32 | | clean-docker | Stops and removes all containers and images for apiscout | 33 | | clean-kube | Removes the apiscout service and deployment from Kubernetes | 34 | | deps | Get dependencies to build the server | 35 | | minikube-delete | Delete the Minikube installation | 36 | | minikube-install | Install Minikube on this machine | 37 | | minikube-show | Show the API Scout UI that is deployed to Minikube | 38 | | minikube-start | Start Minikube with default configuration | 39 | | minikube-stop | Stop Minikube | 40 | | run-docker | Runs a docker container with default settings | 41 | | run-docs | Runs the embedded Hugo server on port 1313 for the documentation | 42 | | run-hugo | Runs the embedded Hugo server on port 1313 | 43 | | run-kube | Deploys apiscout to Kubernetes | 44 | | run-server | Builds the in the server directory and runs it with default settings | 45 | | stop-docker | Stop and remove the running apiscout container | 46 | 47 | ## Requirements for Kubernetes 48 | 49 | To be able to view the services, apiscout needs access to the Kubernetes cluster using the default service account. By default (pun intended) this account doesn't have access to view services so during deployment a new _rolebinding_ is created. After starting, it will register a watcher with the Kubernetes API Server, so that it receives events when something occurs in the cluster. 50 | 51 | apiscout looks for two annotations to be able to index a service: 52 | 53 | * `apiscout/index: 'true'` This annotation ensures that apiscout indexes the service 54 | * `apiscout/swaggerUrl: '/swaggerspec'` This is the URL from where apiscout will read the OpenAPI document 55 | 56 | ## Environment variables for the docker container 57 | 58 | apiscout has a few environment variables that the docker container (and thus the deployment to Kubernetes) can use: 59 | 60 | * **SWAGGERSTORE**: The location where to store the swaggerdocs 61 | * **HUGOSTORE**: The location where to store content for Hugo 62 | * **MODE**: The mode in which apiscout is running (can be either KUBE or LOCAL) 63 | * **EXTERNALIP**: The external IP address of the Kubernetes cluster in case of LOCAL mode 64 | * **HUGODIR**: The base directory for Hugo 65 | 66 | ## Getting started 67 | 68 | This section provides minimal steps to get `apiscout` running inside a kubernetes cluster on local machine / VM of your choice. 69 | 70 | ### Prerequisites 71 | 72 | * Docker 73 | * Kubernetes environment (for example [minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/)) 74 | 75 | ### Steps to follow 76 | 77 | 1. Start minikube 78 | ```bash 79 | $ make minikube-start 80 | ``` 81 | 82 | 2. Build and deploy apiscout 83 | 84 | ```bash 85 | # Install dependencies 86 | $ make deps 87 | 88 | # Build apiscout docker image 89 | $ make build-all 90 | ``` 91 | * Update `image` with apiscout docker image name built in the previous step and `EXTERNALIP` value with minikube IP in apiscout.yml 92 | 93 | ```bash 94 | # Deploy apiscout to Kubernetes 95 | $ make run-kube 96 | ``` 97 | 98 | 3. Build and deploy sample micro service 99 | 100 | ```bash 101 | # Navigate to samples/invoiceservice-go folder 102 | $ cd samples/invoiceservice-go 103 | 104 | # Install dependencies 105 | $ make deps 106 | 107 | # Build sample microservice application 108 | $ make build-app 109 | 110 | # Build dcoker image with the sample microservice application 111 | $ make build-docker 112 | 113 | # Deploy sample application to Kubernetes 114 | $ make run-kube 115 | 116 | ``` 117 | 118 | ### Testing 119 | 120 | 121 | ```bash 122 | # Navigate back to apiscout directory 123 | $ cd ../.. 124 | 125 | # Open kubernetes service url in a web browser to see sample application api specification in swagger format 126 | $ make minikube-show 127 | 128 | ``` 129 | Note: If you face any issue with above command in linux, update Makefile with `xdg-open` instead of `open` command under `minikube-show` target. 130 | 131 | ### Cleanup 132 | 133 | ```bash 134 | # Delete sample application from Kubernetes 135 | $ cd samples/invoiceservice-go 136 | $ make clean-kube 137 | 138 | # Delete apiscout from Kubernetes 139 | $ cd ../.. 140 | $ make clean-kube 141 | 142 | # Stop minikube 143 | $ make minikube-stop 144 | ``` 145 | 146 | ## License 147 | See the [LICENSE](./LICENSE) file 148 | 149 | _The logo made by [Freepik](http://www.freepik.com) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/)_ -------------------------------------------------------------------------------- /apiscout.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1beta1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: default-view 6 | roleRef: 7 | apiGroup: rbac.authorization.k8s.io 8 | kind: ClusterRole 9 | name: view 10 | subjects: 11 | - kind: ServiceAccount 12 | name: default 13 | namespace: default 14 | --- 15 | apiVersion: extensions/v1beta1 16 | kind: Deployment 17 | metadata: 18 | labels: 19 | run: apiscout 20 | name: apiscout 21 | namespace: default 22 | spec: 23 | replicas: 1 24 | selector: 25 | matchLabels: 26 | run: apiscout 27 | template: 28 | metadata: 29 | labels: 30 | run: apiscout 31 | spec: 32 | containers: 33 | - name: apiscout 34 | image: 35 | env: 36 | - name: MODE 37 | value: "KUBE" 38 | - name: HUGODIR 39 | value: "/tmp" 40 | - name: EXTERNALIP 41 | value: "192.168.99.100" 42 | imagePullPolicy: Never 43 | ports: 44 | - containerPort: 80 45 | --- 46 | apiVersion: v1 47 | kind: Service 48 | metadata: 49 | labels: 50 | run: apiscout-svc 51 | name: apiscout-svc 52 | namespace: default 53 | spec: 54 | ports: 55 | - port: 8181 56 | protocol: TCP 57 | targetPort: 80 58 | selector: 59 | run: apiscout 60 | type: LoadBalancer -------------------------------------------------------------------------------- /build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Description: Build script for APIScout documentation 4 | # Author: retgits 5 | # Last Updated: 2018-10-07 6 | 7 | #--- Variables --- 8 | HUGO_VERSION=0.49 9 | 10 | #--- Download and install prerequisites --- 11 | prerequisites() { 12 | wget -O hugo.tar.gz https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz 13 | mkdir -p hugobin 14 | tar -xzvf hugo.tar.gz -C ./hugobin 15 | mv ./hugobin/hugo $HOME/gopath/bin 16 | rm hugo.tar.gz && rm -rf ./hugobin 17 | } 18 | 19 | #--- Execute build --- 20 | build() { 21 | echo "Build docs site..." 22 | cd docs && hugo --themesDir ../webapp/themes 23 | cd public && ls -alh 24 | } 25 | 26 | 27 | case "$1" in 28 | "prerequisites") 29 | prerequisites 30 | ;; 31 | "build") 32 | build 33 | ;; 34 | *) 35 | echo "The target {$1} you want to execute doesn't exist" 36 | esac -------------------------------------------------------------------------------- /docs/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/docs/.DS_Store -------------------------------------------------------------------------------- /docs/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/config.toml: -------------------------------------------------------------------------------- 1 | baseURL = "https://tibcosoftware.github.io/apiscout/" 2 | languageCode = "en-US" 3 | defaultContentLanguage = "en" 4 | 5 | title = "API Scout :: Finding your APIs in Kubernetes" 6 | theme = "hugo-theme-learn" 7 | metaDataFormat = "yaml" 8 | defaultContentLanguageInSubdir = true 9 | 10 | [params] 11 | editURL = "" 12 | description = "Documentation for API Scout" 13 | author = "API Scout" 14 | showVisitedLinks = false 15 | disableAssetsBusting = false 16 | disableInlineCopyToClipBoard = true 17 | disableShortcutsTitle = true 18 | disableLanguageSwitchingButton = true 19 | ordersectionsby = "weight" 20 | themeVariant = "green" 21 | 22 | [outputs] 23 | home = [ "HTML", "RSS", "JSON"] 24 | -------------------------------------------------------------------------------- /docs/content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | weight: 1 4 | chapter: true 5 | --- 6 | 7 | # API Scout 8 | 9 | Finding Your APIs In Kubernetes -------------------------------------------------------------------------------- /docs/content/building-apiscout/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Building API Scout 3 | weight: 4000 4 | --- 5 | 6 | # Building API Scout 7 | 8 | API Scout has a _Makefile_ that can be used for most of the operations. Make sure you have installed Go Programming Language, set GOPATH variable and added $GOPATH/bin in your PATH 9 | 10 | ```bash 11 | usage: make [target] 12 | ``` 13 | 14 | # Build targets 15 | 16 | | Target | Description | 17 | |------------------|-----------------------------------------------------------------------| 18 | | deps | Get dependencies to build the server | 19 | | build-all | Performs clean-all and executes all build targets | 20 | | build-docker | Builds a docker image from the dist directory | 21 | | build-server | Builds the server app in dist | 22 | | build-site | Builds the Hugo distribution in dist | 23 | 24 | # Clean targets 25 | 26 | | Target | Description | 27 | |------------------|-----------------------------------------------------------------------| 28 | | clean-all | Removes the dist directory | 29 | | clean-docker | Stops and removes all containers and images for apiscout | 30 | | clean-kube | Removes the apiscout service and deployment from Kubernetes | 31 | 32 | # Minikube targets 33 | 34 | | Target | Description | 35 | |------------------|-----------------------------------------------------------------------| 36 | | minikube-delete | Delete the Minikube installation | 37 | | minikube-install | Install Minikube on this machine | 38 | | minikube-show | Show the API Scout UI that is deployed to Minikube | 39 | | minikube-start | Start Minikube with default configuration | 40 | | minikube-stop | Stop Minikube | 41 | 42 | # Docker targets 43 | 44 | | Target | Description | 45 | |------------------|-----------------------------------------------------------------------| 46 | | run-docker | Runs a docker container with default settings | 47 | | run-docs | Runs the embedded Hugo server on port 1313 for the documentation | 48 | | run-hugo | Runs the embedded Hugo server on port 1313 | 49 | | run-kube | Deploys apiscout to Kubernetes | 50 | | run-server | Builds the in the server directory and runs it with default settings | 51 | | stop-docker | Stop and remove the running apiscout container | -------------------------------------------------------------------------------- /docs/content/contributing/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Contributing 3 | weight: 5000 4 | --- 5 | 6 | # Contributing to API Scout 7 | 8 | Are you interested in contributing to API Scout? If so, this doc was created specifically for you! If you’re not ready to start contributing code, no problem, feel free to check out any of the issues and begin by helping to enhance any other part of the tool! 9 | 10 | # How to make a contribution 11 | 12 | Never made an open source contribution before? Wondering how contributions work in our project? Here's a quick rundown! 13 | 14 | If you have any questions, feel free to post an issue and tag it as a question: 15 | 16 | * Find an issue that you are interested in addressing or a feature that you would like to add. Look for issues labeled `good first issue`, `kind/help-wanted` if you’re unsure where to begin. 17 | * Fork the repository associated with the issue to your local GitHub account. This means that you will have a copy of the repository under github-username/repository-name. 18 | * Clone the repository to your local machine using `git clone https://github.com/github-username/repository-name.git`. 19 | * Create a new branch for your fix using `git checkout -b branch-name-here`. 20 | * Make the appropriate changes for the issue you are trying to address or the feature that you want to add. 21 | * Use `git add insert-paths-of-changed-files-here` to add the file contents of the changed files to the "snapshot" git uses to manage the state of the project, also known as the index. 22 | * Use `git commit -m "Insert a short message of the changes made here"` to store the contents of the index with a descriptive message. 23 | * Push the changes to the remote repository using `git push origin branch-name-here`. 24 | * Submit a pull request to the upstream repository. 25 | * Title the pull request with a short description of the changes made and the issue or bug number associated with your change. For example, you can title an issue like: "Updating docs as outlined in #4352". 26 | * In the description of the pull request, explain the changes that you made, any issues you think exist with the pull request you made, and any questions you have for the maintainers. 27 | * Wait for the pull request to be reviewed by a maintainers. 28 | * Make changes to the pull request if the reviewing maintainer recommends them. 29 | * Congratulations, you’ve contributed to API Scout and a celebration is in order! 30 | -------------------------------------------------------------------------------- /docs/content/dependencies/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Dependencies 3 | weight: 6000 4 | --- 5 | 6 | ## What are the components of API Scout 7 | 8 | The docker image that is deployed to Kubernetes has several components: 9 | 10 | * The container itself is based on [nginx:1.15-alpine](https://hub.docker.com/_/nginx/) 11 | * The webapp is a staticly generated site by [Hugo](https://github.com/gohugoio/hugo) using the [Learn](https://themes.gohugo.io/hugo-theme-learn/) theme and [Swagger UI](https://github.com/swagger-api/swagger-ui/releases) 12 | * A server app that connects to the Kubernetes cluster using a default role to watch for services that need to be indexed 13 | 14 | _Hugo is downloaded and embedded during the build of the container_ 15 | 16 | ## Logo 17 | 18 | The logo made by [Freepik](http://www.freepik.com) from [www.flaticon.com](https://www.flaticon.com/) is licensed by [CC 3.0 BY](http://creativecommons.org/licenses/by/3.0/) -------------------------------------------------------------------------------- /docs/content/deploying-apiscout/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Deploying API Scout 3 | weight: 3000 4 | --- 5 | 6 | # Deploy to Kubernetes 7 | 8 | You can deploy API Scout to Kubernetes by following three easy steps 9 | 10 | ## Step 1: Create an RBAC role 11 | 12 | Assuming you want to run API Scout inside your Kubernetes cluster, which is the recommended option, you'll need to create an _RBAC role_ so that the ServiceAccount has view access to the Kubernetes API server. This is the least privileged option for API Scout. 13 | 14 | ```yaml 15 | apiVersion: rbac.authorization.k8s.io/v1beta1 16 | kind: ClusterRoleBinding 17 | metadata: 18 | name: default-view 19 | roleRef: 20 | apiGroup: rbac.authorization.k8s.io 21 | kind: ClusterRole 22 | name: view 23 | subjects: 24 | - kind: ServiceAccount 25 | name: default 26 | namespace: default 27 | ``` 28 | 29 | ## Step 2: Create a deployment 30 | 31 | The second step is to create a _deployment_, instructing Kubernetes to deploy API Scout. Using the template yaml file below, there are a few parameters you can update: 32 | 33 | * **image**: The name of the docker image to deploy 34 | * **EXTERNALIP**: The value decides if the basePath of an API specification will be overwritten with this value to enable the "try it out" option 35 | * **SWAGGERSTORE**: The location where to store the swaggerdocs 36 | * **HUGOSTORE**: The location where to store content for Hugo 37 | * **MODE**: The mode in which apiscout is running (can be either KUBE or LOCAL) 38 | * **HUGODIR**: The base directory for Hugo 39 | 40 | ```yaml 41 | apiVersion: extensions/v1beta1 42 | kind: Deployment 43 | metadata: 44 | labels: 45 | run: apiscout 46 | name: apiscout 47 | namespace: default 48 | spec: 49 | replicas: 1 50 | selector: 51 | matchLabels: 52 | run: apiscout 53 | template: 54 | metadata: 55 | labels: 56 | run: apiscout 57 | spec: 58 | containers: 59 | - name: apiscout 60 | image: 61 | env: 62 | - name: MODE 63 | value: "KUBE" 64 | - name: HUGODIR 65 | value: "/tmp" 66 | - name: EXTERNALIP 67 | value: "192.168.99.100" 68 | imagePullPolicy: Never 69 | ports: 70 | - containerPort: 80 71 | ``` 72 | 73 | ## Step 3: Create a service 74 | 75 | To allow access to the documentation portal, the final step is to create a _service_. The below template will instruct Kubernetes to make API Scout available to the outside world on port 8181. 76 | 77 | ```yaml 78 | apiVersion: v1 79 | kind: Service 80 | metadata: 81 | labels: 82 | run: apiscout-svc 83 | name: apiscout-svc 84 | namespace: default 85 | spec: 86 | ports: 87 | - port: 8181 88 | protocol: TCP 89 | targetPort: 80 90 | selector: 91 | run: apiscout 92 | type: LoadBalancer 93 | ``` 94 | 95 | ## Complete yaml 96 | 97 | With YAML you can combine the above steps into a single document. If you prefer that, the complete document will look like: 98 | 99 | ```yaml 100 | --- 101 | apiVersion: rbac.authorization.k8s.io/v1beta1 102 | kind: ClusterRoleBinding 103 | metadata: 104 | name: default-view 105 | roleRef: 106 | apiGroup: rbac.authorization.k8s.io 107 | kind: ClusterRole 108 | name: view 109 | subjects: 110 | - kind: ServiceAccount 111 | name: default 112 | namespace: default 113 | --- 114 | apiVersion: extensions/v1beta1 115 | kind: Deployment 116 | metadata: 117 | labels: 118 | run: apiscout 119 | name: apiscout 120 | namespace: default 121 | spec: 122 | replicas: 1 123 | selector: 124 | matchLabels: 125 | run: apiscout 126 | template: 127 | metadata: 128 | labels: 129 | run: apiscout 130 | spec: 131 | containers: 132 | - name: apiscout 133 | image: retgits/apiscout:latest 134 | env: 135 | - name: MODE 136 | value: "KUBE" 137 | - name: HUGODIR 138 | value: "/tmp" 139 | - name: EXTERNALIP 140 | value: "192.168.99.100" 141 | imagePullPolicy: Never 142 | ports: 143 | - containerPort: 80 144 | --- 145 | apiVersion: v1 146 | kind: Service 147 | metadata: 148 | labels: 149 | run: apiscout-svc 150 | name: apiscout-svc 151 | namespace: default 152 | spec: 153 | ports: 154 | - port: 8181 155 | protocol: TCP 156 | targetPort: 80 157 | selector: 158 | run: apiscout 159 | type: LoadBalancer 160 | ``` -------------------------------------------------------------------------------- /docs/content/getting-started/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | weight: 2000 4 | --- 5 | 6 | # Workflow 7 | 8 | To use API Scout with any of your microservices, existing or new, you'll need to follow three easy steps 9 | 10 | # Step 1: Build a microservice 11 | 12 | The first step is to build your microservice or identify an existing microservice. The microservice that you want to index with API Scout needs to have an endpoint available that returns the OpenAPI specification 13 | 14 | ![picture1](../images/getting-started/Picture1.png) 15 | 16 | # Step 2: Annotate it 17 | 18 | The second step is to annotate your K8s service definition. The Kubernetes deployment file needs two specific annotations for API Scout to work: 19 | 20 | * `apiscout/index: 'true'`: This annotation ensures that apiscout indexes the service 21 | * `apiscout/swaggerUrl: '/swaggerspec'`: This is the URL from where apiscout will read the OpenAPI document 22 | 23 | ![picture2](../images/getting-started/Picture2.png) 24 | 25 | # Step 3: Access the portal 26 | 27 | The third step is easy! Access your microservices documentation through the portal... 28 | 29 | ![picture3](../images/getting-started/Picture3.png) 30 | 31 | # In the background 32 | 33 | While you're deploying microservices to Kubernetes, API Scout takes care of a few things. API Scout registers a watcher for Service event updates. Once an event comes in, API Scout will update its registry to reflect the change in the API. 34 | 35 | Let's say that a developer is working on an update to a microservice. Once the developer is done and deploys the changes, the developer also makes sure the service definition has the annotations for API Scout included. 36 | 37 | The Kubernetes API Server receives the updates made to the service by the the developer and dispatches an event to all watchers. API Scout, being one of those watchers updates its registry to reflect the change in the API. 38 | 39 | The developer goes to the documentation portal and sees the updated API documentation. -------------------------------------------------------------------------------- /docs/content/what-is-apiscout/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: What is API Scout? 3 | weight: 1000 4 | --- 5 | 6 | # What is API Scout 7 | 8 | As we're all building and deploying microservices, there are a few common concerns that every developer and Ops has: 9 | 10 | * _“where did I deploy that microservice?”_ 😩 11 | * _“what is the API definition of that microservice again?“_ 😟 12 | 13 | When your deployment footprint grows, keeping track of all those deployed microservices on Kubernetes can become quite a challenge. Keeping the API documentation updated for developers, could become even more challenging. API Scout is an attempt to solve that challenge. API Scout helps you get up-to-date API docs to your developers by simply annotating your services in Kubernetes. 14 | 15 | # How it works 16 | 17 | API Scout catalogs and documents your Kubernetes microservices to, ultimately, productize them as APIs using an API management platform. To do that it: 18 | 19 | * Automatically discover microservices with annotations 20 | * Generates beautiful pixel-perfect OAS/Swagger-based API Docs 21 | * Has first-class support for Kubernetes, PKS & OpenShift 22 | * Is 100% Open Source, so free to use & build on -------------------------------------------------------------------------------- /docs/layouts/partials/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ .Hugo.Generator }} 7 | {{ partial "meta.html" . }} 8 | {{ partial "favicon.html" . }} 9 | {{ .Title }} :: {{ .Site.Title }} 10 | 11 | {{ $assetBusting := not .Site.Params.disableAssetsBusting }} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {{with .Site.Params.themeVariant}} 21 | 22 | {{end}} 23 | 24 | 25 | 26 | 36 | {{ partial "custom-header.html" . }} 37 | 38 | 39 | {{ partial "menu.html" . }} 40 |
41 |
42 |
43 | {{if not .IsHome}} 44 |
45 |
46 | {{ if and (or .IsPage .IsSection) .Site.Params.editURL }} 47 | {{ $File := .File }} 48 | {{ $Site := .Site }} 49 | {{with $File.Path }} 50 | 56 | {{ end }} 57 | {{ end }} 58 | {{$toc := (and (not .Params.disableToc) (not .Params.chapter))}} 59 | 72 | {{ if $toc }} 73 | {{ partial "toc.html" . }} 74 | {{ end }} 75 |
76 |
77 | {{ end }} 78 | 79 | {{ if .Params.chapter }} 80 |
81 | {{ end }} 82 |
83 | {{if and (not .IsHome) (not .Params.chapter) }} 84 | {{end}} 85 | 86 | {{define "breadcrumb"}} 87 | {{$parent := .page.Parent }} 88 | {{ if $parent }} 89 | {{ $value := (printf "%s > %s" $parent.URL $parent.Title .value) }} 90 | {{ template "breadcrumb" dict "page" $parent "value" $value }} 91 | {{else}} 92 | {{.value|safeHTML}} 93 | {{end}} 94 | {{end}} 95 | -------------------------------------------------------------------------------- /docs/layouts/partials/logo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

API Scout

-------------------------------------------------------------------------------- /docs/static/images/getting-started/Picture1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/docs/static/images/getting-started/Picture1.png -------------------------------------------------------------------------------- /docs/static/images/getting-started/Picture2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/docs/static/images/getting-started/Picture2.png -------------------------------------------------------------------------------- /docs/static/images/getting-started/Picture3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/docs/static/images/getting-started/Picture3.png -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | #charset koi8-r; 6 | #access_log /var/log/nginx/host.access.log main; 7 | 8 | location / { 9 | root /usr/share/nginx/html; 10 | index index.html index.htm; 11 | } 12 | 13 | #error_page 404 /404.html; 14 | 15 | # redirect server error pages to the static page /50x.html 16 | # 17 | error_page 500 502 503 504 /50x.html; 18 | location = /50x.html { 19 | root /usr/share/nginx/html; 20 | } 21 | 22 | # proxy the PHP scripts to Apache listening on 127.0.0.1:80 23 | # 24 | #location ~ \.php$ { 25 | # proxy_pass http://127.0.0.1; 26 | #} 27 | 28 | # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 29 | # 30 | #location ~ \.php$ { 31 | # root html; 32 | # fastcgi_pass 127.0.0.1:9000; 33 | # fastcgi_index index.php; 34 | # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; 35 | # include fastcgi_params; 36 | #} 37 | 38 | # deny access to .htaccess files, if Apache's document root 39 | # concurs with nginx's one 40 | # 41 | #location ~ /\.ht { 42 | # deny all; 43 | #} 44 | } 45 | 46 | -------------------------------------------------------------------------------- /nginx/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #--- Start the apiscout API server --- 4 | ./tmp/server & 5 | 6 | #--- Start NGINX --- 7 | nginx -g "daemon off;" -------------------------------------------------------------------------------- /samples/invoiceservice-go/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ -------------------------------------------------------------------------------- /samples/invoiceservice-go/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | RUN apk update && apk add ca-certificates 3 | ENV HTTPPORT=8080 \ 4 | PAYMENTSERVICE=bla 5 | ADD invoiceservice-go . 6 | ADD swagger.json . 7 | EXPOSE 8080 8 | CMD ./invoiceservice-go -------------------------------------------------------------------------------- /samples/invoiceservice-go/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: deps 2 | 3 | #--- Variables --- 4 | DOCKERHUBUSER=retgits 5 | DOCKERTAG=latest 6 | IPADDR=xxx.xxx.xxx.xxx 7 | 8 | #--- Get the dependencies --- 9 | deps: 10 | go get -u ./... 11 | 12 | #--- Clean up the dist folder --- 13 | clean: 14 | rm -rf dist 15 | 16 | #--- Clean up the Kubernetes deployment --- 17 | clean-kube: 18 | kubectl delete deployment invoice-go-svc 19 | kubectl delete svc invoice-go-svc 20 | 21 | #--- Build the Flogo app --- 22 | build-app: 23 | go generate 24 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o dist/invoiceservice-go 25 | 26 | #--- Build the docker container --- 27 | build-docker: 28 | cp Dockerfile dist/Dockerfile 29 | cp swagger.json dist/swagger.json 30 | eval $$(minikube docker-env) ;\ 31 | cd dist && docker build -t $(DOCKERHUBUSER)/invoiceservice-go:$(DOCKERTAG) . 32 | 33 | #--- Run the container --- 34 | run-docker: 35 | docker run --rm -it -e PAYMENTSERVICE=http://$(IPADDR):9998/api/expected-date/ -p 9999:8080 $(DOCKERHUBUSER)/invoiceservice-go:$(DOCKERTAG) 36 | 37 | #--- Run the app on Kubernetes --- 38 | run-kube: 39 | kubectl apply -f invoice-go-svc.yml -------------------------------------------------------------------------------- /samples/invoiceservice-go/README.md: -------------------------------------------------------------------------------- 1 | # Invoice Service 2 | 3 | This sample Flogo application is used to demonstrate some key Flogo constructs, can be deployed to Kubernetes, and is set to be indexed by API Scout 4 | 5 | ## Files 6 | ```bash 7 | . 8 | ├── Dockerfile <-- A Dockerfile to build a container based on an Alpine base image 9 | ├── main.go <-- The Go source code for the app 10 | ├── Makefile <-- A Makefile to help build and deploy the app 11 | ├── payment-go-svc.yml <-- The Kubernetes deployment file 12 | ├── README.md <-- This file 13 | └── swagger.json <-- The OpenAPI specification for the app 14 | ``` 15 | 16 | ## Make targets 17 | The [Makefile](./Makefile) has a few targets: 18 | * **deps**: Get all the Go dependencies for the app 19 | * **clean**: Remove the `dist` folder for a new deployment 20 | * **clean-kube**: Remove all the deployed artifacts from Kubernetes 21 | * **build-app**: Build an executable (and store it in the dist folder) 22 | * **build-docker**: Build a Docker container from the contents of the `dist` folder 23 | * **run-docker**: Run the Docker image with default settings 24 | * **run-kube**: Deploy the app to Kubernetes 25 | 26 | _For more detailed information on the commands that are executed you can check out the [Makefile](./Makefile)_ 27 | 28 | ## Build and deploy the app 29 | To build and deploy the app to Kubernetes, run the make targets for _deps_, _build-app_, _build-docker_ and _run-kube_ 30 | 31 | ## API 32 | After starting the app, it will register with two endpoints: 33 | * **/api/invoices/:id**: Get the invoice details for the invoice ID. 34 | * **/swagger**: Get the OpenAPI specification for this app 35 | 36 | ## API Scout 37 | As you deploy the app to Kubernetes, after a few seconds the API will be found by API Scout and indexed. The lines 36 to 38 in [invoice-go-svc.yml](./invoice-go-svc.yml) are the annotations that make sure the API is found. -------------------------------------------------------------------------------- /samples/invoiceservice-go/invoice-go-svc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: extensions/v1beta1 3 | kind: Deployment 4 | metadata: 5 | labels: 6 | run: invoice-go-svc 7 | name: invoice-go-svc 8 | namespace: default 9 | spec: 10 | replicas: 1 11 | selector: 12 | matchLabels: 13 | run: invoice-go-svc 14 | template: 15 | metadata: 16 | labels: 17 | run: invoice-go-svc 18 | spec: 19 | containers: 20 | - name: invoice-go-svc 21 | image: retgits/invoiceservice-go:latest 22 | imagePullPolicy: Never 23 | ports: 24 | - containerPort: 8080 25 | --- 26 | apiVersion: v1 27 | kind: Service 28 | metadata: 29 | labels: 30 | run: invoice-go-svc 31 | name: invoice-go-svc 32 | namespace: default 33 | annotations: 34 | apiscout/index: 'true' 35 | apiscout/swaggerUrl: '/swaggerspec' 36 | spec: 37 | ports: 38 | - port: 80 39 | protocol: TCP 40 | targetPort: 8080 41 | selector: 42 | run: invoice-go-svc 43 | type: LoadBalancer 44 | -------------------------------------------------------------------------------- /samples/invoiceservice-go/main.go: -------------------------------------------------------------------------------- 1 | //go:generate go run $GOPATH/src/github.com/TIBCOSoftware/flogo-lib/flogo/gen/gen.go $GOPATH 2 | package main 3 | 4 | import ( 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "strconv" 11 | 12 | "github.com/TIBCOSoftware/flogo-contrib/activity/log" 13 | rt "github.com/TIBCOSoftware/flogo-contrib/trigger/rest" 14 | "github.com/TIBCOSoftware/flogo-lib/core/data" 15 | "github.com/TIBCOSoftware/flogo-lib/engine" 16 | "github.com/TIBCOSoftware/flogo-lib/flogo" 17 | "github.com/TIBCOSoftware/flogo-lib/logger" 18 | "github.com/retgits/flogo-components/activity/randomnumber" 19 | ) 20 | 21 | var ( 22 | httpport = getEnvKey("HTTPPORT", "8080") 23 | ) 24 | 25 | func main() { 26 | // Create a new Flogo app 27 | app := appBuilder() 28 | 29 | e, err := flogo.NewEngine(app) 30 | 31 | if err != nil { 32 | logger.Error(err) 33 | return 34 | } 35 | 36 | engine.RunEngine(e) 37 | } 38 | 39 | func appBuilder() *flogo.App { 40 | app := flogo.NewApp() 41 | 42 | // Convert the HTTPPort to an integer 43 | port, err := strconv.Atoi(httpport) 44 | if err != nil { 45 | logger.Error(err) 46 | } 47 | 48 | // Register the HTTP trigger 49 | trg := app.NewTrigger(&rt.RestTrigger{}, map[string]interface{}{"port": port}) 50 | trg.NewFuncHandler(map[string]interface{}{"method": "GET", "path": "/api/invoices/:id"}, handler) 51 | trg.NewFuncHandler(map[string]interface{}{"method": "GET", "path": "/swaggerspec"}, SwaggerSpec) 52 | 53 | return app 54 | } 55 | 56 | // SwaggerSpec is the function that gets executedto retrieve the SwaggerSpec 57 | func SwaggerSpec(ctx context.Context, inputs map[string]*data.Attribute) (map[string]*data.Attribute, error) { 58 | // The return message is a map[string]*data.Attribute which we'll have to construct 59 | response := make(map[string]interface{}) 60 | ret := make(map[string]*data.Attribute) 61 | 62 | fileData, err := ioutil.ReadFile("swagger.json") 63 | if err != nil { 64 | ret["code"], _ = data.NewAttribute("code", data.TypeInteger, 500) 65 | response["msg"] = err.Error() 66 | } else { 67 | ret["code"], _ = data.NewAttribute("code", data.TypeInteger, 200) 68 | var data map[string]interface{} 69 | if err := json.Unmarshal(fileData, &data); err != nil { 70 | panic(err) 71 | } 72 | response = data 73 | } 74 | 75 | ret["data"], _ = data.NewAttribute("data", data.TypeAny, response) 76 | 77 | return ret, nil 78 | 79 | } 80 | 81 | func handler(ctx context.Context, inputs map[string]*data.Attribute) (map[string]*data.Attribute, error) { 82 | // Get the ID from the path 83 | id := inputs["pathParams"].Value().(map[string]string)["id"] 84 | 85 | // Execute the log activity 86 | in := map[string]interface{}{"message": id, "flowInfo": "true", "addToFlow": "true"} 87 | _, err := flogo.EvalActivity(&log.LogActivity{}, in) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | // Generate a random number for the amount 93 | // There are definitely better ways to do this with Go, but this keeps the flow consistent with the UI version 94 | in = map[string]interface{}{"min": 1000, "max": 2000} 95 | out, err := flogo.EvalActivity(&randomnumber.MyActivity{}, in) 96 | if err != nil { 97 | return nil, err 98 | } 99 | amount := strconv.Itoa(out["result"].Value().(int)) 100 | 101 | // Instead of using the combine activity we'll concat the strings together 102 | ref := fmt.Sprintf("INV-%v", id) 103 | 104 | // Generate a random number for the balance 105 | // There are definitely better ways to do this with Go, but this keeps the flow consistent with the UI version 106 | in = map[string]interface{}{"min": 0, "max": 2000} 107 | out, err = flogo.EvalActivity(&randomnumber.MyActivity{}, in) 108 | if err != nil { 109 | return nil, err 110 | } 111 | balance := strconv.Itoa(out["result"].Value().(int)) 112 | 113 | // The return message is a map[string]*data.Attribute which we'll have to construct 114 | response := make(map[string]interface{}) 115 | response["id"] = id 116 | response["ref"] = ref 117 | response["amount"] = amount 118 | response["balance"] = balance 119 | response["currency"] = "USD" 120 | 121 | ret := make(map[string]*data.Attribute) 122 | ret["code"], _ = data.NewAttribute("code", data.TypeInteger, 200) 123 | ret["data"], _ = data.NewAttribute("data", data.TypeAny, response) 124 | 125 | return ret, nil 126 | } 127 | 128 | // getEnvKey tries to get the specified key from the OS environment and returns either the 129 | // value or the fallback that was provided 130 | func getEnvKey(key string, fallback string) string { 131 | if value, ok := os.LookupEnv(key); ok { 132 | return value 133 | } 134 | return fallback 135 | } 136 | -------------------------------------------------------------------------------- /samples/invoiceservice-go/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "GiveNewSchemaNameHere": { 4 | "properties": { 5 | "amount": { 6 | "default": 1162, 7 | "type": "integer" 8 | }, 9 | "balance": { 10 | "default": 718, 11 | "type": "integer" 12 | }, 13 | "currency": { 14 | "default": "USD", 15 | "type": "string" 16 | }, 17 | "id": { 18 | "default": "1234", 19 | "type": "string" 20 | }, 21 | "ref": { 22 | "default": "INV-1234", 23 | "type": "string" 24 | } 25 | }, 26 | "type": "object" 27 | } 28 | }, 29 | "info": { 30 | "title": "invoiceservice", 31 | "version": "1.0.0", 32 | "x-lastModified": "Aug 08, 2018 13:35PM PST" 33 | }, 34 | "paths": { 35 | "/api/invoices/{id}": { 36 | "get": { 37 | "operationId": "getApiInvoices_id", 38 | "parameters": [ 39 | { 40 | "description": "", 41 | "format": "", 42 | "in": "path", 43 | "name": "id", 44 | "required": true, 45 | "type": "string" 46 | } 47 | ], 48 | "produces": [ 49 | "application/json" 50 | ], 51 | "responses": { 52 | "200": { 53 | "description": "Success response", 54 | "examples": { 55 | "application/json": { 56 | "amount": 1162, 57 | "balance": 718, 58 | "currency": "USD", 59 | "id": "1234", 60 | "ref": "INV-1234" 61 | } 62 | }, 63 | "schema": { 64 | "$ref": "#/definitions/GiveNewSchemaNameHere" 65 | } 66 | } 67 | } 68 | } 69 | } 70 | }, 71 | "swagger": "2.0", 72 | "host": "localhost:8080", 73 | "schemes": [ 74 | "http" 75 | ], 76 | "basePath": "/" 77 | } -------------------------------------------------------------------------------- /server/main.go: -------------------------------------------------------------------------------- 1 | // Package main implements the main start of API Scout 2 | // 3 | // API Scout helps you catalog and document your Kubernetes microservices so you know 4 | // what you've deployed last summer! 5 | // 6 | // API Scout automatically discover microservices by using annotations 7 | // * apiscout/index: This annotation ensures that apiscout indexes the service 8 | // * apiscout/swaggerUrl: This is the URL from where apiscout will read the OpenAPI document 9 | // 10 | // After discovery, API Scout generates pixel-perfect OAS/Swagger-based API Docs and 11 | // displays it using a staticly generated site (powered by [Hugo](https://gohugo.io)) 12 | package main 13 | 14 | // The imports 15 | import ( 16 | "log" 17 | 18 | "github.com/TIBCOSoftware/apiscout/server/server" 19 | "github.com/TIBCOSoftware/apiscout/server/util" 20 | ) 21 | 22 | const ( 23 | // Version 24 | version = "0.2.0" 25 | ) 26 | 27 | var ( 28 | // The location where to store the swaggerdocs 29 | swaggerStore = util.GetEnvKey("SWAGGERSTORE", "/tmp/static/swaggerdocs") 30 | // The location where to store content for Hugo 31 | hugoStore = util.GetEnvKey("HUGOSTORE", "/tmp/content/apis") 32 | // The mode in which apiscout is running (can be either KUBE or LOCAL) 33 | runMode = util.GetEnvKey("MODE", "LOCAL") 34 | // The external IP address of the Kubernetes cluster in case of LOCAL mode 35 | externalIP = util.GetEnvKey("EXTERNALIP", "") 36 | // The base directory for Hugo 37 | hugoDir = util.GetEnvKey("HUGODIR", "") 38 | ) 39 | 40 | // main is the main entrypoint to start APIScout 41 | func main() { 42 | // Print config 43 | log.Printf("------------------------------------------------------------\n") 44 | log.Printf("CONFIG\n") 45 | log.Printf("APIScout version : %s\n", version) 46 | log.Printf("Run mode : %s\n", runMode) 47 | log.Printf("Swagger store : %s\n", swaggerStore) 48 | log.Printf("Hugo store : %s\n", hugoStore) 49 | if len(externalIP) > 0 { 50 | log.Printf("External IP : %s\n", externalIP) 51 | } 52 | if len(hugoDir) > 0 { 53 | log.Printf("Hugo dir : %s\n", hugoDir) 54 | } 55 | log.Printf("------------------------------------------------------------\n") 56 | 57 | // Create a new APIScout server instance 58 | srv, err := server.New(swaggerStore, hugoStore, runMode, externalIP, hugoDir) 59 | if err != nil { 60 | panic(err.Error()) 61 | } 62 | 63 | // Start APIScout server 64 | srv.Start() 65 | } 66 | -------------------------------------------------------------------------------- /server/server/event.go: -------------------------------------------------------------------------------- 1 | // Package server implements the server of APIScout 2 | package server 3 | 4 | import ( 5 | "reflect" 6 | 7 | "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/watch" 9 | ) 10 | 11 | // handleEvent takes care of the event itself and forwards the event in case it is a service 12 | func (srv *Server) handleEvent(event watch.Event) { 13 | v := &v1.Service{} 14 | if reflect.TypeOf(event.Object) == reflect.TypeOf(v) { 15 | i := reflect.ValueOf(event.Object).Interface() 16 | srv.handleService(i.(*v1.Service), event.Type, 0) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/server/retry.go: -------------------------------------------------------------------------------- 1 | // Package server implements the server of APIScout 2 | package server 3 | 4 | import ( 5 | "log" 6 | "time" 7 | 8 | "k8s.io/api/core/v1" 9 | "k8s.io/apimachinery/pkg/watch" 10 | ) 11 | 12 | const maxRetryCount = 3 13 | 14 | // retry takes a nap for 30 seconds in a separate go routine and retries the service. Usually when a service is created 15 | // when the server component of the app isn't fully started (like on initial deployment), the server would respond with 16 | // a dial timeout and it should be retried 17 | func (srv *Server) retry(service *v1.Service, eventType watch.EventType, retryCount int) { 18 | if retryCount < maxRetryCount { 19 | go func() { 20 | log.Printf("Retrying %s in 30 seconds...", service.Name) 21 | time.Sleep(30000 * time.Millisecond) 22 | log.Printf("Retrying %s with current retryCount %d...", service.Name, retryCount) 23 | srv.handleService(service, eventType, retryCount+1) 24 | }() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/server/server.go: -------------------------------------------------------------------------------- 1 | // Package server implements the server of APIScout 2 | package server 3 | 4 | import ( 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/TIBCOSoftware/apiscout/server/util" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/client-go/kubernetes" 11 | "k8s.io/client-go/rest" 12 | "k8s.io/client-go/tools/clientcmd" 13 | ) 14 | 15 | // Server represents the APIScout server and implements methods. 16 | type Server struct { 17 | // A map[string]string of all services that have been indexed by apiscout 18 | ServiceMap map[string]string 19 | // The location where to store the swaggerdocs 20 | SwaggerStore string 21 | // The location where to store content for Hugo 22 | HugoStore string 23 | // The mode in which apiscout is running (can be either KUBE or LOCAL) 24 | RunMode string 25 | // The external IP address of the Kubernetes cluster in case of LOCAL mode 26 | ExternalIP string 27 | // The base directory for Hugo 28 | HugoDir string 29 | } 30 | 31 | // New creates a new instance of the Server 32 | func New(swaggerStore string, hugoStore string, runMode string, externalIP string, hugoDir string) (*Server, error) { 33 | // Return a new struct 34 | return &Server{ 35 | ServiceMap: make(map[string]string), 36 | SwaggerStore: swaggerStore, 37 | HugoStore: hugoStore, 38 | RunMode: runMode, 39 | ExternalIP: externalIP, 40 | HugoDir: hugoDir, 41 | }, nil 42 | } 43 | 44 | // Start is the main engine to start the APIScout server 45 | func (srv *Server) Start() { 46 | var config *rest.Config 47 | var err error 48 | 49 | if strings.ToUpper(srv.RunMode) == "KUBE" { 50 | // Create the Kubernetes in-cluster config 51 | config, err = rest.InClusterConfig() 52 | if err != nil { 53 | panic(err.Error()) 54 | } 55 | } else { 56 | // use the current context in kubeconfig 57 | config, err = clientcmd.BuildConfigFromFlags("", filepath.Join(util.HomeDir(), ".kube", "config")) 58 | if err != nil { 59 | panic(err.Error()) 60 | } 61 | } 62 | 63 | // Create the clientset 64 | clientset, err := kubernetes.NewForConfig(config) 65 | if err != nil { 66 | panic(err.Error()) 67 | } 68 | 69 | // Create a watcher 70 | watcher, err := clientset.CoreV1().Services("").Watch(metav1.ListOptions{}) 71 | if err != nil { 72 | panic(err.Error()) 73 | } 74 | 75 | // Create a channel for the events to come in from the watcher 76 | eventChannel := watcher.ResultChan() 77 | 78 | // Start an indefinite loop 79 | for { 80 | evt := <-eventChannel 81 | srv.handleEvent(evt) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /server/server/service.go: -------------------------------------------------------------------------------- 1 | // Package server implements the server of APIScout 2 | package server 3 | 4 | import ( 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/TIBCOSoftware/apiscout/server/util" 12 | "k8s.io/api/core/v1" 13 | "k8s.io/apimachinery/pkg/watch" 14 | ) 15 | 16 | const ( 17 | // The annotation for apiscout to index a service 18 | annotation = "apiscout/index" 19 | // The annotation for apiscout to get the OpenAPI doc from 20 | swaggerURL = "apiscout/swaggerUrl" 21 | ) 22 | 23 | // handleService takes the Kubernetes service object and the EventType as input to determine what 24 | // to do with the event 25 | func (srv *Server) handleService(service *v1.Service, eventType watch.EventType, retryCount int) { 26 | log.Printf("Received %s for %s\n", eventType, service.Name) 27 | 28 | switch eventType { 29 | case watch.Added: 30 | if service.Annotations[annotation] == "true" { 31 | err := add(service, srv) 32 | if err != nil { 33 | if strings.Contains(err.Error(), "dial tcp") { 34 | srv.retry(service, eventType, retryCount+1) 35 | } else { 36 | log.Println(err.Error()) 37 | return 38 | } 39 | } 40 | } 41 | case watch.Deleted: 42 | err := remove(service, srv) 43 | if err != nil { 44 | log.Println(err.Error()) 45 | return 46 | } 47 | case watch.Modified: 48 | err := remove(service, srv) 49 | if err != nil { 50 | log.Println(err.Error()) 51 | } 52 | if service.Annotations[annotation] == "true" { 53 | err := add(service, srv) 54 | if err != nil { 55 | if strings.Contains(err.Error(), "dial tcp") { 56 | srv.retry(service, eventType, retryCount+1) 57 | } else { 58 | log.Println(err.Error()) 59 | return 60 | } 61 | } 62 | } 63 | case watch.Error: 64 | log.Println("Received watch.EventType Error, this is not recommended to be handled so API Scout will ignore") 65 | return 66 | default: 67 | log.Printf("Received unknown watch.EventType %s, so API Scout will ignore\n", eventType) 68 | return 69 | } 70 | 71 | // Generate the Hugo documentation 72 | err := util.GenerateDocs(srv.HugoDir) 73 | if err != nil { 74 | log.Printf("Error while attemtping to regenerate Hugo content: %s", err.Error()) 75 | } 76 | } 77 | 78 | // add adds a service to the service map and generates the developer documentation if it doesn't exist in the service map yet 79 | func add(service *v1.Service, srv *Server) error { 80 | if _, ok := srv.ServiceMap[service.Name]; !ok { 81 | log.Printf("%s should be indexed from %s\n", service.Name, service.Annotations[swaggerURL]) 82 | 83 | var ip string 84 | var port int32 85 | 86 | if len(srv.ExternalIP) > 0 { 87 | ip = srv.ExternalIP 88 | port = service.Spec.Ports[0].NodePort 89 | } else { 90 | ip = service.Spec.ClusterIP 91 | port = service.Spec.Ports[0].Port 92 | } 93 | 94 | apidoc, err := util.GetAPIDoc(fmt.Sprintf("http://%s:%d%s", ip, port, service.Annotations[swaggerURL])) 95 | if err != nil { 96 | log.Printf("Error while retrieving API document from %s: %s", fmt.Sprintf("http://%s:%d%s", ip, port, service.Annotations[swaggerURL]), err.Error()) 97 | return err 98 | } 99 | 100 | util.WriteSwaggerToDisk(service.Name, apidoc, fmt.Sprintf("%s:%d", ip, port), srv.SwaggerStore, srv.HugoStore) 101 | 102 | srv.ServiceMap[service.Name] = "DONE" 103 | log.Printf("Service %s has been added to API Scout\n", service.Name) 104 | } 105 | 106 | return nil 107 | } 108 | 109 | // remove deletes the service from the service map and removes the JSON and Markdown files from disk 110 | func remove(service *v1.Service, srv *Server) error { 111 | log.Printf("Attempting to delete %s\n", service.Name) 112 | 113 | // Remove JSON file 114 | filename := filepath.Join(srv.SwaggerStore, fmt.Sprintf("%s.json", strings.Replace(strings.ToLower(service.Name), " ", "-", -1))) 115 | err := os.Remove(filename) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | // Remove Markdown file 121 | filename = filepath.Join(srv.HugoStore, fmt.Sprintf("%s.md", strings.Replace(strings.ToLower(service.Name), " ", "-", -1))) 122 | err = os.Remove(filename) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | // Remove service from service map 128 | delete(srv.ServiceMap, service.Name) 129 | log.Printf("Service %s has been removed from API Scout\n", service.Name) 130 | 131 | return nil 132 | } 133 | -------------------------------------------------------------------------------- /server/server/service_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "net/http" 7 | "os" 8 | "strings" 9 | "testing" 10 | 11 | "k8s.io/apimachinery/pkg/watch" 12 | 13 | "k8s.io/api/core/v1" 14 | ) 15 | 16 | const swaggerJSONPayload = `{ 17 | "definitions": { 18 | "GiveNewSchemaNameHere": { 19 | "properties": { 20 | "amount": { 21 | "default": 1162, 22 | "type": "integer" 23 | }, 24 | "balance": { 25 | "default": 718, 26 | "type": "integer" 27 | }, 28 | "currency": { 29 | "default": "USD", 30 | "type": "string" 31 | }, 32 | "id": { 33 | "default": "1234", 34 | "type": "string" 35 | }, 36 | "ref": { 37 | "default": "INV-1234", 38 | "type": "string" 39 | } 40 | }, 41 | "type": "object" 42 | } 43 | }, 44 | "info": { 45 | "title": "invoiceservice", 46 | "version": "1.0.0", 47 | "x-lastModified": "Aug 08, 2018 13:35PM PST" 48 | }, 49 | "paths": { 50 | "/api/invoices/{id}": { 51 | "get": { 52 | "operationId": "getApiInvoices_id", 53 | "parameters": [ 54 | { 55 | "description": "", 56 | "format": "", 57 | "in": "path", 58 | "name": "id", 59 | "required": true, 60 | "type": "string" 61 | } 62 | ], 63 | "produces": [ 64 | "application/json" 65 | ], 66 | "responses": { 67 | "200": { 68 | "description": "Success response", 69 | "examples": { 70 | "application/json": { 71 | "amount": 1162, 72 | "balance": 718, 73 | "currency": "USD", 74 | "id": "1234", 75 | "ref": "INV-1234" 76 | } 77 | }, 78 | "schema": { 79 | "$ref": "#/definitions/GiveNewSchemaNameHere" 80 | } 81 | } 82 | } 83 | } 84 | } 85 | }, 86 | "swagger": "2.0", 87 | "host": "localhost:8080", 88 | "schemes": [ 89 | "http" 90 | ], 91 | "basePath": "/" 92 | }` 93 | 94 | const kubeServicePayload = `{ 95 | "metadata": { 96 | "name": "invoice-go-svc", 97 | "namespace": "default", 98 | "selfLink": "/api/v1/namespaces/default/services/invoice-go-svc", 99 | "uid": "a3f33d97-e0cb-11e8-8617-c85b76f2707f", 100 | "resourceVersion": "2982", 101 | "creationTimestamp": "2018-11-05T07:23:06Z", 102 | "labels": { 103 | "run": "invoice-go-svc" 104 | }, 105 | "annotations": { 106 | "apiscout/index": "true", 107 | "apiscout/swaggerUrl": "/swaggerspec", 108 | "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{\"apiscout/index\":\"true\",\"apiscout/swaggerUrl\":\"/swaggerspec\"},\"labels\":{\"run\":\"invoice-go-svc\"},\"name\":\"invoice-go-svc\",\"namespace\":\"default\"},\"spec\":{\"ports\":[{\"port\":80,\"protocol\":\"TCP\",\"targetPort\":8080}],\"selector\":{\"run\":\"invoice-go-svc\"},\"type\":\"LoadBalancer\"}}\n" 109 | } 110 | }, 111 | "spec": { 112 | "ports": [ 113 | { 114 | "protocol": "TCP", 115 | "port": 80, 116 | "targetPort": 8123, 117 | "nodePort": 8123 118 | } 119 | ], 120 | "selector": { 121 | "run": "invoice-go-svc" 122 | }, 123 | "clusterIP": "10.99.164.156", 124 | "type": "LoadBalancer", 125 | "sessionAffinity": "None", 126 | "externalTrafficPolicy": "Cluster" 127 | }, 128 | "status": { 129 | "loadBalancer": {} 130 | } 131 | }` 132 | 133 | func TestHandleService(t *testing.T) { 134 | server := &http.Server{Addr: ":8123"} 135 | http.HandleFunc("/swaggerspec", func(w http.ResponseWriter, r *http.Request) { 136 | w.Header().Add("Content-Type", "application/json") 137 | io.WriteString(w, swaggerJSONPayload) 138 | }) 139 | done := make(chan bool, 1) 140 | go func() { 141 | server.ListenAndServe() 142 | done <- true 143 | }() 144 | _, err := http.Get("http://localhost:8123/swaggerspec") 145 | for err != nil { 146 | _, err = http.Get("http://localhost:8123/swaggerspec") 147 | } 148 | defer func() { 149 | err := server.Shutdown(nil) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | <-done 154 | }() 155 | 156 | service := &v1.Service{} 157 | 158 | json.Unmarshal([]byte(kubeServicePayload), service) 159 | 160 | tempPath := "/tmp/apiscouttest1234" 161 | runMode := "LOCAL" 162 | externalIP := "localhost" 163 | 164 | os.MkdirAll(tempPath, 0777) 165 | 166 | srv, err := New(tempPath, tempPath, runMode, externalIP, tempPath) 167 | if err != nil { 168 | panic(err.Error()) 169 | } 170 | 171 | srv.handleService(service, watch.Added, 0) 172 | if strings.Compare(srv.ServiceMap["invoice-go-svc"], "DONE") != 0 { 173 | t.Fatal("Service addition failed") 174 | } 175 | 176 | srv.handleService(service, watch.Deleted, 0) 177 | if len(srv.ServiceMap) != 0 { 178 | t.Fatal("Service removal failed") 179 | } 180 | 181 | os.RemoveAll(tempPath) 182 | 183 | } 184 | -------------------------------------------------------------------------------- /server/util/apidoc.go: -------------------------------------------------------------------------------- 1 | // Package util implements utility methods 2 | package util 3 | 4 | import ( 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | "text/template" 15 | ) 16 | 17 | // A template for the Markdown file for Hugo 18 | const markdown = `--- 19 | title: {{.title}} 20 | weight: 1000 21 | --- 22 | 23 | {{.json}}` 24 | 25 | // GetAPIDoc performs an HTTP request to a specified URL to retrieve the OpenAPI document 26 | func GetAPIDoc(url string) (string, error) { 27 | req, err := http.NewRequest("GET", url, nil) 28 | if err != nil { 29 | return "", err 30 | } 31 | 32 | res, err := http.DefaultClient.Do(req) 33 | if err != nil { 34 | return "", err 35 | } 36 | 37 | defer res.Body.Close() 38 | 39 | body, err := ioutil.ReadAll(res.Body) 40 | if err != nil { 41 | return "", err 42 | } 43 | 44 | return string(body), nil 45 | } 46 | 47 | // WriteSwaggerToDisk takes a swagger document and writes both its content as well as a hugo template to disk 48 | // to enable the static site to be updated with the new API 49 | func WriteSwaggerToDisk(name string, apidoc string, svchost string, swaggerStore string, hugoStore string) error { 50 | // Unmarshal the string into a proper document 51 | var swagger map[string]interface{} 52 | if err := json.Unmarshal([]byte(apidoc), &swagger); err != nil { 53 | log.Printf("error while unmarshaling JSON: %s", err.Error()) 54 | return fmt.Errorf("error while unmarshaling JSON: %s", err.Error()) 55 | } 56 | 57 | // Update the host information 58 | if _, ok := swagger["host"]; ok { 59 | swagger["host"] = svchost 60 | } 61 | 62 | // Determine where to save the file 63 | filename := filepath.Join(swaggerStore, fmt.Sprintf("%s.json", strings.Replace(strings.ToLower(name), " ", "-", -1))) 64 | log.Printf("Preparing to write %s to disk", filename) 65 | os.Remove(filename) 66 | 67 | // Create a file on disk 68 | file, err := os.Create(filename) 69 | if err != nil { 70 | log.Printf("error while creating file: %s", err.Error()) 71 | return fmt.Errorf("error while creating file: %s", err.Error()) 72 | } 73 | defer file.Close() 74 | 75 | // Open the file to write 76 | file, err = os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) 77 | if err != nil { 78 | log.Printf("error while opening file: %s", err.Error()) 79 | return fmt.Errorf("error while opening file: %s", err.Error()) 80 | } 81 | 82 | // Serialize the OpenAPI doc 83 | apibytes, err := json.Marshal(swagger) 84 | if err != nil { 85 | log.Printf("error while marshaling API: %s", err.Error()) 86 | return fmt.Errorf("error while marshaling API: %s", err.Error()) 87 | } 88 | 89 | // Write the OpenAPI doc to disk 90 | _, err = file.Write(apibytes) 91 | if err != nil { 92 | log.Printf("error while writing OpenAPI to disk: %s", err.Error()) 93 | return fmt.Errorf("error while writing OpenAPI to disk: %s", err.Error()) 94 | } 95 | 96 | // Prepare the Markdown file for Hugo 97 | var title string 98 | if val, ok := swagger["info"].(map[string]interface{})["title"]; ok { 99 | title = val.(string) 100 | } 101 | 102 | dataMap := make(map[string]interface{}) 103 | dataMap["title"] = title 104 | dataMap["json"] = fmt.Sprintf("{{< oas3 url=\"../../../swaggerdocs/%s.json\" >}}", strings.Replace(strings.ToLower(name), " ", "-", -1)) 105 | 106 | // Render the Markdown file based on the template 107 | t := template.Must(template.New("top").Parse(markdown)) 108 | buf := &bytes.Buffer{} 109 | if err := t.Execute(buf, dataMap); err != nil { 110 | log.Printf("error while rendering Markdown file: %s", err.Error()) 111 | return fmt.Errorf("error while rendering Markdown file: %s", err.Error()) 112 | } 113 | s := buf.String() 114 | 115 | // Determine where to save the file 116 | filename = filepath.Join(hugoStore, fmt.Sprintf("%s.md", strings.Replace(strings.ToLower(name), " ", "-", -1))) 117 | log.Printf("Preparing to write %s to disk", filename) 118 | os.Remove(filename) 119 | 120 | // Create a file on disk 121 | file, err = os.Create(filename) 122 | if err != nil { 123 | log.Printf("error while creating file: %s", err.Error()) 124 | return fmt.Errorf("error while creating file: %s", err.Error()) 125 | } 126 | defer file.Close() 127 | 128 | // Open the file to write 129 | file, err = os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) 130 | if err != nil { 131 | log.Printf("error while opening file: %s", err.Error()) 132 | return fmt.Errorf("error while opening file: %s", err.Error()) 133 | } 134 | 135 | // Write the Markdown doc to disk 136 | _, err = file.Write([]byte(s)) 137 | if err != nil { 138 | log.Printf("error while writing Markdown to disk: %s", err.Error()) 139 | return fmt.Errorf("error while writing Markdown to disk: %s", err.Error()) 140 | } 141 | 142 | return nil 143 | } 144 | -------------------------------------------------------------------------------- /server/util/hugo.go: -------------------------------------------------------------------------------- 1 | // Package util implements utility methods 2 | package util 3 | 4 | import ( 5 | "log" 6 | "os/exec" 7 | ) 8 | 9 | // GenerateDocs is a wrapper around the Hugo binary in the container and instructs the binary to generate the site 10 | func GenerateDocs(hugoDir string) error { 11 | log.Print("Regeneratig Hugo content") 12 | 13 | cmd := exec.Command("sh", "-c", "hugo") 14 | cmd.Dir = hugoDir 15 | output, err := cmd.CombinedOutput() 16 | log.Print(string(output)) 17 | 18 | return err 19 | } 20 | -------------------------------------------------------------------------------- /server/util/os.go: -------------------------------------------------------------------------------- 1 | // Package util implements utility methods 2 | package util 3 | 4 | import "os" 5 | 6 | // GetEnvKey tries to get the specified key from the OS environment and returns either the 7 | // value or the fallback that was provided 8 | func GetEnvKey(key string, fallback string) string { 9 | if value, ok := os.LookupEnv(key); ok { 10 | return value 11 | } 12 | return fallback 13 | } 14 | 15 | // HomeDir gets the homedir of the current user 16 | func HomeDir() string { 17 | if h := os.Getenv("HOME"); h != "" { 18 | return h 19 | } 20 | return os.Getenv("USERPROFILE") // windows 21 | } 22 | -------------------------------------------------------------------------------- /webapp/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /webapp/config.toml: -------------------------------------------------------------------------------- 1 | baseURL = "localhost:1313" 2 | languageCode = "en-US" 3 | defaultContentLanguage = "en" 4 | 5 | title = "API Scout :: Finding your APIs in Kubernetes" 6 | theme = "hugo-theme-learn" 7 | metaDataFormat = "yaml" 8 | defaultContentLanguageInSubdir = true 9 | 10 | [params] 11 | editURL = "" 12 | description = "API Scout" 13 | author = "API Scout" 14 | showVisitedLinks = false 15 | disableAssetsBusting = false 16 | disableInlineCopyToClipBoard = true 17 | disableShortcutsTitle = true 18 | disableLanguageSwitchingButton = true 19 | ordersectionsby = "title" 20 | themeVariant = "green" 21 | 22 | [outputs] 23 | home = [ "HTML", "RSS", "JSON"] 24 | -------------------------------------------------------------------------------- /webapp/content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | weight: 1 4 | chapter: true 5 | --- 6 | 7 | # API Scout 8 | 9 | Finding Your APIs In Kubernetes -------------------------------------------------------------------------------- /webapp/content/apis/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: APIs 3 | weight: 1000 4 | pre: " " 5 | --- 6 | 7 | # APIs 8 | 9 | So you can remember what you deployed last summer! -------------------------------------------------------------------------------- /webapp/layouts/partials/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ .Hugo.Generator }} 7 | {{ partial "meta.html" . }} 8 | {{ partial "favicon.html" . }} 9 | {{ .Title }} :: {{ .Site.Title }} 10 | 11 | {{ $assetBusting := not .Site.Params.disableAssetsBusting }} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {{with .Site.Params.themeVariant}} 21 | 22 | {{end}} 23 | 24 | 25 | 26 | 36 | {{ partial "custom-header.html" . }} 37 | 38 | 39 | {{ partial "menu.html" . }} 40 |
41 |
42 |
43 | {{if not .IsHome}} 44 |
45 |
46 | {{ if and (or .IsPage .IsSection) .Site.Params.editURL }} 47 | {{ $File := .File }} 48 | {{ $Site := .Site }} 49 | {{with $File.Path }} 50 | 56 | {{ end }} 57 | {{ end }} 58 | {{$toc := (and (not .Params.disableToc) (not .Params.chapter))}} 59 | 72 | {{ if $toc }} 73 | {{ partial "toc.html" . }} 74 | {{ end }} 75 |
76 |
77 | {{ end }} 78 | 79 | {{ if .Params.chapter }} 80 |
81 | {{ end }} 82 |
83 | {{if and (not .IsHome) (not .Params.chapter) }} 84 | {{end}} 85 | 86 | {{define "breadcrumb"}} 87 | {{$parent := .page.Parent }} 88 | {{ if $parent }} 89 | {{ $value := (printf "%s > %s" $parent.URL $parent.Title .value) }} 90 | {{ template "breadcrumb" dict "page" $parent "value" $value }} 91 | {{else}} 92 | {{.value|safeHTML}} 93 | {{end}} 94 | {{end}} 95 | -------------------------------------------------------------------------------- /webapp/layouts/shortcodes/oas3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Swagger UI 8 | 9 | 10 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /webapp/static/css/reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | html5doctor.com Reset Stylesheet 3 | v1.6.1 4 | Last Updated: 2010-09-17 5 | Author: Richard Clark - http://richclarkdesign.com 6 | Twitter: @rich_clark 7 | */ 8 | 9 | html, body, div, span, object, iframe, 10 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 11 | abbr, address, cite, code, 12 | del, dfn, em, img, ins, kbd, q, samp, 13 | small, strong, sub, sup, var, 14 | b, i, 15 | dl, dt, dd, ol, ul, li, 16 | fieldset, form, label, legend, 17 | table, caption, tbody, tfoot, thead, tr, th, td, 18 | article, aside, canvas, details, figcaption, figure, 19 | footer, header, hgroup, menu, nav, section, summary, 20 | time, mark, audio, video { 21 | padding:0; 22 | border:0; 23 | outline:0; 24 | font-size:100%; 25 | vertical-align:baseline; 26 | background:transparent; 27 | } 28 | 29 | article,aside,details,figcaption,figure, 30 | footer,header,hgroup,menu,nav,section { 31 | display:block; 32 | } 33 | 34 | nav ul { 35 | list-style:none; 36 | } 37 | 38 | blockquote, q { 39 | quotes:none; 40 | } 41 | 42 | blockquote:before, blockquote:after, 43 | q:before, q:after { 44 | content:''; 45 | content:none; 46 | } 47 | 48 | a { 49 | margin:0; 50 | padding:0; 51 | font-size:100%; 52 | vertical-align:baseline; 53 | background:transparent; 54 | } 55 | 56 | /* change colours to suit your needs */ 57 | ins { 58 | background-color:#ff9; 59 | color:#000; 60 | text-decoration:none; 61 | } 62 | 63 | /* change colours to suit your needs */ 64 | mark { 65 | background-color:#ff9; 66 | color:#000; 67 | font-style:italic; 68 | font-weight:bold; 69 | } 70 | 71 | del { 72 | text-decoration: line-through; 73 | } 74 | 75 | abbr[title], dfn[title] { 76 | border-bottom:1px dotted; 77 | cursor:help; 78 | } 79 | 80 | table { 81 | border-collapse:collapse; 82 | border-spacing:0; 83 | } 84 | 85 | /* change border colour to suit your needs */ 86 | hr { 87 | display:block; 88 | height:1px; 89 | border:0; 90 | border-top:1px solid #cccccc; 91 | margin:1em 0; 92 | padding:0; 93 | } 94 | 95 | input, select { 96 | vertical-align:middle; 97 | } -------------------------------------------------------------------------------- /webapp/static/css/theme-green.css: -------------------------------------------------------------------------------- 1 | 2 | :root{ 3 | 4 | --MAIN-TEXT-color:#323232; /* Color of text by default */ 5 | --MAIN-TITLES-TEXT-color: #5e5e5e; /* Color of titles h2-h3-h4-h5 */ 6 | --MAIN-LINK-color:#599a3e; /* Color of links */ 7 | --MAIN-LINK-HOVER-color:#3f6d2c; /* Color of hovered links */ 8 | --MAIN-ANCHOR-color: #599a3e; /* color of anchors on titles */ 9 | 10 | --MENU-HEADER-BG-color:#89bf04; /* Background color of menu header */ 11 | --MENU-HEADER-BORDER-color:#9cd484; /*Color of menu header border */ 12 | 13 | --MENU-SEARCH-BG-color:#599a3e; /* Search field background color (by default borders + icons) */ 14 | --MENU-SEARCH-BOX-color: #84c767; /* Override search field border color */ 15 | --MENU-SEARCH-BOX-ICONS-color: #c7f7c4; /* Override search field icons color */ 16 | 17 | --MENU-SECTIONS-ACTIVE-BG-color:#1b211c; /* Background color of the active section and its childs */ 18 | --MENU-SECTIONS-BG-color:#222723; /* Background color of other sections */ 19 | --MENU-SECTIONS-LINK-color: #ccc; /* Color of links in menu */ 20 | --MENU-SECTIONS-LINK-HOVER-color: #e6e6e6; /* Color of links in menu, when hovered */ 21 | --MENU-SECTION-ACTIVE-CATEGORY-color: #777; /* Color of active category text */ 22 | --MENU-SECTION-ACTIVE-CATEGORY-BG-color: #fff; /* Color of background for the active category (only) */ 23 | 24 | --MENU-VISITED-color: #599a3e; /* Color of 'page visited' icons in menu */ 25 | --MENU-SECTION-HR-color: #18211c; /* Color of
separator in menu */ 26 | 27 | } 28 | 29 | body { 30 | color: var(--MAIN-TEXT-color) !important; 31 | } 32 | 33 | textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus { 34 | border-color: none; 35 | box-shadow: none; 36 | } 37 | 38 | h2, h3, h4, h5 { 39 | color: var(--MAIN-TITLES-TEXT-color) !important; 40 | } 41 | 42 | a { 43 | color: var(--MAIN-LINK-color); 44 | } 45 | 46 | .anchor { 47 | color: var(--MAIN-ANCHOR-color); 48 | } 49 | 50 | a:hover { 51 | color: var(--MAIN-LINK-HOVER-color); 52 | } 53 | 54 | #sidebar ul li.visited > a .read-icon { 55 | color: var(--MENU-VISITED-color); 56 | } 57 | 58 | #body a.highlight:after { 59 | display: block; 60 | content: ""; 61 | height: 1px; 62 | width: 0%; 63 | -webkit-transition: width 0.5s ease; 64 | -moz-transition: width 0.5s ease; 65 | -ms-transition: width 0.5s ease; 66 | transition: width 0.5s ease; 67 | background-color: var(--MAIN-LINK-HOVER-color); 68 | } 69 | #sidebar { 70 | background-color: var(--MENU-SECTIONS-BG-color); 71 | } 72 | #sidebar #header-wrapper { 73 | background: var(--MENU-HEADER-BG-color); 74 | color: var(--MENU-SEARCH-BOX-color); 75 | border-color: var(--MENU-HEADER-BORDER-color); 76 | } 77 | #sidebar .searchbox { 78 | border-color: var(--MENU-SEARCH-BOX-color); 79 | background: var(--MENU-SEARCH-BG-color); 80 | } 81 | #sidebar ul.topics > li.parent, #sidebar ul.topics > li.active { 82 | background: var(--MENU-SECTIONS-ACTIVE-BG-color); 83 | } 84 | #sidebar .searchbox * { 85 | color: var(--MENU-SEARCH-BOX-ICONS-color); 86 | } 87 | 88 | #sidebar a { 89 | color: var(--MENU-SECTIONS-LINK-color); 90 | } 91 | 92 | #sidebar a:hover { 93 | color: var(--MENU-SECTIONS-LINK-HOVER-color); 94 | } 95 | 96 | #sidebar ul li.active > a { 97 | background: var(--MENU-SECTION-ACTIVE-CATEGORY-BG-color); 98 | color: var(--MENU-SECTION-ACTIVE-CATEGORY-color) !important; 99 | } 100 | 101 | #sidebar hr { 102 | border-color: var(--MENU-SECTION-HR-color); 103 | } 104 | 105 | -------------------------------------------------------------------------------- /webapp/static/oas3/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 SmartBear Software 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 [apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. -------------------------------------------------------------------------------- /webapp/static/oas3/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 68 | -------------------------------------------------------------------------------- /webapp/static/oas3/swagger-ui.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""} -------------------------------------------------------------------------------- /webapp/static/swaggerdocs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/static/swaggerdocs/.gitkeep -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | public/ 3 | exampleSite/public 4 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Grav 4 | Copyright (c) 2016 MATHIEU CORNIC 5 | Copyright (c) 2017 Valere JEANTET 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/README.md: -------------------------------------------------------------------------------- 1 | # Hugo Learn Theme 2 | 3 | This repository contains a theme for [Hugo](https://gohugo.io/), based on great [Grav Learn Theme](http://learn.getgrav.org/). 4 | 5 | Visit the [theme documentation](https://learn.netlify.com/en/) to see what is going on. It is actually built with this theme. 6 | 7 | ## Main features 8 | 9 | - Automatic Search 10 | - Multilingual mode 11 | - Unlimited menu levels 12 | - Automatic next/prev buttons to navigate through menu entries 13 | - Image resizing, shadow… 14 | - Attachments files 15 | - List child pages 16 | - Mermaid diagram (flowchart, sequence, gantt) 17 | - Customizable look and feel and themes variants 18 | - Buttons, Tip/Note/Info/Warning boxes, Expand 19 | 20 | ## Installation 21 | 22 | Navigate to your themes folder in your Hugo site and use the following commands: 23 | 24 | ``` 25 | $ cd themes 26 | $ git clone https://github.com/matcornic/hugo-theme-learn.git 27 | ``` 28 | 29 | Check that your Hugo version is minimum `0.25` with `hugo version`. 30 | 31 | ![Overview](https://github.com/matcornic/hugo-theme-learn/raw/master/images/tn.png) 32 | 33 | ## Usage 34 | 35 | - [Visit the documentation](https://learn.netlify.com/en/) 36 | 37 | ## Download old versions (prior to 2.0.0) 38 | 39 | If you need old version for compatibility purpose, either download [theme source code from releases](https://github.com/matcornic/hugo-theme-learn/releases) or use the right git tag. For example, with `1.1.0` 40 | 41 | - Direct download way: https://github.com/matcornic/hugo-theme-learn/archive/1.1.0.zip 42 | - Git way: 43 | 44 | ```shell 45 | cd themes/hugo-theme-learn 46 | git checkout tags/1.1.0 47 | ``` 48 | 49 | For both solutions, the documentation is available at https://github.com/matcornic/hugo-theme-learn/releases/download/1.1.0/hugo-learn-doc-1.1.0.zip 50 | 51 | ## Credits 52 | 53 | Many thanks to [@vjeantet](https://github.com/vjeantet/) for the fork [docdock](https://github.com/vjeantet/hugo-theme-docdock). The v2 of this theme is mainly based on his work ! 54 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/archetypes/chapter.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "{{ replace .TranslationBaseName "-" " " | title }}" 3 | date = {{ .Date }} 4 | weight = 5 5 | chapter = true 6 | pre = "X. " 7 | +++ 8 | 9 | ### Chapter X 10 | 11 | # Some Chapter title 12 | 13 | Lorem Ipsum. -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/archetypes/default.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "{{ replace .TranslationBaseName "-" " " | title }}" 3 | date = {{ .Date }} 4 | weight = 5 5 | +++ 6 | 7 | Lorem Ipsum. -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/i18n/en.toml: -------------------------------------------------------------------------------- 1 | [Search-placeholder] 2 | other = "Search..." 3 | 4 | [Clear-History] 5 | other = "Clear History" 6 | 7 | [Attachments-label] 8 | other = "Attachments" 9 | 10 | [title-404] 11 | other = "Error" 12 | 13 | [message-404] 14 | other = "Woops. Looks like this page doesn't exist ¯\\_(ツ)_/¯." 15 | 16 | [Go-to-homepage] 17 | other = "Go to homepage" 18 | 19 | [Edit-this-page] 20 | other = "Edit this page" 21 | 22 | [Shortcuts-Title] 23 | other = "More" 24 | 25 | [Expand-title] 26 | other = "Expand me..." -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/i18n/es.toml: -------------------------------------------------------------------------------- 1 | [Search-placeholder] 2 | other = "Buscar..." 3 | 4 | [Clear-History] 5 | other = "Borrar Historial" 6 | 7 | [Attachments-label] 8 | other = "Adjuntos" 9 | 10 | [title-404] 11 | other = "Error" 12 | 13 | [message-404] 14 | other = "Ups. Parece que la página no existe ¯\\_(ツ)_/¯." 15 | 16 | [Go-to-homepage] 17 | other = "Ir al inicio" 18 | 19 | [Edit-this-page] 20 | other = "Editar esta página" 21 | 22 | [Shortcuts-Title] 23 | other = "Más" 24 | 25 | [Expand-title] 26 | other = "Expandir..." 27 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/i18n/fr.toml: -------------------------------------------------------------------------------- 1 | [Search-placeholder] 2 | other = "Rechercher..." 3 | 4 | [Clear-History] 5 | other = "Supprimer l'historique" 6 | 7 | [Attachments-label] 8 | other = "Pièces jointes" 9 | 10 | [title-404] 11 | other = "Erreur" 12 | 13 | [message-404] 14 | other = "Oups. On dirait que cette page n'existe pas ¯\\_(ツ)_/¯" 15 | 16 | [Go-to-homepage] 17 | other = "Vers la page d'accueil" 18 | 19 | [Edit-this-page] 20 | other = "Modifier la page" 21 | 22 | [Shortcuts-Title] 23 | other = "Aller plus loin" 24 | 25 | [Expand-title] 26 | other = "Déroulez-moi..." -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/i18n/pt.toml: -------------------------------------------------------------------------------- 1 | [Search-placeholder] 2 | other = "Procurar..." 3 | 4 | [Clear-History] 5 | other = "Limpar Histórico" 6 | 7 | [Attachments-label] 8 | other = "Anexos" 9 | 10 | [title-404] 11 | other = "Erro" 12 | 13 | [message-404] 14 | other = "Ops. Parece que a página não existe ¯\\_(ツ)_/¯." 15 | 16 | [Go-to-homepage] 17 | other = "Ir para o início" 18 | 19 | [Edit-this-page] 20 | other = "Editar esta página" 21 | 22 | [Shortcuts-Title] 23 | other = "Mais" 24 | 25 | [Expand-title] 26 | other = "Expandir..." 27 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/images/screenshot.png -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/images/tn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/images/tn.png -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ partial "meta.html" . }} {{ partial "favicon.html" . }} {{ .Scratch.Add "title" "" }}{{ if eq .Site.Data.titles .Title }}{{ .Scratch.Set "title" (index .Site.Data.titles .Title).title }}{{ else }}{{ .Scratch.Set "title" .Title}}{{end}} 6 | {{ .Scratch.Get "title" }} 7 | 8 | {{ $assetBusting := not .Site.Params.disableAssetsBusting }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {{with .Site.Params.themeVariant}} 18 | 19 | {{end}} 20 | 35 | {{ partial "custom-header.html" . }} 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 |
45 |
46 |

{{T "title-404"}}

47 |

48 |

49 |

{{T "message-404"}}

50 |

51 |

{{T "Go-to-homepage"}}

52 |

53 |
54 |
55 | 56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/_default/list.html: -------------------------------------------------------------------------------- 1 | {{ partial "header.html" . }} 2 | 3 | {{ .Content }} 4 | 5 |
6 | {{with .Params.LastModifierDisplayName}} 7 | {{ . }} {{with $.Date}} {{ .Format "02/01/2006" }}{{end}} 8 |
9 | {{end}} 10 | 11 | 12 | {{ partial "footer.html" . }} -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/_default/single.html: -------------------------------------------------------------------------------- 1 | {{ partial "header.html" . }} 2 | 3 | {{ .Content }} 4 | 5 |
6 | {{with .Params.LastModifierDisplayName}} 7 | {{ . }} {{with $.Date}} {{ .Format "02/01/2006" }}{{end}} 8 |
9 | {{end}} 10 | 11 | 12 | 13 | {{ partial "footer.html" . }} 14 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/index.html: -------------------------------------------------------------------------------- 1 | {{ partial "header.html" . }} 2 | 3 | navigation 4 | 5 | 6 | {{if .Site.Home.Content }} 7 | {{.Site.Home.Content}} 8 | {{else}} 9 | {{if eq .Site.Language.Lang "fr"}} 10 |

Personaliser la page d'accueil

11 |

12 | Le site fonctionne. Ne pas oublier de personaliser cette page avec votre propre contenu. 3 manières de faire : 13 |

14 |
    15 |
  • 1. Créer un fichier _index.md dans le dossier content et le remplir de Markdown
  • 16 |
  • 2. Créer un fichier index.html dans le dossier static et le remplir de code HTML
  • 17 |
  • 3. Configurer le serveur http pour rediriger automatiquement la homepage vers la page de votre choix dans le site
  • 18 |
19 | {{else}} 20 |

Customize your own home page

21 |

22 | The site is working. Don't forget to customize this homepage with your own. You typically have 3 choices : 23 |

24 |
    25 |
  • 1. Create an _index.md document in content folder and fill it with Markdown content
  • 26 |
  • 2. Create an index.html file in the static folder and fill the file with HTML content
  • 27 |
  • 3. Configure your server to automatically redirect home page to one your documentation page
  • 28 |
29 | {{end}} 30 | {{ end }} 31 | {{ partial "footer.html" . }} 32 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/index.json: -------------------------------------------------------------------------------- 1 | [{{ range $index, $page := .Site.Pages }} 2 | {{- if ne $page.Type "json" -}} 3 | {{- if and $index (gt $index 0) -}},{{- end }} 4 | { 5 | "uri": "{{ $page.Permalink }}", 6 | "title": "{{ htmlEscape $page.Title}}", 7 | "tags": [{{ range $tindex, $tag := $page.Params.tags }}{{ if $tindex }}, {{ end }}"{{ $tag| htmlEscape }}"{{ end }}], 8 | "description": "{{ htmlEscape .Description}}", 9 | "content": {{$page.Plain | jsonify}} 10 | } 11 | {{- end -}} 12 | {{- end -}}] -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/custom-comments.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/custom-footer.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/custom-header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/favicon.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/footer.html: -------------------------------------------------------------------------------- 1 | {{ if .Params.chapter }} 2 |
3 | {{ end }} 4 |
5 | {{ partial "custom-comments.html" . }} 6 |
7 | 8 | 42 | 43 |
44 | 45 |
46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | {{ partial "custom-footer.html" . }} 66 | 67 | 68 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ .Hugo.Generator }} 7 | {{ partial "meta.html" . }} 8 | {{ partial "favicon.html" . }} 9 | {{ .Title }} :: {{ .Site.Title }} 10 | 11 | {{ $assetBusting := not .Site.Params.disableAssetsBusting }} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {{with .Site.Params.themeVariant}} 21 | 22 | {{end}} 23 | 24 | 25 | 26 | 36 | {{ partial "custom-header.html" . }} 37 | 38 | 39 | {{ partial "menu.html" . }} 40 |
41 |
42 |
43 | {{if not .IsHome}} 44 |
45 |
46 | {{ if and (or .IsPage .IsSection) .Site.Params.editURL }} 47 | {{ $File := .File }} 48 | {{ $Site := .Site }} 49 | {{with $File.Path }} 50 | 56 | {{ end }} 57 | {{ end }} 58 | {{$toc := (and (not .Params.disableToc) (not .Params.chapter))}} 59 | 72 | {{ if $toc }} 73 | {{ partial "toc.html" . }} 74 | {{ end }} 75 |
76 |
77 | {{ end }} 78 | 79 | {{ if .Params.chapter }} 80 |
81 | {{ end }} 82 |
83 | {{if and (not .IsHome) (not .Params.chapter) }} 84 |

{{.Title}}

85 | {{end}} 86 | 87 | {{define "breadcrumb"}} 88 | {{$parent := .page.Parent }} 89 | {{ if $parent }} 90 | {{ $value := (printf "%s > %s" $parent.URL $parent.Title .value) }} 91 | {{ template "breadcrumb" dict "page" $parent "value" $value }} 92 | {{else}} 93 | {{.value|safeHTML}} 94 | {{end}} 95 | {{end}} 96 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/logo.html: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/menu-footer.html: -------------------------------------------------------------------------------- 1 |   -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/menu.html: -------------------------------------------------------------------------------- 1 | 92 | 93 | 94 | {{ define "section-tree-nav" }} 95 | {{ $showvisitedlinks := .showvisitedlinks }} 96 | {{ $currentNode := .currentnode }} 97 | {{with .sect}} 98 | {{if .IsSection}} 99 | {{safeHTML .Params.head}} 100 |
  • 105 | 106 | {{safeHTML .Params.Pre}}{{or .Params.menuTitle .LinkTitle .Title}}{{safeHTML .Params.Post}} 107 | {{ if $showvisitedlinks}} 108 | 109 | {{ end }} 110 | 111 | {{ $numberOfPages := (add (len .Pages) (len .Sections)) }} 112 | {{ if ne $numberOfPages 0 }} 113 |
      114 | {{ $currentNode.Scratch.Set "pages" .Pages }} 115 | {{ if .Sections}} 116 | {{ $currentNode.Scratch.Set "pages" (.Pages | union .Sections) }} 117 | {{end}} 118 | {{ $pages := ($currentNode.Scratch.Get "pages") }} 119 | 120 | {{if eq .Site.Params.ordersectionsby "title"}} 121 | {{ range $pages.ByTitle }} 122 | {{ if and .Params.hidden (not $.showhidden) }} 123 | {{else}} 124 | {{ template "section-tree-nav" dict "sect" . "currentnode" $currentNode "showvisitedlinks" $showvisitedlinks }} 125 | {{end}} 126 | {{ end }} 127 | {{else}} 128 | {{ range $pages.ByWeight }} 129 | {{ if and .Params.hidden (not $.showhidden) }} 130 | {{else}} 131 | {{ template "section-tree-nav" dict "sect" . "currentnode" $currentNode "showvisitedlinks" $showvisitedlinks }} 132 | {{end}} 133 | {{ end }} 134 | {{end}} 135 |
    136 | {{ end }} 137 |
  • 138 | {{else}} 139 | {{ if not .Params.Hidden }} 140 |
  • 141 | 142 | {{safeHTML .Params.Pre}}{{or .Params.menuTitle .LinkTitle .Title}}{{safeHTML .Params.Post}} 143 | {{ if $showvisitedlinks}}{{end}} 144 | 145 |
  • 146 | {{ end }} 147 | {{end}} 148 | {{ end }} 149 | {{ end }} 150 | 151 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/meta.html: -------------------------------------------------------------------------------- 1 | 2 | {{ with .Site.Params.author }}{{ end }} 3 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/search.html: -------------------------------------------------------------------------------- 1 | 6 | {{ $assetBusting := not .Site.Params.disableAssetsBusting }} 7 | 8 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/partials/toc.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | {{ .TableOfContents }} 4 |
    5 |
    6 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/shortcodes/attachments.html: -------------------------------------------------------------------------------- 1 |
    2 | 6 | {{if eq .Page.File.BaseFileName "index"}} 7 | {{$.Scratch.Add "filesName" "files"}} 8 | {{else}} 9 | {{$.Scratch.Add "filesName" (printf "%s.files" .Page.File.BaseFileName)}} 10 | {{end}} 11 |
    12 | {{ range (readDir (printf "./content/%s%s" .Page.File.Dir ($.Scratch.Get "filesName")) ) }} 13 | {{ $fileDir := replace $.Page.File.Dir "\\" "/" }} 14 | {{if ($.Get "pattern")}} 15 | {{if (findRE ($.Get "pattern") .Name)}} 16 |
  • 17 | 18 | {{.Name}} 19 | 20 | ({{div .Size 1024 }} ko) 21 |
  • 22 | {{end}} 23 | {{else}} 24 |
  • 25 | 26 | {{.Name}} 27 | 28 | ({{div .Size 1024 }} ko) 29 |
  • 30 | {{end}} 31 | {{end}} 32 |
    33 | {{.Inner}} 34 |
    35 | 36 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/shortcodes/button.html: -------------------------------------------------------------------------------- 1 | 2 | {{ $icon := .Get "icon" }} 3 | {{ $iconposition := .Get "icon-position" }} 4 | {{ if ($icon) }} 5 | {{ if or (not ($iconposition)) (eq $iconposition "left") }} 6 | 7 | {{ end }} 8 | {{ end }} 9 | {{ .Inner }} 10 | {{ if and ($icon) (eq $iconposition "right")}} 11 | 12 | {{ end }} 13 | 14 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/shortcodes/children.html: -------------------------------------------------------------------------------- 1 | {{ $showhidden := .Get "showhidden"}} 2 | {{ $style := .Get "style" | default "li" }} 3 | {{ $depth := .Get "depth" | default 1 }} 4 | {{ $withDescription := .Get "description" | default false }} 5 | {{ $sortTerm := .Get "sort" | default "Weight" }} 6 | 7 | 8 |
      9 | {{ .Scratch.Set "pages" .Page.Pages }} 10 | {{ if .Page.Sections}} 11 | {{ .Scratch.Set "pages" (.Page.Pages | union .Page.Sections) }} 12 | {{end}} 13 | {{ $pages := (.Scratch.Get "pages") }} 14 | 15 | {{if eq $sortTerm "Weight"}} 16 | {{template "childs" dict "menu" $pages.ByWeight "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} 17 | {{else if eq $sortTerm "Name"}} 18 | {{template "childs" dict "menu" $pages.ByTitle "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} 19 | {{else if eq $sortTerm "PublishDate"}} 20 | {{template "childs" dict "menu" $pages.ByPublishDate "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} 21 | {{else if eq $sortTerm "Date"}} 22 | {{template "childs" dict "menu" $pages.ByDate "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} 23 | {{else if eq $sortTerm "Length"}} 24 | {{template "childs" dict "menu" $pages.ByLength "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} 25 | {{else}} 26 | {{template "childs" dict "menu" $pages "style" $style "showhidden" $showhidden "count" 1 "depth" $depth "pages" .Site.Pages "description" $withDescription "sortTerm" $sortTerm}} 27 | {{end}} 28 |
    29 | 30 | {{.Inner|safeHTML}} 31 | 32 | {{ define "childs" }} 33 | {{ range .menu }} 34 | {{ if and .Params.hidden (not $.showhidden) }} 35 | {{else}} 36 | 37 | 38 | {{if hasPrefix $.style "h"}} 39 | {{$num := sub ( int (trim $.style "h") ) 1 }} 40 | {{$numn := add $num $.count }} 41 | 42 | {{(printf "" $numn)|safeHTML}} 43 | {{ .Title }} 44 | {{(printf "" $numn)|safeHTML}} 45 | 46 | {{else}} 47 | {{(printf "<%s>" $.style)|safeHTML}} 48 | {{ .Title }} 49 | {{(printf "" $.style)|safeHTML}} 50 | {{end}} 51 | 52 | 53 | 54 | 55 | 56 | {{if $.description}} 57 | {{if .Description}} 58 |

    {{.Description}}

    59 | {{else}} 60 |

    {{.Summary}}

    61 | {{end}} 62 | {{end}} 63 | 64 | 65 | 66 | {{ if lt $.count $.depth}} 67 | {{if eq $.style "li"}} 68 |
      69 | {{end}} 70 | {{ $.Page.Scratch.Set "pages" .Pages }} 71 | {{ if .Sections}} 72 | {{ $.Page.Scratch.Set "pages" (.Pages | union .Sections) }} 73 | {{end}} 74 | {{ $pages := ($.Page.Scratch.Get "pages") }} 75 | 76 | {{if eq $.sortTerm "Weight"}} 77 | {{template "childs" dict "menu" $pages.ByWeight "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} 78 | {{else if eq $.sortTerm "Name"}} 79 | {{template "childs" dict "menu" $pages.ByTitle "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} 80 | {{else if eq $.sortTerm "PublishDate"}} 81 | {{template "childs" dict "menu" $pages.ByPublishDate "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} 82 | {{else if eq $.sortTerm "Date"}} 83 | {{template "childs" dict "menu" $pages.ByDate "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} 84 | {{else if eq $.sortTerm "Length"}} 85 | {{template "childs" dict "menu" $pages.ByLength "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} 86 | {{else}} 87 | {{template "childs" dict "menu" $pages "style" $.style "showhidden" $.showhidden "count" (add $.count 1) "depth" $.depth "pages" $.pages "description" $.description "sortTerm" $.sortTerm}} 88 | {{end}} 89 | {{if eq $.style "li"}} 90 |
    91 | {{end}} 92 | {{end}} 93 | 94 | {{end}} 95 | {{end}} 96 | {{end}} -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/shortcodes/expand.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | 5 | {{$expandMessage := T "Expand-title"}} 6 | {{ if .IsNamedParams }} 7 | {{.Get "default" | default $expandMessage}} 8 | {{else}} 9 | {{.Get 0 | default $expandMessage}} 10 | {{end}} 11 | 12 |
    13 | 16 |
    -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/shortcodes/mermaid.html: -------------------------------------------------------------------------------- 1 |
    {{ safeHTML .Inner }}
    2 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/shortcodes/notice.html: -------------------------------------------------------------------------------- 1 |
    {{ .Inner }}
    2 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/shortcodes/ref.html: -------------------------------------------------------------------------------- 1 | {{- if in (.Get 0) "/_index.md" -}} 2 | {{- $paths := (split (.Get 0) "_index.md") -}} 3 | {{- $pagepath := index $paths 0 -}} 4 | {{- $anchor := index $paths 1 -}} 5 | {{- with .Site.GetPage "section" (trim $pagepath "/") -}} 6 | {{- ( printf "%s%s" $pagepath $anchor ) | relLangURL -}} 7 | {{- end -}} 8 | {{- else -}} 9 | {{- with .Site.GetPage "section" (.Get 0) }} 10 | {{- .URL -}} 11 | {{- else -}} 12 | {{- .Get 0 | relref .Page -}} 13 | {{- end -}} 14 | {{- end -}} -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/shortcodes/relref.html: -------------------------------------------------------------------------------- 1 | {{- if in (.Get 0) "/_index.md" -}} 2 | {{- $paths := (split (.Get 0) "_index.md") -}} 3 | {{- $pagepath := index $paths 0 -}} 4 | {{- $anchor := index $paths 1 -}} 5 | {{- with .Site.GetPage "section" (trim $pagepath "/") -}} 6 | {{- ( printf "%s%s" $pagepath $anchor ) | relLangURL -}} 7 | {{- end -}} 8 | {{- else -}} 9 | {{- with .Site.GetPage "section" (.Get 0) }} 10 | {{- .URL -}} 11 | {{- else -}} 12 | {{- .Get 0 | relref .Page -}} 13 | {{- end -}} 14 | {{- end -}} -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/layouts/shortcodes/siteparam.html: -------------------------------------------------------------------------------- 1 | {{- $paramName := (.Get 0) -}} 2 | {{- $siteParams := .Site.Params -}} 3 | {{- with $paramName -}} 4 | {{- with $siteParams -}} 5 | {{- index . (lower $paramName) -}} 6 | {{- end -}} 7 | {{- end -}} -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/css/auto-complete.css: -------------------------------------------------------------------------------- 1 | .autocomplete-suggestions { 2 | text-align: left; 3 | cursor: default; 4 | border: 1px solid #ccc; 5 | border-top: 0; 6 | background: #fff; 7 | box-shadow: -1px 1px 3px rgba(0,0,0,.1); 8 | 9 | /* core styles should not be changed */ 10 | position: absolute; 11 | display: none; 12 | z-index: 9999; 13 | max-height: 254px; 14 | overflow: hidden; 15 | overflow-y: auto; 16 | box-sizing: border-box; 17 | 18 | } 19 | .autocomplete-suggestion { 20 | position: relative; 21 | cursor: pointer; 22 | padding: 7px; 23 | line-height: 23px; 24 | white-space: nowrap; 25 | overflow: hidden; 26 | text-overflow: ellipsis; 27 | color: #333; 28 | } 29 | 30 | .autocomplete-suggestion b { 31 | font-weight: normal; 32 | color: #1f8dd6; 33 | } 34 | 35 | .autocomplete-suggestion.selected { 36 | background: #333; 37 | color: #fff; 38 | } 39 | 40 | .autocomplete-suggestion:hover { 41 | background: #444; 42 | color: #fff; 43 | } 44 | 45 | .autocomplete-suggestion > .context { 46 | font-size: 12px; 47 | } 48 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/css/featherlight.min.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Featherlight - ultra slim jQuery lightbox 3 | * Version 1.2.3 - http://noelboss.github.io/featherlight/ 4 | * 5 | * Copyright 2015, Noël Raoul Bossart (http://www.noelboss.com) 6 | * MIT Licensed. 7 | **/ 8 | @media all{.featherlight{display:none;position:fixed;top:0;right:0;bottom:0;left:0;z-index:2147483647;text-align:center;white-space:nowrap;cursor:pointer;background:#333;background:rgba(0,0,0,0)}.featherlight:last-of-type{background:rgba(0,0,0,.8)}.featherlight:before{content:'';display:inline-block;height:100%;vertical-align:middle;margin-right:-.25em}.featherlight .featherlight-content{position:relative;text-align:left;vertical-align:middle;display:inline-block;overflow:auto;padding:25px 25px 0;border-bottom:25px solid transparent;min-width:30%;margin-left:5%;margin-right:5%;max-height:95%;background:#fff;cursor:auto;white-space:normal}.featherlight .featherlight-inner{display:block}.featherlight .featherlight-close-icon{position:absolute;z-index:9999;top:0;right:0;line-height:25px;width:25px;cursor:pointer;text-align:center;font:Arial,sans-serif;background:#fff;background:rgba(255,255,255,.3);color:#000}.featherlight .featherlight-image{width:100%}.featherlight-iframe .featherlight-content{border-bottom:0;padding:0}.featherlight iframe{border:0}}@media only screen and (max-width:1024px){.featherlight .featherlight-content{margin-left:10px;margin-right:10px;max-height:98%;padding:10px 10px 0;border-bottom:10px solid transparent}} -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/css/hugo-theme.css: -------------------------------------------------------------------------------- 1 | /* Insert here special css for hugo theme, on top of any other imported css */ 2 | 3 | 4 | /* Table of contents */ 5 | 6 | .progress ul { 7 | list-style: none; 8 | margin: 0; 9 | padding: 0 5px; 10 | } 11 | 12 | #TableOfContents { 13 | font-size: 13px !important; 14 | max-height: 85vh; 15 | overflow: auto; 16 | padding: 15px !important; 17 | } 18 | 19 | 20 | #TableOfContents > ul > li > ul > li > ul li { 21 | margin-right: 8px; 22 | } 23 | 24 | #TableOfContents > ul > li > a { 25 | font-weight: bold; padding: 0 18px; margin: 0 2px; 26 | } 27 | 28 | #TableOfContents > ul > li > ul > li > a { 29 | font-weight: bold; 30 | } 31 | 32 | #TableOfContents > ul > li > ul > li > ul > li > ul > li > ul > li { 33 | display: none; 34 | } 35 | 36 | body { 37 | font-size: 16px !important; 38 | color: #323232 !important; 39 | } 40 | 41 | #body a.highlight, #body a.highlight:hover, #body a.highlight:focus { 42 | text-decoration: none; 43 | outline: none; 44 | outline: 0; 45 | } 46 | #body a.highlight { 47 | line-height: 1.1; 48 | display: inline-block; 49 | } 50 | #body a.highlight:after { 51 | display: block; 52 | content: ""; 53 | height: 1px; 54 | width: 0%; 55 | background-color: #0082a7; /*#CE3B2F*/ 56 | -webkit-transition: width 0.5s ease; 57 | -moz-transition: width 0.5s ease; 58 | -ms-transition: width 0.5s ease; 59 | transition: width 0.5s ease; 60 | } 61 | #body a.highlight:hover:after, #body a.highlight:focus:after { 62 | width: 100%; 63 | } 64 | .progress { 65 | position:absolute; 66 | background-color: rgba(246, 246, 246, 0.97); 67 | width: auto; 68 | border: thin solid #ECECEC; 69 | display:none; 70 | z-index:200; 71 | } 72 | 73 | #toc-menu { 74 | border-right: thin solid #DAD8D8 !important; 75 | padding-right: 1rem !important; 76 | margin-right: 0.5rem !important; 77 | } 78 | 79 | #sidebar-toggle-span { 80 | border-right: thin solid #DAD8D8 !important; 81 | padding-right: 0.5rem !important; 82 | margin-right: 1rem !important; 83 | } 84 | 85 | .btn { 86 | display: inline-block !important; 87 | padding: 6px 12px !important; 88 | margin-bottom: 0 !important; 89 | font-size: 14px !important; 90 | font-weight: normal !important; 91 | line-height: 1.42857143 !important; 92 | text-align: center !important; 93 | white-space: nowrap !important; 94 | vertical-align: middle !important; 95 | -ms-touch-action: manipulation !important; 96 | touch-action: manipulation !important; 97 | cursor: pointer !important; 98 | -webkit-user-select: none !important; 99 | -moz-user-select: none !important; 100 | -ms-user-select: none !important; 101 | user-select: none !important; 102 | background-image: none !important; 103 | border: 1px solid transparent !important; 104 | border-radius: 4px !important; 105 | -webkit-transition: all 0.15s !important; 106 | -moz-transition: all 0.15s !important; 107 | transition: all 0.15s !important; 108 | } 109 | .btn:focus { 110 | /*outline: thin dotted; 111 | outline: 5px auto -webkit-focus-ring-color; 112 | outline-offset: -2px;*/ 113 | outline: none !important; 114 | } 115 | .btn:hover, 116 | .btn:focus { 117 | color: #2b2b2b !important; 118 | text-decoration: none !important; 119 | } 120 | 121 | .btn-default { 122 | color: #333 !important; 123 | background-color: #fff !important; 124 | border-color: #ccc !important; 125 | } 126 | .btn-default:hover, 127 | .btn-default:focus, 128 | .btn-default:active { 129 | color: #fff !important; 130 | background-color: #9e9e9e !important; 131 | border-color: #9e9e9e !important; 132 | } 133 | .btn-default:active { 134 | background-image: none !important; 135 | } 136 | 137 | /* anchors */ 138 | .anchor { 139 | color: #00bdf3; 140 | font-size: 0.5em; 141 | cursor:pointer; 142 | visibility:hidden; 143 | margin-left: 0.5em; 144 | position: absolute; 145 | margin-top:0.1em; 146 | } 147 | 148 | h2:hover .anchor, h3:hover .anchor, h4:hover .anchor, h5:hover .anchor, h6:hover .anchor { 149 | visibility:visible; 150 | } 151 | 152 | /* Redfines headers style */ 153 | 154 | h2, h3, h4, h5, h6 { 155 | font-weight: 400; 156 | line-height: 1.1; 157 | } 158 | 159 | h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { 160 | font-weight: inherit; 161 | } 162 | 163 | h2 { 164 | font-size: 2.5rem; 165 | line-height: 110% !important; 166 | margin: 2.5rem 0 1.5rem 0; 167 | } 168 | 169 | h3 { 170 | font-size: 2rem; 171 | line-height: 110% !important; 172 | margin: 2rem 0 1rem 0; 173 | } 174 | 175 | h4 { 176 | font-size: 1.5rem; 177 | line-height: 110% !important; 178 | margin: 1.5rem 0 0.75rem 0; 179 | } 180 | 181 | h5 { 182 | font-size: 1rem; 183 | line-height: 110% !important; 184 | margin: 1rem 0 0.2rem 0; 185 | } 186 | 187 | h6 { 188 | font-size: 0.5rem; 189 | line-height: 110% !important; 190 | margin: 0.5rem 0 0.2rem 0; 191 | } 192 | 193 | p { 194 | margin: 1rem 0; 195 | } 196 | 197 | figcaption h4 { 198 | font-weight: 300 !important; 199 | opacity: .85; 200 | font-size: 1em; 201 | text-align: center; 202 | margin-top: -1.5em; 203 | } 204 | 205 | .select-style { 206 | border: 0; 207 | width: 150px; 208 | border-radius: 0px; 209 | overflow: hidden; 210 | display: inline-flex; 211 | } 212 | 213 | .select-style svg { 214 | fill: #ccc; 215 | width: 14px; 216 | height: 14px; 217 | pointer-events: none; 218 | margin: auto; 219 | } 220 | 221 | .select-style svg:hover { 222 | fill: #e6e6e6; 223 | } 224 | 225 | .select-style select { 226 | padding: 0; 227 | width: 130%; 228 | border: none; 229 | box-shadow: none; 230 | background: transparent; 231 | background-image: none; 232 | -webkit-appearance: none; 233 | margin: auto; 234 | margin-left: 0px; 235 | margin-right: -20px; 236 | } 237 | 238 | .select-style select:focus { 239 | outline: none; 240 | } 241 | 242 | .select-style :hover { 243 | cursor: pointer; 244 | } 245 | 246 | @media only all and (max-width: 47.938em) { 247 | #breadcrumbs .links, #top-github-link-text { 248 | display: none; 249 | } 250 | } 251 | 252 | .is-sticky #top-bar { 253 | box-shadow: -1px 2px 5px 1px rgba(0, 0, 0, 0.1); 254 | } -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/css/hybrid.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | vim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid) 4 | 5 | */ 6 | 7 | /*background color*/ 8 | .hljs { 9 | display: block; 10 | overflow-x: auto; 11 | padding: 0.5em; 12 | background: #1d1f21; 13 | } 14 | 15 | /*selection color*/ 16 | .hljs::selection, 17 | .hljs span::selection { 18 | background: #373b41; 19 | } 20 | 21 | .hljs::-moz-selection, 22 | .hljs span::-moz-selection { 23 | background: #373b41; 24 | } 25 | 26 | /*foreground color*/ 27 | .hljs { 28 | color: #c5c8c6; 29 | } 30 | 31 | /*color: fg_yellow*/ 32 | .hljs-title, 33 | .hljs-name { 34 | color: #f0c674; 35 | } 36 | 37 | /*color: fg_comment*/ 38 | .hljs-comment, 39 | .hljs-meta, 40 | .hljs-meta .hljs-keyword { 41 | color: #707880; 42 | } 43 | 44 | /*color: fg_red*/ 45 | .hljs-number, 46 | .hljs-symbol, 47 | .hljs-literal, 48 | .hljs-deletion, 49 | .hljs-link { 50 | color: #cc6666 51 | } 52 | 53 | /*color: fg_green*/ 54 | .hljs-string, 55 | .hljs-doctag, 56 | .hljs-addition, 57 | .hljs-regexp, 58 | .hljs-selector-attr, 59 | .hljs-selector-pseudo { 60 | color: #b5bd68; 61 | } 62 | 63 | /*color: fg_purple*/ 64 | .hljs-attribute, 65 | .hljs-code, 66 | .hljs-selector-id { 67 | color: #b294bb; 68 | } 69 | 70 | /*color: fg_blue*/ 71 | .hljs-keyword, 72 | .hljs-selector-tag, 73 | .hljs-bullet, 74 | .hljs-tag { 75 | color: #81a2be; 76 | } 77 | 78 | /*color: fg_aqua*/ 79 | .hljs-subst, 80 | .hljs-variable, 81 | .hljs-template-tag, 82 | .hljs-template-variable { 83 | color: #8abeb7; 84 | } 85 | 86 | /*color: fg_orange*/ 87 | .hljs-type, 88 | .hljs-built_in, 89 | .hljs-builtin-name, 90 | .hljs-quote, 91 | .hljs-section, 92 | .hljs-selector-class { 93 | color: #de935f; 94 | } 95 | 96 | .hljs-emphasis { 97 | font-style: italic; 98 | } 99 | 100 | .hljs-strong { 101 | font-weight: bold; 102 | } 103 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/css/perfect-scrollbar.min.css: -------------------------------------------------------------------------------- 1 | /* perfect-scrollbar v0.6.13 */ 2 | .ps-container{-ms-touch-action:auto;touch-action:auto;overflow:hidden !important;-ms-overflow-style:none}@supports (-ms-overflow-style: none){.ps-container{overflow:auto !important}}@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none){.ps-container{overflow:auto !important}}.ps-container.ps-active-x>.ps-scrollbar-x-rail,.ps-container.ps-active-y>.ps-scrollbar-y-rail{display:block;background-color:transparent}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:.9}.ps-container.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999;height:11px}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:.9}.ps-container.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999;width:11px}.ps-container>.ps-scrollbar-x-rail{display:none;position:absolute;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;bottom:0px;height:15px}.ps-container>.ps-scrollbar-x-rail>.ps-scrollbar-x{position:absolute;background-color:#aaa;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;-o-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;-moz-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;bottom:2px;height:6px}.ps-container>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x,.ps-container>.ps-scrollbar-x-rail:active>.ps-scrollbar-x{height:11px}.ps-container>.ps-scrollbar-y-rail{display:none;position:absolute;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;right:0;width:15px}.ps-container>.ps-scrollbar-y-rail>.ps-scrollbar-y{position:absolute;background-color:#aaa;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;-o-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;-moz-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;right:2px;width:6px}.ps-container>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y,.ps-container>.ps-scrollbar-y-rail:active>.ps-scrollbar-y{width:11px}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail{background-color:#eee;opacity:.9}.ps-container:hover.ps-in-scrolling.ps-x>.ps-scrollbar-x-rail>.ps-scrollbar-x{background-color:#999;height:11px}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail{background-color:#eee;opacity:.9}.ps-container:hover.ps-in-scrolling.ps-y>.ps-scrollbar-y-rail>.ps-scrollbar-y{background-color:#999;width:11px}.ps-container:hover>.ps-scrollbar-x-rail,.ps-container:hover>.ps-scrollbar-y-rail{opacity:.6}.ps-container:hover>.ps-scrollbar-x-rail:hover{background-color:#eee;opacity:.9}.ps-container:hover>.ps-scrollbar-x-rail:hover>.ps-scrollbar-x{background-color:#999}.ps-container:hover>.ps-scrollbar-y-rail:hover{background-color:#eee;opacity:.9}.ps-container:hover>.ps-scrollbar-y-rail:hover>.ps-scrollbar-y{background-color:#999} 3 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/css/theme-blue.css: -------------------------------------------------------------------------------- 1 | 2 | :root{ 3 | 4 | --MAIN-TEXT-color:#323232; /* Color of text by default */ 5 | --MAIN-TITLES-TEXT-color: #5e5e5e; /* Color of titles h2-h3-h4-h5 */ 6 | --MAIN-LINK-color:#1C90F3; /* Color of links */ 7 | --MAIN-LINK-HOVER-color:#167ad0; /* Color of hovered links */ 8 | --MAIN-ANCHOR-color: #1C90F3; /* color of anchors on titles */ 9 | 10 | --MENU-HEADER-BG-color:#1C90F3; /* Background color of menu header */ 11 | --MENU-HEADER-BORDER-color:#33a1ff; /*Color of menu header border */ 12 | 13 | --MENU-SEARCH-BG-color:#167ad0; /* Search field background color (by default borders + icons) */ 14 | --MENU-SEARCH-BOX-color: #33a1ff; /* Override search field border color */ 15 | --MENU-SEARCH-BOX-ICONS-color: #a1d2fd; /* Override search field icons color */ 16 | 17 | --MENU-SECTIONS-ACTIVE-BG-color:#20272b; /* Background color of the active section and its childs */ 18 | --MENU-SECTIONS-BG-color:#252c31; /* Background color of other sections */ 19 | --MENU-SECTIONS-LINK-color: #ccc; /* Color of links in menu */ 20 | --MENU-SECTIONS-LINK-HOVER-color: #e6e6e6; /* Color of links in menu, when hovered */ 21 | --MENU-SECTION-ACTIVE-CATEGORY-color: #777; /* Color of active category text */ 22 | --MENU-SECTION-ACTIVE-CATEGORY-BG-color: #fff; /* Color of background for the active category (only) */ 23 | 24 | --MENU-VISITED-color: #33a1ff; /* Color of 'page visited' icons in menu */ 25 | --MENU-SECTION-HR-color: #20272b; /* Color of
    separator in menu */ 26 | 27 | } 28 | 29 | body { 30 | color: var(--MAIN-TEXT-color) !important; 31 | } 32 | 33 | textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus { 34 | border-color: none; 35 | box-shadow: none; 36 | } 37 | 38 | h2, h3, h4, h5 { 39 | color: var(--MAIN-TITLES-TEXT-color) !important; 40 | } 41 | 42 | a { 43 | color: var(--MAIN-LINK-color); 44 | } 45 | 46 | .anchor { 47 | color: var(--MAIN-ANCHOR-color); 48 | } 49 | 50 | a:hover { 51 | color: var(--MAIN-LINK-HOVER-color); 52 | } 53 | 54 | #sidebar ul li.visited > a .read-icon { 55 | color: var(--MENU-VISITED-color); 56 | } 57 | 58 | #body a.highlight:after { 59 | display: block; 60 | content: ""; 61 | height: 1px; 62 | width: 0%; 63 | -webkit-transition: width 0.5s ease; 64 | -moz-transition: width 0.5s ease; 65 | -ms-transition: width 0.5s ease; 66 | transition: width 0.5s ease; 67 | background-color: var(--MAIN-LINK-HOVER-color); 68 | } 69 | #sidebar { 70 | background-color: var(--MENU-SECTIONS-BG-color); 71 | } 72 | #sidebar #header-wrapper { 73 | background: var(--MENU-HEADER-BG-color); 74 | color: var(--MENU-SEARCH-BOX-color); 75 | border-color: var(--MENU-HEADER-BORDER-color); 76 | } 77 | #sidebar .searchbox { 78 | border-color: var(--MENU-SEARCH-BOX-color); 79 | background: var(--MENU-SEARCH-BG-color); 80 | } 81 | #sidebar ul.topics > li.parent, #sidebar ul.topics > li.active { 82 | background: var(--MENU-SECTIONS-ACTIVE-BG-color); 83 | } 84 | #sidebar .searchbox * { 85 | color: var(--MENU-SEARCH-BOX-ICONS-color); 86 | } 87 | 88 | #sidebar a { 89 | color: var(--MENU-SECTIONS-LINK-color); 90 | } 91 | 92 | #sidebar a:hover { 93 | color: var(--MENU-SECTIONS-LINK-HOVER-color); 94 | } 95 | 96 | #sidebar ul li.active > a { 97 | background: var(--MENU-SECTION-ACTIVE-CATEGORY-BG-color); 98 | color: var(--MENU-SECTION-ACTIVE-CATEGORY-color) !important; 99 | } 100 | 101 | #sidebar hr { 102 | border-color: var(--MENU-SECTION-HR-color); 103 | } 104 | 105 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/css/theme-green.css: -------------------------------------------------------------------------------- 1 | 2 | :root{ 3 | 4 | --MAIN-TEXT-color:#323232; /* Color of text by default */ 5 | --MAIN-TITLES-TEXT-color: #5e5e5e; /* Color of titles h2-h3-h4-h5 */ 6 | --MAIN-LINK-color:#599a3e; /* Color of links */ 7 | --MAIN-LINK-HOVER-color:#3f6d2c; /* Color of hovered links */ 8 | --MAIN-ANCHOR-color: #599a3e; /* color of anchors on titles */ 9 | 10 | --MENU-HEADER-BG-color:#74b559; /* Background color of menu header */ 11 | --MENU-HEADER-BORDER-color:#9cd484; /*Color of menu header border */ 12 | 13 | --MENU-SEARCH-BG-color:#599a3e; /* Search field background color (by default borders + icons) */ 14 | --MENU-SEARCH-BOX-color: #84c767; /* Override search field border color */ 15 | --MENU-SEARCH-BOX-ICONS-color: #c7f7c4; /* Override search field icons color */ 16 | 17 | --MENU-SECTIONS-ACTIVE-BG-color:#1b211c; /* Background color of the active section and its childs */ 18 | --MENU-SECTIONS-BG-color:#222723; /* Background color of other sections */ 19 | --MENU-SECTIONS-LINK-color: #ccc; /* Color of links in menu */ 20 | --MENU-SECTIONS-LINK-HOVER-color: #e6e6e6; /* Color of links in menu, when hovered */ 21 | --MENU-SECTION-ACTIVE-CATEGORY-color: #777; /* Color of active category text */ 22 | --MENU-SECTION-ACTIVE-CATEGORY-BG-color: #fff; /* Color of background for the active category (only) */ 23 | 24 | --MENU-VISITED-color: #599a3e; /* Color of 'page visited' icons in menu */ 25 | --MENU-SECTION-HR-color: #18211c; /* Color of
    separator in menu */ 26 | 27 | } 28 | 29 | body { 30 | color: var(--MAIN-TEXT-color) !important; 31 | } 32 | 33 | textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus { 34 | border-color: none; 35 | box-shadow: none; 36 | } 37 | 38 | h2, h3, h4, h5 { 39 | color: var(--MAIN-TITLES-TEXT-color) !important; 40 | } 41 | 42 | a { 43 | color: var(--MAIN-LINK-color); 44 | } 45 | 46 | .anchor { 47 | color: var(--MAIN-ANCHOR-color); 48 | } 49 | 50 | a:hover { 51 | color: var(--MAIN-LINK-HOVER-color); 52 | } 53 | 54 | #sidebar ul li.visited > a .read-icon { 55 | color: var(--MENU-VISITED-color); 56 | } 57 | 58 | #body a.highlight:after { 59 | display: block; 60 | content: ""; 61 | height: 1px; 62 | width: 0%; 63 | -webkit-transition: width 0.5s ease; 64 | -moz-transition: width 0.5s ease; 65 | -ms-transition: width 0.5s ease; 66 | transition: width 0.5s ease; 67 | background-color: var(--MAIN-LINK-HOVER-color); 68 | } 69 | #sidebar { 70 | background-color: var(--MENU-SECTIONS-BG-color); 71 | } 72 | #sidebar #header-wrapper { 73 | background: var(--MENU-HEADER-BG-color); 74 | color: var(--MENU-SEARCH-BOX-color); 75 | border-color: var(--MENU-HEADER-BORDER-color); 76 | } 77 | #sidebar .searchbox { 78 | border-color: var(--MENU-SEARCH-BOX-color); 79 | background: var(--MENU-SEARCH-BG-color); 80 | } 81 | #sidebar ul.topics > li.parent, #sidebar ul.topics > li.active { 82 | background: var(--MENU-SECTIONS-ACTIVE-BG-color); 83 | } 84 | #sidebar .searchbox * { 85 | color: var(--MENU-SEARCH-BOX-ICONS-color); 86 | } 87 | 88 | #sidebar a { 89 | color: var(--MENU-SECTIONS-LINK-color); 90 | } 91 | 92 | #sidebar a:hover { 93 | color: var(--MENU-SECTIONS-LINK-HOVER-color); 94 | } 95 | 96 | #sidebar ul li.active > a { 97 | background: var(--MENU-SECTION-ACTIVE-CATEGORY-BG-color); 98 | color: var(--MENU-SECTION-ACTIVE-CATEGORY-color) !important; 99 | } 100 | 101 | #sidebar hr { 102 | border-color: var(--MENU-SECTION-HR-color); 103 | } 104 | 105 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/css/theme-red.css: -------------------------------------------------------------------------------- 1 | 2 | :root{ 3 | 4 | --MAIN-TEXT-color:#323232; /* Color of text by default */ 5 | --MAIN-TITLES-TEXT-color: #5e5e5e; /* Color of titles h2-h3-h4-h5 */ 6 | --MAIN-LINK-color:#f31c1c; /* Color of links */ 7 | --MAIN-LINK-HOVER-color:#d01616; /* Color of hovered links */ 8 | --MAIN-ANCHOR-color: #f31c1c; /* color of anchors on titles */ 9 | 10 | --MENU-HEADER-BG-color:#dc1010; /* Background color of menu header */ 11 | --MENU-HEADER-BORDER-color:#e23131; /*Color of menu header border */ 12 | 13 | --MENU-SEARCH-BG-color:#b90000; /* Search field background color (by default borders + icons) */ 14 | --MENU-SEARCH-BOX-color: #ef2020; /* Override search field border color */ 15 | --MENU-SEARCH-BOX-ICONS-color: #fda1a1; /* Override search field icons color */ 16 | 17 | --MENU-SECTIONS-ACTIVE-BG-color:#2b2020; /* Background color of the active section and its childs */ 18 | --MENU-SECTIONS-BG-color:#312525; /* Background color of other sections */ 19 | --MENU-SECTIONS-LINK-color: #ccc; /* Color of links in menu */ 20 | --MENU-SECTIONS-LINK-HOVER-color: #e6e6e6; /* Color of links in menu, when hovered */ 21 | --MENU-SECTION-ACTIVE-CATEGORY-color: #777; /* Color of active category text */ 22 | --MENU-SECTION-ACTIVE-CATEGORY-BG-color: #fff; /* Color of background for the active category (only) */ 23 | 24 | --MENU-VISITED-color: #ff3333; /* Color of 'page visited' icons in menu */ 25 | --MENU-SECTION-HR-color: #2b2020; /* Color of
    separator in menu */ 26 | 27 | } 28 | 29 | body { 30 | color: var(--MAIN-TEXT-color) !important; 31 | } 32 | 33 | textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus { 34 | border-color: none; 35 | box-shadow: none; 36 | } 37 | 38 | h2, h3, h4, h5 { 39 | color: var(--MAIN-TITLES-TEXT-color) !important; 40 | } 41 | 42 | a { 43 | color: var(--MAIN-LINK-color); 44 | } 45 | 46 | .anchor { 47 | color: var(--MAIN-ANCHOR-color); 48 | } 49 | 50 | a:hover { 51 | color: var(--MAIN-LINK-HOVER-color); 52 | } 53 | 54 | #sidebar ul li.visited > a .read-icon { 55 | color: var(--MENU-VISITED-color); 56 | } 57 | 58 | #body a.highlight:after { 59 | display: block; 60 | content: ""; 61 | height: 1px; 62 | width: 0%; 63 | -webkit-transition: width 0.5s ease; 64 | -moz-transition: width 0.5s ease; 65 | -ms-transition: width 0.5s ease; 66 | transition: width 0.5s ease; 67 | background-color: var(--MAIN-LINK-HOVER-color); 68 | } 69 | #sidebar { 70 | background-color: var(--MENU-SECTIONS-BG-color); 71 | } 72 | #sidebar #header-wrapper { 73 | background: var(--MENU-HEADER-BG-color); 74 | color: var(--MENU-SEARCH-BOX-color); 75 | border-color: var(--MENU-HEADER-BORDER-color); 76 | } 77 | #sidebar .searchbox { 78 | border-color: var(--MENU-SEARCH-BOX-color); 79 | background: var(--MENU-SEARCH-BG-color); 80 | } 81 | #sidebar ul.topics > li.parent, #sidebar ul.topics > li.active { 82 | background: var(--MENU-SECTIONS-ACTIVE-BG-color); 83 | } 84 | #sidebar .searchbox * { 85 | color: var(--MENU-SEARCH-BOX-ICONS-color); 86 | } 87 | 88 | #sidebar a { 89 | color: var(--MENU-SECTIONS-LINK-color); 90 | } 91 | 92 | #sidebar a:hover { 93 | color: var(--MENU-SECTIONS-LINK-HOVER-color); 94 | } 95 | 96 | #sidebar ul li.active > a { 97 | background: var(--MENU-SECTION-ACTIVE-CATEGORY-BG-color); 98 | color: var(--MENU-SECTION-ACTIVE-CATEGORY-color) !important; 99 | } 100 | 101 | #sidebar hr { 102 | border-color: var(--MENU-SECTION-HR-color); 103 | } 104 | 105 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Inconsolata.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Inconsolata.eot -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Inconsolata.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Inconsolata.ttf -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Inconsolata.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Inconsolata.woff -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.eot -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.ttf -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.woff -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-Normal-webfont.woff2 -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.eot -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.ttf -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.woff -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Novecentosanswide-UltraLight-webfont.woff2 -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_200.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_200.eot -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_200.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_200.ttf -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_200.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_200.woff -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_200.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_200.woff2 -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_300.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_300.eot -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_300.ttf -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_300.woff -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_300.woff2 -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_500.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_500.eot -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_500.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_500.ttf -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_500.woff -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/Work_Sans_500.woff2 -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/images/clippy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/images/favicon.png -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/images/gopher-404.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TIBCOSoftware/apiscout/461ba154867880c4bcdc25119f870409f610f1e0/webapp/themes/hugo-theme-learn/static/images/gopher-404.jpg -------------------------------------------------------------------------------- /webapp/themes/hugo-theme-learn/static/js/featherlight.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Featherlight - ultra slim jQuery lightbox 3 | * Version 1.2.3 - http://noelboss.github.io/featherlight/ 4 | * 5 | * Copyright 2015, Noël Raoul Bossart (http://www.noelboss.com) 6 | * MIT Licensed. 7 | **/ 8 | !function(a){"use strict";function b(a,c){if(!(this instanceof b)){var d=new b(a,c);return d.open(),d}this.id=b.id++,this.setup(a,c),this.chainCallbacks(b._callbackChain)}if("undefined"==typeof a)return void("console"in window&&window.console.info("Too much lightness, Featherlight needs jQuery."));var c=[],d=function(b){return c=a.grep(c,function(a){return a!==b&&a.$instance.closest("body").length>0})},e=function(a,b){var c={},d=new RegExp("^"+b+"([A-Z])(.*)");for(var e in a){var f=e.match(d);if(f){var g=(f[1]+f[2].replace(/([A-Z])/g,"-$1")).toLowerCase();c[g]=a[e]}}return c},f={keyup:"onKeyUp",resize:"onResize"},g=function(c){a.each(b.opened().reverse(),function(){return c.isDefaultPrevented()||!1!==this[f[c.type]](c)?void 0:(c.preventDefault(),c.stopPropagation(),!1)})},h=function(c){if(c!==b._globalHandlerInstalled){b._globalHandlerInstalled=c;var d=a.map(f,function(a,c){return c+"."+b.prototype.namespace}).join(" ");a(window)[c?"on":"off"](d,g)}};b.prototype={constructor:b,namespace:"featherlight",targetAttr:"data-featherlight",variant:null,resetCss:!1,background:null,openTrigger:"click",closeTrigger:"click",filter:null,root:"body",openSpeed:250,closeSpeed:250,closeOnClick:"background",closeOnEsc:!0,closeIcon:"✕",loading:"",otherClose:null,beforeOpen:a.noop,beforeContent:a.noop,beforeClose:a.noop,afterOpen:a.noop,afterContent:a.noop,afterClose:a.noop,onKeyUp:a.noop,onResize:a.noop,type:null,contentFilters:["jquery","image","html","ajax","iframe","text"],setup:function(b,c){"object"!=typeof b||b instanceof a!=!1||c||(c=b,b=void 0);var d=a.extend(this,c,{target:b}),e=d.resetCss?d.namespace+"-reset":d.namespace,f=a(d.background||['
    ','
    ','',d.closeIcon,"",'
    '+d.loading+"
    ","
    ","
    "].join("")),g="."+d.namespace+"-close"+(d.otherClose?","+d.otherClose:"");return d.$instance=f.clone().addClass(d.variant),d.$instance.on(d.closeTrigger+"."+d.namespace,function(b){var c=a(b.target);("background"===d.closeOnClick&&c.is("."+d.namespace)||"anywhere"===d.closeOnClick||c.closest(g).length)&&(b.preventDefault(),d.close())}),this},getContent:function(){var b=this,c=this.constructor.contentFilters,d=function(a){return b.$currentTarget&&b.$currentTarget.attr(a)},e=d(b.targetAttr),f=b.target||e||"",g=c[b.type];if(!g&&f in c&&(g=c[f],f=b.target&&e),f=f||d("href")||"",!g)for(var h in c)b[h]&&(g=c[h],f=b[h]);if(!g){var i=f;if(f=null,a.each(b.contentFilters,function(){return g=c[this],g.test&&(f=g.test(i)),!f&&g.regex&&i.match&&i.match(g.regex)&&(f=i),!f}),!f)return"console"in window&&window.console.error("Featherlight: no content filter found "+(i?' for "'+i+'"':" (no target specified)")),!1}return g.process.call(b,f)},setContent:function(b){var c=this;return(b.is("iframe")||a("iframe",b).length>0)&&c.$instance.addClass(c.namespace+"-iframe"),c.$instance.removeClass(c.namespace+"-loading"),c.$instance.find("."+c.namespace+"-inner").slice(1).remove().end().replaceWith(a.contains(c.$instance[0],b[0])?"":b),c.$content=b.addClass(c.namespace+"-inner"),c},open:function(b){var d=this;if(d.$instance.hide().appendTo(d.root),!(b&&b.isDefaultPrevented()||d.beforeOpen(b)===!1)){b&&b.preventDefault();var e=d.getContent();if(e)return c.push(d),h(!0),d.$instance.fadeIn(d.openSpeed),d.beforeContent(b),a.when(e).always(function(a){d.setContent(a),d.afterContent(b)}).then(d.$instance.promise()).done(function(){d.afterOpen(b)})}return d.$instance.detach(),a.Deferred().reject().promise()},close:function(b){var c=this,e=a.Deferred();return c.beforeClose(b)===!1?e.reject():(0===d(c).length&&h(!1),c.$instance.fadeOut(c.closeSpeed,function(){c.$instance.detach(),c.afterClose(b),e.resolve()})),e.promise()},chainCallbacks:function(b){for(var c in b)this[c]=a.proxy(b[c],this,a.proxy(this[c],this))}},a.extend(b,{id:0,autoBind:"[data-featherlight]",defaults:b.prototype,contentFilters:{jquery:{regex:/^[#.]\w/,test:function(b){return b instanceof a&&b},process:function(b){return a(b).clone(!0)}},image:{regex:/\.(png|jpg|jpeg|gif|tiff|bmp)(\?\S*)?$/i,process:function(b){var c=this,d=a.Deferred(),e=new Image,f=a('');return e.onload=function(){f.naturalWidth=e.width,f.naturalHeight=e.height,d.resolve(f)},e.onerror=function(){d.reject(f)},e.src=b,d.promise()}},html:{regex:/^\s*<[\w!][^<]*>/,process:function(b){return a(b)}},ajax:{regex:/./,process:function(b){var c=a.Deferred(),d=a("
    ").load(b,function(a,b){"error"!==b&&c.resolve(d.contents()),c.fail()});return c.promise()}},iframe:{process:function(b){var c=new a.Deferred,d=a("