*: handle api errors

* Use a global error when fetching data
* Use a local error message when calling a mutating api
This commit is contained in:
Simone Gotti 2019-05-13 16:29:47 +02:00
parent 98073502e2
commit 789d0f5fcd
22 changed files with 522 additions and 173 deletions

View File

@ -43,8 +43,27 @@
</div> </div>
</div> </div>
</nav> </nav>
<div class="main-container container">
<router-view></router-view> <div v-if="error" class="container">
<div class="message is-danger global-error-message">
<div class="message-body">
<nav class="level">
<div class="level-left">
<div class="level-item">
<p>Failed to fetch data: {{ error }}</p>
</div>
</div>
<div class="level-right">
<div class="level-item">
<button class="button is-danger" @click="reload()">Retry</button>
</div>
</div>
</nav>
</div>
</div>
</div>
<div v-else class="main-container container">
<router-view v-if="routerActive"></router-view>
</div> </div>
</div> </div>
</template> </template>
@ -57,7 +76,12 @@ export default {
name: "App", name: "App",
components: {}, components: {},
computed: { computed: {
...mapGetters(["user"]) ...mapGetters(["error", "user"])
},
data() {
return {
routerActive: true
};
}, },
watch: { watch: {
user: function(user) { user: function(user) {
@ -68,6 +92,14 @@ export default {
}); });
} }
} }
},
// method to reload current view from https://github.com/vuejs/vue-router/issues/296#issuecomment-356530037
methods: {
reload() {
this.$store.dispatch("setError", null);
this.routerActive = false;
this.$nextTick(() => (this.routerActive = true));
}
} }
}; };
</script> </script>
@ -78,4 +110,8 @@ export default {
.main-container { .main-container {
margin-top: 2rem; margin-top: 2rem;
} }
.global-error-message {
margin-top: 10rem;
}
</style> </style>

View File

@ -19,9 +19,9 @@
<div class="control"> <div class="control">
<button class="button is-primary" @click="createProject()">Create Project</button> <button class="button is-primary" @click="createProject()">Create Project</button>
</div> </div>
<div class="control">
<button class="button is-text">Cancel</button>
</div> </div>
<div v-if="createProjectError" class="message is-danger">
<div class="message-body">{{ createProjectError }}</div>
</div> </div>
</div> </div>
</template> </template>
@ -47,6 +47,7 @@ export default {
}, },
data() { data() {
return { return {
createProjectError: null,
user: null, user: null,
remoteSources: null, remoteSources: null,
remoteRepos: [], remoteRepos: [],
@ -56,23 +57,32 @@ export default {
}; };
}, },
methods: { methods: {
resetErrors() {
this.createProjectError = null;
},
repoSelected(remoteSource, repoPath) { repoSelected(remoteSource, repoPath) {
this.selectedRemoteSource = remoteSource; this.selectedRemoteSource = remoteSource;
this.remoteRepoPath = repoPath; this.remoteRepoPath = repoPath;
}, },
async createProject() { async createProject() {
this.resetErrors();
let refArray = [this.ownertype, this.ownername]; let refArray = [this.ownertype, this.ownername];
if (this.projectgroupref) { if (this.projectgroupref) {
refArray = [...refArray, ...this.projectgroupref]; refArray = [...refArray, ...this.projectgroupref];
} }
let parentref = refArray.join("/"); let parentref = refArray.join("/");
await createProject( let { error } = await createProject(
parentref, parentref,
this.projectName, this.projectName,
this.selectedRemoteSource.name, this.selectedRemoteSource.name,
this.remoteRepoPath this.remoteRepoPath
); );
if (error) {
this.createProjectError = error;
return;
}
let projectref = [this.projectName]; let projectref = [this.projectName];
if (this.projectgroupref) { if (this.projectgroupref) {
@ -84,9 +94,20 @@ export default {
} }
}, },
created: async function() { created: async function() {
this.user = await fetchCurrentUser(); let { data, error } = await fetchCurrentUser();
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.user = data;
// 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
this.remoteSources = await fetchRemoteSources(); ({ data, error } = await fetchRemoteSources());
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.remoteSources = data;
} }
}; };
</script> </script>

View File

@ -15,9 +15,9 @@
<div class="control"> <div class="control">
<button class="button is-primary" @click="createProjectGroup()">Create Project Group</button> <button class="button is-primary" @click="createProjectGroup()">Create Project Group</button>
</div> </div>
<div class="control">
<button class="button is-text">Cancel</button>
</div> </div>
<div v-if="createProjectGroupError" class="message is-danger">
<div class="message-body">{{ createProjectGroupError }}</div>
</div> </div>
</div> </div>
</template> </template>
@ -27,8 +27,6 @@ import { createProjectGroup } from "@/util/data.js";
import { projectGroupLink } from "@/util/link.js"; import { projectGroupLink } from "@/util/link.js";
import remoterepos from "@/components/remoterepos.vue";
export default { export default {
components: {}, components: {},
name: "createprojectgroup", name: "createprojectgroup",
@ -39,18 +37,31 @@ export default {
}, },
data() { data() {
return { return {
createProjectGroupError: null,
projectGroupName: null projectGroupName: null
}; };
}, },
methods: { methods: {
resetErrors() {
this.createProjectGroupError = null;
},
async createProjectGroup() { async createProjectGroup() {
this.resetErrors();
let refArray = [this.ownertype, this.ownername]; let refArray = [this.ownertype, this.ownername];
if (this.projectgroupref) { if (this.projectgroupref) {
refArray = [...refArray, ...this.projectgroupref]; refArray = [...refArray, ...this.projectgroupref];
} }
let parentref = refArray.join("/"); let parentref = refArray.join("/");
await createProjectGroup(parentref, this.projectGroupName); let { error } = await createProjectGroup(
parentref,
this.projectGroupName
);
if (error) {
this.createProjectGroupError = error;
return;
}
let projectgroupref = [this.projectGroupName]; let projectgroupref = [this.projectGroupName];
if (this.projectgroupref) { if (this.projectgroupref) {

View File

@ -42,6 +42,9 @@
>Delete Project Group</button> >Delete Project Group</button>
</div> </div>
</div> </div>
<div v-if="deleteProjectGroupError" class="message is-danger">
<div class="message-body">{{ deleteProjectGroupError }}</div>
</div>
</div> </div>
</nav> </nav>
</div> </div>
@ -64,6 +67,7 @@ export default {
}, },
data() { data() {
return { return {
deleteProjectGroupError: null,
variables: [], variables: [],
allvariables: [], allvariables: [],
projectGroupNameToDelete: "" projectGroupNameToDelete: ""
@ -83,6 +87,9 @@ export default {
} }
}, },
methods: { methods: {
resetErrors() {
this.deleteProjectGroupError = null;
},
async deleteProjectGroup() { async deleteProjectGroup() {
let projectgroupref = [ let projectgroupref = [
this.ownertype, this.ownertype,
@ -91,7 +98,11 @@ export default {
].join("/"); ].join("/");
if (this.projectGroupNameToDelete == this.projectGroupName) { if (this.projectGroupNameToDelete == this.projectGroupName) {
let res = await deleteProjectGroup(projectgroupref); let { error } = await deleteProjectGroup(projectgroupref);
if (error) {
this.deleteProjectGroupError = error;
return;
}
this.$router.push( this.$router.push(
projectGroupLink( projectGroupLink(
this.ownertype, this.ownertype,
@ -103,16 +114,33 @@ export default {
} }
}, },
created: async function() { created: async function() {
this.variables = await fetchVariables( let projectgroupref = [
this.ownertype,
this.ownername,
...this.projectgroupref
].join("/");
let { data, error } = await fetchVariables(
"projectgroup", "projectgroup",
[this.ownertype, this.ownername, ...this.projectgroupref].join("/"), projectgroupref,
false false
); );
this.allvariables = await fetchVariables( if (error) {
this.$store.dispatch("setError", error);
return;
}
this.variables = data;
({ data, error } = await fetchVariables(
"projectgroup", "projectgroup",
[this.ownertype, this.ownername, ...this.projectgroupref].join("/"), projectgroupref,
true true
); ));
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.allvariables = data;
} }
}; };
</script> </script>

View File

@ -32,7 +32,11 @@
</template> </template>
<script> <script>
import { apiurl, fetch } from "@/util/auth"; import {
fetchProjectGroupProjects,
fetchProjectGroupSubgroups
} from "@/util/data.js";
import { projectLink, projectGroupLink } from "@/util/link.js"; import { projectLink, projectGroupLink } from "@/util/link.js";
export default { export default {
@ -66,24 +70,33 @@ export default {
return ref; return ref;
}, },
async fetchProjects(ownertype, ownername) { async fetchProjects(ownertype, ownername) {
let ref = [ownertype, ownername]; let projectgroupref = [ownertype, ownername];
if (this.projectgroupref) { if (this.projectgroupref) {
ref.push(...this.projectgroupref); projectgroupref.push(...this.projectgroupref);
} }
let path = "/projectgroups/" + encodeURIComponent(ref.join("/"));
path += "/projects"; let { data, error } = await fetchProjectGroupProjects(
let res = await (await fetch(apiurl(path))).json(); projectgroupref.join("/")
this.projects = res; );
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.projects = data;
}, },
async fetchProjectGroups(ownertype, ownername) { async fetchProjectGroups(ownertype, ownername) {
let ref = [ownertype, ownername]; let projectgroupref = [ownertype, ownername];
if (this.projectgroupref) { if (this.projectgroupref) {
ref.push(...this.projectgroupref); projectgroupref.push(...this.projectgroupref);
} }
let path = "/projectgroups/" + encodeURIComponent(ref.join("/")); let { data, error } = await fetchProjectGroupSubgroups(
path += "/subgroups"; projectgroupref.join("/")
let res = await (await fetch(apiurl(path))).json(); );
this.projectgroups = res; if (error) {
this.$store.dispatch("setError", error);
return;
}
this.projectgroups = data;
}, },
projectLink: projectLink, projectLink: projectLink,
projectGroupLink: projectGroupLink projectGroupLink: projectGroupLink

View File

@ -23,6 +23,10 @@
<button class="button is-primary" @click="updateProject()">Update</button> <button class="button is-primary" @click="updateProject()">Update</button>
</div> </div>
</div> </div>
<div v-if="updateProjectError" class="message is-danger">
<div class="message-body">{{ updateProjectError }}</div>
</div>
</div> </div>
</nav> </nav>
<nav class="panel"> <nav class="panel">
@ -64,6 +68,10 @@
@click="deleteProject()" @click="deleteProject()"
:disabled="!deleteButtonEnabled" :disabled="!deleteButtonEnabled"
>Delete Project</button> >Delete Project</button>
<div v-if="deleteProjectError" class="message is-danger">
<div class="message-body">{{ deleteProjectError }}</div>
</div>
</div> </div>
</div> </div>
</nav> </nav>
@ -92,6 +100,8 @@ export default {
}, },
data() { data() {
return { return {
updateProjectError: null,
deleteProjectError: null,
project: null, project: null,
projectIsPrivate: false, projectIsPrivate: false,
variables: [], variables: [],
@ -111,7 +121,13 @@ export default {
} }
}, },
methods: { methods: {
resetErrors() {
this.updateProjectError = null;
this.deleteProjectError = null;
},
async updateProject() { async updateProject() {
this.resetErrors();
let projectref = [ let projectref = [
this.ownertype, this.ownertype,
this.ownername, this.ownername,
@ -122,9 +138,19 @@ export default {
if (this.projectIsPrivate) { if (this.projectIsPrivate) {
visibility = "private"; visibility = "private";
} }
let res = await updateProject(projectref, this.project.name, visibility); let { error } = await updateProject(
projectref,
this.project.name,
visibility
);
if (error) {
this.updateProjectError = error;
return;
}
}, },
async deleteProject() { async deleteProject() {
this.resetErrors();
let projectref = [ let projectref = [
this.ownertype, this.ownertype,
this.ownername, this.ownername,
@ -132,7 +158,11 @@ export default {
].join("/"); ].join("/");
if (this.projectNameToDelete == this.projectName) { if (this.projectNameToDelete == this.projectName) {
let res = await deleteProject(projectref); let { error } = await deleteProject(projectref);
if (error) {
this.deleteProjectError = error;
return;
}
this.$router.push( this.$router.push(
projectGroupLink( projectGroupLink(
this.ownertype, this.ownertype,
@ -148,19 +178,27 @@ export default {
"/" "/"
); );
this.project = await fetchProject(projectref); let { data, error } = await fetchProject(projectref);
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.project = data;
this.projectIsPrivate = this.project.visibility == "private"; this.projectIsPrivate = this.project.visibility == "private";
this.variables = await fetchVariables( ({ data, error } = await fetchVariables("project", projectref, false));
"project", if (error) {
[this.ownertype, this.ownername, ...this.projectref].join("/"), this.$store.dispatch("setError", error);
false return;
); }
this.allvariables = await fetchVariables( this.variables = data;
"project",
[this.ownertype, this.ownername, ...this.projectref].join("/"), ({ data, error } = await fetchVariables("project", projectref, true));
true if (error) {
); this.$store.dispatch("setError", error);
return;
}
this.allvariables = data;
} }
}; };
</script> </script>

View File

@ -43,7 +43,12 @@ export default {
this.$emit("reposelected", this.remoterepos[index].path); this.$emit("reposelected", this.remoterepos[index].path);
}, },
async fetchRemoteRepos(remotesourceid) { async fetchRemoteRepos(remotesourceid) {
this.remoterepos = await userRemoteRepos(remotesourceid); let { data, error } = await userRemoteRepos(remotesourceid);
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.remoterepos = data;
} }
}, },
created: function() { created: function() {

View File

@ -101,7 +101,13 @@ export default {
return "unknown"; return "unknown";
}, },
async fetchRun() { async fetchRun() {
this.run = await fetchRun(this.runid); let { data, error } = await fetchRun(this.runid);
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.run = data;
// sort tasks by level // sort tasks by level
let tasks = this.run.tasks; let tasks = this.run.tasks;
let sortedTasks = Object.keys(this.run.tasks) let sortedTasks = Object.keys(this.run.tasks)

View File

@ -67,8 +67,7 @@
</template> </template>
<script> <script>
import { apiurl, fetch } from "@/util/auth"; import { fetchUser, fetchProject, fetchRuns } from "@/util/data.js";
import { fetchProject, fetchRuns } from "@/util/data.js";
import { userLocalRunLink, projectRunLink } from "@/util/link.js"; import { userLocalRunLink, projectRunLink } from "@/util/link.js";
export default { export default {
@ -127,15 +126,28 @@ export default {
this.pollData(); this.pollData();
}, },
async fetchProject() { async fetchProject() {
this.project = await fetchProject( let projectref = [
[this.ownertype, this.ownername, ...this.projectref].join("/") this.ownertype,
); this.ownername,
...this.projectref
].join("/");
let { data, error } = await fetchProject(projectref);
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.project = data;
this.fetchRuns(); this.fetchRuns();
}, },
async fetchUser() { async fetchUser() {
let res = await (await fetch(apiurl("/users/" + this.username))).json(); let { data, error } = await fetchUser(this.username);
this.user = res; if (error) {
this.$store.dispatch("setError", error);
return;
}
this.user = data;
this.fetchRuns(); this.fetchRuns();
}, },
@ -159,7 +171,12 @@ export default {
group = "/user/" + this.user.id; group = "/user/" + this.user.id;
} }
this.runs = await fetchRuns(group, lastrun); let { data, error } = await fetchRuns(group, lastrun);
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.runs = data;
}, },
pollData() { pollData() {
clearInterval(this.polling); clearInterval(this.polling);

View File

@ -63,10 +63,20 @@ export default {
return "unknown"; return "unknown";
}, },
async fetchRun() { async fetchRun() {
this.run = await fetchRun(this.runid); let { data, error } = await fetchRun(this.runid);
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.run = data;
}, },
async fetchTask() { async fetchTask() {
this.task = await fetchTask(this.runid, this.taskid); let { data, error } = await fetchTask(this.runid, this.taskid);
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.task = data;
}, },
pollData() { pollData() {
this.polling = setInterval(() => { this.polling = setInterval(() => {

View File

@ -10,26 +10,33 @@
<button class="button is-primary" @click="deleteUserToken(token)">Delete</button> <button class="button is-primary" @click="deleteUserToken(token)">Delete</button>
</div> </div>
</div> </div>
<div v-if="deleteUserTokenError" class="message is-danger">
<div class="message-body">{{ deleteUserTokenError }}</div>
</div> </div>
<div v-else class="item-list">No user tokens</div> </div>
<div v-else>No user tokens</div>
<hr> <hr>
<div v-if="token" class="notification is-success"> <div v-if="token" class="notification is-success">
<button class="delete" @click="closeNewTokenNotification()"></button> <button class="delete" @click="closeNewTokenNotification()"></button>
User token created: {{token}} User token created: {{token}}
</div> </div>
<h4 class="title is-4">Create new User Token</h4> <h5 class="title is-5">Create new User Token</h5>
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control"> <div class="control">
<input class="input is-primary" type="text" placeholder="Token name" v-model="newtokenname"> <input class="input is-primary" type="text" placeholder="Token name" v-model="newtokenname">
</div> </div>
<button class="button is-primary" @click="createUserToken()">Create Token</button> <button class="button is-primary" @click="createUserToken()">Create Token</button>
</div> </div>
<div v-if="createUserTokenError" class="message is-danger">
<div class="message-body">{{ createUserTokenError }}</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import { import {
fetchCurrentUser, fetchCurrentUser,
fetchRemoteSources,
createUserToken, createUserToken,
deleteUserToken deleteUserToken
} from "@/util/data.js"; } from "@/util/data.js";
@ -40,33 +47,85 @@ export default {
props: {}, props: {},
data() { data() {
return { return {
createUserTokenError: null,
deleteUserTokenError: null,
user: [], user: [],
remotesources: [],
token: null, token: null,
newtokenname: null newtokenname: null
}; };
}, },
methods: { methods: {
resetErrors() {
this.createUserTokenError = null;
this.deleteUserTokenError = null;
},
async fetchCurrentUser() {
let { data, error } = await fetchCurrentUser();
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.user = data;
},
async fetchRemoteSources() {
let { data, error } = await fetchRemoteSources();
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.remotesources = data;
},
async createUserToken() { async createUserToken() {
let res = await createUserToken(this.user.username, this.newtokenname); this.resetErrors();
this.token = res.token;
let { data, error } = await createUserToken(
this.user.username,
this.newtokenname
);
if (error) {
this.createUserTokenError = error;
return;
}
this.token = data.token;
this.newtokenname = null; this.newtokenname = null;
this.user = await fetchCurrentUser(); this.fetchCurrentUser();
}, },
async deleteUserToken(tokenname) { async deleteUserToken(tokenname) {
await deleteUserToken(this.user.username, tokenname); this.resetErrors();
this.user = await fetchCurrentUser();
let { error } = await deleteUserToken(this.user.username, tokenname);
if (error) {
this.deleteUserTokenError = error;
return;
}
this.fetchCurrentUser();
}, },
closeNewTokenNotification() { closeNewTokenNotification() {
this.token = null; this.token = null;
} }
}, },
created: async function() { created: function() {
this.user = await fetchCurrentUser(); this.fetchCurrentUser();
this.fetchRemoteSources();
} }
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@import "@/css/_variables.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> </style>

View File

@ -21,9 +21,11 @@ import Logout from "./views/Logout.vue";
import { parseRef } from "@/util/link.js"; import { parseRef } from "@/util/link.js";
import store from "./store";
Vue.use(VueRouter); Vue.use(VueRouter);
export default new VueRouter({ const router = new VueRouter({
mode: "history", mode: "history",
routes: [ routes: [
{ {
@ -327,3 +329,10 @@ export default new VueRouter({
}, },
] ]
}); });
router.beforeEach((to, from, next) => {
store.dispatch("setError", null);
next()
})
export default router

View File

@ -4,11 +4,16 @@ import Vuex from 'vuex'
Vue.use(Vuex) Vue.use(Vuex)
const state = { const state = {
error: null,
user: null, user: null,
registeruser: null registeruser: null,
} }
const getters = { const getters = {
// global error
error: state => {
return state.error
},
user: state => { user: state => {
return state.user return state.user
}, },
@ -18,6 +23,9 @@ const getters = {
} }
const mutations = { const mutations = {
setError(state, error) {
state.error = error
},
setUser(state, user) { setUser(state, user) {
state.user = user state.user = user
}, },
@ -27,6 +35,9 @@ const mutations = {
} }
const actions = { const actions = {
setError({ commit }, error) {
commit('setError', error)
},
setUser({ commit }, user) { setUser({ commit }, user) {
commit('setUser', user) commit('setUser', user)
}, },

View File

@ -7,13 +7,13 @@ const USER_KEY = 'user';
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;
export function login(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 logout() { export function doLogout() {
unsetIdToken(); unsetIdToken();
unsetUser() unsetUser()
store.dispatch('setUser', null) store.dispatch('setUser', null)
@ -48,6 +48,19 @@ export function oauth2callbackurl() {
return new URL(API_URL + "/oauth2/callback"); return new URL(API_URL + "/oauth2/callback");
} }
export async function loginapi(init) {
if (init === undefined) {
init = {}
}
try {
let res = await window.fetch(loginurl(), init)
return res
} catch (e) {
throw e
}
}
export async function fetch(url, init) { export async function fetch(url, init) {
if (init === undefined) { if (init === undefined) {
init = {} init = {}
@ -60,10 +73,14 @@ export async function fetch(url, init) {
init.headers["Authorization"] = "bearer " + idToken init.headers["Authorization"] = "bearer " + idToken
} }
try {
let res = await window.fetch(url, init) let res = await window.fetch(url, init)
if (res.status === 401) { if (res.status === 401) {
router.push({ name: "login" }) router.push({ name: "login" })
} else { return res } } else { return res }
} catch (e) {
throw e
}
} }
export function setIdToken(idToken) { export function setIdToken(idToken) {

View File

@ -1,9 +1,49 @@
import { apiurl, fetch } from "@/util/auth"; import { apiurl, loginapi } from "@/util/auth";
import { fetch as authfetch } from "@/util/auth";
export async function fetch(url, init) {
try {
let res = await authfetch(url, init)
if (!res.ok) {
let data = await res.json()
return { data: null, error: data.message }
} else {
if (res.status == 204) {
return { data: null, error: null }
}
return { data: await res.json(), error: null }
}
} catch (e) {
return { data: null, error: "api call failed: " + e }
}
}
export async function login(username, password, remotesourcename) {
let init = {
method: "POST",
body: JSON.stringify({
remote_source_name: remotesourcename,
login_name: username,
password: password
})
}
try {
let res = await loginapi(init)
if (!res.ok) {
let data = await res.json()
return { data: null, error: data.message }
} else {
return { data: await res.json(), error: null }
}
} catch (e) {
return { data: null, error: "api call failed: " + e }
}
}
export async function fetchCurrentUser() { export async function fetchCurrentUser() {
let path = "/user" let path = "/user"
let res = await fetch(apiurl(path)); return await fetch(apiurl(path));
return res.json();
} }
export async function fetchRuns(group, lastrun) { export async function fetchRuns(group, lastrun) {
@ -14,25 +54,40 @@ export async function fetchRuns(group, lastrun) {
if (lastrun) { if (lastrun) {
u.searchParams.append("lastrun", true) u.searchParams.append("lastrun", true)
} }
let res = await fetch(u) return await fetch(u)
return res.json();
} }
export async function fetchRun(runid) { export async function fetchRun(runid) {
let res = await fetch(apiurl("/runs/" + runid)); return await fetch(apiurl("/runs/" + runid));
return res.json();
} }
export async function fetchTask(runid, taskid) { export async function fetchTask(runid, taskid) {
let res = await fetch(apiurl("/runs/" + runid + "/tasks/" + taskid)) return await fetch(apiurl("/runs/" + runid + "/tasks/" + taskid))
return res.json(); }
export async function fetchUser(username) {
let path = "/users/" + username
return await fetch(apiurl(path));
}
export async function fetchProjectGroupSubgroups(projectgroupref) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
path += "/subgroups";
return await fetch(apiurl(path));
}
export async function fetchProjectGroupProjects(projectgroupref) {
let path = "/projectgroups/" + encodeURIComponent(projectgroupref)
path += "/projects";
return await fetch(apiurl(path));
} }
export async function fetchProject(ref) { export async function fetchProject(ref) {
let path = "/projects/" + encodeURIComponent(ref) let path = "/projects/" + encodeURIComponent(ref)
let res = await fetch(apiurl(path)); return await fetch(apiurl(path));
return res.json();
} }
export async function fetchVariables(ownertype, ref, all) { export async function fetchVariables(ownertype, ref, all) {
@ -47,8 +102,7 @@ export async function fetchVariables(ownertype, ref, all) {
if (all) { if (all) {
path += "?tree&removeoverridden"; path += "?tree&removeoverridden";
} }
let res = await fetch(apiurl(path)); return await fetch(apiurl(path));
return res.json();
} }
export async function createUserToken(username, tokenname) { export async function createUserToken(username, tokenname) {
@ -59,8 +113,7 @@ export async function createUserToken(username, tokenname) {
token_name: tokenname, token_name: tokenname,
}) })
} }
let res = await fetch(apiurl(path), init) return await fetch(apiurl(path), init)
return res.json();
} }
export async function deleteUserToken(username, tokenname) { export async function deleteUserToken(username, tokenname) {
@ -68,8 +121,7 @@ export async function deleteUserToken(username, tokenname) {
let init = { let init = {
method: "DELETE", method: "DELETE",
} }
let res = await fetch(apiurl(path), init) return await fetch(apiurl(path), init)
return res.text();
} }
export async function restartRun(runid, fromStart) { export async function restartRun(runid, fromStart) {
@ -81,8 +133,7 @@ export async function restartRun(runid, fromStart) {
from_start: fromStart from_start: fromStart
}) })
} }
let res = await fetch(apiurl(path), init) return await fetch(apiurl(path), init)
return res.json();
} }
export async function stopRun(runid) { export async function stopRun(runid) {
@ -93,8 +144,7 @@ export async function stopRun(runid) {
action_type: "stop" action_type: "stop"
}) })
} }
let res = await fetch(apiurl(path), init) return await fetch(apiurl(path), init)
return res.json();
} }
export async function approveTask(runid, taskid) { export async function approveTask(runid, taskid) {
@ -105,20 +155,17 @@ export async function approveTask(runid, taskid) {
action_type: "approve" action_type: "approve"
}) })
} }
let res = await fetch(apiurl(path), init) return await fetch(apiurl(path), init)
return res.json();
} }
export async function fetchRemoteSources() { export async function fetchRemoteSources() {
let path = "/remotesources" let path = "/remotesources"
let res = await fetch(apiurl(path)); return await fetch(apiurl(path));
return res.json();
} }
export async function userRemoteRepos(remotesourceid) { export async function userRemoteRepos(remotesourceid) {
let path = "/user/remoterepos/" + remotesourceid let path = "/user/remoterepos/" + remotesourceid
let res = await fetch(apiurl(path)); return await fetch(apiurl(path));
return res.json();
} }
export async function createProjectGroup(parentref, name) { export async function createProjectGroup(parentref, name) {
@ -131,8 +178,7 @@ export async function createProjectGroup(parentref, name) {
visibility: "public", visibility: "public",
}) })
} }
let res = await fetch(apiurl(path), init) return await fetch(apiurl(path), init)
return res.json();
} }
export async function createProject(parentref, name, remotesourcename, remoterepopath) { export async function createProject(parentref, name, remotesourcename, remoterepopath) {
@ -147,8 +193,7 @@ export async function createProject(parentref, name, remotesourcename, remoterep
repo_path: remoterepopath, repo_path: remoterepopath,
}) })
} }
let res = await fetch(apiurl(path), init) return await fetch(apiurl(path), init)
return res.json();
} }
export async function updateProject(projectref, name, visibility) { export async function updateProject(projectref, name, visibility) {
@ -160,8 +205,7 @@ export async function updateProject(projectref, name, visibility) {
visibility: visibility, visibility: visibility,
}) })
} }
let res = await fetch(apiurl(path), init) return await fetch(apiurl(path), init)
return res.json()
} }
export async function deleteProject(projectref) { export async function deleteProject(projectref) {
@ -169,8 +213,7 @@ export async function deleteProject(projectref) {
let init = { let init = {
method: "DELETE", method: "DELETE",
} }
let res = await fetch(apiurl(path), init) return await fetch(apiurl(path), init)
return res.text();
} }
export async function deleteProjectGroup(projectgroupref) { export async function deleteProjectGroup(projectgroupref) {
@ -178,6 +221,5 @@ export async function deleteProjectGroup(projectgroupref) {
let init = { let init = {
method: "DELETE", method: "DELETE",
} }
let res = await fetch(apiurl(path), init) return await fetch(apiurl(path), init)
return res.text();
} }

View File

@ -1,17 +1,23 @@
<template> <template>
<div> <div>
<div v-if="error" class="message is-danger">
<div class="message-header">
<p>Login error</p>
</div>
<div class="message-body">{{ error }}</div>
</div>
<div class="column is-4 is-offset-4"> <div class="column is-4 is-offset-4">
<div class="box" v-for="rs in remotesources" v-bind:key="rs.id"> <div class="box" v-for="rs in remotesources" v-bind:key="rs.id">
<LoginForm <LoginForm
action="Login" action="Login"
:name="rs.name" :name="rs.name"
v-if="rs.auth_type == 'password'" v-if="rs.auth_type == 'password'"
v-on:login="doLogin(rs.name, $event.username, $event.password)" v-on:login="doLogin($event.username, $event.password, rs.name)"
/> />
<button <button
v-else v-else
class="button is-info is-fullwidth" class="button is-info is-fullwidth"
@click="doLogin(rs.name)" @click="doLogin(null, null, rs.name)"
>Login with {{rs.name}}</button> >Login with {{rs.name}}</button>
</div> </div>
</div> </div>
@ -19,8 +25,8 @@
</template> </template>
<script> <script>
import { fetchRemoteSources } from "@/util/data"; import { fetchRemoteSources, login } from "@/util/data";
import { loginurl, fetch, login, logout } from "@/util/auth"; import { setLoggedUser, doLogout } from "@/util/auth";
import LoginForm from "@/components/loginform"; import LoginForm from "@/components/loginform";
@ -31,34 +37,39 @@ export default {
}, },
data: function() { data: function() {
return { return {
error: null,
remotesources: null remotesources: null
}; };
}, },
methods: { methods: {
async getRemoteSources() { async fetchRemoteSources() {
this.remotesources = await fetchRemoteSources(); let { data, error } = await fetchRemoteSources();
}, if (error) {
async doLogin(rsName, username, password) { this.$store.dispatch("setError", error);
let u = loginurl();
let res = await (await fetch(u, {
method: "POST",
body: JSON.stringify({
remote_source_name: rsName,
login_name: username,
password: password
})
})).json();
if (res.oauth2_redirect) {
window.location = res.oauth2_redirect;
return; return;
} }
login(res.token, res.user); this.remotesources = data;
},
async doLogin(username, password, remotesourcename) {
this.error = null;
let { data, error } = await login(username, password, remotesourcename);
if (error) {
// set local login error on failed login.
this.error = error;
return;
}
if (data.oauth2_redirect) {
window.location = data.oauth2_redirect;
return;
}
setLoggedUser(data.token, data.user);
this.$router.push({ name: "home" }); this.$router.push({ name: "home" });
} }
}, },
created: function() { created: function() {
logout(); doLogout();
this.getRemoteSources(); this.fetchRemoteSources();
} }
}; };
</script> </script>

View File

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

View File

@ -1,12 +1,18 @@
<template> <template>
<div> <div>
<div>{{code}}</div> <div v-if="error" class="message is-danger">
<div>{{username}}</div> <div class="message-header">
<p>Error</p>
</div>
<div class="message-body">{{ error }}</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import { oauth2callbackurl, login, fetch } from "@/util/auth"; import { fetch } from "@/util/data";
import { oauth2callbackurl, setLoggedUser } from "@/util/auth";
export default { export default {
components: {}, components: {},
@ -14,6 +20,7 @@ export default {
props: {}, props: {},
data() { data() {
return { return {
error: null,
run: null, run: null,
code: this.$route.query.code, code: this.$route.query.code,
polling: null, polling: null,
@ -25,12 +32,18 @@ export default {
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 res = await (await fetch(u)).json(); let { data, error } = await fetch(u);
if (res.request_type === "loginuser") { if (error) {
login(res.response.token, res.response.user); // set local login error on failed oauth2.
this.error = error;
return;
}
if (data.request_type === "loginuser") {
setLoggedUser(data.response.token, data.response.user);
this.$router.push("/"); this.$router.push("/");
} else if (res.request_type === "authorize") { } else if (data.request_type === "authorize") {
this.$store.dispatch("setRegisterUser", res.response); this.$store.dispatch("setRegisterUser", data.response);
this.$router.push("/register"); this.$router.push("/register");
} }
} }

View File

@ -134,7 +134,12 @@ export default {
watch: { watch: {
$route: async function(route) { $route: async function(route) {
if (route.params.runid) { if (route.params.runid) {
this.run = await fetchRun(route.params.runid); let { data, error } = await fetchRun(route.params.runid);
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.run = data;
} }
} }
}, },
@ -153,7 +158,12 @@ export default {
}, },
created: async function() { created: async function() {
if (this.$route.params.runid) { if (this.$route.params.runid) {
this.run = await fetchRun(this.$route.params.runid); let { data, error } = await fetchRun(this.$route.params.runid);
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.run = data;
} }
} }
}; };

View File

@ -51,8 +51,6 @@ import {
projectGroupCreateProjectLink projectGroupCreateProjectLink
} from "@/util/link.js"; } from "@/util/link.js";
import { fetchRun } from "@/util/data.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";
@ -64,18 +62,6 @@ export default {
ownername: String, ownername: String,
projectgroupref: Array projectgroupref: Array
}, },
data() {
return {
run: null
};
},
watch: {
$route: async function(route) {
if (route.params.runid) {
this.run = await fetchRun(route.params.runid);
}
}
},
methods: { methods: {
projectGroupProjectsLink: projectGroupProjectsLink, projectGroupProjectsLink: projectGroupProjectsLink,
projectGroupSettingsLink: projectGroupSettingsLink, projectGroupSettingsLink: projectGroupSettingsLink,
@ -103,11 +89,6 @@ export default {
) )
); );
} }
},
created: async function() {
if (this.$route.params.runid) {
this.run = await fetchRun(this.$route.params.runid);
}
} }
}; };
</script> </script>

View File

@ -31,7 +31,9 @@ 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 { apiurl, authorizeurl, registerurl, fetch, logout } from "@/util/auth"; import { fetchRemoteSources } from "@/util/data";
import { authorizeurl, registerurl, fetch, doLogout } from "@/util/auth";
export default { export default {
name: "Register", name: "Register",
@ -48,9 +50,13 @@ export default {
...mapGetters(["registeruser"]) ...mapGetters(["registeruser"])
}, },
methods: { methods: {
async getRemoteSources() { async fetchRemoteSources() {
let res = await (await fetch(apiurl("/remotesources"))).json(); let { data, error } = await fetchRemoteSources();
this.remotesources = res; if (error) {
this.$store.dispatch("setError", error);
return;
}
this.remotesources = data;
}, },
async doAuthorize(rsName, username, password) { async doAuthorize(rsName, username, password) {
let u = authorizeurl(); let u = authorizeurl();
@ -99,8 +105,8 @@ export default {
} }
}, },
created: function() { created: function() {
logout(); doLogout();
this.getRemoteSources(); this.fetchRemoteSources();
} }
}; };
</script> </script>

View File

@ -117,7 +117,12 @@ export default {
watch: { watch: {
$route: async function(route) { $route: async function(route) {
if (route.params.runid) { if (route.params.runid) {
this.run = await fetchRun(route.params.runid); let { data, error } = await fetchRun(route.params.runid);
if (error) {
this.$store.dispatch("setError", error);
return;
}
this.run = data;
} }
} }
}, },