Compare commits

...

12 Commits

Author SHA1 Message Date
elee 0cd11ce578 debug env
agola/agola-web/agola web build/test The run finished successfully Details
2022-03-04 03:14:31 -06:00
elee 583dd58e20 switch to hyphen:
agola/agola-web/agola web build/test The run failed Details
2022-03-04 03:01:21 -06:00
elee 4d32019249 zzz
agola/agola-web/agola web build/test The run finished successfully Details
2022-03-04 02:48:21 -06:00
elee 81a2faf4ab forc
agola/agola-web/agola web build/test The run failed Details
2022-03-04 02:47:04 -06:00
elee d1b067e81b maybe now?
agola/agola-web/agola web build/test The run failed Details
2022-03-04 02:34:07 -06:00
elee 9c4598973d maybe fix?
agola/agola-web/agola web build/test The run failed Details
2022-03-04 02:25:35 -06:00
elee 8794196988 fix
agola/agola-web/agola web build/test The run failed Details
2022-03-04 02:17:39 -06:00
elee 7de411662d test tuxpaint
agola/agola-web/agola web build/test The run failed Details
2022-03-04 02:04:34 -06:00
Simone Gotti 77f7af09ab
Merge pull request #59 from sgotti/format_with_prettier
agola/agola-web/agola web build/test The run is pending Details
*: Format with prettier
2022-02-24 09:48:46 +01:00
Simone Gotti 3f2c57394a *: Format with prettier
Use latest prettier as devDependency so tools will use (or can be
configured to use) the npm provided version.
The unique config change is to use single quotes instead of double
quotes.
2022-02-24 09:02:02 +01:00
Simone Gotti ca0c494425
Merge pull request #60 from sgotti/fix_docker_node_image_version
Dockerfile: use fixed node image version
2022-02-24 09:01:40 +01:00
Simone Gotti 5086f0a4ec Dockerfile: use fixed node image version
Fix it to node 12 until we migrate to new versions of tailwind, vue
etc...
2022-02-24 08:54:03 +01:00
57 changed files with 2006 additions and 1465 deletions

View File

@ -77,7 +77,8 @@ local task_build(version, arch) = {
], ],
}, },
environment: { environment: {
DOCKERAUTH: { from_variable: 'dockerauth' }, DOCKERAUTH: { from_variable: 'harbor-auth' },
DOCKERURL: { from_variable: 'harbor-url' },
}, },
shell: '/busybox/sh', shell: '/busybox/sh',
working_dir: '/workspace', working_dir: '/workspace',
@ -90,13 +91,13 @@ local task_build(version, arch) = {
cat << EOF > /kaniko/.docker/config.json cat << EOF > /kaniko/.docker/config.json
{ {
"auths": { "auths": {
"https://index.docker.io/v1/": { "auth" : "$DOCKERAUTH" } "$DOCKERURL": { "auth" : "$DOCKERAUTH" }
} }
} }
EOF EOF
|||, |||,
}, },
{ type: 'run', command: '/kaniko/executor --destination sorintlab/agola-web:$AGOLA_GIT_TAG' }, { type: 'run', command: '/kaniko/executor --destination $DOCKERURL/tux/agola-web:$AGOLA_GIT_TAG' },
], ],
depends: ['checkout code and save to workspace'], depends: ['checkout code and save to workspace'],
when: { when: {

3
.prettierrc.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
singleQuote: true,
};

View File

@ -1,4 +1,4 @@
FROM node:lts-alpine AS web_build FROM node:12-alpine AS web_build
WORKDIR /agola-web WORKDIR /agola-web

View File

@ -1,5 +1,3 @@
module.exports = { module.exports = {
presets: [ presets: ['@vue/cli-plugin-babel/preset'],
'@vue/cli-plugin-babel/preset' };
]
}

View File

@ -1,13 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"*": [ "*": ["types/*"]
"types/*" }
] },
} "include": ["./src/**/*"]
},
"include": [
"./src/**/*"
]
} }

16
package-lock.json generated
View File

