add workflow client pages

This commit is contained in:
Simon Larsen
2023-02-01 14:10:29 +00:00
parent 9ca2449843
commit 93dffac15d
13 changed files with 511 additions and 3 deletions

View File

@@ -97,6 +97,12 @@ enum Permission {
CanEditStatusPageResource = 'CanEditStatusPageResource',
CanReadStatusPageResource = 'CanReadStatusPageResource',
// Workflow Permissions (Owner Permission)
CanCreateWorkflow = 'CanCreateWorkflow',
CanDeleteWorkflow = 'CanDeleteWorkflow',
CanEditWorkflow = 'CanEditWorkflow',
CanReadWorkflow = 'CanReadWorkflow',
// Probe Permissions (Owner Permission)
CanCreateStatusPageGroup = 'CanCreateStatusPageGroup',
CanDeleteStatusPageGroup = 'CanDeleteStatusPageGroup',
@@ -811,6 +817,42 @@ export class PermissionHelper {
isAccessControlPermission: false,
},
{
permission: Permission.CanCreateWorkflow,
title: 'Can Create Workflow',
description:
'A user assigned this permission can create Workflow in this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanDeleteWorkflow,
title: 'Can Delete Workflow',
description:
'A user assigned this permission can delete Workflow in this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanEditWorkflow,
title: 'Can Edit Workflow',
description:
'A user assigned this permission can edit Workflow in this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanReadWorkflow,
title: 'Can Read Workflow',
description:
'A user assigned this permission can read Workflow in this project.',
isAssignableToTenant: true,
isAccessControlPermission: false,
},
{
permission: Permission.CanCreateStatusPageGroup,
title: 'Can Create Status Page Group',

View File

@@ -0,0 +1,10 @@
import PostgresDatabase from '../Infrastructure/PostgresDatabase';
import Model from 'Model/Models/Workflow';
import DatabaseService from './DatabaseService';
export class Service extends DatabaseService<Model> {
public constructor(postgresDatabase?: PostgresDatabase) {
super(Model, postgresDatabase);
}
}
export default new Service();

View File

@@ -16,8 +16,8 @@ const NotFound: FunctionComponent<ComponentProps> = (
props: ComponentProps
): ReactElement => {
return (
<div className="mx-auto max-w-full sm:px-6 lg:px-8 rounded-lg drop-shadow-md">
<div className="min-h-full bg-white py-16 px-6 sm:py-24 md:grid md:place-items-center lg:px-8">
<div className="mx-auto max-w-full sm:px-6 lg:px-8 rounded-lg">
<div className="min-h-full py-16 px-6 sm:py-24 md:grid md:place-items-center lg:px-8">
<div className="mx-auto">
<main className="sm:flex">
<p className="text-4xl tracking-tight text-indigo-600 sm:text-5xl">

View File

@@ -17,6 +17,8 @@ import OngoingScheduledEvents from './Pages/Home/OngingScheduledMaintenance';
import useAsyncEffect from 'use-async-effect';
import Workflows from './Pages/Workflow/Workflows';
import StatusPages from './Pages/StatusPages/StatusPages';
import StatusPagesView from './Pages/StatusPages/View/Index';
import StatusPagesViewDelete from './Pages/StatusPages/View/Delete';
@@ -381,6 +383,19 @@ const App: FunctionComponent = () => {
}
/>
{/* Workflows */}
<PageRoute
path={RouteMap[PageMap.WORKFLOWS]?.toString() || ''}
element={
<Workflows
pageRoute={RouteMap[PageMap.WORKFLOWS] as Route}
currentProject={selectedProject}
/>
}
/>
{/* Status Pages */}
<PageRoute

View File

@@ -11,7 +11,7 @@ const PageNotFound: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
return (
<Page title={'Page Not Found'} breadcrumbLinks={[]}>
<Page title={''} breadcrumbLinks={[]}>
<NotFound
homeRoute={new Route('/dashboard')}
supportEmail={new Email('support@oneuptime.com')}

View File

@@ -0,0 +1,99 @@
import Route from 'Common/Types/API/Route';
import Page from 'CommonUI/src/Components/Page/Page';
import React, { FunctionComponent, ReactElement } from 'react';
import PageMap from '../../Utils/PageMap';
import RouteMap, { RouteUtil } from '../../Utils/RouteMap';
import PageComponentProps from '../PageComponentProps';
import ModelTable from 'CommonUI/src/Components/ModelTable/ModelTable';
import Workflow from 'Model/Models/Workflow';
import FieldType from 'CommonUI/src/Components/Types/FieldType';
import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType';
import { IconProp } from 'CommonUI/src/Components/Icon/Icon';
import Navigation from 'CommonUI/src/Utils/Navigation';
const Workflows: FunctionComponent<PageComponentProps> = (
_props: PageComponentProps
): ReactElement => {
return (
<Page
title={'Workflows'}
breadcrumbLinks={[
{
title: 'Project',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.HOME] as Route
),
},
{
title: 'Workflows',
to: RouteUtil.populateRouteParams(
RouteMap[PageMap.STATUS_PAGES] as Route
),
},
]}
>
<ModelTable<Workflow>
modelType={Workflow}
id="status-page-table"
isDeleteable={false}
isEditable={true}
isCreateable={true}
name="Workflows"
isViewable={true}
cardProps={{
icon: IconProp.CheckCircle,
title: 'Workflows',
description:
'Here is a list of workflows for this project.',
}}
noItemsMessage={'No workflows found.'}
formFields={[
{
field: {
name: true,
},
title: 'Name',
fieldType: FormFieldSchemaType.Text,
required: true,
placeholder: 'Workflow Name',
validation: {
minLength: 2,
},
},
{
field: {
description: true,
},
title: 'Description',
fieldType: FormFieldSchemaType.LongText,
required: true,
placeholder: 'Description',
}
]}
showRefreshButton={true}
showFilterButton={true}
viewPageRoute={Navigation.getCurrentRoute()}
columns={[
{
field: {
name: true,
},
title: 'Name',
type: FieldType.Text,
isFilterable: true,
},
{
field: {
description: true,
},
title: 'Description',
type: FieldType.Text,
isFilterable: true,
}
]}
/>
</Page>
);
};
export default Workflows;

View File

@@ -98,6 +98,10 @@ enum PageMap {
USER_PROFILE = 'USER_PROFILE',
ACTIVE_INCIDENTS = 'ACTIVE_INCIDENTS',
PROJECT_INVITATIONS = 'PROJECT_INVITATIONS',
// WORKFLOW
WORKFLOWS = 'WORKFLOWS',
WORKFLOW_VIEW = 'WORKFLOW_VIEW'
}
export default PageMap;

View File

@@ -260,6 +260,11 @@ const RouteMap: Dictionary<Route> = {
`/dashboard/${RouteParams.ProjectID}/settings/labels`
),
// labels.
[PageMap.WORKFLOWS]: new Route(
`/dashboard/${RouteParams.ProjectID}/workflows`
),
// logout.
[PageMap.LOGOUT]: new Route(`/dashboard/logout`),
};

View File

@@ -20,6 +20,11 @@ import ProjectService, {
Service as ProjectServiceType,
} from 'CommonServer/Services/ProjectService';
import Workflow from 'Model/Models/Workflow';
import WorkflowService, {
Service as WorkflowServiceType,
} from 'CommonServer/Services/WorkflowService';
import Probe from 'Model/Models/Probe';
import ProbeService, {
Service as ProbeServiceType,
@@ -268,6 +273,14 @@ app.use(
).getRouter()
);
app.use(
`/${APP_NAME.toLocaleLowerCase()}`,
new BaseAPI<Workflow, WorkflowServiceType>(
Workflow,
WorkflowService
).getRouter()
);
app.use(
`/${APP_NAME.toLocaleLowerCase()}`,
new BaseAPI<Domain, DomainServiceType>(Domain, DomainService).getRouter()

View File

@@ -63,6 +63,10 @@ import BillingInvoice from './BillingInvoice';
import GreenlockChallenge from './GreenlockChallenge';
import GreenlockCertificate from './GreenlockCertificate';
// Workflows.
import Workflow from './Workflow';
export default [
User,
Probe,
@@ -109,4 +113,7 @@ export default [
GreenlockChallenge,
GreenlockCertificate,
Workflow
];

312
Model/Models/Workflow.ts Normal file
View File

@@ -0,0 +1,312 @@
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import User from './User';
import Project from './Project';
import CrudApiEndpoint from 'Common/Types/Database/CrudApiEndpoint';
import SlugifyColumn from 'Common/Types/Database/SlugifyColumn';
import Route from 'Common/Types/API/Route';
import TableColumnType from 'Common/Types/Database/TableColumnType';
import TableColumn from 'Common/Types/Database/TableColumn';
import ColumnType from 'Common/Types/Database/ColumnType';
import ObjectID from 'Common/Types/ObjectID';
import ColumnLength from 'Common/Types/Database/ColumnLength';
import TableAccessControl from 'Common/Types/Database/AccessControl/TableAccessControl';
import Permission from 'Common/Types/Permission';
import ColumnAccessControl from 'Common/Types/Database/AccessControl/ColumnAccessControl';
import UniqueColumnBy from 'Common/Types/Database/UniqueColumnBy';
import TenantColumn from 'Common/Types/Database/TenantColumn';
import SingularPluralName from 'Common/Types/Database/SingularPluralName';
import BaseModel from 'Common/Models/BaseModel';
@TenantColumn('projectId')
@TableAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
delete: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanDeleteWorkflow,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanEditWorkflow,
],
})
@CrudApiEndpoint(new Route('/workflow'))
@SlugifyColumn('name', 'slug')
@Entity({
name: 'Workflow',
})
@SingularPluralName('Workflow', 'Workflows')
export default class Workflow extends BaseModel {
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
update: [],
})
@TableColumn({
manyToOneRelationColumn: 'projectId',
type: TableColumnType.Entity,
modelType: Project,
})
@ManyToOne(
(_type: string) => {
return Project;
},
{
eager: false,
nullable: true,
onDelete: 'CASCADE',
orphanedRowAction: 'nullify',
}
)
@JoinColumn({ name: 'projectId' })
public project?: Project = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
update: [],
})
@Index()
@TableColumn({
type: TableColumnType.ObjectID,
required: true,
canReadOnPopulate: true,
})
@Column({
type: ColumnType.ObjectID,
nullable: false,
transformer: ObjectID.getDatabaseTransformer(),
})
public projectId?: ObjectID = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanEditWorkflow,
],
})
@TableColumn({
required: true,
type: TableColumnType.ShortText,
canReadOnPopulate: true,
})
@Column({
nullable: false,
type: ColumnType.ShortText,
length: ColumnLength.ShortText,
})
@UniqueColumnBy('projectId')
public name?: string = undefined;
@ColumnAccessControl({
create: [],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
update: [],
})
@TableColumn({ required: true, unique: true, type: TableColumnType.Slug })
@Column({
nullable: false,
type: ColumnType.Slug,
length: ColumnLength.Slug,
})
public slug?: string = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanEditWorkflow,
],
})
@TableColumn({ required: false, type: TableColumnType.LongText })
@Column({
nullable: true,
type: ColumnType.LongText,
length: ColumnLength.LongText,
})
public description?: string = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
update: [],
})
@TableColumn({
manyToOneRelationColumn: 'createdByUserId',
type: TableColumnType.Entity,
modelType: User,
})
@ManyToOne(
(_type: string) => {
return User;
},
{
eager: false,
nullable: true,
onDelete: 'CASCADE',
orphanedRowAction: 'nullify',
}
)
@JoinColumn({ name: 'createdByUserId' })
public createdByUser?: User = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
update: [],
})
@TableColumn({ type: TableColumnType.ObjectID })
@Column({
type: ColumnType.ObjectID,
nullable: true,
transformer: ObjectID.getDatabaseTransformer(),
})
public createdByUserId?: ObjectID = undefined;
@ColumnAccessControl({
create: [],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
update: [],
})
@TableColumn({
manyToOneRelationColumn: 'deletedByUserId',
type: TableColumnType.ObjectID,
})
@ManyToOne(
(_type: string) => {
return User;
},
{
cascade: false,
eager: false,
nullable: true,
onDelete: 'CASCADE',
orphanedRowAction: 'nullify',
}
)
@JoinColumn({ name: 'deletedByUserId' })
public deletedByUser?: User = undefined;
@ColumnAccessControl({
create: [],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
update: [],
})
@TableColumn({ type: TableColumnType.ObjectID })
@Column({
type: ColumnType.ObjectID,
nullable: true,
transformer: ObjectID.getDatabaseTransformer(),
})
public deletedByUserId?: ObjectID = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanCreateWorkflow,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.CanReadWorkflow,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CanEditWorkflow,
],
})
@TableColumn({ isDefaultValueColumn: true, type: TableColumnType.Boolean })
@Column({
type: ColumnType.Boolean,
default: false,
})
public isEnabled?: boolean = undefined;
}

View File

@@ -70,6 +70,7 @@ const RouteMap: Dictionary<Route> = {
[PageMap.PREVIEW_RESET_PASSWORD]: new Route(
`/status-page/${RouteParams.StatusPageId}/reset-password/:token`
),
};
export class RouteUtil {