feat: Initial commit
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
32
dist/common.js
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// native functions
|
||||||
|
const invoke = window.__TAURI__.invoke
|
||||||
|
const isLoggedIn = () => invoke('is_logged_in')
|
||||||
|
const logIn = token => invoke('log_in', { token })
|
||||||
|
const listRunners = () => invoke('list_runners')
|
||||||
|
const listJobsForRunner = id => invoke('list_jobs_for_runner', { runnerId: id })
|
||||||
|
const retryJob = (projectId, jobId) => invoke('retry_job', { projectId, jobId })
|
||||||
|
|
||||||
|
// shit helpers
|
||||||
|
const $ = (...args) => {
|
||||||
|
if (args.length === 1) return document.querySelector(args[0])
|
||||||
|
return args[0].querySelector(args[1])
|
||||||
|
}
|
||||||
|
const $$ = document.querySelectorAll.bind(document)
|
||||||
|
|
||||||
|
function initTemplate(template, data) {
|
||||||
|
const fragment = template.content.cloneNode(true)
|
||||||
|
Object.keys(data).forEach(key => {
|
||||||
|
const field = fragment.querySelector('.' + key)
|
||||||
|
const value = data[key]
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
field.textContent = value
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
field.textContent = value.toString()
|
||||||
|
} else if (value.link) {
|
||||||
|
const a = field.querySelector('a')
|
||||||
|
a.href = value.link
|
||||||
|
a.textContent = value.text
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return fragment
|
||||||
|
}
|
38
dist/index.html
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<style>
|
||||||
|
#error-text {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>hi! pls give gitlab token</h1>
|
||||||
|
<input id="gitlab-token" placeholder="put token here">
|
||||||
|
<button id="login-button">Submit</button>
|
||||||
|
<span id="error-text"></span>
|
||||||
|
|
||||||
|
<script src="common.js"></script>
|
||||||
|
<script>
|
||||||
|
isLoggedIn().then(loggedIn => {
|
||||||
|
if (loggedIn) document.location.href = 'runners.html'
|
||||||
|
})
|
||||||
|
|
||||||
|
const errorText = $('#error-text')
|
||||||
|
$('#login-button').addEventListener('click', ev => {
|
||||||
|
errorText.textContent = ''
|
||||||
|
|
||||||
|
logIn($('#gitlab-token').value)
|
||||||
|
.then(() => {
|
||||||
|
document.location.href = 'runners.html'
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
errorText.textContent = err.toString()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
155
dist/runner.html
vendored
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<style>
|
||||||
|
table {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
td.status {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
tr.failed td.status {
|
||||||
|
color: #ff0055;
|
||||||
|
}
|
||||||
|
tr.success td.status {
|
||||||
|
color: #009e73;
|
||||||
|
}
|
||||||
|
tr.running td.status {
|
||||||
|
color: #56b4e9;
|
||||||
|
}
|
||||||
|
td.retry {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Pipeline</th>
|
||||||
|
<th>Job name</th>
|
||||||
|
<th>Created at</th>
|
||||||
|
<th class="status">Status</th>
|
||||||
|
<th>Project</th>
|
||||||
|
<th>Runner</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tr>
|
||||||
|
<td class="pipeline"><a target="_blank"></a></td>
|
||||||
|
<td class="name"><a target="_blank"></a></td>
|
||||||
|
<td class="created_at"></td>
|
||||||
|
<td class="status"></td>
|
||||||
|
<td class="project"><a target="_blank"></a></td>
|
||||||
|
<td class="runner"></td>
|
||||||
|
<td class="retry">retry</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="common.js"></script>
|
||||||
|
<script>
|
||||||
|
const runnerId = new URLSearchParams(window.location.search).get('id')
|
||||||
|
// $('h2').textContent = runnerId
|
||||||
|
|
||||||
|
const template = $('template')
|
||||||
|
const table = $('tbody')
|
||||||
|
|
||||||
|
let notifications = true
|
||||||
|
|
||||||
|
$('th.status').addEventListener('click', ev => {
|
||||||
|
notifications = !notifications
|
||||||
|
window.__TAURI__.notification.sendNotification({
|
||||||
|
title: 'GitLab Jobs',
|
||||||
|
body: `Notifications are now ${notifications ? 'enabled' : 'disabled'}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
async function updateJobs() {
|
||||||
|
try {
|
||||||
|
let jobs
|
||||||
|
if (runnerId) {
|
||||||
|
jobs = await listJobsForRunner(runnerId)
|
||||||
|
} else {
|
||||||
|
const runners = await listRunners()
|
||||||
|
const promises = runners.map(runner => listJobsForRunner(runner.id.toString()).then(jobs => jobs.map(job => ({ ...job, runner }))))
|
||||||
|
jobs = (await Promise.all(promises)).flat()
|
||||||
|
}
|
||||||
|
|
||||||
|
jobs.forEach(job => {
|
||||||
|
const existing = $('#job' + job.id)
|
||||||
|
if (existing != null) {
|
||||||
|
const status = $(existing, '.status')
|
||||||
|
if (job.status !== status.textContent) {
|
||||||
|
status.textContent = job.status
|
||||||
|
existing.className = ''
|
||||||
|
existing.classList.add(job.status)
|
||||||
|
if (['success', 'failed'].includes(job.status)) {
|
||||||
|
window.__TAURI__.notification.sendNotification({
|
||||||
|
title: 'GitLab Jobs',
|
||||||
|
body: `Job ${job.name} for ${job.project.name}: ${job.status}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fragment = initTemplate(template, {
|
||||||
|
pipeline: {
|
||||||
|
link: job.pipeline.web_url,
|
||||||
|
text: '#' + job.pipeline.id.toString()
|
||||||
|
},
|
||||||
|
status: job.status,
|
||||||
|
name: {
|
||||||
|
link: job.web_url,
|
||||||
|
text: job.name
|
||||||
|
},
|
||||||
|
created_at: job.created_at.substr(0, 19).replace('T', ' '),
|
||||||
|
project: {
|
||||||
|
link: 'https://gitlab.com/' + job.project.path_with_namespace,
|
||||||
|
text: job.project.name
|
||||||
|
},
|
||||||
|
runner: job.runner.description
|
||||||
|
})
|
||||||
|
|
||||||
|
const row = $(fragment, 'tr')
|
||||||
|
row.id = 'job' + job.id
|
||||||
|
row.classList.add(job.status)
|
||||||
|
|
||||||
|
$(row, '.retry').addEventListener('click', ev => {
|
||||||
|
try {
|
||||||
|
retryJob(job.project.id.toString(), job.id)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
alert(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const before = Array.from(table.children).find(child => {
|
||||||
|
return parseInt(child.id.substr(3)) < job.id
|
||||||
|
})
|
||||||
|
|
||||||
|
if (before) {
|
||||||
|
table.insertBefore(row, before)
|
||||||
|
} else {
|
||||||
|
table.appendChild(row)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
alert(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateJobs()
|
||||||
|
|
||||||
|
setInterval(updateJobs, 5000)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
54
dist/runners.html
vendored
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<style>
|
||||||
|
table tbody tr:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Pick a runner</h1>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="heading">
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
<a href="runner.html">Show all runners</a>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<tr>
|
||||||
|
<td class="id"></td>
|
||||||
|
<td class="desc"></td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="common.js"></script>
|
||||||
|
<script>
|
||||||
|
const template = $('template')
|
||||||
|
const table = $('tbody')
|
||||||
|
|
||||||
|
listRunners()
|
||||||
|
.then(runners => {
|
||||||
|
runners.forEach(runner => {
|
||||||
|
const fragment = initTemplate(template, {
|
||||||
|
id: runner.id,
|
||||||
|
desc: runner.description
|
||||||
|
})
|
||||||
|
fragment.querySelector('tr').addEventListener('click', ev => {
|
||||||
|
console.log('cock')
|
||||||
|
document.location.href = 'runner.html?id=' + runner.id
|
||||||
|
})
|
||||||
|
table.appendChild(fragment)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => alert(err))
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
37
dist/style.css
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #1f1f1f;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td,
|
||||||
|
table th {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:nth-child(even) {
|
||||||
|
background-color: #252525;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tbody tr:hover {
|
||||||
|
background-color: #444444;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #56b4e9;
|
||||||
|
}
|
310
package-lock.json
generated
Normal file
|
@ -0,0 +1,310 @@
|
||||||
|
{
|
||||||
|
"name": "gitlab-jobs-tauri",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "gitlab-jobs-tauri",
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^1.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/api": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-TJwKkXxtF52kN9Auu5TWD2AE4ssqTrsfdpIrixYwRb3gQ/FuYwvZjrMc9weYpgsW2cMhVNkvKgneNXF/4n04lw==",
|
||||||
|
"dependencies": {
|
||||||
|
"type-fest": "2.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.22.0",
|
||||||
|
"npm": ">= 6.6.0",
|
||||||
|
"yarn": ">= 1.19.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tauri"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-4eHnk3p0xnCXd9Zel3kLvdiiSURnN98GMFvWUAdirm5AjyOjcx8TIET/jqRYmYKE5yd+LMQqYMUfHRwA6JJUkg==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tauri": "tauri.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/tauri"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@tauri-apps/cli-darwin-arm64": "1.0.0",
|
||||||
|
"@tauri-apps/cli-darwin-x64": "1.0.0",
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf": "1.0.0",
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu": "1.0.0",
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl": "1.0.0",
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu": "1.0.0",
|
||||||
|
"@tauri-apps/cli-linux-x64-musl": "1.0.0",
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc": "1.0.0",
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0ryomgLjdpylXypMPVXLU3PZCde3Sw5nwN4coUhBcHPBLFRb8QPet+nweVK/HiZ3mxg8WeIazvpx2s8hS0l2GQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-oejvYUT4dEfzBi+FWMj+CMz4cZ6C2gEFHrUtKVLdTXr8Flj5UTwdB1YPGQjiOqk73LOI7cB/vXxb9DZT+Lrxgg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-yAu78v8TeXNx/ETS5F2G2Uw/HX+LQvZkX94zNiqFsAj7snfWI/IqSUM52OBrdh/D0EC9NCdjUJ7Vuo32uxf7tg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-YFUN/S58AN317njAynzcQ+EHhRsCDXqmp5g9Oiqmcdg1vU7fPWZivVLc1WHz+0037C7JnsX5PtKpNYewP/+Oqw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-al+TxMGoNVikEvRQfMyYE/mdjUcUNMo5brkCIAb+fL4rWQlAhAnYVzmg/rM8N4nhdXm1MOaYAagQmxr8898dNA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-KQmYlYyGpn6/2kSl9QivWG6EIepm6PJd57e6IKmYwAyNhLr2XfGl1CLuocUQQgO+jprjT70HXp+MXD0tcB0+Sw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-Qpaq5lZz569Aea6jfrRchgfEJaOrfLpCRBATcF8CJFFwVKmfCUcoV+MxbCIW30Zqw5Y06njC/ffa3261AV/ZIQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-e2DzFqEMI+s+gv14UupdI91gPxTbUJTbbfQlTHdQlOsTk4HEZTsh+ibAYBcCLAaMRW38NEsFlAUe1lQA0iRu/w==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-lWSs90pJeQX+L31IqIzmRhwLayEeyTh7mga0AxX8G868hvdLtcXCQA/rKoFtGdVLuHAx4+M+CBF5SMYb76xGYA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/type-fest": {
|
||||||
|
"version": "2.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.13.1.tgz",
|
||||||
|
"integrity": "sha512-hXYyrPFwETT2swFLHeoKtJrvSF/ftG/sA15/8nGaLuaDGfVAaq8DYFpu4yOyV4tzp082WqnTEoMsm3flKMI2FQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-TJwKkXxtF52kN9Auu5TWD2AE4ssqTrsfdpIrixYwRb3gQ/FuYwvZjrMc9weYpgsW2cMhVNkvKgneNXF/4n04lw==",
|
||||||
|
"requires": {
|
||||||
|
"type-fest": "2.13.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-4eHnk3p0xnCXd9Zel3kLvdiiSURnN98GMFvWUAdirm5AjyOjcx8TIET/jqRYmYKE5yd+LMQqYMUfHRwA6JJUkg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@tauri-apps/cli-darwin-arm64": "1.0.0",
|
||||||
|
"@tauri-apps/cli-darwin-x64": "1.0.0",
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf": "1.0.0",
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu": "1.0.0",
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl": "1.0.0",
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu": "1.0.0",
|
||||||
|
"@tauri-apps/cli-linux-x64-musl": "1.0.0",
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc": "1.0.0",
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-darwin-arm64": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0ryomgLjdpylXypMPVXLU3PZCde3Sw5nwN4coUhBcHPBLFRb8QPet+nweVK/HiZ3mxg8WeIazvpx2s8hS0l2GQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-darwin-x64": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-oejvYUT4dEfzBi+FWMj+CMz4cZ6C2gEFHrUtKVLdTXr8Flj5UTwdB1YPGQjiOqk73LOI7cB/vXxb9DZT+Lrxgg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-yAu78v8TeXNx/ETS5F2G2Uw/HX+LQvZkX94zNiqFsAj7snfWI/IqSUM52OBrdh/D0EC9NCdjUJ7Vuo32uxf7tg==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-linux-arm64-gnu": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-YFUN/S58AN317njAynzcQ+EHhRsCDXqmp5g9Oiqmcdg1vU7fPWZivVLc1WHz+0037C7JnsX5PtKpNYewP/+Oqw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-linux-arm64-musl": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-al+TxMGoNVikEvRQfMyYE/mdjUcUNMo5brkCIAb+fL4rWQlAhAnYVzmg/rM8N4nhdXm1MOaYAagQmxr8898dNA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-linux-x64-gnu": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-KQmYlYyGpn6/2kSl9QivWG6EIepm6PJd57e6IKmYwAyNhLr2XfGl1CLuocUQQgO+jprjT70HXp+MXD0tcB0+Sw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-linux-x64-musl": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-Qpaq5lZz569Aea6jfrRchgfEJaOrfLpCRBATcF8CJFFwVKmfCUcoV+MxbCIW30Zqw5Y06njC/ffa3261AV/ZIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-win32-ia32-msvc": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-e2DzFqEMI+s+gv14UupdI91gPxTbUJTbbfQlTHdQlOsTk4HEZTsh+ibAYBcCLAaMRW38NEsFlAUe1lQA0iRu/w==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@tauri-apps/cli-win32-x64-msvc": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-lWSs90pJeQX+L31IqIzmRhwLayEeyTh7mga0AxX8G868hvdLtcXCQA/rKoFtGdVLuHAx4+M+CBF5SMYb76xGYA==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"type-fest": {
|
||||||
|
"version": "2.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.13.1.tgz",
|
||||||
|
"integrity": "sha512-hXYyrPFwETT2swFLHeoKtJrvSF/ftG/sA15/8nGaLuaDGfVAaq8DYFpu4yOyV4tzp082WqnTEoMsm3flKMI2FQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
package.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"name": "gitlab-jobs-tauri",
|
||||||
|
"scripts": {
|
||||||
|
"tauri": "tauri"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^1.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
3
src-tauri/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
4270
src-tauri/Cargo.lock
generated
Normal file
30
src-tauri/Cargo.toml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
[package]
|
||||||
|
name = "app"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A Tauri App"
|
||||||
|
authors = ["ptrcnull"]
|
||||||
|
license = ""
|
||||||
|
repository = ""
|
||||||
|
default-run = "app"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.57"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "1.0.0", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tauri = { version = "1.0.0", features = ["api-all"] }
|
||||||
|
gitlab = "0.1500.0"
|
||||||
|
chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# by default Tauri runs in production mode
|
||||||
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
|
default = [ "custom-protocol" ]
|
||||||
|
# this feature is used used for production builds where `devPath` points to the filesystem
|
||||||
|
# DO NOT remove this
|
||||||
|
custom-protocol = [ "tauri/custom-protocol" ]
|
3
src-tauri/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
BIN
src-tauri/icons/128x128.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
After Width: | Height: | Size: 903 B |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
src-tauri/icons/icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
77
src-tauri/src/gitlab_api.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// additional queries not defined in the gitlab crate
|
||||||
|
|
||||||
|
use gitlab::{api::{endpoint_prelude::Method, Endpoint}, *};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
|
pub struct RunnerJobs {
|
||||||
|
pub runner_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Endpoint for RunnerJobs {
|
||||||
|
fn method(&self) -> Method {
|
||||||
|
Method::GET
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endpoint(&self) -> std::borrow::Cow<'static, str> {
|
||||||
|
format!("runners/{}/jobs?order_by=id", self.runner_id).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Runners {}
|
||||||
|
impl Endpoint for Runners {
|
||||||
|
fn method(&self) -> Method {
|
||||||
|
Method::GET
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endpoint(&self) -> std::borrow::Cow<'static, str> {
|
||||||
|
format!("runners").into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Job {
|
||||||
|
/// The ID of the job.
|
||||||
|
pub id: JobId,
|
||||||
|
/// The status of the job.
|
||||||
|
pub status: StatusState,
|
||||||
|
pub stage: String,
|
||||||
|
/// The name of the job.
|
||||||
|
pub name: String,
|
||||||
|
#[serde(rename = "ref")]
|
||||||
|
/// The name of the reference that was tested.
|
||||||
|
pub ref_: Option<String>,
|
||||||
|
pub tag: bool,
|
||||||
|
pub coverage: Option<f64>,
|
||||||
|
/// When the job was created or marked as pending.
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
/// When the job was started.
|
||||||
|
pub started_at: Option<DateTime<Utc>>,
|
||||||
|
/// When the job completed.
|
||||||
|
pub finished_at: Option<DateTime<Utc>>,
|
||||||
|
/// The user which ran the job.
|
||||||
|
pub user: Option<User>,
|
||||||
|
/// The artifact file uploaded from the job.
|
||||||
|
pub artifacts_file: Option<JobArtifactFile>,
|
||||||
|
/// The commit the job tested.
|
||||||
|
pub commit: RepoCommit,
|
||||||
|
/// The runner which ran the job.
|
||||||
|
pub runner: Option<Runner>,
|
||||||
|
/// The pipeline the job belongs to.
|
||||||
|
pub pipeline: PipelineBasic,
|
||||||
|
pub allow_failure: bool,
|
||||||
|
pub duration: Option<f64>,
|
||||||
|
pub artifacts: Option<Vec<JobArtifact>>,
|
||||||
|
pub artifacts_expire_at: Option<DateTime<Utc>>,
|
||||||
|
pub web_url: String,
|
||||||
|
pub project: ProjectInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct ProjectInfo {
|
||||||
|
pub id: ProjectId,
|
||||||
|
pub name: String,
|
||||||
|
pub name_with_namespace: String,
|
||||||
|
pub path: String,
|
||||||
|
pub path_with_namespace: String,
|
||||||
|
}
|
103
src-tauri/src/main.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
#![cfg_attr(
|
||||||
|
all(not(debug_assertions), target_os = "windows"),
|
||||||
|
windows_subsystem = "windows"
|
||||||
|
)]
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use gitlab::{api::{Query, AsyncQuery, projects::jobs::RetryJob}, Runner, Gitlab, AsyncGitlab};
|
||||||
|
use gitlab_api::Job;
|
||||||
|
|
||||||
|
pub mod gitlab_api;
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
gl: Option<AsyncGitlab>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
gl: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_client(&self) -> Result<AsyncGitlab, String> {
|
||||||
|
match &self.gl {
|
||||||
|
Some(client) => Ok(client.clone()),
|
||||||
|
None => {
|
||||||
|
Err("not logged in".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type State<'a> = tauri::State<'a, Mutex<AppState>>;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn is_logged_in(state: State) -> bool {
|
||||||
|
state.lock().unwrap().gl.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn log_in(token: String, state: State<'_>) -> Result<(), String> {
|
||||||
|
let gitlab_client = gitlab::GitlabBuilder::new("gitlab.com", token).build_async().await;
|
||||||
|
match gitlab_client {
|
||||||
|
Ok(client) => {
|
||||||
|
state.lock().unwrap().gl.replace(client);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(error) => Err(error.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn list_runners(state: State<'_>) -> Result<Vec<Runner>, String> {
|
||||||
|
let client = {
|
||||||
|
let appstate = state.lock().unwrap();
|
||||||
|
appstate.get_client()
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let endpoint = gitlab_api::Runners{};
|
||||||
|
let response: Result<Vec<Runner>, _> = endpoint.query_async(&client).await;
|
||||||
|
response.map_err(|err| err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn list_jobs_for_runner(runner_id: String, state: State<'_>) -> Result<Vec<Job>, String> {
|
||||||
|
let client = {
|
||||||
|
let appstate = state.lock().unwrap();
|
||||||
|
appstate.get_client()
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let endpoint = gitlab_api::RunnerJobs{runner_id};
|
||||||
|
let response: Result<Vec<Job>, _> = endpoint.query_async(&client).await;
|
||||||
|
response.map_err(|err| err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn retry_job(project_id: String, job_id: u64, state: State<'_>) -> Result<Job, String> {
|
||||||
|
let client = {
|
||||||
|
let appstate = state.lock().unwrap();
|
||||||
|
appstate.get_client()
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let endpoint = RetryJob::builder().project(project_id).job(job_id).build().unwrap();
|
||||||
|
let response: Result<Job, _> = endpoint.query_async(&client).await;
|
||||||
|
response.map_err(|err| err.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let context = tauri::generate_context!();
|
||||||
|
tauri::Builder::default()
|
||||||
|
.manage(Mutex::new(AppState::default()))
|
||||||
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
is_logged_in,
|
||||||
|
log_in,
|
||||||
|
list_runners,
|
||||||
|
list_jobs_for_runner,
|
||||||
|
retry_job
|
||||||
|
])
|
||||||
|
.menu(tauri::Menu::os_default(&context.package_info().name))
|
||||||
|
.run(context)
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
67
src-tauri/tauri.conf.json
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||||
|
"build": {
|
||||||
|
"beforeBuildCommand": "",
|
||||||
|
"beforeDevCommand": "",
|
||||||
|
"devPath": "http://localhost:8000",
|
||||||
|
"distDir": "../dist",
|
||||||
|
"withGlobalTauri": true
|
||||||
|
},
|
||||||
|
"package": {
|
||||||
|
"productName": "gitlab-jobs-tauri",
|
||||||
|
"version": "0.1.0"
|
||||||
|
},
|
||||||
|
"tauri": {
|
||||||
|
"allowlist": {
|
||||||
|
"all": true
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"category": "DeveloperTool",
|
||||||
|
"copyright": "",
|
||||||
|
"deb": {
|
||||||
|
"depends": []
|
||||||
|
},
|
||||||
|
"externalBin": [],
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
],
|
||||||
|
"identifier": "com.tauri.dev",
|
||||||
|
"longDescription": "",
|
||||||
|
"macOS": {
|
||||||
|
"entitlements": null,
|
||||||
|
"exceptionDomain": "",
|
||||||
|
"frameworks": [],
|
||||||
|
"providerShortName": null,
|
||||||
|
"signingIdentity": null
|
||||||
|
},
|
||||||
|
"resources": [],
|
||||||
|
"shortDescription": "",
|
||||||
|
"targets": "all",
|
||||||
|
"windows": {
|
||||||
|
"certificateThumbprint": null,
|
||||||
|
"digestAlgorithm": "sha256",
|
||||||
|
"timestampUrl": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
},
|
||||||
|
"updater": {
|
||||||
|
"active": false
|
||||||
|
},
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"fullscreen": false,
|
||||||
|
"height": 600,
|
||||||
|
"resizable": true,
|
||||||
|
"title": "GitLab Jobs",
|
||||||
|
"width": 800
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|