*: 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:
parent
98073502e2
commit
789d0f5fcd
42
src/App.vue
42
src/App.vue
|
@ -43,8 +43,27 @@
|
|||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</template>
|
||||
|
@ -57,7 +76,12 @@ export default {
|
|||
name: "App",
|
||||
components: {},
|
||||
computed: {
|
||||
...mapGetters(["user"])
|
||||
...mapGetters(["error", "user"])
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
routerActive: true
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
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>
|
||||
|
@ -78,4 +110,8 @@ export default {
|
|||
.main-container {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.global-error-message {
|
||||
margin-top: 10rem;
|
||||
}
|
||||
</style>
|
|
@ -19,9 +19,9 @@
|
|||
<div class="control">
|
||||
<button class="button is-primary" @click="createProject()">Create Project</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-text">Cancel</button>
|
||||
</div>
|
||||
<div v-if="createProjectError" class="message is-danger">
|
||||
<div class="message-body">{{ createProjectError }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -47,6 +47,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
createProjectError: null,
|
||||
user: null,
|
||||
remoteSources: null,
|
||||
remoteRepos: [],
|
||||
|
@ -56,23 +57,32 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
resetErrors() {
|
||||
this.createProjectError = null;
|
||||
},
|
||||
repoSelected(remoteSource, repoPath) {
|
||||
this.selectedRemoteSource = remoteSource;
|
||||
this.remoteRepoPath = repoPath;
|
||||
},
|
||||
async createProject() {
|
||||
this.resetErrors();
|
||||
|
||||
let refArray = [this.ownertype, this.ownername];
|
||||
if (this.projectgroupref) {
|
||||
refArray = [...refArray, ...this.projectgroupref];
|
||||
}
|
||||
let parentref = refArray.join("/");
|
||||
|
||||
await createProject(
|
||||
let { error } = await createProject(
|
||||
parentref,
|
||||
this.projectName,
|
||||
this.selectedRemoteSource.name,
|
||||
this.remoteRepoPath
|
||||
);
|
||||
if (error) {
|
||||
this.createProjectError = error;
|
||||
return;
|
||||
}
|
||||
|
||||
let projectref = [this.projectName];
|
||||
if (this.projectgroupref) {
|
||||
|
@ -84,9 +94,20 @@ export default {
|
|||
}
|
||||
},
|
||||
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
|
||||
this.remoteSources = await fetchRemoteSources();
|
||||
({ data, error } = await fetchRemoteSources());
|
||||
if (error) {
|
||||
this.$store.dispatch("setError", error);
|
||||
return;
|
||||
}
|
||||
this.remoteSources = data;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
<div class="control">
|
||||
<button class="button is-primary" @click="createProjectGroup()">Create Project Group</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-text">Cancel</button>
|
||||
</div>
|
||||
<div v-if="createProjectGroupError" class="message is-danger">
|
||||
<div class="message-body">{{ createProjectGroupError }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -27,8 +27,6 @@ import { createProjectGroup } from "@/util/data.js";
|
|||
|
||||
import { projectGroupLink } from "@/util/link.js";
|
||||
|
||||
import remoterepos from "@/components/remoterepos.vue";
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
name: "createprojectgroup",
|
||||
|
@ -39,18 +37,31 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
createProjectGroupError: null,
|
||||
projectGroupName: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
resetErrors() {
|
||||
this.createProjectGroupError = null;
|
||||
},
|
||||
async createProjectGroup() {
|
||||
this.resetErrors();
|
||||
|
||||
let refArray = [this.ownertype, this.ownername];
|
||||
if (this.projectgroupref) {
|
||||
refArray = [...refArray, ...this.projectgroupref];
|
||||
}
|
||||
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];
|
||||
if (this.projectgroupref) {
|
||||
|
|
|
@ -42,6 +42,9 @@
|
|||
>Delete Project Group</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="deleteProjectGroupError" class="message is-danger">
|
||||
<div class="message-body">{{ deleteProjectGroupError }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
@ -64,6 +67,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
deleteProjectGroupError: null,
|
||||
variables: [],
|
||||
allvariables: [],
|
||||
projectGroupNameToDelete: ""
|
||||
|
@ -83,6 +87,9 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
resetErrors() {
|
||||
this.deleteProjectGroupError = null;
|
||||
},
|
||||
async deleteProjectGroup() {
|
||||
let projectgroupref = [
|
||||
this.ownertype,
|
||||
|
@ -91,7 +98,11 @@ export default {
|
|||
].join("/");
|
||||
|
||||
if (this.projectGroupNameToDelete == this.projectGroupName) {
|
||||
let res = await deleteProjectGroup(projectgroupref);
|
||||
let { error } = await deleteProjectGroup(projectgroupref);
|
||||
if (error) {
|
||||
this.deleteProjectGroupError = error;
|
||||
return;
|
||||
}
|
||||
this.$router.push(
|
||||
projectGroupLink(
|
||||
this.ownertype,
|
||||
|
@ -103,16 +114,33 @@ export default {
|
|||
}
|
||||
},
|
||||
created: async function() {
|
||||
this.variables = await fetchVariables(
|
||||
let projectgroupref = [
|
||||
this.ownertype,
|
||||
this.ownername,
|
||||
...this.projectgroupref
|
||||
].join("/");
|
||||
|
||||
let { data, error } = await fetchVariables(
|
||||
"projectgroup",
|
||||
[this.ownertype, this.ownername, ...this.projectgroupref].join("/"),
|
||||
projectgroupref,
|
||||
false
|
||||
);
|
||||
this.allvariables = await fetchVariables(
|
||||
if (error) {
|
||||
this.$store.dispatch("setError", error);
|
||||
return;
|
||||
}
|
||||
this.variables = data;
|
||||
|
||||
({ data, error } = await fetchVariables(
|
||||
"projectgroup",
|
||||
[this.ownertype, this.ownername, ...this.projectgroupref].join("/"),
|
||||
projectgroupref,
|
||||
true
|
||||
);
|
||||
));
|
||||
if (error) {
|
||||
this.$store.dispatch("setError", error);
|
||||
return;
|
||||
}
|
||||
this.allvariables = data;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -32,7 +32,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { apiurl, fetch } from "@/util/auth";
|
||||
import {
|
||||
fetchProjectGroupProjects,
|
||||
fetchProjectGroupSubgroups
|
||||
} from "@/util/data.js";
|
||||
|
||||
import { projectLink, projectGroupLink } from "@/util/link.js";
|
||||
|
||||
export default {
|
||||
|
@ -66,24 +70,33 @@ export default {
|
|||
return ref;
|
||||
},
|
||||
async fetchProjects(ownertype, ownername) {
|
||||
let ref = [ownertype, ownername];
|
||||
let projectgroupref = [ownertype, ownername];
|
||||
if (this.projectgroupref) {
|
||||
ref.push(...this.projectgroupref);
|
||||
projectgroupref.push(...this.projectgroupref);
|
||||
}
|
||||
let path = "/projectgroups/" + encodeURIComponent(ref.join("/"));
|
||||
path += "/projects";
|
||||
let res = await (await fetch(apiurl(path))).json();
|
||||
this.projects = res;
|
||||
|
||||
let { data, error } = await fetchProjectGroupProjects(
|
||||
projectgroupref.join("/")
|
||||
);
|
||||
if (error) {
|
||||
this.$store.dispatch("setError", error);
|
||||
return;
|
||||
}
|
||||
this.projects = data;
|
||||
},
|
||||
async fetchProjectGroups(ownertype, ownername) {
|
||||
let ref = [ownertype, ownername];
|
||||
let projectgroupref = [ownertype, ownername];
|
||||
if (this.projectgroupref) {
|
||||
ref.push(...this.projectgroupref);
|
||||
projectgroupref.push(...this.projectgroupref);
|
||||
}
|
||||
let path = "/projectgroups/" + encodeURIComponent(ref.join("/"));
|
||||
path += "/subgroups";
|
||||
let res = await (await fetch(apiurl(path))).json();
|
||||
this.projectgroups = res;
|
||||
let { data, error } = await fetchProjectGroupSubgroups(
|
||||
projectgroupref.join("/")
|
||||
);
|
||||
if (error) {
|
||||
this.$store.dispatch("setError", error);
|
||||
return;
|
||||
}
|
||||
this.projectgroups = data;
|
||||
},
|
||||
projectLink: projectLink,
|
||||
projectGroupLink: projectGroupLink
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
<button class="button is-primary" @click="updateProject()">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="updateProjectError" class="message is-danger">
|
||||
<div class="message-body">{{ updateProjectError }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<nav class="panel">
|
||||
|
@ -64,6 +68,10 @@
|
|||
@click="deleteProject()"
|
||||
:disabled="!deleteButtonEnabled"
|
||||
>Delete Project</button>
|
||||
|
||||
<div v-if="deleteProjectError" class="message is-danger">
|
||||
<div class="message-body">{{ deleteProjectError }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -92,6 +100,8 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
updateProjectError: null,
|
||||
deleteProjectError: null,
|
||||
project: null,
|
||||
projectIsPrivate: false,
|
||||
variables: [],
|
||||
|
@ -111,7 +121,13 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
resetErrors() {
|
||||
this.updateProjectError = null;
|
||||
this.deleteProjectError = null;
|
||||
},
|
||||
async updateProject() {
|
||||
this.resetErrors();
|
||||
|
||||
let projectref = [
|
||||
this.ownertype,
|
||||
this.ownername,
|
||||
|
@ -122,9 +138,19 @@ export default {
|
|||
if (this.projectIsPrivate) {
|
||||
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() {
|
||||
this.resetErrors();
|
||||
|
||||
let projectref = [
|
||||
this.ownertype,
|
||||
this.ownername,
|
||||
|
@ -132,7 +158,11 @@ export default {
|
|||
].join("/");
|
||||
|
||||
if (this.projectNameToDelete == this.projectName) {
|
||||
let res = await deleteProject(projectref);
|
||||
let { error } = await deleteProject(projectref);
|
||||
if (error) {
|
||||
this.deleteProjectError = error;
|
||||
return;
|
||||
}
|
||||
this.$router.push(
|
||||
projectGroupLink(
|
||||
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.variables = await fetchVariables(
|
||||
"project",
|
||||
[this.ownertype, this.ownername, ...this.projectref].join("/"),
|
||||
false
|
||||
);
|
||||
this.allvariables = await fetchVariables(
|
||||
"project",
|
||||
[this.ownertype, this.ownername, ...this.projectref].join("/"),
|
||||
true
|
||||
);
|
||||
({ data, error } = await fetchVariables("project", projectref, false));
|
||||
if (error) {
|
||||
this.$store.dispatch("setError", error);
|
||||
return;
|
||||
}
|
||||
this.variables = data;
|
||||
|
||||
({ data, error } = await fetchVariables("project", projectref, true));
|
||||
if (error) {
|
||||
this.$store.dispatch("setError", error);
|
||||
return;
|
||||
}
|
||||
this.allvariables = data;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -43,7 +43,12 @@ export default {
|
|||
this.$emit("reposelected", this.remoterepos[index].path);
|
||||
},
|
||||
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() {
|
||||
|
|
|
@ -101,7 +101,13 @@ export default {
|
|||
return "unknown";
|
||||
},
|
||||
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
|
||||
let tasks = this.run.tasks;
|
||||
let sortedTasks = Object.keys(this.run.tasks)
|
||||
|
|
|
@ -67,8 +67,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { apiurl, fetch } from "@/util/auth";
|
||||
import { fetchProject, fetchRuns } from "@/util/data.js";
|
||||
import { fetchUser, fetchProject, fetchRuns } from "@/util/data.js";
|
||||
import { userLocalRunLink, projectRunLink } from "@/util/link.js";
|
||||
|
||||
export default {
|
||||
|
@ -127,15 +126,28 @@ export default {
|
|||
this.pollData();
|
||||
},
|
||||
async fetchProject() {
|
||||
this.project = await fetchProject(
|
||||
[this.ownertype, this.ownername, ...this.projectref].join("/")
|
||||
);
|
||||
let projectref = [
|
||||
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();
|
||||
},
|
||||
async fetchUser() {
|
||||
let res = await (await fetch(apiurl("/users/" + this.username))).json();
|
||||
this.user = res;
|
||||
let { data, error } = await fetchUser(this.username);
|
||||
if (error) {
|
||||
this.$store.dispatch("setError", error);
|
||||
return;
|
||||
}
|
||||
this.user = data;
|
||||
|
||||
this.fetchRuns();
|
||||
},
|
||||
|
@ -159,7 +171,12 @@ export default {
|
|||
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() {
|
||||
clearInterval(this.polling);
|
||||
|
|
|
@ -63,10 +63,20 @@ export default {
|
|||
return "unknown";
|
||||
},
|
||||
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() {
|
||||
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() {
|
||||
this.polling = setInterval(() => {
|
||||
|
|
|
@ -10,26 +10,33 @@
|
|||
<button class="button is-primary" @click="deleteUserToken(token)">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="deleteUserTokenError" class="message is-danger">
|
||||
<div class="message-body">{{ deleteUserTokenError }}</div>
|
||||
</div>
|
||||
<div v-else class="item-list">No user tokens</div>
|
||||
</div>
|
||||
<div v-else>No user tokens</div>
|
||||
<hr>
|
||||
<div v-if="token" class="notification is-success">
|
||||
<button class="delete" @click="closeNewTokenNotification()"></button>
|
||||
User token created: {{token}}
|
||||
</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="control">
|
||||
<input class="input is-primary" type="text" placeholder="Token name" v-model="newtokenname">
|
||||
</div>
|
||||
<button class="button is-primary" @click="createUserToken()">Create Token</button>
|
||||
</div>
|
||||
<div v-if="createUserTokenError" class="message is-danger">
|
||||
<div class="message-body">{{ createUserTokenError }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
fetchCurrentUser,
|
||||
fetchRemoteSources,
|
||||
createUserToken,
|
||||
deleteUserToken
|
||||
} from "@/util/data.js";
|
||||
|
@ -40,33 +47,85 @@ export default {
|
|||
props: {},
|
||||
data() {
|
||||
return {
|
||||
createUserTokenError: null,
|
||||
deleteUserTokenError: null,
|
||||
user: [],
|
||||
remotesources: [],
|
||||
token: null,
|
||||
newtokenname: null
|
||||
};
|
||||
},
|
||||
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() {
|
||||
let res = await createUserToken(this.user.username, this.newtokenname);
|
||||
this.token = res.token;
|
||||
this.resetErrors();
|
||||
|
||||
let { data, error } = await createUserToken(
|
||||
this.user.username,
|
||||
this.newtokenname
|
||||
);
|
||||
if (error) {
|
||||
this.createUserTokenError = error;
|
||||
return;
|
||||
}
|
||||
this.token = data.token;
|
||||
this.newtokenname = null;
|
||||
this.user = await fetchCurrentUser();
|
||||
this.fetchCurrentUser();
|
||||
},
|
||||
async deleteUserToken(tokenname) {
|
||||
await deleteUserToken(this.user.username, tokenname);
|
||||
this.user = await fetchCurrentUser();
|
||||
this.resetErrors();
|
||||
|
||||
let { error } = await deleteUserToken(this.user.username, tokenname);
|
||||
if (error) {
|
||||
this.deleteUserTokenError = error;
|
||||
return;
|
||||
}
|
||||
this.fetchCurrentUser();
|
||||
},
|
||||
closeNewTokenNotification() {
|
||||
this.token = null;
|
||||
}
|
||||
},
|
||||
created: async function() {
|
||||
this.user = await fetchCurrentUser();
|
||||
created: function() {
|
||||
this.fetchCurrentUser();
|
||||
this.fetchRemoteSources();
|
||||
}
|
||||
};
|
||||
</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>
|
||||
|
||||
|
|
|
@ -21,9 +21,11 @@ import Logout from "./views/Logout.vue";
|
|||
|
||||
import { parseRef } from "@/util/link.js";
|
||||
|
||||
import store from "./store";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
export default new VueRouter({
|
||||
const router = new VueRouter({
|
||||
mode: "history",
|
||||
routes: [
|
||||
{
|
||||
|
@ -327,3 +329,10 @@ export default new VueRouter({
|
|||
},
|
||||
]
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
store.dispatch("setError", null);
|
||||
next()
|
||||
})
|
||||
|
||||
export default router
|
13
src/store.js
13
src/store.js
|
@ -4,11 +4,16 @@ import Vuex from 'vuex'
|
|||
Vue.use(Vuex)
|
||||
|
||||
const state = {
|
||||
error: null,
|
||||
user: null,
|
||||
registeruser: null
|
||||
registeruser: null,
|
||||
}
|
||||
|
||||
const getters = {
|
||||
// global error
|
||||
error: state => {
|
||||
return state.error
|
||||
},
|
||||
user: state => {
|
||||
return state.user
|
||||
},
|
||||
|
@ -18,6 +23,9 @@ const getters = {
|
|||
}
|
||||
|
||||
const mutations = {
|
||||
setError(state, error) {
|
||||
state.error = error
|
||||
},
|
||||
setUser(state, user) {
|
||||
state.user = user
|
||||
},
|
||||
|
@ -27,6 +35,9 @@ const mutations = {
|
|||
}
|
||||
|
||||
const actions = {
|
||||
setError({ commit }, error) {
|
||||
commit('setError', error)
|
||||
},
|
||||
setUser({ commit }, user) {
|
||||
commit('setUser', user)
|
||||
},
|
||||
|
|
|
@ -7,13 +7,13 @@ 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) {
|
||||
export function setLoggedUser(token, user) {
|
||||
setIdToken(token);
|
||||
setUser(user);
|
||||
store.dispatch('setUser', user)
|
||||
}
|
||||
|
||||
export function logout() {
|
||||
export function doLogout() {
|
||||
unsetIdToken();
|
||||
unsetUser()
|
||||
store.dispatch('setUser', null)
|
||||
|
@ -48,6 +48,19 @@ export function oauth2callbackurl() {
|
|||
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) {
|
||||
if (init === undefined) {
|
||||
init = {}
|
||||
|
@ -60,10 +73,14 @@ export async function fetch(url, init) {
|
|||
init.headers["Authorization"] = "bearer " + idToken
|
||||
}
|
||||
|
||||
try {
|
||||
let res = await window.fetch(url, init)
|
||||
if (res.status === 401) {
|
||||
router.push({ name: "login" })
|
||||
} else { return res }
|
||||
} catch (e) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export function setIdToken(idToken) {
|
||||
|
|
116
src/util/data.js
116
src/util/data.js
|
@ -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() {
|
||||
let path = "/user"
|
||||
let res = await fetch(apiurl(path));
|
||||
return res.json();
|
||||
return await fetch(apiurl(path));
|
||||
}
|
||||
|
||||
export async function fetchRuns(group, lastrun) {
|
||||
|
@ -14,25 +54,40 @@ export async function fetchRuns(group, lastrun) {
|
|||
if (lastrun) {
|
||||
u.searchParams.append("lastrun", true)
|
||||
}
|
||||
let res = await fetch(u)
|
||||
return res.json();
|
||||
return await fetch(u)
|
||||
}
|
||||
|
||||
export async function fetchRun(runid) {
|
||||
let res = await fetch(apiurl("/runs/" + runid));
|
||||
return res.json();
|
||||
return await fetch(apiurl("/runs/" + runid));
|
||||
}
|
||||
|
||||
export async function fetchTask(runid, taskid) {
|
||||
let res = await fetch(apiurl("/runs/" + runid + "/tasks/" + taskid))
|
||||
return res.json();
|
||||
return await fetch(apiurl("/runs/" + runid + "/tasks/" + taskid))
|
||||
}
|
||||
|
||||
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) {
|
||||
let path = "/projects/" + encodeURIComponent(ref)
|
||||
|
||||
let res = await fetch(apiurl(path));
|
||||
return res.json();
|
||||
return await fetch(apiurl(path));
|
||||
}
|
||||
|
||||
export async function fetchVariables(ownertype, ref, all) {
|
||||
|
@ -47,8 +102,7 @@ export async function fetchVariables(ownertype, ref, all) {
|
|||
if (all) {
|
||||
path += "?tree&removeoverridden";
|
||||
}
|
||||
let res = await fetch(apiurl(path));
|
||||
return res.json();
|
||||
return await fetch(apiurl(path));
|
||||
}
|
||||
|
||||
export async function createUserToken(username, tokenname) {
|
||||
|
@ -59,8 +113,7 @@ export async function createUserToken(username, tokenname) {
|
|||
token_name: tokenname,
|
||||
})
|
||||
}
|
||||
let res = await fetch(apiurl(path), init)
|
||||
return res.json();
|
||||
return await fetch(apiurl(path), init)
|
||||
}
|
||||
|
||||
export async function deleteUserToken(username, tokenname) {
|
||||
|
@ -68,8 +121,7 @@ export async function deleteUserToken(username, tokenname) {
|
|||
let init = {
|
||||
method: "DELETE",
|
||||
}
|
||||
let res = await fetch(apiurl(path), init)
|
||||
return res.text();
|
||||
return await fetch(apiurl(path), init)
|
||||
}
|
||||
|
||||
export async function restartRun(runid, fromStart) {
|
||||
|
@ -81,8 +133,7 @@ export async function restartRun(runid, fromStart) {
|
|||
from_start: fromStart
|
||||
})
|
||||
}
|
||||
let res = await fetch(apiurl(path), init)
|
||||
return res.json();
|
||||
return await fetch(apiurl(path), init)
|
||||
}
|
||||
|
||||
export async function stopRun(runid) {
|
||||
|
@ -93,8 +144,7 @@ export async function stopRun(runid) {
|
|||
action_type: "stop"
|
||||
})
|
||||
}
|
||||
let res = await fetch(apiurl(path), init)
|
||||
return res.json();
|
||||
return await fetch(apiurl(path), init)
|
||||
}
|
||||
|
||||
export async function approveTask(runid, taskid) {
|
||||
|
@ -105,20 +155,17 @@ export async function approveTask(runid, taskid) {
|
|||
action_type: "approve"
|
||||
})
|
||||
}
|
||||
let res = await fetch(apiurl(path), init)
|
||||
return res.json();
|
||||
return await fetch(apiurl(path), init)
|
||||
}
|
||||
|
||||
export async function fetchRemoteSources() {
|
||||
let path = "/remotesources"
|
||||
let res = await fetch(apiurl(path));
|
||||
return res.json();
|
||||
return await fetch(apiurl(path));
|
||||
}
|
||||
|
||||
export async function userRemoteRepos(remotesourceid) {
|
||||
let path = "/user/remoterepos/" + remotesourceid
|
||||
let res = await fetch(apiurl(path));
|
||||
return res.json();
|
||||
return await fetch(apiurl(path));
|
||||
}
|
||||
|
||||
export async function createProjectGroup(parentref, name) {
|
||||
|
@ -131,8 +178,7 @@ export async function createProjectGroup(parentref, name) {
|
|||
visibility: "public",
|
||||
})
|
||||
}
|
||||
let res = await fetch(apiurl(path), init)
|
||||
return res.json();
|
||||
return await fetch(apiurl(path), init)
|
||||
}
|
||||
|
||||
export async function createProject(parentref, name, remotesourcename, remoterepopath) {
|
||||
|
@ -147,8 +193,7 @@ export async function createProject(parentref, name, remotesourcename, remoterep
|
|||
repo_path: remoterepopath,
|
||||
})
|
||||
}
|
||||
let res = await fetch(apiurl(path), init)
|
||||
return res.json();
|
||||
return await fetch(apiurl(path), init)
|
||||
}
|
||||
|
||||
export async function updateProject(projectref, name, visibility) {
|
||||
|
@ -160,8 +205,7 @@ export async function updateProject(projectref, name, visibility) {
|
|||
visibility: visibility,
|
||||
})
|
||||
}
|
||||
let res = await fetch(apiurl(path), init)
|
||||
return res.json()
|
||||
return await fetch(apiurl(path), init)
|
||||
}
|
||||
|
||||
export async function deleteProject(projectref) {
|
||||
|
@ -169,8 +213,7 @@ export async function deleteProject(projectref) {
|
|||
let init = {
|
||||
method: "DELETE",
|
||||
}
|
||||
let res = await fetch(apiurl(path), init)
|
||||
return res.text();
|
||||
return await fetch(apiurl(path), init)
|
||||
}
|
||||
|
||||
export async function deleteProjectGroup(projectgroupref) {
|
||||
|
@ -178,6 +221,5 @@ export async function deleteProjectGroup(projectgroupref) {
|
|||
let init = {
|
||||
method: "DELETE",
|
||||
}
|
||||
let res = await fetch(apiurl(path), init)
|
||||
return res.text();
|
||||
return await fetch(apiurl(path), init)
|
||||
}
|
|
@ -1,17 +1,23 @@
|
|||
<template>
|
||||
<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="box" v-for="rs in remotesources" v-bind:key="rs.id">
|
||||
<LoginForm
|
||||
action="Login"
|
||||
:name="rs.name"
|
||||
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
|
||||
v-else
|
||||
class="button is-info is-fullwidth"
|
||||
@click="doLogin(rs.name)"
|
||||
@click="doLogin(null, null, rs.name)"
|
||||
>Login with {{rs.name}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -19,8 +25,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { fetchRemoteSources } from "@/util/data";
|
||||
import { loginurl, fetch, login, logout } from "@/util/auth";
|
||||
import { fetchRemoteSources, login } from "@/util/data";
|
||||
import { setLoggedUser, doLogout } from "@/util/auth";
|
||||
|
||||
import LoginForm from "@/components/loginform";
|
||||
|
||||
|
@ -31,34 +37,39 @@ export default {
|
|||
},
|
||||
data: function() {
|
||||
return {
|
||||
error: null,
|
||||
remotesources: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async getRemoteSources() {
|
||||
this.remotesources = await fetchRemoteSources();
|
||||
},
|
||||
async doLogin(rsName, username, password) {
|
||||
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;
|
||||
async fetchRemoteSources() {
|
||||
let { data, error } = await fetchRemoteSources();
|
||||
if (error) {
|
||||
this.$store.dispatch("setError", error);
|
||||
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" });
|
||||
}
|
||||
},
|
||||
created: function() {
|
||||
logout();
|
||||
this.getRemoteSources();
|
||||
doLogout();
|
||||
this.fetchRemoteSources();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
import { logout } from "@/util/auth";
|
||||
import { doLogout } from "@/util/auth";
|
||||
|
||||
export default {
|
||||
name: "Logout",
|
||||
created: function() {
|
||||
logout();
|
||||
doLogout();
|
||||
this.$router.push("/");
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
<template>
|
||||
<div>
|
||||
<div>{{code}}</div>
|
||||
<div>{{username}}</div>
|
||||
<div v-if="error" class="message is-danger">
|
||||
<div class="message-header">
|
||||
<p>Error</p>
|
||||
</div>
|
||||
<div class="message-body">{{ error }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { oauth2callbackurl, login, fetch } from "@/util/auth";
|
||||
import { fetch } from "@/util/data";
|
||||
|
||||
import { oauth2callbackurl, setLoggedUser } from "@/util/auth";
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
|
@ -14,6 +20,7 @@ export default {
|
|||
props: {},
|
||||
data() {
|
||||
return {
|
||||
error: null,
|
||||
run: null,
|
||||
code: this.$route.query.code,
|
||||
polling: null,
|
||||
|
@ -25,12 +32,18 @@ export default {
|
|||
let u = oauth2callbackurl();
|
||||
u.searchParams.append("code", this.$route.query.code);
|
||||
u.searchParams.append("state", this.$route.query.state);
|
||||
let res = await (await fetch(u)).json();
|
||||
if (res.request_type === "loginuser") {
|
||||
login(res.response.token, res.response.user);
|
||||
let { data, error } = await fetch(u);
|
||||
if (error) {
|
||||
// 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("/");
|
||||
} else if (res.request_type === "authorize") {
|
||||
this.$store.dispatch("setRegisterUser", res.response);
|
||||
} else if (data.request_type === "authorize") {
|
||||
this.$store.dispatch("setRegisterUser", data.response);
|
||||
this.$router.push("/register");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,12 @@ export default {
|
|||
watch: {
|
||||
$route: async function(route) {
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -51,8 +51,6 @@ import {
|
|||
projectGroupCreateProjectLink
|
||||
} from "@/util/link.js";
|
||||
|
||||
import { fetchRun } from "@/util/data.js";
|
||||
|
||||
import projbreadcrumbs from "@/components/projbreadcrumbs.vue";
|
||||
import createprojectbutton from "@/components/createprojectbutton.vue";
|
||||
|
||||
|
@ -64,18 +62,6 @@ export default {
|
|||
ownername: String,
|
||||
projectgroupref: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
run: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route: async function(route) {
|
||||
if (route.params.runid) {
|
||||
this.run = await fetchRun(route.params.runid);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
projectGroupProjectsLink: projectGroupProjectsLink,
|
||||
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>
|
||||
|
|
|
@ -31,7 +31,9 @@ import { mapGetters } from "vuex";
|
|||
import LoginForm from "@/components/loginform";
|
||||
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 {
|
||||
name: "Register",
|
||||
|
@ -48,9 +50,13 @@ export default {
|
|||
...mapGetters(["registeruser"])
|
||||
},
|
||||
methods: {
|
||||
async getRemoteSources() {
|
||||
let res = await (await fetch(apiurl("/remotesources"))).json();
|
||||
this.remotesources = res;
|
||||
async fetchRemoteSources() {
|
||||
let { data, error } = await fetchRemoteSources();
|
||||
if (error) {
|
||||
this.$store.dispatch("setError", error);
|
||||
return;
|
||||
}
|
||||
this.remotesources = data;
|
||||
},
|
||||
async doAuthorize(rsName, username, password) {
|
||||
let u = authorizeurl();
|
||||
|
@ -99,8 +105,8 @@ export default {
|
|||
}
|
||||
},
|
||||
created: function() {
|
||||
logout();
|
||||
this.getRemoteSources();
|
||||
doLogout();
|
||||
this.fetchRemoteSources();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -117,7 +117,12 @@ export default {
|
|||
watch: {
|
||||
$route: async function(route) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue