├── .dockerignore
├── .editorconfig
├── .github
├── FUNDING.yml
└── workflows
│ └── docker-builds.yml
├── .gitignore
├── .gitlab-ci.yml
├── .markdownlint.json
├── CONFIGURATION.md
├── Dockerfile
├── Dockerfile.unprivileged
├── LICENSE
├── README.md
├── THEMING.md
├── babel.config.js
├── charts
└── gitlab-monitor
│ ├── .helmignore
│ ├── Chart.yaml
│ ├── README.md
│ ├── templates
│ ├── NOTES.txt
│ ├── _helpers.tpl
│ ├── deployment.yaml
│ ├── ingress.yaml
│ └── service.yaml
│ └── values.yaml
├── docker
└── docker-compose.yml
├── index.html
├── index.template.html
├── logo.svg
├── package.json
├── public
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── browserconfig.xml
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── index.html
├── manifest.json
├── mstile-144x144.png
├── mstile-150x150.png
├── mstile-310x150.png
├── mstile-310x310.png
└── mstile-70x70.png
├── scripts
└── wrapper.sh
├── src
├── Config.js
├── GitLabApi.js
├── assets
│ ├── backdrop.svg
│ └── icons.svg
├── components
│ ├── app.vue
│ ├── gitlab-icon.vue
│ ├── job-view.vue
│ ├── pipeline-view.vue
│ ├── project-card.vue
│ ├── runner-status.vue
│ ├── stage-view.vue
│ └── test-report.vue
├── config.default.json
├── config.template.json
├── main.js
├── themes
│ ├── nord-dark.theme.scss
│ └── nord-light.theme.scss
└── util.js
├── vue.config.js
└── yarn.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
2 | .git
3 | .gitignore
4 | .gitlab-ci.yml
5 | .editorconfig
6 | .markdownlint.json
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: timoschwarzer
2 |
--------------------------------------------------------------------------------
/.github/workflows/docker-builds.yml:
--------------------------------------------------------------------------------
1 | name: docker-build-push
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'main'
7 | tags:
8 | - '*'
9 |
10 | pull_request:
11 | branches:
12 | - 'main'
13 |
14 | workflow_dispatch:
15 |
16 | env:
17 | IMAGE_NAME: "gitlab-monitor"
18 |
19 | jobs:
20 | docker:
21 | runs-on: ubuntu-latest
22 | steps:
23 |
24 | - name: Checkout
25 | uses: actions/checkout@v2
26 |
27 | - name: Prepare
28 | id: prep
29 | run: |
30 | BASE_DIR=.
31 | IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
32 |
33 | # Change all uppercase to lowercase
34 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
35 |
36 | # Strip git ref prefix from version
37 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,')
38 |
39 | # Strip "v" prefix from tag name
40 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')
41 |
42 | # Use Docker `latest` tag convention
43 | [ "$VERSION" == "main" ] && VERSION=latest
44 |
45 | VERSION=$VERSION
46 | IMAGE_ID=$IMAGE_ID
47 | echo ::set-output name=version::${VERSION}
48 | echo ::set-output name=image_id::${IMAGE_ID}
49 | echo ::set-output name=base_dir::${BASE_DIR}
50 |
51 | - name: Set up QEMU
52 | uses: docker/setup-qemu-action@v1
53 | with:
54 | platforms: all
55 |
56 | - name: Set up Docker Buildx
57 | id: buildx
58 | uses: docker/setup-buildx-action@v1
59 | with:
60 | install: true
61 | version: latest
62 |
63 | - name: Log into registry
64 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
65 |
66 | - name: Build and Push
67 | uses: docker/build-push-action@v2
68 | with:
69 | build-args: VERSION=${{ steps.prep.outputs.version }}
70 | context: ${{ steps.prep.outputs.base_dir }}/
71 | file: ${{ steps.prep.outputs.base_dir }}/Dockerfile
72 | platforms: linux/amd64,linux/arm64
73 | push: true
74 | tags: |
75 | ${{ steps.prep.outputs.image_id }}:${{ steps.prep.outputs.version }}
76 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | public/config.json
5 | npm-debug.log
6 | yarn-error.log
7 |
8 | # Editor directories and files
9 | *.todo
10 | .idea
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 | *.sublime-*
16 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: node:12-alpine
2 |
3 | cache:
4 | paths:
5 | - node_modules/
6 |
7 | stages:
8 | - build
9 |
10 | build:
11 | stage: build
12 | only:
13 | - master
14 | artifacts:
15 | paths:
16 | - dist
17 | script:
18 | - yarn install --check-files --non-interactive
19 | - yarn run build
20 |
--------------------------------------------------------------------------------
/.markdownlint.json:
--------------------------------------------------------------------------------
1 | {
2 | "default": true,
3 | "MD013": false,
4 | "MD026": { "punctuation": ".,;:?" }
5 | }
--------------------------------------------------------------------------------
/CONFIGURATION.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
3 | GitLab Monitor is configured with a YAML-encoded configuration file.
4 |
5 | Your configuration overrides the default configuration, which can be here: [/src/config.default.json](./src/config.default.json)
6 |
7 | The configuration can be loaded statically or dynamically.
8 |
9 |
10 | ## Dynamic configuration
11 |
12 | - Start the server in dev or production mode (see [README.md](./README.md)).
13 | - Open the application URL
14 | - On the home page, you'll be asked to enter your configuration (basically you gitlab API endpoint and your private token)
15 | - Once configured, to change the existing configuration, hover the bottom left corner of the viewport!
16 |
17 |
18 | ## Static configuration
19 |
20 | To add your configuration, copy [/src/config.template.json](./src/config.template.json) file into `/public/config.json` and update value.
21 |
22 | ```json
23 | {
24 | "gitlabApi": "https://gitlab.example.com/api/v4",
25 | "privateToken": "ABCDEF1234"
26 | }
27 | ```
28 |
29 | Your can also use "Headless configuration" or "Environment variable configuration" ask detailed below:
30 |
31 | ### Headless configuration
32 | If you can't access the browser directly for some reason, you can pass
33 | your configuration YAML encoded with base64url as `rawConfig` query parameter.
34 |
35 | ### Injecting configuration when starting the container
36 | You can also inject your configuration at runtime by setting an environment variable, `GITLAB_MONITOR_CONFIG`.
37 |
38 | ## Configuration options
39 |
40 | Here you'll find documentation about each configration option:
41 |
42 | ```yaml
43 | # Required
44 | gitlabApi: https://gitlab.example.com/api/v4
45 |
46 | # Required
47 | # Can be generated here: https://gitlab.example.com/profile/personal_access_tokens
48 | # At least api_read scope is needed
49 | privateToken: ABCDEF123456
50 |
51 | # In hours. Projects with last activity older than this age won't be displayed.
52 | # If set to 0, no filter will be applied
53 | maxAge: 168
54 |
55 | # How many projects will be fetched from GitLab.
56 | # If set to greater than 100, then all available projects will be retrieved (in batches of 100)
57 | fetchCount: 20
58 |
59 | # Show only projects with recent pipelines
60 | pipelinesOnly: false
61 |
62 | # Whether to include archived projects
63 | includeArchived: false
64 |
65 | # Zooms the dashboard to fill the screen with all displayed projects.
66 | # Not compatible with all browsers! See https://caniuse.com/#feat=css-zoom
67 | autoZoom: false
68 |
69 | # Whether to show pipeline IDs or not
70 | showPipelineIds: true
71 |
72 | # Only show project for specific job statuses.
73 | # Valid values are: 'created', 'pending', 'running', 'failed', 'success', 'canceled', 'skipped' or 'manual'.
74 | showProjectOnlyOn:
75 | - canceled
76 | - failed
77 | - pending
78 | - running
79 | - skipped
80 | - manual
81 |
82 | # Control how to show job names and icons
83 | # Can be: 'icon', 'name', 'iconAndName'
84 | showJobs: icon
85 |
86 | # Only show job names for specific job statuses.
87 | # Valid values are: 'created', 'pending', 'running', 'failed', 'success', 'canceled', 'skipped' or 'manual'.
88 | # This is an example that shows job names only for jobs with a non-success status:
89 | showJobsNameOnlyOn:
90 | - canceled
91 | - failed
92 | - pending
93 | - running
94 | - skipped
95 | - manual
96 |
97 | # Whether to show jobs that was restarted
98 | showRestartedJobs: true
99 |
100 | # Whether to show stages names
101 | showStagesNames: false
102 |
103 | # Whether to show pipeline durations or not
104 | showDurations: true
105 |
106 | # Whether to show the pipelines test coverage if available
107 | showCoverage: false
108 |
109 | # Whether to show the pipelines test report if available
110 | showTestReport: true
111 |
112 | # Whether to show the user that invoked the pipeline or not
113 | showUsers: false
114 |
115 | # Whether to show a button to rerun pipelines
116 | showRerunButton: false
117 |
118 | # The page title, or null to hide
119 | title: null
120 |
121 | # Project display order. Possible values: lastActivity, created, name, nameWithNamespace
122 | # Note that this only changes the displayed order and not the order in which they will be fetched.
123 | orderBy: lastActivity
124 |
125 | # Display projects in descending order
126 | orderByDesc: false
127 |
128 | # Multiply all polling intervals by this amount
129 | # (e.g. 0.5 will make gitlab-monitor poll twice as often)
130 | pollingIntervalMultiplier: 1.0
131 |
132 | # Disable to prevent refreshes while the browser tab is inactive.
133 | backgroundRefresh: true
134 |
135 | # Limit projects by visibility
136 | # Can be: 'any', 'public', 'internal' or 'private'
137 | projectVisibility: any
138 |
139 | # Limit by projects that the current user is a member of
140 | membership: false
141 |
142 | # Whether to show project badges or not
143 | badges: true
144 |
145 | # Whether to show the amount and availability of runners
146 | showRunnerStatus: true
147 |
148 | # Filter projects
149 | # The filter is applied to the path with namespace
150 | # (e.g. 'my-group/my-project'
151 | filter:
152 |
153 | # Include projects that match this RegExp
154 | include: .*
155 |
156 | # Include projects that have tags matching this RegExp
157 | includeTags: .*
158 |
159 | # Exclude projects of included projects that match this RegExp
160 | exclude: null
161 |
162 | # Exclude projects of included projects that have tags matching this RegExp
163 | excludeTags: null
164 |
165 | # Exclude projects of included projects that don't have any tags
166 | excludeUntagged: false
167 |
168 | # Configure projects
169 | projectConfig:
170 |
171 | # The asterisk selects all projects that
172 | # don't have their own configuration
173 | '*':
174 |
175 | # Include branches that match this RegExp
176 | include: .*
177 |
178 | # Exclude branches of included branches that match this RegExp
179 | exclude: null
180 |
181 | # Override default branch (used for the card background color)
182 | # If null, it uses the default branch of the project
183 | default: null
184 |
185 | # Whether to show pipelines of merged branches
186 | showMerged: true
187 |
188 | # Whether to show pipelines of tags
189 | showTags: true
190 |
191 | # Whether to show only the latest tag
192 | showLatestTagOnly: false
193 |
194 | # Whether to show detached pipelines of merge requests
195 | showDetached: false
196 |
197 | # Whether to show the labels of the merge request for detached pipelines
198 | showLabels: true
199 |
200 | # Minimum number of pipelines to display for this filter
201 | historyCount: 1
202 |
203 | # Hide skipped pipelines
204 | hideSkippedPipelines: false
205 |
206 | # Hide successful pipelines
207 | hideSuccessfulPipelines: false
208 |
209 | soundAlerts:
210 | # If set to a non-null value, sound alerts will be enabled.
211 | # Replace null with URL to sound file.
212 | # IMPORTANT: Due to some browsers blocking autoplaying audio
213 | # you may have to enable audio autoplay first!
214 | # Firefox: https://support.mozilla.org/en-US/kb/block-autoplay
215 | soundUrl: null
216 |
217 | # Play alert sounds for all branches matching this regex
218 | include: .*
219 |
220 | # Play alert sounds for all included branches except for branches
221 | # matching this regex. Set to null to exclude none.
222 | exclude: null
223 |
224 | # Specific per-project filters
225 | my-project/my-group":
226 | # see above...
227 |
228 | # Search projects from given API route.
229 | # Allowed values "groups" and "users"
230 | projectScope: null
231 |
232 | # ID or IDs to use in query with "projectScope" parameter
233 | # For example 123 with "projectScope": "groups" would query /groups/123/projects
234 | # Specify an array if you with to include multiple scope IDs.
235 | projectScopeId: null
236 |
237 | # If projectScope is "groups" you might be interested in using includeSubgroups to include
238 | # projects from all subgroups within the group specified in projectScopeId.
239 | includeSubgroups: false
240 |
241 | # Predefined theme to use. If null, the default theme will be used.
242 | # Available themes: nord-dark, nord-light
243 | # You can override styles in the settings or submit your own theme here: https://git.io/JUOFb
244 | theme: null
245 | ```
246 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Stage 1
2 | FROM node:12-alpine as build
3 |
4 | WORKDIR /usr/src/app
5 | COPY . ./
6 | RUN yarn install \
7 | && yarn build
8 |
9 | # Stage 2
10 | FROM nginx:1.17-alpine
11 | COPY --from=build /usr/src/app/dist /usr/share/nginx/html
12 | COPY scripts/wrapper.sh /wrapper.sh
13 | CMD ["/wrapper.sh"]
14 | EXPOSE 80
15 |
--------------------------------------------------------------------------------
/Dockerfile.unprivileged:
--------------------------------------------------------------------------------
1 | # Stage 1
2 | FROM node:11-alpine as build
3 |
4 | WORKDIR /usr/src/app
5 | COPY . ./
6 | RUN yarn install \
7 | && yarn build
8 |
9 | # Stage 2
10 | FROM nginxinc/nginx-unprivileged:1.15-alpine
11 | COPY --from=build /usr/src/app/dist /usr/share/nginx/html
12 | COPY scripts/wrapper.sh /wrapper.sh
13 | CMD ["/wrapper.sh"]
14 | EXPOSE 8080
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Timo Schwarzer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
GitLab Monitor
2 |
3 | > A browser-based monitor dashboard for GitLab CI
4 |
5 | ## Use & Download
6 |
7 | ### Hosted version
8 |
9 | If you don't want to setup your own server, you can always
10 | use the latest version of GitLab Monitor I upload here:
11 |
12 |
13 |
14 | Don't worry, I don't save access tokens or anything else.
15 | Additionally, this version has a manifest.json attached which
16 | makes it easy to pin it to your Android home screen and open
17 | it as a full screen app.
18 |
19 | [**Support me on Patreon**](https://www.patreon.com/timoschwarzer)
20 |
21 | ### Docker
22 |
23 | [](https://hub.docker.com/r/timoschwarzer/gitlab-monitor)
24 |
25 | There's an official docker image available on [Dockerhub](https://hub.docker.com/r/timoschwarzer/gitlab-monitor/):
26 | ```
27 | docker pull timoschwarzer/gitlab-monitor
28 | ```
29 |
30 | ### Host it yourself
31 |
32 | [Go to releases](https://github.com/timoschwarzer/gitlab-monitor/releases)
33 |
34 | ## Screenshots
35 |
36 | 
37 | 
38 |
39 | ## Build Setup
40 |
41 | ``` bash
42 | # install dependencies
43 | yarn install
44 |
45 | # serve with hot reload at localhost:8080
46 | yarn run dev
47 |
48 | # build for production with minification
49 | yarn run build
50 | ```
51 |
52 | ## Configuration
53 | See [configuration](./CONFIGURATION.md).
54 |
55 | ## Used libraries
56 |
57 | - [Vue](https://vuejs.org)
58 | - [vue-timeago](https://github.com/egoist/vue-timeago)
59 | - [vue-octicon](https://github.com/Justineo/vue-octicon)
60 | - [GitLab SVG icons](https://gitlab.com/gitlab-org/gitlab-svgs)
61 |
62 | ### Visit my [website](https://timoschwarzer.com)!
63 |
--------------------------------------------------------------------------------
/THEMING.md:
--------------------------------------------------------------------------------
1 | # Theming
2 |
3 | You can submit your own themes as a pull request to make them available to all users.
4 |
5 | ## Creating a Theme
6 |
7 | To create a theme simply create a `my-theme.theme.scss` file under `src/themes/`
8 | and add a global class selector named after your theme.
9 |
10 | ```scss
11 | /* src/themes/my-theme.theme.scss */
12 |
13 | .my-theme {
14 | // Your styles here
15 | }
16 | ```
17 |
18 | Enable your theme in your configuration to see your changes:
19 |
20 | ```yaml
21 | theme: my-theme
22 | ```
23 |
24 | ## Variables
25 |
26 | Themes use [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) for variables.
27 |
28 | There are quite a few variables out there to make theme development easier.
29 | Take a look at existing themes for a list of variables or check out
30 | your browser's dev tools.
31 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/charts/gitlab-monitor/.helmignore:
--------------------------------------------------------------------------------
1 | # Patterns to ignore when building packages.
2 | # This supports shell glob matching, relative path matching, and
3 | # negation (prefixed with !). Only one pattern per line.
4 | .DS_Store
5 | # Common VCS dirs
6 | .git/
7 | .gitignore
8 | .bzr/
9 | .bzrignore
10 | .hg/
11 | .hgignore
12 | .svn/
13 | # Common backup files
14 | *.swp
15 | *.bak
16 | *.tmp
17 | *~
18 | # Various IDEs
19 | .project
20 | .idea/
21 | *.tmproj
22 |
--------------------------------------------------------------------------------
/charts/gitlab-monitor/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | description: A Helm chart for Kubernetes
3 | name: gitlab-monitor
4 | version: v0.2.0
5 |
--------------------------------------------------------------------------------
/charts/gitlab-monitor/README.md:
--------------------------------------------------------------------------------
1 | # Gitlab Monitor Helm Chart
2 |
3 | ## Prerequisites
4 |
5 | 1. Have a running [Kubernetes](https://kubernetes.io/docs/setup/) environment
6 | 2. Setup [Helm](https://github.com/kubernetes/helm)
7 |
8 | ## Install
9 |
10 | ### From File
11 | Clone gitlab-monitor and change directory
12 | ```bash
13 | $ git clone https://github.com/timoschwarzer/gitlab-monitor
14 | $ cd gitlab-monitor/charts/gitlab-monitor/
15 | ```
16 | Helm install monitor
17 | > note name and namespace can be customized.
18 | ```bash
19 | $ helm install . -n gitlab-monitor --namespace default
20 | ```
21 |
22 | ## Configure
23 |
24 | Edit the `values.yaml` and set the config yaml string accordingly.
25 |
26 | ```yaml
27 | config: |
28 | {
29 | "gitlabApi": "https://gitlab.example.com/api/v4",
30 | "privateToken": "ABCDEF123456",
31 | "maxAge": 168,
32 | "fetchCount": 20,
33 | "pipelinesOnly": false,
34 | "includeArchived": false,
35 | "autoZoom": false,
36 | "showPipelineIds": true,
37 | "showJobs": "iconAndName",
38 | "showDurations": true,
39 | "showUsers": false,
40 | "projectVisibility": "any",
41 | "linkToFailureSound": null ,
42 | "title": null,
43 | "pollingIntervalMultiplier": 10,
44 | "filter": {
45 | "include": ".*",
46 | "includeTags": ".*",
47 | "exclude": null,
48 | "excludeTags": null
49 | },
50 | "projectFilter": {
51 | "*": {
52 | "include": ".*",
53 | "exclude": null,
54 | "default": null,
55 | "showMerged": false,
56 | "showTags": true,
57 | "showLatestTagOnly": false,
58 | "showDetached": false,
59 | "showLabels": true,
60 | "maxPipelines": 10,
61 | "notifyFailureOn": null
62 | }
63 | }
64 | }
65 | ```
66 |
--------------------------------------------------------------------------------
/charts/gitlab-monitor/templates/NOTES.txt:
--------------------------------------------------------------------------------
1 |
2 | {{- if contains "NodePort" .Values.service.type }}
3 | Get the application URL by running these commands:
4 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "fullname" . }})
5 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
6 | echo http://$NODE_IP:$NODE_PORT/login
7 | {{- else if contains "LoadBalancer" .Values.service.type }}
8 | Get the application URL by running these commands:
9 | NOTE: It may take a few minutes for the LoadBalancer IP to be available.
10 | You can watch the status of by running 'kubectl get svc -w {{ template "fullname" . }}'
11 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template "fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
12 | echo http://$SERVICE_IP:{{ .Values.service.externalPort }}
13 | {{- else }}
14 | http://{{ .Values.ingress.domain }} to access your application
15 | {{- end }}
16 |
--------------------------------------------------------------------------------
/charts/gitlab-monitor/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/* vim: set filetype=mustache: */}}
2 | {{/*
3 | Expand the name of the chart.
4 | */}}
5 | {{- define "name" -}}
6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
7 | {{- end -}}
8 |
9 | {{/*
10 | Create a default fully qualified app name.
11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
12 | */}}
13 | {{- define "fullname" -}}
14 | {{- $name := default .Chart.Name .Values.nameOverride -}}
15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
16 | {{- end -}}
17 |
--------------------------------------------------------------------------------
/charts/gitlab-monitor/templates/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | name: {{ template "fullname" . }}
5 | labels:
6 | draft: {{ default "draft-app" .Values.draft }}
7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
8 | spec:
9 | replicas: {{ .Values.replicaCount }}
10 | template:
11 | metadata:
12 | annotations:
13 | buildID: {{ .Values.buildID }}
14 | labels:
15 | draft: {{ default "draft-app" .Values.draft }}
16 | app: {{ template "fullname" . }}
17 | spec:
18 | containers:
19 | - name: {{ .Chart.Name }}
20 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
21 | imagePullPolicy: {{ .Values.image.pullPolicy }}
22 | env:
23 | - name: GITLAB_MONITOR_CONFIG
24 | value: | {{ .Values.config | trim | nindent 12 }}
25 | ports:
26 | - containerPort: {{ .Values.service.internalPort }}
27 | resources:
28 | {{ toYaml .Values.resources | indent 12 }}
29 |
--------------------------------------------------------------------------------
/charts/gitlab-monitor/templates/ingress.yaml:
--------------------------------------------------------------------------------
1 | {{- if .Values.ingress.enabled -}}
2 | apiVersion: extensions/v1beta1
3 | kind: Ingress
4 | metadata:
5 | name: {{ template "fullname" . }}
6 | labels:
7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
8 | spec:
9 | rules:
10 | - host: {{ .Values.ingress.domain }}
11 | http:
12 | paths:
13 | - path: /
14 | backend:
15 | serviceName: {{ template "fullname" . }}
16 | servicePort: {{ .Values.service.externalPort }}
17 | {{- if .Values.ingress.tls }}
18 | tls:
19 | {{ toYaml .Values.ingress.tls | indent 4 }}
20 | {{- end }}
21 | {{- end -}}
22 |
--------------------------------------------------------------------------------
/charts/gitlab-monitor/templates/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ template "fullname" . }}
5 | labels:
6 | chart: "{{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}"
7 | spec:
8 | type: {{ .Values.service.type }}
9 | ports:
10 | - port: {{ .Values.service.externalPort }}
11 | targetPort: {{ .Values.service.internalPort }}
12 | {{- if eq .Values.service.type "LoadBalancer"}}
13 | nodePort: {{ .Values.service.nodePort }}
14 | {{- end}}
15 | protocol: TCP
16 | name: {{ .Values.service.name }}
17 | selector:
18 | app: {{ template "fullname" . }}
19 |
--------------------------------------------------------------------------------
/charts/gitlab-monitor/values.yaml:
--------------------------------------------------------------------------------
1 | # Default values for node.
2 | # This is a YAML-formatted file.
3 | # Declare variables to be passed into your templates.
4 | replicaCount: 1
5 | image:
6 | pullPolicy: Always
7 | repository: timoschwarzer/gitlab-monitor
8 | tag: latest
9 | service:
10 | name: node
11 | type: LoadBalancer
12 | externalPort: 80
13 | internalPort: 80
14 | nodePort: 31148
15 | resources:
16 | limits:
17 | cpu: 100m
18 | memory: 128Mi
19 | requests:
20 | cpu: 100m
21 | memory: 128Mi
22 | ingress:
23 | enabled: false
24 | domain: gitlab-monitor.example.com
25 | tls: {}
26 | config: |
27 | {
28 | "gitlabApi": "https://gitlab.example.com/api/v4",
29 | "privateToken": "ABCDEF123456",
30 | "maxAge": 168,
31 | "fetchCount": 20,
32 | "pipelinesOnly": false,
33 | "includeArchived": false,
34 | "autoZoom": false,
35 | "showPipelineIds": true,
36 | "showJobs": "iconAndName",
37 | "showDurations": true,
38 | "showUsers": false,
39 | "projectVisibility": "any",
40 | "linkToFailureSound": null ,
41 | "title": null,
42 | "pollingIntervalMultiplier": 10,
43 | "filter": {
44 | "include": ".*",
45 | "includeTags": ".*",
46 | "exclude": null,
47 | "excludeTags": null
48 | },
49 | "projectFilter": {
50 | "*": {
51 | "include": ".*",
52 | "exclude": null,
53 | "default": null,
54 | "showMerged": false,
55 | "showTags": true,
56 | "showLatestTagOnly": false,
57 | "showDetached": false,
58 | "showLabels": true,
59 | "maxPipelines": 10,
60 | "notifyFailureOn": null
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/docker/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | networks:
4 | web:
5 | external: true
6 |
7 | services:
8 | ui:
9 | image: timoschwarzer/gitlab-monitor
10 | networks:
11 | - web
12 | expose:
13 | - 80
14 | restart: always
15 | labels:
16 | - "traefik.enable=true"
17 | - "traefik.frontend.rule=Host:gitlab-monitor.timoschwarzer.com"
18 | - "traefik.port=80"
19 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | GitLab Monitor
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/index.template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | GitLab Monitor
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gitlab-monitor",
3 | "description": "A browser-based monitor dashboard for GitLab CI",
4 | "version": "1.0.0",
5 | "author": "Timo Schwarzer ",
6 | "license": "MIT",
7 | "private": true,
8 | "scripts": {
9 | "dev": "vue-cli-service serve",
10 | "build": "vue-cli-service build",
11 | "serve": "http-server ./dist"
12 | },
13 | "dependencies": {
14 | "@openfonts/roboto-condensed_all": "^1.44.1",
15 | "@openfonts/roboto_all": "^1.44.1",
16 | "axios": "^0.21.1",
17 | "base64url": "^3.0.0",
18 | "core-js": "^3.3.2",
19 | "date-fns": "^1.30.1",
20 | "deepmerge": "^2.2.1",
21 | "http-server": "^0.11.1",
22 | "monaco-editor-webpack-plugin": "1.9.0",
23 | "nord": "^0.2.1",
24 | "parse-link-header": "^1.0.1",
25 | "query-string": "^6.2.0",
26 | "regenerator-runtime": "^0.13.3",
27 | "visibilityjs": "^2.0.2",
28 | "vue": "^2.6.10",
29 | "vue-monaco": "1.2.1",
30 | "vue-octicon": "^2.1.1",
31 | "vue-timeago": "^5.0.0",
32 | "yaml": "^1.6.0"
33 | },
34 | "browserslist": [
35 | "> 1%",
36 | "last 2 versions",
37 | "not ie <= 8"
38 | ],
39 | "devDependencies": {
40 | "@vue/cli-plugin-babel": "^4.0.4",
41 | "@vue/cli-service": "^4.0.4",
42 | "file-loader": "^3.0.1",
43 | "sass": "^1.23.0",
44 | "sass-loader": "^8.0.0",
45 | "style-loader": "^1.2.1",
46 | "vue-template-compiler": "^2.6.10"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #603cba
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | GitLab Monitor
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | We're sorry but gitlab-monitor doesn't work properly without JavaScript enabled. Please enable it to continue.
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "GitLab Monitor",
3 | "icons": [
4 | {
5 | "src": "/android-chrome-192x192.png",
6 | "sizes": "192x192",
7 | "type": "image/png"
8 | },
9 | {
10 | "src": "/android-chrome-512x512.png",
11 | "sizes": "512x512",
12 | "type": "image/png"
13 | }
14 | ],
15 | "theme_color": "#571d54",
16 | "background_color": "#571d54",
17 | "display": "standalone"
18 | }
19 |
--------------------------------------------------------------------------------
/public/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/mstile-144x144.png
--------------------------------------------------------------------------------
/public/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/mstile-150x150.png
--------------------------------------------------------------------------------
/public/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/mstile-310x150.png
--------------------------------------------------------------------------------
/public/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/mstile-310x310.png
--------------------------------------------------------------------------------
/public/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/timoschwarzer/gitlab-monitor/867f71cb88b5d63344c7c53c357e3a934f39e290/public/mstile-70x70.png
--------------------------------------------------------------------------------
/scripts/wrapper.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | if [ ! -z "${GITLAB_MONITOR_CONFIG}" ]; then
4 | echo "${GITLAB_MONITOR_CONFIG}" > /usr/share/nginx/html/config.json
5 | fi
6 | exec nginx -g "daemon off;"
7 |
--------------------------------------------------------------------------------
/src/Config.js:
--------------------------------------------------------------------------------
1 | import { getQueryParameter } from './util'
2 | import merge from 'deepmerge'
3 | import defaultConfig from './config.default'
4 | import base64Url from 'base64url'
5 | import YAML from 'yaml'
6 |
7 | export default new class Config {
8 | constructor() {
9 | this.config = null
10 | this.localConfig = null
11 | this.styleOverride = ''
12 | }
13 |
14 | load(config = null, style = null) {
15 | const rawConfig = getQueryParameter('rawConfig')
16 |
17 | if (style !== null) {
18 | this.styleOverride = style
19 | }
20 |
21 | if (config !== null) {
22 | this.localConfig = config
23 | this.config = merge(defaultConfig, config)
24 | } else if (rawConfig !== null) {
25 | this.localConfig = YAML.parse(base64Url.decode(rawConfig))
26 | this.config = merge(defaultConfig, this.localConfig)
27 | } else {
28 | this.loadFromLocalStorage()
29 | }
30 |
31 | this.persist()
32 | }
33 |
34 | loadFromLocalStorage() {
35 | const config = window.localStorage.getItem('config')
36 | this.styleOverride = window.localStorage.getItem('styleOverride') || ''
37 |
38 | if (config !== null) {
39 | const localConfig = YAML.parse(config)
40 | this.config = merge(defaultConfig, localConfig)
41 | this.localConfig = localConfig
42 | }
43 | }
44 |
45 | persist() {
46 | if (!this.localConfig) {
47 | return
48 | }
49 |
50 | window.localStorage.setItem('config', YAML.stringify(this.localConfig, null, 2))
51 | window.localStorage.setItem('styleOverride', this.styleOverride)
52 | }
53 |
54 | get isConfigured() {
55 | return this.config !== null
56 | }
57 |
58 | get root() {
59 | return this.config
60 | }
61 |
62 | get local() {
63 | return this.localConfig
64 | }
65 |
66 | get style() {
67 | return this.styleOverride
68 | }
69 |
70 | getProjectProperty(property, pathWithNamespace = '*') {
71 | if (this.root.projectConfig.hasOwnProperty(pathWithNamespace)) {
72 | return this.root.projectConfig[pathWithNamespace][property]
73 | } else {
74 | return this.root.projectConfig['*'][property]
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/GitLabApi.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import parse from 'parse-link-header'
3 | import Vue from 'vue'
4 | import Config from './Config'
5 |
6 | const GitLabApi = {}
7 |
8 | GitLabApi.install = (Vue, options) => {
9 | Vue.prototype.$api = async (path, params = {}, behaviour = {}, method = 'GET') => {
10 | const response = await axios(path, {
11 | baseURL: Config.root.gitlabApi,
12 | method: method,
13 | params,
14 | headers: { 'Private-Token': Config.root.privateToken }
15 | })
16 | const result = response.data
17 | if (behaviour.follow_next_page_links) {
18 | // Find the "next" link header and follow it, until we get a result that has no next link
19 | let parsedLinks = parse(response.headers.link)
20 | while (parsedLinks && parsedLinks.next) {
21 | const nextResponse = await axios.get(parsedLinks.next.url, {
22 | headers: { 'Private-Token': Config.root.privateToken }
23 | })
24 | result.push(...nextResponse.data)
25 | parsedLinks = parse(nextResponse.headers.link)
26 | }
27 | }
28 | return result
29 | }
30 | }
31 |
32 | export default GitLabApi
33 |
34 | export function configureApi() {
35 | Vue.use(GitLabApi, {
36 | gitlab_api_url: Config.root.gitlabApi,
37 | private_token: Config.root.privateToken
38 | })
39 | }
40 |
--------------------------------------------------------------------------------
/src/assets/backdrop.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/components/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{ getTitle() }}
6 |
13 |
14 | Configure
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Configuration
23 |
24 | To use GitLab Monitor, it has to be configured.
25 | Configuration is done by supplying YAML formatted options,
26 | all configuration options are described here .
27 | Your configuration is being persisted in this browser.
28 |
29 |
30 |
31 | YAML is invalid!
32 |
33 |
34 |
35 | Custom styles
36 |
37 |
38 |
39 |
Save
40 |
Add custom styles
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
328 |
329 |
374 |
375 |
464 |
--------------------------------------------------------------------------------
/src/components/gitlab-icon.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
24 |
25 |
30 |
--------------------------------------------------------------------------------
/src/components/job-view.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
18 |
19 |
20 |
21 |
22 |
23 | {{ job.name }}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
78 |
79 |
170 |
--------------------------------------------------------------------------------
/src/components/pipeline-view.vue:
--------------------------------------------------------------------------------
1 |
2 |
47 |
48 |
49 |
214 |
215 |
342 |
--------------------------------------------------------------------------------
/src/components/project-card.vue:
--------------------------------------------------------------------------------
1 |
2 |
35 |
36 |
37 |
368 |
369 |
475 |
--------------------------------------------------------------------------------
/src/components/runner-status.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
Runner status:
8 |
9 |
10 | {{ runnersOnline.length }} online
11 |
12 |
13 |
14 | {{ runnersActive.length }} active
15 |
16 |
17 |
18 | {{ runnersPaused.length }} paused
19 |
20 |
21 |
22 | {{ runnersOffline.length }} offline
23 |
24 |
25 |
26 |
27 |
28 |
81 |
82 |
118 |
--------------------------------------------------------------------------------
/src/components/stage-view.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
{{ stage.name }}
11 |
12 |
13 |
14 |
31 |
32 |
92 |
--------------------------------------------------------------------------------
/src/components/test-report.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
25 |
36 |
37 |
--------------------------------------------------------------------------------
/src/config.default.json:
--------------------------------------------------------------------------------
1 | {
2 | "gitlabApi": "https://gitlab.example.com/api/v4",
3 | "privateToken": "ABCDEF123456",
4 | "maxAge": 168,
5 | "fetchCount": 20,
6 | "pipelinesOnly": false,
7 | "includeArchived": false,
8 | "autoZoom": false,
9 | "showPipelineIds": true,
10 | "showProjectOnlyOn": [],
11 | "showJobs": "icon",
12 | "showJobsNameOnlyOn": [],
13 | "showRestartedJobs": true,
14 | "showStagesNames": false,
15 | "showDurations": true,
16 | "showCoverage": false,
17 | "showTestReport": false,
18 | "showUsers": false,
19 | "showRerunButton": false,
20 | "title": null,
21 | "orderBy": "lastActivity",
22 | "orderByDesc": false,
23 | "pollingIntervalMultiplier": 1.0,
24 | "backgroundRefresh": true,
25 | "projectVisibility": "any",
26 | "membership": false,
27 | "badges": false,
28 | "showRunnerStatus": true,
29 | "filter": {
30 | "include": ".*",
31 | "includeTags": ".*",
32 | "exclude": null,
33 | "excludeTags": null,
34 | "excludeUntagged": false
35 | },
36 | "projectConfig": {
37 | "*": {
38 | "include": ".*",
39 | "exclude": null,
40 | "default": null,
41 | "showMerged": true,
42 | "showTags": true,
43 | "showLatestTagOnly": false,
44 | "showDetached": false,
45 | "showLabels": true,
46 | "historyCount": 1,
47 | "hideSkippedPipelines": false,
48 | "hideSuccessfulPipelines": false,
49 | "soundAlerts": {
50 | "soundUrl": null,
51 | "include": ".*",
52 | "exclude": null
53 | }
54 | }
55 | },
56 | "projectScope": null,
57 | "projectScopeId": null,
58 | "theme": null
59 | }
60 |
--------------------------------------------------------------------------------
/src/config.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "gitlabApi": "https://gitlab.example.com/api/v4",
3 | "privateToken": "ABCDEF1234"
4 | }
5 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import 'regenerator-runtime/runtime'
2 | import Vue from 'vue'
3 | import VueTimeago from 'vue-timeago'
4 | import App from './components/app.vue'
5 | import Config from './Config'
6 | import {configureApi} from './GitLabApi'
7 | import axios from 'axios'
8 |
9 | const finish = () => {
10 | if (Config.isConfigured) {
11 | configureApi()
12 | }
13 |
14 | Vue.use(VueTimeago, {
15 | name: 'timeago', // component name, `timeago` by default
16 | locale: 'en',
17 | locales: {
18 | 'en': require('date-fns/locale/en')
19 | }
20 | })
21 |
22 | new Vue({
23 | el: '#app',
24 | render: h => h(App)
25 | })
26 | }
27 |
28 | // Load bundled config, if present.
29 | ;(async () => {
30 | try {
31 | const {data} = await axios.get('./config.json')
32 | Config.load(data)
33 | } catch (e) {
34 | Config.load()
35 | } finally {
36 | finish()
37 | }
38 | })()
39 |
--------------------------------------------------------------------------------
/src/themes/nord-dark.theme.scss:
--------------------------------------------------------------------------------
1 | @import "~nord/src/sass/nord.scss";
2 |
3 | $night-darkest: $nord0;
4 | $night-dark: $nord1;
5 | $night-light: $nord2;
6 | $night-lightest: $nord3;
7 |
8 | $snow-dark: $nord4;
9 | $snow: $nord5;
10 | $snow-light: $nord6;
11 |
12 | $frost-green: $nord7;
13 | $frost-teal: $nord8;
14 | $frost-light-blue: $nord9;
15 | $frost-dark-blue: $nord10;
16 |
17 | $aurora-red: $nord11;
18 | $aurora-orange: $nord12;
19 | $aurora-yellow: $nord13;
20 | $aurora-green: $nord14;
21 | $aurora-purple: $nord15;
22 |
23 | @import '~@openfonts/roboto-condensed_all/index.css';
24 | @import '~@openfonts/roboto_all/index.css';
25 |
26 | .nord-dark {
27 | --background-color: #{$night-darkest};
28 | --color: #{$snow-light};
29 | --font-family: system-ui, sans-serif;
30 | --background: none;
31 |
32 | --project-default: #{$night-light};
33 | --project-success: #{$aurora-green};
34 | --project-running: #{$frost-dark-blue};
35 | --project-pending: #{$aurora-orange};
36 | --project-failed: #{$aurora-red};
37 | --project-canceled: #{$night-dark};
38 | --project-skipped: #{$night-lightest};
39 |
40 | --project-spinner-color: #{$snow-dark};
41 | --project-no-pipelines: #{$snow-dark};
42 | --project-info-color: #{$snow-dark};
43 |
44 | $pipeline-text-color: $snow-light;
45 | $pipeline-icons-color: $snow-dark;
46 |
47 | --pipeline-hashtag: #{$pipeline-text-color};
48 | --pipeline-id: #{$pipeline-text-color};
49 | --pipeline-branch: #{$pipeline-text-color};
50 | --pipeline-clock-icon: #{$pipeline-icons-color};
51 | --pipeline-duration: #{$pipeline-text-color};
52 | --pipeline-user-icon: #{$pipeline-icons-color};
53 | --pipeline-user: #{$pipeline-text-color};
54 |
55 | --job-border-color: #{$snow-dark};
56 | --job-connector-color: #{$snow-dark};
57 | --job-icon-fill-color: #{$snow-dark};
58 | --job-stage-names-color: #{$snow-dark};
59 | --jop-icon-size: 18px;
60 |
61 | --job-success: #{$aurora-green};
62 | --job-running: #{$frost-dark-blue};
63 | --job-pending: #{$aurora-orange};
64 | --job-warning: #{$aurora-yellow};
65 | --job-failed: #{$aurora-red};
66 | --job-canceled: #{$night-darkest};
67 | --job-skipped: #{$night-light};
68 | --job-manual: #{$night-light};
69 |
70 | body {
71 | background: none;
72 | }
73 |
74 | .pipeline-container {
75 | margin-bottom: 8px;
76 | display: flex;
77 | flex-direction: column;
78 |
79 | .spinner-icon {
80 | align-self: center;
81 | justify-self: center;
82 | margin-bottom: 8px;
83 | }
84 | }
85 |
86 | .pipeline-view {
87 | &:not(:last-child) {
88 | margin-bottom: 8px;
89 | }
90 |
91 | .pipeline {
92 | height: 22px;
93 | font-size: 12px;
94 |
95 | .duration {
96 | font-size: 12px;
97 | }
98 |
99 | .pipeline-icon {
100 | width: 10px !important;
101 | margin-right: 4px;
102 | }
103 | }
104 |
105 | .branch {
106 | font-size: 12px;
107 |
108 | svg {
109 | height: 12px !important;
110 | margin-right: 6px !important;
111 | }
112 | }
113 | }
114 |
115 | .job-view .job-circle {
116 | font-size: 10px;
117 |
118 | &.created {
119 | color: $night-lightest;
120 | }
121 | }
122 |
123 | .project-card {
124 | color: var(--pipeline-status-color) !important;
125 |
126 | //&.success {
127 | // color: $night-darkest;
128 | //
129 | // --pipeline-hashtag: #{$night-darkest};
130 | // --pipeline-id: #{$night-darkest};
131 | // --pipeline-branch: #{$night-darkest};
132 | // --pipeline-clock-icon: #{$night-dark};
133 | // --pipeline-duration: #{$night-darkest};
134 | // --pipeline-user-icon: #{$night-dark};
135 | // --pipeline-user: #{$night-darkest};
136 | //}
137 |
138 | .info {
139 | padding: 0.3rem;
140 | font-size: 0.5rem !important;
141 | }
142 |
143 | .content {
144 | padding-bottom: 0;
145 |
146 | .title {
147 | text-shadow: 0 0 0.5px rgba(0, 0, 0, 0.4);
148 |
149 | &:not(.small) {
150 | font-family: 'Roboto Condensed', sans-serif;
151 | }
152 |
153 | &.small {
154 | line-height: 1;
155 | font-weight: 400;
156 | font-size: 10px;
157 | }
158 | }
159 | }
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/themes/nord-light.theme.scss:
--------------------------------------------------------------------------------
1 | @import "~nord/src/sass/nord.scss";
2 |
3 | $night-darkest: $nord0;
4 | $night-dark: $nord1;
5 | $night-light: $nord2;
6 | $night-lightest: $nord3;
7 |
8 | $snow-dark: $nord4;
9 | $snow: $nord5;
10 | $snow-light: $nord6;
11 |
12 | $frost-green: $nord7;
13 | $frost-teal: $nord8;
14 | $frost-light-blue: $nord9;
15 | $frost-dark-blue: $nord10;
16 |
17 | $aurora-red: $nord11;
18 | $aurora-orange: $nord12;
19 | $aurora-yellow: $nord13;
20 | $aurora-green: $nord14;
21 | $aurora-purple: $nord15;
22 |
23 | @import '~@openfonts/roboto-condensed_all/index.css';
24 | @import '~@openfonts/roboto_all/index.css';
25 |
26 | .nord-light {
27 | --background-color: #{$snow-light};
28 | --color: #{$night-light};
29 | --font-family: 'Roboto', sans-serif;
30 | --background: none;
31 | --loading-overlay-color: transparent;
32 | --loading-indicator-color: #{$snow-dark};
33 |
34 | --project-default: #fff;
35 | --project-success: #{$aurora-green};
36 | --project-running: #{$frost-dark-blue};
37 | --project-pending: #{$aurora-orange};
38 | --project-failed: #{$aurora-red};
39 | --project-canceled: #{$night-dark};
40 | --project-skipped: #{$night-lightest};
41 |
42 | --project-spinner-color: #{$snow-dark};
43 | --project-no-pipelines: #{$snow-dark};
44 | --project-info-color: #{$snow-dark};
45 |
46 | $pipeline-text-color: $night-lightest;
47 | $pipeline-icons-color: $night-dark;
48 |
49 | --pipeline-hashtag: #{$pipeline-text-color};
50 | --pipeline-id: #{$pipeline-text-color};
51 | --pipeline-branch: #{$pipeline-icons-color};
52 | --pipeline-clock-icon: #{$pipeline-icons-color};
53 | --pipeline-duration: #{$pipeline-text-color};
54 | --pipeline-user-icon: #{$pipeline-icons-color};
55 | --pipeline-user: #{$pipeline-text-color};
56 |
57 | --job-border-color: #{$snow-dark};
58 | --job-connector-color: #{$snow-dark};
59 | --job-stage-names-color: #{$night-lightest};
60 | --jop-icon-size: 18px;
61 |
62 | --job-created: #{$snow-dark};
63 | --job-success: #{$aurora-green};
64 | --job-running: #{$frost-dark-blue};
65 | --job-pending: #{$aurora-orange};
66 | --job-warning: #{$aurora-yellow};
67 | --job-failed: #{$aurora-red};
68 | --job-canceled: #{$night-darkest};
69 | --job-skipped: #{$night-light};
70 | --job-manual: #{$night-light};
71 |
72 | body {
73 | background: none;
74 | }
75 |
76 | .job-view .job-circle {
77 | --job-icon-fill-color: var(--job-status-color);
78 |
79 | background-color: $snow-light;
80 | border-color: var(--job-status-color, $snow-dark);
81 | font-size: 10px;
82 | color: var(--job-status-color);
83 |
84 | &.created {
85 | color: $night-lightest;
86 | }
87 | }
88 |
89 | .stage-view .stage-title {
90 | text-align: center !important;
91 | }
92 |
93 | .pipeline-view {
94 | margin-bottom: 8px;
95 |
96 | .pipeline {
97 | height: 22px;
98 | font-size: 12px;
99 |
100 | .duration {
101 | font-size: 12px;
102 | }
103 |
104 | .pipeline-icon {
105 | width: 10px !important;
106 | margin-right: 4px;
107 | }
108 |
109 | .skipped {
110 | line-height: var(--job-icon-size);
111 | color: $night-lightest;
112 | padding-top: 0;
113 | padding-bottom: 0;
114 | border-radius: 999px;
115 |
116 | .pipeline-icon {
117 | width: 18px !important;
118 | height: 18px !important;
119 | }
120 | }
121 | }
122 |
123 | .branch {
124 | font-size: 12px;
125 |
126 | svg {
127 | height: 12px !important;
128 | margin-right: 6px !important;
129 | }
130 | }
131 | }
132 |
133 | .project-card {
134 | background-color: white !important;
135 | box-shadow: 0 3px + 100px 0 -100px inset var(--project-card-status-color);
136 |
137 | .info {
138 | padding: 0.5rem;
139 | font-size: 0.6rem !important;
140 | }
141 |
142 | .content {
143 | padding-bottom: 0;
144 |
145 | .title {
146 | text-shadow: none;
147 |
148 | &:not(.small) {
149 | font-family: 'Roboto Condensed', sans-serif;
150 | }
151 |
152 | &.small {
153 | line-height: 1;
154 | font-size: 10px;
155 | color: $night-lightest;
156 | }
157 | }
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | import queryString from 'query-string'
2 |
3 | export const getQueryParameter = (name) => {
4 | const parsed = queryString.parse(location.search)
5 |
6 | if (name in parsed) {
7 | let parameter = parsed[name]
8 |
9 | if (parameter === 'true') {
10 | return true
11 | } else if (parameter === 'false') {
12 | return false
13 | } else if (!isNaN(+parameter)) {
14 | return +parameter
15 | }
16 |
17 | return parameter
18 | } else {
19 | return null
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const MonacoEditorPlugin = require('monaco-editor-webpack-plugin')
2 |
3 | module.exports = {
4 | publicPath: '.',
5 | configureWebpack: {
6 | plugins: [
7 | new MonacoEditorPlugin({
8 | // https://github.com/Microsoft/monaco-editor-webpack-plugin#options
9 | // Include a subset of languages support
10 | // Some language extensions like typescript are so huge that may impact build performance
11 | // e.g. Build full languages support with webpack 4.0 takes over 80 seconds
12 | // Languages are loaded on demand at runtime
13 | languages: ['css', 'yaml']
14 | })
15 | ]
16 | }
17 | };
18 |
--------------------------------------------------------------------------------