mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-24 03:40:52 +01:00
995 lines
24 KiB
Vue
995 lines
24 KiB
Vue
<template>
|
|
<v-app v-if="state === 'success'" class="app">
|
|
<EditDialog
|
|
v-model="passwordDialog"
|
|
save-button-text="Save"
|
|
:title="$t('changePassword')"
|
|
v-if="user"
|
|
event-name="i-user"
|
|
>
|
|
<template v-slot:form="{ onSave, onError, needSave, needReset }">
|
|
<ChangePasswordForm
|
|
:project-id="projectId"
|
|
:item-id="user.id"
|
|
@save="onSave"
|
|
@error="onError"
|
|
:need-save="needSave"
|
|
:need-reset="needReset"
|
|
/>
|
|
</template>
|
|
</EditDialog>
|
|
|
|
<EditDialog
|
|
v-model="userDialog"
|
|
save-button-text="Save"
|
|
:title="$t('editUser')"
|
|
v-if="user"
|
|
event-name="i-user"
|
|
>
|
|
<template v-slot:form="{ onSave, onError, needSave, needReset }">
|
|
<UserForm
|
|
:project-id="projectId"
|
|
:item-id="user.id"
|
|
@save="onSave"
|
|
@error="onError"
|
|
:need-save="needSave"
|
|
:need-reset="needReset"
|
|
:is-admin="user.admin"
|
|
/>
|
|
</template>
|
|
</EditDialog>
|
|
|
|
<EditDialog
|
|
v-model="taskLogDialog"
|
|
save-button-text="Delete"
|
|
:max-width="1000"
|
|
:hide-buttons="true"
|
|
@close="onTaskLogDialogClosed()"
|
|
>
|
|
<template v-slot:title={}>
|
|
<div class="text-truncate" style="max-width: calc(100% - 36px);">
|
|
<router-link
|
|
class="breadcrumbs__item breadcrumbs__item--link"
|
|
:to="`/project/${projectId}/templates/${template ? template.id : null}`"
|
|
@click="taskLogDialog = false"
|
|
>{{ template ? template.name : null }}
|
|
</router-link>
|
|
<v-icon>mdi-chevron-right</v-icon>
|
|
<span class="breadcrumbs__item">{{ $t('task', {expr: task ? task.id : null}) }}</span>
|
|
</div>
|
|
|
|
<v-spacer></v-spacer>
|
|
<v-btn
|
|
icon
|
|
@click="taskLogDialog = false; onTaskLogDialogClosed()"
|
|
>
|
|
<v-icon>mdi-close</v-icon>
|
|
</v-btn>
|
|
</template>
|
|
<template v-slot:form="{}">
|
|
<TaskLogView :project-id="projectId" :item-id="task ? task.id : null"/>
|
|
</template>
|
|
</EditDialog>
|
|
|
|
<EditDialog
|
|
v-model="newProjectDialog"
|
|
save-button-text="Create"
|
|
:title="$t('newProject')"
|
|
event-name="i-project"
|
|
>
|
|
<template v-slot:form="{ onSave, onError, needSave, needReset }">
|
|
<ProjectForm
|
|
item-id="new"
|
|
@save="onSave"
|
|
@error="onError"
|
|
:need-save="needSave"
|
|
:need-reset="needReset"
|
|
/>
|
|
</template>
|
|
</EditDialog>
|
|
|
|
<v-snackbar
|
|
v-model="snackbar"
|
|
:color="snackbarColor"
|
|
:timeout="3000"
|
|
top
|
|
>
|
|
{{ snackbarText }}
|
|
<v-btn
|
|
text
|
|
@click="snackbar = false"
|
|
>
|
|
{{ $t('close') }}
|
|
</v-btn>
|
|
</v-snackbar>
|
|
|
|
<v-navigation-drawer
|
|
app
|
|
dark
|
|
:color="darkMode ? '#003236' : '#005057'"
|
|
fixed
|
|
width="260"
|
|
v-model="drawer"
|
|
mobile-breakpoint="960"
|
|
v-if="$route.path.startsWith('/project/')"
|
|
>
|
|
<v-menu bottom max-width="235" max-height="100%" v-if="project">
|
|
<template v-slot:activator="{ on, attrs }">
|
|
<v-list class="pa-0 overflow-y-auto">
|
|
<v-list-item
|
|
key="project"
|
|
class="app__project-selector"
|
|
v-bind="attrs"
|
|
v-on="on"
|
|
>
|
|
<v-list-item-icon>
|
|
<v-avatar
|
|
:color="getProjectColor(project)"
|
|
size="24"
|
|
style="font-size: 13px; font-weight: bold;"
|
|
>
|
|
<span class="white--text">{{ getProjectInitials(project) }}</span>
|
|
</v-avatar>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title class="app__project-selector-title">
|
|
{{ project.name }}
|
|
</v-list-item-title>
|
|
<v-list-item-subtitle>{{ userRole.role }}</v-list-item-subtitle>
|
|
</v-list-item-content>
|
|
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-chevron-down</v-icon>
|
|
</v-list-item-icon>
|
|
</v-list-item>
|
|
</v-list>
|
|
</template>
|
|
<v-list>
|
|
<v-list-item
|
|
v-for="(item, i) in projects"
|
|
:key="i"
|
|
:to="`/project/${item.id}`"
|
|
@click="selectProject(item.id)"
|
|
>
|
|
<v-list-item-icon>
|
|
<v-avatar
|
|
:color="getProjectColor(item)"
|
|
size="24"
|
|
style="font-size: 13px; font-weight: bold;"
|
|
>
|
|
<span class="white--text">{{ getProjectInitials(item) }}</span>
|
|
</v-avatar>
|
|
</v-list-item-icon>
|
|
<v-list-item-content>{{ item.name }}</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-list-item @click="newProjectDialog = true" v-if="user.can_create_project">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-plus</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
{{ $t('newProject2') }}
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
|
|
<v-list class="pt-0" v-if="!project">
|
|
<v-list-item key="new_project" :to="`/project/new`">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-plus</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title>{{ $t('newProject') }}</v-list-item-title>
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
</v-list>
|
|
|
|
<v-list class="pt-0" v-if="project">
|
|
|
|
<v-list-item key="dashboard" :to="`/project/${projectId}/history`">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-view-dashboard</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title>{{ $t('dashboard') }}</v-list-item-title>
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-list-item key="templates" :to="templatesUrl">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-check-all</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title>{{ $t('taskTemplates') }}</v-list-item-title>
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-list-item key="inventory" :to="`/project/${projectId}/inventory`">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-monitor-multiple</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title>{{ $t('inventory') }}</v-list-item-title>
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-list-item key="environment" :to="`/project/${projectId}/environment`">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-code-braces</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title>{{ $t('environment') }}</v-list-item-title>
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-list-item key="keys" :to="`/project/${projectId}/keys`">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-key-change</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title>{{ $t('keyStore') }}</v-list-item-title>
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-list-item key="repositories" :to="`/project/${projectId}/repositories`">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-git</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title>{{ $t('repositories') }}</v-list-item-title>
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-list-item key="team" :to="`/project/${projectId}/team`">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-account-multiple</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title>{{ $t('team') }}</v-list-item-title>
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
</v-list>
|
|
|
|
<template v-slot:append>
|
|
<v-list class="pa-0">
|
|
<v-list-item>
|
|
<v-switch
|
|
v-model="darkMode"
|
|
inset
|
|
:label="$t('darkMode')"
|
|
persistent-hint
|
|
></v-switch>
|
|
|
|
<v-spacer />
|
|
|
|
<v-menu top min-width="150" max-width="235" nudge-top="12" :position-x="50" absolute>
|
|
<template v-slot:activator="{on, attrs}">
|
|
<v-btn
|
|
icon
|
|
x-large
|
|
v-bind="attrs"
|
|
v-on="on"
|
|
>
|
|
<span style="font-size: 30px;">{{ lang.flag }}</span>
|
|
</v-btn>
|
|
</template>
|
|
|
|
<v-list dense>
|
|
<v-list-item
|
|
v-for="lang in languages"
|
|
:key="lang.id"
|
|
@click="selectLanguage(lang.id)"
|
|
>
|
|
|
|
<v-list-item-icon>
|
|
{{ lang.flag }}
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title>{{ lang.title }}</v-list-item-title>
|
|
</v-list-item-content>
|
|
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
|
|
</v-list-item>
|
|
|
|
<v-menu top max-width="235" nudge-top="12">
|
|
<template v-slot:activator="{ on, attrs }">
|
|
<v-list-item
|
|
key="project"
|
|
v-bind="attrs"
|
|
v-on="on"
|
|
>
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-account</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
<v-list-item-title>
|
|
{{ user.name }}
|
|
</v-list-item-title>
|
|
|
|
</v-list-item-content>
|
|
|
|
<v-list-item-action>
|
|
<v-chip color="red" small>admin</v-chip>
|
|
</v-list-item-action>
|
|
</v-list-item>
|
|
</template>
|
|
|
|
<v-list>
|
|
<v-list-item key="users" to="/users" v-if="user.admin">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-account-multiple</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
{{ $t('users') }}
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-list-item key="edit" @click="userDialog = true">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-pencil</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
{{ $t('editAccount') }}
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
|
|
<v-list-item key="sign_out" @click="signOut()">
|
|
<v-list-item-icon>
|
|
<v-icon>mdi-exit-to-app</v-icon>
|
|
</v-list-item-icon>
|
|
|
|
<v-list-item-content>
|
|
{{ $t('signOut') }}
|
|
</v-list-item-content>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
|
|
</v-list>
|
|
|
|
</template>
|
|
</v-navigation-drawer>
|
|
|
|
<v-main>
|
|
<router-view
|
|
:projectId="projectId"
|
|
:userPermissions="userRole.permissions"
|
|
:userId="user ? user.id : null"
|
|
:isAdmin="user ? user.admin : false"
|
|
></router-view>
|
|
</v-main>
|
|
|
|
</v-app>
|
|
<v-app v-else-if="state === 'loading'">
|
|
<v-main>
|
|
<v-container
|
|
fluid
|
|
fill-height
|
|
align-center
|
|
justify-center
|
|
class="pa-0"
|
|
>
|
|
<v-progress-circular
|
|
:size="70"
|
|
color="primary"
|
|
indeterminate
|
|
></v-progress-circular>
|
|
</v-container>
|
|
</v-main>
|
|
</v-app>
|
|
<v-app v-else-if="state === 'error'">
|
|
<v-main>
|
|
<v-container
|
|
fluid
|
|
flex-column
|
|
fill-height
|
|
align-center
|
|
justify-center
|
|
class="pa-0 text-center"
|
|
>
|
|
<v-alert text color="error" class="d-inline-block">
|
|
<h3 class="headline">
|
|
{{ $t('error') }}
|
|
</h3>
|
|
{{ snackbarText }}
|
|
</v-alert>
|
|
<div class="mb-6">
|
|
<v-btn text color="blue darken-1" @click="refreshPage()">
|
|
<v-icon left>mdi-refresh</v-icon>
|
|
{{ $t('refreshPage') }}
|
|
</v-btn>
|
|
<v-btn text color="blue darken-1" @click="signOut()">
|
|
<v-icon left>mdi-exit-to-app</v-icon>
|
|
{{ $t('relogin') }}
|
|
</v-btn>
|
|
</div>
|
|
</v-container>
|
|
</v-main>
|
|
</v-app>
|
|
<v-app v-else></v-app>
|
|
</template>
|
|
<style lang="scss">
|
|
.v-dialog > .v-card > .v-card__title {
|
|
flex-wrap: nowrap;
|
|
overflow: hidden;
|
|
|
|
& * {
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
.v-data-table tbody tr.v-data-table__expanded__content {
|
|
box-shadow: none !important;
|
|
|
|
}
|
|
|
|
.v-data-table a {
|
|
text-decoration-line: none;
|
|
|
|
&:hover {
|
|
text-decoration-line: underline;
|
|
}
|
|
}
|
|
|
|
.breadcrumbs__item--link {
|
|
text-decoration-line: none;
|
|
|
|
&:hover {
|
|
text-decoration-line: underline;
|
|
}
|
|
}
|
|
|
|
.breadcrumbs__separator {
|
|
padding: 0 10px;
|
|
}
|
|
|
|
.app__project-selector {
|
|
height: 64px;
|
|
|
|
& > .v-list-item__content {
|
|
padding: 0;
|
|
}
|
|
|
|
.v-list-item__icon {
|
|
margin-top: 20px !important;
|
|
}
|
|
}
|
|
|
|
.app__project-selector-title {
|
|
font-size: 1.25rem !important;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.v-application--is-ltr .v-list-item__action:first-child,
|
|
.v-application--is-ltr .v-list-item__icon:first-child {
|
|
margin-right: 16px !important;
|
|
}
|
|
|
|
.v-toolbar__content {
|
|
height: 64px !important;
|
|
}
|
|
|
|
.v-data-table-header {
|
|
}
|
|
|
|
.theme--light.v-data-table > .v-data-table__wrapper > table > thead > tr:last-child > th {
|
|
text-transform: uppercase;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.v-data-table > .v-data-table__wrapper > table > tbody > tr {
|
|
background: transparent !important;
|
|
|
|
& > td {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
& > td:first-child {
|
|
//font-weight: bold !important;
|
|
}
|
|
}
|
|
|
|
.v-data-table > .v-data-table__wrapper > table > tbody > tr > th,
|
|
.v-data-table > .v-data-table__wrapper > table > thead > tr > th,
|
|
.v-data-table > .v-data-table__wrapper > table > tfoot > tr > th,
|
|
.v-data-table > .v-data-table__wrapper > table > tbody > tr > td {
|
|
font-size: 1rem !important;
|
|
}
|
|
|
|
.v-data-footer {
|
|
font-size: 1rem !important;
|
|
}
|
|
|
|
.v-toolbar__title {
|
|
font-weight: bold !important;
|
|
}
|
|
|
|
.v-app-bar__nav-icon {
|
|
margin-left: 0 !important;
|
|
}
|
|
|
|
.v-toolbar__title:not(:first-child) {
|
|
margin-left: 10px !important;
|
|
}
|
|
|
|
@media (min-width: 960px) {
|
|
.v-app-bar__nav-icon {
|
|
display: none !important;
|
|
}
|
|
|
|
.v-toolbar__title:not(:first-child) {
|
|
padding-left: 0 !important;
|
|
margin-left: 0 !important;
|
|
}
|
|
}
|
|
|
|
</style>
|
|
|
|
<script>
|
|
import axios from 'axios';
|
|
import { getErrorMessage } from '@/lib/error';
|
|
import EditDialog from '@/components/EditDialog.vue';
|
|
import TaskLogView from '@/components/TaskLogView.vue';
|
|
import ProjectForm from '@/components/ProjectForm.vue';
|
|
import UserForm from '@/components/UserForm.vue';
|
|
import ChangePasswordForm from '@/components/ChangePasswordForm.vue';
|
|
import EventBus from '@/event-bus';
|
|
import socket from '@/socket';
|
|
|
|
const PROJECT_COLORS = [
|
|
'red',
|
|
'blue',
|
|
'orange',
|
|
'green',
|
|
];
|
|
|
|
const LANGUAGES = {
|
|
en: {
|
|
flag: '🇺🇸',
|
|
title: 'English',
|
|
},
|
|
ru: {
|
|
flag: '🇷🇺',
|
|
title: 'Russian',
|
|
},
|
|
de: {
|
|
flag: '🇩🇪',
|
|
title: 'German',
|
|
},
|
|
zh: {
|
|
flag: '🇨🇳',
|
|
title: 'Chinese',
|
|
},
|
|
fr: {
|
|
flag: '🇫🇷',
|
|
title: 'French',
|
|
},
|
|
pt: {
|
|
flag: '🇵🇹',
|
|
title: 'Portuguese',
|
|
},
|
|
};
|
|
|
|
function getLangInfo(locale) {
|
|
let res = LANGUAGES[locale];
|
|
|
|
if (!res) {
|
|
res = LANGUAGES.en;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
function getSystemLang() {
|
|
const locale = navigator.language.split('-')[0];
|
|
|
|
return getLangInfo(locale || 'en');
|
|
}
|
|
|
|
export default {
|
|
name: 'App',
|
|
components: {
|
|
ChangePasswordForm,
|
|
UserForm,
|
|
EditDialog,
|
|
TaskLogView,
|
|
ProjectForm,
|
|
},
|
|
data() {
|
|
return {
|
|
drawer: null,
|
|
user: null,
|
|
userRole: 0,
|
|
systemInfo: null,
|
|
state: 'loading',
|
|
snackbar: false,
|
|
snackbarText: '',
|
|
snackbarColor: '',
|
|
projects: null,
|
|
newProjectDialog: null,
|
|
userDialog: null,
|
|
passwordDialog: null,
|
|
|
|
taskLogDialog: null,
|
|
task: null,
|
|
template: null,
|
|
darkMode: false,
|
|
languages: [
|
|
{
|
|
id: '',
|
|
flag: getSystemLang().flag,
|
|
title: 'System',
|
|
},
|
|
...Object.keys(LANGUAGES).map((lang) => ({
|
|
id: lang,
|
|
...LANGUAGES[lang],
|
|
})),
|
|
],
|
|
};
|
|
},
|
|
|
|
watch: {
|
|
async projects(val) {
|
|
if (val.length === 0
|
|
&& this.$route.path.startsWith('/project/')
|
|
&& this.$route.path !== '/project/new') {
|
|
await this.$router.push({ path: '/project/new' });
|
|
}
|
|
},
|
|
|
|
async $route(val) {
|
|
if (val.query.t == null) {
|
|
this.taskLogDialog = false;
|
|
} else {
|
|
const taskId = parseInt(this.$route.query.t || '', 10);
|
|
if (taskId) {
|
|
EventBus.$emit('i-show-task', { taskId });
|
|
}
|
|
}
|
|
},
|
|
|
|
darkMode(val) {
|
|
this.$vuetify.theme.dark = val;
|
|
if (val) {
|
|
localStorage.setItem('darkMode', '1');
|
|
} else {
|
|
localStorage.removeItem('darkMode');
|
|
}
|
|
},
|
|
},
|
|
|
|
computed: {
|
|
|
|
lang() {
|
|
const locale = localStorage.getItem('lang');
|
|
|
|
if (!locale) {
|
|
return getSystemLang();
|
|
}
|
|
|
|
return getLangInfo(locale || 'en');
|
|
},
|
|
|
|
projectId() {
|
|
return parseInt(this.$route.params.projectId, 10) || null;
|
|
},
|
|
|
|
project() {
|
|
return this.projects.find((x) => x.id === this.projectId);
|
|
},
|
|
|
|
isAuthenticated() {
|
|
return document.cookie.includes('semaphore=');
|
|
},
|
|
|
|
templatesUrl() {
|
|
let viewId = localStorage.getItem(`project${this.projectId}__lastVisitedViewId`);
|
|
if (viewId) {
|
|
viewId = parseInt(viewId, 10);
|
|
if (!Number.isNaN(viewId)) {
|
|
return `/project/${this.projectId}/views/${viewId}/templates`;
|
|
}
|
|
}
|
|
return `/project/${this.projectId}/templates`;
|
|
},
|
|
},
|
|
|
|
async created() {
|
|
if (!this.isAuthenticated) {
|
|
if (this.$route.path !== '/auth/login') {
|
|
await this.$router.push({ path: '/auth/login' });
|
|
}
|
|
this.state = 'success';
|
|
return;
|
|
}
|
|
|
|
if (localStorage.getItem('darkMode') === '1') {
|
|
this.darkMode = true;
|
|
}
|
|
|
|
try {
|
|
await this.loadData();
|
|
this.state = 'success';
|
|
} catch (err) {
|
|
EventBus.$emit('i-snackbar', {
|
|
color: 'error',
|
|
text: getErrorMessage(err),
|
|
});
|
|
this.state = 'error';
|
|
socket.stop();
|
|
}
|
|
},
|
|
|
|
mounted() {
|
|
EventBus.$on('i-snackbar', (e) => {
|
|
this.snackbar = true;
|
|
this.snackbarColor = e.color;
|
|
this.snackbarText = e.text;
|
|
});
|
|
|
|
EventBus.$on('i-account-change', async () => {
|
|
await this.loadUserInfo();
|
|
});
|
|
|
|
EventBus.$on('i-show-drawer', async () => {
|
|
this.drawer = true;
|
|
});
|
|
|
|
EventBus.$on('i-show-task', async (e) => {
|
|
if (parseInt(this.$route.query.t || '', 10) !== e.taskId) {
|
|
const query = { ...this.$route.query, t: e.taskId };
|
|
await this.$router.replace({ query });
|
|
}
|
|
|
|
this.task = (await axios({
|
|
method: 'get',
|
|
url: `/api/project/${this.projectId}/tasks/${e.taskId}`,
|
|
responseType: 'json',
|
|
})).data;
|
|
|
|
this.template = (await axios({
|
|
method: 'get',
|
|
url: `/api/project/${this.projectId}/templates/${this.task.template_id}`,
|
|
responseType: 'json',
|
|
})).data;
|
|
|
|
this.taskLogDialog = true;
|
|
});
|
|
|
|
EventBus.$on('i-open-last-project', async () => {
|
|
await this.trySelectMostSuitableProject();
|
|
});
|
|
|
|
EventBus.$on('i-user', async (e) => {
|
|
let text;
|
|
|
|
switch (e.action) {
|
|
case 'new':
|
|
text = `User ${e.item.name} created`;
|
|
break;
|
|
case 'edit':
|
|
text = `User ${e.item.name} saved`;
|
|
break;
|
|
case 'delete':
|
|
text = `User ${e.item.name} deleted`;
|
|
break;
|
|
default:
|
|
throw new Error('Unknown project action');
|
|
}
|
|
|
|
EventBus.$emit('i-snackbar', {
|
|
color: 'success',
|
|
text,
|
|
});
|
|
|
|
if (this.user && e.item.id === this.user.id) {
|
|
await this.loadUserInfo();
|
|
}
|
|
});
|
|
|
|
EventBus.$on('i-project', async (e) => {
|
|
let text;
|
|
|
|
const project = this.projects.find((p) => p.id === e.item.id) || e.item;
|
|
const projectName = project.name || `#${project.id}`;
|
|
|
|
switch (e.action) {
|
|
case 'new':
|
|
text = `Project ${projectName} created`;
|
|
break;
|
|
case 'edit':
|
|
text = `Project ${projectName} saved`;
|
|
break;
|
|
case 'delete':
|
|
text = `Project ${projectName} deleted`;
|
|
break;
|
|
default:
|
|
throw new Error('Unknown project action');
|
|
}
|
|
|
|
EventBus.$emit('i-snackbar', {
|
|
color: 'success',
|
|
text,
|
|
});
|
|
|
|
await this.loadProjects();
|
|
|
|
switch (e.action) {
|
|
case 'new':
|
|
await this.selectProject(e.item.id);
|
|
break;
|
|
case 'delete':
|
|
if (this.projectId === e.item.id && this.projects.length > 0) {
|
|
await this.selectProject(this.projects[0].id);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
},
|
|
|
|
methods: {
|
|
|
|
selectLanguage(lang) {
|
|
localStorage.setItem('lang', lang);
|
|
window.location.reload();
|
|
},
|
|
|
|
async onTaskLogDialogClosed() {
|
|
const query = { ...this.$route.query, t: undefined };
|
|
await this.$router.replace({ query });
|
|
},
|
|
|
|
async loadData() {
|
|
if (!socket.isRunning()) {
|
|
socket.start();
|
|
}
|
|
|
|
await this.loadUserInfo();
|
|
await this.loadProjects();
|
|
|
|
// try to find project and switch to it if URL not pointing to any project
|
|
if (this.$route.path === '/'
|
|
|| this.$route.path === '/project'
|
|
|| (this.$route.path.startsWith('/project/'))) {
|
|
await this.trySelectMostSuitableProject();
|
|
}
|
|
|
|
// display task dialog if query param t specified
|
|
if (this.$route.query.t) {
|
|
const taskId = parseInt(this.$route.query.t || '', 10);
|
|
if (taskId) {
|
|
EventBus.$emit('i-show-task', { taskId });
|
|
}
|
|
}
|
|
},
|
|
|
|
async trySelectMostSuitableProject() {
|
|
if (this.projects.length === 0) {
|
|
if (this.$route.path !== '/project/new') {
|
|
await this.$router.push({ path: '/project/new' });
|
|
}
|
|
return;
|
|
}
|
|
|
|
let projectId;
|
|
|
|
if (this.projectId) {
|
|
projectId = this.projectId;
|
|
}
|
|
|
|
if ((projectId == null || !this.projects.some((p) => p.id === projectId))
|
|
&& localStorage.getItem('projectId')) {
|
|
projectId = parseInt(localStorage.getItem('projectId'), 10);
|
|
}
|
|
|
|
if (projectId == null || !this.projects.some((p) => p.id === projectId)) {
|
|
projectId = this.projects[0].id;
|
|
}
|
|
|
|
if (projectId != null) {
|
|
await this.selectProject(projectId);
|
|
}
|
|
},
|
|
|
|
async selectProject(projectId) {
|
|
this.userRole = (await axios({
|
|
method: 'get',
|
|
url: `/api/project/${projectId}/role`,
|
|
responseType: 'json',
|
|
})).data;
|
|
|
|
localStorage.setItem('projectId', projectId);
|
|
if (this.projectId === projectId) {
|
|
return;
|
|
}
|
|
|
|
await this.$router.push({ path: `/project/${projectId}` });
|
|
},
|
|
|
|
async loadProjects() {
|
|
this.projects = (await axios({
|
|
method: 'get',
|
|
url: '/api/projects',
|
|
responseType: 'json',
|
|
})).data;
|
|
},
|
|
|
|
async loadUserInfo() {
|
|
if (!this.isAuthenticated) {
|
|
return;
|
|
}
|
|
|
|
this.user = (await axios({
|
|
method: 'get',
|
|
url: '/api/user',
|
|
responseType: 'json',
|
|
})).data;
|
|
|
|
this.systemInfo = (await axios({
|
|
method: 'get',
|
|
url: '/api/info',
|
|
responseType: 'json',
|
|
})).data;
|
|
},
|
|
|
|
getProjectColor(projectData) {
|
|
const projectIndex = this.projects.length
|
|
- this.projects.findIndex((p) => p.id === projectData.id);
|
|
return PROJECT_COLORS[projectIndex % PROJECT_COLORS.length];
|
|
},
|
|
|
|
getProjectInitials(projectData) {
|
|
const parts = projectData.name.split(/\s/);
|
|
if (parts.length >= 2) {
|
|
return `${parts[0][0]}${parts[1][0]}`.toUpperCase();
|
|
}
|
|
return parts[0].substr(0, 2).toUpperCase();
|
|
},
|
|
|
|
async signOut() {
|
|
this.snackbar = false;
|
|
this.snackbarColor = '';
|
|
this.snackbarText = '';
|
|
|
|
socket.stop();
|
|
|
|
(await axios({
|
|
method: 'post',
|
|
url: '/api/auth/logout',
|
|
responseType: 'json',
|
|
}));
|
|
|
|
if (this.$route.path !== '/auth/login') {
|
|
await this.$router.push({ path: '/auth/login' });
|
|
this.state = 'success';
|
|
}
|
|
},
|
|
|
|
refreshPage() {
|
|
const { location } = document;
|
|
document.location = location;
|
|
},
|
|
},
|
|
};
|
|
</script>
|