mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2025-12-30 02:43:09 +01:00
Add Dashboards (#41)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<v-app class="background">
|
||||
<v-navigation-drawer dark permanent :mini-variant="mini" :expand-on-hover="mini" app color="statusbar">
|
||||
<v-list>
|
||||
<v-list-item class="px-2" :to="{ name: 'Dashboard' }">
|
||||
<v-list-item class="px-2" :to="{ name: 'Home' }">
|
||||
<v-list-item-avatar rounded="0">
|
||||
<v-img src="/flask_white.svg" :width="40"></v-img>
|
||||
</v-list-item-avatar>
|
||||
@@ -182,6 +182,7 @@ export default Vue.extend({
|
||||
},
|
||||
internal: function (): Array<any> {
|
||||
return [
|
||||
{ icon: "mdi-view-dashboard", name: "Dashboards", to: "DashboardList", role: "admin:dashboard:write" },
|
||||
{ icon: "mdi-check-bold", name: "Open Tasks", to: "TaskList", count: this.$store.state.task_count },
|
||||
]
|
||||
},
|
||||
|
||||
@@ -290,6 +290,50 @@ export interface Context {
|
||||
*/
|
||||
'ticket'?: TicketResponse;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface Dashboard
|
||||
*/
|
||||
export interface Dashboard {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Dashboard
|
||||
*/
|
||||
'name': string;
|
||||
/**
|
||||
*
|
||||
* @type {Array<Widget>}
|
||||
* @memberof Dashboard
|
||||
*/
|
||||
'widgets': Array<Widget>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface DashboardResponse
|
||||
*/
|
||||
export interface DashboardResponse {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof DashboardResponse
|
||||
*/
|
||||
'id': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof DashboardResponse
|
||||
*/
|
||||
'name': string;
|
||||
/**
|
||||
*
|
||||
* @type {Array<Widget>}
|
||||
* @memberof DashboardResponse
|
||||
*/
|
||||
'widgets': Array<Widget>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -2279,6 +2323,52 @@ export interface UserResponse {
|
||||
*/
|
||||
'roles': Array<string>;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
* @interface Widget
|
||||
*/
|
||||
export interface Widget {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Widget
|
||||
*/
|
||||
'aggregation': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Widget
|
||||
*/
|
||||
'filter'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Widget
|
||||
*/
|
||||
'name': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Widget
|
||||
*/
|
||||
'type': WidgetTypeEnum;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Widget
|
||||
*/
|
||||
'width': number;
|
||||
}
|
||||
|
||||
export const WidgetTypeEnum = {
|
||||
Bar: 'bar',
|
||||
Line: 'line',
|
||||
Pie: 'pie'
|
||||
} as const;
|
||||
|
||||
export type WidgetTypeEnum = typeof WidgetTypeEnum[keyof typeof WidgetTypeEnum];
|
||||
|
||||
|
||||
/**
|
||||
* AutomationsApi - axios parameter creator
|
||||
@@ -2657,6 +2747,461 @@ export class AutomationsApi extends BaseAPI {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DashboardsApi - axios parameter creator
|
||||
* @export
|
||||
*/
|
||||
export const DashboardsApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary Create a new dashboard
|
||||
* @param {Dashboard} template New template
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createDashboard: async (template: Dashboard, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'template' is not null or undefined
|
||||
assertParamExists('createDashboard', 'template', template)
|
||||
const localVarPath = `/dashboards`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(template, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get widget data
|
||||
* @param {string} aggregation Aggregation
|
||||
* @param {string} [filter] Filter
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
dashboardData: async (aggregation: string, filter?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'aggregation' is not null or undefined
|
||||
assertParamExists('dashboardData', 'aggregation', aggregation)
|
||||
const localVarPath = `/dashboard/data`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
if (aggregation !== undefined) {
|
||||
localVarQueryParameter['aggregation'] = aggregation;
|
||||
}
|
||||
|
||||
if (filter !== undefined) {
|
||||
localVarQueryParameter['filter'] = filter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Delete a dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteDashboard: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('deleteDashboard', 'id', id)
|
||||
const localVarPath = `/dashboards/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get a single dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getDashboard: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('getDashboard', 'id', id)
|
||||
const localVarPath = `/dashboards/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary List dashboards
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listDashboards: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/dashboards`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Update an existing dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {Dashboard} dashboard Dashboard object that needs to be added
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateDashboard: async (id: string, dashboard: Dashboard, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('updateDashboard', 'id', id)
|
||||
// verify required parameter 'dashboard' is not null or undefined
|
||||
assertParamExists('updateDashboard', 'dashboard', dashboard)
|
||||
const localVarPath = `/dashboards/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(dashboard, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DashboardsApi - functional programming interface
|
||||
* @export
|
||||
*/
|
||||
export const DashboardsApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = DashboardsApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary Create a new dashboard
|
||||
* @param {Dashboard} template New template
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async createDashboard(template: Dashboard, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DashboardResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.createDashboard(template, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get widget data
|
||||
* @param {string} aggregation Aggregation
|
||||
* @param {string} [filter] Filter
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async dashboardData(aggregation: string, filter?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.dashboardData(aggregation, filter, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Delete a dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async deleteDashboard(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteDashboard(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get a single dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getDashboard(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DashboardResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getDashboard(id, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary List dashboards
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listDashboards(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<DashboardResponse>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listDashboards(options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Update an existing dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {Dashboard} dashboard Dashboard object that needs to be added
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async updateDashboard(id: string, dashboard: Dashboard, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DashboardResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.updateDashboard(id, dashboard, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DashboardsApi - factory interface
|
||||
* @export
|
||||
*/
|
||||
export const DashboardsApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||
const localVarFp = DashboardsApiFp(configuration)
|
||||
return {
|
||||
/**
|
||||
*
|
||||
* @summary Create a new dashboard
|
||||
* @param {Dashboard} template New template
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createDashboard(template: Dashboard, options?: any): AxiosPromise<DashboardResponse> {
|
||||
return localVarFp.createDashboard(template, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get widget data
|
||||
* @param {string} aggregation Aggregation
|
||||
* @param {string} [filter] Filter
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
dashboardData(aggregation: string, filter?: string, options?: any): AxiosPromise<object> {
|
||||
return localVarFp.dashboardData(aggregation, filter, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Delete a dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteDashboard(id: string, options?: any): AxiosPromise<void> {
|
||||
return localVarFp.deleteDashboard(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Get a single dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getDashboard(id: string, options?: any): AxiosPromise<DashboardResponse> {
|
||||
return localVarFp.getDashboard(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary List dashboards
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listDashboards(options?: any): AxiosPromise<Array<DashboardResponse>> {
|
||||
return localVarFp.listDashboards(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
*
|
||||
* @summary Update an existing dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {Dashboard} dashboard Dashboard object that needs to be added
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
updateDashboard(id: string, dashboard: Dashboard, options?: any): AxiosPromise<DashboardResponse> {
|
||||
return localVarFp.updateDashboard(id, dashboard, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* DashboardsApi - object-oriented interface
|
||||
* @export
|
||||
* @class DashboardsApi
|
||||
* @extends {BaseAPI}
|
||||
*/
|
||||
export class DashboardsApi extends BaseAPI {
|
||||
/**
|
||||
*
|
||||
* @summary Create a new dashboard
|
||||
* @param {Dashboard} template New template
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public createDashboard(template: Dashboard, options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).createDashboard(template, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary Get widget data
|
||||
* @param {string} aggregation Aggregation
|
||||
* @param {string} [filter] Filter
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public dashboardData(aggregation: string, filter?: string, options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).dashboardData(aggregation, filter, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary Delete a dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public deleteDashboard(id: string, options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).deleteDashboard(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary Get a single dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public getDashboard(id: string, options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).getDashboard(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary List dashboards
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public listDashboards(options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).listDashboards(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @summary Update an existing dashboard
|
||||
* @param {string} id Dashboard ID
|
||||
* @param {Dashboard} dashboard Dashboard object that needs to be added
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof DashboardsApi
|
||||
*/
|
||||
public updateDashboard(id: string, dashboard: Dashboard, options?: AxiosRequestConfig) {
|
||||
return DashboardsApiFp(this.configuration).updateDashboard(id, dashboard, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* GraphApi - axios parameter creator
|
||||
* @export
|
||||
|
||||
@@ -68,19 +68,26 @@ const v = new Vue({
|
||||
}).$mount("#app");
|
||||
|
||||
axios.interceptors.response.use(
|
||||
response => response,
|
||||
// response => response,
|
||||
response => {
|
||||
lodash.unset(response.data, 'notoast');
|
||||
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
error => {
|
||||
console.log(error)
|
||||
if (error.response.data && 'title' in error.response.data && 'detail' in error.response.data) {
|
||||
const problem = error.response.data as Problem;
|
||||
v.$store.dispatch("alertError", { name: problem.title, detail: problem.detail });
|
||||
return Promise.reject(error);
|
||||
if (!lodash.has(error.response.data, 'notoast')) {
|
||||
if (error.response.data && 'title' in error.response.data && 'detail' in error.response.data) {
|
||||
const problem = error.response.data as Problem;
|
||||
v.$store.dispatch("alertError", { name: problem.title, detail: problem.detail });
|
||||
return Promise.reject(error);
|
||||
}
|
||||
if (error.response.data && 'error' in error.response.data) {
|
||||
v.$store.dispatch("alertError", { name: "Error", detail: error.response.data.error });
|
||||
return Promise.reject(error);
|
||||
}
|
||||
v.$store.dispatch("alertError", { name: "Error", detail: JSON.stringify(error.response.data) });
|
||||
}
|
||||
if (error.response.data && 'error' in error.response.data) {
|
||||
v.$store.dispatch("alertError", { name: "Error", detail: error.response.data.error });
|
||||
return Promise.reject(error);
|
||||
}
|
||||
v.$store.dispatch("alertError", { name: "Error", detail: JSON.stringify(error.response.data) });
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -15,13 +15,15 @@ import Rule from "../views/Rule.vue";
|
||||
import RuleList from "../views/RuleList.vue";
|
||||
import Template from "../views/Template.vue";
|
||||
import TemplateList from "../views/TemplateList.vue";
|
||||
import Dashboard from "../views/Dashboard.vue";
|
||||
import DashboardList from "../views/DashboardList.vue";
|
||||
import API from "../views/API.vue";
|
||||
import User from '../views/User.vue';
|
||||
import UserList from "@/views/UserList.vue";
|
||||
import Job from '../views/Job.vue';
|
||||
import JobList from "@/views/JobList.vue";
|
||||
import GroupList from "@/views/GroupList.vue";
|
||||
import Dashboard from "@/views/Dashboard.vue";
|
||||
import Home from "@/views/Home.vue";
|
||||
import Group from "@/views/Group.vue";
|
||||
import TicketType from '../views/TicketType.vue';
|
||||
import TicketTypeList from "@/views/TicketTypeList.vue";
|
||||
@@ -59,10 +61,10 @@ const routes: Array<RouteConfig> = [
|
||||
},
|
||||
|
||||
{
|
||||
path: "/dashboard",
|
||||
name: "Dashboard",
|
||||
component: Dashboard,
|
||||
meta: { title: "Dashboard" },
|
||||
path: "/home",
|
||||
name: "Home",
|
||||
component: Home,
|
||||
meta: { title: "Home" },
|
||||
},
|
||||
|
||||
{
|
||||
@@ -227,6 +229,21 @@ const routes: Array<RouteConfig> = [
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
path: "/dashboards",
|
||||
name: "DashboardList",
|
||||
component: DashboardList,
|
||||
meta: { title: "Dashboards" },
|
||||
children: [
|
||||
{
|
||||
path: ":id",
|
||||
name: "Dashboard",
|
||||
component: Dashboard,
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: "/settings",
|
||||
name: "Settings",
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
SettingsApi,
|
||||
SettingsApiFactory,
|
||||
JobsApi,
|
||||
JobsApiFactory,
|
||||
JobsApiFactory, DashboardsApiFactory, DashboardsApi,
|
||||
} from "@/client";
|
||||
|
||||
const config = new Configuration({
|
||||
@@ -56,7 +56,8 @@ export const API: TicketsApi &
|
||||
SettingsApi &
|
||||
TickettypesApi &
|
||||
JobsApi &
|
||||
TasksApi = Object.assign(
|
||||
TasksApi &
|
||||
DashboardsApi = Object.assign(
|
||||
{},
|
||||
TicketsApiFactory(config),
|
||||
PlaybooksApiFactory(config),
|
||||
@@ -74,5 +75,6 @@ export const API: TicketsApi &
|
||||
TickettypesApiFactory(config),
|
||||
TasksApiFactory(config),
|
||||
SettingsApiFactory(config),
|
||||
JobsApiFactory(config)
|
||||
JobsApiFactory(config),
|
||||
DashboardsApiFactory(config)
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Vue from "vue";
|
||||
import Vuex, {ActionContext} from "vuex";
|
||||
import {API} from "@/services/api";
|
||||
import {UserData, TicketList, Settings, UserResponse, SettingsResponse} from "@/client";
|
||||
import {UserData, TicketList, UserResponse, SettingsResponse} from "@/client";
|
||||
import {AxiosResponse} from "axios";
|
||||
import {Alert} from "@/types/types";
|
||||
import {templateStore} from "./modules/templates";
|
||||
@@ -19,7 +19,7 @@ export default new Vuex.Store({
|
||||
counts: {} as Record<string, number>,
|
||||
task_count: 0 as number,
|
||||
|
||||
settings: {} as Settings,
|
||||
settings: {} as SettingsResponse,
|
||||
userdata: {} as UserData,
|
||||
|
||||
alert: {} as Alert,
|
||||
@@ -46,7 +46,7 @@ export default new Vuex.Store({
|
||||
setUserData (state, msg: UserData) {
|
||||
state.userdata = msg
|
||||
},
|
||||
setSettings (state, msg: Settings) {
|
||||
setSettings (state, msg: SettingsResponse) {
|
||||
state.settings = msg
|
||||
},
|
||||
setAlert (state, msg: Alert) {
|
||||
|
||||
@@ -1,110 +1,123 @@
|
||||
<template>
|
||||
<v-main>
|
||||
<div v-if="dashboard">
|
||||
<h2 class="d-flex">
|
||||
<span v-if="!editmode">{{ dashboard.name }}</span>
|
||||
<v-text-field v-else v-model="dashboard.name" outlined dense class="mb-0" hide-details></v-text-field>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn v-if="editmode" small outlined @click="addWidget" class="mr-1">
|
||||
<v-icon>mdi-plus</v-icon>
|
||||
Add Widget
|
||||
</v-btn>
|
||||
<v-btn v-if="editmode" small outlined @click="save" class="mr-1">
|
||||
<v-icon>mdi-content-save</v-icon>
|
||||
Save
|
||||
</v-btn>
|
||||
<v-btn v-if="editmode && $route.params.id !== 'new'" small outlined @click="cancel">
|
||||
<v-icon>mdi-cancel</v-icon>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn v-if="!editmode" small outlined @click="edit">
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
Edit
|
||||
</v-btn>
|
||||
</h2>
|
||||
|
||||
<v-row>
|
||||
<v-col v-if="statistics" cols="12" lg="7">
|
||||
<v-row>
|
||||
<v-col cols="4">
|
||||
<v-subheader>Unassigned tickets</v-subheader>
|
||||
<span style="font-size: 60pt; text-align: center; display: block">
|
||||
<router-link :to="{
|
||||
name: 'TicketList',
|
||||
params: { query: 'status == \'open\' AND !owner' }
|
||||
}">
|
||||
{{ statistics.unassigned }}
|
||||
</router-link>
|
||||
</span>
|
||||
<v-subheader>Your tickets</v-subheader>
|
||||
<span style="font-size: 60pt; text-align: center; display: block">
|
||||
<router-link :to="{
|
||||
name: 'TicketList',
|
||||
params: { query: 'status == \'open\' AND owner == \'' + $store.state.user.id + '\'' }
|
||||
}">
|
||||
{{ $store.state.user.id in statistics.open_tickets_per_user ? statistics.open_tickets_per_user[$store.state.user.id] : 0 }}
|
||||
</router-link>
|
||||
</span>
|
||||
</v-col>
|
||||
<v-col cols="8">
|
||||
<v-subheader>Open tickets per owner</v-subheader>
|
||||
<bar-chart
|
||||
v-if="open_tickets_per_user"
|
||||
:chart-data="open_tickets_per_user"
|
||||
:styles="{
|
||||
width: '100%',
|
||||
'max-height': '400px',
|
||||
position: 'relative'
|
||||
}"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: undefined,
|
||||
scales: { xAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] },
|
||||
onClick: clickUser,
|
||||
hover: {
|
||||
onHover: function(e) {
|
||||
var point = this.getElementAtEvent(e);
|
||||
if (point.length) e.target.style.cursor = 'pointer';
|
||||
else e.target.style.cursor = 'default';
|
||||
}
|
||||
}
|
||||
}"
|
||||
></bar-chart>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="7">
|
||||
<v-subheader>Tickets created per week</v-subheader>
|
||||
<v-col v-for="(widget, index) in dashboard.widgets" :key="index" :cols="widget.width">
|
||||
<v-card class="mb-2">
|
||||
<v-card-title>
|
||||
<span v-if="!editmode">{{ widget.name }}</span>
|
||||
<v-text-field v-else outlined dense hide-details v-model="widget.name" class="mr-1"></v-text-field>
|
||||
<v-btn v-if="editmode" outlined @click="removeWidget(index)">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
Remove
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text v-if="editmode">
|
||||
<v-row>
|
||||
<v-col cols="8">
|
||||
<v-select label="Type" v-model="widget.type" :items="['line', 'bar', 'pie']"></v-select>
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<v-text-field label="Width" type="number" v-model="widget.width"></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-text-field label="Aggregation" v-model="widget.aggregation"></v-text-field>
|
||||
<v-text-field label="Filter" v-model="widget.filter" clearable></v-text-field>
|
||||
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text v-if="data[index] === null">
|
||||
{{ widgetErrors[index] }}
|
||||
</v-card-text>
|
||||
<div v-else>
|
||||
<line-chart
|
||||
v-if="tickets_per_week"
|
||||
:chart-data="tickets_per_week"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: undefined,
|
||||
scales: { yAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] }
|
||||
}"
|
||||
v-if="widget.type === 'line' && data[index]"
|
||||
:chart-data="data[index]"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: false,
|
||||
scales: { yAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] }
|
||||
}"
|
||||
>
|
||||
</line-chart>
|
||||
</v-col>
|
||||
<v-col cols="5">
|
||||
<v-subheader>Ticket Types</v-subheader>
|
||||
|
||||
<pie-chart
|
||||
v-if="tickets_per_type"
|
||||
:chart-data="tickets_per_type"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
onClick: clickPie,
|
||||
hover: {
|
||||
onHover: function(e) {
|
||||
var point = this.getElementAtEvent(e);
|
||||
if (point.length) e.target.style.cursor = 'pointer';
|
||||
else e.target.style.cursor = 'default';
|
||||
}
|
||||
}
|
||||
}"
|
||||
v-if="widget.type === 'pie' && data[index]"
|
||||
:chart-data="data[index]"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
}"
|
||||
>
|
||||
</pie-chart>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
<v-col cols="12" lg="5">
|
||||
<TicketList :type="this.$route.params.type" @click="open"></TicketList>
|
||||
|
||||
<bar-chart
|
||||
v-if="widget.type === 'bar' && data[index]"
|
||||
:chart-data="data[index]"
|
||||
:styles="{
|
||||
width: '100%',
|
||||
'max-height': '400px',
|
||||
position: 'relative'
|
||||
}"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: false,
|
||||
scales: { xAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] },
|
||||
}"
|
||||
></bar-chart>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import {DashboardResponse, Widget} from "@/client";
|
||||
import { API } from "@/services/api";
|
||||
import {createHash} from "crypto";
|
||||
import {colors} from "@/plugins/vuetify";
|
||||
import LineChart from "../components/charts/Line";
|
||||
import BarChart from "../components/charts/Bar";
|
||||
import PieChart from "../components/charts/Doughnut";
|
||||
import { API } from "@/services/api";
|
||||
import {Statistics, TicketResponse} from "@/client";
|
||||
import {DateTime} from "luxon";
|
||||
import { colors } from "@/plugins/vuetify";
|
||||
import TicketList from "@/components/TicketList.vue";
|
||||
import { createHash } from "crypto";
|
||||
import {ChartData} from "chart.js";
|
||||
import {AxiosError, AxiosTransformer} from "axios";
|
||||
|
||||
interface State {
|
||||
dashboard?: DashboardResponse;
|
||||
undodashboard?: DashboardResponse;
|
||||
data: Record<string, any>;
|
||||
editmode: boolean;
|
||||
widgetErrors: Record<number, string>;
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
name: "Dashboard",
|
||||
@@ -112,108 +125,130 @@ export default Vue.extend({
|
||||
LineChart,
|
||||
BarChart,
|
||||
PieChart,
|
||||
TicketList
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statistics: (undefined as unknown) as Statistics
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tickets_per_type: function () {
|
||||
let data = { labels: [] as Array<string>, datasets: [{ backgroundColor: [] as Array<string>, data: [] as Array<number> }] }
|
||||
this.lodash.forEach(this.statistics.tickets_per_type, (count, type) => {
|
||||
data.labels.push(type);
|
||||
data.datasets[0].data.push(count);
|
||||
|
||||
data.datasets[0].backgroundColor.push(this.color(type));
|
||||
})
|
||||
return data
|
||||
data: (): State => ({
|
||||
dashboard: undefined,
|
||||
undodashboard: undefined,
|
||||
data: {},
|
||||
editmode: false,
|
||||
widgetErrors: {},
|
||||
}),
|
||||
watch: {
|
||||
$route: function () {
|
||||
this.loadDashboard();
|
||||
},
|
||||
open_tickets_per_user: function () {
|
||||
let data = { labels: [] as Array<string>, datasets: [{ backgroundColor: [] as Array<string>, data: [] as Array<number> }] }
|
||||
this.lodash.forEach(this.statistics.open_tickets_per_user, (count, user) => {
|
||||
if (!user) {
|
||||
data.labels.push("unassigned");
|
||||
} else {
|
||||
data.labels.push(user);
|
||||
}
|
||||
data.datasets[0].data.push(count);
|
||||
data.datasets[0].backgroundColor.push(this.color(user));
|
||||
})
|
||||
return data
|
||||
},
|
||||
tickets_per_week: function () {
|
||||
let data = {labels: [] as Array<string>, datasets: [{backgroundColor: [] as Array<string>, data: [] as Array<number> }]}
|
||||
this.lodash.forEach(this.weeks(), (week) => {
|
||||
data.labels.push(week);
|
||||
if (week in this.statistics.tickets_per_week) {
|
||||
data.datasets[0].data.push(this.statistics.tickets_per_week[week]);
|
||||
} else {
|
||||
data.datasets[0].data.push(0);
|
||||
}
|
||||
data.datasets[0].backgroundColor.push("#607d8b");
|
||||
})
|
||||
return data
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open: function (ticket: TicketResponse) {
|
||||
if (ticket.id === undefined) {
|
||||
return;
|
||||
edit: function () {
|
||||
this.undodashboard = this.lodash.cloneDeep(this.dashboard);
|
||||
this.editmode = true;
|
||||
},
|
||||
save: function () {
|
||||
if (!this.dashboard) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
name: "Ticket",
|
||||
params: {type: '-', id: ticket.id.toString()}
|
||||
});
|
||||
},
|
||||
clickUser: function (evt, elem) {
|
||||
let owner = this.open_tickets_per_user.labels[elem[0]._index];
|
||||
let query = 'status == \'open\' AND owner == \'' + owner + '\'';
|
||||
let widgets = [] as Array<Widget>;
|
||||
this.lodash.forEach(this.dashboard.widgets, (widget) => {
|
||||
widget.width = this.lodash.toInteger(widget.width);
|
||||
if (!widget.filter) {
|
||||
this.lodash.unset(widget, "filter")
|
||||
}
|
||||
widgets.push(widget);
|
||||
})
|
||||
this.dashboard.widgets = widgets;
|
||||
|
||||
if (owner == 'unassigned') {
|
||||
query = 'status == \'open\' AND !owner';
|
||||
if (this.$route.params.id === 'new') {
|
||||
API.createDashboard(this.dashboard).then((response) => {
|
||||
this.loadWidgetData(response.data.widgets);
|
||||
|
||||
this.dashboard = response.data;
|
||||
this.editmode = false;
|
||||
|
||||
this.$router.push({ name: "Dashboard", params: { id: response.data.id }})
|
||||
})
|
||||
} else {
|
||||
API.updateDashboard(this.dashboard.id, this.dashboard).then((response) => {
|
||||
this.loadWidgetData(response.data.widgets);
|
||||
|
||||
this.dashboard = response.data;
|
||||
this.editmode = false;
|
||||
})
|
||||
}
|
||||
},
|
||||
cancel: function () {
|
||||
this.dashboard = this.lodash.cloneDeep(this.undodashboard);
|
||||
this.editmode = false;
|
||||
},
|
||||
addWidget: function () {
|
||||
if (!this.dashboard) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
name: "TicketList",
|
||||
params: {query: query}
|
||||
});
|
||||
this.dashboard.widgets.push({name: "new widget", width: 6, aggregation: "", type: "line"})
|
||||
},
|
||||
clickPie: function (evt, elem) {
|
||||
this.$router.push({
|
||||
name: "TicketList",
|
||||
params: {type: this.tickets_per_type.labels[elem[0]._index]}
|
||||
});
|
||||
removeWidget: function (id: number) {
|
||||
if (!this.dashboard) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(id);
|
||||
let widgets = this.lodash.cloneDeep(this.dashboard.widgets);
|
||||
this.lodash.pullAt(widgets, [id]);
|
||||
Vue.set(this.dashboard, "widgets", widgets);
|
||||
},
|
||||
loadDashboard: function () {
|
||||
if (this.$route.params.id === 'new') {
|
||||
this.dashboard = {
|
||||
name: "New dashboard",
|
||||
widgets: [{name: "new widget", width: 6, aggregation: "", type: "line"}],
|
||||
} as DashboardResponse
|
||||
this.editmode = true;
|
||||
} else {
|
||||
API.getDashboard(this.$route.params.id).then((response) => {
|
||||
this.loadWidgetData(response.data.widgets);
|
||||
|
||||
this.dashboard = response.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
loadWidgetData: function (widgets: Array<Widget>) {
|
||||
this.lodash.forEach(widgets, (widget: Widget, index: number) => {
|
||||
let widgetErrors = {};
|
||||
let defaultTransformers = this.axios.defaults.transformResponse as AxiosTransformer[]
|
||||
let transformResponse = defaultTransformers.concat((data) => {
|
||||
data.notoast = true;
|
||||
return data
|
||||
});
|
||||
API.dashboardData(widget.aggregation, widget.filter, {transformResponse: transformResponse}).then((response) => {
|
||||
let d = { labels: [], datasets: [{data: [], backgroundColor: []}] } as ChartData;
|
||||
this.lodash.forEach(response.data, (v: any, k: string) => {
|
||||
// @ts-expect-error T2532
|
||||
d.labels.push(k)
|
||||
// @ts-expect-error T2532
|
||||
d.datasets[0].data.push(v)
|
||||
|
||||
if (widget.type !== 'line') {
|
||||
// @ts-expect-error T2532
|
||||
d.datasets[0].backgroundColor.push(this.color(this.lodash.toString(v)));
|
||||
}
|
||||
})
|
||||
|
||||
Vue.set(this.data, index, d);
|
||||
}).catch((err: AxiosError) => {
|
||||
widgetErrors[index] = this.lodash.toString(err.response?.data.error);
|
||||
Vue.set(this.data, index, null);
|
||||
})
|
||||
Vue.set(this, 'widgetErrors', widgetErrors);
|
||||
})
|
||||
},
|
||||
color: function (s: string): string {
|
||||
let pos = createHash('md5').update(s).digest().readUInt32BE(0) % colors.length;
|
||||
return colors[pos];
|
||||
},
|
||||
fillData() {
|
||||
API.getStatistics().then(response => {
|
||||
this.statistics = response.data;
|
||||
});
|
||||
},
|
||||
weeks: function () {
|
||||
let w = [] as Array<string>;
|
||||
for (let i = 0; i < 53; i++) {
|
||||
w.push(DateTime.utc().minus({ weeks: i }).toFormat("kkkk-WW"))
|
||||
}
|
||||
this.lodash.reverse(w);
|
||||
return w
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fillData();
|
||||
this.loadDashboard();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
canvas {
|
||||
position: relative !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
64
ui/src/views/DashboardList.vue
Normal file
64
ui/src/views/DashboardList.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<v-main style="min-height: 100vh;">
|
||||
<List
|
||||
:items="dashboards"
|
||||
routername="Dashboard"
|
||||
itemid="id"
|
||||
itemname="name"
|
||||
singular="Dashboard"
|
||||
plural="Dashboards"
|
||||
writepermission="admin:dashboard:write"
|
||||
@delete="deleteDashboard"
|
||||
></List>
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
|
||||
import {Dashboard} from "@/client";
|
||||
import {API} from "@/services/api";
|
||||
import List from "../components/List.vue";
|
||||
|
||||
interface State {
|
||||
dashboards: Array<Dashboard>;
|
||||
}
|
||||
|
||||
export default Vue.extend({
|
||||
name: "DashboardList",
|
||||
components: {List},
|
||||
data: (): State => ({
|
||||
dashboards: [],
|
||||
}),
|
||||
methods: {
|
||||
loadDashboards() {
|
||||
API.listDashboards().then((response) => {
|
||||
this.dashboards = response.data;
|
||||
});
|
||||
},
|
||||
deleteDashboard(id: string) {
|
||||
API.deleteDashboard(id).then(() => {
|
||||
this.loadDashboards();
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadDashboards();
|
||||
|
||||
this.$store.subscribeAction((action, state) => {
|
||||
if (!action.payload || !(this.lodash.has(action.payload, "ids")) || !action.payload["ids"]) {
|
||||
return
|
||||
}
|
||||
let reload = false;
|
||||
Vue.lodash.forEach(action.payload["ids"], (id) => {
|
||||
if (this.lodash.startsWith(id, "dashboard/")) {
|
||||
reload = true;
|
||||
}
|
||||
});
|
||||
if (reload) {
|
||||
this.loadDashboards()
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
</script>
|
||||
219
ui/src/views/Home.vue
Normal file
219
ui/src/views/Home.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<v-main>
|
||||
<v-row>
|
||||
<v-col v-if="statistics" cols="12" lg="7">
|
||||
<v-row>
|
||||
<v-col cols="4">
|
||||
<v-subheader>Unassigned tickets</v-subheader>
|
||||
<span style="font-size: 60pt; text-align: center; display: block">
|
||||
<router-link :to="{
|
||||
name: 'TicketList',
|
||||
params: { query: 'status == \'open\' AND !owner' }
|
||||
}">
|
||||
{{ statistics.unassigned }}
|
||||
</router-link>
|
||||
</span>
|
||||
<v-subheader>Your tickets</v-subheader>
|
||||
<span style="font-size: 60pt; text-align: center; display: block">
|
||||
<router-link :to="{
|
||||
name: 'TicketList',
|
||||
params: { query: 'status == \'open\' AND owner == \'' + $store.state.user.id + '\'' }
|
||||
}">
|
||||
{{ $store.state.user.id in statistics.open_tickets_per_user ? statistics.open_tickets_per_user[$store.state.user.id] : 0 }}
|
||||
</router-link>
|
||||
</span>
|
||||
</v-col>
|
||||
<v-col cols="8">
|
||||
<v-subheader>Open tickets per owner</v-subheader>
|
||||
<bar-chart
|
||||
v-if="open_tickets_per_user"
|
||||
:chart-data="open_tickets_per_user"
|
||||
:styles="{
|
||||
width: '100%',
|
||||
'max-height': '400px',
|
||||
position: 'relative'
|
||||
}"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: undefined,
|
||||
scales: { xAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] },
|
||||
onClick: clickUser,
|
||||
hover: {
|
||||
onHover: function(e) {
|
||||
var point = this.getElementAtEvent(e);
|
||||
if (point.length) e.target.style.cursor = 'pointer';
|
||||
else e.target.style.cursor = 'default';
|
||||
}
|
||||
}
|
||||
}"
|
||||
></bar-chart>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="7">
|
||||
<v-subheader>Tickets created per week</v-subheader>
|
||||
<line-chart
|
||||
v-if="tickets_per_week"
|
||||
:chart-data="tickets_per_week"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: undefined,
|
||||
scales: { yAxes: [ { ticks: { beginAtZero: true, precision: 0 } } ] }
|
||||
}"
|
||||
>
|
||||
</line-chart>
|
||||
</v-col>
|
||||
<v-col cols="5">
|
||||
<v-subheader>Ticket Types</v-subheader>
|
||||
<pie-chart
|
||||
v-if="tickets_per_type"
|
||||
:chart-data="tickets_per_type"
|
||||
:styles="{ width: '100%', position: 'relative' }"
|
||||
:chart-options="{
|
||||
onClick: clickPie,
|
||||
hover: {
|
||||
onHover: function(e) {
|
||||
var point = this.getElementAtEvent(e);
|
||||
if (point.length) e.target.style.cursor = 'pointer';
|
||||
else e.target.style.cursor = 'default';
|
||||
}
|
||||
}
|
||||
}"
|
||||
>
|
||||
</pie-chart>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
<v-col cols="12" lg="5">
|
||||
<TicketList :type="this.$route.params.type" @click="open"></TicketList>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import LineChart from "../components/charts/Line";
|
||||
import BarChart from "../components/charts/Bar";
|
||||
import PieChart from "../components/charts/Doughnut";
|
||||
import { API } from "@/services/api";
|
||||
import {Statistics, TicketResponse} from "@/client";
|
||||
import {DateTime} from "luxon";
|
||||
import { colors } from "@/plugins/vuetify";
|
||||
import TicketList from "@/components/TicketList.vue";
|
||||
import { createHash } from "crypto";
|
||||
|
||||
export default Vue.extend({
|
||||
name: "Home",
|
||||
components: {
|
||||
LineChart,
|
||||
BarChart,
|
||||
PieChart,
|
||||
TicketList
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statistics: (undefined as unknown) as Statistics
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
tickets_per_type: function () {
|
||||
let data = { labels: [] as Array<string>, datasets: [{ backgroundColor: [] as Array<string>, data: [] as Array<number> }] }
|
||||
this.lodash.forEach(this.statistics.tickets_per_type, (count, type) => {
|
||||
data.labels.push(type);
|
||||
data.datasets[0].data.push(count);
|
||||
|
||||
data.datasets[0].backgroundColor.push(this.color(type));
|
||||
})
|
||||
return data
|
||||
},
|
||||
open_tickets_per_user: function () {
|
||||
let data = { labels: [] as Array<string>, datasets: [{ backgroundColor: [] as Array<string>, data: [] as Array<number> }] }
|
||||
this.lodash.forEach(this.statistics.open_tickets_per_user, (count, user) => {
|
||||
if (!user) {
|
||||
data.labels.push("unassigned");
|
||||
} else {
|
||||
data.labels.push(user);
|
||||
}
|
||||
data.datasets[0].data.push(count);
|
||||
data.datasets[0].backgroundColor.push(this.color(user));
|
||||
})
|
||||
return data
|
||||
},
|
||||
tickets_per_week: function () {
|
||||
let data = {labels: [] as Array<string>, datasets: [{backgroundColor: [] as Array<string>, data: [] as Array<number> }]}
|
||||
this.lodash.forEach(this.weeks(), (week) => {
|
||||
data.labels.push(week);
|
||||
if (week in this.statistics.tickets_per_week) {
|
||||
data.datasets[0].data.push(this.statistics.tickets_per_week[week]);
|
||||
} else {
|
||||
data.datasets[0].data.push(0);
|
||||
}
|
||||
data.datasets[0].backgroundColor.push("#607d8b");
|
||||
})
|
||||
return data
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open: function (ticket: TicketResponse) {
|
||||
if (ticket.id === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
name: "Ticket",
|
||||
params: {type: '-', id: ticket.id.toString()}
|
||||
});
|
||||
},
|
||||
clickUser: function (evt, elem) {
|
||||
let owner = this.open_tickets_per_user.labels[elem[0]._index];
|
||||
let query = 'status == \'open\' AND owner == \'' + owner + '\'';
|
||||
|
||||
if (owner == 'unassigned') {
|
||||
query = 'status == \'open\' AND !owner';
|
||||
}
|
||||
|
||||
this.$router.push({
|
||||
name: "TicketList",
|
||||
params: {query: query}
|
||||
});
|
||||
},
|
||||
clickPie: function (evt, elem) {
|
||||
this.$router.push({
|
||||
name: "TicketList",
|
||||
params: {type: this.tickets_per_type.labels[elem[0]._index]}
|
||||
});
|
||||
},
|
||||
color: function (s: string): string {
|
||||
let pos = createHash('md5').update(s).digest().readUInt32BE(0) % colors.length;
|
||||
return colors[pos];
|
||||
},
|
||||
fillData() {
|
||||
API.getStatistics().then(response => {
|
||||
this.statistics = response.data;
|
||||
});
|
||||
},
|
||||
weeks: function () {
|
||||
let w = [] as Array<string>;
|
||||
for (let i = 0; i < 53; i++) {
|
||||
w.push(DateTime.utc().minus({ weeks: i }).toFormat("kkkk-WW"))
|
||||
}
|
||||
this.lodash.reverse(w);
|
||||
return w
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fillData();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
canvas {
|
||||
position: relative !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user