diff --git a/.github/workflows/pull-request-from-branch-check.yaml b/.github/workflows/pull-request-from-branch-check.yaml
index 77d1970f..401de613 100644
--- a/.github/workflows/pull-request-from-branch-check.yaml
+++ b/.github/workflows/pull-request-from-branch-check.yaml
@@ -1,18 +1,18 @@
name: Main Branch Protection
-on:
- pull_request:
- branches:
- - main
+#on:
+# pull_request:
+# branches:
+# - main
-jobs:
- check-branch:
- runs-on: ubuntu-latest
- steps:
- - name: Check branch
- run: |
- if [[ ${GITHUB_HEAD_REF} != development ]] && [[ ${GITHUB_HEAD_REF} != documentation ]] && ! [[ ${GITHUB_HEAD_REF} =~ ^hotfix/ ]];
- then
- echo "Error: Pull request must come from 'development', 'documentation' or 'hotfix/' branch"
- exit 1
- fi
+#jobs:
+# check-branch:
+# runs-on: ubuntu-latest
+# steps:
+# - name: Check branch
+# run: |
+# if [[ ${GITHUB_HEAD_REF} != development ]] && [[ ${GITHUB_HEAD_REF} != documentation ]] && ! [[ ${GITHUB_HEAD_REF} =~ ^hotfix/ ]];
+# then
+# echo "Error: Pull request must come from 'development', 'documentation' or 'hotfix/' branch"
+# exit 1
+# fi
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 260f2fe6..f04f76a4 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -8,12 +8,12 @@
The OpenConnector Nextcloud app provides a ESB-framework to work together in an (open) data ecosystem
-- 📲 Synchronize your data sources
+- 📲 Synchronize your data sources
- 📰 Send cloud events
- 🆓 Map and translate API calls
]]>
- 0.1.16
+ 0.1.26
agpl
integration
Conduction
@@ -27,12 +27,16 @@ The OpenConnector Nextcloud app provides a ESB-framework to work together in an
pgsql
sqlite
mysql
-
+
curl
+
+ OCA\OpenConnector\Cron\LogCleanUpTask
+
+
openconnector
diff --git a/composer.json b/composer.json
index 612df13f..b20d0b5a 100644
--- a/composer.json
+++ b/composer.json
@@ -34,6 +34,8 @@
"bamarni/composer-bin-plugin": "^1.8",
"elasticsearch/elasticsearch": "^v8.14.0",
"guzzlehttp/guzzle": "^7.0",
+ "jwadhams/json-logic-php": "^1.5",
+ "symfony/console": "^5.4",
"symfony/uid": "^6.4",
"symfony/yaml": "^6.4",
"twig/twig": "^3.14",
diff --git a/composer.lock b/composer.lock
index 68c791f4..94d4d6dd 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "804adee469d5f3c9b6bd2879a48fe872",
+ "content-hash": "675849f1df3dec3e4818f1d9daa65c84",
"packages": [
{
"name": "adbario/php-dot-notation",
@@ -237,16 +237,16 @@
},
{
"name": "elasticsearch/elasticsearch",
- "version": "v8.15.0",
+ "version": "v8.16.0",
"source": {
"type": "git",
"url": "https://github.com/elastic/elasticsearch-php.git",
- "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b"
+ "reference": "ab0fdb43f9e69f0d0539028d8b0b56cdf3328d85"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/34c2444fa8d4c3e6c8b009bd8dea90bca007203b",
- "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b",
+ "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/ab0fdb43f9e69f0d0539028d8b0b56cdf3328d85",
+ "reference": "ab0fdb43f9e69f0d0539028d8b0b56cdf3328d85",
"shasum": ""
},
"require": {
@@ -289,9 +289,9 @@
],
"support": {
"issues": "https://github.com/elastic/elasticsearch-php/issues",
- "source": "https://github.com/elastic/elasticsearch-php/tree/v8.15.0"
+ "source": "https://github.com/elastic/elasticsearch-php/tree/v8.16.0"
},
- "time": "2024-08-14T14:32:50+00:00"
+ "time": "2024-11-14T22:23:33+00:00"
},
{
"name": "guzzlehttp/guzzle",
@@ -618,6 +618,49 @@
],
"time": "2024-07-18T11:15:46+00:00"
},
+ {
+ "name": "jwadhams/json-logic-php",
+ "version": "1.5.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jwadhams/json-logic-php.git",
+ "reference": "060aab5ad36ae1fdd74d3006131b197ca777fa48"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jwadhams/json-logic-php/zipball/060aab5ad36ae1fdd74d3006131b197ca777fa48",
+ "reference": "060aab5ad36ae1fdd74d3006131b197ca777fa48",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "JWadhams": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jeremy Wadhams",
+ "email": "jwadhams@dealerinspire.com"
+ }
+ ],
+ "description": "Build rules with complex comparisons and boolean operators, serialized as JSON, and execute them in PHP",
+ "support": {
+ "issues": "https://github.com/jwadhams/json-logic-php/issues",
+ "source": "https://github.com/jwadhams/json-logic-php/tree/1.5.1"
+ },
+ "time": "2024-07-09T15:20:54+00:00"
+ },
{
"name": "open-telemetry/api",
"version": "1.1.1",
@@ -1736,16 +1779,16 @@
},
{
"name": "symfony/config",
- "version": "v6.4.8",
+ "version": "v6.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "12e7e52515ce37191b193cf3365903c4f3951e35"
+ "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/12e7e52515ce37191b193cf3365903c4f3951e35",
- "reference": "12e7e52515ce37191b193cf3365903c4f3951e35",
+ "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef",
+ "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef",
"shasum": ""
},
"require": {
@@ -1791,7 +1834,7 @@
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/config/tree/v6.4.8"
+ "source": "https://github.com/symfony/config/tree/v6.4.14"
},
"funding": [
{
@@ -1807,51 +1850,56 @@
"type": "tidelift"
}
],
- "time": "2024-05-31T14:49:08+00:00"
+ "time": "2024-11-04T11:33:53+00:00"
},
{
"name": "symfony/console",
- "version": "v6.4.12",
+ "version": "v5.4.47",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765"
+ "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765",
- "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765",
+ "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed",
+ "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed",
"shasum": ""
},
"require": {
- "php": ">=8.1",
- "symfony/deprecation-contracts": "^2.5|^3",
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-mbstring": "~1.0",
- "symfony/service-contracts": "^2.5|^3",
- "symfony/string": "^5.4|^6.0|^7.0"
+ "symfony/polyfill-php73": "^1.9",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/service-contracts": "^1.1|^2|^3",
+ "symfony/string": "^5.1|^6.0"
},
"conflict": {
- "symfony/dependency-injection": "<5.4",
- "symfony/dotenv": "<5.4",
- "symfony/event-dispatcher": "<5.4",
- "symfony/lock": "<5.4",
- "symfony/process": "<5.4"
+ "psr/log": ">=3",
+ "symfony/dependency-injection": "<4.4",
+ "symfony/dotenv": "<5.1",
+ "symfony/event-dispatcher": "<4.4",
+ "symfony/lock": "<4.4",
+ "symfony/process": "<4.4"
},
"provide": {
- "psr/log-implementation": "1.0|2.0|3.0"
+ "psr/log-implementation": "1.0|2.0"
},
"require-dev": {
- "psr/log": "^1|^2|^3",
- "symfony/config": "^5.4|^6.0|^7.0",
- "symfony/dependency-injection": "^5.4|^6.0|^7.0",
- "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
- "symfony/http-foundation": "^6.4|^7.0",
- "symfony/http-kernel": "^6.4|^7.0",
- "symfony/lock": "^5.4|^6.0|^7.0",
- "symfony/messenger": "^5.4|^6.0|^7.0",
- "symfony/process": "^5.4|^6.0|^7.0",
- "symfony/stopwatch": "^5.4|^6.0|^7.0",
- "symfony/var-dumper": "^5.4|^6.0|^7.0"
+ "psr/log": "^1|^2",
+ "symfony/config": "^4.4|^5.0|^6.0",
+ "symfony/dependency-injection": "^4.4|^5.0|^6.0",
+ "symfony/event-dispatcher": "^4.4|^5.0|^6.0",
+ "symfony/lock": "^4.4|^5.0|^6.0",
+ "symfony/process": "^4.4|^5.0|^6.0",
+ "symfony/var-dumper": "^4.4|^5.0|^6.0"
+ },
+ "suggest": {
+ "psr/log": "For using the console logger",
+ "symfony/event-dispatcher": "",
+ "symfony/lock": "",
+ "symfony/process": ""
},
"type": "library",
"autoload": {
@@ -1885,7 +1933,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v6.4.12"
+ "source": "https://github.com/symfony/console/tree/v5.4.47"
},
"funding": [
{
@@ -1901,20 +1949,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-20T08:15:52+00:00"
+ "time": "2024-11-06T11:30:55+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v6.4.12",
+ "version": "v6.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "cfb9d34a1cdd4911bc737a5358fd1cf8ebfb536e"
+ "reference": "70ab1f65a4516ef741e519ea938e6aa465e6aa36"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/cfb9d34a1cdd4911bc737a5358fd1cf8ebfb536e",
- "reference": "cfb9d34a1cdd4911bc737a5358fd1cf8ebfb536e",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/70ab1f65a4516ef741e519ea938e6aa465e6aa36",
+ "reference": "70ab1f65a4516ef741e519ea938e6aa465e6aa36",
"shasum": ""
},
"require": {
@@ -1966,7 +2014,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/dependency-injection/tree/v6.4.12"
+ "source": "https://github.com/symfony/dependency-injection/tree/v6.4.15"
},
"funding": [
{
@@ -1982,7 +2030,7 @@
"type": "tidelift"
}
],
- "time": "2024-09-20T08:18:25+00:00"
+ "time": "2024-11-09T06:56:25+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -2053,16 +2101,16 @@
},
{
"name": "symfony/error-handler",
- "version": "v6.4.10",
+ "version": "v6.4.14",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
- "reference": "231f1b2ee80f72daa1972f7340297d67439224f0"
+ "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/error-handler/zipball/231f1b2ee80f72daa1972f7340297d67439224f0",
- "reference": "231f1b2ee80f72daa1972f7340297d67439224f0",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/9e024324511eeb00983ee76b9aedc3e6ecd993d9",
+ "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9",
"shasum": ""
},
"require": {
@@ -2108,7 +2156,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/error-handler/tree/v6.4.10"
+ "source": "https://github.com/symfony/error-handler/tree/v6.4.14"
},
"funding": [
{
@@ -2124,20 +2172,20 @@
"type": "tidelift"
}
],
- "time": "2024-07-26T12:30:32+00:00"
+ "time": "2024-11-05T15:34:40+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v6.4.8",
+ "version": "v6.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b"
+ "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b",
- "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e",
+ "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e",
"shasum": ""
},
"require": {
@@ -2188,7 +2236,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8"
+ "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13"
},
"funding": [
{
@@ -2204,7 +2252,7 @@
"type": "tidelift"
}
],
- "time": "2024-05-31T14:49:08+00:00"
+ "time": "2024-09-25T14:18:03+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -2284,16 +2332,16 @@
},
{
"name": "symfony/filesystem",
- "version": "v6.4.12",
+ "version": "v6.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12"
+ "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/f810e3cbdf7fdc35983968523d09f349fa9ada12",
- "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3",
+ "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3",
"shasum": ""
},
"require": {
@@ -2330,7 +2378,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/filesystem/tree/v6.4.12"
+ "source": "https://github.com/symfony/filesystem/tree/v6.4.13"
},
"funding": [
{
@@ -2346,20 +2394,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-16T16:01:33+00:00"
+ "time": "2024-10-25T15:07:50+00:00"
},
{
"name": "symfony/http-client",
- "version": "v6.4.12",
+ "version": "v6.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56"
+ "reference": "cb4073c905cd12b8496d24ac428a9228c1750670"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56",
- "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/cb4073c905cd12b8496d24ac428a9228c1750670",
+ "reference": "cb4073c905cd12b8496d24ac428a9228c1750670",
"shasum": ""
},
"require": {
@@ -2423,7 +2471,7 @@
"http"
],
"support": {
- "source": "https://github.com/symfony/http-client/tree/v6.4.12"
+ "source": "https://github.com/symfony/http-client/tree/v6.4.15"
},
"funding": [
{
@@ -2439,7 +2487,7 @@
"type": "tidelift"
}
],
- "time": "2024-09-20T08:21:33+00:00"
+ "time": "2024-11-13T13:40:18+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -2521,16 +2569,16 @@
},
{
"name": "symfony/http-foundation",
- "version": "v6.4.12",
+ "version": "v6.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "133ac043875f59c26c55e79cf074562127cce4d2"
+ "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/133ac043875f59c26c55e79cf074562127cce4d2",
- "reference": "133ac043875f59c26c55e79cf074562127cce4d2",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6",
+ "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6",
"shasum": ""
},
"require": {
@@ -2540,12 +2588,12 @@
"symfony/polyfill-php83": "^1.27"
},
"conflict": {
- "symfony/cache": "<6.3"
+ "symfony/cache": "<6.4.12|>=7.0,<7.1.5"
},
"require-dev": {
"doctrine/dbal": "^2.13.1|^3|^4",
"predis/predis": "^1.1|^2.0",
- "symfony/cache": "^6.3|^7.0",
+ "symfony/cache": "^6.4.12|^7.1.5",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/expression-language": "^5.4|^6.0|^7.0",
"symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0",
@@ -2578,7 +2626,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v6.4.12"
+ "source": "https://github.com/symfony/http-foundation/tree/v6.4.15"
},
"funding": [
{
@@ -2594,20 +2642,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-20T08:18:25+00:00"
+ "time": "2024-11-08T16:09:24+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v6.4.12",
+ "version": "v6.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "96df83d51b5f78804f70c093b97310794fd6257b"
+ "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/96df83d51b5f78804f70c093b97310794fd6257b",
- "reference": "96df83d51b5f78804f70c093b97310794fd6257b",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b002a5b3947653c5aee3adac2a024ea615fd3ff5",
+ "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5",
"shasum": ""
},
"require": {
@@ -2692,7 +2740,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v6.4.12"
+ "source": "https://github.com/symfony/http-kernel/tree/v6.4.15"
},
"funding": [
{
@@ -2708,7 +2756,7 @@
"type": "tidelift"
}
],
- "time": "2024-09-21T06:02:57+00:00"
+ "time": "2024-11-13T13:57:37+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -3028,6 +3076,162 @@
],
"time": "2024-09-09T11:45:10+00:00"
},
+ {
+ "name": "symfony/polyfill-php73",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php73.git",
+ "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb",
+ "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php73\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+ "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
{
"name": "symfony/polyfill-php81",
"version": "v1.31.0",
@@ -3420,16 +3624,16 @@
},
{
"name": "symfony/string",
- "version": "v6.4.12",
+ "version": "v6.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b"
+ "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/f8a1ccebd0997e16112dfecfd74220b78e5b284b",
- "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b",
+ "url": "https://api.github.com/repos/symfony/string/zipball/73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f",
+ "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f",
"shasum": ""
},
"require": {
@@ -3486,7 +3690,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v6.4.12"
+ "source": "https://github.com/symfony/string/tree/v6.4.15"
},
"funding": [
{
@@ -3502,20 +3706,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-20T08:15:52+00:00"
+ "time": "2024-11-13T13:31:12+00:00"
},
{
"name": "symfony/uid",
- "version": "v6.4.12",
+ "version": "v6.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
- "reference": "2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d"
+ "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/uid/zipball/2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d",
- "reference": "2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d",
+ "url": "https://api.github.com/repos/symfony/uid/zipball/18eb207f0436a993fffbdd811b5b8fa35fa5e007",
+ "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007",
"shasum": ""
},
"require": {
@@ -3560,7 +3764,7 @@
"uuid"
],
"support": {
- "source": "https://github.com/symfony/uid/tree/v6.4.12"
+ "source": "https://github.com/symfony/uid/tree/v6.4.13"
},
"funding": [
{
@@ -3576,20 +3780,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-20T08:32:26+00:00"
+ "time": "2024-09-25T14:18:03+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v6.4.11",
+ "version": "v6.4.15",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "ee14c8254a480913268b1e3b1cba8045ed122694"
+ "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ee14c8254a480913268b1e3b1cba8045ed122694",
- "reference": "ee14c8254a480913268b1e3b1cba8045ed122694",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80",
+ "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80",
"shasum": ""
},
"require": {
@@ -3645,7 +3849,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v6.4.11"
+ "source": "https://github.com/symfony/var-dumper/tree/v6.4.15"
},
"funding": [
{
@@ -3661,20 +3865,20 @@
"type": "tidelift"
}
],
- "time": "2024-08-30T16:03:21+00:00"
+ "time": "2024-11-08T15:28:48+00:00"
},
{
"name": "symfony/var-exporter",
- "version": "v6.4.9",
+ "version": "v6.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-exporter.git",
- "reference": "f9a060622e0d93777b7f8687ec4860191e16802e"
+ "reference": "0f605f72a363f8743001038a176eeb2a11223b51"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-exporter/zipball/f9a060622e0d93777b7f8687ec4860191e16802e",
- "reference": "f9a060622e0d93777b7f8687ec4860191e16802e",
+ "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51",
+ "reference": "0f605f72a363f8743001038a176eeb2a11223b51",
"shasum": ""
},
"require": {
@@ -3722,7 +3926,7 @@
"serialize"
],
"support": {
- "source": "https://github.com/symfony/var-exporter/tree/v6.4.9"
+ "source": "https://github.com/symfony/var-exporter/tree/v6.4.13"
},
"funding": [
{
@@ -3738,20 +3942,20 @@
"type": "tidelift"
}
],
- "time": "2024-06-24T15:53:56+00:00"
+ "time": "2024-09-25T14:18:03+00:00"
},
{
"name": "symfony/yaml",
- "version": "v6.4.12",
+ "version": "v6.4.13",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "762ee56b2649659380e0ef4d592d807bc17b7971"
+ "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971",
- "reference": "762ee56b2649659380e0ef4d592d807bc17b7971",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9",
+ "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9",
"shasum": ""
},
"require": {
@@ -3794,7 +3998,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.4.12"
+ "source": "https://github.com/symfony/yaml/tree/v6.4.13"
},
"funding": [
{
@@ -3810,20 +4014,20 @@
"type": "tidelift"
}
],
- "time": "2024-09-17T12:47:12+00:00"
+ "time": "2024-09-25T14:18:03+00:00"
},
{
"name": "twig/twig",
- "version": "v3.14.0",
+ "version": "v3.15.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
- "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72"
+ "reference": "2d5b3964cc21d0188633d7ddce732dc8e874db02"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72",
- "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72",
+ "url": "https://api.github.com/repos/twigphp/Twig/zipball/2d5b3964cc21d0188633d7ddce732dc8e874db02",
+ "reference": "2d5b3964cc21d0188633d7ddce732dc8e874db02",
"shasum": ""
},
"require": {
@@ -3877,7 +4081,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
- "source": "https://github.com/twigphp/Twig/tree/v3.14.0"
+ "source": "https://github.com/twigphp/Twig/tree/v3.15.0"
},
"funding": [
{
@@ -3889,7 +4093,7 @@
"type": "tidelift"
}
],
- "time": "2024-09-09T17:55:12+00:00"
+ "time": "2024-11-17T15:59:19+00:00"
},
{
"name": "web-token/jwt-framework",
@@ -4112,12 +4316,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
- "reference": "51e3fa290bca57eca7ba6c261b8a1278eb13a98a"
+ "reference": "b33a18b5d222c63472a4b41f6fa3e15e591c9595"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/51e3fa290bca57eca7ba6c261b8a1278eb13a98a",
- "reference": "51e3fa290bca57eca7ba6c261b8a1278eb13a98a",
+ "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/b33a18b5d222c63472a4b41f6fa3e15e591c9595",
+ "reference": "b33a18b5d222c63472a4b41f6fa3e15e591c9595",
"shasum": ""
},
"conflict": {
@@ -4125,7 +4329,7 @@
"admidio/admidio": "<4.3.12",
"adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3",
"aheinze/cockpit": "<2.2",
- "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.04.6",
+ "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2",
"aimeos/ai-admin-jsonadm": "<2020.10.13|>=2021.04.1,<2021.10.6|>=2022.04.1,<2022.10.3|>=2023.04.1,<2023.10.4|==2024.04.1",
"aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7",
"aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9|==2024.04.1",
@@ -4137,6 +4341,7 @@
"alextselegidis/easyappointments": "<1.5",
"alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1",
"amazing/media2click": ">=1,<1.3.3",
+ "ameos/ameos_tarteaucitron": "<1.2.23",
"amphp/artax": "<1.0.6|>=2,<2.0.6",
"amphp/http": "<=1.7.2|>=2,<=2.1",
"amphp/http-client": ">=4,<4.4",
@@ -4162,6 +4367,7 @@
"azuracast/azuracast": "<0.18.3",
"backdrop/backdrop": "<1.27.3|>=1.28,<1.28.2",
"backpack/crud": "<3.4.9",
+ "backpack/filemanager": "<2.0.2|>=3,<3.0.9",
"bacula-web/bacula-web": "<8.0.0.0-RC2-dev",
"badaso/core": "<2.7",
"bagisto/bagisto": "<2.1",
@@ -4169,7 +4375,7 @@
"barrelstrength/sprout-forms": "<3.9",
"barryvdh/laravel-translation-manager": "<0.6.2",
"barzahlen/barzahlen-php": "<2.0.1",
- "baserproject/basercms": "<5.0.9",
+ "baserproject/basercms": "<=5.1.1",
"bassjobsen/bootstrap-3-typeahead": ">4.0.2",
"bbpress/bbpress": "<2.6.5",
"bcosca/fatfree": "<3.7.2",
@@ -4225,7 +4431,7 @@
"contao/managed-edition": "<=1.5",
"corveda/phpsandbox": "<1.3.5",
"cosenary/instagram": "<=2.3",
- "craftcms/cms": "<4.6.2|>=5,<=5.2.2",
+ "craftcms/cms": "<=4.12.6.1|>=5,<=5.4.7.1",
"croogo/croogo": "<4",
"cuyz/valinor": "<0.12",
"czim/file-handling": "<1.5|>=2,<2.3",
@@ -4294,13 +4500,14 @@
"ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1",
"ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3",
"ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15",
- "ezyang/htmlpurifier": "<4.1.1",
+ "ezyang/htmlpurifier": "<=4.2",
"facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2",
"facturascripts/facturascripts": "<=2022.08",
"fastly/magento2": "<1.2.26",
"feehi/cms": "<=2.1.1",
"feehi/feehicms": "<=2.1.1",
"fenom/fenom": "<=2.12.1",
+ "filament/actions": ">=3.2,<3.2.123",
"filament/infolists": ">=3,<3.2.115",
"filament/tables": ">=3,<3.2.115",
"filegator/filegator": "<7.8",
@@ -4434,8 +4641,9 @@
"lara-zeus/artemis": ">=1,<=1.0.6",
"lara-zeus/dynamic-dashboard": ">=3,<=3.0.1",
"laravel/fortify": "<1.11.1",
- "laravel/framework": "<6.20.44|>=7,<7.30.6|>=8,<8.75",
+ "laravel/framework": "<6.20.45|>=7,<7.30.7|>=8,<8.83.28|>=9,<9.52.17|>=10,<10.48.23|>=11,<11.31",
"laravel/laravel": ">=5.4,<5.4.22",
+ "laravel/reverb": "<1.4",
"laravel/socialite": ">=1,<2.0.10",
"latte/latte": "<2.10.8",
"lavalite/cms": "<=9|==10.1",
@@ -4454,6 +4662,7 @@
"lms/routes": "<2.1.1",
"localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2",
"luyadev/yii-helpers": "<1.2.1",
+ "maestroerror/php-heic-to-jpg": "<1.0.5",
"magento/community-edition": "<2.4.5|==2.4.5|>=2.4.5.0-patch1,<2.4.5.0-patch10|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch8|>=2.4.7.0-beta1,<2.4.7.0-patch3",
"magento/core": "<=1.9.4.5",
"magento/magento1ce": "<1.9.4.3-dev",
@@ -4467,9 +4676,10 @@
"matyhtf/framework": "<3.0.6",
"mautic/core": "<4.4.13|>=5,<5.1.1",
"mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1",
+ "maximebf/debugbar": "<1.19",
"mdanter/ecc": "<2",
"mediawiki/cargo": "<3.6.1",
- "mediawiki/core": "<1.36.2",
+ "mediawiki/core": "<1.39.5|==1.40",
"mediawiki/matomo": "<2.4.3",
"mediawiki/semantic-media-wiki": "<4.0.2",
"melisplatform/melis-asset-manager": "<5.0.1",
@@ -4489,7 +4699,7 @@
"mojo42/jirafeau": "<4.4",
"mongodb/mongodb": ">=1,<1.9.2",
"monolog/monolog": ">=1.8,<1.12",
- "moodle/moodle": "<4.3.5|>=4.4.0.0-beta,<4.4.1",
+ "moodle/moodle": "<4.3.8|>=4.4,<4.4.4",
"mos/cimage": "<0.7.19",
"movim/moxl": ">=0.8,<=0.10",
"movingbytes/social-network": "<=1.2.1",
@@ -4537,7 +4747,7 @@
"openmage/magento-lts": "<20.10.1",
"opensolutions/vimbadmin": "<=3.0.15",
"opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2",
- "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5",
+ "orchid/platform": ">=8,<14.43",
"oro/calendar-bundle": ">=4.2,<=4.2.6|>=5,<=5.0.6|>=5.1,<5.1.1",
"oro/commerce": ">=4.1,<5.0.11|>=5.1,<5.1.1",
"oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7",
@@ -4568,7 +4778,7 @@
"phenx/php-svg-lib": "<0.5.2",
"php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5",
"php-mod/curl": "<2.3.2",
- "phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1",
+ "phpbb/phpbb": "<3.3.11",
"phpems/phpems": ">=6,<=6.1.3",
"phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7",
"phpmailer/phpmailer": "<6.5",
@@ -4576,8 +4786,8 @@
"phpmyadmin/phpmyadmin": "<5.2.1",
"phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5",
"phpoffice/common": "<0.2.9",
- "phpoffice/phpexcel": "<1.8",
- "phpoffice/phpspreadsheet": "<1.29.2|>=2,<2.1.1|>=2.2,<2.3",
+ "phpoffice/phpexcel": "<1.8.1",
+ "phpoffice/phpspreadsheet": "<1.29.4|>=2,<2.1.3|>=2.2,<2.3.2|>=3.3,<3.4",
"phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36",
"phpservermon/phpservermon": "<3.6",
"phpsysinfo/phpsysinfo": "<3.4.3",
@@ -4614,7 +4824,7 @@
"processwire/processwire": "<=3.0.229",
"propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7",
"propel/propel1": ">=1,<=1.7.1",
- "pterodactyl/panel": "<1.11.6",
+ "pterodactyl/panel": "<1.11.8",
"ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2",
"ptrofimov/beanstalk_console": "<1.7.14",
"pubnub/pubnub": "<6.1",
@@ -4631,7 +4841,7 @@
"rap2hpoutre/laravel-log-viewer": "<0.13",
"react/http": ">=0.7,<1.9",
"really-simple-plugins/complianz-gdpr": "<6.4.2",
- "redaxo/source": "<=5.17.1",
+ "redaxo/source": "<5.18",
"remdex/livehelperchat": "<4.29",
"reportico-web/reportico": "<=8.1",
"rhukster/dom-sanitizer": "<1.0.7",
@@ -4687,7 +4897,7 @@
"slim/slim": "<2.6",
"slub/slub-events": "<3.0.3",
"smarty/smarty": "<4.5.3|>=5,<5.1.1",
- "snipe/snipe-it": "<7.0.10",
+ "snipe/snipe-it": "<=7.0.13",
"socalnick/scn-social-auth": "<1.15.2",
"socialiteproviders/steam": "<1.1",
"spatie/browsershot": "<3.57.4",
@@ -4698,7 +4908,7 @@
"squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1",
"ssddanbrown/bookstack": "<24.05.1",
"starcitizentools/citizen-skin": ">=2.6.3,<2.31",
- "statamic/cms": "<4.46|>=5.3,<5.6.2",
+ "statamic/cms": "<=5.16",
"stormpath/sdk": "<9.9.99",
"studio-42/elfinder": "<=2.1.64",
"studiomitte/friendlycaptcha": "<0.1.4",
@@ -4727,7 +4937,8 @@
"symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4",
"symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1",
"symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4",
- "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7",
+ "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8",
+ "symfony/http-foundation": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7",
"symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6",
"symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13",
"symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1",
@@ -4735,20 +4946,22 @@
"symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
"symfony/polyfill": ">=1,<1.10",
"symfony/polyfill-php55": ">=1,<1.10",
+ "symfony/process": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7",
"symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7",
"symfony/routing": ">=2,<2.0.19",
+ "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7",
"symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8",
- "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6",
+ "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3",
"symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9",
"symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11",
"symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8",
- "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2|>=5.4,<5.4.31|>=6,<6.3.8",
+ "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8",
"symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12",
- "symfony/symfony": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8",
+ "symfony/symfony": "<5.4.47|>=6,<6.4.15|>=7,<7.1.8",
"symfony/translation": ">=2,<2.0.17",
"symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8",
"symfony/ux-autocomplete": "<2.11.2",
- "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3",
+ "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4",
"symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8",
"symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4",
"symfony/webhook": ">=6.3,<6.3.8",
@@ -4774,14 +4987,14 @@
"tobiasbg/tablepress": "<=2.0.0.0-RC1",
"topthink/framework": "<6.0.17|>=6.1,<=8.0.4",
"topthink/think": "<=6.1.1",
- "topthink/thinkphp": "<=3.2.3",
+ "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4",
"torrentpier/torrentpier": "<=2.4.3",
"tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2",
"tribalsystems/zenario": "<=9.7.61188",
"truckersmp/phpwhois": "<=4.3.1",
"ttskch/pagination-service-provider": "<1",
"twbs/bootstrap": "<=3.4.1|>=4,<=4.6.2",
- "twig/twig": "<1.44.8|>=2,<2.16.1|>=3,<3.11.1|>=3.12,<3.14",
+ "twig/twig": "<3.11.2|>=3.12,<3.14.1",
"typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2",
"typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<10.4.46|>=11,<11.5.40|>=12,<12.4.21|>=13,<13.3.1",
"typo3/cms-core": "<=8.7.56|>=9,<=9.5.47|>=10,<=10.4.44|>=11,<=11.5.36|>=12,<=12.4.14|>=13,<=13.1",
@@ -4800,6 +5013,7 @@
"ua-parser/uap-php": "<3.8",
"uasoft-indonesia/badaso": "<=2.9.7",
"unisharp/laravel-filemanager": "<2.6.4",
+ "unopim/unopim": "<0.1.5",
"userfrosting/userfrosting": ">=0.3.1,<4.6.3",
"usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2",
"uvdesk/community-skeleton": "<=1.1.1",
@@ -4845,7 +5059,7 @@
"xataface/xataface": "<3",
"xpressengine/xpressengine": "<3.0.15",
"yab/quarx": "<2.4.5",
- "yeswiki/yeswiki": "<4.1",
+ "yeswiki/yeswiki": "<=4.4.4",
"yetiforce/yetiforce-crm": "<=6.4",
"yidashi/yii2cmf": "<=2",
"yii2mod/yii2-cms": "<1.9.2",
@@ -4936,7 +5150,7 @@
"type": "tidelift"
}
],
- "time": "2024-10-21T20:05:20+00:00"
+ "time": "2024-11-19T21:04:39+00:00"
}
],
"aliases": [],
@@ -4950,7 +5164,7 @@
"platform": {
"php": "^8.1"
},
- "platform-dev": [],
+ "platform-dev": {},
"platform-overrides": {
"php": "8.1"
},
diff --git a/configurations/README.md b/configurations/README.md
new file mode 100644
index 00000000..ac30f4ff
--- /dev/null
+++ b/configurations/README.md
@@ -0,0 +1,5 @@
+You can add folders here for certain synchronizations flows.
+
+These json files are meant to be copy/pasted into a Postman request that creates that specific type of object.
+
+Reminder to always leave out sensitive information like authorization keys et cetera.
\ No newline at end of file
diff --git a/configurations/sharepoint-woo/mappings/sharepoint-woo-verzoek-to-publications.json b/configurations/sharepoint-woo/mappings/sharepoint-woo-verzoek-to-publications.json
new file mode 100644
index 00000000..a6cdbade
--- /dev/null
+++ b/configurations/sharepoint-woo/mappings/sharepoint-woo-verzoek-to-publications.json
@@ -0,0 +1,30 @@
+{
+ "name": "Sharepoint Woo verzoek to Publication",
+ "description": "",
+ "version": "0.0.1",
+ "mapping": {
+ "title": "d.woo_x005f_titel",
+ "description": "d.woo_x005f_beschrijving",
+ "summary": "d.woo_x005f_samenvatting",
+ "category": "d.woo_x005f_categorie",
+ "published": "{% if d['woo_x005f_publicatiedatum']|default %}{{ d['woo_x005f_publicatiedatum']|date('Y-m-d') }}{% endif %}",
+ "modified": "d.vti_x005f_nexttolasttimemodified",
+ "attachments": "[ {% for file in fileUrls %} { {% if file['Name']|default %}\"title\": \"{{ file['Name'] }}\",{% endif %}{% if file['d']['document_x005f_label']|default %}\"labels\": [\"{{ file['d']['document_x005f_label'] }}\"],{% endif %}{% if file['__metadata']['uri']|default %}\"downloadUrl\": {\"accessUrl\": \"{{ file['__metadata']['uri']~'/$value' }}\", \"source\": \"1\"}{% endif %} }{{ loop.last ? '' : ',' }} {% endfor %} ]",
+ "status": "Concept",
+ "catalog": "",
+ "publicationType": ""
+ },
+ "unset": [],
+ "cast": {
+ "title": "unsetIfValue==d.woo_x005f_titel",
+ "description": "unsetIfValue==d.woo_x005f_beschrijving",
+ "summary": "unsetIfValue==d.woo_x005f_samenvatting",
+ "category": "unsetIfValue==d.woo_x005f_categorie",
+ "published": "unsetIfValue==",
+ "modified": "unsetIfValue==d.vti_x005f_nexttolasttimemodified",
+ "attachments": "jsonToArray",
+ "publicationType": "unsetIfValue==",
+ "catalog": "unsetIfValue=="
+ },
+ "passThrough": false
+}
diff --git a/configurations/sharepoint-woo/mappings/xxllnc-suite-to-publications.json b/configurations/sharepoint-woo/mappings/xxllnc-suite-to-publications.json
new file mode 100644
index 00000000..744823b1
--- /dev/null
+++ b/configurations/sharepoint-woo/mappings/xxllnc-suite-to-publications.json
@@ -0,0 +1,26 @@
+{
+ "name": "Xxllnc suite to Publication",
+ "version": "0.0.1",
+ "mapping": {
+ "title": "omschrijving",
+ "summary": "zaaktypeomschrijving",
+ "description": "zaaktypeomschrijving",
+ "category": "{% if zaaktypecode|default %}{% set wooVerzoekenEnBesluiten = ['LP00000431', 'B1873', 'cherry'] %}{% set klachtoordelen = ['LP00000091', 'LP00001132', 'LP00000121', 'B0757', 'LP00000832', 'LP00001096'] %}{% if zaaktypecode in wooVerzoekenEnBesluiten %}{{ 'Woo-verzoeken en -besluiten' }}{% elseif zaaktypecode in klachtoordelen %}{{ 'Klachtoordelen' }}{% endif %}{% endif %}",
+ "published": "startdatum",
+ "modified": "{{ 'now'|date('H:i:sTm-d-Y') }}",
+ "attachments": "[{% if files|default %}{% for file in files %} { {% if file['titel']|default %}\"title\": \"{{ file['titel'] }}\",{% endif %}\"labels\": [\"{{ 'Informatieverzoek' }}\"],{% if file['formaat']|default %}\"extension\": \"{{ file['formaat']|split('/')|last }}\",\"type\": \"{{ file['formaat'] }}\",{% endif %}{% if file['inhoud']|default and file['formaat']|default %}\"accessUrl\": \"data:{{ file['formaat'] }};base64,{{ file.inhoud }}\"{% endif %} }{{ loop.last ? '' : ',' }} {% endfor %}{% endif %}]",
+ "status": "Concept"
+ },
+ "unset": [
+ ""
+ ],
+ "cast": {
+ "title": "unsetIfValue==omschrijving",
+ "summary": "unsetIfValue==zaaktypeomschrijving",
+ "description": "unsetIfValue==zaaktypeomschrijving",
+ "category": "unsetIfValue==",
+ "published": "unsetIfValue==startdatum",
+ "attachments": "jsonToArray"
+ },
+ "passThrough": false
+}
\ No newline at end of file
diff --git a/configurations/sharepoint-woo/sources/sharepoint.json b/configurations/sharepoint-woo/sources/sharepoint.json
new file mode 100644
index 00000000..ddfcdbd6
--- /dev/null
+++ b/configurations/sharepoint-woo/sources/sharepoint.json
@@ -0,0 +1,34 @@
+{
+ "name": "Sharepoint Woo API",
+ "description": "Sharepoint Woo API",
+ "reference": null,
+ "version": "0.0.1",
+ "location": "",
+ "isEnabled": true,
+ "type": "api",
+ "authorizationHeader": "Authorization",
+ "auth": "none",
+ "authorizationPassthroughMethod": "header",
+ "authenticationConfig": [],
+ "configuration": {
+ "headers": {
+ "Authorization": "Bearer {{ oauthToken(source) }}",
+ "Accept": "application/json;odata=verbose"
+ },
+ "authentication": {
+ "grant_type": "",
+ "scope": "",
+ "authentication": "body",
+ "client_id": "",
+ "client_secret": "",
+ "client_assertion_type": "",
+ "private_key": "",
+ "x5t": "",
+ "payload": "",
+ "tokenUrl": ""
+ }
+ },
+ "logRetention": 3600,
+ "errorRetention": 86400,
+ "test": false
+}
\ No newline at end of file
diff --git a/configurations/sharepoint-woo/sources/xxllnc-suite.json b/configurations/sharepoint-woo/sources/xxllnc-suite.json
new file mode 100644
index 00000000..ba40f107
--- /dev/null
+++ b/configurations/sharepoint-woo/sources/xxllnc-suite.json
@@ -0,0 +1,20 @@
+{
+ "name": "Xxllnc suite API",
+ "description": "Xxllnc suite API",
+ "reference": null,
+ "version": "0.0.0",
+ "location": "",
+ "isEnabled": true,
+ "type": "api",
+ "authorizationHeader": null,
+ "auth": "none",
+ "authorizationPassthroughMethod": "header",
+ "authenticationConfig": [],
+ "configuration": {
+ "auth.0": "username",
+ "auth.1": "password"
+ },
+ "logRetention": 3600,
+ "errorRetention": 86400,
+ "test": false
+}
\ No newline at end of file
diff --git a/configurations/sharepoint-woo/synchronizations/sharepoint-convenanten-to-publications.json b/configurations/sharepoint-woo/synchronizations/sharepoint-convenanten-to-publications.json
new file mode 100644
index 00000000..539175ce
--- /dev/null
+++ b/configurations/sharepoint-woo/synchronizations/sharepoint-convenanten-to-publications.json
@@ -0,0 +1,26 @@
+{
+ "name": "Sharepoint Convenanten to Publications",
+ "description": "",
+ "version": "0.0.1",
+ "sourceId": "1",
+ "sourceType": "api",
+ "sourceHash": "",
+ "sourceTargetMapping": "1",
+ "sourceConfig": {
+ "idPosition": "UniqueId",
+ "resultsPosition": "d.results",
+ "endpoint": "\/Web\/GetFolderByServerRelativePath(decodedurl='\/WOO\/Convenanten')\/folders",
+ "extraDataConfigs.0.dynamicEndpointLocation": "Properties.__deferred.uri",
+ "extraDataConfigs.0.mergeExtraData": "true",
+ "extraDataConfigs.1.dynamicEndpointLocation": "Files.__deferred.uri",
+ "extraDataConfigs.1.mergeExtraData": "true",
+ "extraDataConfigs.1.keyToSetExtraData": "fileUrls",
+ "extraDataConfigs.1.resultsLocation": "d.results",
+ "extraDataConfigs.1.extraDataConfigPerResult.dynamicEndpointLocation": "Properties.__deferred.uri",
+ "extraDataConfigs.1.extraDataConfigPerResult.mergeExtraData": "true",
+ "headers": [],
+ "query": []
+ },
+ "targetId": "1/1",
+ "targetType": "register/schema"
+}
\ No newline at end of file
diff --git a/configurations/sharepoint-woo/synchronizations/sharepoint-woo-verzoeken-to-publications.json b/configurations/sharepoint-woo/synchronizations/sharepoint-woo-verzoeken-to-publications.json
new file mode 100644
index 00000000..88568b92
--- /dev/null
+++ b/configurations/sharepoint-woo/synchronizations/sharepoint-woo-verzoeken-to-publications.json
@@ -0,0 +1,26 @@
+{
+ "name": "Sharepoint Woo-verzoeken en -besluiten to Publications",
+ "description": "",
+ "version": "0.0.1",
+ "sourceId": "1",
+ "sourceType": "api",
+ "sourceHash": "",
+ "sourceTargetMapping": "1",
+ "sourceConfig": {
+ "idPosition": "UniqueId",
+ "resultsPosition": "d.results",
+ "endpoint": "/Web/GetFolderByServerRelativePath(decodedurl='/WOO/Woo-verzoeken en -besluiten')/folders",
+ "extraDataConfigs.0.dynamicEndpointLocation": "Properties.__deferred.uri",
+ "extraDataConfigs.0.mergeExtraData": "true",
+ "extraDataConfigs.1.dynamicEndpointLocation": "Files.__deferred.uri",
+ "extraDataConfigs.1.mergeExtraData": "true",
+ "extraDataConfigs.1.keyToSetExtraData": "fileUrls",
+ "extraDataConfigs.1.resultsLocation": "d.results",
+ "extraDataConfigs.1.extraDataConfigPerResult.dynamicEndpointLocation": "Properties.__deferred.uri",
+ "extraDataConfigs.1.extraDataConfigPerResult.mergeExtraData": "true",
+ "headers": [],
+ "query": []
+ },
+ "targetId": "1/1",
+ "targetType": "register/schema"
+}
\ No newline at end of file
diff --git a/configurations/sharepoint-woo/synchronizations/xxllnc-suite-to-publications.json b/configurations/sharepoint-woo/synchronizations/xxllnc-suite-to-publications.json
new file mode 100644
index 00000000..91922ef1
--- /dev/null
+++ b/configurations/sharepoint-woo/synchronizations/xxllnc-suite-to-publications.json
@@ -0,0 +1,25 @@
+{
+ "name": "Xxllnc suite to Publications",
+ "description": "",
+ "version": "0.0.1",
+ "sourceId": "1",
+ "sourceType": "api",
+ "sourceHash": "",
+ "sourceTargetMapping": "1",
+ "sourceConfig": {
+ "idPosition": "identificatie",
+ "resultsPosition": "results",
+ "endpoint": "\/tlb\/zaaksysteem\/api\/v1\/zaken",
+ "headers": [],
+ "query.startdatum__gte": "2024-08-01",
+ "query.einddatum__lt": "2025-01-01",
+ "usesPagination": "false",
+ "extraDataConfigs.0.staticEndpoint": "/tlb/zaaksysteem/api/v1/zaken/{{ originId }}/informatieobjecten",
+ "extraDataConfigs.0.mergeExtraData": "true",
+ "extraDataConfigs.0.keyToSetExtraData": "files",
+ "extraDataConfigs.0.resultsLocation": "results",
+ "extraDataConfigs.0.extraDataConfigPerResult.staticEndpoint": "/tlb/zaaksysteem/api/v1/informatieobjecten/{{ originId }}"
+ },
+ "targetId": "1/1",
+ "targetType": "register/schema"
+}
\ No newline at end of file
diff --git a/lib/Controller/JobsController.php b/lib/Controller/JobsController.php
index 4ffe28b8..201657ae 100644
--- a/lib/Controller/JobsController.php
+++ b/lib/Controller/JobsController.php
@@ -236,13 +236,18 @@ public function run(int $id): JSONResponse
}
try {
- $this->jobList->getById($job->getJobListId())->start($this->jobList);
- $lastLog = $this->jobLogMapper->getLastCallLog();
- if ($lastLog !== null) {
- return new JSONResponse(data: $lastLog, statusCode: 200);
- }
-
- return new JSONResponse(data: ['error' => 'No job log could be found, job did not went succesfully or failed to log anything'], statusCode: 500);
+ $job = $this->jobList->getById($job->getJobListId());
+ if ($job !== null) {
+ $job->setArgument(['jobId' => $id, 'forceRun' => true]);
+ $job->start($this->jobList);
+
+ $lastLog = $this->jobLogMapper->getLastCallLog();
+ if ($lastLog !== null && ($lastLog->getJobId() === null || (int) $lastLog->getJobId() === $id)) {
+ return new JSONResponse(data: $lastLog, statusCode: 200);
+ }
+ }
+
+ return new JSONResponse(data: ['error' => 'No job log could be found, job did not go successfully or failed to log anything'], statusCode: 500);
} catch (Exception $exception) {
return new JSONResponse(data: ['error' => $exception->getMessage()], statusCode: 400);
}
diff --git a/lib/Controller/SynchronizationsController.php b/lib/Controller/SynchronizationsController.php
index 9ceb1e9b..85b5171d 100644
--- a/lib/Controller/SynchronizationsController.php
+++ b/lib/Controller/SynchronizationsController.php
@@ -2,6 +2,7 @@
namespace OCA\OpenConnector\Controller;
+use GuzzleHttp\Exception\GuzzleException;
use OCA\OpenConnector\Service\ObjectService;
use OCA\OpenConnector\Service\SearchService;
use OCA\OpenConnector\Service\SynchronizationService;
@@ -15,6 +16,8 @@
use OCP\IRequest;
use Exception;
use OCP\AppFramework\Db\DoesNotExistException;
+use Psr\Container\ContainerExceptionInterface;
+use Psr\Container\NotFoundExceptionInterface;
class SynchronizationsController extends Controller
{
@@ -213,33 +216,36 @@ public function logs(int $id): JSONResponse
}
}
- /**
- * Tests a synchronization
- *
- * This method tests a synchronization without persisting anything to the database.
- *
- * @NoAdminRequired
- * @NoCSRFRequired
- *
- * @param int $id The ID of the synchronization
- *
- * @return JSONResponse A JSON response containing the test results
- *
- * @example
- * Request:
- * empty POST
- *
- * Response:
- * {
- * "resultObject": {
- * "fullName": "John Doe",
- * "userAge": 30,
- * "contactEmail": "john@example.com"
- * },
- * "isValid": true,
- * "validationErrors": []
- * }
- */
+ /**
+ * Tests a synchronization
+ *
+ * This method tests a synchronization without persisting anything to the database.
+ *
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ *
+ * @param int $id The ID of the synchronization
+ *
+ * @return JSONResponse A JSON response containing the test results
+ * @throws GuzzleException
+ * @throws ContainerExceptionInterface
+ * @throws NotFoundExceptionInterface
+ *
+ * @example
+ * Request:
+ * empty POST
+ *
+ * Response:
+ * {
+ * "resultObject": {
+ * "fullName": "John Doe",
+ * "userAge": 30,
+ * "contactEmail": "john@example.com"
+ * },
+ * "isValid": true,
+ * "validationErrors": []
+ * }
+ */
public function test(int $id): JSONResponse
{
try {
@@ -263,4 +269,4 @@ public function test(int $id): JSONResponse
return new JSONResponse($resultFromTest, 200);
}
-}
\ No newline at end of file
+}
diff --git a/lib/Cron/ActionTask.php b/lib/Cron/ActionTask.php
index a5b3a54d..ec412b38 100644
--- a/lib/Cron/ActionTask.php
+++ b/lib/Cron/ActionTask.php
@@ -8,6 +8,8 @@
use OCP\BackgroundJob\TimedJob;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
+use OCP\IUserManager;
+use OCP\IUserSession;
use Psr\Container\ContainerInterface;
use Symfony\Component\Uid\Uuid;
use DateInterval;
@@ -30,7 +32,9 @@ public function __construct(
JobMapper $jobMapper,
JobLogMapper $jobLogMapper,
IJobList $jobList,
- ContainerInterface $containerInterface
+ ContainerInterface $containerInterface,
+ private IUserSession $userSession,
+ private IUserManager $userManager,
) {
parent::__construct($time);
$this->jobMapper = $jobMapper;
@@ -59,27 +63,53 @@ public function __construct(
public function run($argument)
{
// if we do not have a job id then everything is wrong
- if (isset($arguments['jobId']) === true && is_int($argument['jobId']) === true) {
- return;
+ if (isset($argument['jobId']) === false || is_int($argument['jobId']) === false) {
+ return $this->jobLogMapper->createFromArray([
+ 'jobId' => 'null',
+ 'level' => 'ERROR',
+ 'message' => "Couldn't find a jobId in the action argument"
+ ]);
}
- // Let's get the job, the user might have deleted it in the mean time
+ // Let's get the job, the user might have deleted it in the meantime
try {
$job = $this->jobMapper->find($argument['jobId']);
} catch (Exception $e) {
- return;
+ return $this->jobLogMapper->createFromArray([
+ 'jobId' => $argument['jobId'],
+ 'level' => 'ERROR',
+ 'message' => "Couldn't find a Job with this jobId, message: ".$e->getMessage()
+ ]);
}
+ $forceRun = false;
+ $stackTrace = [];
+ if (isset($argument['forceRun']) === true && $argument['forceRun'] === true) {
+ $forceRun = true;
+ $stackTrace[] = 'Doing a force run for this job, ignoring "enabled" & "nextRun" check...';
+ }
+
// If the job is not enabled, we don't need to do anything
- if ($job->getIsEnabled() === false) {
- return;
+ if ($forceRun === false && $job->getIsEnabled() === false) {
+ return $this->jobLogMapper->createForJob($job, [
+ 'level' => 'WARNING',
+ 'message' => 'This job is disabled'
+ ]);
}
- // if the next run is in the the future, we don't need to do anything
- if ($job->getNextRun() !== null && $job->getNextRun() > new DateTime()) {
- return;
+ // if the next run is in the future, we don't need to do anything
+ if ($forceRun === false && $job->getNextRun() !== null && $job->getNextRun() > new DateTime()) {
+ return $this->jobLogMapper->createForJob($job, [
+ 'level' => 'WARNING',
+ 'message' => 'Next Run is still in the future for this job'
+ ]);
}
+ if (empty($job->getUserId()) === false && $this->userSession->getUser() === null) {
+ $user = $this->userManager->get($job->getUserId());
+ $this->userSession->setUser($user);
+ }
+
$time_start = microtime(true);
$action = $this->containerInterface->get($job->getJobClass());
@@ -93,24 +123,23 @@ public function run($argument)
$executionTime = ( $time_end - $time_start ) * 1000;
// deal with single run
- if ($job->isSingleRun() === true) {
+ if ($forceRun === false && $job->isSingleRun() === true) {
$job->setIsEnabled(false);
}
-
// Update the job
- $job->setLastRun(new DateTime());
- $job->setNextRun(new DateTime());
- $this->jobMapper->update($job);
+ if ($forceRun === false) {
+ $job->setLastRun(new DateTime());
+ $nextRun = new DateTime('now + '.$job->getInterval().' seconds');
+ $nextRun->setTime(hour: $nextRun->format('H'), minute: $nextRun->format('i'));
+ $job->setNextRun($nextRun);
+ $this->jobMapper->update($job);
+ }
// Log the job
- $jobLog = $this->jobLogMapper->createFromArray([
- 'jobId' => $job->getId(),
- 'jobClass' => $job->getJobClass(),
- 'jobListId' => $job->getJobListId(),
- 'arguments' => $job->getArguments(),
- 'lastRun' => $job->getLastRun(),
- 'nextRun' => $job->getNextRun(),
+ $jobLog = $this->jobLogMapper->createForJob($job, [
+ 'level' => 'INFO',
+ 'message' => 'Succes',
'executionTime' => $executionTime
]);
@@ -123,10 +152,14 @@ public function run($argument)
$jobLog->setMessage($result['message']);
}
if (isset($result['stackTrace']) === true) {
- $jobLog->setStackTrace($result['stackTrace']);
+ $stackTrace = array_merge($stackTrace, $result['stackTrace']);
}
}
+ $jobLog->setStackTrace($stackTrace);
+
+ $this->jobLogMapper->update(entity: $jobLog);
+
// Let's report back about what we have just done
return $jobLog;
}
diff --git a/lib/Cron/LogCleanUpTask.php b/lib/Cron/LogCleanUpTask.php
index 0af048ad..0aa7cf32 100644
--- a/lib/Cron/LogCleanUpTask.php
+++ b/lib/Cron/LogCleanUpTask.php
@@ -3,13 +3,35 @@
namespace OCA\OpenConnector\Cron;
use OCA\OpenConnector\Db\CallLogMapper;
+use OCA\OpenConnector\Db\JobLogMapper;
+use OCA\OpenConnector\Db\JobMapper;
+use OCP\BackgroundJob\IJobList;
use OCP\BackgroundJob\TimedJob;
use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\IUserManager;
+use OCP\IUserSession;
-class LogCleanUpTask
+class LogCleanUpTask extends TimedJob
{
- public function doCron(array $arguments, CallLogMapper $callLogMapper){
- $callLogMapper = new ClearLogs();
+ public function __construct(
+ ITimeFactory $time,
+ private readonly CallLogMapper $callLogMapper,
+ ) {
+ parent::__construct($time);
+
+ // Run every 5 minutes
+ $this->setInterval(300);
+
+ // Delay until low-load time
+ //$this->setTimeSensitivity(\OCP\BackgroundJob\IJob::TIME_SENSITIVE);
+ // Or $this->setTimeSensitivity(\OCP\BackgroundJob\IJob::TIME_INSENSITIVE);
+
+ // Only run one instance of this job at a time
+ $this->setAllowParallelRuns(false);
+ }
+
+ public function run(mixed $argument){
+ $this->callLogMapper->clearLogs();
}
}
diff --git a/lib/Db/CallLogMapper.php b/lib/Db/CallLogMapper.php
index 49cac829..615b5bb8 100644
--- a/lib/Db/CallLogMapper.php
+++ b/lib/Db/CallLogMapper.php
@@ -93,7 +93,7 @@ public function clearLogs(): bool
// Build the delete query
$qb->delete('openconnector_call_logs')
- ->where($qb->expr()->lt('expired', $qb->createFunction('NOW()')));
+ ->where($qb->expr()->lt('expires', $qb->createFunction('NOW()')));
// Execute the query and get the number of affected rows
$result = $qb->execute();
diff --git a/lib/Db/JobLogMapper.php b/lib/Db/JobLogMapper.php
index cc0dc683..cea21ca0 100644
--- a/lib/Db/JobLogMapper.php
+++ b/lib/Db/JobLogMapper.php
@@ -56,8 +56,28 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters
return $this->findEntities($qb);
}
+ public function createForJob(Job $job, array $object): JobLog
+ {
+ $jobObject = [
+ 'jobId' => $job->getId(),
+ 'jobClass' => $job->getJobClass(),
+ 'jobListId' => $job->getJobListId(),
+ 'arguments' => $job->getArguments(),
+ 'lastRun' => $job->getLastRun(),
+ 'nextRun' => $job->getNextRun(),
+ ];
+
+ $object = array_merge($jobObject, $object);
+
+ return $this->createFromArray($object);
+ }
+
public function createFromArray(array $object): JobLog
{
+ if (isset($object['executionTime']) === false) {
+ $object['executionTime'] = 0;
+ }
+
$obj = new JobLog();
$obj->hydrate($object);
// Set uuid
diff --git a/lib/Db/Source.php b/lib/Db/Source.php
index 85270d64..a1952937 100644
--- a/lib/Db/Source.php
+++ b/lib/Db/Source.php
@@ -39,12 +39,16 @@ class Source extends Entity implements JsonSerializable
protected ?string $status = null;
protected ?int $logRetention = 3600; // seconds to save all logs
protected ?int $errorRetention = 86400; // seconds to save error logs
+ protected ?int $objectCount = null;
+ protected ?bool $test = null;
+ protected ?int $rateLimitLimit = null; // Indicates the total number of allowed requests within a specific time period.
+ protected ?int $rateLimitRemaining = null; // Specifies how many requests are still allowed within the current limit.
+ protected ?int $rateLimitReset = null; // A Unix Time Stamp that indicates when the rate limit will be reset.
+ protected ?int $rateLimitWindow = null; // Indicates how many seconds the client must wait before making new requests.
protected ?DateTime $lastCall = null;
protected ?DateTime $lastSync = null;
- protected ?int $objectCount = null;
protected ?DateTime $dateCreated = null;
protected ?DateTime $dateModified = null;
- protected ?bool $test = null;
public function __construct() {
$this->addType('uuid', 'string');
@@ -78,12 +82,16 @@ public function __construct() {
$this->addType('status', 'string');
$this->addType('logRetention', 'integer');
$this->addType('errorRetention', 'integer');
+ $this->addType('objectCount', 'integer');
+ $this->addType('test', 'boolean');
+ $this->addType('rateLimitLimit', 'integer');
+ $this->addType('rateLimitRemaining', 'integer');
+ $this->addType('rateLimitReset', 'integer');
+ $this->addType('rateLimitWindow', 'integer');
$this->addType('lastCall', 'datetime');
$this->addType('lastSync', 'datetime');
- $this->addType('objectCount', 'integer');
$this->addType('dateCreated', 'datetime');
$this->addType('dateModified', 'datetime');
- $this->addType('test', 'boolean');
}
public function getJsonFields(): array
@@ -151,12 +159,16 @@ public function jsonSerialize(): array
'status' => $this->status,
'logRetention' => $this->logRetention,
'errorRetention' => $this->errorRetention,
+ 'objectCount' => $this->objectCount,
+ 'test' => $this->test,
+ 'rateLimitLimit' => $this->rateLimitLimit,
+ 'rateLimitRemaining' => $this->rateLimitRemaining,
+ 'rateLimitReset' => $this->rateLimitReset,
+ 'rateLimitWindow' => $this->rateLimitWindow,
'lastCall' => $this->lastCall,
'lastSync' => $this->lastSync,
- 'objectCount' => $this->objectCount,
'dateCreated' => isset($this->dateCreated) ? $this->dateCreated->format('c') : null,
'dateModified' => isset($this->dateModified) ? $this->dateModified->format('c') : null,
- 'test' => $this->test
];
}
}
diff --git a/lib/Db/Synchronization.php b/lib/Db/Synchronization.php
index 0460859c..473524b0 100644
--- a/lib/Db/Synchronization.php
+++ b/lib/Db/Synchronization.php
@@ -34,6 +34,9 @@ class Synchronization extends Entity implements JsonSerializable
protected ?DateTime $created = null; // The date and time the synchronization was created
protected ?DateTime $updated = null; // The date and time the synchronization was updated
+ protected array $conditions = [];
+ protected array $followUps = [];
+
public function __construct() {
$this->addType('uuid', 'string');
@@ -58,8 +61,25 @@ public function __construct() {
$this->addType('targetLastSynced', 'datetime');
$this->addType('created', 'datetime');
$this->addType('updated', 'datetime');
+ $this->addType(fieldName:'conditions', type: 'json');
+ $this->addType(fieldName:'followUps', type: 'json');
}
+ /**
+ * Checks through sourceConfig if the source of this sync uses pagination
+ *
+ * @return bool true if its uses pagination
+ */
+ public function usesPagination(): bool
+ {
+ if (isset($this->sourceConfig['usesPagination']) === true && ($this->sourceConfig['usesPagination'] === false || $this->sourceConfig['usesPagination'] === 'false')) {
+ return false;
+ }
+
+ // By default sources use basic pagination.
+ return true;
+ }
+
public function getJsonFields(): array
{
return array_keys(
@@ -115,7 +135,9 @@ public function jsonSerialize(): array
'targetLastChecked' => isset($this->targetLastChecked) === true ? $this->targetLastChecked->format('c') : null,
'targetLastSynced' => isset($this->targetLastSynced) === true ? $this->targetLastSynced->format('c') : null,
'created' => isset($this->created) === true ? $this->created->format('c') : null,
- 'updated' => isset($this->updated) === true ? $this->updated->format('c') : null
+ 'updated' => isset($this->updated) === true ? $this->updated->format('c') : null,
+ 'conditions' => $this->conditions,
+ 'followUps' => $this->followUps,
];
}
}
diff --git a/lib/Migration/Version1Date20241111144800.php b/lib/Migration/Version1Date20241111144800.php
index 9eb4651d..10a6768c 100644
--- a/lib/Migration/Version1Date20241111144800.php
+++ b/lib/Migration/Version1Date20241111144800.php
@@ -101,7 +101,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array
&& $table->hasColumn('source_id') === true && $table->hasColumn('source_hash') === true
) {
$this->connection->executeQuery("
- UPDATE openconnector_synchronization_contracts
+ UPDATE oc_openconnector_synchronization_contracts
SET origin_id = source_id, origin_hash = source_hash
WHERE source_id IS NOT NULL
");
diff --git a/lib/Migration/Version1Date20241121160300.php b/lib/Migration/Version1Date20241121160300.php
new file mode 100644
index 00000000..daef153b
--- /dev/null
+++ b/lib/Migration/Version1Date20241121160300.php
@@ -0,0 +1,84 @@
+getTable('openconnector_sources');
+
+ if ($table->hasColumn('rate_limit_limit') === false) {
+ $table->addColumn('rate_limit_limit', Types::INTEGER, [
+ 'notnull' => false,
+ 'default' => null
+ ]);
+ }
+
+ if ($table->hasColumn('rate_limit_remaining') === false) {
+ $table->addColumn('rate_limit_remaining', Types::INTEGER, [
+ 'notnull' => false,
+ 'default' => null
+ ]);
+ }
+
+ if ($table->hasColumn('rate_limit_reset') === false) {
+ $table->addColumn('rate_limit_reset', Types::INTEGER, [
+ 'notnull' => false,
+ 'default' => null
+ ]);
+ }
+
+ if ($table->hasColumn('rate_limit_window') === false) {
+ $table->addColumn('rate_limit_window', Types::INTEGER, [
+ 'notnull' => false,
+ 'default' => null
+ ]);
+ }
+
+ return $schema;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure(): ISchemaWrapper $schemaClosure
+ * @param array $options
+ */
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ }
+}
diff --git a/lib/Migration/Version1Date20241126074122.php b/lib/Migration/Version1Date20241126074122.php
new file mode 100644
index 00000000..71edeb90
--- /dev/null
+++ b/lib/Migration/Version1Date20241126074122.php
@@ -0,0 +1,70 @@
+hasTable(tableName: 'openconnector_synchronizations') === true) {
+ $table = $schema->getTable(tableName: 'openconnector_synchronizations');
+ if ($table->hasColumn(name: 'conditions') === false) {
+ $table->addColumn(name: 'conditions', typeName: Types::JSON)
+ ->setDefault(default: '{}')
+ ->setNotnull(notnull:false);
+ }
+ if ($table->hasColumn(name: 'follow_ups') === false) {
+
+ $table->addColumn(name: 'follow_ups', typeName: Types::JSON)
+ ->setDefault(default: '{}')
+ ->setNotnull(notnull:false);
+ }
+ }
+
+ return $schema;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure(): ISchemaWrapper $schemaClosure
+ * @param array $options
+ */
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ }
+}
diff --git a/lib/Migration/Version1Date20241206095007.php b/lib/Migration/Version1Date20241206095007.php
new file mode 100644
index 00000000..d26a0c8d
--- /dev/null
+++ b/lib/Migration/Version1Date20241206095007.php
@@ -0,0 +1,66 @@
+hasTable('openconnector_sources') === true) {
+ $table = $schema->getTable('openconnector_sources');
+
+ if ($table->hasColumn('logRetention') === true) {
+ $table->dropColumn('logRetention');
+ $table->addColumn('log_retention', Types::INTEGER)->setNotnull(false)->setDefault(3600);
+ }
+ if ($table->hasColumn('errorRetention') === true) {
+ $table->dropColumn('errorRetention');
+ $table->addColumn('error_retention', Types::INTEGER)->setNotnull(false)->setDefault(86400);
+ }
+ }
+
+ return $schema;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure(): ISchemaWrapper $schemaClosure
+ * @param array $options
+ */
+ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
+ }
+}
diff --git a/lib/Service/AuthenticationService.php b/lib/Service/AuthenticationService.php
index b59e41b4..f6048746 100644
--- a/lib/Service/AuthenticationService.php
+++ b/lib/Service/AuthenticationService.php
@@ -192,6 +192,35 @@ public function fetchOAuthTokens (array $configuration): string
return $result['access_token'];
}
+ /**
+ * Fetch an access token from the DeCOS non-implementation of OAuth 2.0
+ *
+ * @param array $configuration The configuration of the source.
+ *
+ * @return string The access token
+ *
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function fetchDecosToken(array $configuration): string
+ {
+ $url = $configuration['tokenUrl'];
+ $tokenLocation = $configuration['tokenLocation'];
+ unset($configuration['tokenUrl']);
+
+ $callConfig['json'] = $configuration;
+
+ $client = new Client();
+ $response = $client->post(uri: $url, options: $callConfig);
+
+ $result = json_decode(json: $response->getBody()->getContents(), associative: true);
+
+ if (isset($tokenLocation) === true) {
+ return $result[$tokenLocation];
+ }
+
+ return $result['token'];
+ }
+
/**
* Get RSA key for RS and PS (asymmetrical) encryption.
*
@@ -201,7 +230,7 @@ public function fetchOAuthTokens (array $configuration): string
private function getRSJWK(array $configuration): ?JWK
{
$stamp = microtime().getmypid();
- $filename = "privatekey-$stamp";
+ $filename = "/var/tmp/privatekey-$stamp";
file_put_contents($filename, base64_decode($configuration['secret']));
$jwk = null;
try {
diff --git a/lib/Service/CallService.php b/lib/Service/CallService.php
index 4a86c601..8db6434c 100644
--- a/lib/Service/CallService.php
+++ b/lib/Service/CallService.php
@@ -19,6 +19,7 @@
use OCA\OpenConnector\Db\CallLogMapper;
use OCA\OpenConnector\Twig\AuthenticationExtension;
use OCA\OpenConnector\Twig\AuthenticationRuntimeLoader;
+use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use Symfony\Component\Uid\Uuid;
use Twig\Environment;
use Twig\Error\LoaderError;
@@ -42,11 +43,13 @@ class CallService
* The constructor sets al needed variables.
*
* @param CallLogMapper $callLogMapper
+ * @param SourceMapper $sourceMapper
* @param ArrayLoader $loader
* @param AuthenticationService $authenticationService
*/
public function __construct(
private readonly CallLogMapper $callLogMapper,
+ private readonly SourceMapper $sourceMapper,
ArrayLoader $loader,
AuthenticationService $authenticationService
)
@@ -111,6 +114,8 @@ private function renderConfiguration(array $configuration, Source $source): arra
*
* @return CallLog
* @throws GuzzleException
+ * @throws LoaderError
+ * @throws SyntaxError
* @throws \OCP\DB\Exception
*/
public function call(
@@ -133,7 +138,7 @@ public function call(
$callLog->setStatusCode(409);
$callLog->setStatusMessage("This source is not enabled");
$callLog->setCreated(new \DateTime());
- $callLog->setUpdated(new \DateTime());
+ $callLog->setExpires(new \DateTime('now + '.$source->getErrorRetention().' seconds'));
$this->callLogMapper->insert($callLog);
@@ -148,7 +153,34 @@ public function call(
$callLog->setStatusCode(409);
$callLog->setStatusMessage("This source has no location");
$callLog->setCreated(new \DateTime());
- $callLog->setUpdated(new \DateTime());
+ $callLog->setExpires(new \DateTime('now + '.$source->getErrorRetention().' seconds'));
+
+ $this->callLogMapper->insert($callLog);
+
+ return $callLog;
+ }
+
+ // Check if Source has a RateLimit and if we need to reset RateLimit-Reset and RateLimit-Remaining.
+ if ($this->source->getRateLimitReset() !== null
+ && $this->source->getRateLimitRemaining() !== null
+ && $this->source->getRateLimitReset() <= time()
+ ) {
+ $this->source->setRateLimitReset(null);
+ $this->source->setRateLimitRemaining(null);
+
+ $this->sourceMapper->update($source);
+ }
+
+ // Check if RateLimit-Remaining is set on this source and if limit has been reached.
+ if ($this->source->getRateLimitRemaining() !== null && $this->source->getRateLimitRemaining() <= 0) {
+ // Create and save the CallLog
+ $callLog = new CallLog();
+ $callLog->setUuid(Uuid::v4());
+ $callLog->setSourceId($this->source->getId());
+ $callLog->setStatusCode(429); //
+ $callLog->setStatusMessage("The rate limit for this source has been exceeded. Try again later.");
+ $callLog->setCreated(new \DateTime());
+ $callLog->setExpires(new \DateTime('now + '.$source->getErrorRetention().' seconds'));
$this->callLogMapper->insert($callLog);
@@ -211,6 +243,7 @@ public function call(
if ($asynchronous === false) {
$response = $this->client->request($method, $url, $config);
} else {
+ // @todo: we want to get rate limit headers from async calls as well
return $this->client->requestAsync($method, $url, $config);
}
} catch (GuzzleHttp\Exception\BadResponseException $e) {
@@ -219,6 +252,8 @@ public function call(
$time_end = microtime(true);
+ $body = $response->getBody()->getContents();
+
// Let create the data array
$data = [
'request' => [
@@ -233,10 +268,14 @@ public function call(
'size' => $response->getBody()->getSize(),
'remoteIp' => $response->getHeaderLine('X-Real-IP') ?: $response->getHeaderLine('X-Forwarded-For') ?: null,
'headers' => $response->getHeaders(),
- 'body' => $response->getBody()->getContents(),
+ 'body' => mb_check_encoding(value: $body, encoding: 'UTF-8') !== false ? $body : base64_encode($body),
+ 'encoding' => mb_check_encoding(value: $body, encoding: 'UTF-8') !== false ? 'UTF-8' : 'base64',
]
];
+ // Update Rate Limit info for the source with the rate limit headers if present or if configured in the source.
+ $data['response']['headers'] = $this->sourceRateLimit($source, $data['response']['headers']);
+
// Create and save the CallLog
$callLog = new CallLog();
$callLog->setUuid(Uuid::v4());
@@ -246,12 +285,76 @@ public function call(
$callLog->setRequest($data['request']);
$callLog->setResponse($data['response']);
$callLog->setCreated(new \DateTime());
+ $callLog->setExpires(new \DateTime('now + '.($data['response']['statusCode'] < 400 ? $source->getLogRetention() : $source->getErrorRetention()).' seconds'));
$this->callLogMapper->insert($callLog);
return $callLog;
}
+ /**
+ * Update the source with rate limit info if any of the rate limit headers are found. Else checks if config on the
+ * source has been set for Rate Limit. And update the response headers with this Rate Limit info.
+ *
+ * @param Source $source The source to update.
+ * @param array $headers The response headers to check for Rate Limit headers.
+ *
+ * @return array The updated response headers.
+ * @throws \OCP\DB\Exception
+ */
+ private function sourceRateLimit(Source $source, array $headers): array
+ {
+ // Check if RateLimit-Reset is present in response headers. If so, save it in the source.
+ if (isset($headers['X-RateLimit-Reset']) === true) {
+ $source->setRateLimitReset($headers['X-RateLimit-Reset']);
+ }
+
+ // If RateLimit-Reset not in headers and source->RateLimit-Reset === null. But source->RateLimit-Window is set.
+ if (isset($headers['X-RateLimit-Reset']) === false
+ && $source->getRateLimitReset() === null
+ && $source->getRateLimitWindow() !== null
+ ) {
+ // Set new RateLimit-Reset time on the source.
+ $rateLimitReset = time() + $source->getRateLimitWindow();
+ $source->setRateLimitReset($rateLimitReset);
+ }
+
+ // Check if RateLimit-Limit is present in response headers. If so, save it in the source.
+ if (isset($headers['X-RateLimit-Limit']) === true) {
+ $source->setRateLimitLimit($headers['X-RateLimit-Limit']);
+ }
+
+ // Check if RateLimit-Remaining is present in response headers. If so, save it in the source.
+ if (isset($headers['X-RateLimit-Remaining']) === true) {
+ $source->setRateLimitRemaining($headers['X-RateLimit-Remaining']);
+ }
+
+ // If RateLimit-Remaining not in headers and source->RateLimit-Limit is set, update source->RateLimit-Remaining.
+ if (isset($headers['X-RateLimit-Remaining']) === false && $source->getRateLimitLimit() !== null) {
+ $rateLimitRemaining = $source->getRateLimitRemaining();
+ if ($rateLimitRemaining === null) {
+ // Re-set the RateLimit-Remaining on the source.
+ $rateLimitRemaining = $source->getRateLimitLimit();
+ }
+ $source->setRateLimitRemaining($rateLimitRemaining - 1);
+ }
+
+ $this->sourceMapper->update($source);
+
+ if ($source->getRateLimitLimit() !== null || $source->getRateLimitWindow() !== null) {
+ $headers = array_merge($headers, [
+ 'X-RateLimit-Limit' => [(string) $source->getRateLimitLimit()],
+ 'X-RateLimit-Remaining' => [(string) $source->getRateLimitRemaining()],
+ 'X-RateLimit-Reset' => [(string) $source->getRateLimitReset()],
+ 'X-RateLimit-Used' => ["1"],
+ 'X-RateLimit-Window' => [(string) $source->getRateLimitWindow()],
+ ]);
+ ksort($headers);
+ }
+
+ return $headers;
+ }
+
/**
* Uses Adbar Dot to place the values of keys with a dot in it in the $config array
* to the correct position in the then updated multidimensional $config array.
@@ -260,7 +363,7 @@ public function call(
*
* @return array The updated config array.
*/
- private function applyConfigDot(array $config): array
+ public function applyConfigDot(array $config): array
{
$dotConfig = new Dot($config);
$unsetKeys = [];
diff --git a/lib/Service/JobService.php b/lib/Service/JobService.php
index 81856460..e5da76a5 100644
--- a/lib/Service/JobService.php
+++ b/lib/Service/JobService.php
@@ -51,10 +51,10 @@ public function scheduleJob(Job $job): Job
$arguments['jobId'] = $job->getId();
if (!$job->getScheduleAfter()) {
- $iJob = $this->jobList->add($this->actionTask::class, $arguments);
+ $this->jobList->add($this->actionTask::class, $arguments);
} else {
$runAfter = $job->getScheduleAfter()->getTimestamp();
- $iJob = $this->jobList->scheduleAfter($this->actionTask::class, $runAfter, $arguments);
+ $this->jobList->scheduleAfter($this->actionTask::class, $runAfter, $arguments);
}
// Set the job list id
diff --git a/lib/Service/SynchronizationService.php b/lib/Service/SynchronizationService.php
index 37fcb836..3f51accd 100644
--- a/lib/Service/SynchronizationService.php
+++ b/lib/Service/SynchronizationService.php
@@ -4,6 +4,7 @@
use Exception;
use GuzzleHttp\Exception\GuzzleException;
+use JWadhams\JsonLogic;
use OCA\OpenConnector\Db\CallLog;
use OCA\OpenConnector\Db\Mapping;
use OCA\OpenConnector\Db\Source;
@@ -29,6 +30,7 @@
use DateTime;
use OCA\OpenConnector\Db\MappingMapper;
use OCP\AppFramework\Http\NotFoundResponse;
+use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\SyntaxError;
@@ -45,6 +47,12 @@ class SynchronizationService
private ObjectService $objectService;
private Source $source;
+ const EXTRA_DATA_CONFIGS_LOCATION = 'extraDataConfigs';
+ const EXTRA_DATA_DYNAMIC_ENDPOINT_LOCATION = 'dynamicEndpointLocation';
+ const EXTRA_DATA_STATIC_ENDPOINT_LOCATION = 'staticEndpoint';
+ const KEY_FOR_EXTRA_DATA_LOCATION = 'keyToSetExtraData';
+ const MERGE_EXTRA_DATA_OBJECT_LOCATION = 'mergeExtraData';
+
public function __construct(
CallService $callService,
@@ -54,7 +62,7 @@ public function __construct(
MappingMapper $mappingMapper,
SynchronizationMapper $synchronizationMapper,
SynchronizationContractMapper $synchronizationContractMapper,
- SynchronizationContractLogMapper $synchronizationContractLogMapper
+ SynchronizationContractLogMapper $synchronizationContractLogMapper,
) {
$this->callService = $callService;
$this->mappingService = $mappingService;
@@ -102,13 +110,11 @@ public function synchronize(Synchronization $synchronization, ?bool $isTest = fa
$synchronizationContract = $this->synchronizationContractMapper->createFromArray([
'synchronizationId' => $synchronization->getId(),
'originId' => $originId,
- 'originHash' => md5(serialize($object))
]);
} else {
$synchronizationContract = new SynchronizationContract();
$synchronizationContract->setSynchronizationId($synchronization->getId());
$synchronizationContract->setOriginId($originId);
- $synchronizationContract->setOriginHash(md5(serialize($object)));
}
$synchronizationContract = $this->synchronizeContract(synchronizationContract: $synchronizationContract, synchronization: $synchronization, object: $object, isTest: $isTest);
@@ -135,6 +141,11 @@ public function synchronize(Synchronization $synchronization, ?bool $isTest = fa
$this->synchronizationContractMapper->update($synchronizationContract);
}
+ foreach ($synchronization->getFollowUps() as $followUp) {
+ $followUpSynchronization = $this->synchronizationMapper->find($followUp);
+ $this->synchronize($followUpSynchronization, $isTest);
+ }
+
return $objectList;
}
@@ -174,6 +185,116 @@ private function getOriginId(Synchronization $synchronization, array $object): i
return $originId;
}
+ /**
+ * Fetch an object from a specific endpoint.
+ *
+ * @param Synchronization $synchronization The synchronization containing the source.
+ * @param string $endpoint The endpoint to request to fetch the desired object.
+ *
+ * @return array The resulting object.
+ *
+ * @throws GuzzleException
+ * @throws \OCP\DB\Exception
+ */
+ public function getObjectFromSource(Synchronization $synchronization, string $endpoint): array
+ {
+ $source = $this->sourceMapper->find(id: $synchronization->getSourceId());
+
+ // Lets get the source config
+ $sourceConfig = $synchronization->getSourceConfig();
+ $headers = $sourceConfig['headers'] ?? [];
+ $query = $sourceConfig['query'] ?? [];
+ $config = [
+ 'headers' => $headers,
+ 'query' => $query,
+ ];
+
+ if (str_starts_with($endpoint, $source->getLocation()) === true) {
+ $endpoint = str_replace(search: $source->getLocation(), replace: '', subject: $endpoint);
+ }
+
+ // Make the initial API call
+ // @TODO: method is now fixed to GET, but could end up in configuration.
+ $response = $this->callService->call(source: $source, endpoint: $endpoint, config: $config)->getResponse();
+
+ return json_decode($response['body'], true);
+ }
+
+ /**
+ * Fetches additional data for a given object based on the synchronization configuration.
+ *
+ * This method retrieves extra data using either a dynamically determined endpoint from the object
+ * or a statically defined endpoint in the configuration. The extra data can be merged with the original
+ * object or returned as-is, based on the provided configuration.
+ *
+ * @param Synchronization $synchronization The synchronization instance containing configuration details.
+ * @param array $extraDataConfig The configuration array specifying how to retrieve and handle the extra data:
+ * - EXTRA_DATA_DYNAMIC_ENDPOINT_LOCATION: The key to retrieve the dynamic endpoint from the object.
+ * - EXTRA_DATA_STATIC_ENDPOINT_LOCATION: The statically defined endpoint.
+ * - KEY_FOR_EXTRA_DATA_LOCATION: The key under which the extra data should be returned.
+ * - MERGE_EXTRA_DATA_OBJECT_LOCATION: Boolean flag indicating whether to merge the extra data with the object.
+ * @param array $object The original object for which extra data needs to be fetched.
+ *
+ * @return array The original object merged with the extra data, or the extra data itself based on the configuration.
+ *
+ * @throws Exception If both dynamic and static endpoint configurations are missing or the endpoint cannot be determined.
+ */
+ private function fetchExtraDataForObject(Synchronization $synchronization, array $extraDataConfig, array $object)
+ {
+ if (isset($extraDataConfig[$this::EXTRA_DATA_DYNAMIC_ENDPOINT_LOCATION]) === false && isset($extraDataConfig[$this::EXTRA_DATA_STATIC_ENDPOINT_LOCATION]) === false) {
+ return $object;
+ }
+
+ // Get endpoint from earlier fetched object.
+ if (isset($extraDataConfig[$this::EXTRA_DATA_DYNAMIC_ENDPOINT_LOCATION]) === true) {
+ $dotObject = new Dot($object);
+ $endpoint = $dotObject->get($extraDataConfig[$this::EXTRA_DATA_DYNAMIC_ENDPOINT_LOCATION] ?? null);
+ }
+
+ // Get endpoint static defined in config.
+ if (isset($extraDataConfig[$this::EXTRA_DATA_STATIC_ENDPOINT_LOCATION]) === true) {
+ $endpoint = $extraDataConfig[$this::EXTRA_DATA_STATIC_ENDPOINT_LOCATION];
+ $endpoint = str_replace(search: '{{ originId }}', replace: $this->getOriginId($synchronization, $object), subject: $endpoint);
+ $endpoint = str_replace(search: '{{originId}}', replace: $this->getOriginId($synchronization, $object), subject: $endpoint);
+ }
+
+ if (!$endpoint) {
+ throw new Exception(
+ sprintf(
+ 'Could not get static or dynamic endpoint, object: %s',
+ json_encode($object)
+ )
+ );
+ }
+
+ $extraData = $this->getObjectFromSource($synchronization, $endpoint);
+
+ // Temporary fix,
+ if (isset($extraDataConfig['extraDataConfigPerResult']) === true) {
+ $dotObject = new Dot($extraData);
+ $results = $dotObject->get($extraDataConfig['resultsLocation']);
+
+ foreach ($results as $key => $result) {
+ $results[$key] = $this->fetchExtraDataForObject($synchronization, $extraDataConfig['extraDataConfigPerResult'], $result);
+ }
+
+ $extraData = $results;
+ }
+
+ // Set new key if configured.
+ if (isset($extraDataConfig[$this::KEY_FOR_EXTRA_DATA_LOCATION]) === true) {
+ $extraData = [$extraDataConfig[$this::KEY_FOR_EXTRA_DATA_LOCATION] => $extraData];
+ }
+
+ // Merge with earlier fetchde object if configured.
+ if (isset($extraDataConfig[$this::MERGE_EXTRA_DATA_OBJECT_LOCATION]) === true && ($extraDataConfig[$this::MERGE_EXTRA_DATA_OBJECT_LOCATION] === true || $extraDataConfig[$this::MERGE_EXTRA_DATA_OBJECT_LOCATION] === 'true')) {
+ return array_merge($object, $extraData);
+ }
+
+ return $extraData;
+ }
+
+
/**
* Synchronize a contract
*
@@ -190,20 +311,40 @@ private function getOriginId(Synchronization $synchronization, array $object): i
*/
public function synchronizeContract(SynchronizationContract $synchronizationContract, Synchronization $synchronization = null, array $object = [], ?bool $isTest = false): SynchronizationContract|Exception|array
{
+ $sourceConfig = $this->callService->applyConfigDot($synchronization->getSourceConfig());
+
+ // Check if extra data needs to be fetched
+ if (isset($sourceConfig[$this::EXTRA_DATA_CONFIGS_LOCATION]) === true) {
+ foreach ($sourceConfig[$this::EXTRA_DATA_CONFIGS_LOCATION] as $extraDataConfig) {
+ $object = array_merge($object, $this->fetchExtraDataForObject($synchronization, $extraDataConfig, $object));
+ }
+ }
+
+ // @TODO: This should be unset through pre-mapping
+ if(isset($object['d']['vti_x005f_dirlateststamp']) === true) {
+ unset($object['d']['vti_x005f_dirlateststamp']);
+ }
+
+
// Let create a source hash for the object
$originHash = md5(serialize($object));
- $synchronizationContract->setSourceLastChecked(new DateTime());
// Let's prevent pointless updates @todo account for omnidirectional sync, unless the config has been updated since last check then we do want to rebuild and check if the tagert object has changed
if ($originHash === $synchronizationContract->getOriginHash() && $synchronization->getUpdated() < $synchronizationContract->getSourceLastChecked()) {
// The object has not changed and the config has not been updated since last check
- // return $synchronizationContract;
- // @todo: somehow this always returns true, so we never do the updateTarget
+ return $synchronizationContract;
}
-
+
// The object has changed, oke let do mappig and bla die bla
$synchronizationContract->setOriginHash($originHash);
$synchronizationContract->setSourceLastChanged(new DateTime());
+ $synchronizationContract->setSourceLastChecked(new DateTime());
+
+ // Check if object adheres to conditions.
+ // Take note, JsonLogic::apply() returns a range of return types, so checking it with '=== false' or '!== true' does not work properly.
+ if ($synchronization->getConditions() !== [] && !JsonLogic::apply($synchronization->getConditions(), $object)) {
+ return $synchronizationContract;
+ }
// If no source target mapping is defined, use original object
if (empty($synchronization->getSourceTargetMapping()) === true) {
@@ -223,7 +364,6 @@ public function synchronizeContract(SynchronizationContract $synchronizationCont
}
}
-
// set the target hash
$targetHash = md5(serialize($targetObject));
$synchronizationContract->setTargetHash($targetHash);
@@ -373,7 +513,7 @@ public function getAllObjectsFromApi(Synchronization $synchronization, ?bool $is
$source = $this->sourceMapper->find(id: $synchronization->getSourceId());
// Lets get the source config
- $sourceConfig = $synchronization->getSourceConfig();
+ $sourceConfig = $this->callService->applyConfigDot($synchronization->getSourceConfig());
$endpoint = $sourceConfig['endpoint'] ?? '';
$headers = $sourceConfig['headers'] ?? [];
$query = $sourceConfig['query'] ?? [];
@@ -383,6 +523,7 @@ public function getAllObjectsFromApi(Synchronization $synchronization, ?bool $is
];
// Make the initial API call
+ // @TODO: method is now fixed to GET, but could end up in configuration.
$response = $this->callService->call(source: $source, endpoint: $endpoint, method: 'GET', config: $config)->getResponse();
$lastHash = md5($response['body']);
$body = json_decode($response['body'], true);
@@ -413,7 +554,7 @@ public function getAllObjectsFromApi(Synchronization $synchronization, ?bool $is
$objects = array_merge($objects, $this->getAllObjectsFromArray($body, $synchronization));
}
- if ($useNextEndpoint === false) {
+ if ($useNextEndpoint === false && $synchronization->usesPagination() === true) {
do {
$config = $this->getNextPage(config: $config, sourceConfig: $sourceConfig, currentPage: $currentPage);
$response = $this->callService->call(source: $source, endpoint: $endpoint, method: 'GET', config: $config)->getResponse();
diff --git a/lib/Settings/OpenConnectorAdmin.php b/lib/Settings/OpenConnectorAdmin.php
index 564615c6..4e35c3fa 100644
--- a/lib/Settings/OpenConnectorAdmin.php
+++ b/lib/Settings/OpenConnectorAdmin.php
@@ -7,37 +7,37 @@
use OCP\Settings\ISettings;
class OpenConnectorAdmin implements ISettings {
- private IL10N $l;
- private IConfig $config;
+ private IL10N $l;
+ private IConfig $config;
- public function __construct(IConfig $config, IL10N $l) {
- $this->config = $config;
- $this->l = $l;
- }
+ public function __construct(IConfig $config, IL10N $l) {
+ $this->config = $config;
+ $this->l = $l;
+ }
- /**
- * @return TemplateResponse
- */
- public function getForm() {
- $parameters = [
- 'mySetting' => $this->config->getSystemValue('open_connector_setting', true),
- ];
+ /**
+ * @return TemplateResponse
+ */
+ public function getForm() {
+ $parameters = [
+ 'mySetting' => $this->config->getSystemValue('open_connector_setting', true),
+ ];
- return new TemplateResponse('openconnector', 'settings/admin', $parameters, '');
- }
+ return new TemplateResponse('openconnector', 'settings/admin', $parameters, '');
+ }
- public function getSection() {
- return 'openconnector'; // Name of the previously created section.
- }
+ public function getSection() {
+ return 'openconnector'; // Name of the previously created section.
+ }
- /**
- * @return int whether the form should be rather on the top or bottom of
- * the admin section. The forms are arranged in ascending order of the
- * priority values. It is required to return a value between 0 and 100.
- *
- * E.g.: 70
- */
- public function getPriority() {
- return 10;
- }
-}
\ No newline at end of file
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ */
+ public function getPriority() {
+ return 10;
+ }
+}
diff --git a/lib/Twig/AuthenticationExtension.php b/lib/Twig/AuthenticationExtension.php
index fed2dfb5..a4cc8ec4 100644
--- a/lib/Twig/AuthenticationExtension.php
+++ b/lib/Twig/AuthenticationExtension.php
@@ -11,6 +11,7 @@ public function getFunctions(): array
{
return [
new TwigFunction(name: 'oauthToken', callable: [AuthenticationRuntime::class, 'oauthToken']),
+ new TwigFunction(name: 'decosToken', callable: [AuthenticationRuntime::class, 'decosToken']),
new TwigFunction(name: 'jwtToken', callable: [AuthenticationRuntime::class, 'jwtToken']),
];
//return parent::getFunctions(); // TODO: Change the autogenerated stub
diff --git a/lib/Twig/AuthenticationRuntime.php b/lib/Twig/AuthenticationRuntime.php
index 1655229e..7534bcf9 100644
--- a/lib/Twig/AuthenticationRuntime.php
+++ b/lib/Twig/AuthenticationRuntime.php
@@ -37,6 +37,25 @@ public function oauthToken(Source $source): string
);
}
+ /**
+ * Add a decos non-oauth token to the configuration.
+ *
+ * @param Source $source
+ * @return string
+ *
+ * @throws GuzzleException
+ */
+ public function decosToken(Source $source): string
+ {
+ $configuration = new Dot($source->getConfiguration(), true);
+
+ $authConfig = $configuration->get('authentication');
+
+ return $this->authService->fetchDecosToken(
+ configuration: $authConfig
+ );
+ }
+
/**
* Add a jwt token to the configuration.
*
diff --git a/package-lock.json b/package-lock.json
index 32efce01..637d0838 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "AGPL-3.0-or-later",
"dependencies": {
+ "@codemirror/lang-json": "^6.0.1",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@mdi/js": "^7.4.47",
@@ -32,6 +33,7 @@
"style-loader": "^3.3.3",
"vue": "^2.7.14",
"vue-apexcharts": "^1.6.2",
+ "vue-codemirror6": "^1.3.8",
"vue-loader": "^15.11.1 <16.0.0",
"vue-loading-overlay": "^3.4.3",
"vue-material-design-icons": "^5.3.0",
@@ -1800,6 +1802,91 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
+ "node_modules/@codemirror/autocomplete": {
+ "version": "6.18.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.3.tgz",
+ "integrity": "sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.17.0",
+ "@lezer/common": "^1.0.0"
+ },
+ "peerDependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/commands": {
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz",
+ "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.4.0",
+ "@codemirror/view": "^6.27.0",
+ "@lezer/common": "^1.1.0"
+ }
+ },
+ "node_modules/@codemirror/lang-json": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
+ "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@lezer/json": "^1.0.0"
+ }
+ },
+ "node_modules/@codemirror/language": {
+ "version": "6.10.4",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.4.tgz",
+ "integrity": "sha512-qjt7Wn/nxGuI278GYVlqE5V93Xn8ZQwzqZtgS0FaWr7K2yWgd5/FlBNqNi4jtUvBVvWJzAGfnggIlpyjTOaF4A==",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.23.0",
+ "@lezer/common": "^1.1.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0",
+ "style-mod": "^4.0.0"
+ }
+ },
+ "node_modules/@codemirror/lint": {
+ "version": "6.8.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.3.tgz",
+ "integrity": "sha512-GSGfKxCo867P7EX1k2LoCrjuQFeqVgPGRRsSl4J4c0KMkD+k1y6WYvTQkzv0iZ8JhLJDujEvlnMchv4CZQLh3Q==",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.35.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/search": {
+ "version": "6.5.8",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz",
+ "integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==",
+ "dependencies": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "node_modules/@codemirror/state": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
+ "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
+ },
+ "node_modules/@codemirror/view": {
+ "version": "6.35.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.35.0.tgz",
+ "integrity": "sha512-I0tYy63q5XkaWsJ8QRv5h6ves7kvtrBWjBcnf/bzohFJQc5c14a1AQRdE8QpPF9eMp5Mq2FMm59TCj1gDfE7kw==",
+ "dependencies": {
+ "@codemirror/state": "^6.4.0",
+ "style-mod": "^4.1.0",
+ "w3c-keyname": "^2.2.4"
+ }
+ },
"node_modules/@csstools/css-parser-algorithms": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz",
@@ -3056,6 +3143,37 @@
"dev": true,
"peer": true
},
+ "node_modules/@lezer/common": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
+ "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
+ },
+ "node_modules/@lezer/highlight": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
+ "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
+ "dependencies": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/json": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz",
+ "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "node_modules/@lezer/lr": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
+ "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+ "dependencies": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
"node_modules/@linusborg/vue-simple-portal": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@linusborg/vue-simple-portal/-/vue-simple-portal-0.1.5.tgz",
@@ -7240,6 +7358,20 @@
"node": ">= 0.12.0"
}
},
+ "node_modules/codemirror": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
+ "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+ "dependencies": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/commands": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/lint": "^6.0.0",
+ "@codemirror/search": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ }
+ },
"node_modules/collapse-white-space": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz",
@@ -7729,6 +7861,11 @@
"node": ">=8"
}
},
+ "node_modules/crelt": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
+ },
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -19357,6 +19494,11 @@
"webpack": "^5.0.0"
}
},
+ "node_modules/style-mod": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
+ "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
+ },
"node_modules/style-search": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz",
@@ -21293,6 +21435,52 @@
"apexcharts": "^3.26.0"
}
},
+ "node_modules/vue-codemirror6": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/vue-codemirror6/-/vue-codemirror6-1.3.8.tgz",
+ "integrity": "sha512-pCOzKzBBSFKi/SjUg+XGranV1vt+8S22z56BES/OeZtmyuj2M0CE0aczYS8qbTWNnKcuJcI5FRDHzVXy2v2Htg==",
+ "dependencies": {
+ "@codemirror/commands": "^6.7.1",
+ "@codemirror/language": "^6.10.3",
+ "@codemirror/lint": "^6.8.3",
+ "@codemirror/state": "^6.4.1",
+ "@codemirror/view": "^6.35.0",
+ "codemirror": "^6.0.1",
+ "style-mod": "^4.1.2",
+ "vue-demi": "latest"
+ },
+ "engines": {
+ "pnpm": ">=9.14.2"
+ },
+ "peerDependencies": {
+ "vue": "^2.7.14 || ^3.4"
+ }
+ },
+ "node_modules/vue-codemirror6/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
"node_modules/vue-color": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/vue-color/-/vue-color-2.8.1.tgz",
@@ -21526,6 +21714,11 @@
"vue": "^2.5.0"
}
},
+ "node_modules/w3c-keyname": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
+ },
"node_modules/w3c-xmlserializer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
@@ -23395,6 +23588,85 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true
},
+ "@codemirror/autocomplete": {
+ "version": "6.18.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.3.tgz",
+ "integrity": "sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==",
+ "requires": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.17.0",
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "@codemirror/commands": {
+ "version": "6.7.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz",
+ "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==",
+ "requires": {
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/state": "^6.4.0",
+ "@codemirror/view": "^6.27.0",
+ "@lezer/common": "^1.1.0"
+ }
+ },
+ "@codemirror/lang-json": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
+ "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
+ "requires": {
+ "@codemirror/language": "^6.0.0",
+ "@lezer/json": "^1.0.0"
+ }
+ },
+ "@codemirror/language": {
+ "version": "6.10.4",
+ "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.4.tgz",
+ "integrity": "sha512-qjt7Wn/nxGuI278GYVlqE5V93Xn8ZQwzqZtgS0FaWr7K2yWgd5/FlBNqNi4jtUvBVvWJzAGfnggIlpyjTOaF4A==",
+ "requires": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.23.0",
+ "@lezer/common": "^1.1.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0",
+ "style-mod": "^4.0.0"
+ }
+ },
+ "@codemirror/lint": {
+ "version": "6.8.3",
+ "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.3.tgz",
+ "integrity": "sha512-GSGfKxCo867P7EX1k2LoCrjuQFeqVgPGRRsSl4J4c0KMkD+k1y6WYvTQkzv0iZ8JhLJDujEvlnMchv4CZQLh3Q==",
+ "requires": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.35.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "@codemirror/search": {
+ "version": "6.5.8",
+ "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz",
+ "integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==",
+ "requires": {
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0",
+ "crelt": "^1.0.5"
+ }
+ },
+ "@codemirror/state": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
+ "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
+ },
+ "@codemirror/view": {
+ "version": "6.35.0",
+ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.35.0.tgz",
+ "integrity": "sha512-I0tYy63q5XkaWsJ8QRv5h6ves7kvtrBWjBcnf/bzohFJQc5c14a1AQRdE8QpPF9eMp5Mq2FMm59TCj1gDfE7kw==",
+ "requires": {
+ "@codemirror/state": "^6.4.0",
+ "style-mod": "^4.1.0",
+ "w3c-keyname": "^2.2.4"
+ }
+ },
"@csstools/css-parser-algorithms": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz",
@@ -24299,6 +24571,37 @@
"dev": true,
"peer": true
},
+ "@lezer/common": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
+ "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
+ },
+ "@lezer/highlight": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
+ "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
+ "requires": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
+ "@lezer/json": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz",
+ "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==",
+ "requires": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
+ "@lezer/lr": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
+ "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
+ "requires": {
+ "@lezer/common": "^1.0.0"
+ }
+ },
"@linusborg/vue-simple-portal": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@linusborg/vue-simple-portal/-/vue-simple-portal-0.1.5.tgz",
@@ -27512,6 +27815,20 @@
"integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
"dev": true
},
+ "codemirror": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
+ "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
+ "requires": {
+ "@codemirror/autocomplete": "^6.0.0",
+ "@codemirror/commands": "^6.0.0",
+ "@codemirror/language": "^6.0.0",
+ "@codemirror/lint": "^6.0.0",
+ "@codemirror/search": "^6.0.0",
+ "@codemirror/state": "^6.0.0",
+ "@codemirror/view": "^6.0.0"
+ }
+ },
"collapse-white-space": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz",
@@ -27913,6 +28230,11 @@
}
}
},
+ "crelt": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
+ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
+ },
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -36389,6 +36711,11 @@
"integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==",
"requires": {}
},
+ "style-mod": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
+ "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
+ },
"style-search": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz",
@@ -37779,6 +38106,29 @@
"integrity": "sha512-9HS3scJwWgKjmkcWIf+ndNDR0WytUJD8Ju0V2ZYcjYtlTLwJAf2SKUlBZaQTkDmwje/zMgulvZRi+MXmi+WkKw==",
"requires": {}
},
+ "vue-codemirror6": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/vue-codemirror6/-/vue-codemirror6-1.3.8.tgz",
+ "integrity": "sha512-pCOzKzBBSFKi/SjUg+XGranV1vt+8S22z56BES/OeZtmyuj2M0CE0aczYS8qbTWNnKcuJcI5FRDHzVXy2v2Htg==",
+ "requires": {
+ "@codemirror/commands": "^6.7.1",
+ "@codemirror/language": "^6.10.3",
+ "@codemirror/lint": "^6.8.3",
+ "@codemirror/state": "^6.4.1",
+ "@codemirror/view": "^6.35.0",
+ "codemirror": "^6.0.1",
+ "style-mod": "^4.1.2",
+ "vue-demi": "latest"
+ },
+ "dependencies": {
+ "vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "requires": {}
+ }
+ }
+ },
"vue-color": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/vue-color/-/vue-color-2.8.1.tgz",
@@ -37948,6 +38298,11 @@
"date-format-parse": "^0.2.7"
}
},
+ "w3c-keyname": {
+ "version": "2.2.8",
+ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
+ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
+ },
"w3c-xmlserializer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
diff --git a/package.json b/package.json
index 8c004797..4354ebec 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"extends @nextcloud/browserslist-config"
],
"dependencies": {
+ "@codemirror/lang-json": "^6.0.1",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@mdi/js": "^7.4.47",
@@ -43,6 +44,7 @@
"style-loader": "^3.3.3",
"vue": "^2.7.14",
"vue-apexcharts": "^1.6.2",
+ "vue-codemirror6": "^1.3.8",
"vue-loader": "^15.11.1 <16.0.0",
"vue-loading-overlay": "^3.4.3",
"vue-material-design-icons": "^5.3.0",
diff --git a/src/modals/Job/EditJob.vue b/src/modals/Job/EditJob.vue
index a16c8cde..6713307b 100644
--- a/src/modals/Job/EditJob.vue
+++ b/src/modals/Job/EditJob.vue
@@ -28,10 +28,12 @@ import { jobStore, navigationStore } from '../../store/store.js'
label="Description"
:value.sync="jobItem.description" />
-
+
option.label === jobStore.jobItem.jobClass)
+ activeJobClass && (this.classOptions.value = activeJobClass)
+
this.jobItem = {
...jobStore.jobItem,
name: jobStore.jobItem.name || '',
@@ -221,11 +234,15 @@ export default {
errorRetention: '86400',
userId: '',
}
+ this.classOptions.value = this.classOptions.options[0]
},
async editJob() {
this.loading = true
try {
- await jobStore.saveJob({ ...this.jobItem })
+ await jobStore.saveJob({
+ ...this.jobItem,
+ jobClass: this.classOptions.value.label,
+ })
// Close modal or show success message
this.success = true
this.loading = false
@@ -246,4 +263,8 @@ export default {
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
+
+.jobClassSelect {
+ width: 100%;
+}
diff --git a/src/modals/Synchronization/TestSynchronization.vue b/src/modals/Synchronization/TestSynchronization.vue
index 7e83dde4..e3c8e40e 100644
--- a/src/modals/Synchronization/TestSynchronization.vue
+++ b/src/modals/Synchronization/TestSynchronization.vue
@@ -1,5 +1,6 @@
@@ -35,13 +36,47 @@ import { synchronizationStore, navigationStore } from '../../store/store.js'
-
-
Status: {{ response?.statusText }} ({{ response?.status }})
-
Response time: {{ response?.responseTime ?? 'Onbekend' }} (Milliseconds)
-
Size: {{ response?.size ?? 'Onbekend' }} (Bytes)
-
Remote IP: {{ response?.remoteIp ?? 'Onbekend' }}
-
Headers: {{ response?.headers }}
-
Body: {{ response?.body }}
+
+
+
+ Status: |
+ {{ response?.statusText }} ({{ response?.status }}) |
+
+
+ Response time: |
+ {{ response?.responseTime ?? 'Onbekend' }} (Milliseconds) |
+
+
+ Size: |
+ {{ response?.size ?? 'Onbekend' }} (Bytes) |
+
+
+ Remote IP: |
+ {{ response?.remoteIp ?? 'Onbekend' }} |
+
+
+ Headers: |
+
+
+
+ {{ header[0] }}: |
+ {{ header[1] }} |
+
+
+ |
+
+
+ Body: |
+
+
+ |
+
+
@@ -54,6 +89,8 @@ import {
NcLoadingIcon,
NcNoteCard,
} from '@nextcloud/vue'
+import CodeMirror from 'vue-codemirror6'
+import { json, jsonParseLinter } from '@codemirror/lang-json'
export default {
name: 'TestSynchronization',
@@ -61,10 +98,12 @@ export default {
NcModal,
NcLoadingIcon,
NcNoteCard,
+ CodeMirror,
},
data() {
return {
response: null,
+ responseBody: '',
success: null,
loading: false,
error: false,
@@ -84,12 +123,14 @@ export default {
this.error = false
synchronizationStore.testSynchronization()
- .then(({ response }) => {
+ .then(({ response, data }) => {
this.response = response
+ this.responseBody = JSON.stringify({ ...data }, null, 2)
this.success = response.ok
}).catch((error) => {
this.success = false
this.error = error.message || 'An error occurred while testing the synchronization'
+ console.error(error)
}).finally(() => {
this.loading = false
})
@@ -103,4 +144,69 @@ export default {
grid-template-columns: 1fr 1fr;
gap: 5px;
}
+
+.detailTable {
+ overflow-x: auto;
+}
+
+.detailTable > table {
+ width: 100%;
+ border: 1px solid grey;
+ border-collapse: collapse;
+}
+
+.detailTable > table > tr > td,
+.detailTable > table > tr > th {
+ border: 1px solid grey;
+ padding: 5px;
+}
+
+
+
diff --git a/src/services/getTheme.js b/src/services/getTheme.js
index a94e2c25..332e44a5 100644
--- a/src/services/getTheme.js
+++ b/src/services/getTheme.js
@@ -1,9 +1,12 @@
export const getTheme = () => {
if (document.body.hasAttribute('data-theme-light')) {
return 'light'
+ } else if (document.body.hasAttribute('data-theme-dark')) {
+ return 'dark'
+ } else if (window.matchMedia('(prefers-color-scheme: light)').matches) {
+ return 'light'
+ } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ return 'dark'
}
- if (document.body.hasAttribute('data-theme-default')) {
- return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
- }
- return 'dark'
+ return 'light'
}
diff --git a/src/views/Job/JobDetails.vue b/src/views/Job/JobDetails.vue
index 8797f680..fe2e8731 100644
--- a/src/views/Job/JobDetails.vue
+++ b/src/views/Job/JobDetails.vue
@@ -53,7 +53,7 @@ import { jobStore, navigationStore, logStore } from '../../store/store.js'
id:
-
{{ jobStore.jobItem.uuid }}
+
{{ jobStore.jobItem.id }}
Status:
diff --git a/src/views/Source/SourceDetails.vue b/src/views/Source/SourceDetails.vue
index cbaebb50..fcb53be2 100644
--- a/src/views/Source/SourceDetails.vue
+++ b/src/views/Source/SourceDetails.vue
@@ -147,7 +147,7 @@ import { sourceStore, navigationStore, logStore } from '../../store/store.js'