@ -1977,6 +1977,13 @@
"yallist": "^2.1.2" "yallist": "^2.1.2"
} }
}, },
"prettier": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
"dev": true,
"optional": true
},
"source-map": { "source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -9849,11 +9856,10 @@
"dev": true "dev": true
}, },
"prettier": { "prettier": {
"version": "1.19.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
"dev": true, "dev": true
"optional": true
}, },
"pretty-error": { "pretty-error": {
"version": "2.1.2", "version": "2.1.2",

View File

@ -29,6 +29,7 @@
"eslint": "^6.7.2", "eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"prettier": "2.5.1",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"tailwindcss": "^1.9.6", "tailwindcss": "^1.9.6",
"vue-template-compiler": "^2.6.12" "vue-template-compiler": "^2.6.12"

View File

@ -1,6 +1,3 @@
module.exports = { module.exports = {
"plugins": [ plugins: [require('tailwindcss')('tailwind.js'), require('autoprefixer')()],
require('tailwindcss')('tailwind.js'), };
require('autoprefixer')(),
]
}

View File

@ -1,7 +1,9 @@
<template> <template>
<div id="app"> <div id="app">
<nav class="bg-gray-800 p-3 text-white"> <nav class="bg-gray-800 p-3 text-white">
<div class="container flex items-center justify-between flex-wrap bg-gray-800"> <div
class="container flex items-center justify-between flex-wrap bg-gray-800"
>
<div class="mr-6"> <div class="mr-6">
<router-link <router-link
class="font-semibold flex items-center flex-shrink-0 text-xl tracking-tight" class="font-semibold flex items-center flex-shrink-0 text-xl tracking-tight"
@ -28,12 +30,12 @@
</div> </div>
<div <div
class="w-full block flex-grow lg:flex lg:items-center lg:w-auto" class="w-full block flex-grow lg:flex lg:items-center lg:w-auto"
:class="{'hidden' : !navActive}" :class="{ hidden: !navActive }"
> >
<div class="text-sm lg:flex-grow"></div> <div class="text-sm lg:flex-grow"></div>
<div v-if="user" class="relative mr-3"> <div v-if="user" class="relative mr-3">
<button <button
v-click-outside="() => createDropdownActive = false" v-click-outside="() => (createDropdownActive = false)"
@click="createDropdownActive = !createDropdownActive" @click="createDropdownActive = !createDropdownActive"
class="relative flex items-center focus:outline-none" class="relative flex items-center focus:outline-none"
> >
@ -49,7 +51,8 @@
<router-link <router-link
class="block px-4 py-2 hover:bg-blue-500 hover:text-white" class="block px-4 py-2 hover:bg-blue-500 hover:text-white"
to="/neworganization" to="/neworganization"
>New Organization</router-link> >New Organization</router-link
>
</li> </li>
</ul> </ul>
</div> </div>
@ -57,11 +60,11 @@
<div v-if="user" class="relative"> <div v-if="user" class="relative">
<div class="flex"> <div class="flex">
<button <button
v-click-outside="() => userDropdownActive = false" v-click-outside="() => (userDropdownActive = false)"
@click="userDropdownActive = !userDropdownActive" @click="userDropdownActive = !userDropdownActive"
class="relative flex items-center focus:outline-none" class="relative flex items-center focus:outline-none"
> >
{{user.username}} {{ user.username }}
<i class="mdi mdi-chevron-down"></i> <i class="mdi mdi-chevron-down"></i>
</button> </button>
</div> </div>
@ -72,7 +75,7 @@
<ul> <ul>
<li class="block px-4 py-2 border-b"> <li class="block px-4 py-2 border-b">
Logged as&nbsp; Logged as&nbsp;
<b>{{user.username}}</b> <b>{{ user.username }}</b>
</li> </li>
<li> <li>
<hr class="navbar-divider" /> <hr class="navbar-divider" />
@ -98,18 +101,30 @@
</div> </div>
</div> </div>
<div v-else class="navbar-item"> <div v-else class="navbar-item">
<router-link class="btn btn-blue" to="/register">Sign up</router-link> <router-link class="btn btn-blue" to="/register"
<router-link class="ml-2 btn btn-blue" to="/login">Login</router-link> >Sign up</router-link
>
<router-link class="ml-2 btn btn-blue" to="/login"
>Login</router-link
>
</div> </div>
</div> </div>
</div> </div>
</nav> </nav>
<div v-if="error" class="container h-screen" role="alert"> <div v-if="error" class="container h-screen" role="alert">
<div v-if="error" class="h-full flex justify-center items-center" role="alert"> <div
v-if="error"
class="h-full flex justify-center items-center"
role="alert"
>
<div v-if="error" class="w-full" role="alert"> <div v-if="error" class="w-full" role="alert">
<div class="bg-red-500 text-white font-bold rounded-t px-4 py-2">Error</div> <div class="bg-red-500 text-white font-bold rounded-t px-4 py-2">
<div class="border border-t-0 border-red-400 rounded-b bg-red-100 px-4 py-3 text-red-700"> Error
</div>
<div
class="border border-t-0 border-red-400 rounded-b bg-red-100 px-4 py-3 text-red-700"
>
<p class="mb-8">Failed to fetch data: {{ error }}</p> <p class="mb-8">Failed to fetch data: {{ error }}</p>
<button class="btn btn-red" @click="reload()">Retry</button> <button class="btn btn-red" @click="reload()">Retry</button>
</div> </div>
@ -123,48 +138,46 @@
</div> </div>
</template> </template>
<script> <script>
import * as vClickOutside from "v-click-outside-x"; import * as vClickOutside from 'v-click-outside-x';
import { mapGetters } from "vuex"; import { mapGetters } from 'vuex';
import { ownerSettingsLink } from "@/util/link.js"; import { ownerSettingsLink } from '@/util/link.js';
export default { export default {
name: "App", name: 'App',
directives: { directives: {
clickOutside: vClickOutside.directive clickOutside: vClickOutside.directive,
}, },
components: {}, components: {},
computed: { computed: {
...mapGetters(["error", "user"]) ...mapGetters(['error', 'user']),
}, },
data() { data() {
return { return {
routerActive: true, routerActive: true,
navActive: false, navActive: false,
userDropdownActive: false, userDropdownActive: false,
createDropdownActive: false createDropdownActive: false,
}; };
}, },
watch: { watch: {
$route: function() { $route: function () {
this.userDropdownActive = false; this.userDropdownActive = false;
this.createDropdownActive = false; this.createDropdownActive = false;
} },
}, },
// method to reload current view from https://github.com/vuejs/vue-router/issues/296#issuecomment-356530037 // method to reload current view from https://github.com/vuejs/vue-router/issues/296#issuecomment-356530037
methods: { methods: {
ownerSettingsLink: ownerSettingsLink, ownerSettingsLink: ownerSettingsLink,
reload() { reload() {
this.$store.dispatch("setError", null); this.$store.dispatch('setError', null);
this.routerActive = false; this.routerActive = false;
this.$nextTick(() => (this.routerActive = true)); this.$nextTick(() => (this.routerActive = true));
} },
} },
}; };
</script> </script>
<style lang="scss"> <style lang="scss"></style>
</style>

View File

@ -8,19 +8,21 @@
type="text" type="text"
placeholder="Organization name" placeholder="Organization name"
v-model="orgName" v-model="orgName"
> />
</div> </div>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label> <label>
<input type="checkbox" v-model="orgIsPrivate"> <input type="checkbox" v-model="orgIsPrivate" />
Private Private
</label> </label>
</div> </div>
<button <button
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
@click="createOrg()" @click="createOrg()"
>Create Organization</button> >
Create Organization
</button>
<div <div
v-if="createOrgError" v-if="createOrgError"
class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
@ -32,19 +34,19 @@
</template> </template>
<script> <script>
import { createOrganization } from "@/util/data.js"; import { createOrganization } from '@/util/data.js';
import { ownerLink } from "@/util/link.js"; import { ownerLink } from '@/util/link.js';
export default { export default {
components: {}, components: {},
name: "createorganization", name: 'createorganization',
props: {}, props: {},
data() { data() {
return { return {
createOrgError: null, createOrgError: null,
orgIsPrivate: false, orgIsPrivate: false,
orgName: null orgName: null,
}; };
}, },
methods: { methods: {
@ -54,9 +56,9 @@ export default {
async createOrg() { async createOrg() {
this.resetErrors(); this.resetErrors();
let visibility = "public"; let visibility = 'public';
if (this.orgIsPrivate) { if (this.orgIsPrivate) {
visibility = "private"; visibility = 'private';
} }
let { error } = await createOrganization(this.orgName, visibility); let { error } = await createOrganization(this.orgName, visibility);
@ -65,14 +67,11 @@ export default {
return; return;
} }
this.$router.push(ownerLink("org", this.orgName)); this.$router.push(ownerLink('org', this.orgName));
} },
}, },
created: async function() {} created: async function () {},
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -7,17 +7,18 @@
type="text" type="text"
placeholder="Project Name" placeholder="Project Name"
v-model="projectName" v-model="projectName"
> />
<div class="mb-4"> <div class="mb-4">
<label> <label>
<input type="checkbox" v-model="projectIsPrivate"> <input type="checkbox" v-model="projectIsPrivate" />
Private Private
</label> </label>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" v-model="pass_vars_to_forked_pr" /> <input type="checkbox" v-model="pass_vars_to_forked_pr" />
Pass variables to run even if triggered by PR from forked repo (DANGEROUS) Pass variables to run even if triggered by PR from forked repo
(DANGEROUS)
</label> </label>
</div> </div>
<div class="mb-3 flex items-center"> <div class="mb-3 flex items-center">
@ -31,31 +32,48 @@
v-for="(rs, index) in remoteSources" v-for="(rs, index) in remoteSources"
v-bind:key="rs.id" v-bind:key="rs.id"
:value="index" :value="index"
>{{ rs.name }}</option> >
{{ rs.name }}
</option>
</select> </select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2"> <div
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"> class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2"
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"></path> >
<svg
class="fill-current h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
>
<path
d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
></path>
</svg> </svg>
</div> </div>
</div> </div>
<button <button
class="ml-3 btn btn-blue" class="ml-3 btn btn-blue"
v-bind:class="{ 'spinner': fetchRemoteReposLoading }" v-bind:class="{ spinner: fetchRemoteReposLoading }"
:disabled="selectedRemoteSourceIndex == null" :disabled="selectedRemoteSourceIndex == null"
@click="fetchRemoteRepos()" @click="fetchRemoteRepos()"
>Fetch remote repositories</button> >
Fetch remote repositories
</button>
</div> </div>
<div v-if="remoteRepos.length"> <div v-if="remoteRepos.length">
<h4 class="text-xl">Available remote repositories</h4> <h4 class="text-xl">Available remote repositories</h4>
<remoterepos :remoterepos="remoteRepos" v-on:reposelected="repoSelected($event)"/> <remoterepos
:remoterepos="remoteRepos"
v-on:reposelected="repoSelected($event)"
/>
<button <button
class="btn btn-blue" class="btn btn-blue"
v-bind:class="{ 'spinner': createProjectLoading }" v-bind:class="{ spinner: createProjectLoading }"
:disabled="!createProjectButtonEnabled" :disabled="!createProjectButtonEnabled"
@click="createProject()" @click="createProject()"
>Create Project</button> >
Create Project
</button>
<div <div
v-if="createProjectError" v-if="createProjectError"
class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
@ -72,20 +90,20 @@ import {
fetchCurrentUser, fetchCurrentUser,
fetchRemoteSources, fetchRemoteSources,
createProject, createProject,
userRemoteRepos userRemoteRepos,
} from "@/util/data.js"; } from '@/util/data.js';
import { projectLink } from "@/util/link.js"; import { projectLink } from '@/util/link.js';
import remoterepos from "@/components/remoterepos.vue"; import remoterepos from '@/components/remoterepos.vue';
export default { export default {
components: { remoterepos }, components: { remoterepos },
name: "createproject", name: 'createproject',
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectgroupref: Array projectgroupref: Array,
}, },
data() { data() {
return { return {
@ -98,15 +116,15 @@ export default {
remoteSources: null, remoteSources: null,
remoteRepos: [], remoteRepos: [],
selectedRemoteSourceIndex: null, selectedRemoteSourceIndex: null,
projectName: "", projectName: '',
projectIsPrivate: false, projectIsPrivate: false,
remoteRepoPath: null remoteRepoPath: null,
}; };
}, },
computed: { computed: {
createProjectButtonEnabled: function() { createProjectButtonEnabled: function () {
return this.projectName.length && this.remoteRepoPath; return this.projectName.length && this.remoteRepoPath;
} },
}, },
watch: {}, watch: {},
methods: { methods: {
@ -143,7 +161,7 @@ export default {
let { data, error } = await userRemoteRepos(remoteSource.id); let { data, error } = await userRemoteRepos(remoteSource.id);
this.stopFetchRemoteReposLoading(); this.stopFetchRemoteReposLoading();
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.remoteRepos = data; this.remoteRepos = data;
@ -155,11 +173,11 @@ export default {
if (this.projectgroupref) { if (this.projectgroupref) {
refArray = [...refArray, ...this.projectgroupref]; refArray = [...refArray, ...this.projectgroupref];
} }
let parentref = refArray.join("/"); let parentref = refArray.join('/');
let visibility = "public"; let visibility = 'public';
if (this.projectIsPrivate) { if (this.projectIsPrivate) {
visibility = "private"; visibility = 'private';
} }
let remoteSource = this.remoteSources[this.selectedRemoteSourceIndex]; let remoteSource = this.remoteSources[this.selectedRemoteSourceIndex];
@ -186,12 +204,12 @@ export default {
this.$router.push( this.$router.push(
projectLink(this.ownertype, this.ownername, projectref) projectLink(this.ownertype, this.ownername, projectref)
); );
} },
}, },
created: async function() { created: async function () {
let { data, error } = await fetchCurrentUser(); let { data, error } = await fetchCurrentUser();
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.user = data; this.user = data;
@ -199,7 +217,7 @@ export default {
// TODO(sgotti) filter only remote source where the user has a linked account // TODO(sgotti) filter only remote source where the user has a linked account
({ data, error } = await fetchRemoteSources()); ({ data, error } = await fetchRemoteSources());
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
@ -215,10 +233,8 @@ export default {
} }
this.remoteSources = remoteSources; this.remoteSources = remoteSources;
} }
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -5,9 +5,11 @@
<button <button
@click="clicked" @click="clicked"
class="relative flex items-center focus:outline-none bg-green-500 hover:bg-green-600 text-white font-semibold hover:text-white py-2 px-4 border border-green-700 rounded rounded-r-none" class="relative flex items-center focus:outline-none bg-green-500 hover:bg-green-600 text-white font-semibold hover:text-white py-2 px-4 border border-green-700 rounded rounded-r-none"
>{{ buttonValue }}</button> >
{{ buttonValue }}
</button>
<button <button
v-click-outside="() => dropdownActive = false" v-click-outside="() => (dropdownActive = false)"
@click="dropdownActive = !dropdownActive" @click="dropdownActive = !dropdownActive"
class="relative flex items-center focus:outline-none bg-green-500 hover:bg-green-600 text-white font-semibold hover:text-white py-2 px-4 border border-l-0 border-green-700 rounded rounded-l-none" class="relative flex items-center focus:outline-none bg-green-500 hover:bg-green-600 text-white font-semibold hover:text-white py-2 px-4 border border-l-0 border-green-700 rounded rounded-l-none"
> >
@ -24,14 +26,16 @@
href="#" href="#"
class="block px-4 py-2 hover:bg-blue-500 hover:text-white" class="block px-4 py-2 hover:bg-blue-500 hover:text-white"
@click="setButton('project')" @click="setButton('project')"
>New Project</a> >New Project</a
>
</li> </li>
<li> <li>
<a <a
href="#" href="#"
class="block px-4 py-2 hover:bg-blue-500 hover:text-white" class="block px-4 py-2 hover:bg-blue-500 hover:text-white"
@click="setButton('projectgroup')" @click="setButton('projectgroup')"
>New Project Group</a> >New Project Group</a
>
</li> </li>
</ul> </ul>
</div> </div>
@ -40,28 +44,28 @@
</template> </template>
<script> <script>
import * as vClickOutside from "v-click-outside-x"; import * as vClickOutside from 'v-click-outside-x';
export default { export default {
components: {}, components: {},
directives: { directives: {
clickOutside: vClickOutside.directive clickOutside: vClickOutside.directive,
}, },
name: "createprojectbutton", name: 'createprojectbutton',
props: {}, props: {},
data() { data() {
return { return {
dropdownActive: false, dropdownActive: false,
type: "project" type: 'project',
}; };
}, },
computed: { computed: {
buttonValue: function() { buttonValue: function () {
if (this.type == "project") { if (this.type == 'project') {
return "New Project"; return 'New Project';
} }
return "New Project Group"; return 'New Project Group';
} },
}, },
methods: { methods: {
setButton(type) { setButton(type) {
@ -69,12 +73,10 @@ export default {
this.dropdownActive = false; this.dropdownActive = false;
}, },
clicked() { clicked() {
this.$emit("click", this.type); this.$emit('click', this.type);
} },
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -7,20 +7,22 @@
type="text" type="text"
placeholder="Project Group Name" placeholder="Project Group Name"
v-model="projectGroupName" v-model="projectGroupName"
> />
<div class="mb-4"> <div class="mb-4">
<label> <label>
<input type="checkbox" v-model="projectGroupIsPrivate"> <input type="checkbox" v-model="projectGroupIsPrivate" />
Private Private
</label> </label>
</div> </div>
<button <button
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
v-bind:class="{ 'spinner': createProjectGroupLoading }" v-bind:class="{ spinner: createProjectGroupLoading }"
:disabled="!createProjectGroupButtonEnabled" :disabled="!createProjectGroupButtonEnabled"
@click="createProjectGroup()" @click="createProjectGroup()"
>Create ProjectGroup</button> >
Create ProjectGroup
</button>
<div <div
v-if="createProjectGroupError" v-if="createProjectGroupError"
class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
@ -32,31 +34,31 @@
</template> </template>
<script> <script>
import { createProjectGroup } from "@/util/data.js"; import { createProjectGroup } from '@/util/data.js';
import { projectGroupLink } from "@/util/link.js"; import { projectGroupLink } from '@/util/link.js';
export default { export default {
components: {}, components: {},
name: "createprojectgroup", name: 'createprojectgroup',
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectgroupref: Array projectgroupref: Array,
}, },
data() { data() {
return { return {
createProjectGroupError: null, createProjectGroupError: null,
createProjectGroupLoading: false, createProjectGroupLoading: false,
createProjectGroupLoadingTimeout: null, createProjectGroupLoadingTimeout: null,
projectGroupName: "", projectGroupName: '',
projectGroupIsPrivate: false projectGroupIsPrivate: false,
}; };
}, },
computed: { computed: {
createProjectGroupButtonEnabled: function() { createProjectGroupButtonEnabled: function () {
return this.projectGroupName.length; return this.projectGroupName.length;
} },
}, },
methods: { methods: {
resetErrors() { resetErrors() {
@ -78,11 +80,11 @@ export default {
if (this.projectgroupref) { if (this.projectgroupref) {
refArray = [...refArray, ...this.projectgroupref]; refArray = [...refArray, ...this.projectgroupref];
} }
let parentref = refArray.join("/"); let parentref = refArray.join('/');
let visibility = "public"; let visibility = 'public';
if (this.projectGroupIsPrivate) { if (this.projectGroupIsPrivate) {
visibility = "private"; visibility = 'private';
} }
this.startProjectGroupLoading(); this.startProjectGroupLoading();
@ -104,12 +106,9 @@ export default {
this.$router.push( this.$router.push(
projectGroupLink(this.ownertype, this.ownername, projectgroupref) projectGroupLink(this.ownertype, this.ownername, projectgroupref)
); );
} },
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -12,17 +12,31 @@
</h1> </h1>
<form <form
class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4" class="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
@submit.prevent="$emit('createSource', { token, name, type, clientId, clientSecret, url, skipVerify, sshHostKey, skipSshHostKeyCheck })" @submit.prevent="
$emit('createSource', {
token,
name,
type,
clientId,
clientSecret,
url,
skipVerify,
sshHostKey,
skipSshHostKeyCheck,
})
"
> >
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-bold mb-2" for="token">Agola Admin token</label> <label class="block text-sm font-bold mb-2" for="token"
>Agola Admin token</label
>
<input <input
class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline" class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="token" id="token"
type="password" type="password"
placeholder="Agola Admin token" placeholder="Agola Admin token"
v-model="token" v-model="token"
> />
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-bold mb-2" for="type">Type</label> <label class="block text-sm font-bold mb-2" for="type">Type</label>
@ -37,7 +51,9 @@
</select> </select>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-bold mb-2" for="name">Source name</label> <label class="block text-sm font-bold mb-2" for="name"
>Source name</label
>
<input <input
class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline" class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="name" id="name"
@ -45,17 +61,19 @@
type="text" type="text"
placeholder="Source name (only numbers, letters and -)" placeholder="Source name (only numbers, letters and -)"
v-model="name" v-model="name"
> />
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-bold mb-2" for="url">Source API URL</label> <label class="block text-sm font-bold mb-2" for="url"
>Source API URL</label
>
<input <input
class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline" class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="url" id="url"
type="text" type="text"
placeholder="API URL" placeholder="API URL"
v-model="url" v-model="url"
> />
</div> </div>
<div class="mb-4 flex flex-row"> <div class="mb-4 flex flex-row">
<input <input
@ -63,11 +81,15 @@
type="checkbox" type="checkbox"
class="h-6 w-6 border border-gray-300 rounded-md checked:bg-blue-600 checked:border-transparent focus:outline-none" class="h-6 w-6 border border-gray-300 rounded-md checked:bg-blue-600 checked:border-transparent focus:outline-none"
v-model="skipVerify" v-model="skipVerify"
/>
<label class="text-sm mt-auto font-bold mx-2" for="skip_verify"
>Skip TLS certificate validation</label
> >
<label class="text-sm mt-auto font-bold mx-2" for="skip_verify">Skip TLS certificate validation</label>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-bold mb-2" for="ssh_key">Source Public SSH Key</label> <label class="block text-sm font-bold mb-2" for="ssh_key"
>Source Public SSH Key</label
>
<input <input
class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline" class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="ssh_key" id="ssh_key"
@ -75,7 +97,7 @@
autocomplete="off" autocomplete="off"
placeholder="Public SSH key" placeholder="Public SSH key"
v-model="sshHostKey" v-model="sshHostKey"
> />
</div> </div>
<div class="mb-4 flex flex-row"> <div class="mb-4 flex flex-row">
<input <input
@ -83,46 +105,53 @@
type="checkbox" type="checkbox"
class="h-6 w-6 border border-gray-300 rounded-md checked:bg-blue-600 checked:border-transparent focus:outline-none" class="h-6 w-6 border border-gray-300 rounded-md checked:bg-blue-600 checked:border-transparent focus:outline-none"
v-model="skipSshHostKeyCheck" v-model="skipSshHostKeyCheck"
/>
<label class="text-sm mt-auto font-bold mx-2" for="skip_ssh_key_check"
>Skip SSH host key check</label
> >
<label class="text-sm mt-auto font-bold mx-2" for="skip_ssh_key_check">Skip SSH host key check</label>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-bold mb-2" for="client_id">Source client ID</label> <label class="block text-sm font-bold mb-2" for="client_id"
>Source client ID</label
>
<input <input
class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline" class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="client_id" id="client_id"
type="text" type="text"
placeholder="Client ID" placeholder="Client ID"
v-model="clientId" v-model="clientId"
> />
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-bold mb-2" for="client_secret">Source client secret</label> <label class="block text-sm font-bold mb-2" for="client_secret"
>Source client secret</label
>
<input <input
class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline" class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="client_secret" id="client_secret"
type="password" type="password"
placeholder="Client secret" placeholder="Client secret"
v-model="clientSecret" v-model="clientSecret"
> />
</div> </div>
<div class="flex justify-center"> <div class="flex justify-center">
<button <button
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="submit" type="submit"
>Create</button> >
Create
</button>
</div> </div>
</form> </form>
</div> </div>
</template> </template>
<script> <script>
import { GITHUB_SSH_KEY, GITHUB_API_URL } from "@/util/data" import { GITHUB_SSH_KEY, GITHUB_API_URL } from '@/util/data';
export default { export default {
name: "CreateSourceForm", name: 'CreateSourceForm',
data: function() { data: function () {
return { return {
name: null, name: null,
token: null, token: null,
@ -136,8 +165,8 @@ export default {
}; };
}, },
watch: { watch: {
type: function(value) { type: function (value) {
if (value === "github") { if (value === 'github') {
this.url = GITHUB_API_URL; this.url = GITHUB_API_URL;
this.sshHostKey = GITHUB_SSH_KEY; this.sshHostKey = GITHUB_SSH_KEY;
this.skipVerify = false; this.skipVerify = false;
@ -147,5 +176,3 @@ export default {
}, },
}; };
</script> </script>

View File

@ -43,18 +43,18 @@
</template> </template>
<script> <script>
import { apiurl, fetch } from "@/util/auth"; import { apiurl, fetch } from '@/util/auth';
import AnsiUp from "ansi_up"; import AnsiUp from 'ansi_up';
export default { export default {
name: "Log", name: 'Log',
props: { props: {
show: Boolean, show: Boolean,
runid: String, runid: String,
taskid: String, taskid: String,
setup: Boolean, setup: Boolean,
step: Number, step: Number,
stepphase: String stepphase: String,
}, },
computed: {}, computed: {},
data() { data() {
@ -65,7 +65,7 @@ export default {
fetchAbort: null, fetchAbort: null,
items: [], items: [],
lastitem: "", lastitem: '',
lines: [], lines: [],
formatter: formatter, formatter: formatter,
es: null, es: null,
@ -73,7 +73,7 @@ export default {
streaming: false, streaming: false,
done: false, done: false,
logExists: null, logExists: null,
error: null error: null,
}; };
}, },
methods: { methods: {
@ -83,7 +83,7 @@ export default {
} }
let follow = false; let follow = false;
if (this.stepphase == "running") { if (this.stepphase == 'running') {
follow = true; follow = true;
} }
@ -94,14 +94,14 @@ export default {
this.logExists = null; this.logExists = null;
this.error = null; this.error = null;
let path = "/logs?runID=" + this.runid + "&taskID=" + this.taskid; let path = '/logs?runID=' + this.runid + '&taskID=' + this.taskid;
if (this.setup) { if (this.setup) {
path += "&setup"; path += '&setup';
} else { } else {
path += "&step=" + this.step; path += '&step=' + this.step;
} }
if (follow) { if (follow) {
path += "&follow"; path += '&follow';
} }
try { try {
@ -111,7 +111,7 @@ export default {
this.streaming = true; this.streaming = true;
const reader = res.body.getReader(); const reader = res.body.getReader();
let lastline = ""; let lastline = '';
let j = 0; let j = 0;
for (;;) { for (;;) {
let { done, value } = await reader.read(); let { done, value } = await reader.read();
@ -122,12 +122,12 @@ export default {
return; return;
} }
let data = new TextDecoder("utf-8").decode(value, { stream: true }); let data = new TextDecoder('utf-8').decode(value, { stream: true });
let part = ""; let part = '';
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
let c = data.charAt(i); let c = data.charAt(i);
if (c == "\r") { if (c == '\r') {
// replace lastline from start, simulating line feed (go to start of line) // replace lastline from start, simulating line feed (go to start of line)
// this isn't perfect since the previous line contents could have // this isn't perfect since the previous line contents could have
// been written using different colors and this will lose them but // been written using different colors and this will lose them but
@ -136,17 +136,17 @@ export default {
lastline.slice(0, j) + part + lastline.slice(j + part.length); lastline.slice(0, j) + part + lastline.slice(j + part.length);
j = 0; j = 0;
this.lastitem = this.formatter.ansi_to_html(lastline); this.lastitem = this.formatter.ansi_to_html(lastline);
part = ""; part = '';
} else if (c == "\n") { } else if (c == '\n') {
lastline = lastline =
lastline.slice(0, j) + part + lastline.slice(j + part.length); lastline.slice(0, j) + part + lastline.slice(j + part.length);
j += part.length; j += part.length;
this.lastitem = this.formatter.ansi_to_html(lastline); this.lastitem = this.formatter.ansi_to_html(lastline);
this.items.push(this.lastitem); this.items.push(this.lastitem);
this.lastitem = ""; this.lastitem = '';
lastline = ""; lastline = '';
j = 0; j = 0;
part = ""; part = '';
} else { } else {
part += c; part += c;
} }
@ -174,10 +174,10 @@ export default {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
this.fetchAbort = new AbortController(); this.fetchAbort = new AbortController();
} },
}, },
watch: { watch: {
show: function(post, pre) { show: function (post, pre) {
if (pre == false && post == true) { if (pre == false && post == true) {
this.abortFetch(); this.abortFetch();
this.fetch(); this.fetch();
@ -186,23 +186,23 @@ export default {
this.abortFetch(); this.abortFetch();
} }
}, },
stepphase: function(post) { stepphase: function (post) {
if (!this.show) { if (!this.show) {
return; return;
} }
if (this.fetching) { if (this.fetching) {
return; return;
} }
if (post == "running") { if (post == 'running') {
this.abortFetch(); this.abortFetch();
this.getLogs(true); this.getLogs(true);
} else { } else {
this.abortFetch(); this.abortFetch();
this.getLogs(false); this.getLogs(false);
} }
} },
}, },
created: function() { created: function () {
this.fetchAbort = new AbortController(); this.fetchAbort = new AbortController();
if (this.show) { if (this.show) {
@ -217,6 +217,6 @@ export default {
if (this.es !== null) { if (this.es !== null) {
this.es.close(); this.es.close();
} }
} },
}; };
</script> </script>

View File

@ -5,50 +5,53 @@
@submit.prevent="$emit('login', { username, password })" @submit.prevent="$emit('login', { username, password })"
> >
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-bold mb-2" for="username">Username</label> <label class="block text-sm font-bold mb-2" for="username"
>Username</label
>
<input <input
class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline" class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="username" id="username"
type="text" type="text"
placeholder="Username" placeholder="Username"
v-model="username" v-model="username"
> />
</div> </div>
<div class="mb-6"> <div class="mb-6">
<label class="block text-sm font-bold mb-2" for="password">Password</label> <label class="block text-sm font-bold mb-2" for="password"
>Password</label
>
<input <input
class="appearance-none border rounded w-full py-2 px-3 mb-3 leading-tight focus:outline-none focus:shadow-outline" class="appearance-none border rounded w-full py-2 px-3 mb-3 leading-tight focus:outline-none focus:shadow-outline"
id="password" id="password"
type="password" type="password"
placeholder="******************" placeholder="******************"
v-model="password" v-model="password"
> />
</div> </div>
<div class="flex justify-center"> <div class="flex justify-center">
<button <button
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="submit" type="submit"
>{{action}} with {{name}}</button> >
{{ action }} with {{ name }}
</button>
</div> </div>
</form> </form>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: "LoginForm", name: 'LoginForm',
props: { props: {
action: String, action: String,
name: String name: String,
}, },
data: function() { data: function () {
return { return {
username: null, username: null,
password: null password: null,
}; };
} },
}; };
</script> </script>

View File

@ -3,8 +3,8 @@
<h4 class="mb-3 text-xl">Organization Members</h4> <h4 class="mb-3 text-xl">Organization Members</h4>
<ul v-if="members.length"> <ul v-if="members.length">
<li class="flex" v-for="member in members" v-bind:key="member.user.id"> <li class="flex" v-for="member in members" v-bind:key="member.user.id">
<span class="w-1/2 font-bold">{{member.user.username}}</span> <span class="w-1/2 font-bold">{{ member.user.username }}</span>
<span class="w-1/2">{{member.role}}</span> <span class="w-1/2">{{ member.role }}</span>
</li> </li>
</ul> </ul>
<div v-else>No Members</div> <div v-else>No Members</div>
@ -12,39 +12,38 @@
</template> </template>
<script> <script>
import { fetchOrgMembers } from "@/util/data.js"; import { fetchOrgMembers } from '@/util/data.js';
export default { export default {
components: {}, components: {},
name: "orgmembers", name: 'orgmembers',
props: { props: {
orgname: String orgname: String,
}, },
data() { data() {
return { return {
members: [] members: [],
}; };
}, },
watch: { watch: {
$route: async function() { $route: async function () {
this.fetchOrgMembers(this.orgname); this.fetchOrgMembers(this.orgname);
} },
}, },
methods: { methods: {
async fetchOrgMembers(orgname) { async fetchOrgMembers(orgname) {
let { data, error } = await fetchOrgMembers(orgname); let { data, error } = await fetchOrgMembers(orgname);
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.members = data.members; this.members = data.members;
} },
}, },
created: function() { created: function () {
this.fetchOrgMembers(this.orgname); this.fetchOrgMembers(this.orgname);
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -2,57 +2,67 @@
<nav class="mb-4 bg-grey-light rounded font-sans w-full"> <nav class="mb-4 bg-grey-light rounded font-sans w-full">
<ol class="list-reset flex text-grey-dark"> <ol class="list-reset flex text-grey-dark">
<li> <li>
<a>{{ownertype}}</a> <a>{{ ownertype }}</a>
</li> </li>
<li> <li>
<span class="mx-2">/</span> <span class="mx-2">/</span>
</li> </li>
<li> <li>
<router-link :to="ownerLink(ownertype, ownername)">{{ownername}}</router-link> <router-link :to="ownerLink(ownertype, ownername)">{{
ownername
}}</router-link>
</li> </li>
<li v-for="(ref, i) in projectref" v-bind:key="i"> <li v-for="(ref, i) in projectref" v-bind:key="i">
<span class="mx-2">/</span> <span class="mx-2">/</span>
<router-link <router-link
v-if="i+1 < projectref.length" v-if="i + 1 < projectref.length"
:to="projectGroupLink(ownertype, ownername, projectref.slice(0, i+1))" :to="
>{{ref}}</router-link> projectGroupLink(ownertype, ownername, projectref.slice(0, i + 1))
"
>{{ ref }}</router-link
>
<router-link <router-link
v-else v-else
:to="projectLink(ownertype, ownername, projectref.slice(0, i+1))" :to="projectLink(ownertype, ownername, projectref.slice(0, i + 1))"
>{{ref}}</router-link> >{{ ref }}</router-link
>
</li> </li>
<li v-for="(ref, i) in projectgroupref" v-bind:key="i"> <li v-for="(ref, i) in projectgroupref" v-bind:key="i">
<span class="mx-2">/</span> <span class="mx-2">/</span>
<router-link <router-link
:to="projectGroupLink(ownertype, ownername, projectgroupref.slice(0, i+1))" :to="
>{{ref}}</router-link> projectGroupLink(
ownertype,
ownername,
projectgroupref.slice(0, i + 1)
)
"
>{{ ref }}</router-link
>
</li> </li>
</ol> </ol>
</nav> </nav>
</template> </template>
<script> <script>
import { ownerLink, projectLink, projectGroupLink } from "@/util/link.js"; import { ownerLink, projectLink, projectGroupLink } from '@/util/link.js';
export default { export default {
name: "projbreadcrumbs", name: 'projbreadcrumbs',
components: {}, components: {},
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectref: Array, projectref: Array,
projectgroupref: Array projectgroupref: Array,
}, },
methods: { methods: {
ownerLink: ownerLink, ownerLink: ownerLink,
projectLink: projectLink, projectLink: projectLink,
projectGroupLink: projectGroupLink projectGroupLink: projectGroupLink,
} },
}; };
</script> </script>
<style scoped lang="scss"></style>
<style scoped lang="scss">
</style>

View File

@ -20,7 +20,9 @@
Private Private
</label> </label>
</div> </div>
<button class="btn btn-blue" @click="updateProjectGroup()">Update</button> <button class="btn btn-blue" @click="updateProjectGroup()">
Update
</button>
<div <div
v-if="updateProjectGroupError" v-if="updateProjectGroupError"
@ -35,14 +37,22 @@
<div class="panel"> <div class="panel">
<p class="panel-title">Secrets</p> <p class="panel-title">Secrets</p>
<div class="p-4"> <div class="p-4">
<projectsecrets :secrets="secrets" :allsecrets="allsecrets" type="projectgroup" /> <projectsecrets
:secrets="secrets"
:allsecrets="allsecrets"
type="projectgroup"
/>
</div> </div>
</div> </div>
<div class="panel"> <div class="panel">
<p class="panel-title">Variables</p> <p class="panel-title">Variables</p>
<div class="p-4"> <div class="p-4">
<projectvars :variables="variables" :allvariables="allvariables" type="projectgroup" /> <projectvars
:variables="variables"
:allvariables="allvariables"
type="projectgroup"
/>
</div> </div>
</div> </div>
@ -56,16 +66,13 @@
> >
<p> <p>
This operation This operation
<strong>CANNOT</strong> be undone. <strong>CANNOT</strong> be undone. This operation will remove
This operation will remove <strong>{{ projectGroupPath }}</strong>
<strong>{{projectGroupPath}}</strong>
</p> </p>
</div> </div>
<label class="block mb-2"> <label class="block mb-2">
Please type the project group name for confirmation: Please type the project group name for confirmation:
<span <span class="text-red-500 font-bold">{{ projectGroupName }}</span>
class="text-red-500 font-bold"
>{{ projectGroupName }}</span>
</label> </label>
<div class="mb-4"> <div class="mb-4">
<input <input
@ -79,7 +86,9 @@
class="btn btn-red" class="btn btn-red"
@click="deleteProjectGroup()" @click="deleteProjectGroup()"
:disabled="!deleteButtonEnabled" :disabled="!deleteButtonEnabled"
>Delete Project Group</button> >
Delete Project Group
</button>
</div> </div>
</div> </div>
<div <div
@ -98,21 +107,21 @@ import {
fetchSecrets, fetchSecrets,
fetchVariables, fetchVariables,
updateProjectGroup, updateProjectGroup,
deleteProjectGroup deleteProjectGroup,
} from "@/util/data.js"; } from '@/util/data.js';
import { projectGroupLink } from "@/util/link.js"; import { projectGroupLink } from '@/util/link.js';
import projectsecrets from "@/components/projectsecrets"; import projectsecrets from '@/components/projectsecrets';
import projectvars from "@/components/projectvars"; import projectvars from '@/components/projectvars';
export default { export default {
components: { projectsecrets, projectvars }, components: { projectsecrets, projectvars },
name: "projectgroupsettings", name: 'projectgroupsettings',
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectgroupref: Array projectgroupref: Array,
}, },
data() { data() {
return { return {
@ -124,24 +133,24 @@ export default {
allsecrets: [], allsecrets: [],
variables: [], variables: [],
allvariables: [], allvariables: [],
projectGroupNameToDelete: "" projectGroupNameToDelete: '',
}; };
}, },
computed: { computed: {
projectGroupName: function() { projectGroupName: function () {
return this.projectgroupref[this.projectgroupref.length - 1]; return this.projectgroupref[this.projectgroupref.length - 1];
}, },
projectGroupPath: function() { projectGroupPath: function () {
return ["", this.ownertype, this.ownername, ...this.projectgroupref].join( return ['', this.ownertype, this.ownername, ...this.projectgroupref].join(
"/" '/'
); );
}, },
deleteButtonEnabled: function() { deleteButtonEnabled: function () {
return this.projectGroupNameToDelete == this.projectGroupName; return this.projectGroupNameToDelete == this.projectGroupName;
}, },
isRootProjectGroup() { isRootProjectGroup() {
return this.projectgroupref.length == 0; return this.projectgroupref.length == 0;
} },
}, },
methods: { methods: {
resetErrors() { resetErrors() {
@ -154,12 +163,12 @@ export default {
let projectgroupref = [ let projectgroupref = [
this.ownertype, this.ownertype,
this.ownername, this.ownername,
...this.projectgroupref ...this.projectgroupref,
].join("/"); ].join('/');
let visibility = "public"; let visibility = 'public';
if (this.projectGroupIsPrivate) { if (this.projectGroupIsPrivate) {
visibility = "private"; visibility = 'private';
} }
let { error } = await updateProjectGroup( let { error } = await updateProjectGroup(
projectgroupref, projectgroupref,
@ -175,8 +184,8 @@ export default {
let projectgroupref = [ let projectgroupref = [
this.ownertype, this.ownertype,
this.ownername, this.ownername,
...this.projectgroupref ...this.projectgroupref,
].join("/"); ].join('/');
if (this.projectGroupNameToDelete == this.projectGroupName) { if (this.projectGroupNameToDelete == this.projectGroupName) {
let { error } = await deleteProjectGroup(projectgroupref); let { error } = await deleteProjectGroup(projectgroupref);
@ -192,70 +201,68 @@ export default {
) )
); );
} }
} },
}, },
created: async function() { created: async function () {
let projectgroupref = [ let projectgroupref = [
this.ownertype, this.ownertype,
this.ownername, this.ownername,
...this.projectgroupref ...this.projectgroupref,
].join("/"); ].join('/');
let { data, error } = await fetchProjectGroup(projectgroupref); let { data, error } = await fetchProjectGroup(projectgroupref);
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.projectGroup = data; this.projectGroup = data;
this.projectGroupIsPrivate = this.projectGroup.visibility == "private"; this.projectGroupIsPrivate = this.projectGroup.visibility == 'private';
({ data, error } = await fetchSecrets( ({ data, error } = await fetchSecrets(
"projectgroup", 'projectgroup',
projectgroupref, projectgroupref,
false false
)); ));
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.secrets = data; this.secrets = data;
({ data, error } = await fetchSecrets( ({ data, error } = await fetchSecrets(
"projectgroup", 'projectgroup',
projectgroupref, projectgroupref,
true true
)); ));
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.allsecrets = data; this.allsecrets = data;
({ data, error } = await fetchVariables( ({ data, error } = await fetchVariables(
"projectgroup", 'projectgroup',
projectgroupref, projectgroupref,
false false
)); ));
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.variables = data; this.variables = data;
({ data, error } = await fetchVariables( ({ data, error } = await fetchVariables(
"projectgroup", 'projectgroup',
projectgroupref, projectgroupref,
true true
)); ));
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.allvariables = data; this.allvariables = data;
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -2,13 +2,20 @@
<div> <div>
<h4 class="text-xl my-3">Projects</h4> <h4 class="text-xl my-3">Projects</h4>
<div v-if="fetchProjectsLoading" class="ml-6 flex w-48"> <div v-if="fetchProjectsLoading" class="ml-6 flex w-48">
<div v-bind:class="{ 'spinner': fetchProjectsLoading }"></div> <div v-bind:class="{ spinner: fetchProjectsLoading }"></div>
</div> </div>
<ul v-else-if="projects.length > 0"> <ul v-else-if="projects.length > 0">
<li class="mb-2 border rounded-l" v-for="project in projects" v-bind:key="project.id"> <li
class="mb-2 border rounded-l"
v-for="project in projects"
v-bind:key="project.id"
>
<div class="pl-4 py-4 flex items-center"> <div class="pl-4 py-4 flex items-center">
<router-link class="item" :to="projectLink(ownertype, ownername, ref(project.name))"> <router-link
<span class="font-bold">{{project.name}}</span> class="item"
:to="projectLink(ownertype, ownername, ref(project.name))"
>
<span class="font-bold">{{ project.name }}</span>
</router-link> </router-link>
</div> </div>
</li> </li>
@ -19,7 +26,7 @@
<h4 class="text-xl my-3">Project Groups</h4> <h4 class="text-xl my-3">Project Groups</h4>
<div v-if="fetchProjectGroupsLoading" class="ml-6 flex w-48"> <div v-if="fetchProjectGroupsLoading" class="ml-6 flex w-48">
<div v-bind:class="{ 'spinner': fetchProjectGroupsLoading }"></div> <div v-bind:class="{ spinner: fetchProjectGroupsLoading }"></div>
</div> </div>
<ul v-else-if="projectgroups.length > 0"> <ul v-else-if="projectgroups.length > 0">
<li <li
@ -32,7 +39,7 @@
class="item" class="item"
:to="projectGroupLink(ownertype, ownername, ref(projectgroup.name))" :to="projectGroupLink(ownertype, ownername, ref(projectgroup.name))"
> >
<span class="font-bold">{{projectgroup.name}}</span> <span class="font-bold">{{ projectgroup.name }}</span>
</router-link> </router-link>
</div> </div>
</li> </li>
@ -44,18 +51,18 @@
<script> <script>
import { import {
fetchProjectGroupProjects, fetchProjectGroupProjects,
fetchProjectGroupSubgroups fetchProjectGroupSubgroups,
} from "@/util/data.js"; } from '@/util/data.js';
import { projectLink, projectGroupLink } from "@/util/link.js"; import { projectLink, projectGroupLink } from '@/util/link.js';
export default { export default {
components: {}, components: {},
name: "Projects", name: 'Projects',
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectgroupref: Array projectgroupref: Array,
}, },
data() { data() {
return { return {
@ -65,18 +72,18 @@ export default {
fetchProjectsLoading: false, fetchProjectsLoading: false,
projects: [], projects: [],
projectgroups: [] projectgroups: [],
}; };
}, },
watch: { watch: {
$route: async function() { $route: async function () {
if (this.fetchAbort) { if (this.fetchAbort) {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
this.fetchAbort = new AbortController(); this.fetchAbort = new AbortController();
this.fetchProjects(this.ownertype, this.ownername); this.fetchProjects(this.ownertype, this.ownername);
this.fetchProjectGroups(this.ownertype, this.ownername); this.fetchProjectGroups(this.ownertype, this.ownername);
} },
}, },
methods: { methods: {
startFetchProjectsLoading() { startFetchProjectsLoading() {
@ -107,7 +114,7 @@ export default {
this.startFetchProjectsLoading(); this.startFetchProjectsLoading();
let { data, error, aborted } = await fetchProjectGroupProjects( let { data, error, aborted } = await fetchProjectGroupProjects(
projectgroupref.join("/"), projectgroupref.join('/'),
this.fetchAbort.signal this.fetchAbort.signal
); );
this.stopFetchProjectsLoading(); this.stopFetchProjectsLoading();
@ -115,7 +122,7 @@ export default {
return; return;
} }
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.projects = data; this.projects = data;
@ -127,7 +134,7 @@ export default {
} }
this.startFetchProjectGroupsLoading(); this.startFetchProjectGroupsLoading();
let { data, error, aborted } = await fetchProjectGroupSubgroups( let { data, error, aborted } = await fetchProjectGroupSubgroups(
projectgroupref.join("/"), projectgroupref.join('/'),
this.fetchAbort.signal this.fetchAbort.signal
); );
this.stopFetchProjectGroupsLoading(); this.stopFetchProjectGroupsLoading();
@ -135,15 +142,15 @@ export default {
return; return;
} }
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.projectgroups = data; this.projectgroups = data;
}, },
projectLink: projectLink, projectLink: projectLink,
projectGroupLink: projectGroupLink projectGroupLink: projectGroupLink,
}, },
created: function() { created: function () {
this.fetchAbort = new AbortController(); this.fetchAbort = new AbortController();
this.fetchProjects(this.ownertype, this.ownername); this.fetchProjects(this.ownertype, this.ownername);
this.fetchProjectGroups(this.ownertype, this.ownername); this.fetchProjectGroups(this.ownertype, this.ownername);
@ -152,9 +159,8 @@ export default {
if (this.fetchAbort) { if (this.fetchAbort) {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -7,33 +7,34 @@
<hr class="my-6 border-t" /> <hr class="my-6 border-t" />
<h5 class="text-2xl">All secrets (local and inherited)</h5> <h5 class="text-2xl">All secrets (local and inherited)</h5>
<secrets v-if="allsecrets.length" :secrets="allsecrets" :showparentpath="true" /> <secrets
v-if="allsecrets.length"
:secrets="allsecrets"
:showparentpath="true"
/>
<span v-else>No secrets</span> <span v-else>No secrets</span>
</div> </div>
</template> </template>
<script> <script>
import secrets from "@/components/secrets"; import secrets from '@/components/secrets';
export default { export default {
components: { secrets }, components: { secrets },
name: "projectsecrets", name: 'projectsecrets',
props: { props: {
secrets: Array, secrets: Array,
allsecrets: Array, allsecrets: Array,
type: String type: String,
}, },
computed: { computed: {
typetitle() { typetitle() {
if (this.type == "project") return "Project"; if (this.type == 'project') return 'Project';
if (this.type == "projectgroup") return "Project group"; if (this.type == 'projectgroup') return 'Project group';
return ""; return '';
} },
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -21,7 +21,8 @@
<div class="mb-4"> <div class="mb-4">
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" v-model="project.pass_vars_to_forked_pr" /> <input type="checkbox" v-model="project.pass_vars_to_forked_pr" />
Pass variables to run even if triggered by PR from forked repo (DANGEROUS) Pass variables to run even if triggered by PR from forked repo
(DANGEROUS)
</label> </label>
</div> </div>
<button class="btn btn-blue" @click="updateProject()">Update</button> <button class="btn btn-blue" @click="updateProject()">Update</button>
@ -38,14 +39,22 @@
<div class="panel"> <div class="panel">
<p class="panel-title">Secrets</p> <p class="panel-title">Secrets</p>
<div class="p-4"> <div class="p-4">
<projectsecrets :secrets="secrets" :allsecrets="allsecrets" type="project" /> <projectsecrets
:secrets="secrets"
:allsecrets="allsecrets"
type="project"
/>
</div> </div>
</div> </div>
<div class="panel"> <div class="panel">
<p class="panel-title">Variables</p> <p class="panel-title">Variables</p>
<div class="p-4"> <div class="p-4">
<projectvars :variables="variables" :allvariables="allvariables" type="project" /> <projectvars
:variables="variables"
:allvariables="allvariables"
type="project"
/>
</div> </div>
</div> </div>
@ -59,16 +68,13 @@
> >
<p> <p>
This operation This operation
<strong>CANNOT</strong> be undone. <strong>CANNOT</strong> be undone. This operation will remove
This operation will remove <strong>{{ projectPath }}</strong>
<strong>{{projectPath}}</strong>
</p> </p>
</div> </div>
<label class="block mb-2"> <label class="block mb-2">
Please type the project name for confirmation: Please type the project name for confirmation:
<span <span class="text-red-500 font-bold">{{ projectName }}</span>
class="text-red-500 font-bold"
>{{ projectName }}</span>
</label> </label>
<div class="mb-4"> <div class="mb-4">
<input <input
@ -82,7 +88,9 @@
class="btn btn-red" class="btn btn-red"
@click="deleteProject()" @click="deleteProject()"
:disabled="!deleteButtonEnabled" :disabled="!deleteButtonEnabled"
>Delete Project</button> >
Delete Project
</button>
<div <div
v-if="deleteProjectError" v-if="deleteProjectError"
@ -93,21 +101,30 @@
</div> </div>
</div> </div>
<div class="p-4 border-t"> <div class="p-4 border-t">
<h4 class="mb-4 title text-xl">Change remote repository linked account</h4> <h4 class="mb-4 title text-xl">
Change remote repository linked account
</h4>
<div <div
class="mb-4 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded" class="mb-4 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded"
role="alert" role="alert"
> >
<p>This operation will change the linked account associated with the project remote repository to the current user linked account</p> <p>
This operation will change the linked account associated with the
project remote repository to the current user linked account
</p>
</div> </div>
<button class="btn btn-red" @click="updateRepoLinkedAccount()">Change</button> <button class="btn btn-red" @click="updateRepoLinkedAccount()">
Change
</button>
<div <div
v-if="updateRepoLinkedAccountError" v-if="updateRepoLinkedAccountError"
class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
role="alert" role="alert"
> >
<span class="block sm:inline">{{ updateRepoLinkedAccountError }}</span> <span class="block sm:inline">{{
updateRepoLinkedAccountError
}}</span>
</div> </div>
</div> </div>
</div> </div>
@ -121,21 +138,21 @@ import {
fetchVariables, fetchVariables,
updateProject, updateProject,
deleteProject, deleteProject,
projectUpdateRepoLinkedAccount projectUpdateRepoLinkedAccount,
} from "@/util/data.js"; } from '@/util/data.js';
import { projectGroupLink } from "@/util/link.js"; import { projectGroupLink } from '@/util/link.js';
import projectsecrets from "@/components/projectsecrets"; import projectsecrets from '@/components/projectsecrets';
import projectvars from "@/components/projectvars"; import projectvars from '@/components/projectvars';
export default { export default {
components: { projectsecrets, projectvars }, components: { projectsecrets, projectvars },
name: "projectsettings", name: 'projectsettings',
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectref: Array projectref: Array,
}, },
data() { data() {
return { return {
@ -148,19 +165,19 @@ export default {
allsecrets: [], allsecrets: [],
variables: [], variables: [],
allvariables: [], allvariables: [],
projectNameToDelete: "" projectNameToDelete: '',
}; };
}, },
computed: { computed: {
projectName: function() { projectName: function () {
return this.projectref[this.projectref.length - 1]; return this.projectref[this.projectref.length - 1];
}, },
projectPath: function() { projectPath: function () {
return ["", this.ownertype, this.ownername, ...this.projectref].join("/"); return ['', this.ownertype, this.ownername, ...this.projectref].join('/');
}, },
deleteButtonEnabled: function() { deleteButtonEnabled: function () {
return this.projectNameToDelete == this.projectName; return this.projectNameToDelete == this.projectName;
} },
}, },
methods: { methods: {
resetErrors() { resetErrors() {
@ -174,12 +191,12 @@ export default {
let projectref = [ let projectref = [
this.ownertype, this.ownertype,
this.ownername, this.ownername,
...this.projectref ...this.projectref,
].join("/"); ].join('/');
let visibility = "public"; let visibility = 'public';
if (this.projectIsPrivate) { if (this.projectIsPrivate) {
visibility = "private"; visibility = 'private';
} }
let { error } = await updateProject( let { error } = await updateProject(
projectref, projectref,
@ -198,8 +215,8 @@ export default {
let projectref = [ let projectref = [
this.ownertype, this.ownertype,
this.ownername, this.ownername,
...this.projectref ...this.projectref,
].join("/"); ].join('/');
if (this.projectNameToDelete == this.projectName) { if (this.projectNameToDelete == this.projectName) {
let { error } = await deleteProject(projectref); let { error } = await deleteProject(projectref);
@ -222,60 +239,59 @@ export default {
let projectref = [ let projectref = [
this.ownertype, this.ownertype,
this.ownername, this.ownername,
...this.projectref ...this.projectref,
].join("/"); ].join('/');
let { error } = await projectUpdateRepoLinkedAccount(projectref); let { error } = await projectUpdateRepoLinkedAccount(projectref);
if (error) { if (error) {
this.updateRepoLinkedAccountError = error; this.updateRepoLinkedAccountError = error;
return; return;
} }
} },
}, },
created: async function() { created: async function () {
let projectref = [this.ownertype, this.ownername, ...this.projectref].join( let projectref = [this.ownertype, this.ownername, ...this.projectref].join(
"/" '/'
); );
let { data, error } = await fetchProject(projectref); let { data, error } = await fetchProject(projectref);
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.project = data; this.project = data;
this.projectIsPrivate = this.project.visibility == "private"; this.projectIsPrivate = this.project.visibility == 'private';
({ data, error } = await fetchSecrets("project", projectref, false)); ({ data, error } = await fetchSecrets('project', projectref, false));
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.secrets = data; this.secrets = data;
({ data, error } = await fetchSecrets("project", projectref, true)); ({ data, error } = await fetchSecrets('project', projectref, true));
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.allsecrets = data; this.allsecrets = data;
({ data, error } = await fetchVariables("project", projectref, false)); ({ data, error } = await fetchVariables('project', projectref, false));
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.variables = data; this.variables = data;
({ data, error } = await fetchVariables("project", projectref, true)); ({ data, error } = await fetchVariables('project', projectref, true));
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.allvariables = data; this.allvariables = data;
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -7,32 +7,34 @@
<hr class="my-6 border-t" /> <hr class="my-6 border-t" />
<h5 class="text-2xl">All variables (local and inherited)</h5> <h5 class="text-2xl">All variables (local and inherited)</h5>
<vars v-if="allvariables.length" :variables="allvariables" :showparentpath="true" /> <vars
v-if="allvariables.length"
:variables="allvariables"
:showparentpath="true"
/>
<span v-else>No variables</span> <span v-else>No variables</span>
</div> </div>
</template> </template>
<script> <script>
import vars from "@/components/vars"; import vars from '@/components/vars';
export default { export default {
components: { vars }, components: { vars },
name: "projectvars", name: 'projectvars',
props: { props: {
variables: Array, variables: Array,
allvariables: Array, allvariables: Array,
type: String type: String,
}, },
computed: { computed: {
typetitle() { typetitle() {
if (this.type == "project") return "Project"; if (this.type == 'project') return 'Project';
if (this.type == "projectgroup") return "Project group"; if (this.type == 'projectgroup') return 'Project group';
return ""; return '';
} },
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -5,7 +5,9 @@
@submit.prevent="$emit('login', { username })" @submit.prevent="$emit('login', { username })"
> >
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-bold mb-2" for="username">Remote Username</label> <label class="block text-sm font-bold mb-2" for="username"
>Remote Username</label
>
<input <input
class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline" class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="username" id="username"
@ -13,38 +15,38 @@
placeholder="Username" placeholder="Username"
:disabled="true" :disabled="true"
v-model="remoteUsername" v-model="remoteUsername"
> />
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-bold mb-2" for="username">Username</label> <label class="block text-sm font-bold mb-2" for="username"
>Username</label
>
<input <input
class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline" class="appearance-none border rounded w-full py-2 px-3 leading-tight focus:outline-none focus:shadow-outline"
id="username" id="username"
type="text" type="text"
placeholder="Username" placeholder="Username"
v-model="username" v-model="username"
> />
</div> </div>
<div class="flex justify-center"> <div class="flex justify-center">
<button <button
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
type="submit" type="submit"
>Register</button> >
Register
</button>
</div> </div>
</form> </form>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: "RegisterForm", name: 'RegisterForm',
props: { props: {
remoteUsername: String, remoteUsername: String,
username: String username: String,
} },
}; };
</script> </script>

View File

@ -7,8 +7,8 @@
v-bind:key="repo.id" v-bind:key="repo.id"
@click="select(index)" @click="select(index)"
> >
<input type="radio" :checked="selectedrepo == index"> <input type="radio" :checked="selectedrepo == index" />
{{repo.path}} {{ repo.path }}
</label> </label>
</div> </div>
<div v-else class="block px-4 py-2 border-b">No remote repositories</div> <div v-else class="block px-4 py-2 border-b">No remote repositories</div>
@ -18,23 +18,22 @@
<script> <script>
export default { export default {
components: {}, components: {},
name: "remoterepos", name: 'remoterepos',
props: { props: {
remoterepos: Array remoterepos: Array,
}, },
data() { data() {
return { return {
selectedrepo: null selectedrepo: null,
}; };
}, },
methods: { methods: {
select(index) { select(index) {
this.selectedrepo = index; this.selectedrepo = index;
this.$emit("reposelected", this.remoterepos[index].path); this.$emit('reposelected', this.remoterepos[index].path);
} },
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -27,21 +27,29 @@
<div class="p-4 border border-l-0 rounded-r flex"> <div class="p-4 border border-l-0 rounded-r flex">
<div class="w-4/6 items-start justify-between"> <div class="w-4/6 items-start justify-between">
<div class="flex items-center mb-1"> <div class="flex items-center mb-1">
<h2 class="text-2xl mr-3">{{run.name}}</h2> <h2 class="text-2xl mr-3">{{ run.name }}</h2>
<span <span
class="mr-3 rounded px-2 py-1 text-xs" class="mr-3 rounded px-2 py-1 text-xs"
:class="'is-' + runResultClass(run)" :class="'is-' + runResultClass(run)"
>{{ runStatus(run) | capitalize }}</span> >{{ runStatus(run) | capitalize }}</span
>
<span <span
v-if="stillRunning(run)" v-if="stillRunning(run)"
class="rounded bg-gray-500 text-white px-2 py-1 text-xs" class="rounded bg-gray-500 text-white px-2 py-1 text-xs"
>Still running</span> >Still running</span
>
</div>
<div class="mb-6">
{{ run.annotations.message.split(/\r?\n/)[0] }}
</div> </div>
<div class="mb-6">{{run.annotations.message.split(/\r?\n/)[0]}}</div>
<div> <div>
<a :href="run.annotations.commit_link" class="block" target="_blank"> <a
:href="run.annotations.commit_link"
class="block"
target="_blank"
>
<i class="mdi mdi-source-commit mdi-rotate-90"></i> <i class="mdi mdi-source-commit mdi-rotate-90"></i>
<span>{{run.annotations.commit_sha.substring(0,8)}}</span> <span>{{ run.annotations.commit_sha.substring(0, 8) }}</span>
</a> </a>
<a <a
v-if="run.annotations.ref_type == 'branch'" v-if="run.annotations.ref_type == 'branch'"
@ -50,7 +58,7 @@
target="_blank" target="_blank"
> >
<i class="mdi mdi-source-branch"></i> <i class="mdi mdi-source-branch"></i>
<span>{{run.annotations.branch}}</span> <span>{{ run.annotations.branch }}</span>
</a> </a>
<a <a
v-else-if="run.annotations.ref_type == 'tag'" v-else-if="run.annotations.ref_type == 'tag'"
@ -59,7 +67,7 @@
target="_blank" target="_blank"
> >
<i class="mdi mdi-tag"></i> <i class="mdi mdi-tag"></i>
<span>{{run.annotations.tag}}</span> <span>{{ run.annotations.tag }}</span>
</a> </a>
<a <a
v-else-if="run.annotations.ref_type == 'pull_request'" v-else-if="run.annotations.ref_type == 'pull_request'"
@ -68,7 +76,7 @@
target="_blank" target="_blank"
> >
<i class="mdi mdi-source-pull"></i> <i class="mdi mdi-source-pull"></i>
<span>PR #{{run.annotations.pull_request_id}}</span> <span>PR #{{ run.annotations.pull_request_id }}</span>
</a> </a>
</div> </div>
</div> </div>
@ -85,12 +93,18 @@
<div class="w-1/6 flex items-start justify-between"> <div class="w-1/6 flex items-start justify-between">
<div class="relative ml-auto mr-3"> <div class="relative ml-auto mr-3">
<div <div
v-if="run.can_restart_from_scratch || run.can_restart_from_failed_tasks" v-if="
run.can_restart_from_scratch ||
run.can_restart_from_failed_tasks
"
class="flex" class="flex"
v-click-outside="() => dropdownActive = false" v-click-outside="() => (dropdownActive = false)"
> >
<div class="flex items-center"> <div class="flex items-center">
<button class="btn btn-blue" @click="dropdownActive = !dropdownActive"> <button
class="btn btn-blue"
@click="dropdownActive = !dropdownActive"
>
<span>Restart</span> <span>Restart</span>
<i class="ml-3 mdi mdi-restart" aria-hidden="true"></i> <i class="ml-3 mdi mdi-restart" aria-hidden="true"></i>
</button> </button>
@ -107,14 +121,16 @@
v-if="run.can_restart_from_scratch" v-if="run.can_restart_from_scratch"
class="block px-4 py-2 hover:bg-blue-500 hover:text-white cursor-pointer" class="block px-4 py-2 hover:bg-blue-500 hover:text-white cursor-pointer"
@click="restartRun(run.id, true)" @click="restartRun(run.id, true)"
>From start</a> >From start</a
>
</li> </li>
<li> <li>
<a <a
v-if="run.can_restart_from_failed_tasks" v-if="run.can_restart_from_failed_tasks"
class="block px-4 py-2 hover:bg-blue-500 hover:text-white cursor-pointer" class="block px-4 py-2 hover:bg-blue-500 hover:text-white cursor-pointer"
@click="restartRun(run.id)" @click="restartRun(run.id)"
>From failed tasks</a> >From failed tasks</a
>
</li> </li>
</ul> </ul>
</div> </div>
@ -122,13 +138,17 @@
class="btn btn-red" class="btn btn-red"
v-if="run.phase == 'queued'" v-if="run.phase == 'queued'"
@click="cancelRun(run.id)" @click="cancelRun(run.id)"
>Cancel</button> >
Cancel
</button>
<button <button
class="btn btn-red" class="btn btn-red"
v-if="run.phase == 'running'" v-if="run.phase == 'running'"
:disabled="run.stopping" :disabled="run.stopping"
@click="stopRun(run.id)" @click="stopRun(run.id)"
>Stop</button> >
Stop
</button>
</div> </div>
</div> </div>
</div> </div>
@ -138,27 +158,27 @@
</template> </template>
<script> <script>
import * as vClickOutside from "v-click-outside-x"; import * as vClickOutside from 'v-click-outside-x';
import { cancelRun, stopRun, restartRun } from "@/util/data.js"; import { cancelRun, stopRun, restartRun } from '@/util/data.js';
import { userDirectRunLink, projectRunLink } from "@/util/link.js"; import { userDirectRunLink, projectRunLink } from '@/util/link.js';
import { runStatus, runResultClass } from "@/util/run.js"; import { runStatus, runResultClass } from '@/util/run.js';
import * as moment from "moment"; import * as moment from 'moment';
import momentDurationFormatSetup from "moment-duration-format"; import momentDurationFormatSetup from 'moment-duration-format';
momentDurationFormatSetup(moment); momentDurationFormatSetup(moment);
export default { export default {
name: "rundetail", name: 'rundetail',
directives: { directives: {
clickOutside: vClickOutside.directive clickOutside: vClickOutside.directive,
}, },
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectref: Array, projectref: Array,
run: Object run: Object,
}, },
data() { data() {
return { return {
@ -166,7 +186,7 @@ export default {
stopRunError: null, stopRunError: null,
cancelRunError: null, cancelRunError: null,
restartRunError: null, restartRunError: null,
dropdownActive: false dropdownActive: false,
}; };
}, },
methods: { methods: {
@ -178,14 +198,14 @@ export default {
this.restartRunError = null; this.restartRunError = null;
}, },
stillRunning(run) { stillRunning(run) {
return run.result != "unknown" && run.phase == "running"; return run.result != 'unknown' && run.phase == 'running';
}, },
taskClass(task) { taskClass(task) {
if (task.status == "success") return "success"; if (task.status == 'success') return 'success';
if (task.status == "failed") return "failed"; if (task.status == 'failed') return 'failed';
if (task.status == "stopped") return "failed"; if (task.status == 'stopped') return 'failed';
if (task.status == "running") return "running"; if (task.status == 'running') return 'running';
return "unknown"; return 'unknown';
}, },
async stopRun(runid) { async stopRun(runid) {
this.resetErrors(); this.resetErrors();
@ -207,7 +227,7 @@ export default {
return; return;
} }
this.run.phase = "cancelled"; this.run.phase = 'cancelled';
}, },
async restartRun(runid, fromStart) { async restartRun(runid, fromStart) {
this.dropdownActive = false; this.dropdownActive = false;
@ -231,7 +251,7 @@ export default {
this.$router.push(runLink); this.$router.push(runLink);
}, },
duration(run) { duration(run) {
let formatString = "h:mm:ss[s]"; let formatString = 'h:mm:ss[s]';
let start = moment(run.start_time); let start = moment(run.start_time);
let end = moment(run.end_time); let end = moment(run.end_time);
@ -244,30 +264,29 @@ export default {
return moment.duration(end.diff(start)).format(formatString); return moment.duration(end.diff(start)).format(formatString);
}, },
endTime(run) { endTime(run) {
let formatString = "lll"; let formatString = 'lll';
let end = moment(run.end_time); let end = moment(run.end_time);
if (run.end_time === null) { if (run.end_time === null) {
return ""; return '';
} }
return "Finished " + end.format(formatString); return 'Finished ' + end.format(formatString);
}, },
endTimeHuman(run) { endTimeHuman(run) {
let end = moment(run.end_time); let end = moment(run.end_time);
if (run.end_time === null) { if (run.end_time === null) {
return ""; return '';
} }
return end.fromNow(); return end.fromNow();
} },
}, },
created: function() { created: function () {
window.setInterval(() => { window.setInterval(() => {
this.now = moment(); this.now = moment();
}, 500); }, 500);
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -9,7 +9,7 @@
</div> </div>
<div class="ml-6 flex w-48"> <div class="ml-6 flex w-48">
<div v-bind:class="{ 'spinner': fetchRunsLoading }"></div> <div v-bind:class="{ spinner: fetchRunsLoading }"></div>
</div> </div>
<div v-if="runs"> <div v-if="runs">
<ul> <ul>
@ -27,21 +27,21 @@
class="whitespace-no-wrap overflow-x-hidden" class="whitespace-no-wrap overflow-x-hidden"
> >
<i class="mdi mdi-source-branch mr-1"></i> <i class="mdi mdi-source-branch mr-1"></i>
<span>{{run.annotations.branch}}</span> <span>{{ run.annotations.branch }}</span>
</div> </div>
<div <div
v-else-if="run.annotations.ref_type == 'tag'" v-else-if="run.annotations.ref_type == 'tag'"
class="whitespace-no-wrap overflow-x-hidden" class="whitespace-no-wrap overflow-x-hidden"
> >
<i class="mdi mdi-tag mr-1"></i> <i class="mdi mdi-tag mr-1"></i>
<span>{{run.annotations.tag}}</span> <span>{{ run.annotations.tag }}</span>
</div> </div>
<div <div
v-else-if="run.annotations.ref_type == 'pull_request'" v-else-if="run.annotations.ref_type == 'pull_request'"
class="whitespace-no-wrap overflow-x-hidden" class="whitespace-no-wrap overflow-x-hidden"
> >
<i class="mdi mdi-source-pull mr-1"></i> <i class="mdi mdi-source-pull mr-1"></i>
<span>PR #{{run.annotations.pull_request_id}}</span> <span>PR #{{ run.annotations.pull_request_id }}</span>
</div> </div>
</div> </div>
<div v-else class="w-2/12"> <div v-else class="w-2/12">
@ -53,30 +53,36 @@
class="w-5/12 pl-3 mr-auto whitespace-no-wrap overflow-hidden" class="w-5/12 pl-3 mr-auto whitespace-no-wrap overflow-hidden"
:to="projectRunLink(ownertype, ownername, projectref, run.id)" :to="projectRunLink(ownertype, ownername, projectref, run.id)"
> >
<span class="font-bold">{{run.name}}</span> <span class="font-bold">{{ run.name }}</span>
<div>{{run.annotations.message.split(/\r?\n/)[0]}}</div> <div>{{ run.annotations.message.split(/\r?\n/)[0] }}</div>
</router-link> </router-link>
<router-link <router-link
v-else v-else
class="w-5/12 pl-3 mr-auto whitespace-no-wrap overflow-hidden" class="w-5/12 pl-3 mr-auto whitespace-no-wrap overflow-hidden"
:to="userDirectRunLink(ownername, run.id)" :to="userDirectRunLink(ownername, run.id)"
> >
<span class="font-bold">{{run.name}}</span> <span class="font-bold">{{ run.name }}</span>
<div>{{run.annotations.message.split(/\r?\n/)[0]}}</div> <div>{{ run.annotations.message.split(/\r?\n/)[0] }}</div>
</router-link> </router-link>
<span <span
v-if="waitingApproval(run)" v-if="waitingApproval(run)"
class="w-2/12 bg-gray-200 rounded-full px-3 py-1 text-sm text-center font-semibold mr-2" class="w-2/12 bg-gray-200 rounded-full px-3 py-1 text-sm text-center font-semibold mr-2"
>Waiting Approval</span> >Waiting Approval</span
>
<span <span
v-if="stillRunning(run)" v-if="stillRunning(run)"
class="w-2/12 bg-gray-200 rounded-full px-3 py-1 text-sm text-center font-semibold mr-2" class="w-2/12 bg-gray-200 rounded-full px-3 py-1 text-sm text-center font-semibold mr-2"
>Still running</span> >Still running</span
>
<div class="w-32"> <div class="w-32">
<span>#{{run.counter}}</span> <span>#{{ run.counter }}</span>
<a :href="run.annotations.commit_link" class="block" target="_blank"> <a
:href="run.annotations.commit_link"
class="block"
target="_blank"
>
<i class="mdi mdi-source-commit mdi-rotate-90 mr-1"></i> <i class="mdi mdi-source-commit mdi-rotate-90 mr-1"></i>
<span>{{run.annotations.commit_sha.substring(0,8)}}</span> <span>{{ run.annotations.commit_sha.substring(0, 8) }}</span>
</a> </a>
</div> </div>
<div class="w-32"> <div class="w-32">
@ -97,7 +103,9 @@
v-if="hasMoreRuns" v-if="hasMoreRuns"
class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded" class="bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded"
@click="loadMoreRuns()" @click="loadMoreRuns()"
>Load more...</button> >
Load more...
</button>
</div> </div>
</div> </div>
<div v-if="runs && runs.length == 0" class>No runs</div> <div v-if="runs && runs.length == 0" class>No runs</div>
@ -105,22 +113,22 @@
</template> </template>
<script> <script>
import { fetchUser, fetchProject, fetchRuns } from "@/util/data.js"; import { fetchUser, fetchProject, fetchRuns } from '@/util/data.js';
import { userDirectRunLink, projectRunLink } from "@/util/link.js"; import { userDirectRunLink, projectRunLink } from '@/util/link.js';
import { runResultClass } from "@/util/run.js"; import { runResultClass } from '@/util/run.js';
import * as moment from "moment"; import * as moment from 'moment';
import momentDurationFormatSetup from "moment-duration-format"; import momentDurationFormatSetup from 'moment-duration-format';
momentDurationFormatSetup(moment); momentDurationFormatSetup(moment);
export default { export default {
components: {}, components: {},
name: "runs", name: 'runs',
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectref: Array, projectref: Array,
query: String query: String,
}, },
data() { data() {
return { return {
@ -137,15 +145,15 @@ export default {
wantedRunsNumber: 25, wantedRunsNumber: 25,
hasMoreRuns: false, hasMoreRuns: false,
project: null, project: null,
user: null user: null,
}; };
}, },
watch: { watch: {
$route: function() { $route: function () {
this.runs = null; this.runs = null;
this.hasMoreRuns = false; this.hasMoreRuns = false;
this.update(); this.update();
} },
}, },
methods: { methods: {
projectRunLink: projectRunLink, projectRunLink: projectRunLink,
@ -161,7 +169,7 @@ export default {
this.fetchRunsLoading = false; this.fetchRunsLoading = false;
}, },
stillRunning(run) { stillRunning(run) {
return run.result != "unknown" && run.phase == "running"; return run.result != 'unknown' && run.phase == 'running';
}, },
waitingApproval(run) { waitingApproval(run) {
return run.tasks_waiting_approval.length > 0; return run.tasks_waiting_approval.length > 0;
@ -183,8 +191,8 @@ export default {
let projectref = [ let projectref = [
this.ownertype, this.ownertype,
this.ownername, this.ownername,
...this.projectref ...this.projectref,
].join("/"); ].join('/');
let { data, error, aborted } = await fetchProject( let { data, error, aborted } = await fetchProject(
projectref, projectref,
@ -194,7 +202,7 @@ export default {
return; return;
} }
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.project = data; this.project = data;
@ -212,7 +220,7 @@ export default {
return; return;
} }
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.user = data; this.user = data;
@ -228,17 +236,17 @@ export default {
let group; let group;
let lastrun = false; let lastrun = false;
if (this.project !== null) { if (this.project !== null) {
if (this.query == "branches") { if (this.query == 'branches') {
group = "/project/" + this.project.id + "/branch"; group = '/project/' + this.project.id + '/branch';
} else if (this.query == "tags") { } else if (this.query == 'tags') {
group = "/project/" + this.project.id + "/tag"; group = '/project/' + this.project.id + '/tag';
} else if (this.query == "pullrequests") { } else if (this.query == 'pullrequests') {
group = "/project/" + this.project.id + "/pr"; group = '/project/' + this.project.id + '/pr';
} else { } else {
group = "/project/" + this.project.id; group = '/project/' + this.project.id;
} }
} else if (this.user !== null) { } else if (this.user !== null) {
group = "/user/" + this.user.id; group = '/user/' + this.user.id;
} }
let newRuns = []; let newRuns = [];
@ -289,7 +297,7 @@ export default {
}, 2000); }, 2000);
}, },
duration(run) { duration(run) {
let formatString = "h:mm:ss[s]"; let formatString = 'h:mm:ss[s]';
let start = moment(run.start_time); let start = moment(run.start_time);
let end = moment(run.end_time); let end = moment(run.end_time);
@ -302,24 +310,24 @@ export default {
return moment.duration(end.diff(start)).format(formatString); return moment.duration(end.diff(start)).format(formatString);
}, },
endTime(run) { endTime(run) {
let formatString = "lll"; let formatString = 'lll';
let end = moment(run.end_time); let end = moment(run.end_time);
if (run.end_time === null) { if (run.end_time === null) {
return ""; return '';
} }
return "Finished " + end.format(formatString); return 'Finished ' + end.format(formatString);
}, },
endTimeHuman(run) { endTimeHuman(run) {
let end = moment(run.end_time); let end = moment(run.end_time);
if (run.end_time === null) { if (run.end_time === null) {
return ""; return '';
} }
return end.fromNow(); return end.fromNow();
} },
}, },
created: function() { created: function () {
window.setInterval(() => { window.setInterval(() => {
this.now = moment(); this.now = moment();
}, 500); }, 500);
@ -331,9 +339,8 @@ export default {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
clearTimeout(this.fetchRunsSchedule); clearTimeout(this.fetchRunsSchedule);
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -7,7 +7,12 @@
> >
<div>{{ fetchRunError }}</div> <div>{{ fetchRunError }}</div>
</div> </div>
<rundetail :run="run" :ownertype="ownertype" :ownername="ownername" :projectref="projectref" /> <rundetail
:run="run"
:ownertype="ownertype"
:ownername="ownername"
:projectref="projectref"
/>
<div v-if="run"> <div v-if="run">
<div v-if="run.phase != 'setuperror'"> <div v-if="run.phase != 'setuperror'">
<div class="flex items-center my-6 justify-between"> <div class="flex items-center my-6 justify-between">
@ -17,7 +22,7 @@
<button <button
@click="tasksDisplay = 'graph'" @click="tasksDisplay = 'graph'"
class="relative flex items-center focus:outline-none bg-blue-500 hover:bg-blue-600 text-white font-semibold hover:text-white py-2 px-4 border border-blue-700 rounded rounded-r-none" class="relative flex items-center focus:outline-none bg-blue-500 hover:bg-blue-600 text-white font-semibold hover:text-white py-2 px-4 border border-blue-700 rounded rounded-r-none"
:class="{ 'bg-blue-600': tasksDisplay=='graph'}" :class="{ 'bg-blue-600': tasksDisplay == 'graph' }"
title="Tasks Graph" title="Tasks Graph"
> >
<i class="mr-1 mdi mdi-file-tree" /> <i class="mr-1 mdi mdi-file-tree" />
@ -26,7 +31,7 @@
@click="tasksDisplay = 'list'" @click="tasksDisplay = 'list'"
class="relative flex items-center focus:outline-none bg-blue-500 hover:bg-blue-600 text-white font-semibold hover:text-white py-2 px-4 border border-l-0 border-blue-700 rounded rounded-l-none" class="relative flex items-center focus:outline-none bg-blue-500 hover:bg-blue-600 text-white font-semibold hover:text-white py-2 px-4 border border-l-0 border-blue-700 rounded rounded-l-none"
title="Tasks List" title="Tasks List"
:class="{ 'bg-blue-600': tasksDisplay=='list'}" :class="{ 'bg-blue-600': tasksDisplay == 'list' }"
> >
<i class="mr-1 mdi mdi-format-list-bulleted-square" /> <i class="mr-1 mdi mdi-format-list-bulleted-square" />
</button> </button>
@ -42,7 +47,8 @@
class="font-mono leading-snug text-xs" class="font-mono leading-snug text-xs"
v-for="(error, i) in run.setup_errors" v-for="(error, i) in run.setup_errors"
v-bind:key="i" v-bind:key="i"
>{{error}}</pre> >{{ error }}</pre
>
</div> </div>
</div> </div>
</div> </div>
@ -50,21 +56,21 @@
</template> </template>
<script> <script>
import { fetchRun } from "@/util/data.js"; import { fetchRun } from '@/util/data.js';
import { userDirectRunTaskLink, projectRunTaskLink } from "@/util/link.js"; import { userDirectRunTaskLink, projectRunTaskLink } from '@/util/link.js';
import rundetail from "@/components/rundetail.vue"; import rundetail from '@/components/rundetail.vue';
import tasks from "@/components/tasks.vue"; import tasks from '@/components/tasks.vue';
import tasksgraph from "@/components/tasksgraph.vue"; import tasksgraph from '@/components/tasksgraph.vue';
export default { export default {
name: "runsummary", name: 'runsummary',
components: { rundetail, tasks, tasksgraph }, components: { rundetail, tasks, tasksgraph },
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectref: Array, projectref: Array,
runid: String runid: String,
}, },
data() { data() {
return { return {
@ -80,11 +86,11 @@ export default {
taskYSpace: 20, taskYSpace: 20,
hoverTask: null, hoverTask: null,
tasksDisplay: "graph" tasksDisplay: 'graph',
}; };
}, },
watch: { watch: {
$route: async function() { $route: async function () {
if (this.fetchAbort) { if (this.fetchAbort) {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
@ -93,7 +99,7 @@ export default {
this.fetchAbort = new AbortController(); this.fetchAbort = new AbortController();
this.fetchRun(); this.fetchRun();
} },
}, },
methods: { methods: {
runTaskLink(task) { runTaskLink(task) {
@ -110,16 +116,16 @@ export default {
} }
}, },
parents(task) { parents(task) {
return Object.keys(task.depends).map(key => { return Object.keys(task.depends).map((key) => {
return this.run.tasks[task.depends[key].task_id].name; return this.run.tasks[task.depends[key].task_id].name;
}); });
}, },
taskClass(task) { taskClass(task) {
if (task.status == "success") return "success"; if (task.status == 'success') return 'success';
if (task.status == "failed") return "failed"; if (task.status == 'failed') return 'failed';
if (task.status == "stopped") return "failed"; if (task.status == 'stopped') return 'failed';
if (task.status == "running") return "running"; if (task.status == 'running') return 'running';
return "unknown"; return 'unknown';
}, },
async fetchRun() { async fetchRun() {
let { data, error, aborted } = await fetchRun( let { data, error, aborted } = await fetchRun(
@ -145,9 +151,8 @@ export default {
let task = tasks[taskID]; let task = tasks[taskID];
task.link = this.runTaskLink(task); task.link = this.runTaskLink(task);
task.parents = this.parents(task); task.parents = this.parents(task);
task.waiting_approval = this.run.tasks_waiting_approval.includes( task.waiting_approval =
taskID this.run.tasks_waiting_approval.includes(taskID);
);
} }
this.scheduleFetchRun(); this.scheduleFetchRun();
@ -157,9 +162,9 @@ export default {
this.fetchRunSchedule = setTimeout(() => { this.fetchRunSchedule = setTimeout(() => {
this.fetchRun(); this.fetchRun();
}, 2000); }, 2000);
} },
}, },
created: function() { created: function () {
this.fetchAbort = new AbortController(); this.fetchAbort = new AbortController();
this.fetchRun(); this.fetchRun();
}, },
@ -168,9 +173,8 @@ export default {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
clearTimeout(this.fetchRunSchedule); clearTimeout(this.fetchRunSchedule);
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -5,8 +5,10 @@
</div> </div>
<div class="flex" v-for="secret in secrets" v-bind:key="secret.id"> <div class="flex" v-for="secret in secrets" v-bind:key="secret.id">
<div class="w-2/12"> <div class="w-2/12">
<span class="name">{{secret.name}}</span> <span class="name">{{ secret.name }}</span>
<div v-if="showparentpath" class="text-sm font-light">from {{secret.parent_path}}</div> <div v-if="showparentpath" class="text-sm font-light">
from {{ secret.parent_path }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -15,16 +17,12 @@
<script> <script>
export default { export default {
components: {}, components: {},
name: "secrets", name: 'secrets',
props: { props: {
secrets: Array, secrets: Array,
showparentpath: Boolean showparentpath: Boolean,
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -12,7 +12,7 @@
class="inline-block mr-1 mdi mdi-arrow-right" class="inline-block mr-1 mdi mdi-arrow-right"
:class="{ 'arrow-down': active, 'arrow-right': !active }" :class="{ 'arrow-down': active, 'arrow-right': !active }"
></i> ></i>
<span class="w-1/3 font-bold">{{step.name}}</span> <span class="w-1/3 font-bold">{{ step.name }}</span>
</div> </div>
<span class>{{ duration }}</span> <span class>{{ duration }}</span>
</div> </div>
@ -20,18 +20,24 @@
<div v-if="step.type == 'run'"> <div v-if="step.type == 'run'">
<div class="p-3 rounded-t bg-gray-900 text-white"> <div class="p-3 rounded-t bg-gray-900 text-white">
<span> <span>
<span class="w-2/12 bg-gray-700 rounded-l px-3 py-1 text-center font-semibold">Shell</span> <span
class="w-2/12 bg-gray-700 rounded-l px-3 py-1 text-center font-semibold"
>Shell</span
>
<span <span
class="w-2/12 bg-gray-600 rounded-r px-3 py-1 text-center font-semibold mr-2" class="w-2/12 bg-gray-600 rounded-r px-3 py-1 text-center font-semibold mr-2"
>{{ step.shell}}</span> >{{ step.shell }}</span
>
</span> </span>
<span v-if="step.exit_status != undefined"> <span v-if="step.exit_status != undefined">
<span <span
class="w-2/12 bg-gray-700 rounded-l px-3 py-1 text-center font-semibold" class="w-2/12 bg-gray-700 rounded-l px-3 py-1 text-center font-semibold"
>Exit Status</span> >Exit Status</span
>
<span <span
class="w-2/12 bg-gray-600 rounded-r px-3 py-1 text-center font-semibold mr-2" class="w-2/12 bg-gray-600 rounded-r px-3 py-1 text-center font-semibold mr-2"
>{{ step.exit_status}}</span> >{{ step.exit_status }}</span
>
</span> </span>
</div> </div>
<div <div
@ -40,22 +46,27 @@
> >
<i <i
class="inline-block mr-1 mdi mdi-arrow-right" class="inline-block mr-1 mdi mdi-arrow-right"
:class="{ 'arrow-down': commandActive, 'arrow-right': !commandActive }" :class="{
'arrow-down': commandActive,
'arrow-right': !commandActive,
}"
></i> ></i>
<span>Command</span> <span>Command</span>
</div> </div>
<div <div
v-show="commandActive" v-show="commandActive"
class="p-3 bg-gray-900 text-white overflow-x-auto" class="p-3 bg-gray-900 text-white overflow-x-auto"
:class="{ 'rounded': step.type != 'run' }" :class="{ rounded: step.type != 'run' }"
> >
<pre class="font-mono text-xs">{{ step.command}}</pre> <pre class="font-mono text-xs">{{ step.command }}</pre>
</div> </div>
</div> </div>
<div v-if="step.type == 'run'" class="px-3 py-2 bg-gray-700 text-white">Log</div> <div v-if="step.type == 'run'" class="px-3 py-2 bg-gray-700 text-white">
Log
</div>
<div <div
class="p-3 rounded-b bg-gray-900 text-white" class="p-3 rounded-b bg-gray-900 text-white"
:class="{ 'rounded': step.type != 'run' }" :class="{ rounded: step.type != 'run' }"
> >
<Log <Log
v-bind:runid="runid" v-bind:runid="runid"
@ -72,22 +83,22 @@
</template> </template>
<script> <script>
import * as moment from "moment"; import * as moment from 'moment';
import momentDurationFormatSetup from "moment-duration-format"; import momentDurationFormatSetup from 'moment-duration-format';
import Log from "@/components/log.vue"; import Log from '@/components/log.vue';
momentDurationFormatSetup(moment); momentDurationFormatSetup(moment);
export default { export default {
name: "step", name: 'step',
components: { components: {
Log Log,
}, },
data() { data() {
return { return {
active: false, active: false,
commandActive: true, commandActive: true,
now: moment() now: moment(),
}; };
}, },
props: { props: {
@ -95,11 +106,11 @@ export default {
taskid: String, taskid: String,
setup: Boolean, setup: Boolean,
stepnum: Number, stepnum: Number,
step: Object step: Object,
}, },
computed: { computed: {
duration() { duration() {
let formatString = "h:mm:ss[s]"; let formatString = 'h:mm:ss[s]';
let start = moment(this.step.start_time); let start = moment(this.step.start_time);
let end = moment(this.step.end_time); let end = moment(this.step.end_time);
@ -112,12 +123,12 @@ export default {
return moment.duration(end.diff(start)).format(formatString); return moment.duration(end.diff(start)).format(formatString);
}, },
stepClass() { stepClass() {
if (this.step.phase == "success") return "success"; if (this.step.phase == 'success') return 'success';
if (this.step.phase == "failed") return "failed"; if (this.step.phase == 'failed') return 'failed';
if (this.step.phase == "stopped") return "failed"; if (this.step.phase == 'stopped') return 'failed';
if (this.step.phase == "running") return "running"; if (this.step.phase == 'running') return 'running';
return "unknown"; return 'unknown';
} },
}, },
methods: { methods: {
toggle() { toggle() {
@ -125,13 +136,13 @@ export default {
}, },
toggleCommand() { toggleCommand() {
this.commandActive = !this.commandActive; this.commandActive = !this.commandActive;
} },
}, },
created() { created() {
window.setInterval(() => { window.setInterval(() => {
this.now = moment(); this.now = moment();
}, 500); }, 500);
} },
}; };
</script> </script>

View File

@ -1,14 +1,18 @@
<template> <template>
<div class="arrow"> <div class="arrow">
<svg viewBox="0 0 15 15"> <svg viewBox="0 0 15 15">
<path fill="none" stroke="#9d9d9d" d="M4.32.5l6.247 6.942L4.32 14.5"></path> <path
fill="none"
stroke="#9d9d9d"
d="M4.32.5l6.247 6.942L4.32 14.5"
></path>
</svg> </svg>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: "tabarrow" name: 'tabarrow',
}; };
</script> </script>

View File

@ -2,24 +2,31 @@
<ul> <ul>
<li v-for="task in sortedTasks" v-bind:key="task.id"> <li v-for="task in sortedTasks" v-bind:key="task.id">
<div class="mb-2 border-l-5 rounded-l" :class="taskClass(task)"> <div class="mb-2 border-l-5 rounded-l" :class="taskClass(task)">
<div class="px-4 py-4 flex justify-between items-center border border-l-0 rounded-r"> <div
class="px-4 py-4 flex justify-between items-center border border-l-0 rounded-r"
>
<router-link class="w-1/3 font-bold" tag="a" :to="task.link"> <router-link class="w-1/3 font-bold" tag="a" :to="task.link">
<span class="w-1/3 font-bold">{{task.name}}</span> <span class="w-1/3 font-bold">{{ task.name }}</span>
</router-link> </router-link>
<div class="w-1/4"> <div class="w-1/4">
<span <span
v-if="task.waiting_approval" v-if="task.waiting_approval"
class="w-2/12 bg-gray-200 rounded-full px-3 py-1 text-sm text-center font-semibold mr-2" class="w-2/12 bg-gray-200 rounded-full px-3 py-1 text-sm text-center font-semibold mr-2"
>Waiting Approval</span> >Waiting Approval</span
>
</div> </div>
<div class="w-1/4"> <div class="w-1/4">
<span class="block text-xs" v-if="task.parents.length > 0">depends on: &nbsp;</span> <span class="block text-xs" v-if="task.parents.length > 0"
>depends on: &nbsp;</span
>
<ul> <ul>
<li <li
class="font-thin text-xs text-gray-600" class="font-thin text-xs text-gray-600"
v-for="dep in task.parents" v-for="dep in task.parents"
v-bind:key="dep" v-bind:key="dep"
>{{dep}}</li> >
{{ dep }}
</li>
</ul> </ul>
</div> </div>
<span class="w-16 text-right">{{ task.duration }}</span> <span class="w-16 text-right">{{ task.duration }}</span>
@ -30,21 +37,21 @@
</template> </template>
<script> <script>
import * as moment from "moment"; import * as moment from 'moment';
import momentDurationFormatSetup from "moment-duration-format"; import momentDurationFormatSetup from 'moment-duration-format';
momentDurationFormatSetup(moment); momentDurationFormatSetup(moment);
export default { export default {
name: "tasks", name: 'tasks',
components: {}, components: {},
data() { data() {
return { return {
now: moment() now: moment(),
}; };
}, },
props: { props: {
tasks: Object tasks: Object,
}, },
computed: { computed: {
sortedTasks() { sortedTasks() {
@ -59,18 +66,18 @@ export default {
? -1 ? -1
: 0 : 0
) )
.map(k => tasks[k]); .map((k) => tasks[k]);
for (let task of sortedTasks) { for (let task of sortedTasks) {
task.duration = this.duration(task); task.duration = this.duration(task);
} }
return sortedTasks; return sortedTasks;
} },
}, },
methods: { methods: {
duration(task) { duration(task) {
let formatString = "h:mm:ss[s]"; let formatString = 'h:mm:ss[s]';
let start = moment(task.start_time); let start = moment(task.start_time);
let end = moment(task.end_time); let end = moment(task.end_time);
@ -83,18 +90,18 @@ export default {
return moment.duration(end.diff(start)).format(formatString); return moment.duration(end.diff(start)).format(formatString);
}, },
taskClass(task) { taskClass(task) {
if (task.status == "success") return "success"; if (task.status == 'success') return 'success';
if (task.status == "failed") return "failed"; if (task.status == 'failed') return 'failed';
if (task.status == "stopped") return "failed"; if (task.status == 'stopped') return 'failed';
if (task.status == "running") return "running"; if (task.status == 'running') return 'running';
if (task.status == "skipped") return "skipped"; if (task.status == 'skipped') return 'skipped';
return "unknown"; return 'unknown';
} },
}, },
created() { created() {
window.setInterval(() => { window.setInterval(() => {
this.now = moment(); this.now = moment();
}, 500); }, 500);
} },
}; };
</script> </script>

View File

@ -64,13 +64,13 @@
</template> </template>
<script> <script>
import * as moment from "moment"; import * as moment from 'moment';
import momentDurationFormatSetup from "moment-duration-format"; import momentDurationFormatSetup from 'moment-duration-format';
momentDurationFormatSetup(moment); momentDurationFormatSetup(moment);
export default { export default {
name: "tasksgraph", name: 'tasksgraph',
components: {}, components: {},
data() { data() {
return { return {
@ -85,14 +85,14 @@ export default {
taskYSpace: 20, taskYSpace: 20,
hoverTask: null, hoverTask: null,
height: "400px" height: '400px',
}; };
}, },
props: { props: {
tasks: Object tasks: Object,
}, },
computed: { computed: {
segments: function() { segments: function () {
let segments = []; let segments = [];
for (let edge of this.edges) { for (let edge of this.edges) {
for (let i = 0; i < edge.edgePoints.length - 1; i++) { for (let i = 0; i < edge.edgePoints.length - 1; i++) {
@ -107,7 +107,7 @@ export default {
} }
// TODO(sgotti) set different colors to edges based on source task status??? // TODO(sgotti) set different colors to edges based on source task status???
let stroke = "text-dark"; let stroke = 'text-dark';
segments.push({ segments.push({
edge: edge, edge: edge,
x1: edge.edgePoints[i].x, x1: edge.edgePoints[i].x,
@ -115,7 +115,7 @@ export default {
x2: edge.edgePoints[i + 1].x, x2: edge.edgePoints[i + 1].x,
y2: edge.edgePoints[i + 1].y, y2: edge.edgePoints[i + 1].y,
strokeWidth: strokeWidth, strokeWidth: strokeWidth,
stroke: stroke stroke: stroke,
}); });
} }
} }
@ -130,16 +130,16 @@ export default {
} }
return this.graphTasks; return this.graphTasks;
} },
}, },
watch: { watch: {
tasks: function(tasks) { tasks: function (tasks) {
this.update(tasks); this.update(tasks);
} },
}, },
methods: { methods: {
duration(task) { duration(task) {
let formatString = "h:mm:ss[s]"; let formatString = 'h:mm:ss[s]';
let start = moment(task.start_time); let start = moment(task.start_time);
let end = moment(task.end_time); let end = moment(task.end_time);
@ -152,12 +152,12 @@ export default {
return moment.duration(end.diff(start)).format(formatString); return moment.duration(end.diff(start)).format(formatString);
}, },
taskClass(task) { taskClass(task) {
if (task.status == "success") return "success"; if (task.status == 'success') return 'success';
if (task.status == "failed") return "failed"; if (task.status == 'failed') return 'failed';
if (task.status == "stopped") return "failed"; if (task.status == 'stopped') return 'failed';
if (task.status == "running") return "running"; if (task.status == 'running') return 'running';
if (task.status == "skipped") return "skipped"; if (task.status == 'skipped') return 'skipped';
return "unknown"; return 'unknown';
}, },
update(tasks) { update(tasks) {
// sort tasks by level // sort tasks by level
@ -169,7 +169,7 @@ export default {
? -1 ? -1
: 0 : 0
) )
.map(k => tasks[k]); .map((k) => tasks[k]);
this.graphTasks = graphTasks; this.graphTasks = graphTasks;
@ -180,7 +180,7 @@ export default {
} }
} }
let taskChilds = function(tasks, task) { let taskChilds = function (tasks, task) {
let childs = []; let childs = [];
for (let ot of tasks) { for (let ot of tasks) {
for (let depTaskID in ot.depends) { for (let depTaskID in ot.depends) {
@ -192,7 +192,7 @@ export default {
return childs; return childs;
}; };
let taskMaxChildLevel = function(tasks, task) { let taskMaxChildLevel = function (tasks, task) {
let level = task.level; let level = task.level;
let childs = taskChilds(tasks, task); let childs = taskChilds(tasks, task);
for (let child of childs) { for (let child of childs) {
@ -203,7 +203,7 @@ export default {
return level; return level;
}; };
let levelTasks = function(tasks, level) { let levelTasks = function (tasks, level) {
let levelTasks = []; let levelTasks = [];
for (let task of tasks) { for (let task of tasks) {
if (task.level != level) { if (task.level != level) {
@ -214,7 +214,7 @@ export default {
return levelTasks; return levelTasks;
}; };
let levelsTasks = function(tasks, startLevel) { let levelsTasks = function (tasks, startLevel) {
let levelTasks = []; let levelTasks = [];
for (let task of tasks) { for (let task of tasks) {
if (task.level < startLevel) { if (task.level < startLevel) {
@ -225,13 +225,13 @@ export default {
return levelTasks; return levelTasks;
}; };
let levelsTasksByRow = function(tasks, startLevel) { let levelsTasksByRow = function (tasks, startLevel) {
return levelsTasks(tasks, startLevel).sort((a, b) => return levelsTasks(tasks, startLevel).sort((a, b) =>
a.row > b.row ? 1 : b.row > a.row ? -1 : 0 a.row > b.row ? 1 : b.row > a.row ? -1 : 0
); );
}; };
let levelFreeRow = function(tasks, level) { let levelFreeRow = function (tasks, level) {
let rows = []; let rows = [];
for (let task of tasks) { for (let task of tasks) {
if (task.level != level) { if (task.level != level) {
@ -255,7 +255,7 @@ export default {
return prevrow; return prevrow;
}; };
let levelsMaxRow = function(tasks, level) { let levelsMaxRow = function (tasks, level) {
let row = 0; let row = 0;
for (let task of tasks) { for (let task of tasks) {
if (level >= 0 && task.level > level) { if (level >= 0 && task.level > level) {
@ -344,7 +344,7 @@ export default {
sourceTask: pTask, sourceTask: pTask,
targetTask: curTask, targetTask: curTask,
source: { level: pTask.level, row: pTask.row }, source: { level: pTask.level, row: pTask.row },
target: { level: curTask.level, row: curTask.row } target: { level: curTask.level, row: curTask.row },
}); });
} }
} }
@ -362,30 +362,30 @@ export default {
edge.edgePoints.push({ edge.edgePoints.push({
x: (taskWidth + taskXSpace) * edge.source.level + taskWidth, x: (taskWidth + taskXSpace) * edge.source.level + taskWidth,
y: (taskHeight + taskYSpace) * edge.source.row + taskHeight / 2 y: (taskHeight + taskYSpace) * edge.source.row + taskHeight / 2,
}); });
edge.edgePoints.push({ edge.edgePoints.push({
x: (taskWidth + taskXSpace) * edge.target.level - taskXSpace / 2, x: (taskWidth + taskXSpace) * edge.target.level - taskXSpace / 2,
y: (taskHeight + taskYSpace) * edge.source.row + taskHeight / 2 y: (taskHeight + taskYSpace) * edge.source.row + taskHeight / 2,
}); });
edge.edgePoints.push({ edge.edgePoints.push({
x: (taskWidth + taskXSpace) * edge.target.level - taskXSpace / 2, x: (taskWidth + taskXSpace) * edge.target.level - taskXSpace / 2,
y: (taskHeight + taskYSpace) * edge.target.row + taskHeight / 2 y: (taskHeight + taskYSpace) * edge.target.row + taskHeight / 2,
}); });
edge.edgePoints.push({ edge.edgePoints.push({
x: (taskWidth + taskXSpace) * edge.target.level, x: (taskWidth + taskXSpace) * edge.target.level,
y: (taskHeight + taskYSpace) * edge.target.row + taskHeight / 2 y: (taskHeight + taskYSpace) * edge.target.row + taskHeight / 2,
}); });
} }
let width = (maxlevel + 1) * (this.taskWidth + this.taskXSpace); let width = (maxlevel + 1) * (this.taskWidth + this.taskXSpace);
this.width = width + "px"; this.width = width + 'px';
let height = let height =
(levelsMaxRow(graphTasks, -1) + 1) * (levelsMaxRow(graphTasks, -1) + 1) *
(this.taskHeight + this.taskYSpace); (this.taskHeight + this.taskYSpace);
this.height = height + "px"; this.height = height + 'px';
} },
}, },
created() { created() {
this.update(this.tasks); this.update(this.tasks);
@ -393,6 +393,6 @@ export default {
window.setInterval(() => { window.setInterval(() => {
this.now = moment(); this.now = moment();
}, 500); }, 500);
} },
}; };
</script> </script>

View File

@ -8,22 +8,30 @@
<div>Error fetching Run: {{ fetchRunError }}</div> <div>Error fetching Run: {{ fetchRunError }}</div>
<div>Error fetching Task: {{ fetchTaskError }}</div> <div>Error fetching Task: {{ fetchTaskError }}</div>
</div> </div>
<rundetail :run="run" :ownertype="ownertype" :ownername="ownername" :projectref="projectref" /> <rundetail
:run="run"
:ownertype="ownertype"
:ownername="ownername"
:projectref="projectref"
/>
<div v-if="task != null"> <div v-if="task != null">
<div class="mt-8 mb-4 flex justify-between items-center"> <div class="mt-8 mb-4 flex justify-between items-center">
<div class="flex items-center"> <div class="flex items-center">
<span class="text-2xl mr-3">{{task.name}}</span> <span class="text-2xl mr-3">{{ task.name }}</span>
<span <span
class="mr-3 rounded px-2 py-1 text-xs" class="mr-3 rounded px-2 py-1 text-xs"
:class="taskClass(task)" :class="taskClass(task)"
>{{ task.status | capitalize }}</span> >{{ task.status | capitalize }}</span
>
</div> </div>
<button <button
v-if="task.waiting_approval" v-if="task.waiting_approval"
class="btn btn-blue" class="btn btn-blue"
@click="approveTask(run.id, task.id)" @click="approveTask(run.id, task.id)"
>Approve</button> >
Approve
</button>
</div> </div>
<step <step
v-bind:runid="runid" v-bind:runid="runid"
@ -44,23 +52,23 @@
</template> </template>
<script> <script>
import { fetchRun, fetchTask, approveTask } from "@/util/data.js"; import { fetchRun, fetchTask, approveTask } from '@/util/data.js';
import step from "@/components/step.vue"; import step from '@/components/step.vue';
import rundetail from "@/components/rundetail.vue"; import rundetail from '@/components/rundetail.vue';
export default { export default {
components: { components: {
step, step,
rundetail rundetail,
}, },
name: "tasksummary", name: 'tasksummary',
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectref: Array, projectref: Array,
runid: String, runid: String,
taskid: String taskid: String,
}, },
data() { data() {
return { return {
@ -70,11 +78,11 @@ export default {
fetchTaskError: null, fetchTaskError: null,
run: null, run: null,
task: null task: null,
}; };
}, },
watch: { watch: {
$route: async function() { $route: async function () {
if (this.fetchAbort) { if (this.fetchAbort) {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
@ -85,16 +93,16 @@ export default {
this.fetchRun(); this.fetchRun();
this.fetchTask(); this.fetchTask();
} },
}, },
methods: { methods: {
taskClass(task) { taskClass(task) {
if (task.status == "success") return "is-success"; if (task.status == 'success') return 'is-success';
if (task.status == "failed") return "is-failed"; if (task.status == 'failed') return 'is-failed';
if (task.status == "stopped") return "is-failed"; if (task.status == 'stopped') return 'is-failed';
if (task.status == "running") return "is-running"; if (task.status == 'running') return 'is-running';
if (task.status == "skipped") return "is-skipped"; if (task.status == 'skipped') return 'is-skipped';
return "unknown"; return 'unknown';
}, },
async fetchRun() { async fetchRun() {
let { data, error, aborted } = await fetchRun( let { data, error, aborted } = await fetchRun(
@ -143,9 +151,9 @@ export default {
this.fetchTask(); this.fetchTask();
}, 2000); }, 2000);
}, },
approveTask: approveTask approveTask: approveTask,
}, },
created: function() { created: function () {
this.fetchAbort = new AbortController(); this.fetchAbort = new AbortController();
this.fetchRun(); this.fetchRun();
@ -157,9 +165,8 @@ export default {
} }
clearTimeout(this.fetchRunSchedule); clearTimeout(this.fetchRunSchedule);
clearTimeout(this.fetchTaskSchedule); clearTimeout(this.fetchTaskSchedule);
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -7,7 +7,10 @@
class="mb-4 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded" class="mb-4 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded"
role="alert" role="alert"
> >
<p>Removing a Linked Account will also block all the projects that uses this Linked Account to access their remote repository</p> <p>
Removing a Linked Account will also block all the projects that uses
this Linked Account to access their remote repository
</p>
</div> </div>
<ul v-if="user.linked_accounts"> <ul v-if="user.linked_accounts">
<li <li
@ -16,10 +19,14 @@
v-bind:key="index" v-bind:key="index"
> >
<div> <div>
<span class="font-bold">{{la.remote_user_name}}</span> <span class="font-bold">{{ la.remote_user_name }}</span>
<span class="ml-1">(on remote source {{laRemoteSourceName(la)}})</span> <span class="ml-1"
>(on remote source {{ laRemoteSourceName(la) }})</span
>
</div> </div>
<button class="btn btn-red" @click="deleteLinkedAccount(la)">Delete</button> <button class="btn btn-red" @click="deleteLinkedAccount(la)">
Delete
</button>
</li> </li>
<div <div
v-if="deleteLinkedAccountError" v-if="deleteLinkedAccountError"
@ -40,9 +47,13 @@
class="block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline" class="block appearance-none w-full bg-white border border-gray-400 hover:border-gray-500 px-4 py-2 pr-8 rounded shadow leading-tight focus:outline-none focus:shadow-outline"
v-model="selectedRemoteSourceName" v-model="selectedRemoteSourceName"
> >
<option v-for="rs in remotesources" v-bind:key="rs.id">{{ rs.name }}</option> <option v-for="rs in remotesources" v-bind:key="rs.id">
{{ rs.name }}
</option>
</select> </select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2"> <div
class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2"
>
<svg <svg
class="fill-current h-4 w-4" class="fill-current h-4 w-4"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -56,7 +67,9 @@
</div> </div>
</div> </div>
<button class="ml-3 btn btn-blue" @click="addLinkedAccount()">Add Linked Account</button> <button class="ml-3 btn btn-blue" @click="addLinkedAccount()">
Add Linked Account
</button>
</div> </div>
</div> </div>
@ -69,8 +82,10 @@
v-for="token in user.tokens" v-for="token in user.tokens"
v-bind:key="token" v-bind:key="token"
> >
<span class="font-bold">{{token}}</span> <span class="font-bold">{{ token }}</span>
<button class="btn btn-red" @click="deleteUserToken(token)">Delete</button> <button class="btn btn-red" @click="deleteUserToken(token)">
Delete
</button>
</li> </li>
<div <div
v-if="deleteUserTokenError" v-if="deleteUserTokenError"
@ -102,8 +117,10 @@
</svg> </svg>
</div> </div>
<div> <div>
<p class="font-bold">User token created. Copy it now since it won't be showed again</p> <p class="font-bold">
<p class="text-sm">{{token}}</p> User token created. Copy it now since it won't be showed again
</p>
<p class="text-sm">{{ token }}</p>
</div> </div>
</div> </div>
<span <span
@ -130,8 +147,10 @@
type="text" type="text"
placeholder="Token name" placeholder="Token name"
v-model="newtokenname" v-model="newtokenname"
> />
<button class="ml-3 btn btn-blue" @click="createUserToken()">Create Token</button> <button class="ml-3 btn btn-blue" @click="createUserToken()">
Create Token
</button>
</div> </div>
<div <div
v-if="createUserTokenError" v-if="createUserTokenError"
@ -151,14 +170,14 @@ import {
fetchRemoteSources, fetchRemoteSources,
createUserToken, createUserToken,
deleteUserToken, deleteUserToken,
deleteLinkedAccount deleteLinkedAccount,
} from "@/util/data.js"; } from '@/util/data.js';
import { userAddLinkedAccountLink } from "@/util/link.js"; import { userAddLinkedAccountLink } from '@/util/link.js';
export default { export default {
components: {}, components: {},
name: "usersettings", name: 'usersettings',
props: {}, props: {},
data() { data() {
return { return {
@ -169,7 +188,7 @@ export default {
remotesources: [], remotesources: [],
token: null, token: null,
newtokenname: null, newtokenname: null,
selectedRemoteSourceName: null selectedRemoteSourceName: null,
}; };
}, },
methods: { methods: {
@ -181,7 +200,7 @@ export default {
async fetchCurrentUser() { async fetchCurrentUser() {
let { data, error } = await fetchCurrentUser(); let { data, error } = await fetchCurrentUser();
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.user = data; this.user = data;
@ -189,7 +208,7 @@ export default {
async fetchRemoteSources() { async fetchRemoteSources() {
let { data, error } = await fetchRemoteSources(); let { data, error } = await fetchRemoteSources();
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.remotesources = data; this.remotesources = data;
@ -249,15 +268,13 @@ export default {
return; return;
} }
this.fetchCurrentUser(); this.fetchCurrentUser();
} },
}, },
created: function() { created: function () {
this.fetchCurrentUser(); this.fetchCurrentUser();
this.fetchRemoteSources(); this.fetchRemoteSources();
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -18,21 +18,22 @@
</div> </div>
<div class="flex" v-for="variable in variables" v-bind:key="variable.id"> <div class="flex" v-for="variable in variables" v-bind:key="variable.id">
<div class="w-2/12"> <div class="w-2/12">
<span class="name">{{variable.name}}</span> <span class="name">{{ variable.name }}</span>
<div v-if="showparentpath" class="text-sm font-light">from {{variable.parent_path}}</div> <div v-if="showparentpath" class="text-sm font-light">
from {{ variable.parent_path }}
</div>
</div> </div>
<div class="w-10/12"> <div class="w-10/12">
<div class="flex" v-for="val in variable.values" v-bind:key="val.id"> <div class="flex" v-for="val in variable.values" v-bind:key="val.id">
<div class="w-2/12"> <div class="w-2/12">
<span>{{val.secret_name}}</span> <span>{{ val.secret_name }}</span>
<div <div v-if="val.matching_secret_parent_path" class="text-sm">
v-if="val.matching_secret_parent_path" using secret from {{ val.matching_secret_parent_path }}
class="text-sm" </div>
>using secret from {{val.matching_secret_parent_path}}</div>
<div v-else class="text-sm text-red-600">no matching secret</div> <div v-else class="text-sm text-red-600">no matching secret</div>
</div> </div>
<div class="w-2/12"> <div class="w-2/12">
<span>{{val.secret_var}}</span> <span>{{ val.secret_var }}</span>
</div> </div>
<div class="w-8/12"> <div class="w-8/12">
<div v-if="val.when"> <div v-if="val.when">
@ -43,13 +44,19 @@
<span>{{ type }}</span> <span>{{ type }}</span>
</div> </div>
<div class="w-1/3"> <div class="w-1/3">
<div v-for="include in val.when[type].include" v-bind:key="include.match"> <div
<div>{{include.match}}</div> v-for="include in val.when[type].include"
v-bind:key="include.match"
>
<div>{{ include.match }}</div>
</div> </div>
</div> </div>
<div class="w-1/3"> <div class="w-1/3">
<div v-for="exclude in val.when[type].exclude" v-bind:key="exclude.match"> <div
<div>{{exclude.match}}</div> v-for="exclude in val.when[type].exclude"
v-bind:key="exclude.match"
>
<div>{{ exclude.match }}</div>
</div> </div>
</div> </div>
</div> </div>
@ -66,15 +73,12 @@
<script> <script>
export default { export default {
components: {}, components: {},
name: "vars", name: 'vars',
props: { props: {
variables: Array, variables: Array,
showparentpath: Boolean showparentpath: Boolean,
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -1,7 +1,7 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@import "@/css/_ansi.scss"; @import '@/css/_ansi.scss';
.btn { .btn {
@apply font-bold py-2 px-4 rounded; @apply font-bold py-2 px-4 rounded;
@ -119,7 +119,7 @@
border-radius: 290486px; border-radius: 290486px;
border-right-color: transparent; border-right-color: transparent;
border-top-color: transparent; border-top-color: transparent;
content: ""; content: '';
display: block; display: block;
width: 1em; width: 1em;
height: 1em; height: 1em;

View File

@ -1,11 +1,11 @@
import "@/css/tailwind.scss"; import '@/css/tailwind.scss';
import { getUser } from "@/util/auth"; import { getUser } from '@/util/auth';
import "@mdi/font/css/materialdesignicons.css"; import '@mdi/font/css/materialdesignicons.css';
import Vue from "vue"; import Vue from 'vue';
import Vue2Filters from "vue2-filters"; import Vue2Filters from 'vue2-filters';
import App from "./App.vue"; import App from './App.vue';
import router from "./router"; import router from './router';
import store from "./store"; import store from './store';
Vue.use(Vue2Filters); Vue.use(Vue2Filters);
@ -13,12 +13,12 @@ Vue.use(Vue2Filters);
new Vue({ new Vue({
router, router,
store, store,
created: function() { created: function () {
let user = getUser(); let user = getUser();
if (user) { if (user) {
store.dispatch("setUser", user); store.dispatch('setUser', user);
} }
store.dispatch("setRegisterUser", null); store.dispatch('setRegisterUser', null);
}, },
render: h => h(App) render: (h) => h(App),
}).$mount("#app"); }).$mount('#app');

View File

@ -1,249 +1,348 @@
import Vue from "vue"; import Vue from 'vue';
import VueRouter from "vue-router"; import VueRouter from 'vue-router';
import Home from "./views/Home.vue"; import Home from './views/Home.vue';
import User from "./views/User.vue"; import User from './views/User.vue';
import Org from "./views/Org.vue"; import Org from './views/Org.vue';
import Project from "./views/Project.vue"; import Project from './views/Project.vue';
import ProjectGroup from "./views/ProjectGroup.vue"; import ProjectGroup from './views/ProjectGroup.vue';
import AddLinkedAccount from "./views/AddLinkedAccount.vue"; import AddLinkedAccount from './views/AddLinkedAccount.vue';
import usersettings from "./components/usersettings.vue"; import usersettings from './components/usersettings.vue';
import projects from "./components/projects.vue"; import projects from './components/projects.vue';
import projectsettings from "./components/projectsettings.vue"; import projectsettings from './components/projectsettings.vue';
import projectgroupsettings from "./components/projectgroupsettings.vue"; import projectgroupsettings from './components/projectgroupsettings.vue';
import createproject from "./components/createproject.vue"; import createproject from './components/createproject.vue';
import createprojectgroup from "./components/createprojectgroup.vue"; import createprojectgroup from './components/createprojectgroup.vue';
import createorganization from "./components/createorganization.vue"; import createorganization from './components/createorganization.vue';
import orgmembers from "./components/orgmembers.vue"; import orgmembers from './components/orgmembers.vue';
import runs from "./components/runs.vue"; import runs from './components/runs.vue';
import runsummary from "./components/runsummary.vue"; import runsummary from './components/runsummary.vue';
import tasksummary from "./components/tasksummary.vue"; import tasksummary from './components/tasksummary.vue';
import Oauth2 from "./views/Oauth2.vue"; import Oauth2 from './views/Oauth2.vue';
import Register from "./views/Register.vue"; import Register from './views/Register.vue';
import Login from "./views/Login.vue"; import Login from './views/Login.vue';
import Logout from "./views/Logout.vue"; import Logout from './views/Logout.vue';
import CreateSource from "./views/CreateSource.vue"; import CreateSource from './views/CreateSource.vue';
import { parseRef, projectRunLink } from "@/util/link.js"; import { parseRef, projectRunLink } from '@/util/link.js';
import { fetchProject } from "@/util/data.js"; import { fetchProject } from '@/util/data.js';
import store from "./store"; import store from './store';
Vue.use(VueRouter); Vue.use(VueRouter);
const router = new VueRouter({ const router = new VueRouter({
mode: "history", mode: 'history',
routes: [ routes: [
{ {
path: "/register", path: '/register',
name: "register", name: 'register',
component: Register, component: Register,
}, },
{ {
path: "/newsource", path: '/newsource',
name: "newsource", name: 'newsource',
component: CreateSource, component: CreateSource,
}, },
{ {
path: "/login", path: '/login',
name: "login", name: 'login',
component: Login component: Login,
}, },
{ {
path: "/logout", path: '/logout',
name: "logout", name: 'logout',
component: Logout component: Logout,
}, },
{ {
path: "/oauth2/callback", path: '/oauth2/callback',
name: "oauth2callback", name: 'oauth2callback',
component: Oauth2 component: Oauth2,
}, },
{ {
path: "/", path: '/',
name: "home", name: 'home',
component: Home component: Home,
}, },
{ {
path: "/neworganization", path: '/neworganization',
component: createorganization, component: createorganization,
}, },
{ {
path: "/user/:username", path: '/user/:username',
component: User, component: User,
props: (route) => ({ username: route.params.username }), props: (route) => ({ username: route.params.username }),
children: [ children: [
{ {
path: "", path: '',
name: "user", name: 'user',
component: projects, component: projects,
props: (route) => ({ ownertype: "user", ownername: route.params.username }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
}),
}, },
{ {
path: "projects", path: 'projects',
name: "user projects", name: 'user projects',
component: projects, component: projects,
props: (route) => ({ ownertype: "user", ownername: route.params.username }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
}),
}, },
{ {
path: "runs", path: 'runs',
name: "user direct runs", name: 'user direct runs',
component: runs, component: runs,
props: (route) => ({ ownertype: "user", ownername: route.params.username }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
}),
}, },
{ {
path: "runs/:runid", path: 'runs/:runid',
name: "user direct run", name: 'user direct run',
component: runsummary, component: runsummary,
props: (route) => ({ ownertype: "user", ownername: route.params.username, runid: route.params.runid }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
runid: route.params.runid,
}),
}, },
{ {
path: "runs/:runid/tasks/:taskid", path: 'runs/:runid/tasks/:taskid',
name: "user direct run task", name: 'user direct run task',
component: tasksummary, component: tasksummary,
props: (route) => ({ ownertype: "user", ownername: route.params.username, runid: route.params.runid, taskid: route.params.taskid }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
runid: route.params.runid,
taskid: route.params.taskid,
}),
}, },
{ {
path: "settings", path: 'settings',
name: "user settings", name: 'user settings',
component: usersettings, component: usersettings,
props: (route) => ({ username: route.params.username }), props: (route) => ({ username: route.params.username }),
}, },
{ {
path: "linkedaccounts/add/:remotesource", path: 'linkedaccounts/add/:remotesource',
name: "user add linked account", name: 'user add linked account',
component: AddLinkedAccount, component: AddLinkedAccount,
props: (route) => ({ username: route.params.username, remoteSourceName: route.params.remotesource }) props: (route) => ({
username: route.params.username,
remoteSourceName: route.params.remotesource,
}),
}, },
{ {
path: "createprojectgroup", path: 'createprojectgroup',
name: "user create project group", name: 'user create project group',
component: createprojectgroup, component: createprojectgroup,
props: (route) => ({ ownertype: "user", ownername: route.params.username }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
}),
}, },
{ {
path: "createproject", path: 'createproject',
name: "user create project", name: 'user create project',
component: createproject, component: createproject,
props: (route) => ({ ownertype: "user", ownername: route.params.username }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
}),
}, },
] ],
}, },
{ {
path: "/user/:username/projects/:projectref(.*\\.proj)", path: '/user/:username/projects/:projectref(.*\\.proj)',
component: Project, component: Project,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref) }), props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectref: parseRef(route.params.projectref),
}),
children: [ children: [
{ {
path: "", path: '',
name: "user project", name: 'user project',
component: runs, component: runs,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref) }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectref: parseRef(route.params.projectref),
}),
}, },
{ {
path: "runs", path: 'runs',
name: "user project runs", name: 'user project runs',
component: runs, component: runs,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref) }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectref: parseRef(route.params.projectref),
}),
}, },
{ {
path: "branches", path: 'branches',
name: "user project branches runs", name: 'user project branches runs',
component: runs, component: runs,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref), query: "branches" }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectref: parseRef(route.params.projectref),
query: 'branches',
}),
}, },
{ {
path: "tags", path: 'tags',
name: "user project tags runs", name: 'user project tags runs',
component: runs, component: runs,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref), query: "tags" }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectref: parseRef(route.params.projectref),
query: 'tags',
}),
}, },
{ {
path: "pullrequests", path: 'pullrequests',
name: "user project pull requests runs", name: 'user project pull requests runs',
component: runs, component: runs,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref), query: "pullrequests" }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectref: parseRef(route.params.projectref),
query: 'pullrequests',
}),
}, },
{ {
path: "runs/:runid", path: 'runs/:runid',
name: "user project run", name: 'user project run',
component: runsummary, component: runsummary,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref), runid: route.params.runid }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectref: parseRef(route.params.projectref),
runid: route.params.runid,
}),
}, },
{ {
path: "runs/:runid/tasks/:taskid", path: 'runs/:runid/tasks/:taskid',
name: "user project run task", name: 'user project run task',
component: tasksummary, component: tasksummary,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref), runid: route.params.runid, taskid: route.params.taskid }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectref: parseRef(route.params.projectref),
runid: route.params.runid,
taskid: route.params.taskid,
}),
}, },
{ {
path: "settings", path: 'settings',
name: "user project settings", name: 'user project settings',
component: projectsettings, component: projectsettings,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectref: parseRef(route.params.projectref) }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectref: parseRef(route.params.projectref),
}),
}, },
] ],
}, },
{ {
path: "/user/:username/projectgroups/:projectgroupref(.*\\.proj)", path: '/user/:username/projectgroups/:projectgroupref(.*\\.proj)',
component: ProjectGroup, component: ProjectGroup,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectgroupref: parseRef(route.params.projectgroupref) }), props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectgroupref: parseRef(route.params.projectgroupref),
}),
children: [ children: [
{ {
path: "", path: '',
name: "user project group", name: 'user project group',
component: projects, component: projects,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectgroupref: parseRef(route.params.projectgroupref) }), props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectgroupref: parseRef(route.params.projectgroupref),
}),
}, },
{ {
path: "projects", path: 'projects',
name: "user project group projects", name: 'user project group projects',
component: projects, component: projects,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectgroupref: parseRef(route.params.projectgroupref) }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectgroupref: parseRef(route.params.projectgroupref),
}),
}, },
{ {
path: "settings", path: 'settings',
name: "user project group settings", name: 'user project group settings',
component: projectgroupsettings, component: projectgroupsettings,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectgroupref: parseRef(route.params.projectgroupref) }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectgroupref: parseRef(route.params.projectgroupref),
}),
}, },
{ {
path: "createprojectgroup", path: 'createprojectgroup',
name: "user project group create project group", name: 'user project group create project group',
component: createprojectgroup, component: createprojectgroup,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectgroupref: parseRef(route.params.projectgroupref) }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectgroupref: parseRef(route.params.projectgroupref),
}),
}, },
{ {
path: "createproject", path: 'createproject',
name: "user project group create project", name: 'user project group create project',
component: createproject, component: createproject,
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectgroupref: parseRef(route.params.projectgroupref) }) props: (route) => ({
ownertype: 'user',
ownername: route.params.username,
projectgroupref: parseRef(route.params.projectgroupref),
}),
}, },
] ],
}, },
{ {
path: "/org/:orgname", path: '/org/:orgname',
component: Org, component: Org,
props: (route) => ({ orgname: route.params.orgname }), props: (route) => ({ orgname: route.params.orgname }),
children: [ children: [
{ {
path: "", path: '',
name: "org", name: 'org',
component: projects, component: projects,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
}),
}, },
{ {
path: "projects", path: 'projects',
name: "org projects", name: 'org projects',
component: projects, component: projects,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
}),
}, },
{ {
path: "members", path: 'members',
name: "org members", name: 'org members',
component: orgmembers, component: orgmembers,
props: (route) => ({ orgname: route.params.orgname }) props: (route) => ({ orgname: route.params.orgname }),
}, },
/* { /* {
path: "settings", path: "settings",
@ -252,141 +351,213 @@ const router = new VueRouter({
props: (route) => ({ username: route.params.username }), props: (route) => ({ username: route.params.username }),
}, */ }, */
{ {
path: "createprojectgroup", path: 'createprojectgroup',
name: "org create project group", name: 'org create project group',
component: createprojectgroup, component: createprojectgroup,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
}),
}, },
{ {
path: "createproject", path: 'createproject',
name: "org create project", name: 'org create project',
component: createproject, component: createproject,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
}),
}, },
] ],
}, },
{ {
path: "/org/:orgname/projects/:projectref(.*\\.proj)", path: '/org/:orgname/projects/:projectref(.*\\.proj)',
component: Project, component: Project,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref) }), props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectref: parseRef(route.params.projectref),
}),
children: [ children: [
{ {
path: "", path: '',
name: "org project", name: 'org project',
component: runs, component: runs,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref) }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectref: parseRef(route.params.projectref),
}),
}, },
{ {
path: "runs", path: 'runs',
name: "org project runs", name: 'org project runs',
component: runs, component: runs,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref) }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectref: parseRef(route.params.projectref),
}),
}, },
{ {
path: "branches", path: 'branches',
name: "org project branches runs", name: 'org project branches runs',
component: runs, component: runs,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref), query: "branches" }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectref: parseRef(route.params.projectref),
query: 'branches',
}),
}, },
{ {
path: "tags", path: 'tags',
name: "org project tags runs", name: 'org project tags runs',
component: runs, component: runs,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref), query: "tags" }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectref: parseRef(route.params.projectref),
query: 'tags',
}),
}, },
{ {
path: "pullrequests", path: 'pullrequests',
name: "org project pull requests runs", name: 'org project pull requests runs',
component: runs, component: runs,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref), query: "pullrequests" }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectref: parseRef(route.params.projectref),
query: 'pullrequests',
}),
}, },
{ {
path: "runs/:runid", path: 'runs/:runid',
name: "org project run", name: 'org project run',
component: runsummary, component: runsummary,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref), runid: route.params.runid }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectref: parseRef(route.params.projectref),
runid: route.params.runid,
}),
}, },
{ {
path: "runs/:runid/tasks/:taskid", path: 'runs/:runid/tasks/:taskid',
name: "org project run task", name: 'org project run task',
component: tasksummary, component: tasksummary,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref), runid: route.params.runid, taskid: route.params.taskid }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectref: parseRef(route.params.projectref),
runid: route.params.runid,
taskid: route.params.taskid,
}),
}, },
{ {
path: "settings", path: 'settings',
name: "org project settings", name: 'org project settings',
component: projectsettings, component: projectsettings,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectref: parseRef(route.params.projectref) }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectref: parseRef(route.params.projectref),
}),
}, },
] ],
}, },
{ {
path: "/org/:orgname/projectgroups/:projectgroupref(.*\\.proj)", path: '/org/:orgname/projectgroups/:projectgroupref(.*\\.proj)',
component: ProjectGroup, component: ProjectGroup,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectgroupref: parseRef(route.params.projectgroupref) }), props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectgroupref: parseRef(route.params.projectgroupref),
}),
children: [ children: [
{ {
path: "", path: '',
name: "org project group", name: 'org project group',
component: projects, component: projects,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectgroupref: parseRef(route.params.projectgroupref) }), props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectgroupref: parseRef(route.params.projectgroupref),
}),
}, },
{ {
path: "projects", path: 'projects',
name: "org project group projects", name: 'org project group projects',
component: projects, component: projects,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectgroupref: parseRef(route.params.projectgroupref) }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectgroupref: parseRef(route.params.projectgroupref),
}),
}, },
{ {
path: "settings", path: 'settings',
name: "org project group settings", name: 'org project group settings',
component: projectgroupsettings, component: projectgroupsettings,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectgroupref: parseRef(route.params.projectgroupref) }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectgroupref: parseRef(route.params.projectgroupref),
}),
}, },
{ {
path: "createprojectgroup", path: 'createprojectgroup',
name: "org project group create project group", name: 'org project group create project group',
component: createprojectgroup, component: createprojectgroup,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectgroupref: parseRef(route.params.projectgroupref) }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectgroupref: parseRef(route.params.projectgroupref),
}),
}, },
{ {
path: "createproject", path: 'createproject',
name: "org project group create project", name: 'org project group create project',
component: createproject, component: createproject,
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectgroupref: parseRef(route.params.projectgroupref) }) props: (route) => ({
ownertype: 'org',
ownername: route.params.orgname,
projectgroupref: parseRef(route.params.projectgroupref),
}),
}, },
] ],
}, },
] ],
}); });
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
store.dispatch("setError", null); store.dispatch('setError', null);
const { path, query } = to const { path, query } = to;
if (path == "/run") { if (path == '/run') {
// generic run handler by projectref and runid // generic run handler by projectref and runid
let projectref = query.projectref let projectref = query.projectref;
let runid = query.runid let runid = query.runid;
let { data, error } = await fetchProject(projectref); let { data, error } = await fetchProject(projectref);
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
let project = data; let project = data;
let parts = project.path.split("/") let parts = project.path.split('/');
let nextPath = projectRunLink(parts[0], parts[1], parts.slice(2), runid) let nextPath = projectRunLink(parts[0], parts[1], parts.slice(2), runid);
next({ path: nextPath.path, replace: true }) next({ path: nextPath.path, replace: true });
} }
next() next();
}) });
export default router export default router;

