refactor: remove pocketbase (#1138)

This commit is contained in:
Jonas Plum
2025-09-02 21:58:08 +02:00
committed by GitHub
parent f28c238135
commit eba2615ec0
435 changed files with 42677 additions and 4730 deletions
+41
View File
@@ -1,5 +1,46 @@
<script setup lang="ts">
import Toaster from '@/components/ui/toast/Toaster.vue'
import { onMounted, watch } from 'vue'
import { RouterView } from 'vue-router'
import { useAuthStore } from '@/store/auth'
const authStore = useAuthStore()
const fetchUser = () => {
if (!authStore.token) {
authStore.setUser(undefined)
authStore.setPermissions([])
return
}
fetch('/auth/user', { headers: { Authorization: `Bearer ${authStore.token}` } }).then(
(response) => {
if (response.ok) {
response.json().then((user) => {
if (user) {
authStore.setUser(user.user)
authStore.setPermissions(user.permissions)
} else {
authStore.setUser(undefined)
authStore.setPermissions([])
}
})
}
}
)
}
onMounted(() => {
fetchUser()
})
watch(
() => authStore.token,
() => fetchUser(),
{ immediate: true }
)
</script>
<template>
+16
View File
@@ -0,0 +1,16 @@
import { Configuration, DefaultApi } from '@/client'
import { useAuthStore } from '@/store/auth'
export function useAPI() {
const authStore = useAuthStore()
return new DefaultApi(
new Configuration({
basePath: 'http://localhost:8090/api',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${authStore.token}`
}
})
)
}
+18
View File
@@ -33,6 +33,15 @@
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
--vis-tooltip-background-color: none !important;
--vis-tooltip-border-color: none !important;
--vis-tooltip-text-color: none !important;
@@ -75,6 +84,15 @@
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 35.5 91.7% 32.9%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
+23
View File
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
+57
View File
@@ -0,0 +1,57 @@
.openapi-generator-ignore
apis/DefaultApi.ts
apis/index.ts
index.ts
models/Comment.ts
models/CommentUpdate.ts
models/Config.ts
models/DashboardCounts.ts
models/EmailTemplate.ts
models/ExtendedComment.ts
models/ExtendedTask.ts
models/ExtendedTicket.ts
models/Feature.ts
models/FileUpdate.ts
models/Group.ts
models/GroupRelation.ts
models/GroupUpdate.ts
models/GroupUser.ts
models/Link.ts
models/LinkUpdate.ts
models/ModelError.ts
models/ModelFile.ts
models/NewComment.ts
models/NewFeature.ts
models/NewFile.ts
models/NewGroup.ts
models/NewLink.ts
models/NewReaction.ts
models/NewTask.ts
models/NewTicket.ts
models/NewTimelineEntry.ts
models/NewType.ts
models/NewUser.ts
models/NewWebhook.ts
models/Reaction.ts
models/ReactionUpdate.ts
models/Settings.ts
models/SettingsMeta.ts
models/SettingsSmtp.ts
models/Sidebar.ts
models/Table.ts
models/Task.ts
models/TaskUpdate.ts
models/Ticket.ts
models/TicketSearch.ts
models/TicketUpdate.ts
models/TimelineEntry.ts
models/TimelineEntryUpdate.ts
models/Type.ts
models/TypeUpdate.ts
models/User.ts
models/UserGroup.ts
models/UserUpdate.ts
models/Webhook.ts
models/WebhookUpdate.ts
models/index.ts
runtime.ts
+1
View File
@@ -0,0 +1 @@
7.14.0
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
/* tslint:disable */
/* eslint-disable */
export * from './DefaultApi'
+5
View File
@@ -0,0 +1,5 @@
/* tslint:disable */
/* eslint-disable */
export * from './runtime'
export * from './apis/index'
export * from './models/index'
+111
View File
@@ -0,0 +1,111 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Comment
*/
export interface Comment {
/**
*
* @type {string}
* @memberof Comment
*/
id: string
/**
*
* @type {string}
* @memberof Comment
*/
ticket: string
/**
*
* @type {string}
* @memberof Comment
*/
author: string
/**
*
* @type {string}
* @memberof Comment
*/
message: string
/**
*
* @type {Date}
* @memberof Comment
*/
created: Date
/**
*
* @type {Date}
* @memberof Comment
*/
updated: Date
}
/**
* Check if a given object implements the Comment interface.
*/
export function instanceOfComment(value: object): value is Comment {
if (!('id' in value) || value['id'] === undefined) return false
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('author' in value) || value['author'] === undefined) return false
if (!('message' in value) || value['message'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function CommentFromJSON(json: any): Comment {
return CommentFromJSONTyped(json, false)
}
export function CommentFromJSONTyped(json: any, ignoreDiscriminator: boolean): Comment {
if (json == null) {
return json
}
return {
id: json['id'],
ticket: json['ticket'],
author: json['author'],
message: json['message'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function CommentToJSON(json: any): Comment {
return CommentToJSONTyped(json, false)
}
export function CommentToJSONTyped(
value?: Comment | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
ticket: value['ticket'],
author: value['author'],
message: value['message'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+73
View File
@@ -0,0 +1,73 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface CommentUpdate
*/
export interface CommentUpdate {
/**
*
* @type {string}
* @memberof CommentUpdate
*/
author?: string
/**
*
* @type {string}
* @memberof CommentUpdate
*/
message?: string
}
/**
* Check if a given object implements the CommentUpdate interface.
*/
export function instanceOfCommentUpdate(value: object): value is CommentUpdate {
return true
}
export function CommentUpdateFromJSON(json: any): CommentUpdate {
return CommentUpdateFromJSONTyped(json, false)
}
export function CommentUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): CommentUpdate {
if (json == null) {
return json
}
return {
author: json['author'] == null ? undefined : json['author'],
message: json['message'] == null ? undefined : json['message']
}
}
export function CommentUpdateToJSON(json: any): CommentUpdate {
return CommentUpdateToJSONTyped(json, false)
}
export function CommentUpdateToJSONTyped(
value?: CommentUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
author: value['author'],
message: value['message']
}
}
+86
View File
@@ -0,0 +1,86 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
import type { Table } from './Table'
import { TableFromJSON, TableFromJSONTyped, TableToJSON, TableToJSONTyped } from './Table'
/**
*
* @export
* @interface Config
*/
export interface Config {
/**
*
* @type {Array<string>}
* @memberof Config
*/
flags: Array<string>
/**
*
* @type {Array<string>}
* @memberof Config
*/
permissions: Array<string>
/**
*
* @type {Array<Table>}
* @memberof Config
*/
tables: Array<Table>
}
/**
* Check if a given object implements the Config interface.
*/
export function instanceOfConfig(value: object): value is Config {
if (!('flags' in value) || value['flags'] === undefined) return false
if (!('permissions' in value) || value['permissions'] === undefined) return false
if (!('tables' in value) || value['tables'] === undefined) return false
return true
}
export function ConfigFromJSON(json: any): Config {
return ConfigFromJSONTyped(json, false)
}
export function ConfigFromJSONTyped(json: any, ignoreDiscriminator: boolean): Config {
if (json == null) {
return json
}
return {
flags: json['flags'],
permissions: json['permissions'],
tables: (json['tables'] as Array<any>).map(TableFromJSON)
}
}
export function ConfigToJSON(json: any): Config {
return ConfigToJSONTyped(json, false)
}
export function ConfigToJSONTyped(
value?: Config | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
flags: value['flags'],
permissions: value['permissions'],
tables: (value['tables'] as Array<any>).map(TableToJSON)
}
}
+78
View File
@@ -0,0 +1,78 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface DashboardCounts
*/
export interface DashboardCounts {
/**
*
* @type {string}
* @memberof DashboardCounts
*/
id: string
/**
*
* @type {number}
* @memberof DashboardCounts
*/
count: number
}
/**
* Check if a given object implements the DashboardCounts interface.
*/
export function instanceOfDashboardCounts(value: object): value is DashboardCounts {
if (!('id' in value) || value['id'] === undefined) return false
if (!('count' in value) || value['count'] === undefined) return false
return true
}
export function DashboardCountsFromJSON(json: any): DashboardCounts {
return DashboardCountsFromJSONTyped(json, false)
}
export function DashboardCountsFromJSONTyped(
json: any,
ignoreDiscriminator: boolean
): DashboardCounts {
if (json == null) {
return json
}
return {
id: json['id'],
count: json['count']
}
}
export function DashboardCountsToJSON(json: any): DashboardCounts {
return DashboardCountsToJSONTyped(json, false)
}
export function DashboardCountsToJSONTyped(
value?: DashboardCounts | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
count: value['count']
}
}
+75
View File
@@ -0,0 +1,75 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface EmailTemplate
*/
export interface EmailTemplate {
/**
*
* @type {string}
* @memberof EmailTemplate
*/
body: string
/**
*
* @type {string}
* @memberof EmailTemplate
*/
subject: string
}
/**
* Check if a given object implements the EmailTemplate interface.
*/
export function instanceOfEmailTemplate(value: object): value is EmailTemplate {
if (!('body' in value) || value['body'] === undefined) return false
if (!('subject' in value) || value['subject'] === undefined) return false
return true
}
export function EmailTemplateFromJSON(json: any): EmailTemplate {
return EmailTemplateFromJSONTyped(json, false)
}
export function EmailTemplateFromJSONTyped(json: any, ignoreDiscriminator: boolean): EmailTemplate {
if (json == null) {
return json
}
return {
body: json['body'],
subject: json['subject']
}
}
export function EmailTemplateToJSON(json: any): EmailTemplate {
return EmailTemplateToJSONTyped(json, false)
}
export function EmailTemplateToJSONTyped(
value?: EmailTemplate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
body: value['body'],
subject: value['subject']
}
}
+123
View File
@@ -0,0 +1,123 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface ExtendedComment
*/
export interface ExtendedComment {
/**
*
* @type {string}
* @memberof ExtendedComment
*/
id: string
/**
*
* @type {string}
* @memberof ExtendedComment
*/
ticket: string
/**
*
* @type {string}
* @memberof ExtendedComment
*/
author: string
/**
*
* @type {string}
* @memberof ExtendedComment
*/
message: string
/**
*
* @type {string}
* @memberof ExtendedComment
*/
authorName: string
/**
*
* @type {Date}
* @memberof ExtendedComment
*/
created: Date
/**
*
* @type {Date}
* @memberof ExtendedComment
*/
updated: Date
}
/**
* Check if a given object implements the ExtendedComment interface.
*/
export function instanceOfExtendedComment(value: object): value is ExtendedComment {
if (!('id' in value) || value['id'] === undefined) return false
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('author' in value) || value['author'] === undefined) return false
if (!('message' in value) || value['message'] === undefined) return false
if (!('authorName' in value) || value['authorName'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function ExtendedCommentFromJSON(json: any): ExtendedComment {
return ExtendedCommentFromJSONTyped(json, false)
}
export function ExtendedCommentFromJSONTyped(
json: any,
ignoreDiscriminator: boolean
): ExtendedComment {
if (json == null) {
return json
}
return {
id: json['id'],
ticket: json['ticket'],
author: json['author'],
message: json['message'],
authorName: json['author_name'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function ExtendedCommentToJSON(json: any): ExtendedComment {
return ExtendedCommentToJSONTyped(json, false)
}
export function ExtendedCommentToJSONTyped(
value?: ExtendedComment | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
ticket: value['ticket'],
author: value['author'],
message: value['message'],
author_name: value['authorName'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+145
View File
@@ -0,0 +1,145 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface ExtendedTask
*/
export interface ExtendedTask {
/**
*
* @type {string}
* @memberof ExtendedTask
*/
id: string
/**
*
* @type {string}
* @memberof ExtendedTask
*/
ticket: string
/**
*
* @type {string}
* @memberof ExtendedTask
*/
name: string
/**
*
* @type {boolean}
* @memberof ExtendedTask
*/
open: boolean
/**
*
* @type {string}
* @memberof ExtendedTask
*/
owner?: string
/**
*
* @type {string}
* @memberof ExtendedTask
*/
ownerName?: string
/**
*
* @type {string}
* @memberof ExtendedTask
*/
ticketName: string
/**
*
* @type {string}
* @memberof ExtendedTask
*/
ticketType: string
/**
*
* @type {Date}
* @memberof ExtendedTask
*/
created: Date
/**
*
* @type {Date}
* @memberof ExtendedTask
*/
updated: Date
}
/**
* Check if a given object implements the ExtendedTask interface.
*/
export function instanceOfExtendedTask(value: object): value is ExtendedTask {
if (!('id' in value) || value['id'] === undefined) return false
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('open' in value) || value['open'] === undefined) return false
if (!('ticketName' in value) || value['ticketName'] === undefined) return false
if (!('ticketType' in value) || value['ticketType'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function ExtendedTaskFromJSON(json: any): ExtendedTask {
return ExtendedTaskFromJSONTyped(json, false)
}
export function ExtendedTaskFromJSONTyped(json: any, ignoreDiscriminator: boolean): ExtendedTask {
if (json == null) {
return json
}
return {
id: json['id'],
ticket: json['ticket'],
name: json['name'],
open: json['open'],
owner: json['owner'] == null ? undefined : json['owner'],
ownerName: json['owner_name'] == null ? undefined : json['owner_name'],
ticketName: json['ticket_name'],
ticketType: json['ticket_type'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function ExtendedTaskToJSON(json: any): ExtendedTask {
return ExtendedTaskToJSONTyped(json, false)
}
export function ExtendedTaskToJSONTyped(
value?: ExtendedTask | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
ticket: value['ticket'],
name: value['name'],
open: value['open'],
owner: value['owner'],
owner_name: value['ownerName'],
ticket_name: value['ticketName'],
ticket_type: value['ticketType'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+183
View File
@@ -0,0 +1,183 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface ExtendedTicket
*/
export interface ExtendedTicket {
/**
*
* @type {string}
* @memberof ExtendedTicket
*/
id: string
/**
*
* @type {string}
* @memberof ExtendedTicket
*/
type: string
/**
*
* @type {string}
* @memberof ExtendedTicket
*/
name: string
/**
*
* @type {string}
* @memberof ExtendedTicket
*/
description: string
/**
*
* @type {boolean}
* @memberof ExtendedTicket
*/
open: boolean
/**
*
* @type {string}
* @memberof ExtendedTicket
*/
owner?: string
/**
*
* @type {string}
* @memberof ExtendedTicket
*/
resolution?: string
/**
*
* @type {object}
* @memberof ExtendedTicket
*/
schema: object
/**
*
* @type {object}
* @memberof ExtendedTicket
*/
state: object
/**
*
* @type {string}
* @memberof ExtendedTicket
*/
ownerName?: string
/**
*
* @type {string}
* @memberof ExtendedTicket
*/
typeSingular: string
/**
*
* @type {string}
* @memberof ExtendedTicket
*/
typePlural: string
/**
*
* @type {Date}
* @memberof ExtendedTicket
*/
created: Date
/**
*
* @type {Date}
* @memberof ExtendedTicket
*/
updated: Date
}
/**
* Check if a given object implements the ExtendedTicket interface.
*/
export function instanceOfExtendedTicket(value: object): value is ExtendedTicket {
if (!('id' in value) || value['id'] === undefined) return false
if (!('type' in value) || value['type'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('description' in value) || value['description'] === undefined) return false
if (!('open' in value) || value['open'] === undefined) return false
if (!('schema' in value) || value['schema'] === undefined) return false
if (!('state' in value) || value['state'] === undefined) return false
if (!('typeSingular' in value) || value['typeSingular'] === undefined) return false
if (!('typePlural' in value) || value['typePlural'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function ExtendedTicketFromJSON(json: any): ExtendedTicket {
return ExtendedTicketFromJSONTyped(json, false)
}
export function ExtendedTicketFromJSONTyped(
json: any,
ignoreDiscriminator: boolean
): ExtendedTicket {
if (json == null) {
return json
}
return {
id: json['id'],
type: json['type'],
name: json['name'],
description: json['description'],
open: json['open'],
owner: json['owner'] == null ? undefined : json['owner'],
resolution: json['resolution'] == null ? undefined : json['resolution'],
schema: json['schema'],
state: json['state'],
ownerName: json['owner_name'] == null ? undefined : json['owner_name'],
typeSingular: json['type_singular'],
typePlural: json['type_plural'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function ExtendedTicketToJSON(json: any): ExtendedTicket {
return ExtendedTicketToJSONTyped(json, false)
}
export function ExtendedTicketToJSONTyped(
value?: ExtendedTicket | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
type: value['type'],
name: value['name'],
description: value['description'],
open: value['open'],
owner: value['owner'],
resolution: value['resolution'],
schema: value['schema'],
state: value['state'],
owner_name: value['ownerName'],
type_singular: value['typeSingular'],
type_plural: value['typePlural'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+93
View File
@@ -0,0 +1,93 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Feature
*/
export interface Feature {
/**
*
* @type {string}
* @memberof Feature
*/
id: string
/**
*
* @type {string}
* @memberof Feature
*/
name: string
/**
*
* @type {Date}
* @memberof Feature
*/
created: Date
/**
*
* @type {Date}
* @memberof Feature
*/
updated: Date
}
/**
* Check if a given object implements the Feature interface.
*/
export function instanceOfFeature(value: object): value is Feature {
if (!('id' in value) || value['id'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function FeatureFromJSON(json: any): Feature {
return FeatureFromJSONTyped(json, false)
}
export function FeatureFromJSONTyped(json: any, ignoreDiscriminator: boolean): Feature {
if (json == null) {
return json
}
return {
id: json['id'],
name: json['name'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function FeatureToJSON(json: any): Feature {
return FeatureToJSONTyped(json, false)
}
export function FeatureToJSONTyped(
value?: Feature | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
name: value['name'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+65
View File
@@ -0,0 +1,65 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface FileUpdate
*/
export interface FileUpdate {
/**
*
* @type {string}
* @memberof FileUpdate
*/
name?: string
}
/**
* Check if a given object implements the FileUpdate interface.
*/
export function instanceOfFileUpdate(value: object): value is FileUpdate {
return true
}
export function FileUpdateFromJSON(json: any): FileUpdate {
return FileUpdateFromJSONTyped(json, false)
}
export function FileUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): FileUpdate {
if (json == null) {
return json
}
return {
name: json['name'] == null ? undefined : json['name']
}
}
export function FileUpdateToJSON(json: any): FileUpdate {
return FileUpdateToJSONTyped(json, false)
}
export function FileUpdateToJSONTyped(
value?: FileUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
name: value['name']
}
}
+99
View File
@@ -0,0 +1,99 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Group
*/
export interface Group {
/**
*
* @type {string}
* @memberof Group
*/
id: string
/**
*
* @type {string}
* @memberof Group
*/
name: string
/**
*
* @type {Array<string>}
* @memberof Group
*/
permissions: Array<string>
/**
*
* @type {Date}
* @memberof Group
*/
created: Date
/**
*
* @type {Date}
* @memberof Group
*/
updated: Date
}
/**
* Check if a given object implements the Group interface.
*/
export function instanceOfGroup(value: object): value is Group {
if (!('id' in value) || value['id'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('permissions' in value) || value['permissions'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function GroupFromJSON(json: any): Group {
return GroupFromJSONTyped(json, false)
}
export function GroupFromJSONTyped(json: any, ignoreDiscriminator: boolean): Group {
if (json == null) {
return json
}
return {
id: json['id'],
name: json['name'],
permissions: json['permissions'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function GroupToJSON(json: any): Group {
return GroupToJSONTyped(json, false)
}
export function GroupToJSONTyped(value?: Group | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value
}
return {
id: value['id'],
name: value['name'],
permissions: value['permissions'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+66
View File
@@ -0,0 +1,66 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface GroupRelation
*/
export interface GroupRelation {
/**
*
* @type {string}
* @memberof GroupRelation
*/
groupId: string
}
/**
* Check if a given object implements the GroupRelation interface.
*/
export function instanceOfGroupRelation(value: object): value is GroupRelation {
if (!('groupId' in value) || value['groupId'] === undefined) return false
return true
}
export function GroupRelationFromJSON(json: any): GroupRelation {
return GroupRelationFromJSONTyped(json, false)
}
export function GroupRelationFromJSONTyped(json: any, ignoreDiscriminator: boolean): GroupRelation {
if (json == null) {
return json
}
return {
groupId: json['group_id']
}
}
export function GroupRelationToJSON(json: any): GroupRelation {
return GroupRelationToJSONTyped(json, false)
}
export function GroupRelationToJSONTyped(
value?: GroupRelation | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
group_id: value['groupId']
}
}
+73
View File
@@ -0,0 +1,73 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface GroupUpdate
*/
export interface GroupUpdate {
/**
*
* @type {string}
* @memberof GroupUpdate
*/
name?: string
/**
*
* @type {Array<string>}
* @memberof GroupUpdate
*/
permissions?: Array<string>
}
/**
* Check if a given object implements the GroupUpdate interface.
*/
export function instanceOfGroupUpdate(value: object): value is GroupUpdate {
return true
}
export function GroupUpdateFromJSON(json: any): GroupUpdate {
return GroupUpdateFromJSONTyped(json, false)
}
export function GroupUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): GroupUpdate {
if (json == null) {
return json
}
return {
name: json['name'] == null ? undefined : json['name'],
permissions: json['permissions'] == null ? undefined : json['permissions']
}
}
export function GroupUpdateToJSON(json: any): GroupUpdate {
return GroupUpdateToJSONTyped(json, false)
}
export function GroupUpdateToJSONTyped(
value?: GroupUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
name: value['name'],
permissions: value['permissions']
}
}
+157
View File
@@ -0,0 +1,157 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface GroupUser
*/
export interface GroupUser {
/**
*
* @type {string}
* @memberof GroupUser
*/
id: string
/**
*
* @type {string}
* @memberof GroupUser
*/
username: string
/**
*
* @type {string}
* @memberof GroupUser
*/
avatar?: string
/**
*
* @type {string}
* @memberof GroupUser
*/
email?: string
/**
*
* @type {Date}
* @memberof GroupUser
*/
lastResetSentAt?: Date
/**
*
* @type {Date}
* @memberof GroupUser
*/
lastVerificationSentAt?: Date
/**
*
* @type {string}
* @memberof GroupUser
*/
name?: string
/**
*
* @type {boolean}
* @memberof GroupUser
*/
active: boolean
/**
*
* @type {string}
* @memberof GroupUser
*/
type: string
/**
*
* @type {Date}
* @memberof GroupUser
*/
created: Date
/**
*
* @type {Date}
* @memberof GroupUser
*/
updated: Date
}
/**
* Check if a given object implements the GroupUser interface.
*/
export function instanceOfGroupUser(value: object): value is GroupUser {
if (!('id' in value) || value['id'] === undefined) return false
if (!('username' in value) || value['username'] === undefined) return false
if (!('active' in value) || value['active'] === undefined) return false
if (!('type' in value) || value['type'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function GroupUserFromJSON(json: any): GroupUser {
return GroupUserFromJSONTyped(json, false)
}
export function GroupUserFromJSONTyped(json: any, ignoreDiscriminator: boolean): GroupUser {
if (json == null) {
return json
}
return {
id: json['id'],
username: json['username'],
avatar: json['avatar'] == null ? undefined : json['avatar'],
email: json['email'] == null ? undefined : json['email'],
lastResetSentAt:
json['lastResetSentAt'] == null ? undefined : new Date(json['lastResetSentAt']),
lastVerificationSentAt:
json['lastVerificationSentAt'] == null ? undefined : new Date(json['lastVerificationSentAt']),
name: json['name'] == null ? undefined : json['name'],
active: json['active'],
type: json['type'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function GroupUserToJSON(json: any): GroupUser {
return GroupUserToJSONTyped(json, false)
}
export function GroupUserToJSONTyped(
value?: GroupUser | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
username: value['username'],
avatar: value['avatar'],
email: value['email'],
lastResetSentAt:
value['lastResetSentAt'] == null ? undefined : value['lastResetSentAt'].toISOString(),
lastVerificationSentAt:
value['lastVerificationSentAt'] == null
? undefined
: value['lastVerificationSentAt'].toISOString(),
name: value['name'],
active: value['active'],
type: value['type'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+108
View File
@@ -0,0 +1,108 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Link
*/
export interface Link {
/**
*
* @type {string}
* @memberof Link
*/
id: string
/**
*
* @type {string}
* @memberof Link
*/
ticket: string
/**
*
* @type {string}
* @memberof Link
*/
name: string
/**
*
* @type {string}
* @memberof Link
*/
url: string
/**
*
* @type {Date}
* @memberof Link
*/
created: Date
/**
*
* @type {Date}
* @memberof Link
*/
updated: Date
}
/**
* Check if a given object implements the Link interface.
*/
export function instanceOfLink(value: object): value is Link {
if (!('id' in value) || value['id'] === undefined) return false
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('url' in value) || value['url'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function LinkFromJSON(json: any): Link {
return LinkFromJSONTyped(json, false)
}
export function LinkFromJSONTyped(json: any, ignoreDiscriminator: boolean): Link {
if (json == null) {
return json
}
return {
id: json['id'],
ticket: json['ticket'],
name: json['name'],
url: json['url'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function LinkToJSON(json: any): Link {
return LinkToJSONTyped(json, false)
}
export function LinkToJSONTyped(value?: Link | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value
}
return {
id: value['id'],
ticket: value['ticket'],
name: value['name'],
url: value['url'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+73
View File
@@ -0,0 +1,73 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface LinkUpdate
*/
export interface LinkUpdate {
/**
*
* @type {string}
* @memberof LinkUpdate
*/
name?: string
/**
*
* @type {string}
* @memberof LinkUpdate
*/
url?: string
}
/**
* Check if a given object implements the LinkUpdate interface.
*/
export function instanceOfLinkUpdate(value: object): value is LinkUpdate {
return true
}
export function LinkUpdateFromJSON(json: any): LinkUpdate {
return LinkUpdateFromJSONTyped(json, false)
}
export function LinkUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): LinkUpdate {
if (json == null) {
return json
}
return {
name: json['name'] == null ? undefined : json['name'],
url: json['url'] == null ? undefined : json['url']
}
}
export function LinkUpdateToJSON(json: any): LinkUpdate {
return LinkUpdateToJSONTyped(json, false)
}
export function LinkUpdateToJSONTyped(
value?: LinkUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
name: value['name'],
url: value['url']
}
}
+84
View File
@@ -0,0 +1,84 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface ModelError
*/
export interface ModelError {
/**
*
* @type {number}
* @memberof ModelError
*/
status: number
/**
*
* @type {string}
* @memberof ModelError
*/
error: string
/**
*
* @type {string}
* @memberof ModelError
*/
message: string
}
/**
* Check if a given object implements the ModelError interface.
*/
export function instanceOfModelError(value: object): value is ModelError {
if (!('status' in value) || value['status'] === undefined) return false
if (!('error' in value) || value['error'] === undefined) return false
if (!('message' in value) || value['message'] === undefined) return false
return true
}
export function ModelErrorFromJSON(json: any): ModelError {
return ModelErrorFromJSONTyped(json, false)
}
export function ModelErrorFromJSONTyped(json: any, ignoreDiscriminator: boolean): ModelError {
if (json == null) {
return json
}
return {
status: json['status'],
error: json['error'],
message: json['message']
}
}
export function ModelErrorToJSON(json: any): ModelError {
return ModelErrorToJSONTyped(json, false)
}
export function ModelErrorToJSONTyped(
value?: ModelError | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
status: value['status'],
error: value['error'],
message: value['message']
}
}
+111
View File
@@ -0,0 +1,111 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface ModelFile
*/
export interface ModelFile {
/**
*
* @type {string}
* @memberof ModelFile
*/
id: string
/**
*
* @type {string}
* @memberof ModelFile
*/
ticket: string
/**
*
* @type {string}
* @memberof ModelFile
*/
name: string
/**
*
* @type {number}
* @memberof ModelFile
*/
size: number
/**
*
* @type {Date}
* @memberof ModelFile
*/
created: Date
/**
*
* @type {Date}
* @memberof ModelFile
*/
updated: Date
}
/**
* Check if a given object implements the ModelFile interface.
*/
export function instanceOfModelFile(value: object): value is ModelFile {
if (!('id' in value) || value['id'] === undefined) return false
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('size' in value) || value['size'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function ModelFileFromJSON(json: any): ModelFile {
return ModelFileFromJSONTyped(json, false)
}
export function ModelFileFromJSONTyped(json: any, ignoreDiscriminator: boolean): ModelFile {
if (json == null) {
return json
}
return {
id: json['id'],
ticket: json['ticket'],
name: json['name'],
size: json['size'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function ModelFileToJSON(json: any): ModelFile {
return ModelFileToJSONTyped(json, false)
}
export function ModelFileToJSONTyped(
value?: ModelFile | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
ticket: value['ticket'],
name: value['name'],
size: value['size'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+84
View File
@@ -0,0 +1,84 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewComment
*/
export interface NewComment {
/**
*
* @type {string}
* @memberof NewComment
*/
ticket: string
/**
*
* @type {string}
* @memberof NewComment
*/
author: string
/**
*
* @type {string}
* @memberof NewComment
*/
message: string
}
/**
* Check if a given object implements the NewComment interface.
*/
export function instanceOfNewComment(value: object): value is NewComment {
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('author' in value) || value['author'] === undefined) return false
if (!('message' in value) || value['message'] === undefined) return false
return true
}
export function NewCommentFromJSON(json: any): NewComment {
return NewCommentFromJSONTyped(json, false)
}
export function NewCommentFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewComment {
if (json == null) {
return json
}
return {
ticket: json['ticket'],
author: json['author'],
message: json['message']
}
}
export function NewCommentToJSON(json: any): NewComment {
return NewCommentToJSONTyped(json, false)
}
export function NewCommentToJSONTyped(
value?: NewComment | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
ticket: value['ticket'],
author: value['author'],
message: value['message']
}
}
+66
View File
@@ -0,0 +1,66 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewFeature
*/
export interface NewFeature {
/**
*
* @type {string}
* @memberof NewFeature
*/
name: string
}
/**
* Check if a given object implements the NewFeature interface.
*/
export function instanceOfNewFeature(value: object): value is NewFeature {
if (!('name' in value) || value['name'] === undefined) return false
return true
}
export function NewFeatureFromJSON(json: any): NewFeature {
return NewFeatureFromJSONTyped(json, false)
}
export function NewFeatureFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewFeature {
if (json == null) {
return json
}
return {
name: json['name']
}
}
export function NewFeatureToJSON(json: any): NewFeature {
return NewFeatureToJSONTyped(json, false)
}
export function NewFeatureToJSONTyped(
value?: NewFeature | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
name: value['name']
}
}
+84
View File
@@ -0,0 +1,84 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewFile
*/
export interface NewFile {
/**
*
* @type {string}
* @memberof NewFile
*/
ticket: string
/**
*
* @type {string}
* @memberof NewFile
*/
blob: string
/**
*
* @type {string}
* @memberof NewFile
*/
name: string
}
/**
* Check if a given object implements the NewFile interface.
*/
export function instanceOfNewFile(value: object): value is NewFile {
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('blob' in value) || value['blob'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
return true
}
export function NewFileFromJSON(json: any): NewFile {
return NewFileFromJSONTyped(json, false)
}
export function NewFileFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewFile {
if (json == null) {
return json
}
return {
ticket: json['ticket'],
blob: json['blob'],
name: json['name']
}
}
export function NewFileToJSON(json: any): NewFile {
return NewFileToJSONTyped(json, false)
}
export function NewFileToJSONTyped(
value?: NewFile | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
ticket: value['ticket'],
blob: value['blob'],
name: value['name']
}
}
+75
View File
@@ -0,0 +1,75 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewGroup
*/
export interface NewGroup {
/**
*
* @type {string}
* @memberof NewGroup
*/
name: string
/**
*
* @type {Array<string>}
* @memberof NewGroup
*/
permissions: Array<string>
}
/**
* Check if a given object implements the NewGroup interface.
*/
export function instanceOfNewGroup(value: object): value is NewGroup {
if (!('name' in value) || value['name'] === undefined) return false
if (!('permissions' in value) || value['permissions'] === undefined) return false
return true
}
export function NewGroupFromJSON(json: any): NewGroup {
return NewGroupFromJSONTyped(json, false)
}
export function NewGroupFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewGroup {
if (json == null) {
return json
}
return {
name: json['name'],
permissions: json['permissions']
}
}
export function NewGroupToJSON(json: any): NewGroup {
return NewGroupToJSONTyped(json, false)
}
export function NewGroupToJSONTyped(
value?: NewGroup | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
name: value['name'],
permissions: value['permissions']
}
}
+84
View File
@@ -0,0 +1,84 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewLink
*/
export interface NewLink {
/**
*
* @type {string}
* @memberof NewLink
*/
ticket: string
/**
*
* @type {string}
* @memberof NewLink
*/
name: string
/**
*
* @type {string}
* @memberof NewLink
*/
url: string
}
/**
* Check if a given object implements the NewLink interface.
*/
export function instanceOfNewLink(value: object): value is NewLink {
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('url' in value) || value['url'] === undefined) return false
return true
}
export function NewLinkFromJSON(json: any): NewLink {
return NewLinkFromJSONTyped(json, false)
}
export function NewLinkFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewLink {
if (json == null) {
return json
}
return {
ticket: json['ticket'],
name: json['name'],
url: json['url']
}
}
export function NewLinkToJSON(json: any): NewLink {
return NewLinkToJSONTyped(json, false)
}
export function NewLinkToJSONTyped(
value?: NewLink | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
ticket: value['ticket'],
name: value['name'],
url: value['url']
}
}
+102
View File
@@ -0,0 +1,102 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewReaction
*/
export interface NewReaction {
/**
*
* @type {string}
* @memberof NewReaction
*/
name: string
/**
*
* @type {string}
* @memberof NewReaction
*/
action: string
/**
*
* @type {object}
* @memberof NewReaction
*/
actiondata: object
/**
*
* @type {string}
* @memberof NewReaction
*/
trigger: string
/**
*
* @type {object}
* @memberof NewReaction
*/
triggerdata: object
}
/**
* Check if a given object implements the NewReaction interface.
*/
export function instanceOfNewReaction(value: object): value is NewReaction {
if (!('name' in value) || value['name'] === undefined) return false
if (!('action' in value) || value['action'] === undefined) return false
if (!('actiondata' in value) || value['actiondata'] === undefined) return false
if (!('trigger' in value) || value['trigger'] === undefined) return false
if (!('triggerdata' in value) || value['triggerdata'] === undefined) return false
return true
}
export function NewReactionFromJSON(json: any): NewReaction {
return NewReactionFromJSONTyped(json, false)
}
export function NewReactionFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewReaction {
if (json == null) {
return json
}
return {
name: json['name'],
action: json['action'],
actiondata: json['actiondata'],
trigger: json['trigger'],
triggerdata: json['triggerdata']
}
}
export function NewReactionToJSON(json: any): NewReaction {
return NewReactionToJSONTyped(json, false)
}
export function NewReactionToJSONTyped(
value?: NewReaction | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
name: value['name'],
action: value['action'],
actiondata: value['actiondata'],
trigger: value['trigger'],
triggerdata: value['triggerdata']
}
}
+92
View File
@@ -0,0 +1,92 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewTask
*/
export interface NewTask {
/**
*
* @type {string}
* @memberof NewTask
*/
ticket: string
/**
*
* @type {string}
* @memberof NewTask
*/
name: string
/**
*
* @type {boolean}
* @memberof NewTask
*/
open: boolean
/**
*
* @type {string}
* @memberof NewTask
*/
owner?: string
}
/**
* Check if a given object implements the NewTask interface.
*/
export function instanceOfNewTask(value: object): value is NewTask {
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('open' in value) || value['open'] === undefined) return false
return true
}
export function NewTaskFromJSON(json: any): NewTask {
return NewTaskFromJSONTyped(json, false)
}
export function NewTaskFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewTask {
if (json == null) {
return json
}
return {
ticket: json['ticket'],
name: json['name'],
open: json['open'],
owner: json['owner'] == null ? undefined : json['owner']
}
}
export function NewTaskToJSON(json: any): NewTask {
return NewTaskToJSONTyped(json, false)
}
export function NewTaskToJSONTyped(
value?: NewTask | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
ticket: value['ticket'],
name: value['name'],
open: value['open'],
owner: value['owner']
}
}
+127
View File
@@ -0,0 +1,127 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewTicket
*/
export interface NewTicket {
/**
*
* @type {string}
* @memberof NewTicket
*/
type: string
/**
*
* @type {string}
* @memberof NewTicket
*/
name: string
/**
*
* @type {string}
* @memberof NewTicket
*/
description: string
/**
*
* @type {boolean}
* @memberof NewTicket
*/
open: boolean
/**
*
* @type {string}
* @memberof NewTicket
*/
owner?: string
/**
*
* @type {string}
* @memberof NewTicket
*/
resolution?: string
/**
*
* @type {object}
* @memberof NewTicket
*/
schema: object
/**
*
* @type {object}
* @memberof NewTicket
*/
state: object
}
/**
* Check if a given object implements the NewTicket interface.
*/
export function instanceOfNewTicket(value: object): value is NewTicket {
if (!('type' in value) || value['type'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('description' in value) || value['description'] === undefined) return false
if (!('open' in value) || value['open'] === undefined) return false
if (!('schema' in value) || value['schema'] === undefined) return false
if (!('state' in value) || value['state'] === undefined) return false
return true
}
export function NewTicketFromJSON(json: any): NewTicket {
return NewTicketFromJSONTyped(json, false)
}
export function NewTicketFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewTicket {
if (json == null) {
return json
}
return {
type: json['type'],
name: json['name'],
description: json['description'],
open: json['open'],
owner: json['owner'] == null ? undefined : json['owner'],
resolution: json['resolution'] == null ? undefined : json['resolution'],
schema: json['schema'],
state: json['state']
}
}
export function NewTicketToJSON(json: any): NewTicket {
return NewTicketToJSONTyped(json, false)
}
export function NewTicketToJSONTyped(
value?: NewTicket | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
type: value['type'],
name: value['name'],
description: value['description'],
open: value['open'],
owner: value['owner'],
resolution: value['resolution'],
schema: value['schema'],
state: value['state']
}
}
+87
View File
@@ -0,0 +1,87 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewTimelineEntry
*/
export interface NewTimelineEntry {
/**
*
* @type {string}
* @memberof NewTimelineEntry
*/
ticket: string
/**
*
* @type {string}
* @memberof NewTimelineEntry
*/
message: string
/**
*
* @type {Date}
* @memberof NewTimelineEntry
*/
time: Date
}
/**
* Check if a given object implements the NewTimelineEntry interface.
*/
export function instanceOfNewTimelineEntry(value: object): value is NewTimelineEntry {
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('message' in value) || value['message'] === undefined) return false
if (!('time' in value) || value['time'] === undefined) return false
return true
}
export function NewTimelineEntryFromJSON(json: any): NewTimelineEntry {
return NewTimelineEntryFromJSONTyped(json, false)
}
export function NewTimelineEntryFromJSONTyped(
json: any,
ignoreDiscriminator: boolean
): NewTimelineEntry {
if (json == null) {
return json
}
return {
ticket: json['ticket'],
message: json['message'],
time: new Date(json['time'])
}
}
export function NewTimelineEntryToJSON(json: any): NewTimelineEntry {
return NewTimelineEntryToJSONTyped(json, false)
}
export function NewTimelineEntryToJSONTyped(
value?: NewTimelineEntry | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
ticket: value['ticket'],
message: value['message'],
time: value['time'].toISOString()
}
}
+92
View File
@@ -0,0 +1,92 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewType
*/
export interface NewType {
/**
*
* @type {string}
* @memberof NewType
*/
icon?: string
/**
*
* @type {string}
* @memberof NewType
*/
plural: string
/**
*
* @type {object}
* @memberof NewType
*/
schema: object
/**
*
* @type {string}
* @memberof NewType
*/
singular: string
}
/**
* Check if a given object implements the NewType interface.
*/
export function instanceOfNewType(value: object): value is NewType {
if (!('plural' in value) || value['plural'] === undefined) return false
if (!('schema' in value) || value['schema'] === undefined) return false
if (!('singular' in value) || value['singular'] === undefined) return false
return true
}
export function NewTypeFromJSON(json: any): NewType {
return NewTypeFromJSONTyped(json, false)
}
export function NewTypeFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewType {
if (json == null) {
return json
}
return {
icon: json['icon'] == null ? undefined : json['icon'],
plural: json['plural'],
schema: json['schema'],
singular: json['singular']
}
}
export function NewTypeToJSON(json: any): NewType {
return NewTypeToJSONTyped(json, false)
}
export function NewTypeToJSONTyped(
value?: NewType | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
icon: value['icon'],
plural: value['plural'],
schema: value['schema'],
singular: value['singular']
}
}
+99
View File
@@ -0,0 +1,99 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewUser
*/
export interface NewUser {
/**
*
* @type {string}
* @memberof NewUser
*/
username: string
/**
*
* @type {string}
* @memberof NewUser
*/
avatar?: string
/**
*
* @type {string}
* @memberof NewUser
*/
email?: string
/**
*
* @type {string}
* @memberof NewUser
*/
name?: string
/**
*
* @type {boolean}
* @memberof NewUser
*/
active: boolean
}
/**
* Check if a given object implements the NewUser interface.
*/
export function instanceOfNewUser(value: object): value is NewUser {
if (!('username' in value) || value['username'] === undefined) return false
if (!('active' in value) || value['active'] === undefined) return false
return true
}
export function NewUserFromJSON(json: any): NewUser {
return NewUserFromJSONTyped(json, false)
}
export function NewUserFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewUser {
if (json == null) {
return json
}
return {
username: json['username'],
avatar: json['avatar'] == null ? undefined : json['avatar'],
email: json['email'] == null ? undefined : json['email'],
name: json['name'] == null ? undefined : json['name'],
active: json['active']
}
}
export function NewUserToJSON(json: any): NewUser {
return NewUserToJSONTyped(json, false)
}
export function NewUserToJSONTyped(
value?: NewUser | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
username: value['username'],
avatar: value['avatar'],
email: value['email'],
name: value['name'],
active: value['active']
}
}
+84
View File
@@ -0,0 +1,84 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface NewWebhook
*/
export interface NewWebhook {
/**
*
* @type {string}
* @memberof NewWebhook
*/
name: string
/**
*
* @type {string}
* @memberof NewWebhook
*/
collection: string
/**
*
* @type {string}
* @memberof NewWebhook
*/
destination: string
}
/**
* Check if a given object implements the NewWebhook interface.
*/
export function instanceOfNewWebhook(value: object): value is NewWebhook {
if (!('name' in value) || value['name'] === undefined) return false
if (!('collection' in value) || value['collection'] === undefined) return false
if (!('destination' in value) || value['destination'] === undefined) return false
return true
}
export function NewWebhookFromJSON(json: any): NewWebhook {
return NewWebhookFromJSONTyped(json, false)
}
export function NewWebhookFromJSONTyped(json: any, ignoreDiscriminator: boolean): NewWebhook {
if (json == null) {
return json
}
return {
name: json['name'],
collection: json['collection'],
destination: json['destination']
}
}
export function NewWebhookToJSON(json: any): NewWebhook {
return NewWebhookToJSONTyped(json, false)
}
export function NewWebhookToJSONTyped(
value?: NewWebhook | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
name: value['name'],
collection: value['collection'],
destination: value['destination']
}
}
+129
View File
@@ -0,0 +1,129 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Reaction
*/
export interface Reaction {
/**
*
* @type {string}
* @memberof Reaction
*/
id: string
/**
*
* @type {string}
* @memberof Reaction
*/
name: string
/**
*
* @type {string}
* @memberof Reaction
*/
action: string
/**
*
* @type {object}
* @memberof Reaction
*/
actiondata: object
/**
*
* @type {string}
* @memberof Reaction
*/
trigger: string
/**
*
* @type {object}
* @memberof Reaction
*/
triggerdata: object
/**
*
* @type {Date}
* @memberof Reaction
*/
created: Date
/**
*
* @type {Date}
* @memberof Reaction
*/
updated: Date
}
/**
* Check if a given object implements the Reaction interface.
*/
export function instanceOfReaction(value: object): value is Reaction {
if (!('id' in value) || value['id'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('action' in value) || value['action'] === undefined) return false
if (!('actiondata' in value) || value['actiondata'] === undefined) return false
if (!('trigger' in value) || value['trigger'] === undefined) return false
if (!('triggerdata' in value) || value['triggerdata'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function ReactionFromJSON(json: any): Reaction {
return ReactionFromJSONTyped(json, false)
}
export function ReactionFromJSONTyped(json: any, ignoreDiscriminator: boolean): Reaction {
if (json == null) {
return json
}
return {
id: json['id'],
name: json['name'],
action: json['action'],
actiondata: json['actiondata'],
trigger: json['trigger'],
triggerdata: json['triggerdata'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function ReactionToJSON(json: any): Reaction {
return ReactionToJSONTyped(json, false)
}
export function ReactionToJSONTyped(
value?: Reaction | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
name: value['name'],
action: value['action'],
actiondata: value['actiondata'],
trigger: value['trigger'],
triggerdata: value['triggerdata'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+100
View File
@@ -0,0 +1,100 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface ReactionUpdate
*/
export interface ReactionUpdate {
/**
*
* @type {string}
* @memberof ReactionUpdate
*/
name?: string
/**
*
* @type {string}
* @memberof ReactionUpdate
*/
action?: string
/**
*
* @type {object}
* @memberof ReactionUpdate
*/
actiondata?: object
/**
*
* @type {string}
* @memberof ReactionUpdate
*/
trigger?: string
/**
*
* @type {object}
* @memberof ReactionUpdate
*/
triggerdata?: object
}
/**
* Check if a given object implements the ReactionUpdate interface.
*/
export function instanceOfReactionUpdate(value: object): value is ReactionUpdate {
return true
}
export function ReactionUpdateFromJSON(json: any): ReactionUpdate {
return ReactionUpdateFromJSONTyped(json, false)
}
export function ReactionUpdateFromJSONTyped(
json: any,
ignoreDiscriminator: boolean
): ReactionUpdate {
if (json == null) {
return json
}
return {
name: json['name'] == null ? undefined : json['name'],
action: json['action'] == null ? undefined : json['action'],
actiondata: json['actiondata'] == null ? undefined : json['actiondata'],
trigger: json['trigger'] == null ? undefined : json['trigger'],
triggerdata: json['triggerdata'] == null ? undefined : json['triggerdata']
}
}
export function ReactionUpdateToJSON(json: any): ReactionUpdate {
return ReactionUpdateToJSONTyped(json, false)
}
export function ReactionUpdateToJSONTyped(
value?: ReactionUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
name: value['name'],
action: value['action'],
actiondata: value['actiondata'],
trigger: value['trigger'],
triggerdata: value['triggerdata']
}
}
+89
View File
@@ -0,0 +1,89 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
import type { SettingsMeta } from './SettingsMeta'
import {
SettingsMetaFromJSON,
SettingsMetaFromJSONTyped,
SettingsMetaToJSON,
SettingsMetaToJSONTyped
} from './SettingsMeta'
import type { SettingsSmtp } from './SettingsSmtp'
import {
SettingsSmtpFromJSON,
SettingsSmtpFromJSONTyped,
SettingsSmtpToJSON,
SettingsSmtpToJSONTyped
} from './SettingsSmtp'
/**
*
* @export
* @interface Settings
*/
export interface Settings {
/**
*
* @type {SettingsMeta}
* @memberof Settings
*/
meta: SettingsMeta
/**
*
* @type {SettingsSmtp}
* @memberof Settings
*/
smtp: SettingsSmtp
}
/**
* Check if a given object implements the Settings interface.
*/
export function instanceOfSettings(value: object): value is Settings {
if (!('meta' in value) || value['meta'] === undefined) return false
if (!('smtp' in value) || value['smtp'] === undefined) return false
return true
}
export function SettingsFromJSON(json: any): Settings {
return SettingsFromJSONTyped(json, false)
}
export function SettingsFromJSONTyped(json: any, ignoreDiscriminator: boolean): Settings {
if (json == null) {
return json
}
return {
meta: SettingsMetaFromJSON(json['meta']),
smtp: SettingsSmtpFromJSON(json['smtp'])
}
}
export function SettingsToJSON(json: any): Settings {
return SettingsToJSONTyped(json, false)
}
export function SettingsToJSONTyped(
value?: Settings | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
meta: SettingsMetaToJSON(value['meta']),
smtp: SettingsSmtpToJSON(value['smtp'])
}
}
+110
View File
@@ -0,0 +1,110 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
import type { EmailTemplate } from './EmailTemplate'
import {
EmailTemplateFromJSON,
EmailTemplateFromJSONTyped,
EmailTemplateToJSON,
EmailTemplateToJSONTyped
} from './EmailTemplate'
/**
*
* @export
* @interface SettingsMeta
*/
export interface SettingsMeta {
/**
*
* @type {string}
* @memberof SettingsMeta
*/
appName: string
/**
*
* @type {string}
* @memberof SettingsMeta
*/
appUrl: string
/**
*
* @type {string}
* @memberof SettingsMeta
*/
senderName: string
/**
*
* @type {string}
* @memberof SettingsMeta
*/
senderAddress: string
/**
*
* @type {EmailTemplate}
* @memberof SettingsMeta
*/
resetPasswordTemplate: EmailTemplate
}
/**
* Check if a given object implements the SettingsMeta interface.
*/
export function instanceOfSettingsMeta(value: object): value is SettingsMeta {
if (!('appName' in value) || value['appName'] === undefined) return false
if (!('appUrl' in value) || value['appUrl'] === undefined) return false
if (!('senderName' in value) || value['senderName'] === undefined) return false
if (!('senderAddress' in value) || value['senderAddress'] === undefined) return false
if (!('resetPasswordTemplate' in value) || value['resetPasswordTemplate'] === undefined)
return false
return true
}
export function SettingsMetaFromJSON(json: any): SettingsMeta {
return SettingsMetaFromJSONTyped(json, false)
}
export function SettingsMetaFromJSONTyped(json: any, ignoreDiscriminator: boolean): SettingsMeta {
if (json == null) {
return json
}
return {
appName: json['app_name'],
appUrl: json['app_url'],
senderName: json['sender_name'],
senderAddress: json['sender_address'],
resetPasswordTemplate: EmailTemplateFromJSON(json['reset_password_template'])
}
}
export function SettingsMetaToJSON(json: any): SettingsMeta {
return SettingsMetaToJSONTyped(json, false)
}
export function SettingsMetaToJSONTyped(
value?: SettingsMeta | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
app_name: value['appName'],
app_url: value['appUrl'],
sender_name: value['senderName'],
sender_address: value['senderAddress'],
reset_password_template: EmailTemplateToJSON(value['resetPasswordTemplate'])
}
}
+129
View File
@@ -0,0 +1,129 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface SettingsSmtp
*/
export interface SettingsSmtp {
/**
*
* @type {boolean}
* @memberof SettingsSmtp
*/
enabled: boolean
/**
*
* @type {string}
* @memberof SettingsSmtp
*/
host: string
/**
*
* @type {number}
* @memberof SettingsSmtp
*/
port: number
/**
*
* @type {string}
* @memberof SettingsSmtp
*/
username: string
/**
*
* @type {string}
* @memberof SettingsSmtp
*/
password: string
/**
*
* @type {string}
* @memberof SettingsSmtp
*/
authMethod: string
/**
*
* @type {boolean}
* @memberof SettingsSmtp
*/
tls: boolean
/**
*
* @type {string}
* @memberof SettingsSmtp
*/
localName: string
}
/**
* Check if a given object implements the SettingsSmtp interface.
*/
export function instanceOfSettingsSmtp(value: object): value is SettingsSmtp {
if (!('enabled' in value) || value['enabled'] === undefined) return false
if (!('host' in value) || value['host'] === undefined) return false
if (!('port' in value) || value['port'] === undefined) return false
if (!('username' in value) || value['username'] === undefined) return false
if (!('password' in value) || value['password'] === undefined) return false
if (!('authMethod' in value) || value['authMethod'] === undefined) return false
if (!('tls' in value) || value['tls'] === undefined) return false
if (!('localName' in value) || value['localName'] === undefined) return false
return true
}
export function SettingsSmtpFromJSON(json: any): SettingsSmtp {
return SettingsSmtpFromJSONTyped(json, false)
}
export function SettingsSmtpFromJSONTyped(json: any, ignoreDiscriminator: boolean): SettingsSmtp {
if (json == null) {
return json
}
return {
enabled: json['enabled'],
host: json['host'],
port: json['port'],
username: json['username'],
password: json['password'],
authMethod: json['auth_method'],
tls: json['tls'],
localName: json['local_name']
}
}
export function SettingsSmtpToJSON(json: any): SettingsSmtp {
return SettingsSmtpToJSONTyped(json, false)
}
export function SettingsSmtpToJSONTyped(
value?: SettingsSmtp | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
enabled: value['enabled'],
host: value['host'],
port: value['port'],
username: value['username'],
password: value['password'],
auth_method: value['authMethod'],
tls: value['tls'],
local_name: value['localName']
}
}
+101
View File
@@ -0,0 +1,101 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Sidebar
*/
export interface Sidebar {
/**
*
* @type {string}
* @memberof Sidebar
*/
id: string
/**
*
* @type {string}
* @memberof Sidebar
*/
singular: string
/**
*
* @type {string}
* @memberof Sidebar
*/
plural: string
/**
*
* @type {string}
* @memberof Sidebar
*/
icon?: string
/**
*
* @type {number}
* @memberof Sidebar
*/
count: number
}
/**
* Check if a given object implements the Sidebar interface.
*/
export function instanceOfSidebar(value: object): value is Sidebar {
if (!('id' in value) || value['id'] === undefined) return false
if (!('singular' in value) || value['singular'] === undefined) return false
if (!('plural' in value) || value['plural'] === undefined) return false
if (!('count' in value) || value['count'] === undefined) return false
return true
}
export function SidebarFromJSON(json: any): Sidebar {
return SidebarFromJSONTyped(json, false)
}
export function SidebarFromJSONTyped(json: any, ignoreDiscriminator: boolean): Sidebar {
if (json == null) {
return json
}
return {
id: json['id'],
singular: json['singular'],
plural: json['plural'],
icon: json['icon'] == null ? undefined : json['icon'],
count: json['count']
}
}
export function SidebarToJSON(json: any): Sidebar {
return SidebarToJSONTyped(json, false)
}
export function SidebarToJSONTyped(
value?: Sidebar | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
singular: value['singular'],
plural: value['plural'],
icon: value['icon'],
count: value['count']
}
}
+72
View File
@@ -0,0 +1,72 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Table
*/
export interface Table {
/**
*
* @type {string}
* @memberof Table
*/
id: string
/**
*
* @type {string}
* @memberof Table
*/
name: string
}
/**
* Check if a given object implements the Table interface.
*/
export function instanceOfTable(value: object): value is Table {
if (!('id' in value) || value['id'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
return true
}
export function TableFromJSON(json: any): Table {
return TableFromJSONTyped(json, false)
}
export function TableFromJSONTyped(json: any, ignoreDiscriminator: boolean): Table {
if (json == null) {
return json
}
return {
id: json['id'],
name: json['name']
}
}
export function TableToJSON(json: any): Table {
return TableToJSONTyped(json, false)
}
export function TableToJSONTyped(value?: Table | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value
}
return {
id: value['id'],
name: value['name']
}
}
+116
View File
@@ -0,0 +1,116 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Task
*/
export interface Task {
/**
*
* @type {string}
* @memberof Task
*/
id: string
/**
*
* @type {string}
* @memberof Task
*/
ticket: string
/**
*
* @type {string}
* @memberof Task
*/
name: string
/**
*
* @type {boolean}
* @memberof Task
*/
open: boolean
/**
*
* @type {string}
* @memberof Task
*/
owner?: string
/**
*
* @type {Date}
* @memberof Task
*/
created: Date
/**
*
* @type {Date}
* @memberof Task
*/
updated: Date
}
/**
* Check if a given object implements the Task interface.
*/
export function instanceOfTask(value: object): value is Task {
if (!('id' in value) || value['id'] === undefined) return false
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('open' in value) || value['open'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function TaskFromJSON(json: any): Task {
return TaskFromJSONTyped(json, false)
}
export function TaskFromJSONTyped(json: any, ignoreDiscriminator: boolean): Task {
if (json == null) {
return json
}
return {
id: json['id'],
ticket: json['ticket'],
name: json['name'],
open: json['open'],
owner: json['owner'] == null ? undefined : json['owner'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function TaskToJSON(json: any): Task {
return TaskToJSONTyped(json, false)
}
export function TaskToJSONTyped(value?: Task | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value
}
return {
id: value['id'],
ticket: value['ticket'],
name: value['name'],
open: value['open'],
owner: value['owner'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+81
View File
@@ -0,0 +1,81 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface TaskUpdate
*/
export interface TaskUpdate {
/**
*
* @type {string}
* @memberof TaskUpdate
*/
name?: string
/**
*
* @type {boolean}
* @memberof TaskUpdate
*/
open?: boolean
/**
*
* @type {string}
* @memberof TaskUpdate
*/
owner?: string
}
/**
* Check if a given object implements the TaskUpdate interface.
*/
export function instanceOfTaskUpdate(value: object): value is TaskUpdate {
return true
}
export function TaskUpdateFromJSON(json: any): TaskUpdate {
return TaskUpdateFromJSONTyped(json, false)
}
export function TaskUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): TaskUpdate {
if (json == null) {
return json
}
return {
name: json['name'] == null ? undefined : json['name'],
open: json['open'] == null ? undefined : json['open'],
owner: json['owner'] == null ? undefined : json['owner']
}
}
export function TaskUpdateToJSON(json: any): TaskUpdate {
return TaskUpdateToJSONTyped(json, false)
}
export function TaskUpdateToJSONTyped(
value?: TaskUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
name: value['name'],
open: value['open'],
owner: value['owner']
}
}
+154
View File
@@ -0,0 +1,154 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Ticket
*/
export interface Ticket {
/**
*
* @type {string}
* @memberof Ticket
*/
id: string
/**
*
* @type {string}
* @memberof Ticket
*/
type: string
/**
*
* @type {string}
* @memberof Ticket
*/
name: string
/**
*
* @type {string}
* @memberof Ticket
*/
description: string
/**
*
* @type {boolean}
* @memberof Ticket
*/
open: boolean
/**
*
* @type {string}
* @memberof Ticket
*/
owner?: string
/**
*
* @type {string}
* @memberof Ticket
*/
resolution?: string
/**
*
* @type {object}
* @memberof Ticket
*/
schema: object
/**
*
* @type {object}
* @memberof Ticket
*/
state: object
/**
*
* @type {Date}
* @memberof Ticket
*/
created: Date
/**
*
* @type {Date}
* @memberof Ticket
*/
updated: Date
}
/**
* Check if a given object implements the Ticket interface.
*/
export function instanceOfTicket(value: object): value is Ticket {
if (!('id' in value) || value['id'] === undefined) return false
if (!('type' in value) || value['type'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('description' in value) || value['description'] === undefined) return false
if (!('open' in value) || value['open'] === undefined) return false
if (!('schema' in value) || value['schema'] === undefined) return false
if (!('state' in value) || value['state'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function TicketFromJSON(json: any): Ticket {
return TicketFromJSONTyped(json, false)
}
export function TicketFromJSONTyped(json: any, ignoreDiscriminator: boolean): Ticket {
if (json == null) {
return json
}
return {
id: json['id'],
type: json['type'],
name: json['name'],
description: json['description'],
open: json['open'],
owner: json['owner'] == null ? undefined : json['owner'],
resolution: json['resolution'] == null ? undefined : json['resolution'],
schema: json['schema'],
state: json['state'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function TicketToJSON(json: any): Ticket {
return TicketToJSONTyped(json, false)
}
export function TicketToJSONTyped(
value?: Ticket | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
type: value['type'],
name: value['name'],
description: value['description'],
open: value['open'],
owner: value['owner'],
resolution: value['resolution'],
schema: value['schema'],
state: value['state'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+129
View File
@@ -0,0 +1,129 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface TicketSearch
*/
export interface TicketSearch {
/**
*
* @type {string}
* @memberof TicketSearch
*/
id: string
/**
*
* @type {string}
* @memberof TicketSearch
*/
name: string
/**
*
* @type {string}
* @memberof TicketSearch
*/
description: string
/**
*
* @type {boolean}
* @memberof TicketSearch
*/
open: boolean
/**
*
* @type {string}
* @memberof TicketSearch
*/
type: string
/**
*
* @type {object}
* @memberof TicketSearch
*/
state: object
/**
*
* @type {string}
* @memberof TicketSearch
*/
ownerName: string
/**
*
* @type {Date}
* @memberof TicketSearch
*/
created: Date
}
/**
* Check if a given object implements the TicketSearch interface.
*/
export function instanceOfTicketSearch(value: object): value is TicketSearch {
if (!('id' in value) || value['id'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('description' in value) || value['description'] === undefined) return false
if (!('open' in value) || value['open'] === undefined) return false
if (!('type' in value) || value['type'] === undefined) return false
if (!('state' in value) || value['state'] === undefined) return false
if (!('ownerName' in value) || value['ownerName'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
return true
}
export function TicketSearchFromJSON(json: any): TicketSearch {
return TicketSearchFromJSONTyped(json, false)
}
export function TicketSearchFromJSONTyped(json: any, ignoreDiscriminator: boolean): TicketSearch {
if (json == null) {
return json
}
return {
id: json['id'],
name: json['name'],
description: json['description'],
open: json['open'],
type: json['type'],
state: json['state'],
ownerName: json['owner_name'],
created: new Date(json['created'])
}
}
export function TicketSearchToJSON(json: any): TicketSearch {
return TicketSearchToJSONTyped(json, false)
}
export function TicketSearchToJSONTyped(
value?: TicketSearch | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
name: value['name'],
description: value['description'],
open: value['open'],
type: value['type'],
state: value['state'],
owner_name: value['ownerName'],
created: value['created'].toISOString()
}
}
+121
View File
@@ -0,0 +1,121 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface TicketUpdate
*/
export interface TicketUpdate {
/**
*
* @type {string}
* @memberof TicketUpdate
*/
type?: string
/**
*
* @type {string}
* @memberof TicketUpdate
*/
name?: string
/**
*
* @type {string}
* @memberof TicketUpdate
*/
description?: string
/**
*
* @type {boolean}
* @memberof TicketUpdate
*/
open?: boolean
/**
*
* @type {string}
* @memberof TicketUpdate
*/
owner?: string
/**
*
* @type {string}
* @memberof TicketUpdate
*/
resolution?: string
/**
*
* @type {object}
* @memberof TicketUpdate
*/
schema?: object
/**
*
* @type {object}
* @memberof TicketUpdate
*/
state?: object
}
/**
* Check if a given object implements the TicketUpdate interface.
*/
export function instanceOfTicketUpdate(value: object): value is TicketUpdate {
return true
}
export function TicketUpdateFromJSON(json: any): TicketUpdate {
return TicketUpdateFromJSONTyped(json, false)
}
export function TicketUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): TicketUpdate {
if (json == null) {
return json
}
return {
type: json['type'] == null ? undefined : json['type'],
name: json['name'] == null ? undefined : json['name'],
description: json['description'] == null ? undefined : json['description'],
open: json['open'] == null ? undefined : json['open'],
owner: json['owner'] == null ? undefined : json['owner'],
resolution: json['resolution'] == null ? undefined : json['resolution'],
schema: json['schema'] == null ? undefined : json['schema'],
state: json['state'] == null ? undefined : json['state']
}
}
export function TicketUpdateToJSON(json: any): TicketUpdate {
return TicketUpdateToJSONTyped(json, false)
}
export function TicketUpdateToJSONTyped(
value?: TicketUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
type: value['type'],
name: value['name'],
description: value['description'],
open: value['open'],
owner: value['owner'],
resolution: value['resolution'],
schema: value['schema'],
state: value['state']
}
}
+111
View File
@@ -0,0 +1,111 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface TimelineEntry
*/
export interface TimelineEntry {
/**
*
* @type {string}
* @memberof TimelineEntry
*/
id: string
/**
*
* @type {string}
* @memberof TimelineEntry
*/
ticket: string
/**
*
* @type {string}
* @memberof TimelineEntry
*/
message: string
/**
*
* @type {Date}
* @memberof TimelineEntry
*/
time: Date
/**
*
* @type {Date}
* @memberof TimelineEntry
*/
created: Date
/**
*
* @type {Date}
* @memberof TimelineEntry
*/
updated: Date
}
/**
* Check if a given object implements the TimelineEntry interface.
*/
export function instanceOfTimelineEntry(value: object): value is TimelineEntry {
if (!('id' in value) || value['id'] === undefined) return false
if (!('ticket' in value) || value['ticket'] === undefined) return false
if (!('message' in value) || value['message'] === undefined) return false
if (!('time' in value) || value['time'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function TimelineEntryFromJSON(json: any): TimelineEntry {
return TimelineEntryFromJSONTyped(json, false)
}
export function TimelineEntryFromJSONTyped(json: any, ignoreDiscriminator: boolean): TimelineEntry {
if (json == null) {
return json
}
return {
id: json['id'],
ticket: json['ticket'],
message: json['message'],
time: new Date(json['time']),
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function TimelineEntryToJSON(json: any): TimelineEntry {
return TimelineEntryToJSONTyped(json, false)
}
export function TimelineEntryToJSONTyped(
value?: TimelineEntry | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
ticket: value['ticket'],
message: value['message'],
time: value['time'].toISOString(),
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
@@ -0,0 +1,76 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface TimelineEntryUpdate
*/
export interface TimelineEntryUpdate {
/**
*
* @type {string}
* @memberof TimelineEntryUpdate
*/
message?: string
/**
*
* @type {Date}
* @memberof TimelineEntryUpdate
*/
time?: Date
}
/**
* Check if a given object implements the TimelineEntryUpdate interface.
*/
export function instanceOfTimelineEntryUpdate(value: object): value is TimelineEntryUpdate {
return true
}
export function TimelineEntryUpdateFromJSON(json: any): TimelineEntryUpdate {
return TimelineEntryUpdateFromJSONTyped(json, false)
}
export function TimelineEntryUpdateFromJSONTyped(
json: any,
ignoreDiscriminator: boolean
): TimelineEntryUpdate {
if (json == null) {
return json
}
return {
message: json['message'] == null ? undefined : json['message'],
time: json['time'] == null ? undefined : new Date(json['time'])
}
}
export function TimelineEntryUpdateToJSON(json: any): TimelineEntryUpdate {
return TimelineEntryUpdateToJSONTyped(json, false)
}
export function TimelineEntryUpdateToJSONTyped(
value?: TimelineEntryUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
message: value['message'],
time: value['time'] == null ? undefined : value['time'].toISOString()
}
}
+116
View File
@@ -0,0 +1,116 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Type
*/
export interface Type {
/**
*
* @type {string}
* @memberof Type
*/
id: string
/**
*
* @type {string}
* @memberof Type
*/
icon?: string
/**
*
* @type {string}
* @memberof Type
*/
plural: string
/**
*
* @type {object}
* @memberof Type
*/
schema: object
/**
*
* @type {string}
* @memberof Type
*/
singular: string
/**
*
* @type {Date}
* @memberof Type
*/
created: Date
/**
*
* @type {Date}
* @memberof Type
*/
updated: Date
}
/**
* Check if a given object implements the Type interface.
*/
export function instanceOfType(value: object): value is Type {
if (!('id' in value) || value['id'] === undefined) return false
if (!('plural' in value) || value['plural'] === undefined) return false
if (!('schema' in value) || value['schema'] === undefined) return false
if (!('singular' in value) || value['singular'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function TypeFromJSON(json: any): Type {
return TypeFromJSONTyped(json, false)
}
export function TypeFromJSONTyped(json: any, ignoreDiscriminator: boolean): Type {
if (json == null) {
return json
}
return {
id: json['id'],
icon: json['icon'] == null ? undefined : json['icon'],
plural: json['plural'],
schema: json['schema'],
singular: json['singular'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function TypeToJSON(json: any): Type {
return TypeToJSONTyped(json, false)
}
export function TypeToJSONTyped(value?: Type | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value
}
return {
id: value['id'],
icon: value['icon'],
plural: value['plural'],
schema: value['schema'],
singular: value['singular'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+89
View File
@@ -0,0 +1,89 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface TypeUpdate
*/
export interface TypeUpdate {
/**
*
* @type {string}
* @memberof TypeUpdate
*/
icon?: string
/**
*
* @type {string}
* @memberof TypeUpdate
*/
plural?: string
/**
*
* @type {object}
* @memberof TypeUpdate
*/
schema?: object
/**
*
* @type {string}
* @memberof TypeUpdate
*/
singular?: string
}
/**
* Check if a given object implements the TypeUpdate interface.
*/
export function instanceOfTypeUpdate(value: object): value is TypeUpdate {
return true
}
export function TypeUpdateFromJSON(json: any): TypeUpdate {
return TypeUpdateFromJSONTyped(json, false)
}
export function TypeUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): TypeUpdate {
if (json == null) {
return json
}
return {
icon: json['icon'] == null ? undefined : json['icon'],
plural: json['plural'] == null ? undefined : json['plural'],
schema: json['schema'] == null ? undefined : json['schema'],
singular: json['singular'] == null ? undefined : json['singular']
}
}
export function TypeUpdateToJSON(json: any): TypeUpdate {
return TypeUpdateToJSONTyped(json, false)
}
export function TypeUpdateToJSONTyped(
value?: TypeUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
icon: value['icon'],
plural: value['plural'],
schema: value['schema'],
singular: value['singular']
}
}
+145
View File
@@ -0,0 +1,145 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface User
*/
export interface User {
/**
*
* @type {string}
* @memberof User
*/
id: string
/**
*
* @type {string}
* @memberof User
*/
username: string
/**
*
* @type {string}
* @memberof User
*/
avatar?: string
/**
*
* @type {string}
* @memberof User
*/
email?: string
/**
*
* @type {Date}
* @memberof User
*/
lastResetSentAt?: Date
/**
*
* @type {Date}
* @memberof User
*/
lastVerificationSentAt?: Date
/**
*
* @type {string}
* @memberof User
*/
name?: string
/**
*
* @type {boolean}
* @memberof User
*/
active: boolean
/**
*
* @type {Date}
* @memberof User
*/
created: Date
/**
*
* @type {Date}
* @memberof User
*/
updated: Date
}
/**
* Check if a given object implements the User interface.
*/
export function instanceOfUser(value: object): value is User {
if (!('id' in value) || value['id'] === undefined) return false
if (!('username' in value) || value['username'] === undefined) return false
if (!('active' in value) || value['active'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function UserFromJSON(json: any): User {
return UserFromJSONTyped(json, false)
}
export function UserFromJSONTyped(json: any, ignoreDiscriminator: boolean): User {
if (json == null) {
return json
}
return {
id: json['id'],
username: json['username'],
avatar: json['avatar'] == null ? undefined : json['avatar'],
email: json['email'] == null ? undefined : json['email'],
lastResetSentAt:
json['lastResetSentAt'] == null ? undefined : new Date(json['lastResetSentAt']),
lastVerificationSentAt:
json['lastVerificationSentAt'] == null ? undefined : new Date(json['lastVerificationSentAt']),
name: json['name'] == null ? undefined : json['name'],
active: json['active'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function UserToJSON(json: any): User {
return UserToJSONTyped(json, false)
}
export function UserToJSONTyped(value?: User | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value
}
return {
id: value['id'],
username: value['username'],
avatar: value['avatar'],
email: value['email'],
lastResetSentAt:
value['lastResetSentAt'] == null ? undefined : value['lastResetSentAt'].toISOString(),
lastVerificationSentAt:
value['lastVerificationSentAt'] == null
? undefined
: value['lastVerificationSentAt'].toISOString(),
name: value['name'],
active: value['active'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+111
View File
@@ -0,0 +1,111 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface UserGroup
*/
export interface UserGroup {
/**
*
* @type {string}
* @memberof UserGroup
*/
id: string
/**
*
* @type {string}
* @memberof UserGroup
*/
name: string
/**
*
* @type {Array<string>}
* @memberof UserGroup
*/
permissions: Array<string>
/**
*
* @type {string}
* @memberof UserGroup
*/
type: string
/**
*
* @type {Date}
* @memberof UserGroup
*/
created: Date
/**
*
* @type {Date}
* @memberof UserGroup
*/
updated: Date
}
/**
* Check if a given object implements the UserGroup interface.
*/
export function instanceOfUserGroup(value: object): value is UserGroup {
if (!('id' in value) || value['id'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('permissions' in value) || value['permissions'] === undefined) return false
if (!('type' in value) || value['type'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function UserGroupFromJSON(json: any): UserGroup {
return UserGroupFromJSONTyped(json, false)
}
export function UserGroupFromJSONTyped(json: any, ignoreDiscriminator: boolean): UserGroup {
if (json == null) {
return json
}
return {
id: json['id'],
name: json['name'],
permissions: json['permissions'],
type: json['type'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function UserGroupToJSON(json: any): UserGroup {
return UserGroupToJSONTyped(json, false)
}
export function UserGroupToJSONTyped(
value?: UserGroup | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
name: value['name'],
permissions: value['permissions'],
type: value['type'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+113
View File
@@ -0,0 +1,113 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface UserUpdate
*/
export interface UserUpdate {
/**
*
* @type {string}
* @memberof UserUpdate
*/
username?: string
/**
*
* @type {string}
* @memberof UserUpdate
*/
avatar?: string
/**
*
* @type {string}
* @memberof UserUpdate
*/
email?: string
/**
*
* @type {string}
* @memberof UserUpdate
*/
name?: string
/**
*
* @type {string}
* @memberof UserUpdate
*/
password?: string
/**
*
* @type {string}
* @memberof UserUpdate
*/
passwordConfirm?: string
/**
*
* @type {boolean}
* @memberof UserUpdate
*/
active?: boolean
}
/**
* Check if a given object implements the UserUpdate interface.
*/
export function instanceOfUserUpdate(value: object): value is UserUpdate {
return true
}
export function UserUpdateFromJSON(json: any): UserUpdate {
return UserUpdateFromJSONTyped(json, false)
}
export function UserUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): UserUpdate {
if (json == null) {
return json
}
return {
username: json['username'] == null ? undefined : json['username'],
avatar: json['avatar'] == null ? undefined : json['avatar'],
email: json['email'] == null ? undefined : json['email'],
name: json['name'] == null ? undefined : json['name'],
password: json['password'] == null ? undefined : json['password'],
passwordConfirm: json['passwordConfirm'] == null ? undefined : json['passwordConfirm'],
active: json['active'] == null ? undefined : json['active']
}
}
export function UserUpdateToJSON(json: any): UserUpdate {
return UserUpdateToJSONTyped(json, false)
}
export function UserUpdateToJSONTyped(
value?: UserUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
username: value['username'],
avatar: value['avatar'],
email: value['email'],
name: value['name'],
password: value['password'],
passwordConfirm: value['passwordConfirm'],
active: value['active']
}
}
+111
View File
@@ -0,0 +1,111 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface Webhook
*/
export interface Webhook {
/**
*
* @type {string}
* @memberof Webhook
*/
id: string
/**
*
* @type {string}
* @memberof Webhook
*/
name: string
/**
*
* @type {string}
* @memberof Webhook
*/
collection: string
/**
*
* @type {string}
* @memberof Webhook
*/
destination: string
/**
*
* @type {Date}
* @memberof Webhook
*/
created: Date
/**
*
* @type {Date}
* @memberof Webhook
*/
updated: Date
}
/**
* Check if a given object implements the Webhook interface.
*/
export function instanceOfWebhook(value: object): value is Webhook {
if (!('id' in value) || value['id'] === undefined) return false
if (!('name' in value) || value['name'] === undefined) return false
if (!('collection' in value) || value['collection'] === undefined) return false
if (!('destination' in value) || value['destination'] === undefined) return false
if (!('created' in value) || value['created'] === undefined) return false
if (!('updated' in value) || value['updated'] === undefined) return false
return true
}
export function WebhookFromJSON(json: any): Webhook {
return WebhookFromJSONTyped(json, false)
}
export function WebhookFromJSONTyped(json: any, ignoreDiscriminator: boolean): Webhook {
if (json == null) {
return json
}
return {
id: json['id'],
name: json['name'],
collection: json['collection'],
destination: json['destination'],
created: new Date(json['created']),
updated: new Date(json['updated'])
}
}
export function WebhookToJSON(json: any): Webhook {
return WebhookToJSONTyped(json, false)
}
export function WebhookToJSONTyped(
value?: Webhook | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
id: value['id'],
name: value['name'],
collection: value['collection'],
destination: value['destination'],
created: value['created'].toISOString(),
updated: value['updated'].toISOString()
}
}
+81
View File
@@ -0,0 +1,81 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import { mapValues } from '../runtime'
/**
*
* @export
* @interface WebhookUpdate
*/
export interface WebhookUpdate {
/**
*
* @type {string}
* @memberof WebhookUpdate
*/
name?: string
/**
*
* @type {string}
* @memberof WebhookUpdate
*/
collection?: string
/**
*
* @type {string}
* @memberof WebhookUpdate
*/
destination?: string
}
/**
* Check if a given object implements the WebhookUpdate interface.
*/
export function instanceOfWebhookUpdate(value: object): value is WebhookUpdate {
return true
}
export function WebhookUpdateFromJSON(json: any): WebhookUpdate {
return WebhookUpdateFromJSONTyped(json, false)
}
export function WebhookUpdateFromJSONTyped(json: any, ignoreDiscriminator: boolean): WebhookUpdate {
if (json == null) {
return json
}
return {
name: json['name'] == null ? undefined : json['name'],
collection: json['collection'] == null ? undefined : json['collection'],
destination: json['destination'] == null ? undefined : json['destination']
}
}
export function WebhookUpdateToJSON(json: any): WebhookUpdate {
return WebhookUpdateToJSONTyped(json, false)
}
export function WebhookUpdateToJSONTyped(
value?: WebhookUpdate | null,
ignoreDiscriminator: boolean = false
): any {
if (value == null) {
return value
}
return {
name: value['name'],
collection: value['collection'],
destination: value['destination']
}
}
+53
View File
@@ -0,0 +1,53 @@
/* tslint:disable */
/* eslint-disable */
export * from './Comment'
export * from './CommentUpdate'
export * from './Config'
export * from './DashboardCounts'
export * from './EmailTemplate'
export * from './ExtendedComment'
export * from './ExtendedTask'
export * from './ExtendedTicket'
export * from './Feature'
export * from './FileUpdate'
export * from './Group'
export * from './GroupRelation'
export * from './GroupUpdate'
export * from './GroupUser'
export * from './Link'
export * from './LinkUpdate'
export * from './ModelError'
export * from './ModelFile'
export * from './NewComment'
export * from './NewFeature'
export * from './NewFile'
export * from './NewGroup'
export * from './NewLink'
export * from './NewReaction'
export * from './NewTask'
export * from './NewTicket'
export * from './NewTimelineEntry'
export * from './NewType'
export * from './NewUser'
export * from './NewWebhook'
export * from './Reaction'
export * from './ReactionUpdate'
export * from './Settings'
export * from './SettingsMeta'
export * from './SettingsSmtp'
export * from './Sidebar'
export * from './Table'
export * from './Task'
export * from './TaskUpdate'
export * from './Ticket'
export * from './TicketSearch'
export * from './TicketUpdate'
export * from './TimelineEntry'
export * from './TimelineEntryUpdate'
export * from './Type'
export * from './TypeUpdate'
export * from './User'
export * from './UserGroup'
export * from './UserUpdate'
export * from './Webhook'
export * from './WebhookUpdate'
+490
View File
@@ -0,0 +1,490 @@
/* tslint:disable */
/* eslint-disable */
/**
* Ticketing System API
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export const BASE_PATH = 'http://localhost:8081'.replace(/\/+$/, '')
export interface ConfigurationParameters {
basePath?: string // override base path
fetchApi?: FetchAPI // override for fetch implementation
middleware?: Middleware[] // middleware to apply before/after fetch requests
queryParamsStringify?: (params: HTTPQuery) => string // stringify function for query strings
username?: string // parameter for basic security
password?: string // parameter for basic security
apiKey?: string | Promise<string> | ((name: string) => string | Promise<string>) // parameter for apiKey security
accessToken?:
| string
| Promise<string>
| ((name?: string, scopes?: string[]) => string | Promise<string>) // parameter for oauth2 security
headers?: HTTPHeaders //header params we want to use on every request
credentials?: RequestCredentials //value for the credentials param we want to use on each request
}
export class Configuration {
constructor(private configuration: ConfigurationParameters = {}) {}
set config(configuration: Configuration) {
this.configuration = configuration
}
get basePath(): string {
return this.configuration.basePath != null ? this.configuration.basePath : BASE_PATH
}
get fetchApi(): FetchAPI | undefined {
return this.configuration.fetchApi
}
get middleware(): Middleware[] {
return this.configuration.middleware || []
}
get queryParamsStringify(): (params: HTTPQuery) => string {
return this.configuration.queryParamsStringify || querystring
}
get username(): string | undefined {
return this.configuration.username
}
get password(): string | undefined {
return this.configuration.password
}
get apiKey(): ((name: string) => string | Promise<string>) | undefined {
const apiKey = this.configuration.apiKey
if (apiKey) {
return typeof apiKey === 'function' ? apiKey : () => apiKey
}
return undefined
}
get accessToken(): ((name?: string, scopes?: string[]) => string | Promise<string>) | undefined {
const accessToken = this.configuration.accessToken
if (accessToken) {
return typeof accessToken === 'function' ? accessToken : async () => accessToken
}
return undefined
}
get headers(): HTTPHeaders | undefined {
return this.configuration.headers
}
get credentials(): RequestCredentials | undefined {
return this.configuration.credentials
}
}
export const DefaultConfig = new Configuration()
/**
* This is the base class for all generated API classes.
*/
export class BaseAPI {
private static readonly jsonRegex = new RegExp(
'^(:?application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(:?;.*)?$',
'i'
)
private middleware: Middleware[]
constructor(protected configuration = DefaultConfig) {
this.middleware = configuration.middleware
}
withMiddleware<T extends BaseAPI>(this: T, ...middlewares: Middleware[]) {
const next = this.clone<T>()
next.middleware = next.middleware.concat(...middlewares)
return next
}
withPreMiddleware<T extends BaseAPI>(this: T, ...preMiddlewares: Array<Middleware['pre']>) {
const middlewares = preMiddlewares.map((pre) => ({ pre }))
return this.withMiddleware<T>(...middlewares)
}
withPostMiddleware<T extends BaseAPI>(this: T, ...postMiddlewares: Array<Middleware['post']>) {
const middlewares = postMiddlewares.map((post) => ({ post }))
return this.withMiddleware<T>(...middlewares)
}
/**
* Check if the given MIME is a JSON MIME.
* JSON MIME examples:
* application/json
* application/json; charset=UTF8
* APPLICATION/JSON
* application/vnd.company+json
* @param mime - MIME (Multipurpose Internet Mail Extensions)
* @return True if the given MIME is JSON, false otherwise.
*/
protected isJsonMime(mime: string | null | undefined): boolean {
if (!mime) {
return false
}
return BaseAPI.jsonRegex.test(mime)
}
protected async request(
context: RequestOpts,
initOverrides?: RequestInit | InitOverrideFunction
): Promise<Response> {
const { url, init } = await this.createFetchParams(context, initOverrides)
const response = await this.fetchApi(url, init)
if (response && response.status >= 200 && response.status < 300) {
return response
}
throw new ResponseError(response, 'Response returned an error code')
}
private async createFetchParams(
context: RequestOpts,
initOverrides?: RequestInit | InitOverrideFunction
) {
let url = this.configuration.basePath + context.path
if (context.query !== undefined && Object.keys(context.query).length !== 0) {
// only add the querystring to the URL if there are query parameters.
// this is done to avoid urls ending with a "?" character which buggy webservers
// do not handle correctly sometimes.
url += '?' + this.configuration.queryParamsStringify(context.query)
}
const headers = Object.assign({}, this.configuration.headers, context.headers)
Object.keys(headers).forEach((key) => (headers[key] === undefined ? delete headers[key] : {}))
const initOverrideFn =
typeof initOverrides === 'function' ? initOverrides : async () => initOverrides
const initParams = {
method: context.method,
headers,
body: context.body,
credentials: this.configuration.credentials
}
const overriddenInit: RequestInit = {
...initParams,
...(await initOverrideFn({
init: initParams,
context
}))
}
let body: any
if (
isFormData(overriddenInit.body) ||
overriddenInit.body instanceof URLSearchParams ||
isBlob(overriddenInit.body)
) {
body = overriddenInit.body
} else if (this.isJsonMime(headers['Content-Type'])) {
body = JSON.stringify(overriddenInit.body)
} else {
body = overriddenInit.body
}
const init: RequestInit = {
...overriddenInit,
body
}
return { url, init }
}
private fetchApi = async (url: string, init: RequestInit) => {
let fetchParams = { url, init }
for (const middleware of this.middleware) {
if (middleware.pre) {
fetchParams =
(await middleware.pre({
fetch: this.fetchApi,
...fetchParams
})) || fetchParams
}
}
let response: Response | undefined = undefined
try {
response = await (this.configuration.fetchApi || fetch)(fetchParams.url, fetchParams.init)
} catch (e) {
for (const middleware of this.middleware) {
if (middleware.onError) {
response =
(await middleware.onError({
fetch: this.fetchApi,
url: fetchParams.url,
init: fetchParams.init,
error: e,
response: response ? response.clone() : undefined
})) || response
}
}
if (response === undefined) {
if (e instanceof Error) {
throw new FetchError(
e,
'The request failed and the interceptors did not return an alternative response'
)
} else {
throw e
}
}
}
for (const middleware of this.middleware) {
if (middleware.post) {
response =
(await middleware.post({
fetch: this.fetchApi,
url: fetchParams.url,
init: fetchParams.init,
response: response.clone()
})) || response
}
}
return response
}
/**
* Create a shallow clone of `this` by constructing a new instance
* and then shallow cloning data members.
*/
private clone<T extends BaseAPI>(this: T): T {
const constructor = this.constructor as any
const next = new constructor(this.configuration)
next.middleware = this.middleware.slice()
return next
}
}
function isBlob(value: any): value is Blob {
return typeof Blob !== 'undefined' && value instanceof Blob
}
function isFormData(value: any): value is FormData {
return typeof FormData !== 'undefined' && value instanceof FormData
}
export class ResponseError extends Error {
override name: 'ResponseError' = 'ResponseError'
constructor(
public response: Response,
msg?: string
) {
super(msg)
}
}
export class FetchError extends Error {
override name: 'FetchError' = 'FetchError'
constructor(
public cause: Error,
msg?: string
) {
super(msg)
}
}
export class RequiredError extends Error {
override name: 'RequiredError' = 'RequiredError'
constructor(
public field: string,
msg?: string
) {
super(msg)
}
}
export const COLLECTION_FORMATS = {
csv: ',',
ssv: ' ',
tsv: '\t',
pipes: '|'
}
export type FetchAPI = WindowOrWorkerGlobalScope['fetch']
export type Json = any
export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'
export type HTTPHeaders = { [key: string]: string }
export type HTTPQuery = {
[key: string]:
| string
| number
| null
| boolean
| Array<string | number | null | boolean>
| Set<string | number | null | boolean>
| HTTPQuery
}
export type HTTPBody = Json | FormData | URLSearchParams
export type HTTPRequestInit = {
headers?: HTTPHeaders
method: HTTPMethod
credentials?: RequestCredentials
body?: HTTPBody
}
export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original'
export type InitOverrideFunction = (requestContext: {
init: HTTPRequestInit
context: RequestOpts
}) => Promise<RequestInit>
export interface FetchParams {
url: string
init: RequestInit
}
export interface RequestOpts {
path: string
method: HTTPMethod
headers: HTTPHeaders
query?: HTTPQuery
body?: HTTPBody
}
export function querystring(params: HTTPQuery, prefix: string = ''): string {
return Object.keys(params)
.map((key) => querystringSingleKey(key, params[key], prefix))
.filter((part) => part.length > 0)
.join('&')
}
function querystringSingleKey(
key: string,
value:
| string
| number
| null
| undefined
| boolean
| Array<string | number | null | boolean>
| Set<string | number | null | boolean>
| HTTPQuery,
keyPrefix: string = ''
): string {
const fullKey = keyPrefix + (keyPrefix.length ? `[${key}]` : key)
if (value instanceof Array) {
const multiValue = value
.map((singleValue) => encodeURIComponent(String(singleValue)))
.join(`&${encodeURIComponent(fullKey)}=`)
return `${encodeURIComponent(fullKey)}=${multiValue}`
}
if (value instanceof Set) {
const valueAsArray = Array.from(value)
return querystringSingleKey(key, valueAsArray, keyPrefix)
}
if (value instanceof Date) {
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(value.toISOString())}`
}
if (value instanceof Object) {
return querystring(value as HTTPQuery, fullKey)
}
return `${encodeURIComponent(fullKey)}=${encodeURIComponent(String(value))}`
}
export function exists(json: any, key: string) {
const value = json[key]
return value !== null && value !== undefined
}
export function mapValues(data: any, fn: (item: any) => any) {
const result: { [key: string]: any } = {}
for (const key of Object.keys(data)) {
result[key] = fn(data[key])
}
return result
}
export function canConsumeForm(consumes: Consume[]): boolean {
for (const consume of consumes) {
if ('multipart/form-data' === consume.contentType) {
return true
}
}
return false
}
export interface Consume {
contentType: string
}
export interface RequestContext {
fetch: FetchAPI
url: string
init: RequestInit
}
export interface ResponseContext {
fetch: FetchAPI
url: string
init: RequestInit
response: Response
}
export interface ErrorContext {
fetch: FetchAPI
url: string
init: RequestInit
error: unknown
response?: Response
}
export interface Middleware {
pre?(context: RequestContext): Promise<FetchParams | void>
post?(context: ResponseContext): Promise<Response | void>
onError?(context: ErrorContext): Promise<Response | void>
}
export interface ApiResponse<T> {
raw: Response
value(): Promise<T>
}
export interface ResponseTransformer<T> {
(json: any): T
}
export class JSONApiResponse<T> {
constructor(
public raw: Response,
private transformer: ResponseTransformer<T> = (jsonValue: any) => jsonValue
) {}
async value(): Promise<T> {
return this.transformer(await this.raw.json())
}
}
export class VoidApiResponse {
constructor(public raw: Response) {}
async value(): Promise<void> {
return undefined
}
}
export class BlobApiResponse {
constructor(public raw: Response) {}
async value(): Promise<Blob> {
return await this.raw.blob()
}
}
export class TextApiResponse {
constructor(public raw: Response) {}
async value(): Promise<string> {
return await this.raw.text()
}
}
+11 -10
View File
@@ -4,31 +4,32 @@ import * as icons from 'lucide-vue-next'
import { computed } from 'vue'
const props = defineProps({
name: {
type: String,
required: true
},
name: String,
size: Number,
color: String,
strokeWidth: Number,
defaultClass: String
})
const icon = computed((): string => {
if (!icons[props.name]) {
return 'Flame'
const IconComponent = computed(() => {
if (!props.name) {
return icons.HelpCircle
}
return icons[props.name]
const component = icons[props.name]
if (!component) {
return icons.HelpCircle
}
return component
})
</script>
<template>
<component
:is="icon"
:is="IconComponent"
:size="size"
:color="color"
:stroke-width="strokeWidth"
:default-class="defaultClass"
:class="defaultClass"
/>
</template>
+4 -4
View File
@@ -1,16 +1,16 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: string
defineProps<{
size: number
}>()
</script>
<template>
<img src="@/assets/flask.svg" alt="Catalyst Logo" :class="cn('dark:hidden', props.class)" />
<img src="@/assets/flask.svg" alt="Catalyst Logo" :class="cn('dark:hidden', 'size-' + size)" />
<img
src="@/assets/flask_white.svg"
alt="Catalyst Logo"
:class="cn('hidden dark:flex', props.class)"
:class="cn('hidden dark:flex', 'size-' + size)"
/>
</template>
+9 -22
View File
@@ -13,42 +13,29 @@ import {
import { Trash2 } from 'lucide-vue-next'
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { ref } from 'vue'
import { type RouteLocationRaw, useRouter } from 'vue-router'
import { pb } from '@/lib/pocketbase'
import { handleError } from '@/lib/utils'
const queryClient = useQueryClient()
const router = useRouter()
const props = defineProps<{
collection: string
id: string
name: string
singular: string
queryKey: string[]
to?: RouteLocationRaw
}>()
const isOpen = ref(false)
const deleteMutation = useMutation({
mutationFn: () => pb.collection(props.collection).delete(props.id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: props.queryKey })
if (props.to) router.push(props.to)
},
onError: handleError
})
const emit = defineEmits<{
(e: 'delete'): void
}>()
const deleteRecord = () => {
emit('delete')
}
</script>
<template>
<Dialog v-model:open="isOpen">
<DialogTrigger as-child>
<slot>
<Button variant="outline">
<Button role="button" variant="outline">
<Trash2 class="mr-2 h-4 w-4" />
Delete {{ props.singular }}
</Button>
@@ -64,7 +51,7 @@ const deleteMutation = useMutation({
</DialogHeader>
<DialogFooter class="mt-2 sm:justify-start">
<Button type="button" variant="destructive" @click="deleteMutation.mutate"> Delete </Button>
<Button type="button" variant="destructive" @click="deleteRecord"> Delete </Button>
<DialogClose as-child>
<Button type="button" variant="secondary">Cancel</Button>
</DialogClose>
+20 -7
View File
@@ -1,23 +1,36 @@
<script setup lang="ts">
import UserSelectList from '@/components/common/UserSelectList.vue'
import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { ref } from 'vue'
import { User2 } from 'lucide-vue-next'
import type { User } from '@/lib/types'
import type { User } from '@/client'
const user = defineModel<User>()
defineProps<{
userID?: string
userName?: string
}>()
const open = ref(false)
const select = (user: User) => {
emit('select', user)
}
const emit = defineEmits<{
(e: 'select', value: User): void
}>()
</script>
<template>
<Popover v-model:open="open">
<Popover>
<PopoverTrigger as-child>
<slot />
<Button variant="outline" role="combobox">
<User2 class="mr-2 size-4 h-4 w-4 shrink-0 opacity-50" />
{{ userName ? userName : 'Unassigned' }}
</Button>
</PopoverTrigger>
<PopoverContent class="w-[150px] p-0">
<UserSelectList v-model="user" :key="user ? user.id : 'unassigned'" :user="user" />
<UserSelectList :userID="userID" @select="select" />
</PopoverContent>
</Popover>
</template>
+24 -12
View File
@@ -14,13 +14,16 @@ import { useQuery } from '@tanstack/vue-query'
import debounce from 'lodash.debounce'
import { ref, watch } from 'vue'
import { pb } from '@/lib/pocketbase'
import type { User } from '@/lib/types'
import { useAPI } from '@/api'
import type { User } from '@/client'
import { cn } from '@/lib/utils'
const user = defineModel<User>()
const api = useAPI()
defineProps<{
userID: string | undefined
}>()
const open = ref(false)
const searchTerm = ref('')
const {
@@ -32,11 +35,16 @@ const {
} = useQuery({
queryKey: ['users', 'search', searchTerm.value],
queryFn: () =>
pb.collection('users').getFullList({
sort: 'name',
perPage: 5,
filter: pb.filter(`name ~ {:search}`, { search: searchTerm.value })
})
api
.listUsers()
.then((users) =>
users.filter(
(user) =>
user.name.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
user.username.toLowerCase().includes(searchTerm.value.toLowerCase()) ||
user.email.toLowerCase().includes(searchTerm.value.toLowerCase())
)
)
})
const searchUserDebounced = debounce(() => refetch(), 300)
@@ -45,10 +53,14 @@ watch(
() => searchTerm.value,
() => searchUserDebounced()
)
const emit = defineEmits<{
(e: 'select', value: User): void
}>()
</script>
<template>
<Command v-model="user" v-model:search-term="searchTerm">
<Command v-model:search-term="searchTerm">
<CommandInput placeholder="Search user..." />
<CommandEmpty>
<span v-if="usersIsPending"> Loading... </span>
@@ -61,11 +73,11 @@ watch(
v-for="u in users"
:key="u.id"
:value="u"
@select="open = false"
@select="emit('select', u)"
class="cursor-pointer"
>
<Check
:class="cn('mr-2 h-4 w-4', user && user.id === u.id ? 'opacity-100' : 'opacity-0')"
:class="cn('mr-2 h-4 w-4', userID && userID === u.id ? 'opacity-100' : 'opacity-0')"
/>
{{ u.name }}
</CommandItem>
+14 -12
View File
@@ -8,9 +8,14 @@ import { ChevronRight } from 'lucide-vue-next'
import { useQuery } from '@tanstack/vue-query'
import { pb } from '@/lib/pocketbase'
import type { Task } from '@/lib/types'
import { useAPI } from '@/api'
import type { ExtendedTask } from '@/client/models'
import { cn } from '@/lib/utils'
import { useAuthStore } from '@/store/auth'
const api = useAPI()
const authStore = useAuthStore()
const {
isPending,
@@ -19,13 +24,10 @@ const {
error
} = useQuery({
queryKey: ['tasks'],
queryFn: (): Promise<Array<Task>> => {
if (!pb.authStore.model) return Promise.reject('Not authenticated')
return pb.collection('tasks').getFullList({
sort: '-created',
filter: pb.filter(`open = true && owner = {:owner}`, { owner: pb.authStore.model.id }),
expand: 'owner,ticket'
})
queryFn: (): Promise<Array<ExtendedTask>> => {
return api
.listTasks()
.then((tasks) => tasks.filter((task) => task.open && task.owner === authStore.user?.id))
}
})
</script>
@@ -42,17 +44,17 @@ const {
<RouterLink
:to="{
name: 'tickets',
params: { type: task.expand.ticket.type, id: task.expand.ticket.id }
params: { type: task.ticketType, id: task.ticket }
}"
:class="
cn(
buttonVariants({ variant: 'outline', size: 'sm' }),
'h-8 w-full sm:ml-auto sm:w-auto'
'h-8 w-full sm:w-auto md:ml-auto'
)
"
>
<span class="flex flex-row items-center text-sm text-gray-500">
Go to {{ task.expand.ticket.name }}
Open
<ChevronRight class="ml-2 h-4 w-4" />
</span>
</RouterLink>
+21 -22
View File
@@ -9,29 +9,28 @@ import { ChevronRight } from 'lucide-vue-next'
import { useQuery } from '@tanstack/vue-query'
import { intervalToDuration } from 'date-fns'
import { pb } from '@/lib/pocketbase'
import type { Ticket } from '@/lib/types'
import { useAPI } from '@/api'
import type { ExtendedTicket } from '@/client/models'
import { cn } from '@/lib/utils'
import { useAuthStore } from '@/store/auth'
const {
isPending,
isError,
data: tickets,
error
} = useQuery({
const api = useAPI()
const authStore = useAuthStore()
const { data: tickets } = useQuery({
queryKey: ['tickets', 'dashboard'],
queryFn: (): Promise<Array<Ticket>> => {
if (!pb.authStore.model) return Promise.reject('Not authenticated')
return pb.collection('tickets').getFullList({
sort: '-created',
filter: pb.filter(`open = true && owner = {:owner}`, { owner: pb.authStore.model.id }),
expand: 'owner,type'
})
queryFn: (): Promise<Array<ExtendedTicket>> => {
return api
.listTickets()
.then((tickets) =>
tickets.filter((ticket) => ticket.open && ticket.owner === authStore.user?.id)
)
}
})
const age = (ticket: Ticket) => {
const days = intervalToDuration({ start: new Date(ticket.created), end: new Date() }).days
const age = (ticket: ExtendedTicket) => {
const days = intervalToDuration({ start: ticket.created, end: new Date() }).days
if (!days) return 'today'
if (days === 1) return 'yesterday'
@@ -48,9 +47,9 @@ const age = (ticket: Ticket) => {
</div>
<PanelListElement v-else v-for="ticket in tickets" :key="ticket.id" class="gap-2 pr-1">
<span>{{ ticket.name }}</span>
<Separator orientation="vertical" class="hidden h-4 sm:block" />
<span class="text-sm text-muted-foreground">{{ ticket.expand.type.singular }}</span>
<Separator orientation="vertical" class="hidden h-4 sm:block" />
<Separator orientation="vertical" class="hidden h-4 md:block" />
<span class="text-sm text-muted-foreground">{{ ticket.typeSingular }}</span>
<Separator orientation="vertical" class="hidden h-4 md:block" />
<span class="text-sm text-muted-foreground">Open since {{ age(ticket) }}</span>
<RouterLink
:to="{
@@ -60,12 +59,12 @@ const age = (ticket: Ticket) => {
:class="
cn(
buttonVariants({ variant: 'outline', size: 'sm' }),
'h-8 w-full sm:ml-auto sm:w-auto'
'h-8 w-full sm:w-auto md:ml-auto'
)
"
>
<span class="flex flex-row items-center text-sm text-gray-500">
Go to {{ ticket.name }}
Open
<ChevronRight class="ml-2 h-4 w-4" />
</span>
</RouterLink>
@@ -6,8 +6,10 @@ import { useQuery } from '@tanstack/vue-query'
import { getWeek } from 'date-fns'
import { computed } from 'vue'
import { pb } from '@/lib/pocketbase'
import type { Task } from '@/lib/types'
import { useAPI } from '@/api'
import type { Ticket } from '@/client/models'
const api = useAPI()
const {
isPending,
@@ -16,11 +18,7 @@ const {
error
} = useQuery({
queryKey: ['tickets'],
queryFn: (): Promise<Array<Task>> =>
pb.collection('tickets').getFullList({
sort: '-created',
expand: 'owner,type'
})
queryFn: (): Promise<Array<Ticket>> => api.listTickets()
})
const ticketsPerWeek = computed(() => {
+5 -2
View File
@@ -5,7 +5,10 @@ import { DonutChart } from '@/components/ui/chart-donut'
import { useQuery } from '@tanstack/vue-query'
import { computed } from 'vue'
import { pb } from '@/lib/pocketbase'
import { useAPI } from '@/api'
import type { Sidebar } from '@/client/models'
const api = useAPI()
const {
isPending,
@@ -14,7 +17,7 @@ const {
error
} = useQuery({
queryKey: ['sidebar'],
queryFn: (): Promise<Array<any>> => pb.collection('sidebar').getFullList()
queryFn: (): Promise<Array<Sidebar>> => api.getSidebar()
})
const namedTypes = computed(() => {
+100
View File
@@ -0,0 +1,100 @@
<script setup lang="ts">
import Icon from '@/components/Icon.vue'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { computed, ref } from 'vue'
const modelValue = defineModel<string>({
default: ''
})
const isOpen = ref(false)
const searchTerm = ref('')
const iconMap = [
// Priority/Importance
{ name: 'Flame' },
{ name: 'Star' },
{ name: 'Flag' },
{ name: 'Bell' },
{ name: 'BellRing' },
{ name: 'Shield' },
{ name: 'ShieldAlert' },
{ name: 'Siren' },
// Status/State
{ name: 'HelpCircle' },
{ name: 'Info' },
{ name: 'AlertCircle' },
{ name: 'AlertTriangle' },
{ name: 'CheckCircle' },
{ name: 'XCircle' },
{ name: 'Clock' },
{ name: 'Timer' },
// Action/Process
{ name: 'Calendar' },
{ name: 'FileText' },
{ name: 'FileQuestion' },
{ name: 'FileWarning' },
{ name: 'User' },
// Communication
{ name: 'MessageSquare' },
{ name: 'MessageCircle' },
{ name: 'Mail' },
// System/Technical
{ name: 'Settings' },
{ name: 'Wrench' },
{ name: 'Hammer' },
{ name: 'Code' },
{ name: 'Bug' },
{ name: 'Database' },
{ name: 'Server' },
{ name: 'Cloud' }
]
// Get all icon names that are actual components
const iconNames = computed(() =>
iconMap.filter((icon) => {
return icon.name.toLowerCase().includes(searchTerm.value.toLowerCase())
})
)
const selectIcon = (name: string) => {
modelValue.value = name
isOpen.value = false
}
</script>
<template>
<div class="flex items-center gap-2">
<Popover>
<PopoverTrigger>
<Button
type="button"
variant="outline"
role="combobox"
class="flex items-center justify-start gap-2"
>
<Icon :name="modelValue || 'CircleHelp'" class="size-4" />
</Button>
</PopoverTrigger>
<PopoverContent align="start" class="grid w-full grid-cols-8 gap-2 p-2">
<Button
v-for="icon in iconNames"
:key="icon.name"
type="button"
@click="selectIcon(icon.name)"
class="cursor-pointer"
variant="ghost"
>
<Icon :name="icon.name || 'CircleHelp'" class="size-4" />
</Button>
</PopoverContent>
</Popover>
<Input v-model="modelValue" />
</div>
</template>
@@ -14,7 +14,19 @@ import {
import isEqual from 'lodash.isequal'
import { onMounted, ref, watch } from 'vue'
import type { JSONSchema } from '@/lib/types'
interface JSONSchema {
type: 'object'
properties: Record<
string,
{
title: string
type: string
description?: string
enum?: Array<string>
}
>
required?: Array<string>
}
const model = defineModel<Record<string, any>>()
@@ -27,7 +39,7 @@ const formdata = ref<Record<string, any>>({})
onMounted(() => {
if (!model.value) return
for (const key in props.schema.properties) {
for (const key in props.schema?.properties) {
formdata.value[key] = model.value[key]
}
})
@@ -47,7 +59,7 @@ watch(
</script>
<template>
<div v-for="(property, key) in schema.properties" :key="key">
<div v-for="(property, key) in schema?.properties" :key="key">
<FormField v-if="property.enum" :name="key" v-slot="{ componentField }" v-model="formdata[key]">
<FormItem>
<FormLabel :for="key" class="text-right">
+16 -6
View File
@@ -16,11 +16,13 @@ const props = withDefaults(
modelValue?: string[]
items: string[]
placeholder?: string
disabled?: boolean
}>(),
{
modelValue: () => [],
items: () => [],
placeholder: ''
placeholder: '',
disabled: false
}
)
@@ -40,18 +42,26 @@ const filteredItems = computed(() => {
if (!selectedItems.value) return props.items
return props.items.filter((i) => !selectedItems.value.includes(i))
})
const removeItem = (item: string) => {
const index = selectedItems.value.indexOf(item)
if (index > -1) {
selectedItems.value.splice(index, 1)
}
}
</script>
<template>
<TagsInput class="flex items-center gap-2 px-0" :modelValue="selectedItems">
<div class="flex flex-wrap items-center">
<TagsInputItem v-for="item in selectedItems" :key="item" :value="item" class="ml-2">
<TagsInput class="flex items-center gap-2 px-2" :modelValue="selectedItems">
<div class="flex flex-wrap items-center gap-2">
<TagsInputItem v-for="item in selectedItems" :key="item" :value="item">
<TagsInputItemText />
<TagsInputItemDelete />
<TagsInputItemDelete v-if="!disabled" @click="removeItem(item)" />
</TagsInputItem>
</div>
<ComboboxRoot
v-if="!disabled"
v-model="selectedItems"
v-model:open="open"
v-model:searchTerm="searchTerm"
@@ -71,7 +81,7 @@ const filteredItems = computed(() => {
<CommandList
v-if="selectedItems.length < items.length"
position="popper"
class="mt-2 w-[--radix-popper-anchor-width] rounded-md border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
class="w-(--radix-popper-anchor-width) mt-2 rounded-md border bg-popover text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
>
<CommandEmpty />
<CommandGroup>
+98
View File
@@ -0,0 +1,98 @@
<script setup lang="ts">
import TanView from '@/components/TanView.vue'
import DeleteDialog from '@/components/common/DeleteDialog.vue'
import GroupForm from '@/components/group/GroupForm.vue'
import GroupGroup from '@/components/group/GroupGroup.vue'
import ColumnBody from '@/components/layout/ColumnBody.vue'
import ColumnBodyContainer from '@/components/layout/ColumnBodyContainer.vue'
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import { Button } from '@/components/ui/button'
import { useToast } from '@/components/ui/toast/use-toast'
import { ChevronLeft } from 'lucide-vue-next'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { useRouter } from 'vue-router'
import { useAPI } from '@/api'
import type { Group, GroupUpdate } from '@/client/models'
import { handleError } from '@/lib/utils'
const api = useAPI()
const router = useRouter()
const queryClient = useQueryClient()
const { toast } = useToast()
const props = defineProps<{
id: string
}>()
const {
isPending,
isError,
data: group,
error
} = useQuery({
queryKey: ['groups', props.id],
queryFn: (): Promise<Group> => api.getGroup({ id: props.id })
})
const updateGroupMutation = useMutation({
mutationFn: (update: GroupUpdate) => api.updateGroup({ id: props.id, groupUpdate: update }),
onSuccess: () => {
toast({
title: 'Group updated',
description: 'The group has been updated successfully'
})
queryClient.invalidateQueries({ queryKey: ['groups'] })
},
onError: handleError('Failed to update group')
})
const deleteMutation = useMutation({
mutationFn: () => api.deleteGroup({ id: props.id }),
onSuccess: () => {
queryClient.removeQueries({ queryKey: ['groups', props.id] })
queryClient.invalidateQueries({ queryKey: ['groups'] })
toast({
title: 'Group deleted',
description: 'The group has been deleted successfully'
})
router.push({ name: 'groups' })
},
onError: handleError('Failed to delete group')
})
</script>
<template>
<TanView :isError="isError" :isPending="isPending" :error="error">
<ColumnHeader>
<Button @click="router.push({ name: 'groups' })" variant="outline" class="md:hidden">
<ChevronLeft class="mr-2 size-4" />
Back
</Button>
<div class="ml-auto">
<DeleteDialog
v-if="group && group.id !== 'admin'"
:name="group.name"
singular="Group"
@delete="deleteMutation.mutate"
/>
</div>
</ColumnHeader>
<ColumnBody v-if="group">
<ColumnBodyContainer>
<div class="flex flex-col gap-4 xl:flex-row">
<div class="flex flex-col gap-4 xl:flex-1">
<GroupForm :group="group" @submit="updateGroupMutation.mutate" />
</div>
<div class="flex w-full flex-col gap-4 xl:w-96 xl:shrink-0">
<GroupGroup :id="group.id" />
</div>
</div>
</ColumnBodyContainer>
</ColumnBody>
</TanView>
</template>
+201
View File
@@ -0,0 +1,201 @@
<script setup lang="ts">
import MultiSelect from '@/components/form/MultiSelect.vue'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { useQuery } from '@tanstack/vue-query'
import { defineRule, useForm } from 'vee-validate'
import { computed, ref, watch } from 'vue'
import { useAPI } from '@/api'
import type { NewGroup } from '@/client'
const api = useAPI()
const submitDisabledReason = ref<string>('')
const props = defineProps<{
group?: NewGroup
}>()
const emit = defineEmits(['submit'])
const isDemo = ref(false)
const { data: config } = useQuery({
queryKey: ['config'],
queryFn: () => api.getConfig()
})
watch(
() => config.value,
() => {
if (!config.value) return
if (config.value.flags.includes('demo')) {
isDemo.value = true
}
},
{ immediate: true }
)
defineRule('required', (value: string) => {
if (!value || !value.length) {
return 'This field is required'
}
return true
})
const { handleSubmit, validate, values } = useForm({
initialValues: () => ({
name: props.group?.name || '',
permissions: props.group?.permissions || []
}),
validationSchema: {
name: 'required'
}
})
const equalGroup = (values: NewGroup, group?: NewGroup): boolean => {
if (!group) return false
return group.name === values.name && equalElements(group.permissions, values.permissions)
}
const equalElements = (a: string[], b: string[]): boolean => {
if (a.length !== b.length) return false
const sortedA = [...a].sort()
const sortedB = [...b].sort()
return sortedA.every((value, index) => value === sortedB[index])
}
const updateSubmitDisabledReason = () => {
if (props.group && props.group.name === 'Admin') {
submitDisabledReason.value = 'The admin group cannot be edited'
return
}
if (isDemo.value) {
submitDisabledReason.value = 'Groups cannot be created or edited in demo mode'
return
}
if (equalGroup(values, props.group)) {
submitDisabledReason.value = 'Make changes to save'
return
}
validate({ mode: 'silent' }).then((res) => {
if (res.valid) {
submitDisabledReason.value = ''
} else {
submitDisabledReason.value = 'Please fix the errors'
}
})
}
watch(
() => isDemo.value,
() => updateSubmitDisabledReason()
)
watch(
() => props.group,
() => updateSubmitDisabledReason(),
{ immediate: true }
)
watch(
() => values,
() => updateSubmitDisabledReason(),
{ deep: true, immediate: true }
)
const onSubmit = handleSubmit((values) => {
emit('submit', {
name: values.name,
permissions: values.permissions
})
})
const permissionItems = computed(() => config.value?.permissions || [])
</script>
<template>
<form @submit="onSubmit" class="flex w-full flex-col items-start gap-4">
<FormField name="name" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="name" class="text-right">Name</FormLabel>
<Input
id="name"
class="col-span-3"
v-bind="componentField"
:disabled="group && group.name === 'Admin'"
/>
<FormMessage />
</FormItem>
</FormField>
<FormField
key="permission.name"
name="permissions"
v-slot="{ componentField }"
validate-on-input
>
<FormItem id="permissions" class="w-full">
<div class="space-y-0.5">
<FormLabel for="permissions" class="text-right">Permissions</FormLabel>
</div>
<FormControl>
<MultiSelect
v-bind="componentField"
:items="permissionItems"
placeholder="Select permissions..."
:disabled="values.name === 'Admin'"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<Alert v-if="isDemo" variant="destructive">
<AlertTitle>Cannot save</AlertTitle>
<AlertDescription>{{ submitDisabledReason }}</AlertDescription>
</Alert>
<Alert v-if="values.name === 'Admin'">
<AlertTitle>Cannot save</AlertTitle>
<AlertDescription>{{ submitDisabledReason }}</AlertDescription>
</Alert>
<div v-if="values.name !== 'Admin'" class="flex gap-4">
<TooltipProvider :delay-duration="0">
<Tooltip>
<TooltipTrigger class="cursor-default">
<Button
role="submit"
:variant="submitDisabledReason !== '' ? 'secondary' : 'default'"
:disabled="submitDisabledReason !== ''"
:title="submitDisabledReason"
>
Save
</Button>
</TooltipTrigger>
<TooltipContent>
<span v-if="submitDisabledReason !== ''">
{{ submitDisabledReason }}
</span>
<span v-else> Save the group. </span>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<slot name="cancel"></slot>
</div>
</form>
</template>
+323
View File
@@ -0,0 +1,323 @@
<script setup lang="ts">
import DeleteDialog from '@/components/common/DeleteDialog.vue'
import GroupSelectDialog from '@/components/group/GroupSelectDialog.vue'
import PanelListElement from '@/components/layout/PanelListElement.vue'
import TicketPanel from '@/components/ticket/TicketPanel.vue'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { useToast } from '@/components/ui/toast/use-toast'
import UserSelectDialog from '@/components/user/UserSelectDialog.vue'
import { Trash2 } from 'lucide-vue-next'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { ref } from 'vue'
import { useAPI } from '@/api'
import type { GroupUser, UserGroup } from '@/client'
import { handleError } from '@/lib/utils'
const api = useAPI()
const queryClient = useQueryClient()
const { toast } = useToast()
const props = defineProps<{
id: string
}>()
const { data: parentGroups } = useQuery({
queryKey: ['parent_groups', props.id],
queryFn: (): Promise<Array<UserGroup>> => api.listParentGroups({ id: props.id })
})
const { data: parentPermissions } = useQuery({
queryKey: ['parent_permissions', props.id],
queryFn: (): Promise<Array<string>> => api.listParentPermissions({ id: props.id })
})
const { data: childGroups } = useQuery({
queryKey: ['child_groups', props.id],
queryFn: (): Promise<Array<UserGroup>> => api.listChildGroups({ id: props.id })
})
const { data: groupUsers } = useQuery({
queryKey: ['group_users', props.id],
queryFn: (): Promise<Array<GroupUser>> => api.listGroupUsers({ id: props.id })
})
const addGroupUserMutation = useMutation({
mutationFn: (id: string): Promise<void> =>
api.addUserGroup({
id: id,
groupRelation: {
groupId: props.id
}
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['group_users'] })
toast({
title: 'User added',
description: 'The user has been added to the group'
})
},
onError: handleError('Failed to add user to group')
})
const addGroupParentMutation = useMutation({
mutationFn: (id: string): Promise<void> =>
api.addGroupParent({
id: id,
groupRelation: {
groupId: props.id
}
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['parent_groups'] })
queryClient.invalidateQueries({ queryKey: ['parent_permissions'] })
toast({
title: 'Parent group added',
description: 'The parent group has been added successfully'
})
},
onError: handleError('Failed to add parent group')
})
const addGroupChildMutation = useMutation({
mutationFn: (id: string): Promise<void> =>
api.addGroupParent({
id: props.id,
groupRelation: {
groupId: id
}
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['child_groups'] })
toast({
title: 'Child group added',
description: 'The child group has been added successfully'
})
},
onError: handleError('Failed to add child group')
})
const removeGroupUserMutation = useMutation({
mutationFn: (id: string): Promise<void> =>
api.removeUserGroup({
id: id,
groupId: props.id
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['group_users'] })
toast({
title: 'User removed',
description: 'The user has been removed from the group'
})
},
onError: handleError('Failed to remove user from group')
})
const removeGroupParentMutation = useMutation({
mutationFn: (id: string): Promise<void> =>
api.removeGroupParent({
id: id,
parentGroupId: props.id
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['parent_groups'] })
queryClient.invalidateQueries({ queryKey: ['parent_permissions'] })
toast({
title: 'Parent group removed',
description: 'The parent group has been removed successfully'
})
},
onError: handleError('Failed to remove parent group')
})
const removeGroupChildMutation = useMutation({
mutationFn: (id: string): Promise<void> =>
api.removeGroupParent({
id: props.id,
parentGroupId: id
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['child_groups'] })
toast({
title: 'Child group removed',
description: 'The child group has been removed successfully'
})
},
onError: handleError('Failed to remove child group')
})
const dialogOpenParent = ref(false)
const dialogOpenChild = ref(false)
const dialogOpenUser = ref(false)
const selectParent = (group: { group: string }) => {
addGroupParentMutation.mutate(group.group)
dialogOpenParent.value = false
}
const selectChild = (group: { group: string }) => {
addGroupChildMutation.mutate(group.group)
dialogOpenChild.value = false
}
const selectUser = (user: { user: string }) => {
addGroupUserMutation.mutate(user.user)
dialogOpenUser.value = false
}
</script>
<template>
<Card>
<CardHeader>
<CardTitle>Members</CardTitle>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-4">
<TicketPanel title="Child Groups" @add="dialogOpenChild = true">
<GroupSelectDialog
v-model="dialogOpenChild"
@select="selectChild"
:exclude="childGroups?.map((group) => group.id).concat([id]) ?? [id]"
/>
<PanelListElement
v-for="groupGroup in childGroups"
:key="groupGroup.id"
class="flex h-10 flex-row items-center pr-1"
>
<div class="flex flex-1 items-center overflow-hidden">
<RouterLink
:to="{ name: 'groups', params: { id: groupGroup.id } }"
class="hover:underline"
>
{{ groupGroup.name }}
</RouterLink>
<span class="ml-1 text-sm text-muted-foreground">({{ groupGroup.type }})</span>
</div>
<DeleteDialog
v-if="groupGroup.type === 'direct'"
:name="groupGroup.name"
singular="Membership"
@delete="removeGroupChildMutation.mutate(groupGroup.id)"
>
<Button variant="ghost" size="icon" class="h-8 w-8">
<Trash2 class="size-4" />
</Button>
</DeleteDialog>
</PanelListElement>
<div
v-if="!childGroups || childGroups.length === 0"
class="flex h-10 items-center p-4 text-muted-foreground"
>
No groups assigned yet.
</div>
</TicketPanel>
</div>
<div class="mt-4 flex flex-col gap-4">
<TicketPanel title="Users" @add="dialogOpenUser = true">
<UserSelectDialog
v-model="dialogOpenUser"
@select="selectUser"
:exclude="groupUsers?.map((user) => user.id) ?? []"
/>
<PanelListElement
v-for="groupUser in groupUsers"
:key="groupUser.id"
class="flex h-10 flex-row items-center pr-1"
>
<div class="flex flex-1 items-center overflow-hidden">
<RouterLink
:to="{ name: 'users', params: { id: groupUser.id } }"
class="hover:underline"
>
{{ groupUser.name }}
</RouterLink>
<span class="ml-1 text-sm text-muted-foreground">({{ groupUser.type }})</span>
</div>
<DeleteDialog
v-if="groupUser.type === 'direct'"
:name="groupUser.name"
singular="Membership"
@delete="removeGroupUserMutation.mutate(groupUser.id)"
>
<Button variant="ghost" size="icon" class="h-8 w-8">
<Trash2 class="size-4" />
</Button>
</DeleteDialog>
</PanelListElement>
<div
v-if="!groupUsers || groupUsers.length === 0"
class="flex h-10 items-center p-4 text-muted-foreground"
>
No users assigned yet.
</div>
</TicketPanel>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Inheritance</CardTitle>
</CardHeader>
<CardContent>
<div class="mt-4 flex flex-col gap-4">
<TicketPanel title="Parent Groups" @add="dialogOpenParent = true">
<GroupSelectDialog
v-model="dialogOpenParent"
@select="selectParent"
:exclude="parentGroups?.map((group) => group.id).concat([id]) ?? [id]"
/>
<PanelListElement
v-for="groupGroup in parentGroups"
:key="groupGroup.id"
class="flex h-10 flex-row items-center pr-1"
>
<div class="flex flex-1 items-center overflow-hidden">
<RouterLink
:to="{ name: 'groups', params: { id: groupGroup.id } }"
class="hover:underline"
>
{{ groupGroup.name }}
</RouterLink>
<span class="ml-1 text-sm text-muted-foreground">({{ groupGroup.type }})</span>
</div>
<DeleteDialog
v-if="groupGroup.type === 'direct'"
:name="groupGroup.name"
singular="Inheritance"
@delete="removeGroupParentMutation.mutate(groupGroup.id)"
>
<Button variant="ghost" size="icon" class="h-8 w-8">
<Trash2 class="size-4" />
</Button>
</DeleteDialog>
</PanelListElement>
<div
v-if="!parentGroups || parentGroups.length === 0"
class="flex h-10 items-center p-4 text-muted-foreground"
>
No groups assigned yet.
</div>
</TicketPanel>
</div>
<div class="mt-4 flex flex-col gap-4">
<h2 class="text-sm font-medium">Permissions</h2>
<p class="text-sm text-muted-foreground">
The following permissions are granted in addition to the permissions selected to the left
by the parent groups.
</p>
<div class="flex flex-wrap gap-2">
<Badge v-for="(permission, index) in parentPermissions" :key="index">{{
permission
}}</Badge>
</div>
</div>
</CardContent>
</Card>
</template>
+60
View File
@@ -0,0 +1,60 @@
<script setup lang="ts">
import TanView from '@/components/TanView.vue'
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import ResourceListElement from '@/components/layout/ResourceListElement.vue'
import { Button } from '@/components/ui/button'
import { useQuery } from '@tanstack/vue-query'
import { useRoute, useRouter } from 'vue-router'
import { useAPI } from '@/api'
import type { Group } from '@/client/models'
const api = useAPI()
const route = useRoute()
const router = useRouter()
const {
isPending,
isError,
data: groups,
error
} = useQuery({
queryKey: ['groups'],
queryFn: (): Promise<Array<Group>> => api.listGroups()
})
const description = (group: Group): string => {
return group.permissions.join(', ')
}
const openNew = () => {
router.push({ name: 'groups', params: { id: 'new' } })
}
</script>
<template>
<TanView :isError="isError" :isPending="isPending" :error="error">
<ColumnHeader title="Groups" show-sidebar-trigger>
<div class="ml-auto">
<Button variant="ghost" @click="openNew">New Group</Button>
</div>
</ColumnHeader>
<div class="mt-2 flex flex-1 flex-col gap-2 overflow-auto p-2 pt-0">
<ResourceListElement
v-for="group in groups"
:key="group.id"
:title="group.name"
:created="group.created"
subtitle=""
:description="description(group)"
:active="route.params.id === group.id"
:to="{ name: 'groups', params: { id: group.id } }"
:open="false"
>
{{ group.name }}
</ResourceListElement>
</div>
</TanView>
</template>
+51
View File
@@ -0,0 +1,51 @@
<script setup lang="ts">
import GroupForm from '@/components/group/GroupForm.vue'
import ColumnBody from '@/components/layout/ColumnBody.vue'
import ColumnBodyContainer from '@/components/layout/ColumnBodyContainer.vue'
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import { Button } from '@/components/ui/button'
import { useToast } from '@/components/ui/toast/use-toast'
import { ChevronLeft } from 'lucide-vue-next'
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { useRouter } from 'vue-router'
import { useAPI } from '@/api'
import type { Group, NewGroup } from '@/client'
import { handleError } from '@/lib/utils'
const api = useAPI()
const queryClient = useQueryClient()
const router = useRouter()
const { toast } = useToast()
const addGroupMutation = useMutation({
mutationFn: (values: NewGroup): Promise<Group> => api.createGroup({ newGroup: values }),
onSuccess: (data: Group) => {
router.push({ name: 'groups', params: { id: data.id } })
toast({
title: 'Group created',
description: 'The group has been created successfully'
})
queryClient.invalidateQueries({ queryKey: ['groups'] })
},
onError: handleError('Failed to create group')
})
</script>
<template>
<ColumnHeader>
<Button @click="router.push({ name: 'groups' })" variant="outline" class="md:hidden">
<ChevronLeft class="mr-2 size-4" />
Back
</Button>
</ColumnHeader>
<ColumnBody>
<ColumnBodyContainer small>
<GroupForm @submit="addGroupMutation.mutate" />
</ColumnBodyContainer>
</ColumnBody>
</template>
@@ -0,0 +1,115 @@
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle
} from '@/components/ui/dialog'
import { FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select'
import { useQuery } from '@tanstack/vue-query'
import { defineRule, useForm } from 'vee-validate'
import { computed, ref, watch } from 'vue'
import { useAPI } from '@/api'
import type { Group } from '@/client'
const api = useAPI()
const isOpen = defineModel<boolean>()
const props = defineProps<{
exclude: Array<string>
}>()
const emit = defineEmits(['select'])
const { data: groups } = useQuery({
queryKey: ['groups'],
queryFn: (): Promise<Array<Group>> => api.listGroups()
})
const filteredGroups = computed(() => {
return groups.value?.filter((group) => !props.exclude.includes(group.id)) ?? []
})
defineRule('required', (value: string) => {
if (!value || !value.length) {
return 'This field is required'
}
return true
})
const { handleSubmit, validate, values } = useForm({
validationSchema: {
group: 'required'
}
})
const onSubmit = handleSubmit((values) => emit('select', values))
const submitDisabled = ref(true)
const change = () => validate({ mode: 'silent' }).then((res) => (submitDisabled.value = !res.valid))
watch(
() => values,
() => change(),
{ deep: true, immediate: true }
)
</script>
<template>
<Dialog v-model:open="isOpen">
<DialogContent>
<DialogHeader>
<DialogTitle>New Group</DialogTitle>
<DialogDescription> Add a new group to this user</DialogDescription>
</DialogHeader>
<form @submit="onSubmit" @change="change">
<FormField name="group" v-slot="{ componentField }">
<FormItem>
<FormLabel for="group" class="text-right"> Group</FormLabel>
<Select id="group" v-bind="componentField">
<SelectTrigger>
<SelectValue placeholder="Select a group" />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="group in filteredGroups" :key="group.id" :value="group.id"
>{{ group.name }}
</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
</FormField>
<DialogFooter class="mt-2 sm:justify-start">
<Button
:title="submitDisabled ? 'Please fill out all required fields' : undefined"
:disabled="submitDisabled"
type="submit"
>
Save
</Button>
<DialogClose as-child>
<Button type="button" variant="secondary"> Cancel</Button>
</DialogClose>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</template>
+15 -1
View File
@@ -1,5 +1,19 @@
<script setup lang="ts">
import { type HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
}>()
</script>
<template>
<div class="flex flex-1 items-start justify-start overflow-y-auto overflow-x-hidden">
<div
:class="
cn('flex flex-1 items-start justify-start overflow-y-auto overflow-x-hidden', props.class)
"
>
<slot />
</div>
</template>
@@ -7,7 +7,9 @@ defineProps<{
</script>
<template>
<div :class="cn('mx-auto flex w-full max-w-[72rem] gap-4 p-4', small && 'max-w-[47rem]')">
<div
:class="cn('mx-auto flex w-full max-w-[72rem] flex-col gap-4 p-4', small && 'max-w-[47rem]')"
>
<slot />
</div>
</template>
@@ -1,9 +1,11 @@
<script setup lang="ts">
import { Separator } from '@/components/ui/separator'
import { SidebarTrigger } from '@/components/ui/sidebar'
import { cn } from '@/lib/utils'
defineProps<{
showSidebarTrigger?: boolean
title?: string
nowrap?: boolean
hideSeparator?: boolean
@@ -16,6 +18,7 @@ defineProps<{
cn('flex min-h-14 flex-wrap items-center gap-2 bg-background p-2', nowrap && 'flex-nowrap')
"
>
<SidebarTrigger v-if="showSidebarTrigger" />
<h1 v-if="title" class="text-xl font-bold">
{{ title }}
</h1>
@@ -1,13 +1,16 @@
<script setup lang="ts">
import Icon from '@/components/Icon.vue'
import { formatDistanceToNow } from 'date-fns'
import { cn } from '@/lib/utils'
defineProps<{
icon?: string
title: string
subtitle: string
description: string
created: string
created: Date
open: boolean
active: boolean
@@ -27,14 +30,15 @@ defineProps<{
>
<div class="flex w-full flex-col gap-1">
<div class="flex items-center">
<div class="flex items-center gap-2">
<div class="flex items-center gap-1">
<Icon v-if="icon" :name="icon" class="mr-2 size-4" />
<div class="font-semibold">
{{ title }}
</div>
<span v-if="open" class="flex h-2 w-2 rounded-full bg-blue-600" />
<span v-if="open" class="ml-1 flex h-2 w-2 rounded-full bg-blue-600" />
</div>
<div :class="cn('ml-auto text-xs', active ? 'text-foreground' : 'text-muted-foreground')">
{{ formatDistanceToNow(new Date(created), { addSuffix: true }) }}
{{ formatDistanceToNow(created, { addSuffix: true }) }}
</div>
</div>
+196 -76
View File
@@ -1,85 +1,205 @@
<script setup lang="ts">
import Icon from '@/components/Icon.vue'
import CatalystLogo from '@/components/common/CatalystLogo.vue'
import IncidentNav from '@/components/sidebar/IncidentNav.vue'
import NavList from '@/components/sidebar/NavList.vue'
import UserDropDown from '@/components/sidebar/UserDropDown.vue'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarHeader,
SidebarInset,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarProvider,
SidebarRail
} from '@/components/ui/sidebar'
import { Menu } from 'lucide-vue-next'
import { ChevronsUpDown, LogOut, Settings, Tag, User, Users, Zap } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
import { useCatalystStore } from '@/store/catalyst'
import { useQuery } from '@tanstack/vue-query'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
const catalystStore = useCatalystStore()
import { useAPI } from '@/api.ts'
import type { Sidebar as SidebarModel } from '@/client/models'
import { useAuthStore } from '@/store/auth'
const authStore = useAuthStore()
const links = [
{
name: 'Reactions',
url: '/reactions',
icon: Zap,
permission: 'reaction:write'
},
{
name: 'Users',
url: '/users',
icon: User,
permission: 'group:write'
},
{
name: 'Groups',
url: '/groups',
icon: Users,
permission: 'group:write'
},
{
name: 'Types',
url: '/types',
icon: Tag,
permission: 'type:write'
},
{
name: 'Settings',
url: '/settings',
icon: Settings,
permission: 'settings:write'
}
]
const userLinks = computed(() => {
return links.filter((link) => authStore.hasPermission(link.permission))
})
const api = useAPI()
const router = useRouter()
const { data: sidebar } = useQuery({
queryKey: ['sidebar'],
queryFn: (): Promise<Array<SidebarModel>> => api.getSidebar()
})
const logout = () => {
authStore.setToken('')
router.push({ name: 'login' })
}
const initials = (user: { name?: string } | undefined) => {
if (!user || !user.name) return ''
const names = user.name.split(' ')
return names.length > 1 ? `${names[0][0]}${names[1][0]}` : names[0][0]
}
</script>
<template>
<div
:class="
cn(
'flex min-w-48 shrink-0 flex-col border-r bg-popover', // transition-all duration-300 ease-in-out',
catalystStore.sidebarCollapsed && 'min-w-[50px]'
)
"
>
<div class="flex h-[57px] items-center border-b bg-background">
<CatalystLogo
class="size-8"
:class="{
'flex-1': catalystStore.sidebarCollapsed,
'mx-3': !catalystStore.sidebarCollapsed
}"
/>
<h1 class="text-xl font-bold" v-if="!catalystStore.sidebarCollapsed">Catalyst</h1>
</div>
<NavList
:is-collapsed="catalystStore.sidebarCollapsed"
:links="[
{
title: 'Dashboard',
icon: 'PanelsTopLeft',
variant: 'ghost',
to: '/dashboard'
}
]"
/>
<Separator />
<IncidentNav :is-collapsed="catalystStore.sidebarCollapsed" />
<div class="flex-1" />
<Separator />
<NavList
:is-collapsed="catalystStore.sidebarCollapsed"
:links="[
{
title: 'Reactions',
icon: 'Zap',
variant: 'ghost',
to: '/reactions'
}
]"
/>
<Separator />
<UserDropDown :is-collapsed="catalystStore.sidebarCollapsed" />
<Separator />
<div :class="cn('flex h-14 items-center px-3', !catalystStore.sidebarCollapsed && 'px-2')">
<Button
variant="ghost"
@click="catalystStore.toggleSidebar()"
size="default"
:class="
cn(
'p-0',
catalystStore.sidebarCollapsed && 'w-9',
!catalystStore.sidebarCollapsed && 'w-full justify-start px-3'
)
"
>
<Menu class="size-4" />
<span v-if="!catalystStore.sidebarCollapsed" class="ml-2">Toggle Sidebar</span>
</Button>
</div>
</div>
<SidebarProvider class="h-full w-full" style="--sidebar-width: 12rem">
<Sidebar collapsible="icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem class="flex items-center gap-2 p-2">
<CatalystLogo :size="5" />
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">Catalyst</span>
<span class="truncate text-xs">Incident Management</span>
</div>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupLabel>Tickets</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem v-for="(typ, index) of sidebar" :key="index">
<SidebarMenuButton :tooltip="typ.plural" as-child>
<RouterLink :to="`/tickets/${typ.id}`">
<Icon :name="typ.icon" class="size-4" />
<span>{{ typ.plural }}</span>
<span class="ml-auto">{{ typ.count }}</span>
</RouterLink>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
<div class="flex-1" />
<SidebarGroup v-if="userLinks.length > 0" class="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>Administration</SidebarGroupLabel>
<SidebarMenu>
<SidebarMenuItem v-for="item in userLinks" :key="item.name">
<SidebarMenuButton as-child>
<RouterLink :to="item.url">
<component :is="item.icon" />
<span>{{ item.name }}</span>
</RouterLink>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage
:src="authStore.user?.avatar ? authStore.user.avatar : ''"
:alt="authStore.user?.name"
/>
<AvatarFallback class="rounded-lg"
>{{ initials(authStore.user) }}
</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ authStore.user?.name }}</span>
<span class="truncate text-xs">{{ authStore.user?.email }}</span>
</div>
<ChevronsUpDown class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side="bottom"
align="end"
:side-offset="4"
>
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage
:src="authStore.user?.avatar ? authStore.user.avatar : ''"
:alt="authStore.user?.name"
/>
<AvatarFallback class="rounded-lg"
>{{ initials(authStore.user) }}
</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ authStore.user?.name }}</span>
<span class="truncate text-xs">{{ authStore.user?.email }}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem @click="logout">
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
<SidebarRail />
</Sidebar>
<SidebarInset class="flex h-full w-full">
<slot />
</SidebarInset>
</SidebarProvider>
</template>
+9 -20
View File
@@ -11,30 +11,19 @@ defineProps<{
<template>
<TooltipProvider :delay-duration="0">
<div class="flex h-full flex-row items-stretch bg-muted/40">
<SideBar />
<div
:class="
cn(
'w-full flex-initial border-r sm:w-72',
!showDetails && 'flex',
showDetails && 'hidden sm:flex'
)
"
>
<div class="flex h-full w-full flex-col">
<SideBar>
<div class="flex h-full w-full flex-col overflow-auto md:flex-row">
<div
:class="
cn('flex h-full w-full flex-col border-r md:w-72', showDetails ? 'hidden md:flex' : '')
"
>
<slot name="list" />
</div>
</div>
<div
:class="
cn('flex-1 overflow-hidden', !showDetails && 'hidden sm:flex', showDetails && 'flex')
"
>
<div class="flex h-full w-full flex-1 flex-col">
<div :class="cn('flex h-full w-full flex-1 flex-col', showDetails ? '' : 'hidden md:flex')">
<slot name="single" />
</div>
</div>
</div>
</SideBar>
</TooltipProvider>
</template>
+3 -6
View File
@@ -5,11 +5,8 @@ import { TooltipProvider } from '@/components/ui/tooltip'
<template>
<TooltipProvider :delay-duration="0">
<div class="flex h-full flex-row items-stretch bg-muted/40">
<SideBar />
<div class="flex h-full w-full flex-col">
<slot />
</div>
</div>
<SideBar>
<slot />
</SideBar>
</TooltipProvider>
</template>
+30 -44
View File
@@ -6,20 +6,22 @@ import ColumnBodyContainer from '@/components/layout/ColumnBodyContainer.vue'
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import ReactionForm from '@/components/reaction/ReactionForm.vue'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/toast'
import { useToast } from '@/components/ui/toast/use-toast'
import { ChevronLeft } from 'lucide-vue-next'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { pb } from '@/lib/pocketbase'
import type { Reaction } from '@/lib/types'
import { useAPI } from '@/api'
import type { Reaction } from '@/client/models'
import { handleError } from '@/lib/utils'
const api = useAPI()
const router = useRouter()
const queryClient = useQueryClient()
const { toast } = useToast()
const props = defineProps<{
id: string
@@ -32,65 +34,49 @@ const {
error
} = useQuery({
queryKey: ['reactions', props.id],
queryFn: (): Promise<Reaction> => pb.collection('reactions').getOne(props.id)
queryFn: (): Promise<Reaction> => api.getReaction({ id: props.id })
})
const updateReactionMutation = useMutation({
mutationFn: (update: any) => pb.collection('reactions').update(props.id, update),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['reactions'] }),
onError: handleError
})
onMounted(() => {
if (props.id) {
pb.collection('reactions').subscribe(props.id, (data) => {
if (data.action === 'delete') {
toast({
title: 'Reaction deleted',
description: 'The reaction has been deleted.',
variant: 'destructive'
})
router.push({ name: 'reactions' })
return
}
if (data.action === 'update') {
toast({
title: 'Reaction updated',
description: 'The reaction has been updated.'
})
queryClient.invalidateQueries({ queryKey: ['reactions', props.id] })
}
mutationFn: (update: any) => api.updateReaction({ id: props.id, reactionUpdate: update }),
onSuccess: () => {
toast({
title: 'Reaction updated',
description: 'The reaction has been updated successfully'
})
}
queryClient.invalidateQueries({ queryKey: ['reactions'] })
},
onError: handleError('Failed to update reaction')
})
onUnmounted(() => {
if (props.id) {
pb.collection('reactions').unsubscribe(props.id)
}
const deleteMutation = useMutation({
mutationFn: () => api.deleteReaction({ id: props.id }),
onSuccess: () => {
queryClient.removeQueries({ queryKey: ['reactions', props.id] })
queryClient.invalidateQueries({ queryKey: ['reactions'] })
toast({
title: 'Reaction deleted',
description: 'The reaction has been deleted successfully'
})
router.push({ name: 'reactions' })
},
onError: handleError('Failed to delete reaction')
})
</script>
<template>
<TanView :isError="isError" :isPending="isPending" :error="error">
<ColumnHeader>
<Button @click="router.push({ name: 'reactions' })" variant="outline" class="sm:hidden">
<Button @click="router.push({ name: 'reactions' })" variant="outline" class="md:hidden">
<ChevronLeft class="mr-2 size-4" />
Back
</Button>
<div class="ml-auto">
<DeleteDialog
v-if="reaction"
collection="reactions"
:id="reaction.id"
:name="reaction.name"
:singular="'Reaction'"
:to="{ name: 'reactions' }"
:queryKey="['reactions']"
singular="Reaction"
@delete="deleteMutation.mutate"
/>
</div>
</ColumnHeader>
+12 -8
View File
@@ -31,8 +31,10 @@ import { useQuery } from '@tanstack/vue-query'
import { defineRule, useForm } from 'vee-validate'
import { computed, ref, watch } from 'vue'
import { pb } from '@/lib/pocketbase'
import type { Reaction } from '@/lib/types'
import { useAPI } from '@/api'
import type { Reaction } from '@/client/models'
const api = useAPI()
const submitDisabledReason = ref<string>('')
@@ -46,14 +48,14 @@ const isDemo = ref(false)
const { data: config } = useQuery({
queryKey: ['config'],
queryFn: (): Promise<Record<string, Array<String>>> => pb.send('/api/config', {})
queryFn: () => api.getConfig()
})
watch(
() => config.value,
() => {
if (!config.value) return
if (config.value['flags'].includes('demo')) {
if (config.value.flags.includes('demo')) {
isDemo.value = true
}
},
@@ -87,7 +89,7 @@ defineRule('triggerdata.expression', (value: string) => {
return 'Invalid cron expression'
})
defineRule('triggerdata.token', (value: string) => {
defineRule('triggerdata.token', () => {
return true
})
@@ -251,8 +253,10 @@ const curlExample = computed(() => {
cmd += ` -H "Authorization: Bearer ${values.triggerdata.token}"`
}
const url = `${location.protocol}//${location.hostname}:${location.port}`
if (values.triggerdata.path) {
cmd += ` https://${location.hostname}/reaction/${values.triggerdata.path}`
cmd += ` -d '{"foo":"bar"}' ${url}/reaction/${values.triggerdata.path}`
}
return cmd
@@ -304,8 +308,8 @@ const curlExample = computed(() => {
<TriggerHookFormFields v-else-if="values.trigger === 'hook'" />
<div v-if="values.trigger === 'webhook'">
<Label for="url">Usage</Label>
<Input id="url" readonly :modelValue="curlExample" class="bg-accent" />
<Label for="usage">Usage</Label>
<Input id="usage" readonly :modelValue="curlExample" class="bg-accent" />
</div>
</CardContent>
</Card>
+21 -32
View File
@@ -4,16 +4,16 @@ import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import ResourceListElement from '@/components/layout/ResourceListElement.vue'
import { Button } from '@/components/ui/button'
import { useQuery, useQueryClient } from '@tanstack/vue-query'
import { onMounted } from 'vue'
import { useQuery } from '@tanstack/vue-query'
import { useRoute, useRouter } from 'vue-router'
import { pb } from '@/lib/pocketbase'
import type { Reaction } from '@/lib/types'
import { useAPI } from '@/api'
import type { Reaction } from '@/client/models'
const api = useAPI()
const route = useRoute()
const router = useRouter()
const queryClient = useQueryClient()
const {
isPending,
@@ -22,10 +22,7 @@ const {
error
} = useQuery({
queryKey: ['reactions'],
queryFn: (): Promise<Array<Reaction>> =>
pb.collection('reactions').getFullList({
sort: '-created'
})
queryFn: (): Promise<Array<Reaction>> => api.listReactions()
})
const subtitle = (reaction: Reaction) =>
@@ -56,37 +53,29 @@ const reactionNiceName = (reaction: Reaction) => {
const openNew = () => {
router.push({ name: 'reactions', params: { id: 'new' } })
}
onMounted(() => {
pb.collection('reactions').subscribe('*', () => {
queryClient.invalidateQueries({ queryKey: ['reactions'] })
})
})
</script>
<template>
<TanView :isError="isError" :isPending="isPending" :error="error">
<ColumnHeader title="Reactions">
<ColumnHeader title="Reactions" show-sidebar-trigger>
<div class="ml-auto">
<Button variant="ghost" @click="openNew">New Reaction</Button>
</div>
</ColumnHeader>
<div class="mt-2 flex flex-1 flex-col gap-2 p-2 pt-0">
<TransitionGroup name="list" appear>
<ResourceListElement
v-for="reaction in reactions"
:key="reaction.id"
:title="reaction.name"
:created="reaction.created"
:subtitle="subtitle(reaction)"
description=""
:active="route.params.id === reaction.id"
:to="{ name: 'reactions', params: { id: reaction.id } }"
:open="false"
>
{{ reaction.name }}
</ResourceListElement>
</TransitionGroup>
<div class="mt-2 flex flex-1 flex-col gap-2 overflow-auto p-2 pt-0">
<ResourceListElement
v-for="reaction in reactions"
:key="reaction.id"
:title="reaction.name"
:created="reaction.created"
:subtitle="subtitle(reaction)"
description=""
:active="route.params.id === reaction.id"
:to="{ name: 'reactions', params: { id: reaction.id } }"
:open="false"
>
{{ reaction.name }}
</ResourceListElement>
</div>
</TanView>
</template>
+14 -6
View File
@@ -4,32 +4,40 @@ import ColumnBodyContainer from '@/components/layout/ColumnBodyContainer.vue'
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import ReactionForm from '@/components/reaction/ReactionForm.vue'
import { Button } from '@/components/ui/button'
import { useToast } from '@/components/ui/toast/use-toast'
import { ChevronLeft } from 'lucide-vue-next'
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { useRouter } from 'vue-router'
import { pb } from '@/lib/pocketbase'
import type { Reaction, Ticket } from '@/lib/types'
import { useAPI } from '@/api'
import type { Reaction } from '@/client/models'
import { handleError } from '@/lib/utils'
const api = useAPI()
const queryClient = useQueryClient()
const router = useRouter()
const { toast } = useToast()
const addReactionMutation = useMutation({
mutationFn: (values: Reaction): Promise<Reaction> => pb.collection('reactions').create(values),
onSuccess: (data: Ticket) => {
mutationFn: (values: Reaction): Promise<Reaction> => api.createReaction({ newReaction: values }),
onSuccess: (data: Reaction) => {
router.push({ name: 'reactions', params: { id: data.id } })
toast({
title: 'Reaction created',
description: 'The reaction has been created successfully'
})
queryClient.invalidateQueries({ queryKey: ['reactions'] })
},
onError: handleError
onError: handleError('Failed to create reaction')
})
</script>
<template>
<ColumnHeader>
<Button @click="router.push({ name: 'reactions' })" variant="outline" class="sm:hidden">
<Button @click="router.push({ name: 'reactions' })" variant="outline" class="md:hidden">
<ChevronLeft class="mr-2 size-4" />
Back
</Button>
+455
View File
@@ -0,0 +1,455 @@
<script setup lang="ts">
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
import { toast } from '@/components/ui/toast'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { defineRule, useForm } from 'vee-validate'
import { ref, watch } from 'vue'
import { useAPI } from '@/api'
import type { Settings } from '@/client/models'
import { handleError } from '@/lib/utils'
const api = useAPI()
const queryClient = useQueryClient()
const submitDisabledReason = ref<string>('')
const isDemo = ref(false)
const { data: config } = useQuery({
queryKey: ['config'],
queryFn: () => api.getConfig()
})
watch(
() => config.value,
() => {
if (!config.value) return
if (config.value.flags.includes('demo')) {
isDemo.value = true
}
},
{ immediate: true }
)
const { data: settings } = useQuery({
queryKey: ['settings'],
queryFn: (): Promise<Settings> => api.getSettings()
})
defineRule('required', (value: string) => {
if (!value || !value.length) {
return 'This field is required'
}
return true
})
const { handleSubmit, validate, values, setValues } = useForm({
initialValues: {
meta: {
appName: '',
appUrl: '',
senderName: '',
senderAddress: ''
},
smtp: {
enabled: false,
host: '',
port: 0,
username: '',
password: '',
authMethod: '',
tls: false,
localName: ''
}
},
validationSchema: {
'meta.appName': 'required',
'meta.appUrl': 'required',
'meta.senderName': 'required',
'meta.senderAddress': 'required',
'smtp.host': (val: string, values: any) =>
values.smtp && values.smtp.enabled ? (!val ? 'This field is required' : true) : true,
'smtp.port': (val: number, values: any) =>
values.smtp && values.smtp.enabled ? (!val ? 'This field is required' : true) : true,
'smtp.username': (val: string, values: any) =>
values.smtp && values.smtp.enabled ? (!val ? 'This field is required' : true) : true,
// 'smtp.password': (val: string, values: any) => true,
'smtp.authMethod': (val: string, values: any) =>
values.smtp && values.smtp.enabled ? (!val ? 'This field is required' : true) : true
// 'smtp.localName': (val: string, values: any) => true
}
})
const updateSettingsMutation = useMutation({
mutationFn: (vals: Settings): Promise<Settings> => api.updateSettings({ settings: vals }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['settings'] })
toast({
title: 'Settings updated',
description: 'The settings have been updated successfully'
})
},
onError: handleError('Failed to update settings')
})
function equalSettings(values: any, value?: Settings) {
if (!value) return false
return (
values.meta.appName === value.meta.appName &&
values.meta.appUrl === value.meta.appUrl &&
values.meta.senderName === value.meta.senderName &&
values.meta.senderAddress === value.meta.senderAddress &&
values.smtp.enabled === value.smtp.enabled &&
values.smtp.host === value.smtp.host &&
values.smtp.port === value.smtp.port &&
values.smtp.username === value.smtp.username &&
values.smtp.password === value.smtp.password &&
values.smtp.authMethod === value.smtp.authMethod &&
values.smtp.tls === value.smtp.tls &&
values.smtp.localName === value.smtp.localName
)
}
const updateSubmitDisabledReason = () => {
if (isDemo.value) {
submitDisabledReason.value = 'Settings cannot be edited in demo mode'
return
}
if (equalSettings(values, settings.value)) {
submitDisabledReason.value = 'Make changes to save'
return
}
validate({ mode: 'silent' }).then((res) => {
if (res.valid) {
submitDisabledReason.value = ''
} else {
submitDisabledReason.value = 'Please fix the errors'
}
})
}
watch(
() => settings.value,
() => {
if (!settings.value) return
setValues({
meta: {
appName: settings.value.meta.appName,
appUrl: settings.value.meta.appUrl,
senderName: settings.value.meta.senderName,
senderAddress: settings.value.meta.senderAddress
},
smtp: {
enabled: settings.value.smtp.enabled,
host: settings.value.smtp.host,
port: settings.value.smtp.port,
username: settings.value.smtp.username,
password: settings.value.smtp.password,
authMethod: settings.value.smtp.authMethod,
tls: settings.value.smtp.tls,
localName: settings.value.smtp.localName
}
})
updateSubmitDisabledReason()
},
{ immediate: true }
)
watch(
() => values,
() => updateSubmitDisabledReason(),
{ deep: true, immediate: true }
)
watch(
() => isDemo.value,
() => updateSubmitDisabledReason()
)
const onSubmit = handleSubmit((vals) => {
if (!settings.value) return
updateSettingsMutation.mutate({
...settings.value,
meta: {
...settings.value.meta,
appName: vals.meta.appName,
appUrl: vals.meta.appUrl,
senderName: vals.meta.senderName,
senderAddress: vals.meta.senderAddress
},
smtp: {
...settings.value.smtp,
enabled: vals.smtp.enabled,
host: vals.smtp.host,
port: vals.smtp.port,
username: vals.smtp.username,
password: vals.smtp.password,
authMethod: vals.smtp.authMethod,
tls: vals.smtp.tls,
localName: vals.smtp.localName
}
})
})
</script>
<template>
<form @submit="onSubmit" class="flex w-full flex-col items-start gap-4">
<FormField name="meta.appName" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="meta.appName" class="text-right">App Name</FormLabel>
<Input id="meta.appName" class="col-span-3" v-bind="componentField" />
<FormMessage />
</FormItem>
</FormField>
<FormField name="meta.appUrl" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="meta.appUrl" class="text-right">App URL</FormLabel>
<Input id="meta.appUrl" class="col-span-3" v-bind="componentField" />
<FormMessage />
</FormItem>
</FormField>
<FormField name="meta.senderName" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="meta.senderName" class="text-right">Sender Name</FormLabel>
<Input id="meta.senderName" class="col-span-3" v-bind="componentField" />
<FormMessage />
</FormItem>
</FormField>
<FormField name="meta.senderAddress" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="meta.senderAddress" class="text-right">Sender Address</FormLabel>
<Input id="meta.senderAddress" type="email" class="col-span-3" v-bind="componentField" />
<FormMessage />
</FormItem>
</FormField>
<!-- Card class="w-full">
<CardHeader>
<CardTitle>Logs</CardTitle>
</CardHeader>
<CardContent class="flex flex-col gap-4">
<FormField name="maxDays" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="maxDays" class="text-right">Max Days</FormLabel>
<Input id="maxDays" type="number" class="col-span-3" v-bind="componentField" />
<FormMessage />
</FormItem>
</FormField>
<FormField name="logLevel" v-slot="{ componentField }" validate-on-input>
<FormItem>
<FormLabel for="logLevel" class="text-right">Log Level</FormLabel>
<FormControl>
<Select id="logLevel" class="col-span-3" v-bind="componentField">
<SelectTrigger class="font-medium">
<SelectValue placeholder="Select log level" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="debug">Debug</SelectItem>
<SelectItem value="info">Info</SelectItem>
<SelectItem value="warn">Warn</SelectItem>
<SelectItem value="error">Error</SelectItem>
<SelectItem value="fatal">Fatal</SelectItem>
<SelectItem value="off">Off</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField name="logIP" v-slot="{ value, handleChange }">
<FormItem class="w-full items-center gap-2">
<FormLabel>Log IP</FormLabel>
<div class="flex flex-row items-center gap-2">
<FormControl>
<Switch :checked="value" @update:checked="handleChange" />
</FormControl>
<FormDescription>
Check to log the IP address of the user in the logs.
</FormDescription>
</div>
</FormItem>
<FormMessage />
</FormField>
</CardContent>
</Card -->
<Card class="w-full">
<CardHeader>
<CardTitle>SMTP</CardTitle>
</CardHeader>
<CardContent class="flex flex-col gap-4">
<FormField name="smtp.enabled" v-slot="{ value, handleChange }">
<FormItem class="w-full items-center gap-2">
<FormLabel>Enabled</FormLabel>
<div class="flex flex-row items-center gap-2">
<FormControl>
<Switch :checked="value" @update:checked="handleChange" />
</FormControl>
<FormDescription> Enable email sending through an SMTP server</FormDescription>
</div>
</FormItem>
</FormField>
<FormField name="smtp.host" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="smtp.host" class="text-right">Host</FormLabel>
<Input id="smtp.host" class="col-span-3" v-bind="componentField" />
<FormMessage />
<FormDescription>SMTP server hostname (e.g., smtp.gmail.com)</FormDescription>
</FormItem>
</FormField>
<FormField name="smtp.port" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="smtp.port" class="text-right">Port</FormLabel>
<Input id="smtp.port" type="number" class="col-span-3" v-bind="componentField" />
<FormMessage />
<FormDescription>Common ports: 25, 465 (SSL), 587 (TLS)</FormDescription>
</FormItem>
</FormField>
<FormField name="smtp.username" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="smtp.username" class="text-right">Username</FormLabel>
<Input id="smtp.username" class="col-span-3" v-bind="componentField" />
<FormMessage />
</FormItem>
</FormField>
<FormField name="smtp.password" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="smtp.password" class="text-right">Password</FormLabel>
<Input id="smtp.password" type="password" class="col-span-3" v-bind="componentField" />
<FormMessage />
</FormItem>
</FormField>
<FormField name="smtp.authMethod" v-slot="{ componentField }" validate-on-input>
<FormItem>
<FormLabel for="smtp.authMethod" class="text-right"> Authentication Method </FormLabel>
<Select id="smtp.authMethod" class="col-span-3" v-bind="componentField">
<SelectTrigger class="font-medium">
<SelectValue placeholder="Select authentication method" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="plain">Plain (default)</SelectItem>
<SelectItem value="login">Login</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
</FormField>
<FormField name="smtp.tls" v-slot="{ value, handleChange }">
<FormItem class="w-full items-center gap-2">
<FormLabel>TLS</FormLabel>
<div class="flex flex-row items-center gap-2">
<FormControl>
<Switch :checked="value" @update:checked="handleChange" />
</FormControl>
<FormDescription>Enable TLS/SSL for SMTP connections (recommended)</FormDescription>
</div>
</FormItem>
</FormField>
<FormField name="smtp.localName" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="smtp.localName" class="text-right">HELO domain</FormLabel>
<Input id="smtp.localName" class="col-span-3" v-bind="componentField" />
<FormDescription>Optional. Leave empty to use default hostname.</FormDescription>
<FormMessage />
</FormItem>
</FormField>
</CardContent>
</Card>
<!-- Card class="w-full">
<CardHeader>
<CardTitle>Backup</CardTitle>
</CardHeader>
<CardContent class="flex flex-col gap-4">
<FormField name="cron" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="cron" class="text-right">Cron Expression</FormLabel>
<Input id="cron" class="col-span-3" v-bind="componentField" />
<FormMessage />
</FormItem>
</FormField>
<FormField name="cronMaxKeep" v-slot="{ componentField }" validate-on-input>
<FormItem class="w-full">
<FormLabel for="cronMaxKeep" class="text-right">Max Keep</FormLabel>
<Input id="cronMaxKeep" type="number" class="col-span-3" v-bind="componentField" />
<FormMessage />
</FormItem>
</FormField>
</CardContent>
</Card -->
<Alert v-if="isDemo" variant="destructive">
<AlertTitle>Cannot save</AlertTitle>
<AlertDescription>{{ submitDisabledReason }}</AlertDescription>
</Alert>
<div class="flex gap-4">
<TooltipProvider :delay-duration="0">
<Tooltip>
<TooltipTrigger class="cursor-default">
<Button
type="submit"
:variant="submitDisabledReason !== '' ? 'secondary' : 'default'"
:disabled="submitDisabledReason !== ''"
:title="submitDisabledReason"
>
Save
</Button>
</TooltipTrigger>
<TooltipContent>
<span v-if="submitDisabledReason !== ''">
{{ submitDisabledReason }}
</span>
<span v-else> Save settings. </span>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</form>
</template>
+10 -7
View File
@@ -9,10 +9,12 @@ import { LoaderCircle } from 'lucide-vue-next'
import { useQuery } from '@tanstack/vue-query'
import { useRoute } from 'vue-router'
import { pb } from '@/lib/pocketbase'
import type { Type } from '@/lib/types'
import { useAPI } from '@/api'
import type { Sidebar } from '@/client/models'
import { cn } from '@/lib/utils'
const api = useAPI()
const route = useRoute()
defineProps<{
@@ -26,10 +28,11 @@ const {
error
} = useQuery({
queryKey: ['sidebar'],
queryFn: (): Promise<Array<any>> => pb.collection('sidebar').getFullList()
queryFn: (): Promise<Array<Sidebar>> => api.getSidebar()
})
const variant = (t: Type): 'default' | 'ghost' => (route.params.type === t.id ? 'default' : 'ghost')
const variant = (t: Sidebar): 'default' | 'ghost' =>
route.params.type === t.id ? 'default' : 'ghost'
</script>
<template>
@@ -37,16 +40,16 @@ const variant = (t: Type): 'default' | 'ghost' => (route.params.type === t.id ?
:data-collapsed="isCollapsed"
class="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2"
>
<div v-if="isPending" class="flex h-screen w-screen items-center justify-center">
<div v-if="isPending" class="flex items-center justify-center">
<LoaderCircle class="h-16 w-16 animate-spin text-primary" />
</div>
<Alert v-else-if="isError" variant="destructive" class="mb-4 h-screen w-screen">
<Alert v-else-if="isError" variant="destructive" class="mb-4">
<AlertTitle>Error</AlertTitle>
<AlertDescription>{{ error }}</AlertDescription>
</Alert>
<nav
v-else-if="sidebar"
class="grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2"
class="grid gap-1 px-2 group-data-[collapsed=true]:justify-center group-data-[collapsed=true]:px-2"
>
<template v-for="(typ, index) of sidebar">
<Tooltip v-if="isCollapsed" :key="`1-${index}`" :delay-duration="0">
+19 -3
View File
@@ -1,6 +1,12 @@
<script lang="ts" setup>
import NavItem from '@/components/sidebar/NavItem.vue'
import { computed } from 'vue'
import { useAuthStore } from '@/store/auth'
const authStore = useAuthStore()
export interface LinkProp {
title: string
label?: string
@@ -8,12 +14,22 @@ export interface LinkProp {
to: string
variant: 'default' | 'ghost'
disabled?: boolean
permission?: string
}
defineProps<{
const props = defineProps<{
isCollapsed: boolean
links: LinkProp[]
}>()
const filteredLinks = computed(() => {
return props.links.filter((link) => {
if (link.permission) {
return authStore.hasPermission(link.permission)
}
return true
})
})
</script>
<template>
@@ -22,9 +38,9 @@ defineProps<{
class="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2"
>
<nav
class="grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2"
class="grid gap-1 px-2 group-data-[collapsed=true]:justify-center group-data-[collapsed=true]:px-2"
>
<template v-for="(link, index) of links" :key="index">
<template v-for="(link, index) of filteredLinks" :key="index">
<NavItem :index="index" :link="link" :is-collapsed="isCollapsed" />
</template>
</nav>
@@ -1,94 +0,0 @@
<script setup lang="ts">
import { Button, buttonVariants } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { CircleUser } from 'lucide-vue-next'
import type { AuthModel } from 'pocketbase'
import { onMounted, ref } from 'vue'
import { pb } from '@/lib/pocketbase'
import { cn } from '@/lib/utils'
defineProps<{
isCollapsed: boolean
}>()
const variant = 'secondary'
interface User {
name: string
}
const user = ref<AuthModel | User>(pb.authStore.model)
const logout = () => {
pb.authStore.clear()
window.location.href = '/ui/login'
}
onMounted(() => {
pb.collection('users')
.authRefresh()
.catch(() => {
pb.authStore.clear()
window.location.href = '/ui/login'
})
})
</script>
<template>
<div class="group flex flex-col gap-4 py-2 data-[collapsed=true]:py-2">
<nav
v-if="user"
class="grid gap-1 px-2 group-[[data-collapsed=true]]:justify-center group-[[data-collapsed=true]]:px-2"
>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<div>
<Tooltip v-if="isCollapsed" :delay-duration="0">
<TooltipTrigger as-child>
<Button
:class="
cn(buttonVariants({ variant: variant, size: 'icon' }), 'mx-1 h-9 w-9 px-0')
"
>
<CircleUser class="size-4" />
<span class="sr-only">{{ user.name }}</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right" class="flex items-center gap-4">
{{ user.name }}
</TooltipContent>
</Tooltip>
<Button
v-else
:class="cn(buttonVariants({ variant: variant, size: 'sm' }), 'w-full justify-start')"
>
<CircleUser class="mr-2 size-4" />
{{ user.name }}
</Button>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem
@click="logout"
class="cursor-pointer text-muted-foreground transition-colors hover:text-foreground"
>
Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</nav>
</div>
</template>
+49 -32
View File
@@ -11,6 +11,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { useToast } from '@/components/ui/toast/use-toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { Check, ChevronLeft, CircleDot, Repeat } from 'lucide-vue-next'
@@ -19,59 +20,78 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { pb } from '@/lib/pocketbase'
import type { Ticket, Type } from '@/lib/types'
import { useAPI } from '@/api'
import type { ExtendedTicket, Ticket } from '@/client/models'
import type { Type } from '@/client/models/Type'
import { handleError } from '@/lib/utils'
const api = useAPI()
const queryClient = useQueryClient()
const router = useRouter()
const { toast } = useToast()
const props = defineProps<{
ticket: Ticket
ticket: ExtendedTicket
}>()
const {
isPending,
isError,
data: types,
error
} = useQuery({
const { data: types } = useQuery({
queryKey: ['types'],
queryFn: (): Promise<Array<Type>> =>
pb.collection('types').getFullList({
sort: '-created'
})
queryFn: (): Promise<Array<Type>> => api.listTypes()
})
const changeTypeMutation = useMutation({
mutationFn: (typeID: string): Promise<Ticket> =>
pb.collection('tickets').update(props.ticket.id, {
type: typeID
}),
api.updateTicket({ id: props.ticket.id, ticketUpdate: { type: typeID } }),
onSuccess: (data: Ticket) => {
queryClient.invalidateQueries({ queryKey: ['tickets'] })
// router.push({ name: 'tickets', params: { type: data.type, id: props.ticket.id } })
queryClient.invalidateQueries({ queryKey: ['sidebar'] })
toast({
title: 'Type changed',
description: 'The ticket type has been updated'
})
router.push({ name: 'tickets', params: { type: data.type, id: props.ticket.id } })
},
onError: handleError
onError: handleError('Failed to change ticket type')
})
const closeTicketMutation = useMutation({
mutationFn: (): Promise<Ticket> =>
pb.collection('tickets').update(props.ticket.id, {
open: !props.ticket.open
}),
api.updateTicket({ id: props.ticket.id, ticketUpdate: { open: !props.ticket.open } }),
onSuccess: (data: Ticket) => {
queryClient.invalidateQueries({ queryKey: ['tickets'] })
toast({
title: data.open ? 'Ticket reopened' : 'Ticket closed',
description: data.open
? 'The ticket has been reopened successfully'
: 'The ticket has been closed successfully'
})
if (!data.open) {
router.push({ name: 'tickets', params: { type: props.ticket.expand.type.id } })
router.push({ name: 'tickets', params: { type: props.ticket.type } })
}
},
onError: handleError
onError: handleError('Failed to update ticket status')
})
const otherTypes = computed(() => types.value?.filter((t) => t.id !== props.ticket.expand.type.id))
const ticketType = computed(() => types.value?.find((t) => t.id === props.ticket.type))
const otherTypes = computed(() => types.value?.filter((t) => t.id !== props.ticket.type))
const closeTicketDialogOpen = ref(false)
const deleteMutation = useMutation({
mutationFn: () => api.deleteTicket({ id: props.ticket.id }),
onSuccess: () => {
queryClient.removeQueries({ queryKey: ['tickets', props.ticket.id] })
queryClient.invalidateQueries({ queryKey: ['tickets'] })
toast({
title: 'Ticket deleted',
description: 'The ticket has been deleted successfully'
})
router.push({ name: 'tickets' })
},
onError: handleError('Failed to delete ticket')
})
</script>
<template>
@@ -79,7 +99,7 @@ const closeTicketDialogOpen = ref(false)
<Button
@click="router.push({ name: 'tickets', params: { type: ticket.type } })"
variant="outline"
class="sm:hidden"
class="md:hidden"
>
<ChevronLeft class="mr-2 size-4" />
Back
@@ -90,8 +110,8 @@ const closeTicketDialogOpen = ref(false)
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="outline" :disabled="!ticket">
<Icon :name="ticket.expand.type.icon" class="mr-2 size-4" />
{{ ticket.expand.type.singular }}
<Icon :name="ticketType?.icon ?? ''" class="mr-2 size-4" />
{{ ticketType?.singular }}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
@@ -144,7 +164,7 @@ const closeTicketDialogOpen = ref(false)
<Tooltip>
<TooltipTrigger as-child>
<div>
<TicketUserSelect :key="ticket.owner" :uID="ticket.owner" :ticket="ticket" />
<TicketUserSelect :key="ticket.owner" :ticket="ticket" />
</div>
</TooltipTrigger>
<TooltipContent>Change User</TooltipContent>
@@ -152,12 +172,9 @@ const closeTicketDialogOpen = ref(false)
<div class="-mx-1 flex-1" />
<DeleteDialog
v-if="ticket"
:collection="'tickets'"
:id="ticket.id"
:name="ticket.name"
singular="Ticket"
:to="{ name: 'tickets' }"
:queryKey="['tickets']"
@delete="deleteMutation.mutate"
/>
</ColumnHeader>
</template>
+19 -11
View File
@@ -2,6 +2,7 @@
import ColumnHeader from '@/components/layout/ColumnHeader.vue'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { useToast } from '@/components/ui/toast/use-toast'
import { Check, Repeat } from 'lucide-vue-next'
@@ -9,30 +10,39 @@ import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { pb } from '@/lib/pocketbase'
import type { Ticket } from '@/lib/types'
import { useAPI } from '@/api'
import type { ExtendedTicket, Ticket } from '@/client/models'
import { handleError } from '@/lib/utils'
const api = useAPI()
const queryClient = useQueryClient()
const router = useRouter()
const { toast } = useToast()
const props = defineProps<{
ticket: Ticket
ticket: ExtendedTicket
}>()
const resolution = ref(props.ticket.resolution)
const closeTicketMutation = useMutation({
mutationFn: (): Promise<Ticket> =>
pb.collection('tickets').update(props.ticket.id, {
open: !props.ticket.open,
resolution: resolution.value
api.updateTicket({
id: props.ticket.id,
ticketUpdate: { open: !props.ticket.open, resolution: resolution.value }
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tickets'] })
router.push({ name: 'tickets', params: { type: props.ticket.expand.type.id } })
toast({
title: props.ticket.open ? 'Ticket closed' : 'Ticket reopened',
description: props.ticket.open
? 'The ticket has been closed successfully'
: 'The ticket has been reopened successfully'
})
router.push({ name: 'tickets', params: { type: props.ticket.type } })
},
onError: handleError
onError: handleError('Failed to update ticket status')
})
const closeButtonDisabled = false // computed(() => !props.ticket.open || message.value == '')
@@ -52,9 +62,7 @@ const closeButtonDisabled = false // computed(() => !props.ticket.open || messag
<Check v-if="ticket.open" class="mr-2 h-4 w-4" />
<Repeat v-else class="mr-2 h-4 w-4" />
{{
ticket?.open
? 'Close ' + props.ticket.expand.type.singular
: 'Reopen ' + props.ticket.expand.type.singular
ticket?.open ? 'Close ' + props.ticket.typeSingular : 'Reopen ' + props.ticket.typeSingular
}}
</Button>
</ColumnHeader>
+20 -7
View File
@@ -10,17 +10,21 @@ import {
DialogTitle
} from '@/components/ui/dialog'
import { Textarea } from '@/components/ui/textarea'
import { useToast } from '@/components/ui/toast/use-toast'
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { pb } from '@/lib/pocketbase'
import type { Ticket } from '@/lib/types'
import { useAPI } from '@/api'
import type { Ticket } from '@/client/models'
import { handleError } from '@/lib/utils'
const api = useAPI()
const queryClient = useQueryClient()
const router = useRouter()
const { toast } = useToast()
const model = defineModel<boolean>()
@@ -32,17 +36,26 @@ const resolution = ref(props.ticket.resolution)
const closeTicketMutation = useMutation({
mutationFn: (): Promise<Ticket> =>
pb.collection('tickets').update(props.ticket.id, {
open: !props.ticket.open,
resolution: resolution.value
api.updateTicket({
id: props.ticket.id,
ticketUpdate: {
open: !props.ticket.open,
resolution: resolution.value
}
}),
onSuccess: (data: Ticket) => {
queryClient.invalidateQueries({ queryKey: ['tickets'] })
toast({
title: data.open ? 'Ticket reopened' : 'Ticket closed',
description: data.open
? 'The ticket has been reopened successfully'
: 'The ticket has been closed successfully'
})
if (!data.open) {
router.push({ name: 'tickets', params: { type: props.ticket.expand.type.id } })
router.push({ name: 'tickets', params: { type: props.ticket.type } })
}
},
onError: handleError
onError: handleError('Failed to update ticket status')
})
</script>
+74 -52
View File
@@ -19,6 +19,7 @@ import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Separator } from '@/components/ui/separator'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { useToast } from '@/components/ui/toast/use-toast'
import { Edit } from 'lucide-vue-next'
@@ -26,12 +27,24 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import { pb } from '@/lib/pocketbase'
import type { Ticket, Type } from '@/lib/types'
import { useAPI } from '@/api'
import type {
ExtendedComment,
ExtendedTask,
ExtendedTicket,
Link,
ModelFile,
Ticket,
TimelineEntry,
Type
} from '@/client/models'
import { handleError } from '@/lib/utils'
const api = useAPI()
const route = useRoute()
const queryClient = useQueryClient()
const { toast } = useToast()
defineProps<{
selectedType: Type
@@ -49,34 +62,61 @@ const {
error
} = useQuery({
queryKey: ['tickets', id.value],
queryFn: (): Promise<Ticket> =>
pb.collection('tickets').getOne(id.value, {
expand:
'type,owner,comments_via_ticket.author,files_via_ticket,timeline_via_ticket,links_via_ticket,tasks_via_ticket.owner'
})
queryFn: (): Promise<ExtendedTicket> => api.getTicket({ id: id.value })
})
const { data: timeline } = useQuery({
queryKey: ['timeline', id.value],
queryFn: (): Promise<Array<TimelineEntry>> => api.listTimeline({ ticket: id.value })
})
const { data: tasks } = useQuery({
queryKey: ['tasks', id.value],
queryFn: (): Promise<Array<ExtendedTask>> => api.listTasks({ ticket: id.value })
})
const { data: comments } = useQuery({
queryKey: ['comments', id.value],
queryFn: (): Promise<Array<ExtendedComment>> => api.listComments({ ticket: id.value })
})
const { data: files } = useQuery({
queryKey: ['files', id.value],
queryFn: (): Promise<Array<ModelFile>> => api.listFiles({ ticket: id.value })
})
const { data: links } = useQuery({
queryKey: ['links', id.value],
queryFn: (): Promise<Array<Link>> => api.listLinks({ ticket: id.value })
})
const editDescriptionMutation = useMutation({
mutationFn: () =>
pb.collection('tickets').update(id.value, {
description: message.value
}),
api.updateTicket({ id: id.value, ticketUpdate: { description: message.value } }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tickets', id.value] })
toast({
title: 'Ticket updated',
description: 'The ticket description has been updated'
})
editMode.value = false
},
onError: handleError
onError: handleError('Failed to update description')
})
const edit = () => (editMode.value = true)
const editStateMutation = useMutation({
mutationFn: (state: Record<string, any>): Promise<Ticket> =>
pb.collection('tickets').update(id.value, {
state: state
}),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['tickets', id.value] }),
onError: handleError
api.updateTicket({ id: id.value, ticketUpdate: { state } }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['tickets', id.value] })
toast({
title: 'Ticket updated',
description: 'The ticket state has been updated'
})
},
onError: handleError('Failed to update state')
})
const taskStatus = computed(() => {
@@ -84,26 +124,24 @@ const taskStatus = computed(() => {
return 'pending'
}
const tasks = ticket.value.expand.tasks_via_ticket
if (tasks.every((task) => !task.open)) {
if (tasks.value && tasks.value.every((task) => !task.open)) {
return 'completed'
}
if (tasks.every((task) => task.open)) {
if (tasks.value && tasks.value.every((task) => task.open)) {
return 'open'
}
return 'pending'
})
const updateDescription = (value: string) => (message.value = value)
const updateDescription = (value: string | undefined) => (message.value = value ?? '')
</script>
<template>
<TanView :isError="isError" :isPending="isPending" :error="error">
<template v-if="ticket">
<TicketActionBar :ticket="ticket" class="shrink-0" />
<TicketActionBar :ticket="ticket" />
<ColumnBody>
<ColumnBodyContainer class="flex-col gap-4 xl:flex-row">
<div class="flex flex-1 flex-col gap-4">
@@ -134,67 +172,51 @@ const updateDescription = (value: string) => (message.value = value)
<TabsTrigger value="timeline">
Timeline
<Badge
v-if="
ticket.expand.timeline_via_ticket &&
ticket.expand.timeline_via_ticket.length > 0
"
v-if="timeline && timeline.length > 0"
variant="outline"
class="ml-2 hidden sm:inline-flex"
>
{{
ticket.expand.timeline_via_ticket
? ticket.expand.timeline_via_ticket.length
: 0
}}
{{ timeline.length }}
</Badge>
</TabsTrigger>
<TabsTrigger value="tasks">
Tasks
<Badge
v-if="
ticket.expand.tasks_via_ticket && ticket.expand.tasks_via_ticket.length > 0
"
v-if="tasks && tasks.length > 0"
variant="outline"
class="ml-2 hidden sm:inline-flex"
>
{{ ticket.expand.tasks_via_ticket ? ticket.expand.tasks_via_ticket.length : 0 }}
{{ tasks.length }}
<StatusIcon :status="taskStatus" class="size-6" />
</Badge>
</TabsTrigger>
<TabsTrigger value="comments">
Comments
<Badge
v-if="
ticket.expand.comments_via_ticket &&
ticket.expand.comments_via_ticket.length > 0
"
v-if="comments && comments.length > 0"
variant="outline"
class="ml-2 hidden sm:inline-flex"
>
{{
ticket.expand.comments_via_ticket
? ticket.expand.comments_via_ticket.length
: 0
}}
{{ comments.length }}
</Badge>
</TabsTrigger>
</TabsList>
<TicketTab value="timeline">
<TicketTimeline :ticket="ticket" :timeline="ticket.expand.timeline_via_ticket" />
<TicketTimeline :ticket="ticket" :timeline="timeline" />
</TicketTab>
<TicketTab value="tasks">
<TicketTasks :ticket="ticket" :tasks="ticket.expand.tasks_via_ticket" />
<TicketTasks :ticket="ticket" :tasks="tasks" />
</TicketTab>
<TicketTab value="comments">
<TicketComments :ticket="ticket" :comments="ticket.expand.comments_via_ticket" />
<TicketComments :ticket="ticket" :comments="comments" />
</TicketTab>
</Tabs>
<Separator class="xl:hidden" />
</div>
<div class="flex flex-col gap-4 xl:w-96 xl:flex-initial">
<div>
<div class="flex h-10 flex-row items-center justify-between text-muted-foreground">
<span class="text-sm font-semibold"> Details </span>
<div class="flex h-10 flex-row items-center justify-between">
<span class="text-sm font-medium"> Details </span>
</div>
<JSONSchemaFormFields
:modelValue="ticket.state"
@@ -203,14 +225,14 @@ const updateDescription = (value: string) => (message.value = value)
/>
</div>
<Separator />
<TicketLinks :ticket="ticket" :links="ticket.expand.links_via_ticket" />
<TicketLinks :ticket="ticket" :links="links" />
<Separator />
<TicketFiles :ticket="ticket" :files="ticket.expand.files_via_ticket" />
<TicketFiles :ticket="ticket" :files="files" />
</div>
</ColumnBodyContainer>
</ColumnBody>
<Separator />
<TicketCloseBar :ticket="ticket" class="shrink-0" />
<TicketCloseBar :ticket="ticket" />
</template>
</TanView>
</template>
+28 -10
View File
@@ -1,16 +1,20 @@
<script setup lang="ts">
import DynamicInput from '@/components/input/DynamicInput.vue'
import { Separator } from '@/components/ui/separator'
import { useToast } from '@/components/ui/toast/use-toast'
import { useMutation, useQueryClient } from '@tanstack/vue-query'
import format from 'date-fns/format'
import { ref } from 'vue'
import { pb } from '@/lib/pocketbase'
import type { Ticket } from '@/lib/types'
import { useAPI } from '@/api'
import type { Ticket } from '@/client/models'
import { handleError } from '@/lib/utils'
const api = useAPI()
const queryClient = useQueryClient()
const { toast } = useToast()
const props = defineProps<{
ticket: Ticket
@@ -19,12 +23,21 @@ const props = defineProps<{
const name = ref(props.ticket.name)
const editNameMutation = useMutation({
mutationFn: () =>
pb.collection('tickets').update(props.ticket.id, {
name: name.value
mutationFn: (): Promise<Ticket> =>
api.updateTicket({
id: props.ticket.id,
ticketUpdate: {
name: name.value
}
}),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['tickets', props.ticket.id] }),
onError: handleError
onSuccess: (data: Ticket) => {
toast({
title: 'Ticket updated',
description: 'The ticket name has been updated'
})
queryClient.invalidateQueries({ queryKey: ['tickets', data.id] })
},
onError: handleError('Failed to update ticket name')
})
const updateName = (value: string) => {
@@ -35,18 +48,23 @@ const updateName = (value: string) => {
<template>
<span class="text-4xl font-bold">
<DynamicInput :modelValue="ticket.name" @update:modelValue="updateName" class="-mx-1" />
<DynamicInput
id="name"
:modelValue="ticket.name"
@update:modelValue="updateName"
class="-mx-1"
/>
</span>
<div class="flex flex-col items-stretch gap-1 text-xs text-muted-foreground md:h-4 md:flex-row">
<div>
Created:
{{ format(new Date(ticket.created), 'PPpp') }}
{{ format(ticket.created, 'PPpp') }}
</div>
<Separator orientation="vertical" class="hidden md:block" />
<div>
Updated:
{{ format(new Date(ticket.updated), 'PPpp') }}
{{ format(ticket.updated, 'PPpp') }}
</div>
</div>
</template>

Some files were not shown because too many files have changed in this diff Show More