Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
99025c6a7c |
1
packages/backend/src/apps/gitea/assets/favicon.svg
Normal file
1
packages/backend/src/apps/gitea/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640" xml:space="preserve" width="32" height="32"><path style="fill:#fff" d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z"/><path style="fill:#609926" d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z"/><path style="fill:#609926" d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z"/></svg>
|
After Width: | Height: | Size: 2.2 KiB |
24
packages/backend/src/apps/gitea/auth/generate-auth-url.js
Normal file
24
packages/backend/src/apps/gitea/auth/generate-auth-url.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
|
export default async function generateAuthUrl($) {
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const state = Math.random().toString();
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
response_type: 'code',
|
||||||
|
state: state,
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = `${
|
||||||
|
$.auth.data.instanceUrl
|
||||||
|
}/login/oauth/authorize?${searchParams.toString()}`;
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
url,
|
||||||
|
originalState: state,
|
||||||
|
});
|
||||||
|
}
|
59
packages/backend/src/apps/gitea/auth/index.js
Normal file
59
packages/backend/src/apps/gitea/auth/index.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import generateAuthUrl from './generate-auth-url.js';
|
||||||
|
import verifyCredentials from './verify-credentials.js';
|
||||||
|
import refreshToken from './refresh-token.js';
|
||||||
|
import isStillVerified from './is-still-verified.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'oAuthRedirectUrl',
|
||||||
|
label: 'OAuth Redirect URL',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: '{WEB_APP_URL}/app/gitea/connections/add',
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'When asked to input a redirect URL in Gitea, enter the URL above.',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'instanceUrl',
|
||||||
|
label: 'Instance URL',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientId',
|
||||||
|
label: 'Client ID',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'clientSecret',
|
||||||
|
label: 'Client Secret',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
generateAuthUrl,
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
refreshToken,
|
||||||
|
};
|
@@ -0,0 +1,8 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
|
||||||
|
const isStillVerified = async ($) => {
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
return !!currentUser.email;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
29
packages/backend/src/apps/gitea/auth/refresh-token.js
Normal file
29
packages/backend/src/apps/gitea/auth/refresh-token.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { URLSearchParams } from 'node:url';
|
||||||
|
|
||||||
|
const refreshToken = async ($) => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
client_secret: $.auth.data.clientSecret,
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: $.auth.data.refreshToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`${$.auth.data.instanceUrl}/login/oauth/access_token`,
|
||||||
|
params.toString(),
|
||||||
|
{
|
||||||
|
additionalProperties: {
|
||||||
|
skipAddingBaseUrl: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
expiresIn: data.expires_in,
|
||||||
|
tokenType: data.token_type,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default refreshToken;
|
50
packages/backend/src/apps/gitea/auth/verify-credentials.js
Normal file
50
packages/backend/src/apps/gitea/auth/verify-credentials.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import getCurrentUser from '../common/get-current-user.js';
|
||||||
|
import { URLSearchParams } from 'url';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($) => {
|
||||||
|
if ($.auth.data.originalState !== $.auth.data.state) {
|
||||||
|
throw new Error(`The 'state' parameter does not match.`);
|
||||||
|
}
|
||||||
|
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||||
|
(field) => field.key == 'oAuthRedirectUrl'
|
||||||
|
);
|
||||||
|
const redirectUri = oauthRedirectUrlField.value;
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
client_id: $.auth.data.clientId,
|
||||||
|
client_secret: $.auth.data.clientSecret,
|
||||||
|
code: $.auth.data.code,
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data } = await $.http.post(
|
||||||
|
`${$.auth.data.instanceUrl}/login/oauth/access_token`,
|
||||||
|
params.toString(),
|
||||||
|
{
|
||||||
|
additionalProperties: {
|
||||||
|
skipAddingBaseUrl: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
accessToken: data.access_token,
|
||||||
|
tokenType: data.token_type,
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
const screenName = [currentUser.username, currentUser.email]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' @ ');
|
||||||
|
|
||||||
|
await $.auth.set({
|
||||||
|
clientId: $.auth.data.clientId,
|
||||||
|
clientSecret: $.auth.data.clientSecret,
|
||||||
|
expiresIn: data.expires_in,
|
||||||
|
refreshToken: data.refresh_token,
|
||||||
|
repoOwner: currentUser.username,
|
||||||
|
screenName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
@@ -0,0 +1,9 @@
|
|||||||
|
const addAuthHeader = ($, requestConfig) => {
|
||||||
|
if ($.auth.data?.accessToken) {
|
||||||
|
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
@@ -0,0 +1,6 @@
|
|||||||
|
const getCurrentUser = async ($) => {
|
||||||
|
const { data: currentUser } = await $.http.get(`/user`);
|
||||||
|
return currentUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentUser;
|
14
packages/backend/src/apps/gitea/common/set-base-url.js
Normal file
14
packages/backend/src/apps/gitea/common/set-base-url.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const setBaseUrl = ($, requestConfig) => {
|
||||||
|
if (requestConfig.additionalProperties?.skipAddingBaseUrl)
|
||||||
|
return requestConfig;
|
||||||
|
|
||||||
|
const instanceUrl = $.auth.data.instanceUrl;
|
||||||
|
|
||||||
|
if (instanceUrl) {
|
||||||
|
requestConfig.baseURL = `${instanceUrl}/api/v1`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default setBaseUrl;
|
17
packages/backend/src/apps/gitea/index.js
Normal file
17
packages/backend/src/apps/gitea/index.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import defineApp from '../../helpers/define-app.js';
|
||||||
|
import addAuthHeader from './common/add-auth-header.js';
|
||||||
|
import auth from './auth/index.js';
|
||||||
|
import setBaseUrl from './common/set-base-url.js';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Gitea',
|
||||||
|
key: 'gitea',
|
||||||
|
iconUrl: '{BASE_URL}/apps/gitea/assets/favicon.svg',
|
||||||
|
authDocUrl: '{DOCS_URL}/apps/gitea/connection',
|
||||||
|
supportsConnections: true,
|
||||||
|
baseUrl: '',
|
||||||
|
apiBaseUrl: '',
|
||||||
|
primaryColor: '609926',
|
||||||
|
beforeRequest: [setBaseUrl, addAuthHeader],
|
||||||
|
auth,
|
||||||
|
});
|
@@ -1,70 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action.js';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create board',
|
|
||||||
key: 'createBoard',
|
|
||||||
description: 'Creates a new board.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Board Name',
|
|
||||||
key: 'boardName',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
description: 'Title for the board.',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Board Kind',
|
|
||||||
key: 'boardKind',
|
|
||||||
type: 'dropdown',
|
|
||||||
required: true,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: 'Main',
|
|
||||||
value: 'public',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Private',
|
|
||||||
value: 'private',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Shareable',
|
|
||||||
value: 'share',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Template ID',
|
|
||||||
key: 'templateId',
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
"When you switch on developer mode, you'll spot the template IDs in your template store. Additionally, you have the option to utilize the Board ID from any board you've saved as a template.",
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const { boardName, boardKind, templateId } = $.step.parameters;
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
query: `mutation {
|
|
||||||
create_board (board_name: "${boardName}", board_kind: ${boardKind}${
|
|
||||||
templateId ? `, template_id: ${templateId}` : ''
|
|
||||||
}) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
board_kind
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/', body);
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: data,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,98 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action.js';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create column',
|
|
||||||
key: 'createColumn',
|
|
||||||
description: 'Creates a new column in a board.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Board',
|
|
||||||
key: 'boardId',
|
|
||||||
type: 'dropdown',
|
|
||||||
required: true,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listBoards',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Column Title',
|
|
||||||
key: 'columnTitle',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Column Type',
|
|
||||||
key: 'columnType',
|
|
||||||
type: 'dropdown',
|
|
||||||
required: true,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{ label: 'Button', value: 'button' },
|
|
||||||
{ label: 'Checkbox', value: 'checkbox' },
|
|
||||||
{ label: 'Color Picker', value: 'color_picker' },
|
|
||||||
{ label: 'Connect Boards', value: 'board_relation' },
|
|
||||||
{ label: 'Country', value: 'country' },
|
|
||||||
{ label: 'Creation Log', value: 'creation_log' },
|
|
||||||
{ label: 'Date', value: 'date' },
|
|
||||||
{ label: 'Dependency', value: 'dependency' },
|
|
||||||
{ label: 'Dropdown', value: 'dropdown' },
|
|
||||||
{ label: 'Email', value: 'email' },
|
|
||||||
{ label: 'Files', value: 'file' },
|
|
||||||
{ label: 'Formula', value: 'formula' },
|
|
||||||
{ label: 'Hour', value: 'hour' },
|
|
||||||
{ label: 'Item ID', value: 'item_id' },
|
|
||||||
{ label: 'Last Updated', value: 'last_updated' },
|
|
||||||
{ label: 'Link', value: 'link' },
|
|
||||||
{ label: 'Location', value: 'location' },
|
|
||||||
{ label: 'Long Text', value: 'long_text' },
|
|
||||||
{ label: 'Mirror', value: 'mirror' },
|
|
||||||
{ label: 'monday Doc', value: 'doc' },
|
|
||||||
{ label: 'Name', value: 'name' },
|
|
||||||
{ label: 'Numbers', value: 'numbers' },
|
|
||||||
{ label: 'People', value: 'people' },
|
|
||||||
{ label: 'Phone', value: 'phone' },
|
|
||||||
{ label: 'Rating', value: 'rating' },
|
|
||||||
{ label: 'Status', value: 'status' },
|
|
||||||
{ label: 'Tags', value: 'tags' },
|
|
||||||
{ label: 'Text', value: 'text' },
|
|
||||||
{ label: 'Timeline', value: 'timeline' },
|
|
||||||
{ label: 'Time Tracking', value: 'time_tracking' },
|
|
||||||
{ label: 'Vote', value: 'vote' },
|
|
||||||
{ label: 'Week', value: 'week' },
|
|
||||||
{ label: 'World Clock', value: 'world_clock' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const { boardId, columnTitle, columnType } = $.step.parameters;
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
query: `
|
|
||||||
mutation{
|
|
||||||
create_column (board_id: ${boardId}, title: "${columnTitle}", column_type: ${columnType}) {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/', body);
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: data,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,112 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action.js';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create item',
|
|
||||||
key: 'createItem',
|
|
||||||
description: 'Creates a new item in a board.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Board',
|
|
||||||
key: 'boardId',
|
|
||||||
type: 'dropdown',
|
|
||||||
required: true,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listBoards',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Group',
|
|
||||||
key: 'groupId',
|
|
||||||
type: 'dropdown',
|
|
||||||
required: false,
|
|
||||||
description: '',
|
|
||||||
dependsOn: ['parameters.boardId'],
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listGroups',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.boardId',
|
|
||||||
value: '{parameters.boardId}',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Item Name',
|
|
||||||
key: 'itemName',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Subitem Names',
|
|
||||||
key: 'subitemNames',
|
|
||||||
type: 'dynamic',
|
|
||||||
required: false,
|
|
||||||
description: '',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: 'Subitem Name',
|
|
||||||
key: 'subitemName',
|
|
||||||
type: 'string',
|
|
||||||
required: false,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const { boardId, groupId, itemName, subitemNames } = $.step.parameters;
|
|
||||||
const allSubitems = subitemNames.map((entry) => entry.subitemName);
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
query: `
|
|
||||||
mutation {
|
|
||||||
create_item (board_id: ${boardId}${
|
|
||||||
groupId ? `, group_id: "${groupId}"` : ''
|
|
||||||
}, item_name: "${itemName}") {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/', body);
|
|
||||||
|
|
||||||
const itemId = data.data.create_item.id;
|
|
||||||
|
|
||||||
for (let subitemName of allSubitems) {
|
|
||||||
let body = {
|
|
||||||
query: `
|
|
||||||
mutation {
|
|
||||||
create_subitem (parent_item_id:${itemId}, item_name:"${subitemName}") {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
await $.http.post('/', body);
|
|
||||||
}
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: data,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,5 +0,0 @@
|
|||||||
import createBoard from './create-board/index.js';
|
|
||||||
import createColumn from './create-column/index.js';
|
|
||||||
import createItem from './create-item/index.js';
|
|
||||||
|
|
||||||
export default [createBoard, createColumn, createItem];
|
|
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="256px" height="156px" viewBox="0 0 256 156" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
|
||||||
<g>
|
|
||||||
<path d="M31.8458633,153.488694 C20.3244423,153.513586 9.68073708,147.337265 3.98575204,137.321731 C-1.62714067,127.367831 -1.29055839,115.129325 4.86093879,105.498969 L62.2342919,15.4033556 C68.2125882,5.54538256 79.032489,-0.333585033 90.5563073,0.0146553508 C102.071737,0.290611552 112.546041,6.74705604 117.96667,16.9106216 C123.315033,27.0238906 122.646488,39.1914174 116.240607,48.6847625 L58.9037201,138.780375 C52.9943022,147.988884 42.7873202,153.537154 31.8458633,153.488694 L31.8458633,153.488694 Z" fill="#F62B54"></path>
|
|
||||||
<path d="M130.25575,153.488484 C118.683837,153.488484 108.035731,147.301291 102.444261,137.358197 C96.8438154,127.431292 97.1804475,115.223704 103.319447,105.620522 L160.583402,15.7315506 C166.47539,5.73210989 177.327374,-0.284878136 188.929728,0.0146553508 C200.598885,0.269918151 211.174058,6.7973526 216.522421,17.0078646 C221.834319,27.2183766 221.056375,39.4588356 214.456008,48.9278699 L157.204209,138.816842 C151.313487,147.985468 141.153618,153.5168 130.25575,153.488484 Z" fill="#FFCC00"></path>
|
|
||||||
<ellipse fill="#00CA72" cx="226.465527" cy="125.324379" rx="29.5375538" ry="28.9176274"></ellipse>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,21 +0,0 @@
|
|||||||
import verifyCredentials from './verify-credentials.js';
|
|
||||||
import isStillVerified from './is-still-verified.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'apiToken',
|
|
||||||
label: 'API Token',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: 'Monday.com API token of your account.',
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
verifyCredentials,
|
|
||||||
isStillVerified,
|
|
||||||
};
|
|
@@ -1,8 +0,0 @@
|
|||||||
import verifyCredentials from './verify-credentials.js';
|
|
||||||
|
|
||||||
const isStillVerified = async ($) => {
|
|
||||||
await verifyCredentials($);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default isStillVerified;
|
|
@@ -1,18 +0,0 @@
|
|||||||
const verifyCredentials = async ($) => {
|
|
||||||
const body = {
|
|
||||||
query: 'query { me { name, email } }',
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/', body);
|
|
||||||
|
|
||||||
const screenName = [data.data.me.name, data.data.me.email]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' @ ');
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
screenName,
|
|
||||||
apiToken: $.auth.data.apiToken,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default verifyCredentials;
|
|
@@ -1,9 +0,0 @@
|
|||||||
const addAuthHeader = ($, requestConfig) => {
|
|
||||||
if ($.auth.data?.apiToken) {
|
|
||||||
requestConfig.headers.Authorization = $.auth.data.apiToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default addAuthHeader;
|
|
@@ -1,4 +0,0 @@
|
|||||||
import listBoards from './list-boards/index.js';
|
|
||||||
import listGroups from './list-groups/index.js';
|
|
||||||
|
|
||||||
export default [listBoards, listGroups];
|
|
@@ -1,34 +0,0 @@
|
|||||||
export default {
|
|
||||||
name: 'List boards',
|
|
||||||
key: 'listBoards',
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const boards = {
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
query: `
|
|
||||||
query {
|
|
||||||
boards {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/', body);
|
|
||||||
|
|
||||||
if (data.data.boards?.length) {
|
|
||||||
for (const board of data.data.boards) {
|
|
||||||
boards.data.push({
|
|
||||||
value: board.id,
|
|
||||||
name: board.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return boards;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,40 +0,0 @@
|
|||||||
export default {
|
|
||||||
name: 'List groups',
|
|
||||||
key: 'listGroups',
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const groups = {
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
const boardId = $.step.parameters.parameters.boardId;
|
|
||||||
|
|
||||||
if (!boardId) {
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
query: `query {
|
|
||||||
boards (ids: ${boardId}) {
|
|
||||||
groups {
|
|
||||||
title
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/', body);
|
|
||||||
|
|
||||||
if (data.data.boards[0].groups.length) {
|
|
||||||
for (const group of data.data.boards[0].groups) {
|
|
||||||
groups.data.push({
|
|
||||||
value: group.id,
|
|
||||||
name: group.title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return groups;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,22 +0,0 @@
|
|||||||
import defineApp from '../../helpers/define-app.js';
|
|
||||||
import addAuthHeader from './common/add-auth-header.js';
|
|
||||||
import auth from './auth/index.js';
|
|
||||||
import triggers from './triggers/index.js';
|
|
||||||
import actions from './actions/index.js';
|
|
||||||
import dynamicData from './dynamic-data/index.js';
|
|
||||||
|
|
||||||
export default defineApp({
|
|
||||||
name: 'Monday',
|
|
||||||
key: 'monday',
|
|
||||||
iconUrl: '{BASE_URL}/apps/monday/assets/favicon.svg',
|
|
||||||
authDocUrl: '{DOCS_URL}/apps/monday/connection',
|
|
||||||
supportsConnections: true,
|
|
||||||
baseUrl: 'https://monday.com',
|
|
||||||
apiBaseUrl: 'https://api.monday.com/v2',
|
|
||||||
primaryColor: 'F62B54',
|
|
||||||
beforeRequest: [addAuthHeader],
|
|
||||||
auth,
|
|
||||||
triggers,
|
|
||||||
actions,
|
|
||||||
dynamicData,
|
|
||||||
});
|
|
@@ -1,4 +0,0 @@
|
|||||||
import newBoards from './new-boards/index.js';
|
|
||||||
import newUsers from './new-users/index.js';
|
|
||||||
|
|
||||||
export default [newBoards, newUsers];
|
|
@@ -1,29 +0,0 @@
|
|||||||
import defineTrigger from '../../../../helpers/define-trigger.js';
|
|
||||||
|
|
||||||
export default defineTrigger({
|
|
||||||
name: 'New board',
|
|
||||||
key: 'newBoard',
|
|
||||||
pollInterval: 15,
|
|
||||||
description: 'Triggers when a new board is created.',
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const body = {
|
|
||||||
query: 'query { boards { id, name } }',
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/', body);
|
|
||||||
|
|
||||||
if (!data?.data?.boards?.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const board of data.data.boards) {
|
|
||||||
$.pushTriggerItem({
|
|
||||||
raw: board,
|
|
||||||
meta: {
|
|
||||||
internalId: board.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,29 +0,0 @@
|
|||||||
import defineTrigger from '../../../../helpers/define-trigger.js';
|
|
||||||
|
|
||||||
export default defineTrigger({
|
|
||||||
name: 'New users',
|
|
||||||
key: 'newUsers',
|
|
||||||
pollInterval: 15,
|
|
||||||
description: 'Triggers when a new user joins your account.',
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const body = {
|
|
||||||
query: 'query { users { id name } }',
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/', body);
|
|
||||||
|
|
||||||
if (!data.data?.users?.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const user of data.data.users.reverse()) {
|
|
||||||
$.pushTriggerItem({
|
|
||||||
raw: user,
|
|
||||||
meta: {
|
|
||||||
internalId: user.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
@@ -122,6 +122,12 @@ export default defineConfig({
|
|||||||
{ text: 'Connection', link: '/apps/ghost/connection' },
|
{ text: 'Connection', link: '/apps/ghost/connection' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Gitea',
|
||||||
|
collapsible: true,
|
||||||
|
collapsed: true,
|
||||||
|
items: [{ text: 'Connection', link: '/apps/gitea/connection' }],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'GitHub',
|
text: 'GitHub',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
@@ -224,16 +230,6 @@ export default defineConfig({
|
|||||||
{ text: 'Connection', link: '/apps/miro/connection' },
|
{ text: 'Connection', link: '/apps/miro/connection' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: 'Monday',
|
|
||||||
collapsible: true,
|
|
||||||
collapsed: true,
|
|
||||||
items: [
|
|
||||||
{ text: 'Actions', link: '/apps/monday/actions' },
|
|
||||||
{ text: 'Triggers', link: '/apps/monday/triggers' },
|
|
||||||
{ text: 'Connection', link: '/apps/monday/connection' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: 'Notion',
|
text: 'Notion',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
|
16
packages/docs/pages/apps/gitea/connection.md
Normal file
16
packages/docs/pages/apps/gitea/connection.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Gitea
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This page explains the steps you need to follow to set up the Gitea
|
||||||
|
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||||
|
:::
|
||||||
|
|
||||||
|
1. Go to your Gitea Settings panel.
|
||||||
|
2. Click on the **Applications** button.
|
||||||
|
3. Create a new OAuth2 application under **Manage OAuth2 Applications**.
|
||||||
|
4. Copy **OAuth Redirect URL** from Automatisch to **Redirect URIs. Please use a new line for every URI.** field.
|
||||||
|
5. Copy **Client ID** to **Client ID** field on Automatisch.
|
||||||
|
6. Copy **Client Secret** to **Client Secret** field on Automatisch.
|
||||||
|
7. Add your instance url in the **Instance URL** field on Automatisch.
|
||||||
|
8. Click **Submit** button on Automatisch.
|
||||||
|
9. Congrats! Start using your new Gitea connection within the flows.
|
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
favicon: /favicons/monday.svg
|
|
||||||
items:
|
|
||||||
- name: Create board
|
|
||||||
desc: Creates a new board.
|
|
||||||
- name: Create column
|
|
||||||
desc: Creates a new column in a board.
|
|
||||||
- name: Create item
|
|
||||||
desc: Creates a new item in a board.
|
|
||||||
---
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import CustomListing from '../../components/CustomListing.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<CustomListing />
|
|
@@ -1,12 +0,0 @@
|
|||||||
# Monday
|
|
||||||
|
|
||||||
:::info
|
|
||||||
This page explains the steps you need to follow to set up the Monday
|
|
||||||
connection in Automatisch. If any of the steps are outdated, please let us know!
|
|
||||||
:::
|
|
||||||
|
|
||||||
1. Login to your Monday account: [https://monday.com](https://monday.com).
|
|
||||||
2. Click on the account logo and go to the **Developers** page.
|
|
||||||
3. Click on the **My access tokens** tab on the left.
|
|
||||||
4. Click on the **Show** button and copy the access token from the page to the `API Token` field on Automatisch.
|
|
||||||
5. Now, you can start using the Monday connection with Automatisch.
|
|
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
favicon: /favicons/monday.svg
|
|
||||||
items:
|
|
||||||
- name: New board
|
|
||||||
desc: Triggers when a new board is created.
|
|
||||||
- name: New users
|
|
||||||
desc: Triggers when a new user joins your account.
|
|
||||||
---
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import CustomListing from '../../components/CustomListing.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<CustomListing />
|
|
@@ -23,7 +23,6 @@ The following integrations are currently supported by Automatisch.
|
|||||||
- [Invoice Ninja](/apps/invoice-ninja/triggers)
|
- [Invoice Ninja](/apps/invoice-ninja/triggers)
|
||||||
- [Mattermost](/apps/mattermost/actions)
|
- [Mattermost](/apps/mattermost/actions)
|
||||||
- [Miro](/apps/miro/actions)
|
- [Miro](/apps/miro/actions)
|
||||||
- [Monday](/apps/monday/triggers)
|
|
||||||
- [Notion](/apps/notion/triggers)
|
- [Notion](/apps/notion/triggers)
|
||||||
- [Ntfy](/apps/ntfy/actions)
|
- [Ntfy](/apps/ntfy/actions)
|
||||||
- [Odoo](/apps/odoo/actions)
|
- [Odoo](/apps/odoo/actions)
|
||||||
|
1
packages/docs/pages/public/favicons/gitea.svg
Normal file
1
packages/docs/pages/public/favicons/gitea.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640" xml:space="preserve" width="32" height="32"><path style="fill:#fff" d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z"/><path style="fill:#609926" d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z"/><path style="fill:#609926" d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8-1.9 8 2 16.3 9.1 20 7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3 7.8 4 17.4 1.7 22.5-5.3 5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8l-24.6 50.4z"/></svg>
|
After Width: | Height: | Size: 2.2 KiB |
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="256px" height="156px" viewBox="0 0 256 156" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
|
||||||
<g>
|
|
||||||
<path d="M31.8458633,153.488694 C20.3244423,153.513586 9.68073708,147.337265 3.98575204,137.321731 C-1.62714067,127.367831 -1.29055839,115.129325 4.86093879,105.498969 L62.2342919,15.4033556 C68.2125882,5.54538256 79.032489,-0.333585033 90.5563073,0.0146553508 C102.071737,0.290611552 112.546041,6.74705604 117.96667,16.9106216 C123.315033,27.0238906 122.646488,39.1914174 116.240607,48.6847625 L58.9037201,138.780375 C52.9943022,147.988884 42.7873202,153.537154 31.8458633,153.488694 L31.8458633,153.488694 Z" fill="#F62B54"></path>
|
|
||||||
<path d="M130.25575,153.488484 C118.683837,153.488484 108.035731,147.301291 102.444261,137.358197 C96.8438154,127.431292 97.1804475,115.223704 103.319447,105.620522 L160.583402,15.7315506 C166.47539,5.73210989 177.327374,-0.284878136 188.929728,0.0146553508 C200.598885,0.269918151 211.174058,6.7973526 216.522421,17.0078646 C221.834319,27.2183766 221.056375,39.4588356 214.456008,48.9278699 L157.204209,138.816842 C151.313487,147.985468 141.153618,153.5168 130.25575,153.488484 Z" fill="#FFCC00"></path>
|
|
||||||
<ellipse fill="#00CA72" cx="226.465527" cy="125.324379" rx="29.5375538" ry="28.9176274"></ellipse>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 1.4 KiB |
Reference in New Issue
Block a user