View File

@ -1,55 +1,54 @@
import Vue from 'vue' import Vue from 'vue';
import Vuex from 'vuex' import Vuex from 'vuex';
Vue.use(Vuex) Vue.use(Vuex);
const state = { const state = {
error: null, error: null,
user: null, user: null,
registeruser: null, registeruser: null,
} };
const getters = { const getters = {
// global error // global error
error: state => { error: (state) => {
return state.error return state.error;
}, },
user: state => { user: (state) => {
return state.user return state.user;
}, },
registeruser: state => { registeruser: (state) => {
return state.registeruser return state.registeruser;
} },
} };
const mutations = { const mutations = {
setError(state, error) { setError(state, error) {
state.error = error state.error = error;
}, },
setUser(state, user) { setUser(state, user) {
state.user = user state.user = user;
}, },
setRegisterUser(state, user) { setRegisterUser(state, user) {
state.registeruser = user state.registeruser = user;
} },
} };
const actions = { const actions = {
setError({ commit }, error) { setError({ commit }, error) {
commit('setError', error) commit('setError', error);
}, },
setUser({ commit }, user) { setUser({ commit }, user) {
commit('setUser', user) commit('setUser', user);
}, },
setRegisterUser({ commit }, user) { setRegisterUser({ commit }, user) {
commit('setRegisterUser', user) commit('setRegisterUser', user);
} },
} };
export default new Vuex.Store({ export default new Vuex.Store({
state, state,
getters, getters,
actions, actions,
mutations, mutations,
}) });

