Initial commit
This commit is contained in:
commit
b780e148cc
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
|
@ -0,0 +1,21 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw*
|
|
@ -0,0 +1,15 @@
|
||||||
|
FROM node:lts-alpine AS web_build
|
||||||
|
|
||||||
|
WORKDIR /agola-web
|
||||||
|
|
||||||
|
# copy both 'package.json' and 'package-lock.json' (if available)
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# install project dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy all the source
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build app
|
||||||
|
RUN npm run build
|
|
@ -0,0 +1,202 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# agola-web
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run your tests
|
||||||
|
```
|
||||||
|
npm run test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/app'
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"name": "agola-web",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@mdi/font": "^3.3.92",
|
||||||
|
"ansi_up": "^3.0.0",
|
||||||
|
"axios": "^0.18.0",
|
||||||
|
"bulma": "^0.7.4",
|
||||||
|
"moment": "^2.23.0",
|
||||||
|
"moment-duration-format": "^2.2.2",
|
||||||
|
"vue": "^2.5.21",
|
||||||
|
"vue-router": "^3.0.2",
|
||||||
|
"vue2-filters": "^0.4.1",
|
||||||
|
"vuex": "^3.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/cli-plugin-babel": "^3.4.0",
|
||||||
|
"@vue/cli-plugin-eslint": "^3.4.0",
|
||||||
|
"@vue/cli-service": "^3.4.0",
|
||||||
|
"babel-eslint": "^10.0.1",
|
||||||
|
"eslint": "^5.12.0",
|
||||||
|
"eslint-plugin-vue": "^5.1.0",
|
||||||
|
"node-sass": "^4.11.0",
|
||||||
|
"sass-loader": "^7.1.0",
|
||||||
|
"vue-template-compiler": "^2.5.21"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"rules": {},
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "babel-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postcss": {
|
||||||
|
"plugins": {
|
||||||
|
"autoprefixer": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not ie <= 8"
|
||||||
|
]
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
|
||||||
|
<title>agola</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but agola doesn't work properly without JavaScript
|
||||||
|
enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<script src="/config.js" />
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
// default config if no config.js is provided
|
||||||
|
if (!window.CONFIG) {
|
||||||
|
const CONFIG = {
|
||||||
|
API_URL: window.location.protocol + "//" + window.location.hostname + ":8000",
|
||||||
|
API_BASE_PATH: "/api/v1alpha"
|
||||||
|
}
|
||||||
|
window.CONFIG = CONFIG
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,80 @@
|
||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<nav class="navbar is-light has-shadow" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" href="/">
|
||||||
|
<h1>Agola</h1>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
role="button"
|
||||||
|
class="navbar-burger burger"
|
||||||
|
aria-label="menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
data-target="navbarBasicExample"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="navbar-menu">
|
||||||
|
<div class="navbar-start"></div>
|
||||||
|
<div class="navbar-end">
|
||||||
|
<div v-if="user" class="navbar-item has-dropdown is-hoverable">
|
||||||
|
<a class="navbar-link">{{user.username}}</a>
|
||||||
|
<div class="navbar-dropdown">
|
||||||
|
<div class="navbar-item">
|
||||||
|
Logged as
|
||||||
|
<b>{{user.username}}</b>
|
||||||
|
</div>
|
||||||
|
<hr class="navbar-divider">
|
||||||
|
<router-link class="navbar-item" to="/logout">
|
||||||
|
<i class="mdi mdi-logout"></i>Logout
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="navbar-item">
|
||||||
|
<router-link class="button" to="/login">Login</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="main-container container">
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "App",
|
||||||
|
components: {},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(["user"])
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
user: function(user) {
|
||||||
|
if (user) {
|
||||||
|
this.$router.push({
|
||||||
|
name: "user",
|
||||||
|
params: { username: this.user.username }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "@/css/main.scss";
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,156 @@
|
||||||
|
<template>
|
||||||
|
<div class="item">
|
||||||
|
<div
|
||||||
|
class="touchable"
|
||||||
|
:class="stepClass(step)"
|
||||||
|
role="tab"
|
||||||
|
:aria-expanded="active ? 'true' : 'false'"
|
||||||
|
@click.prevent="toggle"
|
||||||
|
>
|
||||||
|
<div class="item-content">
|
||||||
|
<div class="header">
|
||||||
|
<span class="icon">
|
||||||
|
<i
|
||||||
|
class="mdi mdi-arrow-right"
|
||||||
|
:class="{ 'arrow-down': active, 'arrow-right': !active }"
|
||||||
|
></i>
|
||||||
|
</span>
|
||||||
|
<span class="name">{{step.name}}</span>
|
||||||
|
<span class="duration">{{duration}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="log-container" v-show="active">
|
||||||
|
<Log
|
||||||
|
v-bind:runid="runid"
|
||||||
|
v-bind:taskid="taskid"
|
||||||
|
v-bind:step="stepnum"
|
||||||
|
v-bind:stepphase="step.phase"
|
||||||
|
v-bind:show="active"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as moment from "moment";
|
||||||
|
import momentDurationFormatSetup from "moment-duration-format";
|
||||||
|
import Log from "@/components/log.vue";
|
||||||
|
|
||||||
|
momentDurationFormatSetup(moment);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Collapse",
|
||||||
|
components: {
|
||||||
|
Log
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
active: false,
|
||||||
|
duration: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
runid: String,
|
||||||
|
taskid: String,
|
||||||
|
stepnum: Number,
|
||||||
|
step: Object
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.updateDuration(this.step);
|
||||||
|
},
|
||||||
|
ready() {
|
||||||
|
if (this.active) {
|
||||||
|
this.$emit("collapse-open", this.index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
step: function(step) {
|
||||||
|
this.updateDuration(step);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
stepClass(step) {
|
||||||
|
if (step.phase == "success") return "success";
|
||||||
|
if (step.phase == "failed") return "failed";
|
||||||
|
if (step.phase == "stopped") return "failed";
|
||||||
|
if (step.phase == "running") return "running";
|
||||||
|
return "unknown";
|
||||||
|
},
|
||||||
|
updateDuration(step) {
|
||||||
|
let start = moment(step.start_time);
|
||||||
|
let end = moment(step.end_time);
|
||||||
|
if (start === null || end === null) {
|
||||||
|
this.duration = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.duration = moment.duration(end.diff(start)).format("h:mm:ss[s]");
|
||||||
|
},
|
||||||
|
toggle() {
|
||||||
|
this.active = !this.active;
|
||||||
|
if (this.active) {
|
||||||
|
this.$emit("collapse-open", this.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/css/_variables.scss";
|
||||||
|
|
||||||
|
.item {
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-content {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border: 1px solid $grey-lighter;
|
||||||
|
border-left: 0 solid;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
border-left: 5px solid $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
border-left: 5px solid $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running {
|
||||||
|
border-left: 5px solid $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unknown {
|
||||||
|
border-left: 5px solid $grey-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
flex: 0 0 30%;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-container {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-right {
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-down {
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,146 @@
|
||||||
|
<template>
|
||||||
|
<div class="dark">
|
||||||
|
<div class="log">
|
||||||
|
<div class="stream-line" v-for="(item, index) in items" :key="index">
|
||||||
|
<div v-html="item"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { apiurl, apiurlwithtoken, fetch } from "@/util/auth";
|
||||||
|
import AnsiUp from "ansi_up";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Log",
|
||||||
|
props: {
|
||||||
|
show: Boolean,
|
||||||
|
runid: String,
|
||||||
|
taskid: String,
|
||||||
|
step: Number,
|
||||||
|
stepphase: String
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
data() {
|
||||||
|
let formatter = new AnsiUp();
|
||||||
|
formatter.use_classes = true;
|
||||||
|
|
||||||
|
return {
|
||||||
|
items: [],
|
||||||
|
lines: [],
|
||||||
|
formatter: formatter,
|
||||||
|
es: null,
|
||||||
|
fetching: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch: function() {
|
||||||
|
if (this.fetching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.fetching = true;
|
||||||
|
if (this.stepphase == "running") {
|
||||||
|
this.streamLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stepphase == "success" || this.stepphase == "failed") {
|
||||||
|
this.getLogs();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
streamLogs: function() {
|
||||||
|
this.es = new EventSource(
|
||||||
|
apiurlwithtoken(
|
||||||
|
"/logs?runID=" +
|
||||||
|
this.runid +
|
||||||
|
"&taskID=" +
|
||||||
|
this.taskid +
|
||||||
|
"&step=" +
|
||||||
|
this.step +
|
||||||
|
"&follow"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.es.onmessage = event => {
|
||||||
|
var data = event.data;
|
||||||
|
// TODO(sgotti) ansi_up doesn't handle carriage return (\r), find a way to also handle it
|
||||||
|
this.items.push(this.formatter.ansi_to_html(data));
|
||||||
|
};
|
||||||
|
// don't reconnect on error
|
||||||
|
this.es.onerror = () => {
|
||||||
|
this.es.close();
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getLogs: function() {
|
||||||
|
fetch(
|
||||||
|
apiurl(
|
||||||
|
"/logs?runID=" +
|
||||||
|
this.runid +
|
||||||
|
"&taskID=" +
|
||||||
|
this.taskid +
|
||||||
|
"&step=" +
|
||||||
|
this.step
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(r => {
|
||||||
|
if (r.status == 200) {
|
||||||
|
return r.text();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.items.push(this.formatter.ansi_to_html(data));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
show: function(post, pre) {
|
||||||
|
if (pre == false && post == true) {
|
||||||
|
this.fetch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
stepphase: function(post, pre) {
|
||||||
|
if (pre == "notstarted" && post == "running") {
|
||||||
|
this.streamLogs();
|
||||||
|
}
|
||||||
|
if (pre == "notstarted" && (post == "success" || post == "failed")) {
|
||||||
|
this.getLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pre == "running" && (post == "success" || post == "failed")) {
|
||||||
|
// TODO(sgotti)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function() {
|
||||||
|
if (this.show) {
|
||||||
|
this.fetch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.es !== null) {
|
||||||
|
this.es.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.log {
|
||||||
|
background-color: #222;
|
||||||
|
color: #f1f1f1;
|
||||||
|
font-family: Cousine, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 19px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
.stream-line {
|
||||||
|
pre {
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,52 @@
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="field">
|
||||||
|
<p class="control has-icons-left has-icons-right">
|
||||||
|
<input v-model="username" class="input" type="email" placeholder="Email">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fas fa-envelope"></i>
|
||||||
|
</span>
|
||||||
|
<span class="icon is-small is-right">
|
||||||
|
<i class="fas fa-check"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<p class="control has-icons-left">
|
||||||
|
<input v-model="password" class="input" type="password" placeholder="Password">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fas fa-lock"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<p class="control">
|
||||||
|
<button
|
||||||
|
@click="$emit('login', { username, password })"
|
||||||
|
class="button is-info is-fullwidth"
|
||||||
|
>Login with {{name}}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { apiurl, loginurl, fetch } from "@/util/auth";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Loginform",
|
||||||
|
props: {
|
||||||
|
name: String
|
||||||
|
},
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
username: null,
|
||||||
|
password: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
<template>
|
||||||
|
<nav class="breadcrumb is-large" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<router-link :to="ownerLink(ownertype, ownername)">{{ownername}}</router-link>
|
||||||
|
</li>
|
||||||
|
<li v-if="projectname">
|
||||||
|
<router-link :to="projectLink(ownertype, ownername, projectname)">{{projectname}}</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ownerLink, projectLink } from "@/util/link.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "projbreadcrumbs",
|
||||||
|
components: {},
|
||||||
|
props: {
|
||||||
|
ownertype: String,
|
||||||
|
ownername: String,
|
||||||
|
projectname: String
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
ownerLink: ownerLink,
|
||||||
|
projectLink: projectLink
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/css/_variables.scss";
|
||||||
|
</style>
|
|
@ -0,0 +1,80 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="item-list" v-for="project in projects" v-bind:key="project.id">
|
||||||
|
<router-link tag="div" class="item" :to="projectURL(project)">
|
||||||
|
<span class="name">{{project.name}}</span>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { apiurl, fetch } from "@/util/auth";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
name: "Projects",
|
||||||
|
props: {
|
||||||
|
ownertype: String,
|
||||||
|
ownername: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
projects: [],
|
||||||
|
polling: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
projectURL(project) {
|
||||||
|
if (this.ownertype == "user") {
|
||||||
|
return {
|
||||||
|
name: "user project",
|
||||||
|
params: { username: this.ownername, projectname: project.name }
|
||||||
|
};
|
||||||
|
} else if (this.ownertype == "org") {
|
||||||
|
return {
|
||||||
|
name: "org project",
|
||||||
|
params: { orgname: this.ownername, projectname: project.name }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchProjects(ownertype, ownername) {
|
||||||
|
let path = "/" + ownertype;
|
||||||
|
if (ownername) {
|
||||||
|
path += "/" + ownername;
|
||||||
|
}
|
||||||
|
path += "/projects";
|
||||||
|
fetch(apiurl(path))
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
console.log(res);
|
||||||
|
let projects = res.projects.map(function(project) {
|
||||||
|
return project;
|
||||||
|
});
|
||||||
|
this.projects = projects;
|
||||||
|
console.log("projects", this.projects);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function() {
|
||||||
|
this.fetchProjects(this.ownertype, this.ownername);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/css/_variables.scss";
|
||||||
|
|
||||||
|
.item-list {
|
||||||
|
.item {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border: 1px solid $grey-lighter;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,158 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<RunDetail :run="run"/>
|
||||||
|
|
||||||
|
<div class="tabs">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a>Tasks</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="run" class="tasks-list">
|
||||||
|
<div v-for="task in run.sortedTasks" v-bind:key="task.id" :class="taskClass(task)">
|
||||||
|
<div class="task-content">
|
||||||
|
<div class="columns">
|
||||||
|
<router-link class="column is-10" tag="a" :to="runTaskLink(task)">
|
||||||
|
<span class="name">{{task.name}}</span>
|
||||||
|
</router-link>
|
||||||
|
<div class="parents column">
|
||||||
|
<span v-if="parents(task).length > 0">depends on: </span>
|
||||||
|
<span class="parent" v-for="dep in parents(task)" v-bind:key="dep">{{dep}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <span
|
||||||
|
class="duration"
|
||||||
|
v-if="duration && (step.Phase == 'success' || step.Phase == 'failed') "
|
||||||
|
>{{duration}}</span>-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { fetchRun } from "@/util/data.js";
|
||||||
|
import { userLocalRunTaskLink, projectRunTaskLink } from "@/util/link.js";
|
||||||
|
|
||||||
|
import RunDetail from "@/components/rundetail.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "run",
|
||||||
|
components: { RunDetail },
|
||||||
|
props: {
|
||||||
|
ownertype: String,
|
||||||
|
ownername: String,
|
||||||
|
projectname: String,
|
||||||
|
runid: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
run: null,
|
||||||
|
polling: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
runTaskLink(task) {
|
||||||
|
if (this.projectname) {
|
||||||
|
return projectRunTaskLink(
|
||||||
|
this.ownertype,
|
||||||
|
this.ownername,
|
||||||
|
this.projectname,
|
||||||
|
this.runid,
|
||||||
|
task.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return userLocalRunTaskLink(this.ownername, this.runid, task.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parents(task) {
|
||||||
|
return task.depends.map(d => {
|
||||||
|
console.log(d.task_id);
|
||||||
|
return this.run.tasks[d.task_id].name;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
taskClass(task) {
|
||||||
|
if (task.status == "success") return "success";
|
||||||
|
if (task.status == "failed") return "failed";
|
||||||
|
if (task.status == "stopped") return "failed";
|
||||||
|
if (task.status == "running") return "running";
|
||||||
|
return "unknown";
|
||||||
|
},
|
||||||
|
async fetchRun() {
|
||||||
|
this.run = await fetchRun(this.runid);
|
||||||
|
// sort tasks by level
|
||||||
|
let tasks = this.run.tasks;
|
||||||
|
let sortedTasks = Object.keys(this.run.tasks)
|
||||||
|
.sort((a, b) =>
|
||||||
|
tasks[a].level > tasks[b].level
|
||||||
|
? 1
|
||||||
|
: tasks[b].level > tasks[a].level
|
||||||
|
? -1
|
||||||
|
: 0
|
||||||
|
)
|
||||||
|
.map(k => this.run.tasks[k]);
|
||||||
|
this.run.sortedTasks = sortedTasks;
|
||||||
|
console.log("run: ", this.run);
|
||||||
|
},
|
||||||
|
pollData() {
|
||||||
|
this.polling = setInterval(() => {
|
||||||
|
this.fetchRun();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function() {
|
||||||
|
this.fetchRun();
|
||||||
|
this.pollData();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.polling);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/css/_variables.scss";
|
||||||
|
|
||||||
|
.tasks-list {
|
||||||
|
.task-content {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border: 1px solid $grey-lighter;
|
||||||
|
border-left: 0 solid;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
border-left: 5px solid $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
border-left: 5px solid $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running {
|
||||||
|
border-left: 5px solid $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unknown {
|
||||||
|
border-left: 5px solid $grey-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.parents {
|
||||||
|
margin-left: 1rem;
|
||||||
|
margin-right: 0rem;
|
||||||
|
font-weight: lighter;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
.parent {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,252 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="run != null">
|
||||||
|
<div class="run">
|
||||||
|
<div :class="runResultClass(run)">
|
||||||
|
<div class="run-content">
|
||||||
|
<div class="item-content columns">
|
||||||
|
<div class="run-title column is-10">
|
||||||
|
<span class="run-name">{{run.name}}</span>
|
||||||
|
<span
|
||||||
|
class="tag"
|
||||||
|
:class="'is-'+runResultClass(run)"
|
||||||
|
>{{ runStatus(run) | capitalize }}</span>
|
||||||
|
<span v-if="stillRunning(run)" class="stillrunning tag">Still running</span>
|
||||||
|
<span v-if="!stillRunning(run)" class="stillrunning"></span>
|
||||||
|
</div>
|
||||||
|
<div class="run-actions column is-2 is-pulled-right">
|
||||||
|
<div class="dropdown is-hoverable is-right" v-if="run.phase == 'finished'">
|
||||||
|
<div class="dropdown-trigger">
|
||||||
|
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu">
|
||||||
|
<span>Restart</span>
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="mdi mdi-restart" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-menu" role="menu">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a class="dropdown-item" @click="restartRun(run, true)">From start</a>
|
||||||
|
<a class="dropdown-item" @click="restartRun(run)">From failed tasks</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="button is-danger"
|
||||||
|
v-if="run.phase == 'running'"
|
||||||
|
@click="stopRun(run)"
|
||||||
|
>Stop</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-content columns">
|
||||||
|
<div class="commitmessage column">{{run.annotations.message}}</div>
|
||||||
|
<div class="source-info column">
|
||||||
|
<a :href="run.annotations.commit_link" class="commit" target="_blank">
|
||||||
|
<i class="mdi mdi-source-commit mdi-rotate-90"></i>
|
||||||
|
<span>{{run.annotations.commit_sha.substring(0,8)}}</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
v-if="run.annotations.event_type == 'push'"
|
||||||
|
:href="run.annotations.branch_link"
|
||||||
|
class="commit"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-source-branch"></i>
|
||||||
|
<span>{{run.annotations.branch}}</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
v-else-if="run.annotations.event_type == 'tag'"
|
||||||
|
:href="run.annotations.tag_link"
|
||||||
|
class="commit"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-tag"></i>
|
||||||
|
<span>{{run.annotations.tag}}</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
v-else-if="run.annotations.event_type == 'pull_request'"
|
||||||
|
:href="run.annotations.pull_request_link"
|
||||||
|
class="commit"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-source-pull"></i>
|
||||||
|
<span>PR #{{run.annotations.pull_request_id}}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { apiurl, fetch } from "@/util/auth";
|
||||||
|
import { userLocalRunTaskLink, projectRunTaskLink } from "@/util/link.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "RunDetail",
|
||||||
|
props: {
|
||||||
|
run: Object
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
stillRunning(run) {
|
||||||
|
return run.result != "unknown" && run.phase == "running";
|
||||||
|
},
|
||||||
|
runStatus(run) {
|
||||||
|
if (run.phase != "finished") return run.phase;
|
||||||
|
if (run.result != "unknown") return run.result;
|
||||||
|
if (run.stopping) return "stopping";
|
||||||
|
|
||||||
|
return run.result;
|
||||||
|
},
|
||||||
|
runResultClass(run) {
|
||||||
|
status = this.runStatus(run);
|
||||||
|
|
||||||
|
if (status == "queued") return "unknown";
|
||||||
|
if (status == "cancelled") return "failed";
|
||||||
|
if (status == "running") return "running";
|
||||||
|
if (status == "stopping") return "failed";
|
||||||
|
if (status == "stopped") return "failed";
|
||||||
|
if (status == "success") return "success";
|
||||||
|
if (status == "failed") return "failed";
|
||||||
|
return "unknown";
|
||||||
|
},
|
||||||
|
taskClass(task) {
|
||||||
|
if (task.status == "success") return "success";
|
||||||
|
if (task.status == "failed") return "failed";
|
||||||
|
if (task.status == "stopped") return "failed";
|
||||||
|
if (task.status == "running") return "running";
|
||||||
|
return "unknown";
|
||||||
|
},
|
||||||
|
restartRun(run, fromStart) {
|
||||||
|
fetch(apiurl("/run/" + run.id + "/actions"), {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
action_type: "restart",
|
||||||
|
from_start: fromStart
|
||||||
|
})
|
||||||
|
}).then(r => {
|
||||||
|
console.log("r: " + r);
|
||||||
|
if (r.status == 200) {
|
||||||
|
return r.json();
|
||||||
|
}
|
||||||
|
throw Error(r.statusText);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stopRun(run) {
|
||||||
|
fetch(apiurl("/run/" + run.id + "/actions"), {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
action_type: "stop"
|
||||||
|
})
|
||||||
|
}).then(r => {
|
||||||
|
console.log("r: " + r);
|
||||||
|
if (r.status == 200) {
|
||||||
|
return r.json();
|
||||||
|
}
|
||||||
|
throw Error(r.statusText);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/css/_variables.scss";
|
||||||
|
|
||||||
|
.run {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
|
||||||
|
.run-content {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border: 1px solid $grey-lighter;
|
||||||
|
border-left: 0 solid;
|
||||||
|
display: block;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-title {
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 5px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
|
||||||
|
.run-name {
|
||||||
|
padding-left: 5px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
border-left: 0px solid $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
border-left: 5px solid $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running {
|
||||||
|
border-left: 5px solid $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unknown {
|
||||||
|
border-left: 5px solid $grey-lighter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-actions {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-content {
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
border-left: 5px solid $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
border-left: 5px solid $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running {
|
||||||
|
border-left: 5px solid $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unknown {
|
||||||
|
border-left: 5px solid $grey-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commitmessage {
|
||||||
|
}
|
||||||
|
|
||||||
|
.stillrunning {
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-info {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
a {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,253 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="item-list">
|
||||||
|
<div class="item" v-for="run in runs" v-bind:key="run.id" :class="runResultClass(run)">
|
||||||
|
<div class="item-content">
|
||||||
|
<router-link
|
||||||
|
v-if="username"
|
||||||
|
tag="div"
|
||||||
|
class="name"
|
||||||
|
:to="userLocalRunLink(username, run.id)"
|
||||||
|
>
|
||||||
|
<span>{{run.name}}</span>
|
||||||
|
</router-link>
|
||||||
|
<router-link
|
||||||
|
v-else
|
||||||
|
tag="div"
|
||||||
|
class="name"
|
||||||
|
:to="projectRunLink(ownertype, ownername, projectname, run.id)"
|
||||||
|
>
|
||||||
|
<span>{{run.name}}</span>
|
||||||
|
</router-link>
|
||||||
|
<div class="commitmessage">{{run.annotations.message}}</div>
|
||||||
|
<span v-if="stillRunning(run)" class="stillrunning tag">Still running</span>
|
||||||
|
<span v-if="!stillRunning(run)" class="stillrunning"></span>
|
||||||
|
<div class="source-info">
|
||||||
|
<a :href="run.annotations.commit_link" class="commit" target="_blank">
|
||||||
|
<i class="mdi mdi-source-commit mdi-rotate-90"></i>
|
||||||
|
<span>{{run.annotations.commit_sha.substring(0,8)}}</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
v-if="run.annotations.event_type == 'push'"
|
||||||
|
:href="run.annotations.branch_link"
|
||||||
|
class="commit"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-source-branch"></i>
|
||||||
|
<span>{{run.annotations.branch}}</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
v-else-if="run.annotations.event_type == 'tag'"
|
||||||
|
:href="run.annotations.tag_link"
|
||||||
|
class="commit"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-tag"></i>
|
||||||
|
<span>{{run.annotations.tag}}</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
v-else-if="run.annotations.event_type == 'pull_request'"
|
||||||
|
:href="run.annotations.pull_request_link"
|
||||||
|
class="commit"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-source-pull"></i>
|
||||||
|
<span>PR #{{run.annotations.pull_request_id}}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { apiurl, fetch } from "@/util/auth";
|
||||||
|
import { userLocalRunLink, projectRunLink } from "@/util/link.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
name: "runs",
|
||||||
|
props: {
|
||||||
|
ownertype: String,
|
||||||
|
ownername: String,
|
||||||
|
username: String,
|
||||||
|
projectname: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
runs: [],
|
||||||
|
polling: null,
|
||||||
|
project: null,
|
||||||
|
user: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
projectRunLink: projectRunLink,
|
||||||
|
userLocalRunLink: userLocalRunLink,
|
||||||
|
stillRunning(run) {
|
||||||
|
return run.result != "unknown" && run.phase == "running";
|
||||||
|
},
|
||||||
|
runResultClass(run) {
|
||||||
|
if (run.result == "unknown") {
|
||||||
|
if (run.phase == "queued") return "unknown";
|
||||||
|
if (run.phase == "cancelled") return "failed";
|
||||||
|
if (run.phase == "running") return "running";
|
||||||
|
}
|
||||||
|
if (run.result == "success") return "success";
|
||||||
|
if (run.result == "failed") return "failed";
|
||||||
|
if (run.result == "stopped") return "failed";
|
||||||
|
return "unknown";
|
||||||
|
},
|
||||||
|
fetchProjectRuns() {
|
||||||
|
fetch(
|
||||||
|
apiurl(
|
||||||
|
"/projects/" +
|
||||||
|
this.ownertype +
|
||||||
|
"/" +
|
||||||
|
this.ownername +
|
||||||
|
"/" +
|
||||||
|
this.projectname
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.project = res;
|
||||||
|
console.log("project", this.project);
|
||||||
|
|
||||||
|
this.fetchRuns();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fetchUserRuns() {
|
||||||
|
fetch(apiurl("/users/" + this.username))
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
console.log(res);
|
||||||
|
this.user = res;
|
||||||
|
console.log("user", this.user);
|
||||||
|
|
||||||
|
this.fetchRuns();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fetchRuns() {
|
||||||
|
let u = apiurl("/runs");
|
||||||
|
//console.log("this.project.id", this.project.id);
|
||||||
|
console.log("u", u);
|
||||||
|
if (this.project !== null) {
|
||||||
|
u.searchParams.append("group", this.project.id);
|
||||||
|
} else if (this.user !== null) {
|
||||||
|
u.searchParams.append("group", this.user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(u)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
console.log(res);
|
||||||
|
let runs = res.runs.map(function(run) {
|
||||||
|
return run;
|
||||||
|
});
|
||||||
|
this.runs = runs;
|
||||||
|
console.log("runs", this.runs);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
pollData() {
|
||||||
|
this.polling = setInterval(() => {
|
||||||
|
this.fetchRuns();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function() {
|
||||||
|
console.log("username", this.username);
|
||||||
|
console.log("projectname", this.projectname);
|
||||||
|
if (this.projectname !== undefined) {
|
||||||
|
this.fetchProjectRuns();
|
||||||
|
} else if (this.username !== undefined) {
|
||||||
|
this.fetchUserRuns();
|
||||||
|
} else {
|
||||||
|
this.fetchRuns();
|
||||||
|
}
|
||||||
|
this.pollData();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.polling);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/css/_variables.scss";
|
||||||
|
|
||||||
|
.project-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 5px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
.project-name {
|
||||||
|
padding-left: 5px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-list {
|
||||||
|
.item {
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-content {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border: 1px solid $grey-lighter;
|
||||||
|
border-left: 0 solid;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
border-left: 5px solid $green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
border-left: 5px solid $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.running {
|
||||||
|
border-left: 5px solid $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unknown {
|
||||||
|
border-left: 5px solid $grey-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
flex: 0 0 30%;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.commitmessage {
|
||||||
|
flex: 0 0 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stillrunning {
|
||||||
|
flex: 0 0 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.source-info {
|
||||||
|
flex: 0 0 10%;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
a {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.commit {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<div class="arrow">
|
||||||
|
<svg viewBox="0 0 15 15">
|
||||||
|
<path fill="none" stroke="#9d9d9d" d="M4.32.5l6.247 6.942L4.32 14.5"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "tabarrow"
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.arrow {
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.4em;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
top: 0.1rem;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,88 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<RunDetail :run="run"/>
|
||||||
|
<div v-if="task != null">
|
||||||
|
<div class="task-title">
|
||||||
|
<span class="task-name" v-html="task.name"/>
|
||||||
|
<span class="tag" :class="taskClass(task)">{{ task.status | capitalize }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-for="(step, index) in task.steps" v-bind:key="index">
|
||||||
|
<Collapse
|
||||||
|
v-bind:runid="runid"
|
||||||
|
v-bind:taskid="taskid"
|
||||||
|
v-bind:stepnum="index"
|
||||||
|
v-bind:step="step"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { apiurl, fetch } from "@/util/auth";
|
||||||
|
import { fetchRun, fetchTask } from "@/util/data.js";
|
||||||
|
|
||||||
|
import Collapse from "@/components/collapse.vue";
|
||||||
|
import RunDetail from "@/components/rundetail.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Collapse,
|
||||||
|
RunDetail
|
||||||
|
},
|
||||||
|
name: "task",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
run: null,
|
||||||
|
task: null,
|
||||||
|
runid: this.$route.params.runid,
|
||||||
|
taskid: this.$route.params.taskid,
|
||||||
|
polling: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
taskClass(task) {
|
||||||
|
if (task.status == "success") return "is-success";
|
||||||
|
if (task.status == "failed") return "is-failed";
|
||||||
|
if (task.status == "stopped") return "is-failed";
|
||||||
|
if (task.status == "running") return "is-running";
|
||||||
|
return "unknown";
|
||||||
|
},
|
||||||
|
async fetchRun() {
|
||||||
|
this.run = await fetchRun(this.runid);
|
||||||
|
},
|
||||||
|
async fetchTask() {
|
||||||
|
this.task = await fetchTask(this.runid, this.taskid);
|
||||||
|
},
|
||||||
|
pollData() {
|
||||||
|
this.polling = setInterval(() => {
|
||||||
|
this.fetchTask();
|
||||||
|
this.fetchRun();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function() {
|
||||||
|
this.fetchRun();
|
||||||
|
this.fetchTask();
|
||||||
|
this.pollData();
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.polling);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.task-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 5px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
|
||||||
|
.task-name {
|
||||||
|
padding-left: 5px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,143 @@
|
||||||
|
// **
|
||||||
|
// black
|
||||||
|
// **
|
||||||
|
|
||||||
|
.ansi-black-fg {
|
||||||
|
color: $ansi-black;
|
||||||
|
}
|
||||||
|
.ansi-black-bg {
|
||||||
|
background-color: $ansi-black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ansi-bright-black-fg {
|
||||||
|
color: $ansi-black-bright;
|
||||||
|
}
|
||||||
|
.ansi-bright-black-bg {
|
||||||
|
background-color: $ansi-black-bright;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
// red
|
||||||
|
// **
|
||||||
|
|
||||||
|
.ansi-red-fg {
|
||||||
|
color: $ansi-red;
|
||||||
|
}
|
||||||
|
.ansi-red-bg {
|
||||||
|
background-color: $ansi-red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ansi-bright-red-fg {
|
||||||
|
color: $ansi-red-bright;
|
||||||
|
}
|
||||||
|
.ansi-bright-red-bg {
|
||||||
|
background-color: $ansi-red-bright;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
// green
|
||||||
|
// **
|
||||||
|
|
||||||
|
.ansi-green-fg {
|
||||||
|
color: $ansi-green;
|
||||||
|
}
|
||||||
|
.ansi-green-bg {
|
||||||
|
background-color: $ansi-green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ansi-bright-green-fg {
|
||||||
|
color: $ansi-green-bright;
|
||||||
|
}
|
||||||
|
.ansi-bright-green-bg {
|
||||||
|
background-color: $ansi-green-bright;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
// yellow
|
||||||
|
// **
|
||||||
|
|
||||||
|
.ansi-yellow-fg {
|
||||||
|
color: $ansi-yellow;
|
||||||
|
}
|
||||||
|
.ansi-yellow-bg {
|
||||||
|
background-color: $ansi-yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ansi-bright-yellow-fg {
|
||||||
|
color: $ansi-yellow-bright;
|
||||||
|
}
|
||||||
|
.ansi-bright-yellow-bg {
|
||||||
|
background-color: $ansi-yellow-bright;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
// blue
|
||||||
|
// **
|
||||||
|
|
||||||
|
.ansi-blue-fg {
|
||||||
|
color: $ansi-blue;
|
||||||
|
}
|
||||||
|
.ansi-blue-bg {
|
||||||
|
background-color: $ansi-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ansi-bright-blue-fg {
|
||||||
|
color: $ansi-blue-bright;
|
||||||
|
}
|
||||||
|
.ansi-bright-blue-bg {
|
||||||
|
background-color: $ansi-blue-bright;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
// magenta
|
||||||
|
// **
|
||||||
|
|
||||||
|
.ansi-magenta-fg {
|
||||||
|
color: $ansi-magenta;
|
||||||
|
}
|
||||||
|
.ansi-magenta-bg {
|
||||||
|
background-color: $ansi-magenta;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ansi-bright-magenta-fg {
|
||||||
|
color: $ansi-magenta-bright;
|
||||||
|
}
|
||||||
|
.ansi-bright-magenta-bg {
|
||||||
|
background-color: $ansi-magenta-bright;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
// cyan
|
||||||
|
// **
|
||||||
|
|
||||||
|
.ansi-cyan-fg {
|
||||||
|
color: $ansi-cyan;
|
||||||
|
}
|
||||||
|
.ansi-cyan-bg {
|
||||||
|
background-color: $ansi-cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ansi-bright-cyan-fg {
|
||||||
|
color: $ansi-cyan-bright;
|
||||||
|
}
|
||||||
|
.ansi-bright-cyan-bg {
|
||||||
|
background-color: $ansi-cyan-bright;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **
|
||||||
|
// white
|
||||||
|
// **
|
||||||
|
|
||||||
|
.ansi-white-fg {
|
||||||
|
color: $ansi-white;
|
||||||
|
}
|
||||||
|
.ansi-white-bg {
|
||||||
|
background-color: $ansi-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ansi-bright-white-fg {
|
||||||
|
color: $ansi-white-bright;
|
||||||
|
}
|
||||||
|
.ansi-bright-white-bg {
|
||||||
|
background-color: $ansi-white-bright;
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
@import "~bulma/sass/utilities/initial-variables.sass";
|
||||||
|
@import "~bulma/sass/utilities/functions.sass";
|
||||||
|
|
||||||
|
$grey: #8c9b9d;
|
||||||
|
$grey-light: #a9afb7;
|
||||||
|
$grey-lighter: #dee2e5;
|
||||||
|
$orange: #e67e22;
|
||||||
|
$yellow: #f1b70e;
|
||||||
|
$green: #2ecc71;
|
||||||
|
$turquoise: #1abc9c;
|
||||||
|
$blue: #3498db;
|
||||||
|
$purple: #8e44ad;
|
||||||
|
$red: #e42522;
|
||||||
|
$white-ter: #ecf0f1;
|
||||||
|
$primary: #34495e !default;
|
||||||
|
$yellow-invert: #fff;
|
||||||
|
|
||||||
|
$grey-lighter-invert: findColorInvert($grey-lighter);
|
||||||
|
$green-invert: findColorInvert($green);
|
||||||
|
$red-invert: findColorInvert($red);
|
||||||
|
$blue-invert: findColorInvert($red);
|
||||||
|
|
||||||
|
$custom-colors: (
|
||||||
|
"unknown": (
|
||||||
|
$grey-lighter,
|
||||||
|
$grey-lighter-invert
|
||||||
|
),
|
||||||
|
"success": (
|
||||||
|
$green,
|
||||||
|
$green-invert
|
||||||
|
),
|
||||||
|
"failed": (
|
||||||
|
$red,
|
||||||
|
$red-invert
|
||||||
|
),
|
||||||
|
"running": (
|
||||||
|
$blue,
|
||||||
|
$blue-invert
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$link: $grey;
|
||||||
|
$tabs-link-active-color: $blue;
|
||||||
|
$tabs-link-active-border-bottom-color: $blue;
|
||||||
|
$tabs-link-hover-color: $blue;
|
||||||
|
$tabs-link-hover-border-bottom-color: $blue;
|
||||||
|
|
||||||
|
$spacing: 20px;
|
||||||
|
|
||||||
|
$breadcrumb-item-color: $grey-dark !default;
|
||||||
|
$breadcrumb-item-hover-color: $grey-dark !default;
|
||||||
|
|
||||||
|
$breadcrumb-item-padding-vertical: 0 !default;
|
||||||
|
$breadcrumb-item-padding-horizontal: 0.5em !default;
|
||||||
|
|
||||||
|
$breadcrumb-item-separator-color: $grey-light !default;
|
||||||
|
|
||||||
|
$ansi-black: #222;
|
||||||
|
$ansi-black-bright: #222;
|
||||||
|
$ansi-red: #c0392b;
|
||||||
|
$ansi-red-bright: #e74c3c;
|
||||||
|
$ansi-green: #27af60;
|
||||||
|
$ansi-green-bright: #2ecc71;
|
||||||
|
$ansi-yellow: #f39c12;
|
||||||
|
$ansi-yellow-bright: #f1c40f;
|
||||||
|
$ansi-blue: #2e8dcd;
|
||||||
|
$ansi-blue-bright: #3498db;
|
||||||
|
$ansi-magenta: #8e44ad;
|
||||||
|
$ansi-magenta-bright: #9b59b6;
|
||||||
|
$ansi-cyan: #0097a4;
|
||||||
|
$ansi-cyan-bright: #02c8d9;
|
||||||
|
$ansi-white: #bdc3c7;
|
||||||
|
$ansi-white-bright: #ffffff;
|
|
@ -0,0 +1,5 @@
|
||||||
|
@import "@/css/_variables.scss";
|
||||||
|
|
||||||
|
@import "~bulma/bulma.sass";
|
||||||
|
|
||||||
|
@import "./css/_ansi.scss";
|
|
@ -0,0 +1,26 @@
|
||||||
|
import "@mdi/font/css/materialdesignicons.css";
|
||||||
|
|
||||||
|
import Vue from "vue";
|
||||||
|
import Vue2Filters from "vue2-filters";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "./router";
|
||||||
|
import store from "./store";
|
||||||
|
|
||||||
|
import { getUser } from "@/util/auth";
|
||||||
|
|
||||||
|
Vue.use(Vue2Filters);
|
||||||
|
|
||||||
|
const USER = 'user';
|
||||||
|
|
||||||
|
// TODO(sgotti) use vuex for login/logout
|
||||||
|
new Vue({
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
created: function () {
|
||||||
|
let user = getUser()
|
||||||
|
if (user) {
|
||||||
|
store.dispatch('setUser', user)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: h => h(App)
|
||||||
|
}).$mount("#app");
|
|
@ -0,0 +1,149 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import VueRouter from "vue-router";
|
||||||
|
import Home from "./views/Home.vue";
|
||||||
|
import User from "./views/User.vue";
|
||||||
|
import Org from "./views/Org.vue";
|
||||||
|
import Project from "./views/Project.vue";
|
||||||
|
//import Run from "./views/Run.vue";
|
||||||
|
import projects from "./components/projects.vue";
|
||||||
|
import runs from "./components/runs.vue";
|
||||||
|
import run from "./components/run.vue";
|
||||||
|
import task from "./components/task.vue";
|
||||||
|
import Oauth2 from "./views/Oauth2.vue";
|
||||||
|
import Login from "./views/Login.vue";
|
||||||
|
import Logout from "./views/Logout.vue";
|
||||||
|
|
||||||
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
export default new VueRouter({
|
||||||
|
mode: "history",
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "login",
|
||||||
|
component: Login
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/logout",
|
||||||
|
name: "logout",
|
||||||
|
component: Logout
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/oauth2/callback",
|
||||||
|
name: "oauth2callback",
|
||||||
|
component: Oauth2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
name: "home",
|
||||||
|
component: Home
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/user/:username",
|
||||||
|
component: User,
|
||||||
|
props: (route) => ({ username: route.params.username }),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "user",
|
||||||
|
component: projects,
|
||||||
|
props: (route) => ({ ownertype: "user", ownername: route.params.username })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "projects",
|
||||||
|
name: "user projects",
|
||||||
|
component: projects,
|
||||||
|
props: (route) => ({ ownertype: "user", ownername: route.params.username })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "runs",
|
||||||
|
name: "user local runs",
|
||||||
|
component: runs,
|
||||||
|
props: (route) => ({ ownertype: "user", username: route.params.username })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "runs/:runid",
|
||||||
|
name: "user local run",
|
||||||
|
component: run,
|
||||||
|
props: (route) => ({ ownertype: "user", ownername: route.params.username, runid: route.params.runid })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "runs/:runid/tasks/:taskid",
|
||||||
|
name: "user local run task",
|
||||||
|
component: task,
|
||||||
|
props: (route) => ({ ownertype: "user", ownername: route.params.username, runid: route.params.runid, taskid: route.params.taskid })
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/user/:username/projects/:projectname",
|
||||||
|
component: Project,
|
||||||
|
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectname: route.params.projectname }),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "user project",
|
||||||
|
component: runs,
|
||||||
|
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectname: route.params.projectname })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "runs",
|
||||||
|
name: "user project runs",
|
||||||
|
component: runs,
|
||||||
|
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectname: route.params.projectname })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "runs/:runid",
|
||||||
|
name: "user project run",
|
||||||
|
component: run,
|
||||||
|
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectname: route.params.projectname, runid: route.params.runid })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "runs/:runid/tasks/:taskid",
|
||||||
|
name: "user project run task",
|
||||||
|
component: task,
|
||||||
|
props: (route) => ({ ownertype: "user", ownername: route.params.username, projectname: route.params.projectname, runid: route.params.runid, taskid: route.params.taskid })
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/org/:orgname",
|
||||||
|
name: "org",
|
||||||
|
component: Org,
|
||||||
|
props: (route) => ({ orgname: route.params.orgname }),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: "/org/:orgname/projects/:projectname",
|
||||||
|
component: Project,
|
||||||
|
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectname: route.params.projectname }),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
name: "org project",
|
||||||
|
component: runs,
|
||||||
|
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectname: route.params.projectname })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "runs",
|
||||||
|
name: "org project runs",
|
||||||
|
component: runs,
|
||||||
|
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectname: route.params.projectname })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "runs/:runid",
|
||||||
|
name: "org project run",
|
||||||
|
component: run,
|
||||||
|
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectname: route.params.projectname, runid: route.params.runid })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "runs/:runid/tasks/:taskid",
|
||||||
|
name: "org project run task",
|
||||||
|
component: task,
|
||||||
|
props: (route) => ({ ownertype: "org", ownername: route.params.orgname, projectname: route.params.projectname, runid: route.params.runid, taskid: route.params.taskid })
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
|
Vue.use(Vuex)
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
user: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
user: state => {
|
||||||
|
return state.user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
setUser(state, user) {
|
||||||
|
state.user = user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
setUser({ commit }, user) {
|
||||||
|
commit('setUser', user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default new Vuex.Store({
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations,
|
||||||
|
})
|
|
@ -0,0 +1,93 @@
|
||||||
|
import router from "@/router";
|
||||||
|
import store from "@/store";
|
||||||
|
|
||||||
|
const ID_TOKEN_KEY = 'id_token';
|
||||||
|
const USER_KEY = 'user';
|
||||||
|
|
||||||
|
let API_URL = window.CONFIG.API_URL;
|
||||||
|
let API_BASE_PATH = window.CONFIG.API_BASE_PATH;
|
||||||
|
|
||||||
|
export function login(token, user) {
|
||||||
|
setIdToken(token);
|
||||||
|
setUser(user);
|
||||||
|
store.dispatch('setUser', user)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout() {
|
||||||
|
unsetIdToken();
|
||||||
|
unsetUser()
|
||||||
|
store.dispatch('setUser', null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function apiurlwithtoken(path) {
|
||||||
|
let u = new URL(API_URL + API_BASE_PATH + path);
|
||||||
|
let idToken = getIdToken();
|
||||||
|
if (idToken) {
|
||||||
|
u.searchParams.append("access_token", idToken);
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
export function apiurl(path) {
|
||||||
|
return new URL(API_URL + API_BASE_PATH + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loginurl() {
|
||||||
|
return new URL(API_URL + "/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function oauth2callbackurl() {
|
||||||
|
return new URL(API_URL + "/oauth2/callback");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetch(url, init) {
|
||||||
|
if (init === undefined) {
|
||||||
|
init = {}
|
||||||
|
}
|
||||||
|
if (init.headers === undefined) {
|
||||||
|
init["headers"] = {}
|
||||||
|
}
|
||||||
|
let idToken = getIdToken();
|
||||||
|
if (idToken) {
|
||||||
|
init.headers["Authorization"] = "bearer " + idToken
|
||||||
|
}
|
||||||
|
|
||||||
|
return window.fetch(url, init).then(res => {
|
||||||
|
if (res.status === 401) {
|
||||||
|
router.push({ name: "login" })
|
||||||
|
} else { return res }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setIdToken(idToken) {
|
||||||
|
localStorage.setItem(ID_TOKEN_KEY, idToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIdToken() {
|
||||||
|
return localStorage.getItem(ID_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unsetIdToken() {
|
||||||
|
localStorage.removeItem(ID_TOKEN_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setUser(user) {
|
||||||
|
localStorage.setItem(USER_KEY, JSON.stringify(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUser() {
|
||||||
|
let user = localStorage.getItem(USER_KEY);
|
||||||
|
if (user) {
|
||||||
|
return JSON.parse(user)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unsetUser() {
|
||||||
|
localStorage.removeItem(USER_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLoggedIn() {
|
||||||
|
const idToken = getIdToken();
|
||||||
|
return !!idToken;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { apiurl, fetch } from "@/util/auth";
|
||||||
|
|
||||||
|
export async function fetchRun(runid) {
|
||||||
|
let res = await fetch(apiurl("/run/" + runid));
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchTask(runid, taskid) {
|
||||||
|
let res = await fetch(apiurl("/run/" + runid + "/task/" + taskid))
|
||||||
|
return res.json();
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
|
||||||
|
export function ownerLink(ownertype, ownername) {
|
||||||
|
if (ownertype == "user") {
|
||||||
|
return { name: ownertype, params: { username: ownername } }
|
||||||
|
} else if (ownertype == "org") {
|
||||||
|
return { name: ownertype, params: { orgname: ownername } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ownerProjectsLink(ownertype, ownername) {
|
||||||
|
return { name: ownertype + " projects", params: { ownername: ownername } }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function userLocalRunsLink(username) {
|
||||||
|
return { name: "user local runs", params: { username: username } }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function userLocalRunLink(username, runid) {
|
||||||
|
return { name: "user local run", params: { username: username, runid: runid } }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function userLocalRunTaskLink(username, runid, taskid) {
|
||||||
|
return { name: "user local run task", params: { username: username, runid: runid, taskid: taskid } }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function projectLink(ownertype, ownername, projectname) {
|
||||||
|
return { name: ownertype + " project", params: { username: ownername, projectname: projectname } }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function projectRunsLink(ownertype, ownername, projectname) {
|
||||||
|
return { name: ownertype + " project runs", params: { orgname: ownername, projectname: projectname } }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function projectRunLink(ownertype, ownername, projectname, runid) {
|
||||||
|
return { name: ownertype + " project run", params: { orgname: ownername, projectname: projectname, runid: runid } }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function projectRunTaskLink(ownertype, ownername, projectname, runid, taskid) {
|
||||||
|
return { name: ownertype + " project run task", params: { orgname: ownername, projectname: projectname, runid: runid, taskid: taskid } }
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<div class="home"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Home",
|
||||||
|
components: {},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(["user"])
|
||||||
|
},
|
||||||
|
created: function() {
|
||||||
|
let user = this.$store.getters.user;
|
||||||
|
if (user) {
|
||||||
|
this.$router.push({
|
||||||
|
name: "user",
|
||||||
|
params: { username: this.user.username }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="column is-4 is-offset-4">
|
||||||
|
<div class="box" v-for="rs in remotesources" v-bind:key="rs.id">
|
||||||
|
<Loginform
|
||||||
|
:name="rs.name"
|
||||||
|
v-if="rs.auth_type == 'password'"
|
||||||
|
v-on:login="doLogin(rs.name, $event.username, $event.password)"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
class="button is-info is-fullwidth"
|
||||||
|
@click="doLogin(rs.name)"
|
||||||
|
>Login with {{rs.name}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Loginform from "@/components/loginform";
|
||||||
|
import { apiurl, loginurl, fetch, login, logout } from "@/util/auth";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Login",
|
||||||
|
components: {
|
||||||
|
Loginform
|
||||||
|
},
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
remotesources: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getRemoteSources() {
|
||||||
|
fetch(apiurl("/remotesources"))
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
console.log("remote sources result", res);
|
||||||
|
this.remotesources = res.remote_sources;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
doLogin(rsName, username, password) {
|
||||||
|
let u = loginurl();
|
||||||
|
console.log("u:", u);
|
||||||
|
fetch(u, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
remote_source_name: rsName,
|
||||||
|
login_name: username,
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
console.log("login result", res);
|
||||||
|
if (res.oauth2_redirect) {
|
||||||
|
window.location = res.oauth2_redirect;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
login(res.token, res.user);
|
||||||
|
this.$router.push({ name: "home" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function() {
|
||||||
|
logout();
|
||||||
|
this.getRemoteSources();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
<template></template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { logout } from "@/util/auth";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Logout",
|
||||||
|
created: function() {
|
||||||
|
logout();
|
||||||
|
this.$router.push("/");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div>{{code}}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { apiurl, oauth2callbackurl, fetch, setUser } from "@/util/auth";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
name: "Oauth2",
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
run: null,
|
||||||
|
code: this.$route.query.code,
|
||||||
|
polling: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
doOauth2() {
|
||||||
|
let u = oauth2callbackurl();
|
||||||
|
u.searchParams.append("code", this.$route.query.code);
|
||||||
|
u.searchParams.append("state", this.$route.query.state);
|
||||||
|
fetch(u)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(res => {
|
||||||
|
console.log("oauth2 result", res);
|
||||||
|
if (res.request_type === "loginuser") {
|
||||||
|
this.$root.login(res.response.token, res.response.user);
|
||||||
|
this.$router.push("/");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function() {
|
||||||
|
this.doOauth2();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="org-title">
|
||||||
|
<span class="org-name">{{orgname}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tabs">
|
||||||
|
<ul>
|
||||||
|
<li :class="[{ 'is-active': currentTab === 'projects' }]">
|
||||||
|
<a @click="currentTab = 'projects'">Projects</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<projects v-if="currentTab == 'projects'" ownertype="org" :ownername="orgname"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import projects from "@/components/projects.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Org",
|
||||||
|
components: { projects },
|
||||||
|
props: {
|
||||||
|
orgname: String,
|
||||||
|
currentTab: {
|
||||||
|
type: String,
|
||||||
|
default: "projects"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/css/_variables.scss";
|
||||||
|
|
||||||
|
.org-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 5px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
.org-name {
|
||||||
|
padding-left: 5px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,90 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<projbreadcrumbs :ownertype="ownertype" :ownername="ownername" :projectname="projectname"/>
|
||||||
|
<div class="tabs">
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
:class="[{ 'is-active': $route.name.endsWith('project runs') || $route.name.endsWith('project') }]"
|
||||||
|
>
|
||||||
|
<router-link :to="projectRunsLink(ownertype, ownername, projectname)">Runs</router-link>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="$route.name.endsWith('project run') || $route.name.endsWith('project run task')"
|
||||||
|
:class="[{ 'is-active': $route.name.endsWith('project run') }]"
|
||||||
|
>
|
||||||
|
<tabarrow/>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="$route.name.endsWith('project run') || $route.name.endsWith('project run task')"
|
||||||
|
:class="[{ 'is-active': $route.name.endsWith('project run') }]"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
:to="projectRunLink(ownertype, ownername, $route.params.projectname, $route.params.runid)"
|
||||||
|
>Run {{$route.params.runid}}</router-link>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="$route.name.endsWith('project run task')"
|
||||||
|
:class="[{ 'is-active': $route.name.endsWith('project run') }]"
|
||||||
|
>
|
||||||
|
<tabarrow/>
|
||||||
|
</li>
|
||||||
|
<li v-if="$route.name.endsWith('project run task')" class="is-active">
|
||||||
|
<router-link
|
||||||
|
:to="projectRunTaskLink(ownertype, ownername, $route.params.projectname, $route.params.runid, $route.params.taskid)"
|
||||||
|
>Task {{$route.params.taskid}}</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
projectLink,
|
||||||
|
projectRunsLink,
|
||||||
|
projectRunLink,
|
||||||
|
projectRunTaskLink
|
||||||
|
} from "@/util/link.js";
|
||||||
|
|
||||||
|
import projbreadcrumbs from "@/components/projbreadcrumbs.vue";
|
||||||
|
import runs from "@/components/runs.vue";
|
||||||
|
import tabarrow from "@/components/tabarrow.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Project",
|
||||||
|
components: { projbreadcrumbs, runs, tabarrow },
|
||||||
|
props: {
|
||||||
|
ownertype: String,
|
||||||
|
ownername: String,
|
||||||
|
projectname: String,
|
||||||
|
currentTab: {
|
||||||
|
type: String,
|
||||||
|
default: "description"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
projectLink: projectLink,
|
||||||
|
projectRunsLink: projectRunsLink,
|
||||||
|
projectRunLink: projectRunLink,
|
||||||
|
projectRunTaskLink: projectRunTaskLink
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/css/_variables.scss";
|
||||||
|
|
||||||
|
.user-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 5px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
.user-name {
|
||||||
|
padding-left: 5px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,120 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="user-title">
|
||||||
|
<router-link class="user-name" :to="ownerLink('user', username)">
|
||||||
|
<span>{{username}}</span>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
<div class="tabs">
|
||||||
|
<ul>
|
||||||
|
<li :class="[{ 'is-active': $route.name === 'user projects' || $route.name === 'user' }]">
|
||||||
|
<router-link :to="ownerProjectsLink('user', username)">Projects</router-link>
|
||||||
|
</li>
|
||||||
|
<li :class="[{ 'is-active': $route.name === 'user local runs' }]">
|
||||||
|
<router-link :to="userLocalRunsLink(username)">Local Runs</router-link>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="$route.name === 'user local run' || $route.name == 'user local run task'"
|
||||||
|
:class="[{ 'is-active': $route.name === 'user local run' }]"
|
||||||
|
>
|
||||||
|
<tabarrow/>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="$route.name === 'user local run' || $route.name == 'user local run task'"
|
||||||
|
:class="[{ 'is-active': $route.name === 'user local run' }]"
|
||||||
|
>
|
||||||
|
<router-link :to="userLocalRunLink(username, $route.params.runid)">
|
||||||
|
<p v-if="run">
|
||||||
|
Run
|
||||||
|
<strong>#{{run.counter}}</strong>
|
||||||
|
</p>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="$route.name === 'user local run task'"
|
||||||
|
:class="[{ 'is-active': $route.name === 'user local run' }]"
|
||||||
|
>
|
||||||
|
<tabarrow/>
|
||||||
|
</li>
|
||||||
|
<li v-if="$route.name == 'user local run task'" class="is-active">
|
||||||
|
<router-link
|
||||||
|
:to="userLocalRunTaskLink(username, $route.params.runid, $route.params.taskid)"
|
||||||
|
>
|
||||||
|
<p v-if="run">
|
||||||
|
Task
|
||||||
|
<strong>{{run.tasks[$route.params.taskid].name}}</strong>
|
||||||
|
</p>
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<router-view></router-view>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { apiurl, fetch } from "@/util/auth";
|
||||||
|
import {
|
||||||
|
ownerLink,
|
||||||
|
ownerProjectsLink,
|
||||||
|
userLocalRunsLink,
|
||||||
|
userLocalRunLink,
|
||||||
|
userLocalRunTaskLink
|
||||||
|
} from "@/util/link.js";
|
||||||
|
|
||||||
|
import { fetchRun } from "@/util/data.js";
|
||||||
|
|
||||||
|
import tabarrow from "@/components/tabarrow.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "User",
|
||||||
|
components: { tabarrow },
|
||||||
|
props: {
|
||||||
|
username: String,
|
||||||
|
currentTab: {
|
||||||
|
type: String,
|
||||||
|
default: "projects"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
run: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
if (!to.params.runid) next();
|
||||||
|
let run = await fetchRun(to.params.runid);
|
||||||
|
next(vm => (vm.run = run));
|
||||||
|
},
|
||||||
|
async beforeRouteUpdate(to, from, next) {
|
||||||
|
if (!to.params.runid) next();
|
||||||
|
this.run = await fetchRun(to.params.runid);
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
ownerLink: ownerLink,
|
||||||
|
ownerProjectsLink: ownerProjectsLink,
|
||||||
|
userLocalRunsLink: userLocalRunsLink,
|
||||||
|
userLocalRunLink: userLocalRunLink,
|
||||||
|
userLocalRunTaskLink: userLocalRunTaskLink
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/css/_variables.scss";
|
||||||
|
|
||||||
|
.user-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 5px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
.user-name {
|
||||||
|
color: $grey-dark;
|
||||||
|
padding-left: 5px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,7 @@
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
css: {
|
||||||
|
sourceMap: true
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue