gitlab-radiator 3.3.8 → 3.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +4 -0
- package/.eslintrc +13 -0
- package/.github/workflows/test.yml +20 -0
- package/.nvmrc +1 -0
- package/build-npm +19 -0
- package/package.json +31 -31
- package/screenshot.png +0 -0
- package/src/app.js +80 -167
- package/src/auth.js +13 -25
- package/src/client/.eslintrc +21 -0
- package/src/client/arguments.ts +75 -0
- package/src/client/gitlab-types.ts +54 -0
- package/src/client/groupedProjects.tsx +40 -0
- package/src/client/groups.tsx +59 -0
- package/src/client/index.tsx +94 -0
- package/src/client/info.tsx +16 -0
- package/src/client/jobs.tsx +56 -0
- package/src/client/projects.tsx +51 -0
- package/src/client/renderTimestamp.tsx +45 -0
- package/src/client/stages.tsx +18 -0
- package/src/config.js +28 -44
- package/src/dev-assets.js +10 -0
- package/src/gitlab/client.js +17 -39
- package/src/gitlab/index.js +44 -160
- package/src/gitlab/pipelines.js +79 -247
- package/src/gitlab/projects.js +39 -118
- package/src/gitlab/runners.js +15 -76
- package/src/index.js +2 -4
- package/test/gitlab/projects.test.js +75 -0
- package/test/gitlab-integration.js +384 -0
- package/tsconfig.json +28 -0
- package/webpack.common.js +25 -0
- package/webpack.dev.js +13 -0
- package/webpack.prod.js +9 -0
- package/public/client.js +0 -2
- package/public/client.js.LICENSE.txt +0 -41
package/.babelrc
ADDED
package/.eslintrc
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Run tests
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
strategy:
|
|
9
|
+
matrix:
|
|
10
|
+
node-version: [12.x, 14.x]
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v2
|
|
13
|
+
- uses: actions/setup-node@v1
|
|
14
|
+
with:
|
|
15
|
+
node-version: ${{ matrix.node-version }}
|
|
16
|
+
- run: npm ci
|
|
17
|
+
- run: npm audit
|
|
18
|
+
- run: npm run eslint
|
|
19
|
+
- run: npm test
|
|
20
|
+
- run: npm run build
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
14.17.0
|
package/build-npm
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
rm -fr build
|
|
4
|
+
mkdir -p build/src
|
|
5
|
+
|
|
6
|
+
# Copy static resources
|
|
7
|
+
cp -r public build
|
|
8
|
+
|
|
9
|
+
# Copy LICENSE, README and package.json
|
|
10
|
+
cp LICENSE package.json README.md build
|
|
11
|
+
|
|
12
|
+
# Copy bin script
|
|
13
|
+
cp -r bin build
|
|
14
|
+
|
|
15
|
+
# Bundle and minify client JS
|
|
16
|
+
npx webpack --config webpack.prod.js
|
|
17
|
+
|
|
18
|
+
# Transpile server
|
|
19
|
+
node_modules/.bin/babel src --ignore **/client/*.js,**/dev-assets.js --out-dir build/src
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"email": "heikki.pora@gmail.com"
|
|
6
6
|
},
|
|
7
7
|
"description": "The missing GitLab build radiator view",
|
|
8
|
-
"version": "3.3.
|
|
8
|
+
"version": "3.3.9",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"bin": {
|
|
11
11
|
"gitlab-radiator": "bin/gitlab-radiator"
|
|
@@ -38,52 +38,52 @@
|
|
|
38
38
|
}
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@babel/runtime": "7.
|
|
42
|
-
"axios": "0.
|
|
41
|
+
"@babel/runtime": "7.16.3",
|
|
42
|
+
"axios": "0.24.0",
|
|
43
43
|
"basic-auth": "2.0.1",
|
|
44
44
|
"compression": "1.7.4",
|
|
45
|
-
"date-fns": "2.
|
|
45
|
+
"date-fns": "2.27.0",
|
|
46
46
|
"esm": "3.2.25",
|
|
47
47
|
"express": "4.17.1",
|
|
48
48
|
"js-yaml": "4.1.0",
|
|
49
49
|
"less-middleware": "3.1.0",
|
|
50
50
|
"lodash": "4.17.21",
|
|
51
|
-
"socket.io": "4.
|
|
51
|
+
"socket.io": "4.4.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@babel/cli": "7.
|
|
55
|
-
"@babel/core": "7.
|
|
56
|
-
"@babel/node": "7.
|
|
57
|
-
"@babel/plugin-transform-runtime": "7.
|
|
58
|
-
"@babel/preset-env": "7.
|
|
59
|
-
"@babel/preset-react": "7.
|
|
60
|
-
"@babel/register": "7.
|
|
61
|
-
"@types/lodash": "4.14.
|
|
62
|
-
"@types/react": "17.0.
|
|
63
|
-
"@types/react-dom": "17.0.
|
|
64
|
-
"@types/webpack-env": "1.16.
|
|
65
|
-
"@typescript-eslint/eslint-plugin": "5.
|
|
66
|
-
"@typescript-eslint/parser": "5.
|
|
67
|
-
"babel-
|
|
68
|
-
"babel-loader": "8.2.
|
|
54
|
+
"@babel/cli": "7.16.0",
|
|
55
|
+
"@babel/core": "7.16.0",
|
|
56
|
+
"@babel/node": "7.16.0",
|
|
57
|
+
"@babel/plugin-transform-runtime": "7.16.4",
|
|
58
|
+
"@babel/preset-env": "7.16.4",
|
|
59
|
+
"@babel/preset-react": "7.16.0",
|
|
60
|
+
"@babel/register": "7.16.0",
|
|
61
|
+
"@types/lodash": "4.14.177",
|
|
62
|
+
"@types/react": "17.0.37",
|
|
63
|
+
"@types/react-dom": "17.0.11",
|
|
64
|
+
"@types/webpack-env": "1.16.3",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "5.5.0",
|
|
66
|
+
"@typescript-eslint/parser": "5.5.0",
|
|
67
|
+
"@babel/eslint-parser": "7.16.3",
|
|
68
|
+
"babel-loader": "8.2.3",
|
|
69
69
|
"chai": "4.3.4",
|
|
70
|
-
"css-loader": "6.
|
|
71
|
-
"eslint": "
|
|
70
|
+
"css-loader": "6.5.1",
|
|
71
|
+
"eslint": "8.3.0",
|
|
72
72
|
"eslint-plugin-mocha": "9.0.0",
|
|
73
|
-
"eslint-plugin-react": "7.
|
|
73
|
+
"eslint-plugin-react": "7.27.1",
|
|
74
74
|
"less": "4.1.2",
|
|
75
|
-
"less-loader": "10.
|
|
76
|
-
"mocha": "9.1.
|
|
75
|
+
"less-loader": "10.2.0",
|
|
76
|
+
"mocha": "9.1.3",
|
|
77
77
|
"normalize.css": "8.0.1",
|
|
78
78
|
"react": "17.0.2",
|
|
79
79
|
"react-dom": "17.0.2",
|
|
80
|
-
"sinon": "
|
|
81
|
-
"style-loader": "3.3.
|
|
80
|
+
"sinon": "12.0.1",
|
|
81
|
+
"style-loader": "3.3.1",
|
|
82
82
|
"ts-loader": "9.2.6",
|
|
83
|
-
"typescript": "4.
|
|
84
|
-
"webpack": "5.
|
|
85
|
-
"webpack-cli": "4.9.
|
|
86
|
-
"webpack-dev-middleware": "5.2.
|
|
83
|
+
"typescript": "4.5.2",
|
|
84
|
+
"webpack": "5.64.4",
|
|
85
|
+
"webpack-cli": "4.9.1",
|
|
86
|
+
"webpack-dev-middleware": "5.2.2",
|
|
87
87
|
"webpack-hot-middleware": "2.25.1",
|
|
88
88
|
"webpack-merge": "5.8.0"
|
|
89
89
|
},
|
package/screenshot.png
ADDED
|
Binary file
|
package/src/app.js
CHANGED
|
@@ -1,187 +1,100 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
var _runners = require("./gitlab/runners");
|
|
20
|
-
|
|
21
|
-
var _http = _interopRequireDefault(require("http"));
|
|
22
|
-
|
|
23
|
-
var _lessMiddleware = _interopRequireDefault(require("less-middleware"));
|
|
24
|
-
|
|
25
|
-
var _os = _interopRequireDefault(require("os"));
|
|
26
|
-
|
|
27
|
-
var _path = _interopRequireDefault(require("path"));
|
|
28
|
-
|
|
29
|
-
var _socket = _interopRequireDefault(require("socket.io"));
|
|
30
|
-
|
|
31
|
-
var _gitlab = require("./gitlab");
|
|
32
|
-
|
|
33
|
-
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; }
|
|
34
|
-
|
|
35
|
-
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
36
|
-
|
|
37
|
-
var cacheDir = _path.default.join(_os.default.tmpdir(), 'gitlab-radiator-css-cache');
|
|
38
|
-
|
|
39
|
-
var app = (0, _express.default)();
|
|
40
|
-
|
|
41
|
-
var httpServer = _http.default.Server(app);
|
|
42
|
-
|
|
43
|
-
var socketIoServer = (0, _socket.default)(httpServer);
|
|
1
|
+
import {basicAuth} from './auth'
|
|
2
|
+
import compression from 'compression'
|
|
3
|
+
import {config} from './config'
|
|
4
|
+
import express from 'express'
|
|
5
|
+
import {fetchOfflineRunners} from './gitlab/runners'
|
|
6
|
+
import http from 'http'
|
|
7
|
+
import lessMiddleware from 'less-middleware'
|
|
8
|
+
import os from 'os'
|
|
9
|
+
import path from 'path'
|
|
10
|
+
import socketIo from 'socket.io'
|
|
11
|
+
import {update} from './gitlab'
|
|
12
|
+
|
|
13
|
+
const cacheDir = path.join(os.tmpdir(), 'gitlab-radiator-css-cache')
|
|
14
|
+
|
|
15
|
+
const app = express()
|
|
16
|
+
const httpServer = http.Server(app)
|
|
17
|
+
const socketIoServer = socketIo(httpServer)
|
|
44
18
|
|
|
45
19
|
if (process.env.NODE_ENV !== 'production') {
|
|
46
20
|
// eslint-disable-next-line global-require
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
bindDevAssets(app);
|
|
21
|
+
const {bindDevAssets} = require('./dev-assets')
|
|
22
|
+
bindDevAssets(app)
|
|
51
23
|
}
|
|
52
24
|
|
|
53
|
-
app.disable('x-powered-by')
|
|
54
|
-
app.use((
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
25
|
+
app.disable('x-powered-by')
|
|
26
|
+
app.use(lessMiddleware(`${__dirname}/../public`, {
|
|
27
|
+
dest: cacheDir,
|
|
28
|
+
preprocess: {
|
|
29
|
+
less: (src) => {
|
|
30
|
+
let colorLess = ''
|
|
31
|
+
Object.keys(config.colors).forEach((stateName) => {
|
|
32
|
+
colorLess += `@${stateName}-color:${config.colors[stateName]};`
|
|
33
|
+
})
|
|
34
|
+
return src + colorLess
|
|
35
|
+
}
|
|
63
36
|
}
|
|
64
37
|
}
|
|
65
|
-
|
|
66
|
-
app.use(
|
|
67
|
-
app.use(
|
|
68
|
-
app.use((
|
|
69
|
-
app.use(
|
|
70
|
-
|
|
38
|
+
))
|
|
39
|
+
app.use(express.static(cacheDir))
|
|
40
|
+
app.use(express.static(`${__dirname}/../public`))
|
|
41
|
+
app.use(compression())
|
|
42
|
+
app.use(basicAuth(config.auth))
|
|
43
|
+
|
|
44
|
+
httpServer.listen(config.port, () => {
|
|
71
45
|
// eslint-disable-next-line no-console
|
|
72
|
-
console.log(
|
|
73
|
-
})
|
|
74
|
-
|
|
46
|
+
console.log(`Listening on port *:${config.port}`)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const globalState = {
|
|
75
50
|
projects: null,
|
|
76
51
|
error: null,
|
|
77
|
-
zoom:
|
|
78
|
-
projectsOrder:
|
|
79
|
-
columns:
|
|
80
|
-
groupSuccessfulProjects:
|
|
81
|
-
};
|
|
82
|
-
socketIoServer.on('connection', socket => {
|
|
83
|
-
socket.emit('state', withDate(globalState));
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
function runUpdate() {
|
|
87
|
-
return _runUpdate.apply(this, arguments);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function _runUpdate() {
|
|
91
|
-
_runUpdate = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
|
|
92
|
-
return _regenerator.default.wrap(function _callee$(_context) {
|
|
93
|
-
while (1) {
|
|
94
|
-
switch (_context.prev = _context.next) {
|
|
95
|
-
case 0:
|
|
96
|
-
_context.prev = 0;
|
|
97
|
-
_context.next = 3;
|
|
98
|
-
return (0, _gitlab.update)(_config.config);
|
|
99
|
-
|
|
100
|
-
case 3:
|
|
101
|
-
globalState.projects = _context.sent;
|
|
102
|
-
_context.next = 6;
|
|
103
|
-
return errorIfRunnerOffline();
|
|
104
|
-
|
|
105
|
-
case 6:
|
|
106
|
-
globalState.error = _context.sent;
|
|
107
|
-
socketIoServer.emit('state', withDate(globalState));
|
|
108
|
-
_context.next = 15;
|
|
109
|
-
break;
|
|
110
|
-
|
|
111
|
-
case 10:
|
|
112
|
-
_context.prev = 10;
|
|
113
|
-
_context.t0 = _context["catch"](0);
|
|
114
|
-
// eslint-disable-next-line no-console
|
|
115
|
-
console.error(_context.t0.message);
|
|
116
|
-
globalState.error = "Failed to communicate with GitLab API: ".concat(_context.t0.message);
|
|
117
|
-
socketIoServer.emit('state', withDate(globalState));
|
|
118
|
-
|
|
119
|
-
case 15:
|
|
120
|
-
setTimeout(runUpdate, _config.config.interval);
|
|
121
|
-
|
|
122
|
-
case 16:
|
|
123
|
-
case "end":
|
|
124
|
-
return _context.stop();
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}, _callee, null, [[0, 10]]);
|
|
128
|
-
}));
|
|
129
|
-
return _runUpdate.apply(this, arguments);
|
|
52
|
+
zoom: config.zoom,
|
|
53
|
+
projectsOrder: config.projectsOrder,
|
|
54
|
+
columns: config.columns,
|
|
55
|
+
groupSuccessfulProjects: config.groupSuccessfulProjects
|
|
130
56
|
}
|
|
131
57
|
|
|
132
|
-
|
|
133
|
-
|
|
58
|
+
socketIoServer.on('connection', (socket) => {
|
|
59
|
+
socket.emit('state', withDate(globalState))
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
async function runUpdate() {
|
|
63
|
+
try {
|
|
64
|
+
globalState.projects = await update(config)
|
|
65
|
+
globalState.error = await errorIfRunnerOffline()
|
|
66
|
+
socketIoServer.emit('state', withDate(globalState))
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// eslint-disable-next-line no-console
|
|
69
|
+
console.error(error.message)
|
|
70
|
+
globalState.error = `Failed to communicate with GitLab API: ${error.message}`
|
|
71
|
+
socketIoServer.emit('state', withDate(globalState))
|
|
72
|
+
}
|
|
73
|
+
setTimeout(runUpdate, config.interval)
|
|
134
74
|
}
|
|
135
75
|
|
|
136
|
-
function
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
_context2.next = 2;
|
|
145
|
-
return Promise.all(_config.config.gitlabs.map(_runners.fetchOfflineRunners));
|
|
146
|
-
|
|
147
|
-
case 2:
|
|
148
|
-
offlineRunnersPerGitlab = _context2.sent;
|
|
149
|
-
_offlineRunnersPerGit = offlineRunnersPerGitlab.reduce((acc, runner) => {
|
|
150
|
-
return {
|
|
151
|
-
offline: acc.offline.concat(runner.offline),
|
|
152
|
-
totalCount: acc.totalCount + runner.totalCount
|
|
153
|
-
};
|
|
154
|
-
}, {
|
|
155
|
-
offline: [],
|
|
156
|
-
totalCount: 0
|
|
157
|
-
}), offline = _offlineRunnersPerGit.offline, totalCount = _offlineRunnersPerGit.totalCount;
|
|
158
|
-
|
|
159
|
-
if (!(offline.length > 0)) {
|
|
160
|
-
_context2.next = 8;
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
names = offline.map(r => r.name).sort().join(', ');
|
|
165
|
-
counts = offline.length === totalCount ? 'All' : "".concat(offline.length, "/").concat(totalCount);
|
|
166
|
-
return _context2.abrupt("return", "".concat(counts, " runners offline: ").concat(names));
|
|
167
|
-
|
|
168
|
-
case 8:
|
|
169
|
-
return _context2.abrupt("return", null);
|
|
76
|
+
async function errorIfRunnerOffline() {
|
|
77
|
+
const offlineRunnersPerGitlab = await Promise.all(config.gitlabs.map(fetchOfflineRunners))
|
|
78
|
+
const {offline, totalCount} = offlineRunnersPerGitlab.reduce((acc, runner) => {
|
|
79
|
+
return {
|
|
80
|
+
offline: acc.offline.concat(runner.offline),
|
|
81
|
+
totalCount: acc.totalCount + runner.totalCount
|
|
82
|
+
}
|
|
83
|
+
}, {offline: [], totalCount: 0})
|
|
170
84
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}));
|
|
178
|
-
return _errorIfRunnerOffline.apply(this, arguments);
|
|
85
|
+
if (offline.length > 0) {
|
|
86
|
+
const names = offline.map(r => r.name).sort().join(', ')
|
|
87
|
+
const counts = offline.length === totalCount ? 'All' : `${offline.length}/${totalCount}`
|
|
88
|
+
return `${counts} runners offline: ${names}`
|
|
89
|
+
}
|
|
90
|
+
return null
|
|
179
91
|
}
|
|
180
92
|
|
|
181
|
-
runUpdate()
|
|
93
|
+
runUpdate()
|
|
182
94
|
|
|
183
95
|
function withDate(state) {
|
|
184
|
-
return
|
|
96
|
+
return {
|
|
97
|
+
...state,
|
|
185
98
|
now: Date.now()
|
|
186
|
-
}
|
|
187
|
-
}
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/auth.js
CHANGED
|
@@ -1,33 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
import authenticate from 'basic-auth'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", {
|
|
6
|
-
value: true
|
|
7
|
-
});
|
|
8
|
-
exports.basicAuth = basicAuth;
|
|
9
|
-
|
|
10
|
-
var _basicAuth = _interopRequireDefault(require("basic-auth"));
|
|
11
|
-
|
|
12
|
-
function basicAuth(auth) {
|
|
3
|
+
export function basicAuth(auth) {
|
|
13
4
|
if (!auth || !auth.username || !auth.password) {
|
|
14
5
|
// eslint-disable-next-line no-console
|
|
15
|
-
console.log('No authentication configured')
|
|
16
|
-
return (req, res, next) => next()
|
|
17
|
-
}
|
|
6
|
+
console.log('No authentication configured')
|
|
7
|
+
return (req, res, next) => next()
|
|
8
|
+
}
|
|
18
9
|
|
|
19
|
-
|
|
20
|
-
console.log('HTTP basic auth enabled')
|
|
10
|
+
// eslint-disable-next-line no-console
|
|
11
|
+
console.log('HTTP basic auth enabled')
|
|
21
12
|
return (req, res, next) => {
|
|
22
|
-
|
|
23
|
-
name = _ref.name,
|
|
24
|
-
pass = _ref.pass;
|
|
25
|
-
|
|
13
|
+
const {name, pass} = authenticate(req) || {}
|
|
26
14
|
if (auth.username === name && auth.password === pass) {
|
|
27
|
-
next()
|
|
15
|
+
next()
|
|
28
16
|
} else {
|
|
29
|
-
res.setHeader('WWW-Authenticate', 'Basic realm="gitlab-radiator"')
|
|
30
|
-
res.status(401).end()
|
|
17
|
+
res.setHeader('WWW-Authenticate', 'Basic realm="gitlab-radiator"')
|
|
18
|
+
res.status(401).end()
|
|
31
19
|
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"browser": true,
|
|
4
|
+
"es6": true
|
|
5
|
+
},
|
|
6
|
+
"parser": "@typescript-eslint/parser",
|
|
7
|
+
"plugins": [
|
|
8
|
+
"@typescript-eslint"
|
|
9
|
+
],
|
|
10
|
+
"extends": [
|
|
11
|
+
"eslint:recommended",
|
|
12
|
+
"plugin:react/recommended",
|
|
13
|
+
"plugin:@typescript-eslint/eslint-recommended",
|
|
14
|
+
"plugin:@typescript-eslint/recommended"
|
|
15
|
+
],
|
|
16
|
+
"settings": {
|
|
17
|
+
"react": {
|
|
18
|
+
"version": "17.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
interface ParsedQueryString {
|
|
2
|
+
[key: string]: string | undefined
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function argumentsFromDocumentUrl(): {override: {columns?: number, zoom?: number}, includedTags: string[] | null, screen: {id: number, total: number}} {
|
|
6
|
+
const args = parseQueryString(document.location.search)
|
|
7
|
+
return {
|
|
8
|
+
override: overrideArguments(args),
|
|
9
|
+
includedTags: tagArguments(args),
|
|
10
|
+
screen: screenArguments(args)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function tagArguments(args: ParsedQueryString): string[] | null {
|
|
15
|
+
if (args.tags === undefined) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
return args.tags
|
|
19
|
+
.split(',')
|
|
20
|
+
.map(t => t.toLowerCase().trim())
|
|
21
|
+
.filter(t => t)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function overrideArguments(args: ParsedQueryString): {columns?: number, zoom?: number} {
|
|
25
|
+
return {
|
|
26
|
+
...parseColumns(args),
|
|
27
|
+
...parseZoom(args)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseColumns(args: ParsedQueryString) {
|
|
32
|
+
if (args.columns) {
|
|
33
|
+
const columns = Number(args.columns)
|
|
34
|
+
if (columns > 0 && columns <= 10) {
|
|
35
|
+
return {columns}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
function parseZoom(args: ParsedQueryString) {
|
|
43
|
+
if (args.zoom) {
|
|
44
|
+
const zoom = Number(args.zoom)
|
|
45
|
+
if (zoom > 0 && zoom <= 2) {
|
|
46
|
+
return {zoom}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function screenArguments(args: ParsedQueryString): {id: number, total: number} {
|
|
53
|
+
const matches = (/(\d)of(\d)/).exec(args.screen || '')
|
|
54
|
+
let id = matches ? Number(matches[1]) : 1
|
|
55
|
+
const total = matches ? Number(matches[2]) : 1
|
|
56
|
+
if (id > total) {
|
|
57
|
+
id = total
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
id,
|
|
61
|
+
total
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseQueryString(search: string): ParsedQueryString {
|
|
66
|
+
const entries = search
|
|
67
|
+
.slice(1)
|
|
68
|
+
.split('&')
|
|
69
|
+
.filter(parameter => parameter)
|
|
70
|
+
.map((parameter: string): [string, string | undefined] => {
|
|
71
|
+
const [key, value] = parameter.split('=')
|
|
72
|
+
return [key, value]
|
|
73
|
+
})
|
|
74
|
+
return Object.fromEntries(entries)
|
|
75
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
export interface GlobalState {
|
|
3
|
+
columns: number
|
|
4
|
+
error: string | null
|
|
5
|
+
groupSuccessfulProjects: boolean
|
|
6
|
+
projects: Project[] | null
|
|
7
|
+
projectsOrder: string[]
|
|
8
|
+
zoom: number
|
|
9
|
+
now: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Project {
|
|
13
|
+
archived: false
|
|
14
|
+
group: string
|
|
15
|
+
id: number
|
|
16
|
+
name: string
|
|
17
|
+
nameWithoutNamespace: string
|
|
18
|
+
tags: string[]
|
|
19
|
+
url: string
|
|
20
|
+
default_branch: string
|
|
21
|
+
pipelines: Pipeline[]
|
|
22
|
+
maxNonFailedJobsVisible: number
|
|
23
|
+
status: 'success' | 'failed'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface Pipeline {
|
|
27
|
+
commit: Commit | null
|
|
28
|
+
id: number
|
|
29
|
+
ref: string
|
|
30
|
+
stages: Stage[]
|
|
31
|
+
status: 'success' | 'failed'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Commit {
|
|
35
|
+
title: string
|
|
36
|
+
author: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Stage {
|
|
40
|
+
jobs: Job[]
|
|
41
|
+
name: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface Job {
|
|
45
|
+
finishedAt: string | null
|
|
46
|
+
id: number
|
|
47
|
+
name: string
|
|
48
|
+
stage: string
|
|
49
|
+
startedAt: string | null
|
|
50
|
+
status: JobStatus
|
|
51
|
+
url: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type JobStatus = 'created' | 'failed' | 'manual' | 'pending' | 'running' | 'skipped' | 'success'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import _ from 'lodash'
|
|
2
|
+
import {Groups} from './groups'
|
|
3
|
+
import type {Project} from './gitlab-types'
|
|
4
|
+
import {Projects} from './projects'
|
|
5
|
+
import React from 'react'
|
|
6
|
+
|
|
7
|
+
export function GroupedProjects({projects, projectsOrder, groupSuccessfulProjects, zoom, columns, now, screen}: {projects: Project[], projectsOrder: string[], groupSuccessfulProjects: boolean, zoom: number, columns: number, now: number, screen: {id: number, total: number}}): JSX.Element {
|
|
8
|
+
if (groupSuccessfulProjects) {
|
|
9
|
+
return renderProjectsGrouped(projects, projectsOrder, zoom, columns, now, screen)
|
|
10
|
+
}
|
|
11
|
+
return renderProjects(projects, projectsOrder, zoom, columns, now, screen)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function renderProjectsGrouped(projects: Project[], projectsOrder: string[], zoom: number, columns: number, now: number, screen: {id: number, total: number}) {
|
|
15
|
+
const successfullProjects: Project[] = []
|
|
16
|
+
const otherProjects: Project[] = []
|
|
17
|
+
projects.forEach((project) => {
|
|
18
|
+
if (project.status === 'success') {
|
|
19
|
+
successfullProjects.push(project)
|
|
20
|
+
} else {
|
|
21
|
+
otherProjects.push(project)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
const groupedProjects = _.groupBy(successfullProjects, 'group')
|
|
25
|
+
return <React.Fragment>
|
|
26
|
+
{renderProjects(otherProjects, projectsOrder, zoom, columns, now, screen)}
|
|
27
|
+
{renderGroupedProjects(groupedProjects, zoom, columns, now)}
|
|
28
|
+
</React.Fragment>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function renderProjects(projects: Project[], projectsOrder: string[], zoom: number, columns: number, now: number, screen: {id: number, total: number}) {
|
|
32
|
+
return <Projects now={now} zoom={zoom} columns={columns}
|
|
33
|
+
projects={projects || []} projectsOrder={projectsOrder}
|
|
34
|
+
screen={screen}/>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderGroupedProjects(groupedProjects: {[groupname: string]: Project[]}, zoom: number, columns: number, now: number) {
|
|
38
|
+
return <Groups zoom={zoom} columns={columns} now={now}
|
|
39
|
+
groupedProjects={groupedProjects || []} />
|
|
40
|
+
}
|