View File

@ -1,8 +1,8 @@
import store from "@/store"; import store from '@/store';
const ID_TOKEN_KEY = "id_token"; const ID_TOKEN_KEY = 'id_token';
const USER_KEY = "user"; const USER_KEY = 'user';
const LOGIN_REDIRECT_KEY = "login_redirect"; const LOGIN_REDIRECT_KEY = 'login_redirect';
let API_URL = window.CONFIG.API_URL; let API_URL = window.CONFIG.API_URL;
let API_BASE_PATH = window.CONFIG.API_BASE_PATH; let API_BASE_PATH = window.CONFIG.API_BASE_PATH;
@ -10,20 +10,20 @@ let API_BASE_PATH = window.CONFIG.API_BASE_PATH;
export function setLoggedUser(token, user) { export function setLoggedUser(token, user) {
setIdToken(token); setIdToken(token);
setUser(user); setUser(user);
store.dispatch("setUser", user); store.dispatch('setUser', user);
} }
export function doLogout() { export function doLogout() {
unsetIdToken(); unsetIdToken();
unsetUser(); unsetUser();
store.dispatch("setUser", null); store.dispatch('setUser', null);
} }
export function apiurlwithtoken(path) { export function apiurlwithtoken(path) {
let u = new URL(API_URL + API_BASE_PATH + path); let u = new URL(API_URL + API_BASE_PATH + path);
let idToken = getIdToken(); let idToken = getIdToken();
if (idToken) { if (idToken) {
u.searchParams.append("access_token", idToken); u.searchParams.append('access_token', idToken);
} }
return u; return u;
} }
@ -33,19 +33,19 @@ export function apiurl(path) {
} }
export function loginurl() { export function loginurl() {
return apiurl("/auth/login"); return apiurl('/auth/login');
} }
export function authorizeurl() { export function authorizeurl() {
return apiurl("/auth/authorize"); return apiurl('/auth/authorize');
} }
export function registerurl() { export function registerurl() {
return new apiurl("/auth/register"); return new apiurl('/auth/register');
} }
export function oauth2callbackurl() { export function oauth2callbackurl() {
return new apiurl("/auth/oauth2/callback"); return new apiurl('/auth/oauth2/callback');
} }
export async function loginapi(init) { export async function loginapi(init) {
@ -64,19 +64,19 @@ export async function registerapi(init) {
return await window.fetch(registerurl(), init); return await window.fetch(registerurl(), init);
} }
export async function fetch(url, init, signal, token, tokenType = "bearer") { export async function fetch(url, init, signal, token, tokenType = 'bearer') {
if (!init) { if (!init) {
init = {}; init = {};
} }
if (init.headers === undefined) { if (init.headers === undefined) {
init["headers"] = {}; init['headers'] = {};
} }
if (signal) { if (signal) {
init["signal"] = signal; init['signal'] = signal;
} }
let idToken = token || getIdToken(); let idToken = token || getIdToken();
if (idToken) { if (idToken) {
init.headers["Authorization"] = tokenType + " " + idToken; init.headers['Authorization'] = tokenType + ' ' + idToken;
} }
return await window.fetch(url, init); return await window.fetch(url, init);

View File

@ -1,8 +1,9 @@
import router from "@/router"; import router from '@/router';
import { apiurl, fetch as authfetch, loginapi, registerapi } from "@/util/auth"; import { apiurl, fetch as authfetch, loginapi, registerapi } from '@/util/auth';
export const GITHUB_API_URL = "https://api.github.com"; export const GITHUB_API_URL = 'https://api.github.com';
export const GITHUB_SSH_KEY = "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=="; export const GITHUB_SSH_KEY =
'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==';
export async function fetch(url, init, signal, token, tokenType) { export async function fetch(url, init, signal, token, tokenType) {
try { try {
@ -10,8 +11,8 @@ export async function fetch(url, init, signal, token, tokenType) {
if (!res.ok) { if (!res.ok) {
if (res.status === 401) { if (res.status === 401) {
router.push({ router.push({
name: "login", name: 'login',
query: { redirect: router.currentRoute.fullPath } query: { redirect: router.currentRoute.fullPath },
}); });
// if we return a response containing an error what happens is // if we return a response containing an error what happens is
// that router.push mounts the login view before the calling // that router.push mounts the login view before the calling
@ -36,21 +37,21 @@ export async function fetch(url, init, signal, token, tokenType) {
return { data: await res.json(), error: null }; return { data: await res.json(), error: null };
} }
} catch (e) { } catch (e) {
if (e.name == "AbortError") { if (e.name == 'AbortError') {
return { data: null, error: null, aborted: true }; return { data: null, error: null, aborted: true };
} }
return { data: null, error: "api call failed: " + e.message }; return { data: null, error: 'api call failed: ' + e.message };
} }
} }
export async function login(username, password, remotesourcename) { export async function login(username, password, remotesourcename) {
let init = { let init = {
method: "POST", method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
remote_source_name: remotesourcename, remote_source_name: remotesourcename,
login_name: username, login_name: username,
password: password password: password,
}) }),
}; };
try { try {
@ -62,7 +63,7 @@ export async function login(username, password, remotesourcename) {
return { data: await res.json(), error: null }; return { data: await res.json(), error: null };
} }
} catch (e) { } catch (e) {
return { data: null, error: "api call failed: " + e.message }; return { data: null, error: 'api call failed: ' + e.message };
} }
} }
@ -73,13 +74,13 @@ export async function register(
remotepassword remotepassword
) { ) {
let init = { let init = {
method: "POST", method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
username: username, username: username,
remote_source_name: remotesourcename, remote_source_name: remotesourcename,
remote_source_login_name: remoteloginname, remote_source_login_name: remoteloginname,
remote_source_login_password: remotepassword remote_source_login_password: remotepassword,
}) }),
}; };
try { try {
@ -91,111 +92,122 @@ export async function register(
return { data: await res.json(), error: null }; return { data: await res.json(), error: null };
} }
} catch (e) { } catch (e) {
return { data: null, error: "api call failed: " + e.message }; return { data: null, error: 'api call failed: ' + e.message };
} }
} }
export async function fetchCurrentUser(signal) { export async function fetchCurrentUser(signal) {
let path = "/user"; let path = '/user';
return await fetch(apiurl(path), null, signal); return await fetch(apiurl(path), null, signal);
} }
export async function fetchOrgMembers(orgref, signal) { export async function fetchOrgMembers(orgref, signal) {
let path = "/orgs/" + orgref + "/members"; let path = '/orgs/' + orgref + '/members';
return await fetch(apiurl(path), null, signal); return await fetch(apiurl(path), null, signal);
} }
export async function fetchRuns(group, startRunID, lastrun, signal) { export async function fetchRuns(group, startRunID, lastrun, signal) {
let u = apiurl("/runs"); let u = apiurl('/runs');
if (group) { if (group) {
u.searchParams.append("group", group); u.searchParams.append('group', group);
} }
if (lastrun) { if (lastrun) {
u.searchParams.append("lastrun", true); u.searchParams.append('lastrun', true);
} }
if (startRunID) { if (startRunID) {
u.searchParams.append("start", startRunID); u.searchParams.append('start', startRunID);
} }
return await fetch(u, null, signal); return await fetch(u, null, signal);
} }
export async function fetchRun(runid, signal) { export async function fetchRun(runid, signal) {
return await fetch(apiurl("/runs/" + runid), null, signal); return await fetch(apiurl('/runs/' + runid), null, signal);
} }
export async function fetchTask(runid, taskid, signal) { export async function fetchTask(runid, taskid, signal) {
return await fetch(apiurl("/runs/" + runid + "/tasks/" + taskid), signal); return await fetch(apiurl('/runs/' + runid + '/tasks/' + taskid), signal);
} }
export async function fetchUser(username, signal) { export async function fetchUser(username, signal) {
let path = "/users/" + username; let path = '/users/' + username;
return await fetch(apiurl(path), null, signal); return await fetch(apiurl(path), null, signal);
} }
export async function fetchProjectGroup(projectgroupref, signal) { export async function fetchProjectGroup(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref); let path = '/projectgroups/' + encodeURIComponent(projectgroupref);
return await fetch(apiurl(path), null, signal); return await fetch(apiurl(path), null, signal);
} }
export async function fetchProjectGroupSubgroups(projectgroupref, signal) { export async function fetchProjectGroupSubgroups(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref); let path = '/projectgroups/' + encodeURIComponent(projectgroupref);
path += "/subgroups"; path += '/subgroups';
return await fetch(apiurl(path), null, signal); return await fetch(apiurl(path), null, signal);
} }
export async function fetchProjectGroupProjects(projectgroupref, signal) { export async function fetchProjectGroupProjects(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref); let path = '/projectgroups/' + encodeURIComponent(projectgroupref);
path += "/projects"; path += '/projects';
return await fetch(apiurl(path), null, signal); return await fetch(apiurl(path), null, signal);
} }
export async function fetchProject(ref, signal) { export async function fetchProject(ref, signal) {
let path = "/projects/" + encodeURIComponent(ref); let path = '/projects/' + encodeURIComponent(ref);
return await fetch(apiurl(path), null, signal); return await fetch(apiurl(path), null, signal);
} }
export async function fetchSecrets(ownertype, ref, all, signal) { export async function fetchSecrets(ownertype, ref, all, signal) {
let path; let path;
if (ownertype == "project") { if (ownertype == 'project') {
path = "/projects/"; path = '/projects/';
} else if (ownertype == "projectgroup") { } else if (ownertype == 'projectgroup') {
path = "/projectgroups/"; path = '/projectgroups/';
} }
path += encodeURIComponent(ref); path += encodeURIComponent(ref);
path += "/secrets"; path += '/secrets';
if (all) { if (all) {
path += "?tree&removeoverridden"; path += '?tree&removeoverridden';
} }
return await fetch(apiurl(path), null, signal); return await fetch(apiurl(path), null, signal);
} }
export async function fetchVariables(ownertype, ref, all, signal) { export async function fetchVariables(ownertype, ref, all, signal) {
let path; let path;
if (ownertype == "project") { if (ownertype == 'project') {
path = "/projects/"; path = '/projects/';
} else if (ownertype == "projectgroup") { } else if (ownertype == 'projectgroup') {
path = "/projectgroups/"; path = '/projectgroups/';
} }
path += encodeURIComponent(ref); path += encodeURIComponent(ref);
path += "/variables"; path += '/variables';
if (all) { if (all) {
path += "?tree&removeoverridden"; path += '?tree&removeoverridden';
} }
return await fetch(apiurl(path), null, signal); return await fetch(apiurl(path), null, signal);
} }
export async function createRemoteSource( export async function createRemoteSource(
token, type, name, clientID, clientSecret, apiURL, authType, skipVerify, token,
sshHostKey, skipSshHostKeyCheck, registrationEnabled, loginEnabled, signal, type,
name,
clientID,
clientSecret,
apiURL,
authType,
skipVerify,
sshHostKey,
skipSshHostKeyCheck,
registrationEnabled,
loginEnabled,
signal
) { ) {
let path = "/remotesources"; let path = '/remotesources';
let init = { let init = {
method: "POST", method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
name, name,
apiurl: apiURL, apiurl: apiURL,
@ -208,38 +220,38 @@ export async function createRemoteSource(
oauth_2_client_secret: clientSecret, oauth_2_client_secret: clientSecret,
registration_enabled: registrationEnabled, registration_enabled: registrationEnabled,
login_enabled: loginEnabled, login_enabled: loginEnabled,
}) }),
}; };
return await fetch(apiurl(path), init, signal, token, "token"); return await fetch(apiurl(path), init, signal, token, 'token');
} }
export async function createOrganization(orgname, visibility, signal) { export async function createOrganization(orgname, visibility, signal) {
let path = "/orgs"; let path = '/orgs';
let init = { let init = {
method: "POST", method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
name: orgname, name: orgname,
visibility: visibility visibility: visibility,
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function createUserToken(username, tokenname, signal) { export async function createUserToken(username, tokenname, signal) {
let path = "/users/" + username + "/tokens"; let path = '/users/' + username + '/tokens';
let init = { let init = {
method: "POST", method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
token_name: tokenname token_name: tokenname,
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function deleteUserToken(username, tokenname, signal) { export async function deleteUserToken(username, tokenname, signal) {
let path = "/users/" + username + "/tokens/" + tokenname; let path = '/users/' + username + '/tokens/' + tokenname;
let init = { let init = {
method: "DELETE" method: 'DELETE',
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
@ -251,90 +263,90 @@ export async function createUserLinkedAccount(
password, password,
signal signal
) { ) {
let path = "/users/" + username + "/linkedaccounts"; let path = '/users/' + username + '/linkedaccounts';
let init = { let init = {
method: "POST", method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
remote_source_name: remotesourcename, remote_source_name: remotesourcename,
remote_source_login_name: loginname, remote_source_login_name: loginname,
remote_source_login_password: password remote_source_login_password: password,
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function deleteLinkedAccount(username, laid, signal) { export async function deleteLinkedAccount(username, laid, signal) {
let path = "/users/" + username + "/linkedaccounts/" + laid; let path = '/users/' + username + '/linkedaccounts/' + laid;
let init = { let init = {
method: "DELETE" method: 'DELETE',
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function restartRun(runid, fromStart, signal) { export async function restartRun(runid, fromStart, signal) {
let path = "/runs/" + runid + "/actions"; let path = '/runs/' + runid + '/actions';
let init = { let init = {
method: "PUT", method: 'PUT',
body: JSON.stringify({ body: JSON.stringify({
action_type: "restart", action_type: 'restart',
from_start: fromStart from_start: fromStart,
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function cancelRun(runid, signal) { export async function cancelRun(runid, signal) {
let path = "/runs/" + runid + "/actions"; let path = '/runs/' + runid + '/actions';
let init = { let init = {
method: "PUT", method: 'PUT',
body: JSON.stringify({ body: JSON.stringify({
action_type: "cancel" action_type: 'cancel',
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function stopRun(runid, signal) { export async function stopRun(runid, signal) {
let path = "/runs/" + runid + "/actions"; let path = '/runs/' + runid + '/actions';
let init = { let init = {
method: "PUT", method: 'PUT',
body: JSON.stringify({ body: JSON.stringify({
action_type: "stop" action_type: 'stop',
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function approveTask(runid, taskid, signal) { export async function approveTask(runid, taskid, signal) {
let path = "/runs/" + runid + "/tasks/" + taskid + "/actions"; let path = '/runs/' + runid + '/tasks/' + taskid + '/actions';
let init = { let init = {
method: "PUT", method: 'PUT',
body: JSON.stringify({ body: JSON.stringify({
action_type: "approve" action_type: 'approve',
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function fetchRemoteSources(signal) { export async function fetchRemoteSources(signal) {
let path = "/remotesources"; let path = '/remotesources';
return await fetch(apiurl(path), null, signal); return await fetch(apiurl(path), null, signal);
} }
export async function userRemoteRepos(remotesourceid, signal) { export async function userRemoteRepos(remotesourceid, signal) {
let path = "/user/remoterepos/" + remotesourceid; let path = '/user/remoterepos/' + remotesourceid;
return await fetch(apiurl(path, null, signal)); return await fetch(apiurl(path, null, signal));
} }
export async function createProjectGroup(parentref, name, visibility, signal) { export async function createProjectGroup(parentref, name, visibility, signal) {
let path = "/projectgroups"; let path = '/projectgroups';
let init = { let init = {
method: "POST", method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
name: name, name: name,
parent_ref: parentref, parent_ref: parentref,
visibility: visibility visibility: visibility,
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
@ -345,13 +357,13 @@ export async function updateProjectGroup(
visibility, visibility,
signal signal
) { ) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref); let path = '/projectgroups/' + encodeURIComponent(projectgroupref);
let init = { let init = {
method: "PUT", method: 'PUT',
body: JSON.stringify({ body: JSON.stringify({
name: name, name: name,
visibility: visibility visibility: visibility,
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
@ -365,56 +377,61 @@ export async function createProject(
passvarstoforkedpr, passvarstoforkedpr,
signal signal
) { ) {
let path = "/projects"; let path = '/projects';
let init = { let init = {
method: "POST", method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
name: name, name: name,
parent_ref: parentref, parent_ref: parentref,
visibility: visibility, visibility: visibility,
remote_source_name: remotesourcename, remote_source_name: remotesourcename,
repo_path: remoterepopath, repo_path: remoterepopath,
pass_vars_to_forked_pr: passvarstoforkedpr pass_vars_to_forked_pr: passvarstoforkedpr,
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function updateProject(projectref, name, visibility, passvarstoforkedpr, signal) { export async function updateProject(
let path = "/projects/" + encodeURIComponent(projectref); projectref,
name,
visibility,
passvarstoforkedpr,
signal
) {
let path = '/projects/' + encodeURIComponent(projectref);
let init = { let init = {
method: "PUT", method: 'PUT',
body: JSON.stringify({ body: JSON.stringify({
name: name, name: name,
visibility: visibility, visibility: visibility,
pass_vars_to_forked_pr: passvarstoforkedpr pass_vars_to_forked_pr: passvarstoforkedpr,
}) }),
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function deleteProject(projectref, signal) { export async function deleteProject(projectref, signal) {
let path = "/projects/" + encodeURIComponent(projectref); let path = '/projects/' + encodeURIComponent(projectref);
let init = { let init = {
method: "DELETE" method: 'DELETE',
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function projectUpdateRepoLinkedAccount(projectref, signal) { export async function projectUpdateRepoLinkedAccount(projectref, signal) {
let path = let path =
"/projects/" + encodeURIComponent(projectref) + "/updaterepolinkedaccount"; '/projects/' + encodeURIComponent(projectref) + '/updaterepolinkedaccount';
let init = { let init = {
method: "PUT" method: 'PUT',
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }
export async function deleteProjectGroup(projectgroupref, signal) { export async function deleteProjectGroup(projectgroupref, signal) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref); let path = '/projectgroups/' + encodeURIComponent(projectgroupref);
let init = { let init = {
method: "DELETE" method: 'DELETE',
}; };
return await fetch(apiurl(path), init, signal); return await fetch(apiurl(path), init, signal);
} }

View File

@ -1,130 +1,173 @@
export function parseRef(ref) { export function parseRef(ref) {
ref = ref.replace(/\.proj/, "") ref = ref.replace(/\.proj/, '');
// return empty array or split return an array with the empty element // return empty array or split return an array with the empty element
if (!ref) { if (!ref) {
return [] return [];
} }
return ref.split("/") return ref.split('/');
} }
export function ownerLink(ownertype, ownername) { export function ownerLink(ownertype, ownername) {
if (ownertype == "user") { if (ownertype == 'user') {
return { name: ownertype, params: { username: ownername } } return { name: ownertype, params: { username: ownername } };
} else if (ownertype == "org") { } else if (ownertype == 'org') {
return { name: ownertype, params: { orgname: ownername } } return { name: ownertype, params: { orgname: ownername } };
} }
} }
export function ownerProjectsLink(ownertype, ownername) { export function ownerProjectsLink(ownertype, ownername) {
return { name: ownertype + " projects", params: { ownername: ownername } } return { name: ownertype + ' projects', params: { ownername: ownername } };
} }
export function ownerSettingsLink(ownertype, ownername) { export function ownerSettingsLink(ownertype, ownername) {
if (ownertype == "user") { if (ownertype == 'user') {
return { name: ownertype + " settings", params: { username: ownername } } return { name: ownertype + ' settings', params: { username: ownername } };
} else if (ownertype == "org") { } else if (ownertype == 'org') {
return { name: ownertype + " settings", params: { orgname: ownername } } return { name: ownertype + ' settings', params: { orgname: ownername } };
} }
} }
export function userDirectRunsLink(username) { export function userDirectRunsLink(username) {
return { name: "user direct runs", params: { username: username } } return { name: 'user direct runs', params: { username: username } };
} }
export function userDirectRunLink(username, runid) { export function userDirectRunLink(username, runid) {
return { name: "user direct run", params: { username: username, runid: runid } } return {
name: 'user direct run',
params: { username: username, runid: runid },
};
} }
export function userDirectRunTaskLink(username, runid, taskid) { export function userDirectRunTaskLink(username, runid, taskid) {
return { name: "user direct run task", params: { username: username, runid: runid, taskid: taskid } } return {
name: 'user direct run task',
params: { username: username, runid: runid, taskid: taskid },
};
} }
export function userAddLinkedAccountLink(username, remotesourcename) { export function userAddLinkedAccountLink(username, remotesourcename) {
return { name: "user add linked account", params: { username: username, remotesource: remotesourcename } } return {
name: 'user add linked account',
params: { username: username, remotesource: remotesourcename },
};
} }
export function orgMembersLink(orgname) { export function orgMembersLink(orgname) {
return { name: "org members", params: { orgname: orgname } } return { name: 'org members', params: { orgname: orgname } };
} }
// Note, when creating a router link containing a project/projectgroup ref (a // Note, when creating a router link containing a project/projectgroup ref (a
// path), unfortunately, we cannot use route name and params since it will path // path), unfortunately, we cannot use route name and params since it will path
// escape it // escape it
export function projectGroupPath(ownertype, ownername, projectgroupref) { export function projectGroupPath(ownertype, ownername, projectgroupref) {
let path = `/${ownertype}/${ownername}` let path = `/${ownertype}/${ownername}`;
// root project group will have a .proj without a name // root project group will have a .proj without a name
let projectgrouppath = (projectgroupref.join("/") + ".proj") let projectgrouppath = projectgroupref.join('/') + '.proj';
path = `${path}/projectgroups/${projectgrouppath}` path = `${path}/projectgroups/${projectgrouppath}`;
return path return path;
} }
export function projectPath(ownertype, ownername, projectref) { export function projectPath(ownertype, ownername, projectref) {
let path = `/${ownertype}/${ownername}` let path = `/${ownertype}/${ownername}`;
let projectpath = (projectref.join("/") + ".proj") let projectpath = projectref.join('/') + '.proj';
path = `${path}/projects/${projectpath}` path = `${path}/projects/${projectpath}`;
return path return path;
} }
export function projectGroupLink(ownertype, ownername, projectgroupref) { export function projectGroupLink(ownertype, ownername, projectgroupref) {
return { path: projectGroupPath(ownertype, ownername, projectgroupref) } return { path: projectGroupPath(ownertype, ownername, projectgroupref) };
} }
export function projectGroupProjectsLink(ownertype, ownername, projectgroupref) { export function projectGroupProjectsLink(
let projectgrouppath = (projectgroupref.join("/") + ".proj") ownertype,
return { path: `/${ownertype}/${ownername}/projectgroups/${projectgrouppath}/projects` } ownername,
projectgroupref
) {
let projectgrouppath = projectgroupref.join('/') + '.proj';
return {
path: `/${ownertype}/${ownername}/projectgroups/${projectgrouppath}/projects`,
};
} }
export function projectLink(ownertype, ownername, projectref) { export function projectLink(ownertype, ownername, projectref) {
let projectpath = (projectref.join("/") + ".proj") let projectpath = projectref.join('/') + '.proj';
return { path: `/${ownertype}/${ownername}/projects/${projectpath}` } return { path: `/${ownertype}/${ownername}/projects/${projectpath}` };
} }
export function projectRunsLink(ownertype, ownername, projectref) { export function projectRunsLink(ownertype, ownername, projectref) {
let projectpath = (projectref.join("/") + ".proj") let projectpath = projectref.join('/') + '.proj';
return { path: `/${ownertype}/${ownername}/projects/${projectpath}/runs` } return { path: `/${ownertype}/${ownername}/projects/${projectpath}/runs` };
} }
export function projectBranchesRunsLink(ownertype, ownername, projectref) { export function projectBranchesRunsLink(ownertype, ownername, projectref) {
let projectpath = (projectref.join("/") + ".proj") let projectpath = projectref.join('/') + '.proj';
return { path: `/${ownertype}/${ownername}/projects/${projectpath}/branches` } return {
path: `/${ownertype}/${ownername}/projects/${projectpath}/branches`,
};
} }
export function projectTagsRunsLink(ownertype, ownername, projectref) { export function projectTagsRunsLink(ownertype, ownername, projectref) {
let projectpath = (projectref.join("/") + ".proj") let projectpath = projectref.join('/') + '.proj';
return { path: `/${ownertype}/${ownername}/projects/${projectpath}/tags` } return { path: `/${ownertype}/${ownername}/projects/${projectpath}/tags` };
} }
export function projectPRsRunsLink(ownertype, ownername, projectref) { export function projectPRsRunsLink(ownertype, ownername, projectref) {
let projectpath = (projectref.join("/") + ".proj") let projectpath = projectref.join('/') + '.proj';
return { path: `/${ownertype}/${ownername}/projects/${projectpath}/pullrequests` } return {
path: `/${ownertype}/${ownername}/projects/${projectpath}/pullrequests`,
};
} }
export function projectRunLink(ownertype, ownername, projectref, runid) { export function projectRunLink(ownertype, ownername, projectref, runid) {
let projectpath = (projectref.join("/") + ".proj") let projectpath = projectref.join('/') + '.proj';
return { path: `/${ownertype}/${ownername}/projects/${projectpath}/runs/${runid}` } return {
path: `/${ownertype}/${ownername}/projects/${projectpath}/runs/${runid}`,
};
} }
export function projectRunTaskLink(ownertype, ownername, projectref, runid, taskid) { export function projectRunTaskLink(
let projectpath = (projectref.join("/") + ".proj") ownertype,
return { path: `/${ownertype}/${ownername}/projects/${projectpath}/runs/${runid}/tasks/${taskid}` } ownername,
projectref,
runid,
taskid
) {
let projectpath = projectref.join('/') + '.proj';
return {
path: `/${ownertype}/${ownername}/projects/${projectpath}/runs/${runid}/tasks/${taskid}`,
};
} }
export function projectGroupSettingsLink(ownertype, ownername, projectgroupref) { export function projectGroupSettingsLink(
let path = projectGroupPath(ownertype, ownername, projectgroupref) ownertype,
return { path: `${path}/settings` } ownername,
projectgroupref
) {
let path = projectGroupPath(ownertype, ownername, projectgroupref);
return { path: `${path}/settings` };
} }
export function projectSettingsLink(ownertype, ownername, projectref) { export function projectSettingsLink(ownertype, ownername, projectref) {
let projectpath = (projectref.join("/") + ".proj") let projectpath = projectref.join('/') + '.proj';
return { path: `/${ownertype}/${ownername}/projects/${projectpath}/settings` } return {
path: `/${ownertype}/${ownername}/projects/${projectpath}/settings`,
};
} }
export function projectGroupCreateProjectGroupLink(ownertype, ownername, projectgroupref) { export function projectGroupCreateProjectGroupLink(
let path = projectGroupPath(ownertype, ownername, projectgroupref) ownertype,
return { path: `${path}/createprojectgroup` } ownername,
projectgroupref
) {
let path = projectGroupPath(ownertype, ownername, projectgroupref);
return { path: `${path}/createprojectgroup` };
} }
export function projectGroupCreateProjectLink(ownertype, ownername, projectgroupref) { export function projectGroupCreateProjectLink(
let path = projectGroupPath(ownertype, ownername, projectgroupref) ownertype,
return { path: `${path}/createproject` } ownername,
projectgroupref
) {
let path = projectGroupPath(ownertype, ownername, projectgroupref);
return { path: `${path}/createproject` };
} }

View File

@ -1,25 +1,24 @@
export function runStatus(run) { export function runStatus(run) {
// * if the run has a result then return the result // * if the run has a result then return the result
// * if stopping return "stopping" // * if stopping return "stopping"
// * return the phase // * return the phase
if (run.result != "unknown") return run.result; if (run.result != 'unknown') return run.result;
if (run.stopping) return "stopping"; if (run.stopping) return 'stopping';
if (run.phase != "finished") return run.phase; if (run.phase != 'finished') return run.phase;
return run.result; return run.result;
} }
export function runResultClass(run) { export function runResultClass(run) {
let status = runStatus(run); let status = runStatus(run);
if (status == "setuperror") return "setuperror"; if (status == 'setuperror') return 'setuperror';
if (status == "queued") return "unknown"; if (status == 'queued') return 'unknown';
if (status == "cancelled") return "failed"; if (status == 'cancelled') return 'failed';
if (status == "running") return "running"; if (status == 'running') return 'running';
if (status == "stopping") return "failed"; if (status == 'stopping') return 'failed';
if (status == "stopped") return "failed"; if (status == 'stopped') return 'failed';
if (status == "success") return "success"; if (status == 'success') return 'success';
if (status == "failed") return "failed"; if (status == 'failed') return 'failed';
return "unknown"; return 'unknown';
} }

View File

@ -13,36 +13,44 @@
v-if="remotesource.auth_type == 'password'" v-if="remotesource.auth_type == 'password'"
action="Add Linked Account" action="Add Linked Account"
:name="remotesource.name" :name="remotesource.name"
v-on:login="doAddLinkedAccount(remotesource.name, $event.username, $event.password)" v-on:login="
doAddLinkedAccount(
remotesource.name,
$event.username,
$event.password
)
"
/> />
<button <button
v-else v-else
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
@click="doAddLinkedAccount(remotesource.name)" @click="doAddLinkedAccount(remotesource.name)"
>Add Linked Account with {{remotesource.name}}</button> >
Add Linked Account with {{ remotesource.name }}
</button>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import LoginForm from "@/components/loginform"; import LoginForm from '@/components/loginform';
import { fetchRemoteSources, createUserLinkedAccount } from "@/util/data"; import { fetchRemoteSources, createUserLinkedAccount } from '@/util/data';
export default { export default {
name: "AddLinkedAccount", name: 'AddLinkedAccount',
props: { props: {
username: String, username: String,
remoteSourceName: String remoteSourceName: String,
}, },
components: { components: {
LoginForm LoginForm,
}, },
data: function() { data: function () {
return { return {
addLinkedAccountError: null, addLinkedAccountError: null,
remotesource: null remotesource: null,
}; };
}, },
methods: { methods: {
@ -52,7 +60,7 @@ export default {
async fetchRemoteSources() { async fetchRemoteSources() {
let { data, error } = await fetchRemoteSources(); let { data, error } = await fetchRemoteSources();
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
@ -79,17 +87,13 @@ export default {
return; return;
} }
this.$router.push({ this.$router.push({
name: "user settings", name: 'user settings',
params: { username: this.username } params: { username: this.username },
}); });
} },
}, },
created: function() { created: function () {
this.fetchRemoteSources(); this.fetchRemoteSources();
} },
}; };
</script> </script>

View File

@ -8,13 +8,9 @@
<span class="block sm:inline">{{ error }}</span> <span class="block sm:inline">{{ error }}</span>
</div> </div>
<div> <div>
<div > <div>
<div <div class="flex justify-center items-center w-max">
class="flex justify-center items-center w-max" <CreateSourceForm v-on:createSource="createSource($event)" />
>
<CreateSourceForm
v-on:createSource="createSource($event)"
/>
</div> </div>
</div> </div>
</div> </div>
@ -22,40 +18,53 @@
</template> </template>
<script> <script>
import CreateSourceForm from '@/components/createsourceform';
import CreateSourceForm from "@/components/createsourceform"; import { createRemoteSource } from '@/util/data.js';
import { createRemoteSource } from "@/util/data.js"; import router from '@/router';
import router from "@/router";
export default { export default {
name: "CreateSource", name: 'CreateSource',
components: { components: {
CreateSourceForm CreateSourceForm,
}, },
data: function() { data: function () {
return { return {
error: null, error: null,
}; };
}, },
methods: { methods: {
async createSource({ async createSource({
token, name, type, clientId, clientSecret, url, skipVerify, sshHostKey, token,
name,
type,
clientId,
clientSecret,
url,
skipVerify,
sshHostKey,
skipSshHostKeyCheck, skipSshHostKeyCheck,
}) { }) {
const res = await createRemoteSource( const res = await createRemoteSource(
token, type, name, clientId, clientSecret, url, "oauth2", skipVerify, token,
sshHostKey, skipSshHostKeyCheck, true, true, undefined, type,
name,
clientId,
clientSecret,
url,
'oauth2',
skipVerify,
sshHostKey,
skipSshHostKeyCheck,
true,
true,
undefined
); );
if (res.error) if (res.error) this.$store.dispatch('setError', res.error);
this.$store.dispatch("setError", res.error); else router.push({ name: 'login' });
else },
router.push({name: "login"});
}
}, },
mounted: function() { mounted: function () {
this.$store.dispatch("setError", null); this.$store.dispatch('setError', null);
}, },
}; };
</script> </script>

View File

@ -1,25 +1,15 @@
<template> <template>
<div class="home flex flex-col items-center"> <div class="home flex flex-col items-center">
<img <img class="w-64 h-64" src="/img/agola-logo-name.svg" alt="agola logo" />
class="w-64 h-64"
src="/img/agola-logo-name.svg" alt="agola logo"
/>
<h1 class="text-2xl">CI/CD redefined</h1> <h1 class="text-2xl">CI/CD redefined</h1>
<div class="m-8"> <div class="m-8">
<h1 class="text-lg"> <h1 class="text-lg">
Hi, you are almost ready to go! Just Hi, you are almost ready to go! Just
<router-link <router-link class="underline text-blue-600" to="/login">
class="underline text-blue-600"
to="/login"
>
login login
</router-link> </router-link>
into your account into your account or create a
or create a <router-link class="underline text-blue-600" to="/register">
<router-link
class="underline text-blue-600"
to="/register"
>
new one new one
</router-link> </router-link>
</h1> </h1>
@ -28,22 +18,22 @@
</template> </template>
<script> <script>
import { mapGetters } from "vuex"; import { mapGetters } from 'vuex';
export default { export default {
name: "Home", name: 'Home',
components: {}, components: {},
computed: { computed: {
...mapGetters(["user"]) ...mapGetters(['user']),
}, },
created: function() { created: function () {
let user = this.$store.getters.user; let user = this.$store.getters.user;
if (user) { if (user) {
this.$router.push({ this.$router.push({
name: "user", name: 'user',
params: { username: this.user.username } params: { username: this.user.username },
}); });
} }
} },
}; };
</script> </script>

View File

@ -13,14 +13,9 @@
class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
> >
No remote sources defined No remote sources defined
<router-link <router-link class="underline text-blue-600 block" to="/newsource">
class="underline text-blue-600 block" <button class="btn btn-blue">Create one</button>
to="/newsource" </router-link>
>
<button class="btn btn-blue">
Create one
</button>
</router-link>
</div> </div>
<div <div
v-else-if="!hasLoginRemoteSources" v-else-if="!hasLoginRemoteSources"
@ -61,25 +56,25 @@
</template> </template>
<script> <script>
import { fetchRemoteSources, login } from "@/util/data"; import { fetchRemoteSources, login } from '@/util/data';
import { import {
setLoggedUser, setLoggedUser,
unsetLoginRedirect, unsetLoginRedirect,
setLoginRedirect, setLoginRedirect,
doLogout doLogout,
} from "@/util/auth"; } from '@/util/auth';
import LoginForm from "@/components/loginform"; import LoginForm from '@/components/loginform';
export default { export default {
name: "Login", name: 'Login',
components: { components: {
LoginForm LoginForm,
}, },
data: function() { data: function () {
return { return {
error: null, error: null,
remotesources: null remotesources: null,
}; };
}, },
computed: { computed: {
@ -96,20 +91,20 @@ export default {
} }
} }
return false; return false;
} },
}, },
methods: { methods: {
async fetchRemoteSources() { async fetchRemoteSources() {
let { data, error } = await fetchRemoteSources(); let { data, error } = await fetchRemoteSources();
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.remotesources = data; this.remotesources = data;
}, },
async doLogin(username, password, remotesourcename) { async doLogin(username, password, remotesourcename) {
unsetLoginRedirect(); unsetLoginRedirect();
let redirect = this.$route.query["redirect"]; let redirect = this.$route.query['redirect'];
this.error = null; this.error = null;
@ -131,18 +126,16 @@ export default {
unsetLoginRedirect(); unsetLoginRedirect();
this.$router.push(redirect); this.$router.push(redirect);
} else { } else {
this.$router.push({ name: "home" }); this.$router.push({ name: 'home' });
} }
} },
}, },
mounted: function() { mounted: function () {
this.$store.dispatch("setError", null); this.$store.dispatch('setError', null);
}, },
created: function() { created: function () {
doLogout(); doLogout();
this.fetchRemoteSources(); this.fetchRemoteSources();
} },
}; };
</script> </script>

View File

@ -1,13 +1,11 @@
<script> <script>
import { doLogout } from "@/util/auth"; import { doLogout } from '@/util/auth';
export default { export default {
name: "Logout", name: 'Logout',
created: function() { created: function () {
doLogout(); doLogout();
this.$router.push("/"); this.$router.push('/');
} },
}; };
</script> </script>

View File

@ -11,32 +11,32 @@
</template> </template>
<script> <script>
import { fetch } from "@/util/data"; import { fetch } from '@/util/data';
import { import {
oauth2callbackurl, oauth2callbackurl,
setLoggedUser, setLoggedUser,
unsetLoginRedirect, unsetLoginRedirect,
getLoginRedirect getLoginRedirect,
} from "@/util/auth"; } from '@/util/auth';
export default { export default {
components: {}, components: {},
name: "Oauth2", name: 'Oauth2',
props: {}, props: {},
data() { data() {
return { return {
error: null, error: null,
run: null, run: null,
code: this.$route.query.code, code: this.$route.query.code,
username: null username: null,
}; };
}, },
methods: { methods: {
async doOauth2() { async doOauth2() {
let u = oauth2callbackurl(); let u = oauth2callbackurl();
u.searchParams.append("code", this.$route.query.code); u.searchParams.append('code', this.$route.query.code);
u.searchParams.append("state", this.$route.query.state); u.searchParams.append('state', this.$route.query.state);
let { data, error } = await fetch(u); let { data, error } = await fetch(u);
if (error) { if (error) {
// set local login error on failed oauth2. // set local login error on failed oauth2.
@ -44,29 +44,28 @@ export default {
return; return;
} }
if (data.request_type === "loginuser") { if (data.request_type === 'loginuser') {
setLoggedUser(data.response.token, data.response.user); setLoggedUser(data.response.token, data.response.user);
let redirect = getLoginRedirect(redirect); let redirect = getLoginRedirect(redirect);
if (redirect) { if (redirect) {
unsetLoginRedirect(); unsetLoginRedirect();
this.$router.push(redirect); this.$router.push(redirect);
} else { } else {
this.$router.push({ name: "home" }); this.$router.push({ name: 'home' });
} }
} else if (data.request_type === "authorize") { } else if (data.request_type === 'authorize') {
this.$store.dispatch("setRegisterUser", data.response); this.$store.dispatch('setRegisterUser', data.response);
this.$router.push("/register"); this.$router.push('/register');
} else if (data.request_type === "createuserla") { } else if (data.request_type === 'createuserla') {
this.$router.push({ this.$router.push({
name: "user settings", name: 'user settings',
params: { username: this.username } params: { username: this.username },
}); });
} }
} },
}, },
created: function() { created: function () {
this.doOauth2(); this.doOauth2();
} },
}; };
</script> </script>

View File

@ -9,13 +9,15 @@
<span class="mx-2">/</span> <span class="mx-2">/</span>
</li> </li>
<li> <li>
<router-link :to="ownerLink('org', orgname)">{{orgname}}</router-link> <router-link :to="ownerLink('org', orgname)">{{
orgname
}}</router-link>
</li> </li>
</ol> </ol>
</nav> </nav>
<div class="mb-8 flex justify-between"> <div class="mb-8 flex justify-between">
<span class="text-3xl">{{orgname}}</span> <span class="text-3xl">{{ orgname }}</span>
<createprojectbutton v-on:click="goToCreate($event)" /> <createprojectbutton v-on:click="goToCreate($event)" />
</div> </div>
@ -23,7 +25,12 @@
<ul class="flex-grow tab"> <ul class="flex-grow tab">
<li <li
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name === 'org projects' || $route.name === 'org' }]" :class="[
{
'tab-element-selected':
$route.name === 'org projects' || $route.name === 'org',
},
]"
> >
<router-link :to="ownerProjectsLink('org', orgname)"> <router-link :to="ownerProjectsLink('org', orgname)">
<i class="mr-1 mdi mdi-home" /> <i class="mr-1 mdi mdi-home" />
@ -42,7 +49,13 @@
<li <li
v-if="$route.name.endsWith('org project group settings')" v-if="$route.name.endsWith('org project group settings')"
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.endsWith('org project group settings') }]" :class="[
{
'tab-element-selected': $route.name.endsWith(
'org project group settings'
),
},
]"
> >
<router-link :to="projectGroupSettingsLink('org', orgname, [])"> <router-link :to="projectGroupSettingsLink('org', orgname, [])">
<i class="mr-1 mdi mdi-settings" /> <i class="mr-1 mdi mdi-settings" />
@ -52,7 +65,9 @@
<li <li
v-if="$route.name.endsWith('org settings')" v-if="$route.name.endsWith('org settings')"
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.endsWith('org settings') }]" :class="[
{ 'tab-element-selected': $route.name.endsWith('org settings') },
]"
> >
<router-link :to="ownerSettingsLink('org', orgname)"> <router-link :to="ownerSettingsLink('org', orgname)">
<i class="mr-1 mdi mdi-settings" /> <i class="mr-1 mdi mdi-settings" />
@ -65,7 +80,7 @@
<div class="relative"> <div class="relative">
<div <div
class="flex -mt-3" class="flex -mt-3"
v-click-outside="() => dropdownActive = false" v-click-outside="() => (dropdownActive = false)"
@click="dropdownActive = !dropdownActive" @click="dropdownActive = !dropdownActive"
> >
<button <button
@ -108,9 +123,8 @@
</div> </div>
</template> </template>
<script> <script>
import * as vClickOutside from "v-click-outside-x"; import * as vClickOutside from 'v-click-outside-x';
import { import {
ownerLink, ownerLink,
@ -119,23 +133,23 @@ import {
orgMembersLink, orgMembersLink,
projectGroupCreateProjectGroupLink, projectGroupCreateProjectGroupLink,
projectGroupCreateProjectLink, projectGroupCreateProjectLink,
projectGroupSettingsLink projectGroupSettingsLink,
} from "@/util/link.js"; } from '@/util/link.js';
import createprojectbutton from "@/components/createprojectbutton.vue"; import createprojectbutton from '@/components/createprojectbutton.vue';
export default { export default {
name: "Org", name: 'Org',
components: { createprojectbutton }, components: { createprojectbutton },
directives: { directives: {
clickOutside: vClickOutside.directive clickOutside: vClickOutside.directive,
}, },
props: { props: {
orgname: String orgname: String,
}, },
data() { data() {
return { return {
dropdownActive: false dropdownActive: false,
}; };
}, },
methods: { methods: {
@ -147,19 +161,18 @@ export default {
projectGroupCreateProjectLink: projectGroupCreateProjectLink, projectGroupCreateProjectLink: projectGroupCreateProjectLink,
projectGroupSettingsLink: projectGroupSettingsLink, projectGroupSettingsLink: projectGroupSettingsLink,
goToCreate(type) { goToCreate(type) {
if (type == "project") { if (type == 'project') {
this.$router.push( this.$router.push(
projectGroupCreateProjectLink("org", this.orgname, []) projectGroupCreateProjectLink('org', this.orgname, [])
); );
return; return;
} }
this.$router.push( this.$router.push(
projectGroupCreateProjectGroupLink("org", this.orgname, []) projectGroupCreateProjectGroupLink('org', this.orgname, [])
); );
} },
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -1,9 +1,13 @@
<template> <template>
<div> <div>
<projbreadcrumbs :ownertype="ownertype" :ownername="ownername" :projectref="projectref" /> <projbreadcrumbs
:ownertype="ownertype"
:ownername="ownername"
:projectref="projectref"
/>
<div class="mb-8"> <div class="mb-8">
<span class="text-3xl">{{projectName()}}</span> <span class="text-3xl">{{ projectName() }}</span>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
@ -17,7 +21,13 @@
</li> </li>
<li <li
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.match('project runs') || $route.name.endsWith('project') }]" :class="[
{
'tab-element-selected':
$route.name.match('project runs') ||
$route.name.endsWith('project'),
},
]"
> >
<router-link :to="projectRunsLink(ownertype, ownername, projectref)"> <router-link :to="projectRunsLink(ownertype, ownername, projectref)">
<i class="mr-1 mdi mdi-asterisk" /> <i class="mr-1 mdi mdi-asterisk" />
@ -26,45 +36,84 @@
</li> </li>
<li <li
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.match('project branches runs') }]" :class="[
{
'tab-element-selected': $route.name.match(
'project branches runs'
),
},
]"
> >
<router-link :to="projectBranchesRunsLink(ownertype, ownername, projectref)"> <router-link
:to="projectBranchesRunsLink(ownertype, ownername, projectref)"
>
<i class="mr-1 mdi mdi-source-branch" /> <i class="mr-1 mdi mdi-source-branch" />
<span>Branches</span> <span>Branches</span>
</router-link> </router-link>
</li> </li>
<li <li
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.match('project tags runs') }]" :class="[
{ 'tab-element-selected': $route.name.match('project tags runs') },
]"
> >
<router-link :to="projectTagsRunsLink(ownertype, ownername, projectref)"> <router-link
:to="projectTagsRunsLink(ownertype, ownername, projectref)"
>
<i class="mr-1 mdi mdi-tag" /> <i class="mr-1 mdi mdi-tag" />
<span>Tags</span> <span>Tags</span>
</router-link> </router-link>
</li> </li>
<li <li
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.match('project pull requests runs') }]" :class="[
{
'tab-element-selected': $route.name.match(
'project pull requests runs'
),
},
]"
> >
<router-link :to="projectPRsRunsLink(ownertype, ownername, projectref)"> <router-link
:to="projectPRsRunsLink(ownertype, ownername, projectref)"
>
<i class="mr-1 mdi mdi-source-pull" /> <i class="mr-1 mdi mdi-source-pull" />
<span>Pull Requests</span> <span>Pull Requests</span>
</router-link> </router-link>
</li> </li>
<li <li
v-if="run && ($route.name.endsWith('project run') || $route.name.endsWith('project run task'))" v-if="
run &&
($route.name.endsWith('project run') ||
$route.name.endsWith('project run task'))
"
> >
<tabarrow /> <tabarrow />
</li> </li>
<li <li
class="tab-element" class="tab-element"
v-if="run && ($route.name.endsWith('project run') || $route.name.endsWith('project run task'))" v-if="
:class="[{ 'tab-element-selected': $route.name.endsWith('project run') }]" run &&
($route.name.endsWith('project run') ||
$route.name.endsWith('project run task'))
"
:class="[
{ 'tab-element-selected': $route.name.endsWith('project run') },
]"
> >
<router-link :to="projectRunLink(ownertype, ownername, projectref, $route.params.runid)"> <router-link
:to="
projectRunLink(
ownertype,
ownername,
projectref,
$route.params.runid
)
"
>
<p> <p>
Run Run
<strong>#{{run.counter}}</strong> <strong>#{{ run.counter }}</strong>
</p> </p>
</router-link> </router-link>
</li> </li>
@ -74,23 +123,41 @@
<li <li
class="tab-element" class="tab-element"
v-if="run && $route.name.endsWith('project run task')" v-if="run && $route.name.endsWith('project run task')"
:class="[{ 'tab-element-selected': $route.name.endsWith('project run task') }]" :class="[
{
'tab-element-selected': $route.name.endsWith('project run task'),
},
]"
> >
<router-link <router-link
:to="projectRunTaskLink(ownertype, ownername, projectref, $route.params.runid, $route.params.taskid)" :to="
projectRunTaskLink(
ownertype,
ownername,
projectref,
$route.params.runid,
$route.params.taskid
)
"
> >
<p> <p>
Task Task
<strong>{{run.tasks[$route.params.taskid].name}}</strong> <strong>{{ run.tasks[$route.params.taskid].name }}</strong>
</p> </p>
</router-link> </router-link>
</li> </li>
<li <li
v-if="$route.name.endsWith('project settings')" v-if="$route.name.endsWith('project settings')"
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.endsWith('project settings') }]" :class="[
{
'tab-element-selected': $route.name.endsWith('project settings'),
},
]"
> >
<router-link :to="projectSettingsLink(ownertype, ownername, projectref)"> <router-link
:to="projectSettingsLink(ownertype, ownername, projectref)"
>
<i class="mr-1 mdi mdi-settings" /> <i class="mr-1 mdi mdi-settings" />
<span>Project Settings</span> <span>Project Settings</span>
</router-link> </router-link>
@ -101,7 +168,7 @@
<div class="relative"> <div class="relative">
<div <div
class="flex -mt-3" class="flex -mt-3"
v-click-outside="() => dropdownActive = false" v-click-outside="() => (dropdownActive = false)"
@click="dropdownActive = !dropdownActive" @click="dropdownActive = !dropdownActive"
> >
<button <button
@ -135,9 +202,8 @@
</div> </div>
</template> </template>
<script> <script>
import * as vClickOutside from "v-click-outside-x"; import * as vClickOutside from 'v-click-outside-x';
import { import {
projectLink, projectLink,
@ -147,35 +213,35 @@ import {
projectPRsRunsLink, projectPRsRunsLink,
projectRunLink, projectRunLink,
projectRunTaskLink, projectRunTaskLink,
projectSettingsLink projectSettingsLink,
} from "@/util/link.js"; } from '@/util/link.js';
import { fetchRun } from "@/util/data.js"; import { fetchRun } from '@/util/data.js';
import projbreadcrumbs from "@/components/projbreadcrumbs.vue"; import projbreadcrumbs from '@/components/projbreadcrumbs.vue';
import tabarrow from "@/components/tabarrow.vue"; import tabarrow from '@/components/tabarrow.vue';
export default { export default {
name: "Project", name: 'Project',
components: { projbreadcrumbs, tabarrow }, components: { projbreadcrumbs, tabarrow },
directives: { directives: {
clickOutside: vClickOutside.directive clickOutside: vClickOutside.directive,
}, },
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectref: Array projectref: Array,
}, },
data() { data() {
return { return {
fetchAbort: null, fetchAbort: null,
dropdownActive: false, dropdownActive: false,
run: null run: null,
}; };
}, },
watch: { watch: {
$route: async function(route) { $route: async function (route) {
if (this.fetchAbort) { if (this.fetchAbort) {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
@ -191,12 +257,12 @@ export default {
return; return;
} }
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.run = data; this.run = data;
} }
} },
}, },
methods: { methods: {
projectLink: projectLink, projectLink: projectLink,
@ -209,9 +275,9 @@ export default {
projectSettingsLink: projectSettingsLink, projectSettingsLink: projectSettingsLink,
projectName() { projectName() {
return this.projectref[this.projectref.length - 1]; return this.projectref[this.projectref.length - 1];
} },
}, },
created: async function() { created: async function () {
this.fetchAbort = new AbortController(); this.fetchAbort = new AbortController();
if (this.$route.params.runid) { if (this.$route.params.runid) {
@ -223,7 +289,7 @@ export default {
return; return;
} }
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.run = data; this.run = data;
@ -233,9 +299,8 @@ export default {
if (this.fetchAbort) { if (this.fetchAbort) {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -1,4 +1,3 @@
<template> <template>
<div> <div>
<projbreadcrumbs <projbreadcrumbs
@ -8,7 +7,7 @@
/> />
<div class="mb-8 flex justify-between"> <div class="mb-8 flex justify-between">
<span class="text-3xl">{{projectGroupName()}}</span> <span class="text-3xl">{{ projectGroupName() }}</span>
<createprojectbutton v-on:click="goToCreate($event)" /> <createprojectbutton v-on:click="goToCreate($event)" />
</div> </div>
@ -16,9 +15,19 @@
<ul class="flex-grow tab"> <ul class="flex-grow tab">
<li <li
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.match('project group project') || $route.name.endsWith('project group') }]" :class="[
{
'tab-element-selected':
$route.name.match('project group project') ||
$route.name.endsWith('project group'),
},
]"
> >
<router-link :to="projectGroupProjectsLink(ownertype, ownername, projectgroupref)"> <router-link
:to="
projectGroupProjectsLink(ownertype, ownername, projectgroupref)
"
>
<i class="mdi mdi-home" /> <i class="mdi mdi-home" />
<span>Projects</span> <span>Projects</span>
</router-link> </router-link>
@ -26,9 +35,19 @@
<li <li
v-if="$route.name.endsWith('project group settings')" v-if="$route.name.endsWith('project group settings')"
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.endsWith('project group settings') }]" :class="[
{
'tab-element-selected': $route.name.endsWith(
'project group settings'
),
},
]"
> >
<router-link :to="projectGroupSettingsLink(ownertype, ownername, projectgroupref)"> <router-link
:to="
projectGroupSettingsLink(ownertype, ownername, projectgroupref)
"
>
<i class="mdi mdi-settings" /> <i class="mdi mdi-settings" />
<span>Project Group Settings</span> <span>Project Group Settings</span>
</router-link> </router-link>
@ -39,7 +58,7 @@
<div class="relative"> <div class="relative">
<div <div
class="flex -mt-3" class="flex -mt-3"
v-click-outside="() => dropdownActive = false" v-click-outside="() => (dropdownActive = false)"
@click="dropdownActive = !dropdownActive" @click="dropdownActive = !dropdownActive"
> >
<button <button
@ -57,7 +76,13 @@
<li> <li>
<router-link <router-link
class="block px-4 py-2 hover:bg-blue-500 hover:text-white" class="block px-4 py-2 hover:bg-blue-500 hover:text-white"
:to="projectGroupSettingsLink(ownertype, ownername, projectgroupref)" :to="
projectGroupSettingsLink(
ownertype,
ownername,
projectgroupref
)
"
> >
<i class="mdi mdi-settings" /> <i class="mdi mdi-settings" />
<span>Project Group Settings</span> <span>Project Group Settings</span>
@ -73,34 +98,33 @@
</div> </div>
</template> </template>
<script> <script>
import * as vClickOutside from "v-click-outside-x"; import * as vClickOutside from 'v-click-outside-x';
import { import {
projectGroupProjectsLink, projectGroupProjectsLink,
projectGroupSettingsLink, projectGroupSettingsLink,
projectGroupCreateProjectGroupLink, projectGroupCreateProjectGroupLink,
projectGroupCreateProjectLink projectGroupCreateProjectLink,
} from "@/util/link.js"; } from '@/util/link.js';
import projbreadcrumbs from "@/components/projbreadcrumbs.vue"; import projbreadcrumbs from '@/components/projbreadcrumbs.vue';
import createprojectbutton from "@/components/createprojectbutton.vue"; import createprojectbutton from '@/components/createprojectbutton.vue';
export default { export default {
name: "ProjectGroup", name: 'ProjectGroup',
components: { projbreadcrumbs, createprojectbutton }, components: { projbreadcrumbs, createprojectbutton },
directives: { directives: {
clickOutside: vClickOutside.directive clickOutside: vClickOutside.directive,
}, },
props: { props: {
ownertype: String, ownertype: String,
ownername: String, ownername: String,
projectgroupref: Array projectgroupref: Array,
}, },
data() { data() {
return { return {
dropdownActive: false dropdownActive: false,
}; };
}, },
methods: { methods: {
@ -110,12 +134,12 @@ export default {
projectGroupCreateProjectLink: projectGroupCreateProjectLink, projectGroupCreateProjectLink: projectGroupCreateProjectLink,
projectGroupName() { projectGroupName() {
if (!this.projectgroupref.length) { if (!this.projectgroupref.length) {
return "Root Project Group"; return 'Root Project Group';
} }
return this.projectgroupref[this.projectgroupref.length - 1]; return this.projectgroupref[this.projectgroupref.length - 1];
}, },
goToCreate(type) { goToCreate(type) {
if (type == "project") { if (type == 'project') {
this.$router.push( this.$router.push(
projectGroupCreateProjectLink( projectGroupCreateProjectLink(
this.ownertype, this.ownertype,
@ -132,10 +156,9 @@ export default {
this.projectgroupref this.projectgroupref
) )
); );
} },
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -28,13 +28,8 @@
class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" class="mb-10 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative"
> >
No remote sources defined No remote sources defined
<router-link <router-link class="underline text-blue-600 block" to="/newsource">
class="underline text-blue-600 block" <button class="btn btn-blue">Create one</button>
to="/newsource"
>
<button class="btn btn-blue">
Create one
</button>
</router-link> </router-link>
</div> </div>
<div <div
@ -75,29 +70,29 @@
</template> </template>
<script> <script>
import { mapGetters } from "vuex"; import { mapGetters } from 'vuex';
import LoginForm from "@/components/loginform"; import LoginForm from '@/components/loginform';
import RegisterForm from "@/components/registerform"; import RegisterForm from '@/components/registerform';
import { fetchRemoteSources, register } from "@/util/data"; import { fetchRemoteSources, register } from '@/util/data';
import { authorizeurl, fetch, doLogout } from "@/util/auth"; import { authorizeurl, fetch, doLogout } from '@/util/auth';
export default { export default {
name: "Register", name: 'Register',
components: { components: {
LoginForm, LoginForm,
RegisterForm RegisterForm,
}, },
data: function() { data: function () {
return { return {
error: null, error: null,
remotesources: null remotesources: null,
}; };
}, },
computed: { computed: {
...mapGetters(["registeruser"]), ...mapGetters(['registeruser']),
hasRemoteSources() { hasRemoteSources() {
if (this.remotesources) { if (this.remotesources) {
@ -112,13 +107,13 @@ export default {
} }
} }
return false; return false;
} },
}, },
methods: { methods: {
async fetchRemoteSources() { async fetchRemoteSources() {
let { data, error } = await fetchRemoteSources(); let { data, error } = await fetchRemoteSources();
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.remotesources = data; this.remotesources = data;
@ -127,12 +122,12 @@ export default {
let u = authorizeurl(); let u = authorizeurl();
let res = await ( let res = await (
await fetch(u, { await fetch(u, {
method: "POST", method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
remote_source_name: remotesourcename, remote_source_name: remotesourcename,
login_name: username, login_name: username,
password: password password: password,
}) }),
}) })
).json(); ).json();
@ -140,11 +135,11 @@ export default {
window.location = res.oauth2_redirect; window.location = res.oauth2_redirect;
return; return;
} }
this.$store.dispatch("setRegisterUser", { this.$store.dispatch('setRegisterUser', {
remote_user_info: res.remote_user_info, remote_user_info: res.remote_user_info,
remote_source_name: res.remote_source_name, remote_source_name: res.remote_source_name,
remote_source_login_name: username, remote_source_login_name: username,
remote_source_login_password: password remote_source_login_password: password,
}); });
}, },
async doRegister( async doRegister(
@ -170,18 +165,15 @@ export default {
window.location = data.oauth2_redirect; window.location = data.oauth2_redirect;
return; return;
} }
this.$router.push({ name: "home" }); this.$router.push({ name: 'home' });
} },
}, },
mounted: function() { mounted: function () {
this.$store.dispatch("setError", null); this.$store.dispatch('setError', null);
}, },
created: function() { created: function () {
doLogout(); doLogout();
this.fetchRemoteSources(); this.fetchRemoteSources();
} },
}; };
</script> </script>

View File

@ -9,13 +9,15 @@
<span class="mx-2">/</span> <span class="mx-2">/</span>
</li> </li>
<li> <li>
<router-link :to="ownerLink('user', username)">{{username}}</router-link> <router-link :to="ownerLink('user', username)">{{
username
}}</router-link>
</li> </li>
</ol> </ol>
</nav> </nav>
<div class="mb-8 flex justify-between"> <div class="mb-8 flex justify-between">
<span class="text-3xl">{{username}}</span> <span class="text-3xl">{{ username }}</span>
<createprojectbutton v-on:click="goToCreate($event)" /> <createprojectbutton v-on:click="goToCreate($event)" />
</div> </div>
@ -23,7 +25,12 @@
<ul class="flex-grow tab"> <ul class="flex-grow tab">
<li <li
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name === 'user projects' || $route.name === 'user' }]" :class="[
{
'tab-element-selected':
$route.name === 'user projects' || $route.name === 'user',
},
]"
> >
<router-link :to="ownerProjectsLink('user', username)"> <router-link :to="ownerProjectsLink('user', username)">
<i class="mr-1 mdi mdi-home" /> <i class="mr-1 mdi mdi-home" />
@ -32,7 +39,9 @@
</li> </li>
<li <li
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name === 'user direct runs' }]" :class="[
{ 'tab-element-selected': $route.name === 'user direct runs' },
]"
> >
<router-link :to="userDirectRunsLink(username)"> <router-link :to="userDirectRunsLink(username)">
<i class="mr-1 mdi mdi-run-fast" /> <i class="mr-1 mdi mdi-run-fast" />
@ -40,19 +49,29 @@
</router-link> </router-link>
</li> </li>
<li <li
v-if="run && ($route.name === 'user direct run' || $route.name == 'user direct run task')" v-if="
run &&
($route.name === 'user direct run' ||
$route.name == 'user direct run task')
"
> >
<tabarrow /> <tabarrow />
</li> </li>
<li <li
class="tab-element" class="tab-element"
v-if="run && ($route.name === 'user direct run' || $route.name == 'user direct run task')" v-if="
:class="[{ 'tab-element-selected': $route.name === 'user direct run' }]" run &&
($route.name === 'user direct run' ||
$route.name == 'user direct run task')
"
:class="[
{ 'tab-element-selected': $route.name === 'user direct run' },
]"
> >
<router-link :to="userDirectRunLink(username, $route.params.runid)"> <router-link :to="userDirectRunLink(username, $route.params.runid)">
<span> <span>
Run Run
<strong>#{{run.counter}}</strong> <strong>#{{ run.counter }}</strong>
</span> </span>
</router-link> </router-link>
</li> </li>
@ -62,21 +81,35 @@
<li <li
class="tab-element" class="tab-element"
v-if="run && $route.name == 'user direct run task'" v-if="run && $route.name == 'user direct run task'"
:class="[{ 'tab-element-selected': $route.name === 'user direct run task' }]" :class="[
{ 'tab-element-selected': $route.name === 'user direct run task' },
]"
> >
<router-link <router-link
:to="userDirectRunTaskLink(username, $route.params.runid, $route.params.taskid)" :to="
userDirectRunTaskLink(
username,
$route.params.runid,
$route.params.taskid
)
"
> >
<span> <span>
Task Task
<strong>{{run.tasks[$route.params.taskid].name}}</strong> <strong>{{ run.tasks[$route.params.taskid].name }}</strong>
</span> </span>
</router-link> </router-link>
</li> </li>
<li <li
v-if="$route.name.endsWith('user project group settings')" v-if="$route.name.endsWith('user project group settings')"
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.endsWith('user project group settings') }]" :class="[
{
'tab-element-selected': $route.name.endsWith(
'user project group settings'
),
},
]"
> >
<router-link :to="projectGroupSettingsLink('user', username, [])"> <router-link :to="projectGroupSettingsLink('user', username, [])">
<i class="mr-1 mdi mdi-settings" /> <i class="mr-1 mdi mdi-settings" />
@ -86,7 +119,9 @@
<li <li
v-if="$route.name.endsWith('user settings')" v-if="$route.name.endsWith('user settings')"
class="tab-element" class="tab-element"
:class="[{ 'tab-element-selected': $route.name.endsWith('user settings') }]" :class="[
{ 'tab-element-selected': $route.name.endsWith('user settings') },
]"
> >
<router-link :to="ownerSettingsLink('user', username)"> <router-link :to="ownerSettingsLink('user', username)">
<i class="mr-1 mdi mdi-settings" /> <i class="mr-1 mdi mdi-settings" />
@ -99,7 +134,7 @@
<div class="relative"> <div class="relative">
<div <div
class="flex -mt-3" class="flex -mt-3"
v-click-outside="() => dropdownActive = false" v-click-outside="() => (dropdownActive = false)"
@click="dropdownActive = !dropdownActive" @click="dropdownActive = !dropdownActive"
> >
<button <button
@ -134,7 +169,7 @@
</template> </template>
<script> <script>
import * as vClickOutside from "v-click-outside-x"; import * as vClickOutside from 'v-click-outside-x';
import { import {
ownerLink, ownerLink,
@ -145,33 +180,33 @@ import {
ownerSettingsLink, ownerSettingsLink,
projectGroupCreateProjectGroupLink, projectGroupCreateProjectGroupLink,
projectGroupCreateProjectLink, projectGroupCreateProjectLink,
projectGroupSettingsLink projectGroupSettingsLink,
} from "@/util/link.js"; } from '@/util/link.js';
import { fetchRun } from "@/util/data.js"; import { fetchRun } from '@/util/data.js';
import createprojectbutton from "@/components/createprojectbutton.vue"; import createprojectbutton from '@/components/createprojectbutton.vue';
import tabarrow from "@/components/tabarrow.vue"; import tabarrow from '@/components/tabarrow.vue';
export default { export default {
name: "User", name: 'User',
components: { createprojectbutton, tabarrow }, components: { createprojectbutton, tabarrow },
directives: { directives: {
clickOutside: vClickOutside.directive clickOutside: vClickOutside.directive,
}, },
props: { props: {
username: String username: String,
}, },
data() { data() {
return { return {
fetchAbort: null, fetchAbort: null,
dropdownActive: false, dropdownActive: false,
run: null run: null,
}; };
}, },
watch: { watch: {
$route: async function(route) { $route: async function (route) {
if (this.fetchAbort) { if (this.fetchAbort) {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
@ -187,12 +222,12 @@ export default {
return; return;
} }
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.run = data; this.run = data;
} }
} },
}, },
methods: { methods: {
ownerLink: ownerLink, ownerLink: ownerLink,
@ -205,18 +240,18 @@ export default {
projectGroupCreateProjectLink: projectGroupCreateProjectLink, projectGroupCreateProjectLink: projectGroupCreateProjectLink,
projectGroupSettingsLink: projectGroupSettingsLink, projectGroupSettingsLink: projectGroupSettingsLink,
goToCreate(type) { goToCreate(type) {
if (type == "project") { if (type == 'project') {
this.$router.push( this.$router.push(
projectGroupCreateProjectLink("user", this.username, []) projectGroupCreateProjectLink('user', this.username, [])
); );
return; return;
} }
this.$router.push( this.$router.push(
projectGroupCreateProjectGroupLink("user", this.username, []) projectGroupCreateProjectGroupLink('user', this.username, [])
); );
} },
}, },
created: async function() { created: async function () {
this.fetchAbort = new AbortController(); this.fetchAbort = new AbortController();
if (this.$route.params.runid) { if (this.$route.params.runid) {
@ -228,7 +263,7 @@ export default {
return; return;
} }
if (error) { if (error) {
this.$store.dispatch("setError", error); this.$store.dispatch('setError', error);
return; return;
} }
this.run = data; this.run = data;
@ -238,9 +273,8 @@ export default {
if (this.fetchAbort) { if (this.fetchAbort) {
this.fetchAbort.abort(); this.fetchAbort.abort();
} }
} },
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -6,43 +6,54 @@ module.exports = {
padding: '2rem', padding: '2rem',
}, },
fontFamily: { fontFamily: {
'sans': ['Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans', 'sans-serif', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'] sans: [
'Segoe UI',
'Roboto',
'Helvetica Neue',
'Arial',
'Noto Sans',
'sans-serif',
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol',
'Noto Color Emoji',
],
}, },
borderWidth: { borderWidth: {
default: '1px', default: '1px',
'0': '0', 0: '0',
'2': '2px', 2: '2px',
'4': '4px', 4: '4px',
'5': '5px', 5: '5px',
'6': '6px', 6: '6px',
}, },
spacing: { spacing: {
px: '1px', px: '1px',
'2px': '2px', '2px': '2px',
'3px': '3px', '3px': '3px',
'0': '0', 0: '0',
'1': '0.25rem', 1: '0.25rem',
'2': '0.5rem', 2: '0.5rem',
'3': '0.75rem', 3: '0.75rem',
'4': '1rem', 4: '1rem',
'5': '1.25rem', 5: '1.25rem',
'6': '1.5rem', 6: '1.5rem',
'8': '2rem', 8: '2rem',
'10': '2.5rem', 10: '2.5rem',
'12': '3rem', 12: '3rem',
'16': '4rem', 16: '4rem',
'20': '5rem', 20: '5rem',
'24': '6rem', 24: '6rem',
'32': '8rem', 32: '8rem',
'40': '10rem', 40: '10rem',
'48': '12rem', 48: '12rem',
'56': '14rem', 56: '14rem',
'64': '16rem', 64: '16rem',
}, },
colors: { colors: {
dark: '#4a4a4a', dark: '#4a4a4a',
} },
} },
}, },
variants: { variants: {
backgroundColor: ['responsive', 'hover', 'focus', 'disabled'], backgroundColor: ['responsive', 'hover', 'focus', 'disabled'],
@ -56,9 +67,9 @@ module.exports = {
function ({ addVariant, e }) { function ({ addVariant, e }) {
addVariant('disabled', ({ modifySelectors, separator }) => { addVariant('disabled', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => { modifySelectors(({ className }) => {
return `.${e(`disabled${separator}${className}`)}:disabled` return `.${e(`disabled${separator}${className}`)}:disabled`;
}) });
}) });
} },
] ],
} };

View File

@ -1,7 +1,7 @@
const path = require("path"); const path = require('path');
module.exports = { module.exports = {
css: { css: {
sourceMap: true sourceMap: true,
} },
}; };