Compare commits
13 Commits
update-sam
...
add-loadin
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d3bc3a796b | ||
![]() |
9e64af4793 | ||
![]() |
b581f539e2 | ||
![]() |
aac1295c10 | ||
![]() |
e8f2802ee0 | ||
![]() |
75b3730a70 | ||
![]() |
af29dc9c3f | ||
![]() |
181cb5f335 | ||
![]() |
94e560c262 | ||
![]() |
f802061722 | ||
![]() |
58a7f6eec6 | ||
![]() |
5e11d3cc4d | ||
![]() |
399fb8312a |
@@ -4,7 +4,7 @@ WORKDIR /automatisch
|
|||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk --no-cache add --virtual build-dependencies python3 build-base && \
|
apk --no-cache add --virtual build-dependencies python3 build-base && \
|
||||||
yarn global add @automatisch/cli@0.8.0 --network-timeout 1000000 && \
|
yarn global add @automatisch/cli@0.7.1 --network-timeout 1000000 && \
|
||||||
rm -rf /usr/local/share/.cache/ && \
|
rm -rf /usr/local/share/.cache/ && \
|
||||||
apk del build-dependencies
|
apk del build-dependencies
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM automatischio/automatisch:0.8.0
|
FROM automatischio/automatisch:0.7.1
|
||||||
WORKDIR /automatisch
|
WORKDIR /automatisch
|
||||||
|
|
||||||
RUN apk add --no-cache openssl dos2unix
|
RUN apk add --no-cache openssl dos2unix
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"version": "0.8.0",
|
"version": "0.7.1",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"command": {
|
"command": {
|
||||||
|
@@ -3,16 +3,36 @@ import logger from '../../src/helpers/logger';
|
|||||||
import client from './client';
|
import client from './client';
|
||||||
import User from '../../src/models/user';
|
import User from '../../src/models/user';
|
||||||
import Role from '../../src/models/role';
|
import Role from '../../src/models/role';
|
||||||
|
import Permission from '../../src/models/permission';
|
||||||
import '../../src/config/orm';
|
import '../../src/config/orm';
|
||||||
|
|
||||||
async function fetchAdminRole() {
|
async function seedPermissionsIfNeeded() {
|
||||||
const role = await Role
|
const existingPermissions = await Permission.query().limit(1).first();
|
||||||
.query()
|
|
||||||
.where({
|
if (!existingPermissions) return;
|
||||||
key: 'admin'
|
|
||||||
})
|
const getPermission = (subject: string, actions: string[]) => actions.map(action => ({ subject, action }));
|
||||||
.limit(1)
|
|
||||||
.first();
|
await Permission.query().insert([
|
||||||
|
...getPermission('Connection', ['create', 'read', 'delete', 'update']),
|
||||||
|
...getPermission('Execution', ['read']),
|
||||||
|
...getPermission('Flow', ['create', 'delete', 'publish', 'read', 'update']),
|
||||||
|
...getPermission('Role', ['create', 'delete', 'read', 'update']),
|
||||||
|
...getPermission('User', ['create', 'delete', 'read', 'update']),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createOrFetchRole() {
|
||||||
|
const role = await Role.query().limit(1).first();
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
const createdRole = await Role.query().insertAndFetch({
|
||||||
|
name: 'Admin',
|
||||||
|
key: 'admin',
|
||||||
|
});
|
||||||
|
|
||||||
|
return createdRole;
|
||||||
|
}
|
||||||
|
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
@@ -23,7 +43,9 @@ export async function createUser(
|
|||||||
) {
|
) {
|
||||||
const UNIQUE_VIOLATION_CODE = '23505';
|
const UNIQUE_VIOLATION_CODE = '23505';
|
||||||
|
|
||||||
const role = await fetchAdminRole();
|
await seedPermissionsIfNeeded();
|
||||||
|
|
||||||
|
const role = await createOrFetchRole();
|
||||||
const userParams = {
|
const userParams = {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/backend",
|
"name": "@automatisch/backend",
|
||||||
"version": "0.8.0",
|
"version": "0.7.1",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"prebuild": "rm -rf ./dist"
|
"prebuild": "rm -rf ./dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/web": "^0.8.0",
|
"@automatisch/web": "^0.7.1",
|
||||||
"@bull-board/express": "^3.10.1",
|
"@bull-board/express": "^3.10.1",
|
||||||
"@casl/ability": "^6.5.0",
|
"@casl/ability": "^6.5.0",
|
||||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||||
@@ -53,8 +53,6 @@
|
|||||||
"graphql-type-json": "^0.3.2",
|
"graphql-type-json": "^0.3.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
"http-proxy-agent": "^7.0.0",
|
|
||||||
"https-proxy-agent": "^7.0.1",
|
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"knex": "^2.4.0",
|
"knex": "^2.4.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
@@ -108,7 +106,7 @@
|
|||||||
"url": "https://github.com/automatisch/automatisch/issues"
|
"url": "https://github.com/automatisch/automatisch/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@automatisch/types": "^0.8.0",
|
"@automatisch/types": "^0.7.1",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bull": "^3.15.8",
|
"@types/bull": "^3.15.8",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
|
@@ -6,7 +6,7 @@ import actions from './actions';
|
|||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'GitHub',
|
name: 'Github',
|
||||||
key: 'github',
|
key: 'github',
|
||||||
baseUrl: 'https://github.com',
|
baseUrl: 'https://github.com',
|
||||||
apiBaseUrl: 'https://api.github.com',
|
apiBaseUrl: 'https://api.github.com',
|
||||||
|
@@ -9,11 +9,11 @@ export default {
|
|||||||
// ref:
|
// ref:
|
||||||
// - https://docs.gitlab.com/ee/api/projects.html#list-all-projects
|
// - https://docs.gitlab.com/ee/api/projects.html#list-all-projects
|
||||||
// - https://docs.gitlab.com/ee/api/rest/index.html#keyset-based-pagination
|
// - https://docs.gitlab.com/ee/api/rest/index.html#keyset-based-pagination
|
||||||
|
|
||||||
const firstPageRequest = $.http.get('/api/v4/projects', {
|
const firstPageRequest = $.http.get('/api/v4/projects', {
|
||||||
params: {
|
params: {
|
||||||
simple: true,
|
simple: true,
|
||||||
pagination: 'keyset',
|
pagination: 'keyset',
|
||||||
membership: true,
|
|
||||||
order_by: 'id',
|
order_by: 'id',
|
||||||
sort: 'asc',
|
sort: 'asc',
|
||||||
},
|
},
|
||||||
|
@@ -6,7 +6,7 @@ import triggers from './triggers';
|
|||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'GitLab',
|
name: 'Gitlab',
|
||||||
key: 'gitlab',
|
key: 'gitlab',
|
||||||
baseUrl: 'https://gitlab.com',
|
baseUrl: 'https://gitlab.com',
|
||||||
apiBaseUrl: 'https://gitlab.com',
|
apiBaseUrl: 'https://gitlab.com',
|
||||||
|
@@ -1,191 +0,0 @@
|
|||||||
import { IJSONObject } from '@automatisch/types';
|
|
||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
type THeaders = {
|
|
||||||
__id: string;
|
|
||||||
header: string;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
type TSheetsResponse = {
|
|
||||||
sheets: {
|
|
||||||
properties: {
|
|
||||||
sheetId: string;
|
|
||||||
title: string;
|
|
||||||
};
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type TBody = {
|
|
||||||
requests: IJSONObject[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create worksheet',
|
|
||||||
key: 'createWorksheet',
|
|
||||||
description:
|
|
||||||
'Create a blank worksheet with a title. Optionally, provide headers.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Drive',
|
|
||||||
key: 'driveId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
'The Google Drive where your spreadsheet resides. If nothing is selected, then your personal Google Drive will be used.',
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listDrives',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Spreadsheet',
|
|
||||||
key: 'spreadsheetId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
dependsOn: ['parameters.driveId'],
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listSpreadsheets',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.driveId',
|
|
||||||
value: '{parameters.driveId}',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Title',
|
|
||||||
key: 'title',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Headers',
|
|
||||||
key: 'headers',
|
|
||||||
type: 'dynamic' as const,
|
|
||||||
required: false,
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: 'Header',
|
|
||||||
key: 'header',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Overwrite',
|
|
||||||
key: 'overwrite',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
value: false,
|
|
||||||
description:
|
|
||||||
'If a worksheet with the specified title exists, its content would be lost. Please, use with caution.',
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: 'Yes',
|
|
||||||
value: 'true',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'No',
|
|
||||||
value: 'false',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const {
|
|
||||||
data: { sheets },
|
|
||||||
} = await $.http.get<TSheetsResponse>(
|
|
||||||
`/v4/spreadsheets/${$.step.parameters.spreadsheetId}`
|
|
||||||
);
|
|
||||||
const selectedSheet = sheets.find(
|
|
||||||
(sheet) => sheet.properties.title === $.step.parameters.title
|
|
||||||
);
|
|
||||||
const headers = $.step.parameters.headers as THeaders;
|
|
||||||
const values = headers.map((entry) => entry.header);
|
|
||||||
|
|
||||||
const body: TBody = {
|
|
||||||
requests: [
|
|
||||||
{
|
|
||||||
addSheet: {
|
|
||||||
properties: {
|
|
||||||
title: $.step.parameters.title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
if ($.step.parameters.overwrite === 'true' && selectedSheet) {
|
|
||||||
body.requests.unshift({
|
|
||||||
deleteSheet: {
|
|
||||||
sheetId: selectedSheet.properties.sheetId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = await $.http.post(
|
|
||||||
`https://sheets.googleapis.com/v4/spreadsheets/${$.step.parameters.spreadsheetId}:batchUpdate`,
|
|
||||||
body
|
|
||||||
);
|
|
||||||
|
|
||||||
if (values.length) {
|
|
||||||
const body = {
|
|
||||||
requests: [
|
|
||||||
{
|
|
||||||
updateCells: {
|
|
||||||
rows: [
|
|
||||||
{
|
|
||||||
values: values.map((header) => ({
|
|
||||||
userEnteredValue: { stringValue: header },
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
fields: '*',
|
|
||||||
start: {
|
|
||||||
sheetId:
|
|
||||||
data.replies[data.replies.length - 1].addSheet.properties
|
|
||||||
.sheetId,
|
|
||||||
rowIndex: 0,
|
|
||||||
columnIndex: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data: response } = await $.http.post(
|
|
||||||
`https://sheets.googleapis.com/v4/spreadsheets/${$.step.parameters.spreadsheetId}:batchUpdate`,
|
|
||||||
body
|
|
||||||
);
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: response,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: data,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,5 +1,4 @@
|
|||||||
import createSpreadsheet from './create-spreadsheet';
|
import createSpreadsheet from './create-spreadsheet';
|
||||||
import createSpreadsheetRow from './create-spreadsheet-row';
|
import createSpreadsheetRow from './create-spreadsheet-row';
|
||||||
import createWorksheet from './create-worksheet';
|
|
||||||
|
|
||||||
export default [createSpreadsheet, createSpreadsheetRow, createWorksheet];
|
export default [createSpreadsheet, createSpreadsheetRow];
|
||||||
|
@@ -1,100 +0,0 @@
|
|||||||
import { IJSONArray, IJSONObject } from '@automatisch/types';
|
|
||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
type TBody = {
|
|
||||||
parent: IJSONObject;
|
|
||||||
properties: IJSONObject;
|
|
||||||
children: IJSONArray;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create database item',
|
|
||||||
key: 'createDatabaseItem',
|
|
||||||
description: 'Creates an item in a database.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Database',
|
|
||||||
key: 'databaseId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listDatabases',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Name',
|
|
||||||
key: 'name',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Content',
|
|
||||||
key: 'content',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
'The text to add to the page body. The max length for this field is 2000 characters. Any characters beyond 2000 will not be included.',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const name = $.step.parameters.name as string;
|
|
||||||
const truncatedName = name.slice(0, 2000);
|
|
||||||
const content = $.step.parameters.content as string;
|
|
||||||
const truncatedContent = content.slice(0, 2000);
|
|
||||||
|
|
||||||
const body: TBody = {
|
|
||||||
parent: {
|
|
||||||
database_id: $.step.parameters.databaseId,
|
|
||||||
},
|
|
||||||
properties: {},
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (name) {
|
|
||||||
body.properties.Name = {
|
|
||||||
title: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: truncatedName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content) {
|
|
||||||
body.children = [
|
|
||||||
{
|
|
||||||
object: 'block',
|
|
||||||
paragraph: {
|
|
||||||
rich_text: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: truncatedContent,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/v1/pages', body);
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: data,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,104 +0,0 @@
|
|||||||
import { IJSONArray, IJSONObject } from '@automatisch/types';
|
|
||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
type TBody = {
|
|
||||||
parent: IJSONObject;
|
|
||||||
properties: IJSONObject;
|
|
||||||
children: IJSONArray;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create page',
|
|
||||||
key: 'createPage',
|
|
||||||
description: 'Creates a page inside a parent page',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Parent page',
|
|
||||||
key: 'parentPageId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listParentPages',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Title',
|
|
||||||
key: 'title',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Content',
|
|
||||||
key: 'content',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
'The text to add to the page body. The max length for this field is 2000 characters. Any characters beyond 2000 will not be included.',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const parentPageId = $.step.parameters.parentPageId as string;
|
|
||||||
const title = $.step.parameters.title as string;
|
|
||||||
const truncatedTitle = title.slice(0, 2000);
|
|
||||||
const content = $.step.parameters.content as string;
|
|
||||||
const truncatedContent = content.slice(0, 2000);
|
|
||||||
|
|
||||||
const body: TBody = {
|
|
||||||
parent: {
|
|
||||||
page_id: parentPageId,
|
|
||||||
},
|
|
||||||
properties: {},
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (title) {
|
|
||||||
body.properties.title = {
|
|
||||||
type: 'title',
|
|
||||||
title: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: truncatedTitle,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content) {
|
|
||||||
body.children = [
|
|
||||||
{
|
|
||||||
object: 'block',
|
|
||||||
type: 'paragraph',
|
|
||||||
paragraph: {
|
|
||||||
rich_text: [
|
|
||||||
{
|
|
||||||
type: 'text',
|
|
||||||
text: {
|
|
||||||
content: truncatedContent,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/v1/pages', body);
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: data,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,70 +0,0 @@
|
|||||||
import { IJSONArray, IJSONObject } from '@automatisch/types';
|
|
||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
type TBody = {
|
|
||||||
filter: IJSONObject;
|
|
||||||
sorts: IJSONArray;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Find database item',
|
|
||||||
key: 'findDatabaseItem',
|
|
||||||
description: 'Searches for an item in a database by property.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Database',
|
|
||||||
key: 'databaseId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listDatabases',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Name',
|
|
||||||
key: 'name',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const databaseId = $.step.parameters.databaseId as string;
|
|
||||||
const name = $.step.parameters.name as string;
|
|
||||||
const truncatedName = name.slice(0, 2000);
|
|
||||||
|
|
||||||
const body: TBody = {
|
|
||||||
filter: {
|
|
||||||
property: 'Name',
|
|
||||||
rich_text: {
|
|
||||||
equals: truncatedName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sorts: [
|
|
||||||
{
|
|
||||||
timestamp: 'last_edited_time',
|
|
||||||
direction: 'descending',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post(
|
|
||||||
`/v1/databases/${databaseId}/query`,
|
|
||||||
body
|
|
||||||
);
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: data.results[0],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,5 +0,0 @@
|
|||||||
import createDatabaseItem from './create-database-item';
|
|
||||||
import createPage from './create-page';
|
|
||||||
import findDatabaseItem from './find-database-item';
|
|
||||||
|
|
||||||
export default [createDatabaseItem, createPage, findDatabaseItem];
|
|
@@ -1,4 +1,3 @@
|
|||||||
import listDatabases from './list-databases';
|
import listDatabases from './list-databases';
|
||||||
import listParentPages from './list-parent-pages';
|
|
||||||
|
|
||||||
export default [listDatabases, listParentPages];
|
export default [listDatabases];
|
||||||
|
@@ -1,70 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
type Page = {
|
|
||||||
id: string;
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
title: [
|
|
||||||
{
|
|
||||||
plain_text: string;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
parent: {
|
|
||||||
workspace: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
type ResponseData = {
|
|
||||||
results: Page[];
|
|
||||||
next_cursor?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Payload = {
|
|
||||||
filter: {
|
|
||||||
value: 'page';
|
|
||||||
property: 'object';
|
|
||||||
};
|
|
||||||
start_cursor?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List parent pages',
|
|
||||||
key: 'listParentPages',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const parentPages: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
error: IJSONObject | null;
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
error: null,
|
|
||||||
};
|
|
||||||
const payload: Payload = {
|
|
||||||
filter: {
|
|
||||||
value: 'page',
|
|
||||||
property: 'object',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
do {
|
|
||||||
const response = await $.http.post<ResponseData>('/v1/search', payload);
|
|
||||||
|
|
||||||
payload.start_cursor = response.data.next_cursor;
|
|
||||||
|
|
||||||
const topLevelPages = response.data.results.filter(
|
|
||||||
(page) => page.parent.workspace
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const pages of topLevelPages) {
|
|
||||||
parentPages.data.push({
|
|
||||||
value: pages.id as string,
|
|
||||||
name: pages.properties.title.title[0].plain_text as string,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} while (payload.start_cursor);
|
|
||||||
|
|
||||||
return parentPages;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -3,7 +3,6 @@ import addAuthHeader from './common/add-auth-header';
|
|||||||
import addNotionVersionHeader from './common/add-notion-version-header';
|
import addNotionVersionHeader from './common/add-notion-version-header';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
import triggers from './triggers';
|
import triggers from './triggers';
|
||||||
import actions from './actions';
|
|
||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
@@ -15,9 +14,11 @@ export default defineApp({
|
|||||||
authDocUrl: 'https://automatisch.io/docs/apps/notion/connection',
|
authDocUrl: 'https://automatisch.io/docs/apps/notion/connection',
|
||||||
primaryColor: '000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader, addNotionVersionHeader],
|
beforeRequest: [
|
||||||
|
addAuthHeader,
|
||||||
|
addNotionVersionHeader,
|
||||||
|
],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
actions,
|
|
||||||
dynamicData,
|
dynamicData,
|
||||||
});
|
});
|
||||||
|
@@ -16,27 +16,13 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
.select('role')
|
.select('role')
|
||||||
.groupBy('role');
|
.groupBy('role');
|
||||||
|
|
||||||
let shouldCreateAdminRole = true;
|
|
||||||
for (const { role } of uniqueUserRoles) {
|
for (const { role } of uniqueUserRoles) {
|
||||||
// skip empty roles
|
// skip empty roles
|
||||||
if (!role) continue;
|
if (!role) continue;
|
||||||
|
|
||||||
const lowerCaseRole = lowerCase(role);
|
|
||||||
|
|
||||||
if (lowerCaseRole === 'admin') {
|
|
||||||
shouldCreateAdminRole = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await knex('roles').insert({
|
await knex('roles').insert({
|
||||||
name: capitalize(role),
|
name: capitalize(role),
|
||||||
key: lowerCaseRole,
|
key: lowerCase(role),
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldCreateAdminRole) {
|
|
||||||
await knex('roles').insert({
|
|
||||||
name: 'Admin',
|
|
||||||
key: 'admin',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,46 +1,23 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
const getPermissionForRole = (roleId: string, subject: string, actions: string[], conditions: string[] = []) => actions
|
const getPermission = (subject: string, actions: string[]) => actions.map(action => ({ subject, action }));
|
||||||
.map(action => ({
|
|
||||||
role_id: roleId,
|
|
||||||
subject,
|
|
||||||
action,
|
|
||||||
conditions,
|
|
||||||
}));
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
await knex.schema.createTable('permissions', (table) => {
|
await knex.schema.createTable('permissions', (table) => {
|
||||||
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
||||||
table.uuid('role_id').references('id').inTable('roles');
|
|
||||||
table.string('action').notNullable();
|
table.string('action').notNullable();
|
||||||
table.string('subject').notNullable();
|
table.string('subject').notNullable();
|
||||||
table.jsonb('conditions').notNullable().defaultTo([]);
|
|
||||||
|
|
||||||
table.timestamps(true, true);
|
table.timestamps(true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
const roles = await knex('roles').select(['id', 'key']) as { id: string, key: string }[];
|
await knex('permissions').insert([
|
||||||
|
...getPermission('Connection', ['create', 'read', 'delete', 'update']),
|
||||||
for (const role of roles) {
|
...getPermission('Execution', ['read']),
|
||||||
// `admin` role should have no conditions unlike others by default
|
...getPermission('Flow', ['create', 'delete', 'publish', 'read', 'update']),
|
||||||
const isAdmin = role.key === 'admin';
|
...getPermission('Role', ['create', 'delete', 'read', 'update']),
|
||||||
const roleConditions = isAdmin ? [] : ['isCreator'];
|
...getPermission('User', ['create', 'delete', 'read', 'update']),
|
||||||
|
]);
|
||||||
// default permissions
|
|
||||||
await knex('permissions').insert([
|
|
||||||
...getPermissionForRole(role.id, 'Connection', ['create', 'read', 'delete', 'update'], roleConditions),
|
|
||||||
...getPermissionForRole(role.id, 'Execution', ['read'], roleConditions),
|
|
||||||
...getPermissionForRole(role.id, 'Flow', ['create', 'delete', 'publish', 'read', 'update'], roleConditions),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// admin specific permission
|
|
||||||
if (isAdmin) {
|
|
||||||
await knex('permissions').insert([
|
|
||||||
...getPermissionForRole(role.id, 'User', ['create', 'read', 'delete', 'update']),
|
|
||||||
...getPermissionForRole(role.id, 'Role', ['create', 'read', 'delete', 'update']),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
@@ -0,0 +1,25 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.createTable('roles_permissions', (table) => {
|
||||||
|
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
||||||
|
table.uuid('role_id').references('id').inTable('roles');
|
||||||
|
table.uuid('permission_id').references('id').inTable('permissions');
|
||||||
|
});
|
||||||
|
|
||||||
|
const roles = await knex('roles').select('id');
|
||||||
|
const permissions = await knex('permissions').select('id');
|
||||||
|
|
||||||
|
for (const role of roles) {
|
||||||
|
for (const permission of permissions) {
|
||||||
|
await knex('roles_permissions').insert({
|
||||||
|
role_id: role.id,
|
||||||
|
permission_id: permission.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.dropTable('roles_permissions');
|
||||||
|
}
|
@@ -13,7 +13,6 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
table.text('email_attribute_name').notNullable();
|
table.text('email_attribute_name').notNullable();
|
||||||
table.text('role_attribute_name').notNullable();
|
table.text('role_attribute_name').notNullable();
|
||||||
table.uuid('default_role_id').references('id').inTable('roles');
|
table.uuid('default_role_id').references('id').inTable('roles');
|
||||||
table.boolean('active').defaultTo(false);
|
|
||||||
|
|
||||||
table.timestamps(true, true);
|
table.timestamps(true, true);
|
||||||
});
|
});
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.alterTable('permissions', (table) => {
|
||||||
|
table.jsonb('conditions').notNullable().defaultTo([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.alterTable('permissions', (table) => {
|
||||||
|
table.dropColumn('conditions');
|
||||||
|
});
|
||||||
|
}
|
@@ -1,33 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
|
|
||||||
const getPermissionForRole = (
|
|
||||||
roleId: string,
|
|
||||||
subject: string,
|
|
||||||
actions: string[]
|
|
||||||
) =>
|
|
||||||
actions.map((action) => ({
|
|
||||||
role_id: roleId,
|
|
||||||
subject,
|
|
||||||
action,
|
|
||||||
conditions: [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
const role = (await knex('roles')
|
|
||||||
.first(['id', 'key'])
|
|
||||||
.where({ key: 'admin' })
|
|
||||||
.limit(1)) as { id: string; key: string };
|
|
||||||
|
|
||||||
await knex('permissions').insert(
|
|
||||||
getPermissionForRole(role.id, 'SamlAuthProvider', [
|
|
||||||
'create',
|
|
||||||
'read',
|
|
||||||
'delete',
|
|
||||||
'update',
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
await knex('permissions').where({ subject: 'SamlAuthProvider' }).delete();
|
|
||||||
}
|
|
@@ -25,8 +25,6 @@ import updateRole from './mutations/update-role.ee';
|
|||||||
import updateStep from './mutations/update-step';
|
import updateStep from './mutations/update-step';
|
||||||
import updateUser from './mutations/update-user.ee';
|
import updateUser from './mutations/update-user.ee';
|
||||||
import verifyConnection from './mutations/verify-connection';
|
import verifyConnection from './mutations/verify-connection';
|
||||||
import createSamlAuthProvider from './mutations/create-saml-auth-provider.ee';
|
|
||||||
import updateSamlAuthProvider from './mutations/update-saml-auth-provider.ee';
|
|
||||||
|
|
||||||
const mutationResolvers = {
|
const mutationResolvers = {
|
||||||
createConnection,
|
createConnection,
|
||||||
@@ -56,8 +54,6 @@ const mutationResolvers = {
|
|||||||
updateRole,
|
updateRole,
|
||||||
updateStep,
|
updateStep,
|
||||||
verifyConnection,
|
verifyConnection,
|
||||||
createSamlAuthProvider,
|
|
||||||
updateSamlAuthProvider,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default mutationResolvers;
|
export default mutationResolvers;
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import kebabCase from 'lodash/kebabCase';
|
import kebabCase from 'lodash/kebabCase';
|
||||||
import Permission from '../../models/permission';
|
|
||||||
import Role from '../../models/role';
|
import Role from '../../models/role';
|
||||||
import Context from '../../types/express/context';
|
import Permission from '../../models/permission';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
input: {
|
input: {
|
||||||
@@ -11,9 +10,8 @@ type Params = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const createRole = async (_parent: unknown, params: Params, context: Context) => {
|
// TODO: access
|
||||||
context.currentUser.can('create', 'Role');
|
const createRole = async (_parent: unknown, params: Params) => {
|
||||||
|
|
||||||
const { name, description, permissions } = params.input;
|
const { name, description, permissions } = params.input;
|
||||||
const key = kebabCase(name);
|
const key = kebabCase(name);
|
||||||
|
|
||||||
|
@@ -1,54 +0,0 @@
|
|||||||
import type { SamlConfig } from '@node-saml/passport-saml';
|
|
||||||
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
|
||||||
import Context from '../../types/express/context';
|
|
||||||
|
|
||||||
type Params = {
|
|
||||||
input: {
|
|
||||||
name: string;
|
|
||||||
certificate: string;
|
|
||||||
signatureAlgorithm: SamlConfig['signatureAlgorithm'];
|
|
||||||
issuer: string;
|
|
||||||
entryPoint: string;
|
|
||||||
firstnameAttributeName: string;
|
|
||||||
surnameAttributeName: string;
|
|
||||||
emailAttributeName: string;
|
|
||||||
roleAttributeName: string;
|
|
||||||
defaultRoleId: string;
|
|
||||||
active: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createSamlAuthProvider = async (
|
|
||||||
_parent: unknown,
|
|
||||||
params: Params,
|
|
||||||
context: Context
|
|
||||||
) => {
|
|
||||||
context.currentUser.can('create', 'SamlAuthProvider');
|
|
||||||
|
|
||||||
const samlAuthProviderPayload: Partial<SamlAuthProvider> = {
|
|
||||||
...params.input,
|
|
||||||
};
|
|
||||||
|
|
||||||
const existingSamlAuthProvider = await SamlAuthProvider.query()
|
|
||||||
.limit(1)
|
|
||||||
.first();
|
|
||||||
|
|
||||||
let samlAuthProvider: SamlAuthProvider;
|
|
||||||
|
|
||||||
if (!existingSamlAuthProvider) {
|
|
||||||
samlAuthProvider = await SamlAuthProvider.query().insert(
|
|
||||||
samlAuthProviderPayload
|
|
||||||
);
|
|
||||||
|
|
||||||
return samlAuthProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
samlAuthProvider = await SamlAuthProvider.query().patchAndFetchById(
|
|
||||||
existingSamlAuthProvider.id,
|
|
||||||
samlAuthProviderPayload
|
|
||||||
);
|
|
||||||
|
|
||||||
return samlAuthProvider;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default createSamlAuthProvider;
|
|
@@ -1,22 +1,17 @@
|
|||||||
import User from '../../models/user';
|
import User from '../../models/user';
|
||||||
import Role from '../../models/role';
|
|
||||||
import Context from '../../types/express/context';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
input: {
|
input: {
|
||||||
fullName: string;
|
fullName: string;
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
role: {
|
roleId: string;
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const createUser = async (_parent: unknown, params: Params, context: Context) => {
|
// TODO: access
|
||||||
context.currentUser.can('create', 'User');
|
const createUser = async (_parent: unknown, params: Params) => {
|
||||||
|
const { fullName, email, password, roleId } = params.input;
|
||||||
const { fullName, email, password } = params.input;
|
|
||||||
|
|
||||||
const existingUser = await User.query().findOne({ email });
|
const existingUser = await User.query().findOne({ email });
|
||||||
|
|
||||||
@@ -24,23 +19,12 @@ const createUser = async (_parent: unknown, params: Params, context: Context) =>
|
|||||||
throw new Error('User already exists!');
|
throw new Error('User already exists!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const userPayload: Partial<User> = {
|
const user = await User.query().insert({
|
||||||
fullName,
|
fullName,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
};
|
roleId,
|
||||||
|
});
|
||||||
try {
|
|
||||||
context.currentUser.can('update', 'Role');
|
|
||||||
|
|
||||||
userPayload.roleId = params.input.role.id;
|
|
||||||
} catch {
|
|
||||||
// void
|
|
||||||
const role = await Role.query().findOne({ key: 'user' });
|
|
||||||
userPayload.roleId = role.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await User.query().insert(userPayload);
|
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
@@ -2,7 +2,8 @@ import { Duration } from 'luxon';
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import deleteUserQueue from '../../queues/delete-user.ee';
|
import deleteUserQueue from '../../queues/delete-user.ee';
|
||||||
|
|
||||||
const deleteCurrentUser = async (_parent: unknown, params: never, context: Context) => {
|
// TODO: access
|
||||||
|
const deleteUser = async (_parent: unknown, params: never, context: Context) => {
|
||||||
const id = context.currentUser.id;
|
const id = context.currentUser.id;
|
||||||
|
|
||||||
await context.currentUser.$query().delete();
|
await context.currentUser.$query().delete();
|
||||||
@@ -19,4 +20,4 @@ const deleteCurrentUser = async (_parent: unknown, params: never, context: Conte
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default deleteCurrentUser;
|
export default deleteUser;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import Role from '../../models/role';
|
import Role from '../../models/role';
|
||||||
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@@ -8,37 +7,22 @@ type Params = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
const deleteRole = async (
|
const deleteRole = async (
|
||||||
_parent: unknown,
|
_parent: unknown,
|
||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
context.currentUser.can('delete', 'Role');
|
|
||||||
|
|
||||||
const role = await Role.query().findById(params.input.id).throwIfNotFound();
|
const role = await Role.query().findById(params.input.id).throwIfNotFound();
|
||||||
const count = await role.$relatedQuery('users').resultSize();
|
|
||||||
|
|
||||||
if (count > 0) {
|
|
||||||
throw new Error('All users must be migrated away from the role!');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role.isAdmin) {
|
if (role.isAdmin) {
|
||||||
throw new Error('Admin role cannot be deleted!');
|
throw new Error('Admin role cannot be deleted!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const samlAuthProviderUsingDefaultRole = await SamlAuthProvider.query()
|
/**
|
||||||
.where({ default_role_id: role.id })
|
* TODO: consider migrations for users that still have the role or
|
||||||
.limit(1)
|
* do not let the role get deleted if there are still associated users
|
||||||
.first();
|
*/
|
||||||
|
|
||||||
if (samlAuthProviderUsingDefaultRole) {
|
|
||||||
throw new Error(
|
|
||||||
'You need to change the default role in the SAML configuration before deleting this role.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete permissions first
|
|
||||||
await role.$relatedQuery('permissions').delete();
|
|
||||||
await role.$query().delete();
|
await role.$query().delete();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@@ -9,13 +9,12 @@ type Params = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
const deleteUser = async (
|
const deleteUser = async (
|
||||||
_parent: unknown,
|
_parent: unknown,
|
||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
context.currentUser.can('delete', 'User');
|
|
||||||
|
|
||||||
const id = params.input.id;
|
const id = params.input.id;
|
||||||
|
|
||||||
await User.query().deleteById(id);
|
await User.query().deleteById(id);
|
||||||
|
@@ -13,7 +13,7 @@ const TOKEN_EXPIRES_IN = '14d';
|
|||||||
|
|
||||||
const login = async (_parent: unknown, params: Params) => {
|
const login = async (_parent: unknown, params: Params) => {
|
||||||
const user = await User.query().findOne({
|
const user = await User.query().findOne({
|
||||||
email: params.input.email.toLowerCase(),
|
email: params.input.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user && (await user.login(params.input.password))) {
|
if (user && (await user.login(params.input.password))) {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import Role from '../../models/role';
|
import Role from '../../models/role';
|
||||||
import Permission from '../../models/permission';
|
import Permission from '../../models/permission';
|
||||||
import permissionCatalog from '../../helpers/permission-catalog.ee';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
input: {
|
input: {
|
||||||
@@ -12,13 +11,12 @@ type Params = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRole = async (
|
// TODO: access
|
||||||
|
const updateUser = async (
|
||||||
_parent: unknown,
|
_parent: unknown,
|
||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
context.currentUser.can('update', 'Role');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
@@ -26,66 +24,21 @@ const updateRole = async (
|
|||||||
permissions,
|
permissions,
|
||||||
} = params.input;
|
} = params.input;
|
||||||
|
|
||||||
const role = await Role
|
const role = await Role.query().findById(id).throwIfNotFound();
|
||||||
.query()
|
|
||||||
.findById(id)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
try {
|
// TODO: delete the unrelated items!
|
||||||
const updatedRole = await Role.transaction(async (trx) => {
|
await role.$relatedQuery('permissions').unrelate();
|
||||||
await role.$relatedQuery('permissions', trx).delete();
|
|
||||||
|
|
||||||
if (permissions?.length) {
|
// TODO: possibly assert that given permissions do actually exist in catalog
|
||||||
const sanitizedPermissions = permissions
|
// TODO: possibly optimize it with patching the different permissions compared to current ones
|
||||||
.filter((permission) => {
|
return await role.$query()
|
||||||
const {
|
.patchAndFetch(
|
||||||
action,
|
{
|
||||||
subject,
|
name,
|
||||||
conditions,
|
description,
|
||||||
} = permission;
|
permissions,
|
||||||
|
|
||||||
const relevantAction = permissionCatalog.actions.find(actionCatalogItem => actionCatalogItem.key === action);
|
|
||||||
const validSubject = relevantAction.subjects.includes(subject);
|
|
||||||
const validConditions = conditions.every(condition => {
|
|
||||||
return !!permissionCatalog
|
|
||||||
.conditions
|
|
||||||
.find((conditionCatalogItem) => conditionCatalogItem.key === condition);
|
|
||||||
})
|
|
||||||
|
|
||||||
return validSubject && validConditions;
|
|
||||||
})
|
|
||||||
.map((permission) => ({
|
|
||||||
...permission,
|
|
||||||
roleId: role.id,
|
|
||||||
}));
|
|
||||||
|
|
||||||
await Permission.query().insert(sanitizedPermissions);
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
await role
|
|
||||||
.$query(trx)
|
|
||||||
.patch(
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return await Role
|
|
||||||
.query(trx)
|
|
||||||
.leftJoinRelated({
|
|
||||||
permissions: true
|
|
||||||
})
|
|
||||||
.withGraphFetched({
|
|
||||||
permissions: true
|
|
||||||
})
|
|
||||||
.findById(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return updatedRole;
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error('The role could not be updated!');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default updateRole;
|
export default updateUser;
|
||||||
|
@@ -1,45 +0,0 @@
|
|||||||
import type { SamlConfig } from '@node-saml/passport-saml';
|
|
||||||
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
|
||||||
import Context from '../../types/express/context';
|
|
||||||
|
|
||||||
type Params = {
|
|
||||||
input: {
|
|
||||||
name: string;
|
|
||||||
certificate: string;
|
|
||||||
signatureAlgorithm: SamlConfig['signatureAlgorithm'];
|
|
||||||
issuer: string;
|
|
||||||
entryPoint: string;
|
|
||||||
firstnameAttributeName: string;
|
|
||||||
surnameAttributeName: string;
|
|
||||||
emailAttributeName: string;
|
|
||||||
roleAttributeName: string;
|
|
||||||
defaultRoleId: string;
|
|
||||||
active: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateSamlAuthProvider = async (
|
|
||||||
_parent: unknown,
|
|
||||||
params: Params,
|
|
||||||
context: Context
|
|
||||||
) => {
|
|
||||||
context.currentUser.can('update', 'SamlAuthProvider');
|
|
||||||
|
|
||||||
const samlAuthProviderPayload: Partial<SamlAuthProvider> = {
|
|
||||||
...params.input,
|
|
||||||
};
|
|
||||||
|
|
||||||
const existingSamlAuthProvider = await SamlAuthProvider.query()
|
|
||||||
.limit(1)
|
|
||||||
.first()
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const samlAuthProvider = await SamlAuthProvider.query().patchAndFetchById(
|
|
||||||
existingSamlAuthProvider.id,
|
|
||||||
samlAuthProviderPayload
|
|
||||||
);
|
|
||||||
|
|
||||||
return samlAuthProvider;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default updateSamlAuthProvider;
|
|
@@ -12,30 +12,21 @@ type Params = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
const updateUser = async (
|
const updateUser = async (
|
||||||
_parent: unknown,
|
_parent: unknown,
|
||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
context.currentUser.can('update', 'User');
|
const user = await User.query()
|
||||||
|
.patchAndFetchById(
|
||||||
const userPayload: Partial<User> = {
|
params.input.id,
|
||||||
email: params.input.email,
|
{
|
||||||
fullName: params.input.fullName,
|
email: params.input.email,
|
||||||
};
|
fullName: params.input.fullName,
|
||||||
|
roleId: params.input.role.id,
|
||||||
try {
|
}
|
||||||
context.currentUser.can('update', 'Role');
|
);
|
||||||
|
|
||||||
userPayload.roleId = params.input.role.id;
|
|
||||||
} catch {
|
|
||||||
// void
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await User.query().patchAndFetchById(
|
|
||||||
params.input.id,
|
|
||||||
userPayload
|
|
||||||
);
|
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
import Connection from '../../models/connection';
|
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@@ -7,17 +6,13 @@ type Params = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getApp = async (_parent: unknown, params: Params, context: Context) => {
|
const getApp = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
const conditions = context.currentUser.can('read', 'Connection');
|
context.currentUser.can('read', 'Connection');
|
||||||
|
|
||||||
const userConnections = context.currentUser.$relatedQuery('connections');
|
|
||||||
const allConnections = Connection.query();
|
|
||||||
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
|
||||||
|
|
||||||
const app = await App.findOneByKey(params.key);
|
const app = await App.findOneByKey(params.key);
|
||||||
|
|
||||||
if (context.currentUser) {
|
if (context.currentUser) {
|
||||||
const connections = await connectionBaseQuery
|
const connections = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('connections')
|
||||||
.select('connections.*')
|
.select('connections.*')
|
||||||
.fullOuterJoinRelated('steps')
|
.fullOuterJoinRelated('steps')
|
||||||
.where({
|
.where({
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
import { IConnection } from '@automatisch/types';
|
import { IConnection } from '@automatisch/types';
|
||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import Flow from '../../models/flow';
|
|
||||||
import Connection from '../../models/connection';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -13,27 +11,19 @@ const getConnectedApps = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('read', 'Connection');
|
context.currentUser.can('read', 'Connection');
|
||||||
|
|
||||||
const userConnections = context.currentUser.$relatedQuery('connections');
|
|
||||||
const allConnections = Connection.query();
|
|
||||||
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
|
||||||
|
|
||||||
const userFlows = context.currentUser.$relatedQuery('flows');
|
|
||||||
const allFlows = Flow.query();
|
|
||||||
const flowBaseQuery = conditions.isCreator ? userFlows : allFlows;
|
|
||||||
|
|
||||||
let apps = await App.findAll(params.name);
|
let apps = await App.findAll(params.name);
|
||||||
|
|
||||||
const connections = await connectionBaseQuery
|
const connections = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('connections')
|
||||||
.select('connections.key')
|
.select('connections.key')
|
||||||
.where({ draft: false })
|
.where({ draft: false })
|
||||||
.count('connections.id as count')
|
.count('connections.id as count')
|
||||||
.groupBy('connections.key');
|
.groupBy('connections.key');
|
||||||
|
|
||||||
const flows = await flowBaseQuery
|
const flows = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('flows')
|
||||||
.withGraphJoined('steps')
|
.withGraphJoined('steps')
|
||||||
.orderBy('created_at', 'desc');
|
.orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { IDynamicData, IJSONObject } from '@automatisch/types';
|
import { IDynamicData, IJSONObject } from '@automatisch/types';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
import Step from '../../models/step';
|
|
||||||
import ExecutionStep from '../../models/execution-step';
|
import ExecutionStep from '../../models/execution-step';
|
||||||
import globalVariable from '../../helpers/global-variable';
|
import globalVariable from '../../helpers/global-variable';
|
||||||
import computeParameters from '../../helpers/compute-parameters';
|
import computeParameters from '../../helpers/compute-parameters';
|
||||||
@@ -17,13 +16,10 @@ const getDynamicData = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('update', 'Flow');
|
context.currentUser.can('update', 'Flow');
|
||||||
const userSteps = context.currentUser.$relatedQuery('steps');
|
|
||||||
const allSteps = Step.query();
|
|
||||||
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
|
|
||||||
|
|
||||||
const step = await stepBaseQuery
|
const step = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('steps')
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
connection: true,
|
connection: true,
|
||||||
flow: true,
|
flow: true,
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { IDynamicFields, IJSONObject } from '@automatisch/types';
|
import { IDynamicFields, IJSONObject } from '@automatisch/types';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
import Step from '../../models/step';
|
|
||||||
import globalVariable from '../../helpers/global-variable';
|
import globalVariable from '../../helpers/global-variable';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@@ -15,13 +14,10 @@ const getDynamicFields = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('update', 'Flow');
|
context.currentUser.can('update', 'Flow');
|
||||||
const userSteps = context.currentUser.$relatedQuery('steps');
|
|
||||||
const allSteps = Step.query();
|
|
||||||
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
|
|
||||||
|
|
||||||
const step = await stepBaseQuery
|
const step = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('steps')
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
connection: true,
|
connection: true,
|
||||||
flow: true,
|
flow: true,
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import paginate from '../../helpers/pagination';
|
import paginate from '../../helpers/pagination';
|
||||||
import Execution from '../../models/execution';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
executionId: string;
|
executionId: string;
|
||||||
@@ -13,13 +12,10 @@ const getExecutionSteps = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('read', 'Execution');
|
context.currentUser.can('read', 'Execution');
|
||||||
const userExecutions = context.currentUser.$relatedQuery('executions');
|
|
||||||
const allExecutions = Execution.query();
|
|
||||||
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
|
|
||||||
|
|
||||||
const execution = await executionBaseQuery
|
const execution = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('executions')
|
||||||
.withSoftDeleted()
|
.withSoftDeleted()
|
||||||
.findById(params.executionId)
|
.findById(params.executionId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import Execution from '../../models/execution';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
executionId: string;
|
executionId: string;
|
||||||
@@ -10,13 +9,10 @@ const getExecution = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('read', 'Execution');
|
context.currentUser.can('read', 'Execution');
|
||||||
const userExecutions = context.currentUser.$relatedQuery('executions');
|
|
||||||
const allExecutions = Execution.query();
|
|
||||||
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
|
|
||||||
|
|
||||||
const execution = await executionBaseQuery
|
const execution = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('executions')
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
flow: {
|
flow: {
|
||||||
steps: true,
|
steps: true,
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { raw } from 'objection';
|
import { raw } from 'objection';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import Execution from '../../models/execution';
|
|
||||||
import paginate from '../../helpers/pagination';
|
import paginate from '../../helpers/pagination';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@@ -13,11 +12,7 @@ const getExecutions = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('read', 'Execution');
|
context.currentUser.can('read', 'Execution');
|
||||||
|
|
||||||
const userExecutions = context.currentUser.$relatedQuery('executions');
|
|
||||||
const allExecutions = Execution.query();
|
|
||||||
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
|
|
||||||
|
|
||||||
const selectStatusStatement = `
|
const selectStatusStatement = `
|
||||||
case
|
case
|
||||||
@@ -28,8 +23,8 @@ const getExecutions = async (
|
|||||||
as status
|
as status
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const executions = executionBaseQuery
|
const executions = context.currentUser
|
||||||
.clone()
|
.$relatedQuery('executions')
|
||||||
.joinRelated('executionSteps as execution_steps')
|
.joinRelated('executionSteps as execution_steps')
|
||||||
.select('executions.*', raw(selectStatusStatement))
|
.select('executions.*', raw(selectStatusStatement))
|
||||||
.withSoftDeleted()
|
.withSoftDeleted()
|
||||||
|
@@ -1,18 +1,14 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import Flow from '../../models/flow';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFlow = async (_parent: unknown, params: Params, context: Context) => {
|
const getFlow = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
const conditions = context.currentUser.can('read', 'Flow');
|
context.currentUser.can('read', 'Flow');
|
||||||
const userFlows = context.currentUser.$relatedQuery('flows');
|
|
||||||
const allFlows = Flow.query();
|
|
||||||
const baseQuery = conditions.isCreator ? userFlows : allFlows;
|
|
||||||
|
|
||||||
const flow = await baseQuery
|
const flow = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('flows')
|
||||||
.withGraphJoined('[steps.[connection]]')
|
.withGraphJoined('[steps.[connection]]')
|
||||||
.orderBy('steps.position', 'asc')
|
.orderBy('steps.position', 'asc')
|
||||||
.findOne({ 'flows.id': params.id })
|
.findOne({ 'flows.id': params.id })
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import Flow from '../../models/flow';
|
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import paginate from '../../helpers/pagination';
|
import paginate from '../../helpers/pagination';
|
||||||
|
|
||||||
@@ -11,13 +10,10 @@ type Params = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getFlows = async (_parent: unknown, params: Params, context: Context) => {
|
const getFlows = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
const conditions = context.currentUser.can('read', 'Flow');
|
context.currentUser.can('read', 'Flow');
|
||||||
const userFlows = context.currentUser.$relatedQuery('flows');
|
|
||||||
const allFlows = Flow.query();
|
|
||||||
const baseQuery = conditions.isCreator ? userFlows : allFlows;
|
|
||||||
|
|
||||||
const flowsQuery = baseQuery
|
const flowsQuery = context.currentUser
|
||||||
.clone()
|
.$relatedQuery('flows')
|
||||||
.joinRelated({
|
.joinRelated({
|
||||||
steps: true,
|
steps: true,
|
||||||
})
|
})
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
import permissionCatalog from '../../helpers/permission-catalog.ee';
|
|
||||||
|
|
||||||
const getPermissionCatalog = async () => {
|
|
||||||
return permissionCatalog;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getPermissionCatalog;
|
|
76
packages/backend/src/graphql/queries/get-permissions.ee.ts
Normal file
76
packages/backend/src/graphql/queries/get-permissions.ee.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
const getPermissions = async () => {
|
||||||
|
const Connection = {
|
||||||
|
label: 'Connection',
|
||||||
|
key: 'Connection',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Flow = {
|
||||||
|
label: 'Flow',
|
||||||
|
key: 'Flow',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Execution = {
|
||||||
|
label: 'Execution',
|
||||||
|
key: 'Execution',
|
||||||
|
};
|
||||||
|
|
||||||
|
const permissions = {
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
key: 'isCreator',
|
||||||
|
label: 'Is creator'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: 'Create',
|
||||||
|
action: 'create',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Read',
|
||||||
|
action: 'read',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Execution.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Update',
|
||||||
|
action: 'update',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
action: 'delete',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Publish',
|
||||||
|
action: 'publish',
|
||||||
|
subjects: [
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
subjects: [
|
||||||
|
Connection,
|
||||||
|
Flow,
|
||||||
|
Execution
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return permissions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getPermissions;
|
@@ -5,19 +5,9 @@ type Params = {
|
|||||||
id: string
|
id: string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
const getRole = async (_parent: unknown, params: Params, context: Context) => {
|
const getRole = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
context.currentUser.can('read', 'Role');
|
return await Role.query().findById(params.id).throwIfNotFound();
|
||||||
|
|
||||||
return await Role
|
|
||||||
.query()
|
|
||||||
.leftJoinRelated({
|
|
||||||
permissions: true
|
|
||||||
})
|
|
||||||
.withGraphFetched({
|
|
||||||
permissions: true
|
|
||||||
})
|
|
||||||
.findById(params.id)
|
|
||||||
.throwIfNotFound();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getRole;
|
export default getRole;
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import Role from '../../models/role';
|
import Role from '../../models/role';
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
const getRoles = async (_parent: unknown, params: unknown, context: Context) => {
|
const getRoles = async (_parent: unknown, params: unknown, context: Context) => {
|
||||||
context.currentUser.can('read', 'Role');
|
return await Role.query();
|
||||||
|
|
||||||
return await Role.query().orderBy('name');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getRoles;
|
export default getRoles;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
||||||
|
|
||||||
const getSamlAuthProviders = async () => {
|
const getSamlAuthProviders = async () => {
|
||||||
const providers = await SamlAuthProvider.query().where({ active: true });
|
const providers = await SamlAuthProvider.query();
|
||||||
|
|
||||||
return providers;
|
return providers;
|
||||||
};
|
};
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { ref } from 'objection';
|
|
||||||
import ExecutionStep from '../../models/execution-step';
|
|
||||||
import Step from '../../models/step';
|
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
import ExecutionStep from '../../models/execution-step';
|
||||||
|
import { ref } from 'objection';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
stepId: string;
|
stepId: string;
|
||||||
@@ -12,18 +11,15 @@ const getStepWithTestExecutions = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('update', 'Flow');
|
context.currentUser.can('update', 'Flow');
|
||||||
const userSteps = context.currentUser.$relatedQuery('steps');
|
|
||||||
const allSteps = Step.query();
|
|
||||||
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
|
|
||||||
|
|
||||||
const step = await stepBaseQuery
|
const step = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('steps')
|
||||||
.findOne({ 'steps.id': params.stepId })
|
.findOne({ 'steps.id': params.stepId })
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const previousStepsWithCurrentStep = await stepBaseQuery
|
const previousStepsWithCurrentStep = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('steps')
|
||||||
.withGraphJoined('executionSteps')
|
.withGraphJoined('executionSteps')
|
||||||
.where('flow_id', '=', step.flowId)
|
.where('flow_id', '=', step.flowId)
|
||||||
.andWhere('position', '<', step.position)
|
.andWhere('position', '<', step.position)
|
||||||
|
@@ -5,9 +5,8 @@ type Params = {
|
|||||||
id: string
|
id: string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
const getUser = async (_parent: unknown, params: Params, context: Context) => {
|
const getUser = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
context.currentUser.can('read', 'User');
|
|
||||||
|
|
||||||
return await User
|
return await User
|
||||||
.query()
|
.query()
|
||||||
.leftJoinRelated({
|
.leftJoinRelated({
|
||||||
|
@@ -7,9 +7,8 @@ type Params = {
|
|||||||
offset: number;
|
offset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
const getUsers = async (_parent: unknown, params: Params, context: Context) => {
|
const getUsers = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
context.currentUser.can('read', 'User');
|
|
||||||
|
|
||||||
const usersQuery = User
|
const usersQuery = User
|
||||||
.query()
|
.query()
|
||||||
.leftJoinRelated({
|
.leftJoinRelated({
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
import Connection from '../../models/connection';
|
|
||||||
import globalVariable from '../../helpers/global-variable';
|
import globalVariable from '../../helpers/global-variable';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
@@ -13,13 +12,10 @@ const testConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('update', 'Connection');
|
context.currentUser.can('update', 'Connection');
|
||||||
const userConnections = context.currentUser.$relatedQuery('connections');
|
|
||||||
const allConnections = Connection.query();
|
|
||||||
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
|
||||||
|
|
||||||
let connection = await connectionBaseQuery
|
let connection = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
id: params.id,
|
id: params.id,
|
||||||
})
|
})
|
||||||
|
@@ -16,7 +16,7 @@ import getUsers from './queries/get-users';
|
|||||||
import getInvoices from './queries/get-invoices.ee';
|
import getInvoices from './queries/get-invoices.ee';
|
||||||
import getPaddleInfo from './queries/get-paddle-info.ee';
|
import getPaddleInfo from './queries/get-paddle-info.ee';
|
||||||
import getPaymentPlans from './queries/get-payment-plans.ee';
|
import getPaymentPlans from './queries/get-payment-plans.ee';
|
||||||
import getPermissionCatalog from './queries/get-permission-catalog.ee';
|
import getPermissions from './queries/get-permissions.ee';
|
||||||
import getRole from './queries/get-role.ee';
|
import getRole from './queries/get-role.ee';
|
||||||
import getRoles from './queries/get-roles.ee';
|
import getRoles from './queries/get-roles.ee';
|
||||||
import getSamlAuthProviders from './queries/get-saml-auth-providers.ee';
|
import getSamlAuthProviders from './queries/get-saml-auth-providers.ee';
|
||||||
@@ -43,7 +43,7 @@ const queryResolvers = {
|
|||||||
getInvoices,
|
getInvoices,
|
||||||
getPaddleInfo,
|
getPaddleInfo,
|
||||||
getPaymentPlans,
|
getPaymentPlans,
|
||||||
getPermissionCatalog,
|
getPermissions,
|
||||||
getRole,
|
getRole,
|
||||||
getRoles,
|
getRoles,
|
||||||
getSamlAuthProviders,
|
getSamlAuthProviders,
|
||||||
|
@@ -42,11 +42,14 @@ type Query {
|
|||||||
getTrialStatus: GetTrialStatus
|
getTrialStatus: GetTrialStatus
|
||||||
getSubscriptionStatus: GetSubscriptionStatus
|
getSubscriptionStatus: GetSubscriptionStatus
|
||||||
getSamlAuthProviders: [GetSamlAuthProviders]
|
getSamlAuthProviders: [GetSamlAuthProviders]
|
||||||
getUsers(limit: Int!, offset: Int!): UserConnection
|
getUsers(
|
||||||
|
limit: Int!
|
||||||
|
offset: Int!
|
||||||
|
): UserConnection
|
||||||
getUser(id: String!): User
|
getUser(id: String!): User
|
||||||
getRoles: [Role]
|
getRoles: [Role]
|
||||||
getRole(id: String!): Role
|
getRole(id: String!): Role
|
||||||
getPermissionCatalog: PermissionCatalog
|
getPermissions: Permissions
|
||||||
healthcheck: AppHealth
|
healthcheck: AppHealth
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +81,6 @@ type Mutation {
|
|||||||
updateStep(input: UpdateStepInput): Step
|
updateStep(input: UpdateStepInput): Step
|
||||||
updateUser(input: UpdateUserInput): User
|
updateUser(input: UpdateUserInput): User
|
||||||
verifyConnection(input: VerifyConnectionInput): Connection
|
verifyConnection(input: VerifyConnectionInput): Connection
|
||||||
createSamlAuthProvider(input: CreateSamlAuthProviderInput): SamlAuthProvider
|
|
||||||
updateSamlAuthProvider(input: UpdateSamlAuthProviderInput): SamlAuthProvider
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -291,20 +292,6 @@ type Execution {
|
|||||||
flow: Flow
|
flow: Flow
|
||||||
}
|
}
|
||||||
|
|
||||||
type SamlAuthProvider {
|
|
||||||
id: String
|
|
||||||
name: String
|
|
||||||
certificate: String
|
|
||||||
signatureAlgorithm: String
|
|
||||||
issuer: String
|
|
||||||
entryPoint: String
|
|
||||||
firstnameAttributeName: String
|
|
||||||
surnameAttributeName: String
|
|
||||||
emailAttributeName: String
|
|
||||||
roleAttributeName: String
|
|
||||||
active: Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserConnection {
|
type UserConnection {
|
||||||
edges: [UserEdge]
|
edges: [UserEdge]
|
||||||
pageInfo: PageInfo
|
pageInfo: PageInfo
|
||||||
@@ -336,34 +323,6 @@ input VerifyConnectionInput {
|
|||||||
id: String!
|
id: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
input CreateSamlAuthProviderInput {
|
|
||||||
name: String!
|
|
||||||
certificate: String!
|
|
||||||
signatureAlgorithm: String!
|
|
||||||
issuer: String!
|
|
||||||
entryPoint: String!
|
|
||||||
firstnameAttributeName: String!
|
|
||||||
surnameAttributeName: String!
|
|
||||||
emailAttributeName: String!
|
|
||||||
roleAttributeName: String!
|
|
||||||
defaultRoleId: String!
|
|
||||||
active: Boolean!
|
|
||||||
}
|
|
||||||
|
|
||||||
input UpdateSamlAuthProviderInput {
|
|
||||||
name: String!
|
|
||||||
certificate: String!
|
|
||||||
signatureAlgorithm: String!
|
|
||||||
issuer: String!
|
|
||||||
entryPoint: String!
|
|
||||||
firstnameAttributeName: String!
|
|
||||||
surnameAttributeName: String!
|
|
||||||
emailAttributeName: String!
|
|
||||||
roleAttributeName: String!
|
|
||||||
defaultRoleId: String!
|
|
||||||
active: Boolean!
|
|
||||||
}
|
|
||||||
|
|
||||||
input DeleteConnectionInput {
|
input DeleteConnectionInput {
|
||||||
id: String!
|
id: String!
|
||||||
}
|
}
|
||||||
@@ -681,13 +640,13 @@ type GetSamlAuthProviders {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Permission {
|
type Permission {
|
||||||
id: String
|
|
||||||
action: String
|
action: String
|
||||||
subject: String
|
subject: String
|
||||||
conditions: [String]
|
conditions: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
type PermissionCatalog {
|
# TODO: emphasize it's a catalog item
|
||||||
|
type Permissions {
|
||||||
actions: [Action]
|
actions: [Action]
|
||||||
subjects: [Subject]
|
subjects: [Subject]
|
||||||
conditions: [Condition]
|
conditions: [Condition]
|
||||||
@@ -695,7 +654,7 @@ type PermissionCatalog {
|
|||||||
|
|
||||||
type Action {
|
type Action {
|
||||||
label: String
|
label: String
|
||||||
key: String
|
action: String
|
||||||
subjects: [String]
|
subjects: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,24 +0,0 @@
|
|||||||
import axios, { AxiosRequestConfig } from 'axios';
|
|
||||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
||||||
import { HttpProxyAgent } from 'http-proxy-agent';
|
|
||||||
|
|
||||||
const config: AxiosRequestConfig = {};
|
|
||||||
const httpProxyUrl = process.env.http_proxy;
|
|
||||||
const httpsProxyUrl = process.env.https_proxy;
|
|
||||||
const supportsProxy = httpProxyUrl || httpsProxyUrl;
|
|
||||||
|
|
||||||
if (supportsProxy) {
|
|
||||||
if (httpProxyUrl) {
|
|
||||||
config.httpAgent = new HttpProxyAgent(process.env.http_proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (httpsProxyUrl) {
|
|
||||||
config.httpsAgent = new HttpsProxyAgent(process.env.https_proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.proxy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const axiosWithProxyInstance = axios.create(config);
|
|
||||||
|
|
||||||
export default axiosWithProxyInstance;
|
|
@@ -1,4 +1,3 @@
|
|||||||
// TODO: replace with axios-with-proxy when needed
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import appConfig from '../../config/app';
|
import appConfig from '../../config/app';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
// TODO: replace with axios-with-proxy
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
import memoryCache from 'memory-cache';
|
import memoryCache from 'memory-cache';
|
||||||
|
@@ -3,7 +3,7 @@ import ExecutionStep from '../models/execution-step';
|
|||||||
import get from 'lodash.get';
|
import get from 'lodash.get';
|
||||||
|
|
||||||
// INFO: don't remove space in allowed character group!
|
// INFO: don't remove space in allowed character group!
|
||||||
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[\da-zA-Z-_ :]+)+}})/g;
|
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[\da-zA-Z-_ ]+)+}})/g;
|
||||||
|
|
||||||
export default function computeParameters(
|
export default function computeParameters(
|
||||||
parameters: Step['parameters'],
|
parameters: Step['parameters'],
|
||||||
@@ -42,7 +42,7 @@ export default function computeParameters(
|
|||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
[key]: value.map((item) => computeParameters(item, executionSteps)),
|
[key]: value.map(item => computeParameters(item, executionSteps)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,8 @@
|
|||||||
import { IHttpClientParams } from '@automatisch/types';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import { AxiosRequestConfig } from 'axios';
|
|
||||||
import { URL } from 'node:url';
|
|
||||||
export { AxiosInstance as IHttpClient } from 'axios';
|
export { AxiosInstance as IHttpClient } from 'axios';
|
||||||
|
import { IHttpClientParams } from '@automatisch/types';
|
||||||
|
import { URL } from 'url';
|
||||||
import HttpError from '../../errors/http';
|
import HttpError from '../../errors/http';
|
||||||
import axios from '../axios-with-proxy';
|
|
||||||
|
|
||||||
const removeBaseUrlForAbsoluteUrls = (
|
const removeBaseUrlForAbsoluteUrls = (
|
||||||
requestConfig: AxiosRequestConfig
|
requestConfig: AxiosRequestConfig
|
||||||
|
@@ -1,72 +0,0 @@
|
|||||||
const Connection = {
|
|
||||||
label: 'Connection',
|
|
||||||
key: 'Connection',
|
|
||||||
};
|
|
||||||
|
|
||||||
const Flow = {
|
|
||||||
label: 'Flow',
|
|
||||||
key: 'Flow',
|
|
||||||
};
|
|
||||||
|
|
||||||
const Execution = {
|
|
||||||
label: 'Execution',
|
|
||||||
key: 'Execution',
|
|
||||||
};
|
|
||||||
|
|
||||||
const permissionCatalog = {
|
|
||||||
conditions: [
|
|
||||||
{
|
|
||||||
key: 'isCreator',
|
|
||||||
label: 'Is creator'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
label: 'Create',
|
|
||||||
key: 'create',
|
|
||||||
subjects: [
|
|
||||||
Connection.key,
|
|
||||||
Flow.key,
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Read',
|
|
||||||
key: 'read',
|
|
||||||
subjects: [
|
|
||||||
Connection.key,
|
|
||||||
Execution.key,
|
|
||||||
Flow.key,
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Update',
|
|
||||||
key: 'update',
|
|
||||||
subjects: [
|
|
||||||
Connection.key,
|
|
||||||
Flow.key,
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Delete',
|
|
||||||
key: 'delete',
|
|
||||||
subjects: [
|
|
||||||
Connection.key,
|
|
||||||
Flow.key,
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Publish',
|
|
||||||
key: 'publish',
|
|
||||||
subjects: [
|
|
||||||
Flow.key,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
subjects: [
|
|
||||||
Connection,
|
|
||||||
Flow,
|
|
||||||
Execution
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
export default permissionCatalog;
|
|
@@ -1,20 +0,0 @@
|
|||||||
import { PureAbility, fieldPatternMatcher, mongoQueryMatcher } from '@casl/ability';
|
|
||||||
import type User from '../models/user'
|
|
||||||
|
|
||||||
// Must be kept in sync with `packages/web/src/helpers/userAbility.ts`!
|
|
||||||
export default function userAbility(user: Partial<User>) {
|
|
||||||
const permissions = user?.permissions;
|
|
||||||
const role = user?.role;
|
|
||||||
|
|
||||||
// We're not using mongo, but our fields, conditions match
|
|
||||||
const options = {
|
|
||||||
conditionsMatcher: mongoQueryMatcher,
|
|
||||||
fieldMatcher: fieldPatternMatcher
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!role || !permissions) {
|
|
||||||
return new PureAbility([], options);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PureAbility<[string, string], string[]>(permissions, options);
|
|
||||||
}
|
|
@@ -2,7 +2,6 @@ import Base from './base';
|
|||||||
|
|
||||||
class Permission extends Base {
|
class Permission extends Base {
|
||||||
id: string;
|
id: string;
|
||||||
roleId: string;
|
|
||||||
action: string;
|
action: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
conditions: string[];
|
conditions: string[];
|
||||||
@@ -11,11 +10,10 @@ class Permission extends Base {
|
|||||||
|
|
||||||
static jsonSchema = {
|
static jsonSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['roleId', 'action', 'subject'],
|
required: ['action', 'subject'],
|
||||||
|
|
||||||
properties: {
|
properties: {
|
||||||
id: { type: 'string', format: 'uuid' },
|
id: { type: 'string', format: 'uuid' },
|
||||||
roleId: { type: 'string', format: 'uuid' },
|
|
||||||
action: { type: 'string', minLength: 1 },
|
action: { type: 'string', minLength: 1 },
|
||||||
subject: { type: 'string', minLength: 1 },
|
subject: { type: 'string', minLength: 1 },
|
||||||
conditions: { type: 'array', items: { type: 'string' } },
|
conditions: { type: 'array', items: { type: 'string' } },
|
||||||
|
@@ -20,7 +20,7 @@ class Role extends Base {
|
|||||||
id: { type: 'string', format: 'uuid' },
|
id: { type: 'string', format: 'uuid' },
|
||||||
name: { type: 'string', minLength: 1 },
|
name: { type: 'string', minLength: 1 },
|
||||||
key: { type: 'string', minLength: 1 },
|
key: { type: 'string', minLength: 1 },
|
||||||
description: { type: ['string', 'null'], maxLength: 255 },
|
description: { type: ['string', 'null'], minLength: 1, maxLength: 255 },
|
||||||
createdAt: { type: 'string' },
|
createdAt: { type: 'string' },
|
||||||
updatedAt: { type: 'string' },
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
@@ -40,11 +40,15 @@ class Role extends Base {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
relation: Base.HasManyRelation,
|
relation: Base.ManyToManyRelation,
|
||||||
modelClass: Permission,
|
modelClass: Permission,
|
||||||
join: {
|
join: {
|
||||||
from: 'roles.id',
|
from: 'roles.id',
|
||||||
to: 'permissions.role_id',
|
through: {
|
||||||
|
from: 'roles_permissions.role_id',
|
||||||
|
to: 'roles_permissions.permission_id',
|
||||||
|
},
|
||||||
|
to: 'permissions.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -8,7 +8,7 @@ class SamlAuthProvider extends Base {
|
|||||||
id!: string;
|
id!: string;
|
||||||
name: string;
|
name: string;
|
||||||
certificate: string;
|
certificate: string;
|
||||||
signatureAlgorithm: SamlConfig['signatureAlgorithm'];
|
signatureAlgorithm: SamlConfig["signatureAlgorithm"];
|
||||||
issuer: string;
|
issuer: string;
|
||||||
entryPoint: string;
|
entryPoint: string;
|
||||||
firstnameAttributeName: string;
|
firstnameAttributeName: string;
|
||||||
@@ -16,7 +16,6 @@ class SamlAuthProvider extends Base {
|
|||||||
emailAttributeName: string;
|
emailAttributeName: string;
|
||||||
roleAttributeName: string;
|
roleAttributeName: string;
|
||||||
defaultRoleId: string;
|
defaultRoleId: string;
|
||||||
active: boolean;
|
|
||||||
|
|
||||||
static tableName = 'saml_auth_providers';
|
static tableName = 'saml_auth_providers';
|
||||||
|
|
||||||
@@ -39,18 +38,14 @@ class SamlAuthProvider extends Base {
|
|||||||
id: { type: 'string', format: 'uuid' },
|
id: { type: 'string', format: 'uuid' },
|
||||||
name: { type: 'string', minLength: 1 },
|
name: { type: 'string', minLength: 1 },
|
||||||
certificate: { type: 'string', minLength: 1 },
|
certificate: { type: 'string', minLength: 1 },
|
||||||
signatureAlgorithm: {
|
signatureAlgorithm: { type: 'string', enum: ['sha1', 'sha256', 'sha512'] },
|
||||||
type: 'string',
|
|
||||||
enum: ['sha1', 'sha256', 'sha512'],
|
|
||||||
},
|
|
||||||
issuer: { type: 'string', minLength: 1 },
|
issuer: { type: 'string', minLength: 1 },
|
||||||
entryPoint: { type: 'string', minLength: 1 },
|
entryPoint: { type: 'string', minLength: 1 },
|
||||||
firstnameAttributeName: { type: 'string', minLength: 1 },
|
firstnameAttributeName: { type: 'string', minLength: 1 },
|
||||||
surnameAttributeName: { type: 'string', minLength: 1 },
|
surnameAttributeName: { type: 'string', minLength: 1 },
|
||||||
emailAttributeName: { type: 'string', minLength: 1 },
|
emailAttributeName: { type: 'string', minLength: 1 },
|
||||||
roleAttributeName: { type: 'string', minLength: 1 },
|
roleAttributeName: { type: 'string', minLength: 1 },
|
||||||
defaultRoleId: { type: 'string', format: 'uuid' },
|
defaultRoleId: { type: 'string', format: 'uuid' }
|
||||||
active: { type: 'boolean' },
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -77,7 +72,7 @@ class SamlAuthProvider extends Base {
|
|||||||
entryPoint: this.entryPoint,
|
entryPoint: this.entryPoint,
|
||||||
issuer: this.issuer,
|
issuer: this.issuer,
|
||||||
signatureAlgorithm: this.signatureAlgorithm,
|
signatureAlgorithm: this.signatureAlgorithm,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,22 +1,22 @@
|
|||||||
|
import crypto from 'node:crypto';
|
||||||
|
import { QueryContext, ModelOptions } from 'objection';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import crypto from 'node:crypto';
|
import { PureAbility, fieldPatternMatcher, mongoQueryMatcher } from '@casl/ability';
|
||||||
import { ModelOptions, QueryContext } from 'objection';
|
import type { Subject } from '@casl/ability';
|
||||||
|
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
import checkLicense from '../helpers/check-license.ee';
|
|
||||||
import userAbility from '../helpers/user-ability';
|
|
||||||
import Base from './base';
|
import Base from './base';
|
||||||
import Connection from './connection';
|
|
||||||
import Execution from './execution';
|
|
||||||
import Flow from './flow';
|
|
||||||
import Identity from './identity.ee';
|
|
||||||
import Permission from './permission';
|
|
||||||
import ExtendedQueryBuilder from './query-builder';
|
import ExtendedQueryBuilder from './query-builder';
|
||||||
import Role from './role';
|
import Connection from './connection';
|
||||||
|
import Flow from './flow';
|
||||||
import Step from './step';
|
import Step from './step';
|
||||||
import Subscription from './subscription.ee';
|
import Role from './role';
|
||||||
|
import Permission from './permission';
|
||||||
|
import Execution from './execution';
|
||||||
|
import Identity from './identity.ee';
|
||||||
import UsageData from './usage-data.ee';
|
import UsageData from './usage-data.ee';
|
||||||
|
import Subscription from './subscription.ee';
|
||||||
|
|
||||||
class User extends Base {
|
class User extends Base {
|
||||||
id!: string;
|
id!: string;
|
||||||
@@ -148,11 +148,15 @@ class User extends Base {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
relation: Base.HasManyRelation,
|
relation: Base.ManyToManyRelation,
|
||||||
modelClass: Permission,
|
modelClass: Permission,
|
||||||
join: {
|
join: {
|
||||||
from: 'users.role_id',
|
from: 'users.role_id',
|
||||||
to: 'permissions.role_id',
|
through: {
|
||||||
|
from: 'roles_permissions.role_id',
|
||||||
|
to: 'roles_permissions.permission_id',
|
||||||
|
},
|
||||||
|
to: 'permissions.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
identities: {
|
identities: {
|
||||||
@@ -161,8 +165,8 @@ class User extends Base {
|
|||||||
join: {
|
join: {
|
||||||
from: 'identities.user_id',
|
from: 'identities.user_id',
|
||||||
to: 'users.id',
|
to: 'users.id',
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
login(password: string) {
|
login(password: string) {
|
||||||
@@ -288,44 +292,27 @@ class User extends Base {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $afterFind(): Promise<any> {
|
get ability() {
|
||||||
const hasValidLicense = await checkLicense();
|
if (!this.permissions) {
|
||||||
|
throw new Error('User.permissions must be fetched!');
|
||||||
if (hasValidLicense) return this;
|
|
||||||
|
|
||||||
if (Array.isArray(this.permissions)) {
|
|
||||||
this.permissions = this.permissions.filter((permission) => {
|
|
||||||
const isRolePermission = permission.subject === 'Role';
|
|
||||||
const isSamlAuthProviderPermission =
|
|
||||||
permission.subject === 'SamlAuthProvider';
|
|
||||||
|
|
||||||
return !isRolePermission && !isSamlAuthProviderPermission;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
// We're not using mongo, but our fields, conditions match
|
||||||
|
return new PureAbility(this.permissions, {
|
||||||
|
conditionsMatcher: mongoQueryMatcher,
|
||||||
|
fieldMatcher: fieldPatternMatcher
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get ability(): ReturnType<typeof userAbility> {
|
can(action: string, subject: Subject) {
|
||||||
return userAbility(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
can(action: string, subject: string) {
|
|
||||||
const can = this.ability.can(action, subject);
|
const can = this.ability.can(action, subject);
|
||||||
|
|
||||||
if (!can) throw new Error('Not authorized!');
|
if (!can) throw new Error('Not authorized!');
|
||||||
|
|
||||||
const relevantRule = this.ability.relevantRuleFor(action, subject);
|
return can;
|
||||||
|
|
||||||
const conditions = (relevantRule?.conditions as string[]) || [];
|
|
||||||
const conditionMap: Record<string, true> = Object.fromEntries(
|
|
||||||
conditions.map((condition) => [condition, true])
|
|
||||||
);
|
|
||||||
|
|
||||||
return conditionMap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cannot(action: string, subject: string) {
|
cannot(action: string, subject: Subject) {
|
||||||
const cannot = this.ability.cannot(action, subject);
|
const cannot = this.ability.cannot(action, subject);
|
||||||
|
|
||||||
if (cannot) throw new Error('Not authorized!');
|
if (cannot) throw new Error('Not authorized!');
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/cli",
|
"name": "@automatisch/cli",
|
||||||
"version": "0.8.0",
|
"version": "0.7.1",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"version": "oclif readme && git add README.md"
|
"version": "oclif readme && git add README.md"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/backend": "^0.8.0",
|
"@automatisch/backend": "^0.7.1",
|
||||||
"@oclif/core": "^1",
|
"@oclif/core": "^1",
|
||||||
"@oclif/plugin-help": "^5",
|
"@oclif/plugin-help": "^5",
|
||||||
"@oclif/plugin-plugins": "^2.0.1",
|
"@oclif/plugin-plugins": "^2.0.1",
|
||||||
|
@@ -15593,6 +15593,11 @@ winston@^3.6.0, winston@^3.7.1:
|
|||||||
triple-beam "^1.3.0"
|
triple-beam "^1.3.0"
|
||||||
winston-transport "^4.5.0"
|
winston-transport "^4.5.0"
|
||||||
|
|
||||||
|
word-wrap@^1.2.3, word-wrap@~1.2.3:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz"
|
||||||
|
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||||
|
|
||||||
workbox-background-sync@6.5.4:
|
workbox-background-sync@6.5.4:
|
||||||
version "6.5.4"
|
version "6.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9"
|
resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/docs",
|
"name": "@automatisch/docs",
|
||||||
"version": "0.8.0",
|
"version": "0.7.1",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@@ -157,7 +157,6 @@ export default defineConfig({
|
|||||||
collapsed: true,
|
collapsed: true,
|
||||||
items: [
|
items: [
|
||||||
{ text: 'Triggers', link: '/apps/notion/triggers' },
|
{ text: 'Triggers', link: '/apps/notion/triggers' },
|
||||||
{ text: 'Actions', link: '/apps/notion/actions' },
|
|
||||||
{ text: 'Connection', link: '/apps/notion/connection' },
|
{ text: 'Connection', link: '/apps/notion/connection' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@@ -5,8 +5,6 @@ items:
|
|||||||
desc: Create a blank spreadsheet or duplicate an existing spreadsheet. Optionally, provide headers.
|
desc: Create a blank spreadsheet or duplicate an existing spreadsheet. Optionally, provide headers.
|
||||||
- name: Create spreadsheet row
|
- name: Create spreadsheet row
|
||||||
desc: Creates a new row in a specific spreadsheet.
|
desc: Creates a new row in a specific spreadsheet.
|
||||||
- name: Create worksheet
|
|
||||||
desc: Create a blank worksheet with a title. Optionally, provide headers.
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
favicon: /favicons/notion.svg
|
|
||||||
items:
|
|
||||||
- name: Create database item
|
|
||||||
desc: Creates an item in a database.
|
|
||||||
- name: Create page
|
|
||||||
desc: Creates a page inside a parent page.
|
|
||||||
- name: Find database item
|
|
||||||
desc: Searches for an item in a database by property.
|
|
||||||
---
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import CustomListing from '../../components/CustomListing.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<CustomListing />
|
|
@@ -1,5 +1,9 @@
|
|||||||
# Available Apps
|
# Available Apps
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
We just have a few available integrations at the moment and we also know that workflows you can build with them is limited. But we still made the project public and want to share them with you so you can get a sense of what Automatisch can do. Meanwhile, we're working on adding more integrations and improving the existing ones.
|
||||||
|
:::
|
||||||
|
|
||||||
Following integrations are currently supported by Automatisch.
|
Following integrations are currently supported by Automatisch.
|
||||||
|
|
||||||
- [DeepL](/apps/deepl/actions)
|
- [DeepL](/apps/deepl/actions)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/e2e-tests",
|
"name": "@automatisch/e2e-tests",
|
||||||
"version": "0.8.0",
|
"version": "0.7.1",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
|
20
packages/types/index.d.ts
vendored
20
packages/types/index.d.ts
vendored
@@ -96,7 +96,6 @@ export interface IUser {
|
|||||||
flows: IFlow[];
|
flows: IFlow[];
|
||||||
steps: IStep[];
|
steps: IStep[];
|
||||||
role: IRole;
|
role: IRole;
|
||||||
permissions: IPermission[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRole {
|
export interface IRole {
|
||||||
@@ -105,20 +104,6 @@ export interface IRole {
|
|||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
permissions: IPermission[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPermission {
|
|
||||||
id: string;
|
|
||||||
action: string;
|
|
||||||
subject: string;
|
|
||||||
conditions: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPermissionCatalog {
|
|
||||||
actions: { label: string; key: string; subjects: string[] }[];
|
|
||||||
subjects: { label: string; key: string; }[];
|
|
||||||
conditions: { label: string; key: string; }[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFieldDropdown {
|
export interface IFieldDropdown {
|
||||||
@@ -432,11 +417,6 @@ declare module 'axios' {
|
|||||||
interface AxiosRequestConfig {
|
interface AxiosRequestConfig {
|
||||||
additionalProperties?: Record<string, unknown>;
|
additionalProperties?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ref: https://github.com/axios/axios/issues/5095
|
|
||||||
interface AxiosInstance {
|
|
||||||
create(config?: CreateAxiosDefaults): AxiosInstance;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRequest extends Request {
|
export interface IRequest extends Request {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/types",
|
"name": "@automatisch/types",
|
||||||
"version": "0.8.0",
|
"version": "0.7.1",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "Type definitions for automatisch",
|
"description": "Type definitions for automatisch",
|
||||||
"homepage": "https://github.com/automatisch/automatisch",
|
"homepage": "https://github.com/automatisch/automatisch",
|
||||||
|
@@ -1,13 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/web",
|
"name": "@automatisch/web",
|
||||||
"version": "0.8.0",
|
"version": "0.7.1",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.6.9",
|
"@apollo/client": "^3.6.9",
|
||||||
"@automatisch/types": "^0.8.0",
|
"@automatisch/types": "^0.7.1",
|
||||||
"@casl/ability": "^6.5.0",
|
|
||||||
"@casl/react": "^3.1.0",
|
|
||||||
"@emotion/react": "^11.4.1",
|
"@emotion/react": "^11.4.1",
|
||||||
"@emotion/styled": "^11.3.0",
|
"@emotion/styled": "^11.3.0",
|
||||||
"@hookform/resolvers": "^2.8.8",
|
"@hookform/resolvers": "^2.8.8",
|
||||||
@@ -33,7 +31,7 @@
|
|||||||
"notistack": "^2.0.2",
|
"notistack": "^2.0.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-hook-form": "^7.45.2",
|
"react-hook-form": "^7.43.9",
|
||||||
"react-intl": "^5.20.12",
|
"react-intl": "^5.20.12",
|
||||||
"react-json-tree": "^0.16.2",
|
"react-json-tree": "^0.16.2",
|
||||||
"react-router-dom": "^6.0.2",
|
"react-router-dom": "^6.0.2",
|
||||||
|
@@ -8,74 +8,60 @@ import CreateRole from 'pages/CreateRole/index.ee';
|
|||||||
import EditRole from 'pages/EditRole/index.ee';
|
import EditRole from 'pages/EditRole/index.ee';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import Can from 'components/Can';
|
|
||||||
|
|
||||||
// TODO: consider introducing redirections to `/` as fallback
|
|
||||||
export default (
|
export default (
|
||||||
<>
|
<>
|
||||||
<Route
|
<Route
|
||||||
path={URLS.USERS}
|
path={URLS.USERS}
|
||||||
element={
|
element={
|
||||||
<Can I="read" a="User">
|
<AdminSettingsLayout>
|
||||||
<AdminSettingsLayout>
|
<Users />
|
||||||
<Users />
|
</AdminSettingsLayout>
|
||||||
</AdminSettingsLayout>
|
|
||||||
</Can>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={URLS.CREATE_USER}
|
path={URLS.CREATE_USER}
|
||||||
element={
|
element={
|
||||||
<Can I="create" a="User">
|
<AdminSettingsLayout>
|
||||||
<AdminSettingsLayout>
|
<CreateUser />
|
||||||
<CreateUser />
|
</AdminSettingsLayout>
|
||||||
</AdminSettingsLayout>
|
|
||||||
</Can>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={URLS.USER_PATTERN}
|
path={URLS.USER_PATTERN}
|
||||||
element={
|
element={
|
||||||
<Can I="update" a="User">
|
<AdminSettingsLayout>
|
||||||
<AdminSettingsLayout>
|
<EditUser />
|
||||||
<EditUser />
|
</AdminSettingsLayout>
|
||||||
</AdminSettingsLayout>
|
|
||||||
</Can>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={URLS.ROLES}
|
path={URLS.ROLES}
|
||||||
element={
|
element={
|
||||||
<Can I="read" a="Role">
|
<AdminSettingsLayout>
|
||||||
<AdminSettingsLayout>
|
<Roles />
|
||||||
<Roles />
|
</AdminSettingsLayout>
|
||||||
</AdminSettingsLayout>
|
|
||||||
</Can>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={URLS.CREATE_ROLE}
|
path={URLS.CREATE_ROLE}
|
||||||
element={
|
element={
|
||||||
<Can I="create" a="Role">
|
<AdminSettingsLayout>
|
||||||
<AdminSettingsLayout>
|
<CreateRole />
|
||||||
<CreateRole />
|
</AdminSettingsLayout>
|
||||||
</AdminSettingsLayout>
|
|
||||||
</Can>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={URLS.ROLE_PATTERN}
|
path={URLS.ROLE_PATTERN}
|
||||||
element={
|
element={
|
||||||
<Can I="update" a="Role">
|
<AdminSettingsLayout>
|
||||||
<AdminSettingsLayout>
|
<EditRole />
|
||||||
<EditRole />
|
</AdminSettingsLayout>
|
||||||
</AdminSettingsLayout>
|
|
||||||
</Can>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@@ -4,7 +4,6 @@ import MenuItem from '@mui/material/MenuItem';
|
|||||||
import Menu, { MenuProps } from '@mui/material/Menu';
|
import Menu, { MenuProps } from '@mui/material/Menu';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import Can from 'components/Can';
|
|
||||||
import apolloClient from 'graphql/client';
|
import apolloClient from 'graphql/client';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import useAuthentication from 'hooks/useAuthentication';
|
import useAuthentication from 'hooks/useAuthentication';
|
||||||
@@ -55,14 +54,9 @@ function AccountDropdownMenu(
|
|||||||
{formatMessage('accountDropdownMenu.settings')}
|
{formatMessage('accountDropdownMenu.settings')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
<Can I="read" a="User">
|
<MenuItem component={Link} to={URLS.ADMIN_SETTINGS_DASHBOARD}>
|
||||||
<MenuItem
|
{formatMessage('accountDropdownMenu.adminSettings')}
|
||||||
component={Link}
|
</MenuItem>
|
||||||
to={URLS.ADMIN_SETTINGS_DASHBOARD}
|
|
||||||
>
|
|
||||||
{formatMessage('accountDropdownMenu.adminSettings')}
|
|
||||||
</MenuItem>
|
|
||||||
</Can>
|
|
||||||
|
|
||||||
<MenuItem onClick={logout} data-test="logout-item">
|
<MenuItem onClick={logout} data-test="logout-item">
|
||||||
{formatMessage('accountDropdownMenu.logout')}
|
{formatMessage('accountDropdownMenu.logout')}
|
||||||
|
@@ -12,7 +12,7 @@ import useFormatMessage from 'hooks/useFormatMessage';
|
|||||||
import computeAuthStepVariables from 'helpers/computeAuthStepVariables';
|
import computeAuthStepVariables from 'helpers/computeAuthStepVariables';
|
||||||
import { processStep } from 'helpers/authenticationSteps';
|
import { processStep } from 'helpers/authenticationSteps';
|
||||||
import InputCreator from 'components/InputCreator';
|
import InputCreator from 'components/InputCreator';
|
||||||
import { generateExternalLink } from '../../helpers/translationValues';
|
import { generateExternalLink } from '../../helpers/translation-values';
|
||||||
import { Form } from './style';
|
import { Form } from './style';
|
||||||
|
|
||||||
type AddAppConnectionProps = {
|
type AddAppConnectionProps = {
|
||||||
|
@@ -1,42 +1,34 @@
|
|||||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
import * as React from 'react';
|
||||||
import GroupIcon from '@mui/icons-material/Group';
|
|
||||||
import GroupsIcon from '@mui/icons-material/Groups';
|
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||||
import * as React from 'react';
|
import GroupIcon from '@mui/icons-material/Group';
|
||||||
|
import GroupsIcon from '@mui/icons-material/Groups';
|
||||||
|
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||||
|
|
||||||
import { SvgIconComponent } from '@mui/icons-material';
|
import * as URLS from 'config/urls';
|
||||||
|
import useAutomatischInfo from 'hooks/useAutomatischInfo';
|
||||||
import AppBar from 'components/AppBar';
|
import AppBar from 'components/AppBar';
|
||||||
import Drawer from 'components/Drawer';
|
import Drawer from 'components/Drawer';
|
||||||
import * as URLS from 'config/urls';
|
|
||||||
import useCurrentUserAbility from 'hooks/useCurrentUserAbility';
|
|
||||||
|
|
||||||
type SettingsLayoutProps = {
|
type SettingsLayoutProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DrawerLink = {
|
function createDrawerLinks({ isCloud }: { isCloud: boolean }) {
|
||||||
Icon: SvgIconComponent,
|
|
||||||
primary: string,
|
|
||||||
to: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDrawerLinks({ canReadRole, canReadUser }: { canReadRole: boolean; canReadUser: boolean; }) {
|
|
||||||
const items = [
|
const items = [
|
||||||
canReadUser ? {
|
{
|
||||||
Icon: GroupIcon,
|
Icon: GroupIcon,
|
||||||
primary: 'adminSettingsDrawer.users',
|
primary: 'adminSettingsDrawer.users',
|
||||||
to: URLS.USERS,
|
to: URLS.USERS,
|
||||||
} : null,
|
},
|
||||||
canReadRole ? {
|
{
|
||||||
Icon: GroupsIcon,
|
Icon: GroupsIcon,
|
||||||
primary: 'adminSettingsDrawer.roles',
|
primary: 'adminSettingsDrawer.roles',
|
||||||
to: URLS.ROLES,
|
to: URLS.ROLES,
|
||||||
} : null
|
}
|
||||||
]
|
]
|
||||||
.filter(Boolean) as DrawerLink[];
|
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
@@ -52,17 +44,14 @@ const drawerBottomLinks = [
|
|||||||
export default function SettingsLayout({
|
export default function SettingsLayout({
|
||||||
children,
|
children,
|
||||||
}: SettingsLayoutProps): React.ReactElement {
|
}: SettingsLayoutProps): React.ReactElement {
|
||||||
|
const { isCloud } = useAutomatischInfo();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const currentUserAbility = useCurrentUserAbility();
|
|
||||||
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
|
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'));
|
||||||
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
|
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
|
||||||
|
|
||||||
const openDrawer = () => setDrawerOpen(true);
|
const openDrawer = () => setDrawerOpen(true);
|
||||||
const closeDrawer = () => setDrawerOpen(false);
|
const closeDrawer = () => setDrawerOpen(false);
|
||||||
const drawerLinks = createDrawerLinks({
|
const drawerLinks = createDrawerLinks({ isCloud });
|
||||||
canReadUser: currentUserAbility.can('read', 'User'),
|
|
||||||
canReadRole: currentUserAbility.can('read', 'Role'),
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@@ -1,22 +0,0 @@
|
|||||||
import { Can as OriginalCan } from '@casl/react';
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import useCurrentUserAbility from 'hooks/useCurrentUserAbility';
|
|
||||||
|
|
||||||
type CanProps = {
|
|
||||||
I: string;
|
|
||||||
a: string;
|
|
||||||
passThrough?: boolean;
|
|
||||||
children: React.ReactNode | ((isAllowed: boolean) => React.ReactNode);
|
|
||||||
} | {
|
|
||||||
I: string;
|
|
||||||
an: string;
|
|
||||||
passThrough?: boolean;
|
|
||||||
children: React.ReactNode | ((isAllowed: boolean) => React.ReactNode);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Can(props: CanProps) {
|
|
||||||
const currentUserAbility = useCurrentUserAbility();
|
|
||||||
|
|
||||||
return (<OriginalCan ability={currentUserAbility} {...props} />);
|
|
||||||
};
|
|
@@ -20,7 +20,6 @@ export default function ConditionalIconButton(props: any): React.ReactElement {
|
|||||||
size={buttonProps.size}
|
size={buttonProps.size}
|
||||||
component={buttonProps.component}
|
component={buttonProps.component}
|
||||||
to={buttonProps.to}
|
to={buttonProps.to}
|
||||||
disabled={buttonProps.disabled}
|
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@@ -2,7 +2,7 @@ import { styled } from '@mui/material/styles';
|
|||||||
import MuiIconButton, { iconButtonClasses } from '@mui/material/IconButton';
|
import MuiIconButton, { iconButtonClasses } from '@mui/material/IconButton';
|
||||||
|
|
||||||
export const IconButton = styled(MuiIconButton)`
|
export const IconButton = styled(MuiIconButton)`
|
||||||
&.${iconButtonClasses.colorPrimary}:not(.${iconButtonClasses.disabled}) {
|
&.${iconButtonClasses.colorPrimary} {
|
||||||
background: ${({ theme }) => theme.palette.primary.main};
|
background: ${({ theme }) => theme.palette.primary.main};
|
||||||
color: ${({ theme }) => theme.palette.primary.contrastText};
|
color: ${({ theme }) => theme.palette.primary.contrastText};
|
||||||
|
|
||||||
|
@@ -1,57 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
|
||||||
import Checkbox, { CheckboxProps } from '@mui/material/Checkbox';
|
|
||||||
|
|
||||||
type ControlledCheckboxProps = {
|
|
||||||
name: string;
|
|
||||||
} & CheckboxProps;
|
|
||||||
|
|
||||||
export default function ControlledCheckbox(props: ControlledCheckboxProps): React.ReactElement {
|
|
||||||
const { control } = useFormContext();
|
|
||||||
const {
|
|
||||||
required,
|
|
||||||
name,
|
|
||||||
defaultValue = false,
|
|
||||||
disabled = false,
|
|
||||||
onBlur,
|
|
||||||
onChange,
|
|
||||||
...checkboxProps
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Controller
|
|
||||||
rules={{ required }}
|
|
||||||
name={name}
|
|
||||||
defaultValue={defaultValue}
|
|
||||||
control={control}
|
|
||||||
render={({
|
|
||||||
field: {
|
|
||||||
ref,
|
|
||||||
onChange: controllerOnChange,
|
|
||||||
onBlur: controllerOnBlur,
|
|
||||||
value,
|
|
||||||
name,
|
|
||||||
...field
|
|
||||||
},
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Checkbox
|
|
||||||
{...checkboxProps}
|
|
||||||
{...field}
|
|
||||||
checked={!!value}
|
|
||||||
name={name}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={(...args) => {
|
|
||||||
controllerOnChange(...args);
|
|
||||||
onChange?.(...args);
|
|
||||||
}}
|
|
||||||
onBlur={(...args) => {
|
|
||||||
controllerOnBlur();
|
|
||||||
onBlur?.(...args);
|
|
||||||
}}
|
|
||||||
inputRef={ref}
|
|
||||||
/>
|
|
||||||
)}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -2,54 +2,35 @@ import * as React from 'react';
|
|||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import { useSnackbar } from 'notistack';
|
|
||||||
|
|
||||||
import Can from 'components/Can';
|
|
||||||
import ConfirmationDialog from 'components/ConfirmationDialog';
|
import ConfirmationDialog from 'components/ConfirmationDialog';
|
||||||
import { DELETE_ROLE } from 'graphql/mutations/delete-role.ee';
|
import { DELETE_ROLE } from 'graphql/mutations/delete-role.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
type DeleteRoleButtonProps = {
|
type DeleteRoleButtonProps = {
|
||||||
disabled?: boolean;
|
|
||||||
roleId: string;
|
roleId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
|
export default function DeleteRoleButton(props: DeleteRoleButtonProps) {
|
||||||
const { disabled, roleId } = props;
|
const { roleId } = props;
|
||||||
const [showConfirmation, setShowConfirmation] = React.useState(false);
|
const [showConfirmation, setShowConfirmation] = React.useState(false);
|
||||||
const [deleteRole] = useMutation(DELETE_ROLE, {
|
const [deleteRole] = useMutation(DELETE_ROLE, {
|
||||||
variables: { input: { id: roleId } },
|
variables: { input: { id: roleId } },
|
||||||
refetchQueries: ['GetRoles'],
|
refetchQueries: ['GetRoles'],
|
||||||
});
|
});
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
|
||||||
|
|
||||||
const handleConfirm = React.useCallback(async () => {
|
const handleConfirm = React.useCallback(async () => {
|
||||||
try {
|
await deleteRole();
|
||||||
await deleteRole();
|
|
||||||
|
|
||||||
setShowConfirmation(false);
|
setShowConfirmation(false);
|
||||||
enqueueSnackbar(formatMessage('deleteRoleButton.successfullyDeleted'), {
|
|
||||||
variant: 'success',
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Failed while deleting!');
|
|
||||||
}
|
|
||||||
}, [deleteRole]);
|
}, [deleteRole]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Can I="delete" a="Role" passThrough>
|
<IconButton onClick={() => setShowConfirmation(true)} size="small">
|
||||||
{(allowed) => (
|
<DeleteIcon />
|
||||||
<IconButton
|
</IconButton>
|
||||||
disabled={!allowed || disabled}
|
|
||||||
onClick={() => setShowConfirmation(true)}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Can>
|
|
||||||
|
|
||||||
<ConfirmationDialog
|
<ConfirmationDialog
|
||||||
open={showConfirmation}
|
open={showConfirmation}
|
||||||
|
@@ -2,7 +2,6 @@ import * as React from 'react';
|
|||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import { useSnackbar } from 'notistack';
|
|
||||||
|
|
||||||
import ConfirmationDialog from 'components/ConfirmationDialog';
|
import ConfirmationDialog from 'components/ConfirmationDialog';
|
||||||
import { DELETE_USER } from 'graphql/mutations/delete-user.ee';
|
import { DELETE_USER } from 'graphql/mutations/delete-user.ee';
|
||||||
@@ -20,19 +19,11 @@ export default function DeleteUserButton(props: DeleteUserButtonProps) {
|
|||||||
refetchQueries: ['GetUsers'],
|
refetchQueries: ['GetUsers'],
|
||||||
});
|
});
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
|
||||||
|
|
||||||
const handleConfirm = React.useCallback(async () => {
|
const handleConfirm = React.useCallback(async () => {
|
||||||
try {
|
await deleteUser();
|
||||||
await deleteUser();
|
|
||||||
|
|
||||||
setShowConfirmation(false);
|
setShowConfirmation(false);
|
||||||
enqueueSnackbar(formatMessage('deleteUserButton.successfullyDeleted'), {
|
|
||||||
variant: 'success',
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Failed while deleting!');
|
|
||||||
}
|
|
||||||
}, [deleteUser]);
|
}, [deleteUser]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -6,7 +6,6 @@ import type { PopoverProps } from '@mui/material/Popover';
|
|||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import { useSnackbar } from 'notistack';
|
import { useSnackbar } from 'notistack';
|
||||||
|
|
||||||
import Can from 'components/Can';
|
|
||||||
import { DELETE_FLOW } from 'graphql/mutations/delete-flow';
|
import { DELETE_FLOW } from 'graphql/mutations/delete-flow';
|
||||||
import { DUPLICATE_FLOW } from 'graphql/mutations/duplicate-flow';
|
import { DUPLICATE_FLOW } from 'graphql/mutations/duplicate-flow';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
@@ -73,39 +72,13 @@ export default function ContextMenu(
|
|||||||
hideBackdrop={false}
|
hideBackdrop={false}
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
>
|
>
|
||||||
<Can I="read" a="Flow" passThrough>
|
<MenuItem component={Link} to={URLS.FLOW(flowId)}>
|
||||||
{(allowed) => (
|
{formatMessage('flow.view')}
|
||||||
<MenuItem
|
</MenuItem>
|
||||||
disabled={!allowed}
|
|
||||||
component={Link}
|
|
||||||
to={URLS.FLOW(flowId)}
|
|
||||||
>
|
|
||||||
{formatMessage('flow.view')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Can>
|
|
||||||
|
|
||||||
<Can I="create" a="Flow" passThrough>
|
<MenuItem onClick={onFlowDuplicate}>{formatMessage('flow.duplicate')}</MenuItem>
|
||||||
{(allowed) => (
|
|
||||||
<MenuItem
|
|
||||||
disabled={!allowed}
|
|
||||||
onClick={onFlowDuplicate}
|
|
||||||
>
|
|
||||||
{formatMessage('flow.duplicate')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Can>
|
|
||||||
|
|
||||||
<Can I="delete" a="Flow" passThrough>
|
<MenuItem onClick={onFlowDelete}>{formatMessage('flow.delete')}</MenuItem>
|
||||||
{(allowed) => (
|
|
||||||
<MenuItem
|
|
||||||
disabled={!allowed}
|
|
||||||
onClick={onFlowDelete}
|
|
||||||
>
|
|
||||||
{formatMessage('flow.delete')}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</Can>
|
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
47
packages/web/src/components/ListLoader/index.tsx
Normal file
47
packages/web/src/components/ListLoader/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
TableCell,
|
||||||
|
TableRow,
|
||||||
|
} from '@mui/material';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
|
||||||
|
type ListLoaderProps = {
|
||||||
|
rowsNumber: number;
|
||||||
|
cellNumber: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ListLoader = ({ rowsNumber, cellNumber }: ListLoaderProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{[...Array(rowsNumber)].map((row, index) => (
|
||||||
|
<TableRow
|
||||||
|
key={index}
|
||||||
|
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||||
|
>
|
||||||
|
{[...Array(cellNumber)].map((cell, index) => (
|
||||||
|
<TableCell key={index} scope="row">
|
||||||
|
<Skeleton />
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<TableCell>
|
||||||
|
<Stack direction="row" gap={1} justifyContent="right">
|
||||||
|
<IconButton size="small">
|
||||||
|
<EditIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<IconButton size="small">
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListLoader;
|
@@ -1,142 +0,0 @@
|
|||||||
import Button from '@mui/material/Button';
|
|
||||||
import Dialog from '@mui/material/Dialog';
|
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
|
||||||
import Paper from '@mui/material/Paper';
|
|
||||||
import Table from '@mui/material/Table';
|
|
||||||
import TableBody from '@mui/material/TableBody';
|
|
||||||
import TableCell from '@mui/material/TableCell';
|
|
||||||
import TableContainer from '@mui/material/TableContainer';
|
|
||||||
import TableHead from '@mui/material/TableHead';
|
|
||||||
import TableRow from '@mui/material/TableRow';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import * as React from 'react';
|
|
||||||
import { useFormContext } from 'react-hook-form';
|
|
||||||
|
|
||||||
import { IPermissionCatalog } from '@automatisch/types';
|
|
||||||
import ControlledCheckbox from 'components/ControlledCheckbox';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
|
||||||
|
|
||||||
type PermissionSettingsProps = {
|
|
||||||
onClose: () => void;
|
|
||||||
fieldPrefix: string;
|
|
||||||
subject: string;
|
|
||||||
actions: IPermissionCatalog['actions'];
|
|
||||||
conditions: IPermissionCatalog['conditions'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PermissionSettings(props: PermissionSettingsProps) {
|
|
||||||
const {
|
|
||||||
onClose,
|
|
||||||
fieldPrefix,
|
|
||||||
subject,
|
|
||||||
actions,
|
|
||||||
conditions,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const formatMessage = useFormatMessage();
|
|
||||||
const { getValues, resetField } = useFormContext();
|
|
||||||
|
|
||||||
const cancel = () => {
|
|
||||||
for (const action of actions) {
|
|
||||||
for (const condition of conditions) {
|
|
||||||
const fieldName = `${fieldPrefix}.${action.key}.conditions.${condition.key}`;
|
|
||||||
resetField(fieldName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
const apply = () => {
|
|
||||||
for (const action of actions) {
|
|
||||||
for (const condition of conditions) {
|
|
||||||
const fieldName = `${fieldPrefix}.${action.key}.conditions.${condition.key}`;
|
|
||||||
const value = getValues(fieldName);
|
|
||||||
resetField(fieldName, { defaultValue: value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open onClose={cancel}>
|
|
||||||
<DialogTitle>
|
|
||||||
{formatMessage('permissionSettings.title')}
|
|
||||||
</DialogTitle>
|
|
||||||
|
|
||||||
<DialogContent>
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell component="th" />
|
|
||||||
|
|
||||||
{actions.map(action => (
|
|
||||||
<TableCell component="th" key={action.key}>
|
|
||||||
<Typography
|
|
||||||
variant="subtitle1"
|
|
||||||
align="center"
|
|
||||||
sx={{
|
|
||||||
color: 'text.secondary',
|
|
||||||
fontWeight: 700
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{action.label}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{conditions.map((condition) => (
|
|
||||||
<TableRow
|
|
||||||
key={condition.key}
|
|
||||||
sx={{ '&:last-child td': { border: 0 } }}
|
|
||||||
>
|
|
||||||
<TableCell scope="row">
|
|
||||||
<Typography
|
|
||||||
variant="subtitle2"
|
|
||||||
>
|
|
||||||
{condition.label}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
{actions.map((action) => (
|
|
||||||
<TableCell
|
|
||||||
key={`${action.key}.${condition.key}`}
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="subtitle2"
|
|
||||||
>
|
|
||||||
{action.subjects.includes(subject) && (
|
|
||||||
<ControlledCheckbox
|
|
||||||
name={`${fieldPrefix}.${action.key}.conditions.${condition.key}`}
|
|
||||||
disabled={getValues(`${fieldPrefix}.${action.key}.value`) !== true}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!action.subjects.includes(subject) && '-'}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
</DialogContent>
|
|
||||||
|
|
||||||
<DialogActions>
|
|
||||||
<Button onClick={cancel}>{formatMessage('permissionSettings.cancel')}</Button>
|
|
||||||
|
|
||||||
<Button onClick={apply} color="error">
|
|
||||||
{formatMessage('permissionSettings.apply')}
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
@@ -1,122 +0,0 @@
|
|||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
|
||||||
import IconButton from '@mui/material/IconButton';
|
|
||||||
import Paper from '@mui/material/Paper';
|
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import Table from '@mui/material/Table';
|
|
||||||
import TableBody from '@mui/material/TableBody';
|
|
||||||
import TableCell from '@mui/material/TableCell';
|
|
||||||
import TableContainer from '@mui/material/TableContainer';
|
|
||||||
import TableHead from '@mui/material/TableHead';
|
|
||||||
import TableRow from '@mui/material/TableRow';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import ControlledCheckbox from 'components/ControlledCheckbox';
|
|
||||||
import usePermissionCatalog from 'hooks/usePermissionCatalog.ee';
|
|
||||||
import PermissionSettings from './PermissionSettings.ee';
|
|
||||||
|
|
||||||
type PermissionCatalogFieldProps = {
|
|
||||||
name?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PermissionCatalogField = ({ name = 'permissions', disabled = false }: PermissionCatalogFieldProps) => {
|
|
||||||
const permissionCatalog = usePermissionCatalog();
|
|
||||||
const [dialogName, setDialogName] = React.useState<string>();
|
|
||||||
|
|
||||||
if (!permissionCatalog) return (<React.Fragment />);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableContainer component={Paper}>
|
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell component="th" />
|
|
||||||
|
|
||||||
{permissionCatalog.actions.map(action => (
|
|
||||||
<TableCell component="th" key={action.key}>
|
|
||||||
<Typography
|
|
||||||
variant="subtitle1"
|
|
||||||
align="center"
|
|
||||||
sx={{
|
|
||||||
color: 'text.secondary',
|
|
||||||
fontWeight: 700
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{action.label}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<TableCell component="th" />
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{permissionCatalog.subjects.map((subject) => (
|
|
||||||
<TableRow
|
|
||||||
key={subject.key}
|
|
||||||
sx={{ '&:last-child td': { border: 0 } }}
|
|
||||||
>
|
|
||||||
<TableCell scope="row">
|
|
||||||
<Typography
|
|
||||||
variant="subtitle2"
|
|
||||||
>
|
|
||||||
{subject.label}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
|
|
||||||
{permissionCatalog.actions.map((action) => (
|
|
||||||
<TableCell
|
|
||||||
key={`${subject.key}.${action.key}`}
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="subtitle2"
|
|
||||||
>
|
|
||||||
{action.subjects.includes(subject.key) && (
|
|
||||||
<ControlledCheckbox
|
|
||||||
disabled={disabled}
|
|
||||||
name={`${name}.${subject.key}.${action.key}.value`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!action.subjects.includes(subject.key) && '-'}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<TableCell>
|
|
||||||
<Stack
|
|
||||||
direction="row"
|
|
||||||
gap={1}
|
|
||||||
justifyContent="right"
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
color="info"
|
|
||||||
size="small"
|
|
||||||
onClick={() => setDialogName(subject.key)}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
<SettingsIcon />
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
{dialogName === subject.key && (
|
|
||||||
<PermissionSettings
|
|
||||||
onClose={() => setDialogName('')}
|
|
||||||
fieldPrefix={`${name}.${subject.key}`}
|
|
||||||
subject={subject.key}
|
|
||||||
actions={permissionCatalog.actions}
|
|
||||||
conditions={permissionCatalog.conditions}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PermissionCatalogField;
|
|
@@ -13,14 +13,15 @@ import Typography from '@mui/material/Typography';
|
|||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
|
||||||
import DeleteRoleButton from 'components/DeleteRoleButton/index.ee';
|
import DeleteRoleButton from 'components/DeleteRoleButton/index.ee';
|
||||||
|
import ListLoader from 'components/ListLoader';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useRoles from 'hooks/useRoles.ee';
|
import useRoles from 'hooks/useRoles.ee';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
|
|
||||||
// TODO: introduce loading bar
|
// TODO: introduce interaction feedback upon deletion (successful + failure)
|
||||||
export default function RoleList(): React.ReactElement {
|
export default function RoleList(): React.ReactElement {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { roles } = useRoles();
|
const { roles, loading } = useRoles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContainer component={Paper}>
|
<TableContainer component={Paper}>
|
||||||
@@ -49,34 +50,40 @@ export default function RoleList(): React.ReactElement {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{roles.map((role) => (
|
{loading ? (
|
||||||
<TableRow
|
<ListLoader rowsNumber={3} cellNumber={2} />
|
||||||
key={role.id}
|
) : (
|
||||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
roles.map((role) => (
|
||||||
>
|
<TableRow
|
||||||
<TableCell scope="row">
|
key={role.id}
|
||||||
<Typography variant="subtitle2">{role.name}</Typography>
|
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||||
</TableCell>
|
>
|
||||||
|
<TableCell scope="row">
|
||||||
|
<Typography variant="subtitle2">{role.name}</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell scope="row">
|
<TableCell scope="row">
|
||||||
<Typography variant="subtitle2">{role.description}</Typography>
|
<Typography variant="subtitle2">
|
||||||
</TableCell>
|
{role.description}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Stack direction="row" gap={1} justifyContent="right">
|
<Stack direction="row" gap={1} justifyContent="right">
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={URLS.ROLE(role.id)}
|
to={URLS.ROLE(role.id)}
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<DeleteRoleButton disabled={role.isAdmin} roleId={role.id} />
|
<DeleteRoleButton roleId={role.id} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
@@ -4,7 +4,7 @@ import Button from '@mui/material/Button';
|
|||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
import appConfig from 'config/app';
|
||||||
import useSamlAuthProviders from 'hooks/useSamlAuthProviders.ee';
|
import useSamlAuthProviders from 'hooks/useSamlAuthProviders.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
@@ -24,12 +24,10 @@ function SsoProviders() {
|
|||||||
<Button
|
<Button
|
||||||
key={provider.id}
|
key={provider.id}
|
||||||
component="a"
|
component="a"
|
||||||
href={URLS.SSO_LOGIN(provider.issuer)}
|
href={`${appConfig.apiUrl}/login/saml/${provider.issuer}`}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
>
|
>
|
||||||
{formatMessage('ssoProviders.loginWithProvider', {
|
{provider.name}
|
||||||
providerName: provider.name
|
|
||||||
})}
|
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@@ -3,7 +3,7 @@ import Alert from '@mui/material/Alert';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
import { generateInternalLink } from 'helpers/translationValues';
|
import { generateInternalLink } from 'helpers/translation-values';
|
||||||
import useTrialStatus from 'hooks/useTrialStatus.ee';
|
import useTrialStatus from 'hooks/useTrialStatus.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
|
@@ -13,11 +13,13 @@ import Typography from '@mui/material/Typography';
|
|||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
|
||||||
import DeleteUserButton from 'components/DeleteUserButton/index.ee';
|
import DeleteUserButton from 'components/DeleteUserButton/index.ee';
|
||||||
|
import ListLoader from 'components/ListLoader';
|
||||||
import useUsers from 'hooks/useUsers';
|
import useUsers from 'hooks/useUsers';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import * as URLS from 'config/urls';
|
import * as URLS from 'config/urls';
|
||||||
|
|
||||||
// TODO: introduce loading bar
|
// TODO: introduce translation entries
|
||||||
|
// TODO: introduce interaction feedback upon deletion (successful + failure)
|
||||||
export default function UserList(): React.ReactElement {
|
export default function UserList(): React.ReactElement {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { users, loading } = useUsers();
|
const { users, loading } = useUsers();
|
||||||
@@ -49,34 +51,38 @@ export default function UserList(): React.ReactElement {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{users.map((user) => (
|
{loading ? (
|
||||||
<TableRow
|
<ListLoader rowsNumber={3} cellNumber={2} />
|
||||||
key={user.id}
|
) : (
|
||||||
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
users.map((user) => (
|
||||||
>
|
<TableRow
|
||||||
<TableCell scope="row">
|
key={user.id}
|
||||||
<Typography variant="subtitle2">{user.fullName}</Typography>
|
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
|
||||||
</TableCell>
|
>
|
||||||
|
<TableCell scope="row">
|
||||||
|
<Typography variant="subtitle2">{user.fullName}</Typography>
|
||||||
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Typography variant="subtitle2">{user.email}</Typography>
|
<Typography variant="subtitle2">{user.email}</Typography>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Stack direction="row" gap={1} justifyContent="right">
|
<Stack direction="row" gap={1} justifyContent="right">
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
component={Link}
|
component={Link}
|
||||||
to={URLS.USER(user.id)}
|
to={URLS.USER(user.id)}
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<EditIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<DeleteUserButton userId={user.id} />
|
<DeleteUserButton userId={user.id} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))
|
||||||
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
@@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import type { AlertProps } from '@mui/material/Alert';
|
import type { AlertProps } from '@mui/material/Alert';
|
||||||
|
|
||||||
import { generateExternalLink } from '../../helpers/translationValues';
|
import { generateExternalLink } from '../../helpers/translation-values';
|
||||||
import { WEBHOOK_DOCS } from '../../config/urls';
|
import { WEBHOOK_DOCS } from '../../config/urls';
|
||||||
import TextField from '../TextField';
|
import TextField from '../TextField';
|
||||||
import { Alert } from './style';
|
import { Alert } from './style';
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import MuiAlert, { alertClasses } from '@mui/material/Alert';
|
import MuiAlert, { alertClasses } from '@mui/material/Alert';
|
||||||
|
|
||||||
export const Alert = styled(MuiAlert)(() => ({
|
export const Alert = styled(MuiAlert)(({ theme }) => ({
|
||||||
[`&.${alertClasses.root}`]: {
|
[`&.${alertClasses.root}`]: {
|
||||||
fontWeight: 300,
|
fontWeight: 300,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
@@ -17,9 +17,7 @@ const config: Config = {
|
|||||||
supportEmailAddress: 'support@automatisch.io'
|
supportEmailAddress: 'support@automatisch.io'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!config.apiUrl && !config.graphqlUrl) {
|
if (!config.apiUrl) {
|
||||||
config.apiUrl = '/';
|
|
||||||
} else if (!config.apiUrl) {
|
|
||||||
config.apiUrl = (new URL(config.graphqlUrl)).origin;
|
config.apiUrl = (new URL(config.graphqlUrl)).origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
import appConfig from './app';
|
|
||||||
|
|
||||||
export const CONNECTIONS = '/connections';
|
export const CONNECTIONS = '/connections';
|
||||||
export const EXECUTIONS = '/executions';
|
export const EXECUTIONS = '/executions';
|
||||||
export const EXECUTION_PATTERN = '/executions/:executionId';
|
export const EXECUTION_PATTERN = '/executions/:executionId';
|
||||||
@@ -8,7 +6,6 @@ export const EXECUTION = (executionId: string) =>
|
|||||||
|
|
||||||
export const LOGIN = '/login';
|
export const LOGIN = '/login';
|
||||||
export const LOGIN_CALLBACK = `${LOGIN}/callback`;
|
export const LOGIN_CALLBACK = `${LOGIN}/callback`;
|
||||||
export const SSO_LOGIN = (issuer: string) => `${appConfig.apiUrl}/login/saml/${issuer}`;
|
|
||||||
export const SIGNUP = '/sign-up';
|
export const SIGNUP = '/sign-up';
|
||||||
export const FORGOT_PASSWORD = '/forgot-password';
|
export const FORGOT_PASSWORD = '/forgot-password';
|
||||||
export const RESET_PASSWORD = '/reset-password';
|
export const RESET_PASSWORD = '/reset-password';
|
||||||
|
@@ -6,12 +6,6 @@ export const UPDATE_ROLE = gql`
|
|||||||
id
|
id
|
||||||
name
|
name
|
||||||
description
|
description
|
||||||
permissions {
|
|
||||||
id
|
|
||||||
action
|
|
||||||
subject
|
|
||||||
conditions
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -7,11 +7,9 @@ export const GET_CURRENT_USER = gql`
|
|||||||
fullName
|
fullName
|
||||||
email
|
email
|
||||||
role {
|
role {
|
||||||
id
|
|
||||||
isAdmin
|
isAdmin
|
||||||
}
|
}
|
||||||
permissions {
|
permissions {
|
||||||
id
|
|
||||||
action
|
action
|
||||||
subject
|
subject
|
||||||
conditions
|
conditions
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user