diff --git a/.gitignore b/.gitignore index 4c8a4e9..134b769 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ vendor/**/composer.lock /vue/dist/*.common.js /vue/dist/*.map /vue/dist/*.development.* +/vue/node_modules/ /tmp/specs/* !/tmp/specs/.gitkeep /tmp/annotations/* diff --git a/Annotations/AnnotationGenerator.php b/Annotations/AnnotationGenerator.php index beca23c..ee04cc3 100644 --- a/Annotations/AnnotationGenerator.php +++ b/Annotations/AnnotationGenerator.php @@ -964,8 +964,8 @@ protected function getExampleIfAvailable(string $url, bool $useLocalToken = fals $httpMethod = 'GET' ); } catch (\Throwable $e) { - // Add a little bit more context for troubleshooting the failed request - throw new \Exception('Error getting example from URL: ' . $url . PHP_EOL . $e, 0, $e); + // Example lookups are best-effort. Timeouts and other transport failures should not abort spec generation. + return ''; } // If the example didn't load or resulted in an error, simply return an empty string diff --git a/Controller.php b/Controller.php new file mode 100644 index 0000000..f50b700 --- /dev/null +++ b/Controller.php @@ -0,0 +1,28 @@ +setBasicVariablesView($view); + + return $view->render(); + } +} diff --git a/Menu.php b/Menu.php new file mode 100644 index 0000000..75198f4 --- /dev/null +++ b/Menu.php @@ -0,0 +1,29 @@ +addPlatformItem( + 'OpenApiDocs_SwaggerApi', + $this->urlForAction('swagger'), + 30 + ); + } +} diff --git a/OpenApiDocs.php b/OpenApiDocs.php index 804c677..52dbd70 100644 --- a/OpenApiDocs.php +++ b/OpenApiDocs.php @@ -22,6 +22,25 @@ class OpenApiDocs extends \Piwik\Plugin public function registerEvents() { - return []; + return [ + 'Translate.getClientSideTranslationKeys' => 'getClientSideTranslationKeys', + ]; + } + + public function getClientSideTranslationKeys(&$translationKeys): void + { + $translationKeys[] = 'CoreHome_LearnMoreFullStop'; + $translationKeys[] = 'OpenApiDocs_ReportingApiMoreInformation'; + $translationKeys[] = 'OpenApiDocs_ReportingApiReference'; + $translationKeys[] = 'OpenApiDocs_ReportingApiSummary'; + $translationKeys[] = 'OpenApiDocs_SwaggerApi'; + $translationKeys[] = 'OpenApiDocs_SwaggerPagePluginEmpty'; + $translationKeys[] = 'OpenApiDocs_SwaggerPageRequestFailed'; + $translationKeys[] = 'OpenApiDocs_SwaggerPageSpecLoadFailed'; + $translationKeys[] = 'OpenApiDocs_SwaggerPageSearchNoResults'; + $translationKeys[] = 'OpenApiDocs_SwaggerPageSearchPlaceholder'; + $translationKeys[] = 'OpenApiDocs_UserAuthentication'; + $translationKeys[] = 'OpenApiDocs_UserAuthenticationManageTokens'; + $translationKeys[] = 'OpenApiDocs_UserAuthenticationUsingTokenAuth'; } } diff --git a/README.md b/README.md index 66342ac..8a27d4a 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,16 @@ Allow generating OpenAPI documentation for the Matomo public APIs. +## Frontend assets +Swagger UI is managed via npm inside [vue/package.json](/vue/package.json). The plugin runtime does not load Swagger UI from `node_modules`; instead, the needed distributable files are synced into `vue/lib/swagger-ui/`. + +Typical workflow: +- Run `npm install` in `plugins/OpenApiDocs/vue`. +- Run `npm run sync-swagger-ui` in `plugins/OpenApiDocs/vue` after adding or updating `swagger-ui-dist`. +- Run `ddev matomo:console vue:build OpenApiDocs` after changing Vue source. + +The plugin-specific Swagger overrides live in [vue/src/SwaggerPage/swagger-ui-overrides.css](/vue/src/SwaggerPage/swagger-ui-overrides.css). + ## Dependencies This plugin had its vendored dependencies scoped using [matomo scoper](https://github.com/matomo-org/matomo-scoper). This means that composer packages are prefixed so that they won't conflict with the same libraries used by other plugins. If you need to update a dependency, you should be able to run `composer install` to populate the vendor directory and then follow the [instructions for scoping a plugin](https://github.com/matomo-org/matomo-scoper#how-to-scope-a-matomo-plugin). Since the scoper.inc.php file already exists, it will hopefully be as simple as running the scoper for this plugin. Once that's done, you'll also need to make some of the dependencies compatible with Matomo's minimum supported version of PHP. @@ -30,4 +40,4 @@ return static function (RectorConfig $rectorConfig): void { ``` With all that in place, you should be able to run Rector like so: `vendor/bin/rector process {path_to_this_plugin/vendor/prefixed} --config={path_to_config_file}` -> **_NOTE:_** For Matomo developers, there's an internal DevPluginCommands plugin with a command that handles scoping and running Rector. See the SearchEngineKeywordsPerformance plugin's README.md for more details. \ No newline at end of file +> **_NOTE:_** For Matomo developers, there's an internal DevPluginCommands plugin with a command that handles scoping and running Rector. See the SearchEngineKeywordsPerformance plugin's README.md for more details. diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..ce030eb --- /dev/null +++ b/lang/en.json @@ -0,0 +1,16 @@ +{ + "OpenApiDocs": { + "ReportingApiMoreInformation": "More info about the Matomo APIs available in %1$sIntroduction to Matomo API%2$s and the %3$sMatomo API Reference%4$s.", + "ReportingApiReference": "Reporting API Reference", + "ReportingApiSummary": "All the data in Matomo is available through simple APIs.", + "SwaggerApi": "Swagger API", + "SwaggerPagePluginEmpty": "No plugins are configured in the OpenApiDocs whitelist.", + "SwaggerPageRequestFailed": "The plugin whitelist could not be loaded.", + "SwaggerPageSpecLoadFailed": "The OpenAPI spec could not be loaded for this plugin.", + "SwaggerPageSearchNoResults": "No plugins match your search.", + "SwaggerPageSearchPlaceholder": "Search by plugin name", + "UserAuthentication": "User authentication", + "UserAuthenticationManageTokens": "You can manage your authentication tokens on your security page.", + "UserAuthenticationUsingTokenAuth": "If you want to request data within a script, a crontab, etc. you need to add the '%3$s' URL parameter to the API calls for URLs that require authentication." + } +} diff --git a/templates/swagger.twig b/templates/swagger.twig new file mode 100644 index 0000000..606f585 --- /dev/null +++ b/templates/swagger.twig @@ -0,0 +1,17 @@ +{% extends 'admin.twig' %} + +{% block head %} + {{ parent() }} + + + +{% endblock %} + +{% set title %}{{ 'OpenApiDocs_SwaggerApi'|translate }}{% endset %} + +{% block content %} +
+{% endblock %} diff --git a/tests/Integration/ControllerTest.php b/tests/Integration/ControllerTest.php new file mode 100644 index 0000000..957ef27 --- /dev/null +++ b/tests/Integration/ControllerTest.php @@ -0,0 +1,115 @@ + + */ + private $backupGet; + + /** + * @var arraytoken_auth');
+ const learnMore = Object(external_CoreHome_["translate"])('CoreHome_LearnMoreFullStop', Object(external_CoreHome_["externalLink"])('https://developer.matomo.org/api-reference/reporting-api#authenticate-to-the-api-via-token_auth-parameter'), '');
+ return `${usingTokenAuth} ${learnMore}`;
+ },
+ userSecurityUrl() {
+ return `?${external_CoreHome_["MatomoUrl"].stringify(Object.assign(Object.assign({}, external_CoreHome_["MatomoUrl"].urlParsed.value), {}, {
+ module: 'UsersManager',
+ action: 'userSecurity'
+ }))}#/#authtokens`;
+ },
+ filteredPlugins() {
+ const searchTerm = this.searchTerm.trim().toLowerCase();
+ if (!searchTerm) {
+ return this.plugins;
+ }
+ return this.plugins.filter(plugin => plugin.toLowerCase().includes(searchTerm));
+ },
+ filteredPluginSet() {
+ return new Set(this.filteredPlugins);
+ }
+ },
+ data() {
+ return {
+ expandedPluginName: null,
+ isLoading: false,
+ loadError: null,
+ plugins: [],
+ pluginSpecs: {},
+ searchTerm: ''
+ };
+ },
+ created() {
+ this.fetchPlugins();
+ },
+ watch: {
+ searchTerm(value) {
+ if (this.expandedPluginName && !this.matchesSearch(this.expandedPluginName, value)) {
+ this.expandedPluginName = null;
+ }
+ }
+ },
+ methods: {
+ matchesSearch(plugin, searchTerm) {
+ const normalizedSearchTerm = (searchTerm !== null && searchTerm !== void 0 ? searchTerm : this.searchTerm).trim().toLowerCase();
+ return plugin.toLowerCase().includes(normalizedSearchTerm);
+ },
+ forceReflow(element) {
+ element.getBoundingClientRect();
+ },
+ getPluginBodyTransitionDuration(height) {
+ return Math.min(400, Math.max(180, Math.round(height / 4)));
+ },
+ resetPluginBodyTransitionStyles(element) {
+ const htmlElement = element;
+ htmlElement.style.height = '';
+ htmlElement.style.transitionDuration = '';
+ htmlElement.style.overflow = '';
+ },
+ setPluginBodyTransitionState(element, height) {
+ element.style.height = height;
+ element.style.overflow = 'hidden';
+ },
+ transitionPluginBody(element, startHeight, endHeight) {
+ this.setPluginBodyTransitionState(element, startHeight);
+ element.style.transitionDuration = `${this.getPluginBodyTransitionDuration(element.scrollHeight)}ms`;
+ this.forceReflow(element);
+ element.style.height = endHeight;
+ },
+ onPluginBodyBeforeEnter(element) {
+ this.setPluginBodyTransitionState(element, '0');
+ },
+ onPluginBodyEnter(element) {
+ const htmlElement = element;
+ this.transitionPluginBody(htmlElement, '0', `${htmlElement.scrollHeight}px`);
+ },
+ onPluginBodyBeforeLeave(element) {
+ const htmlElement = element;
+ this.setPluginBodyTransitionState(htmlElement, `${htmlElement.scrollHeight}px`);
+ },
+ onPluginBodyLeave(element) {
+ const htmlElement = element;
+ this.transitionPluginBody(htmlElement, `${htmlElement.scrollHeight}px`, '0');
+ },
+ async fetchPlugins() {
+ this.expandedPluginName = null;
+ this.isLoading = true;
+ this.loadError = null;
+ this.plugins = [];
+ this.pluginSpecs = {};
+ this.searchTerm = '';
+ try {
+ const plugins = await external_CoreHome_["AjaxHelper"].fetch({
+ method: 'OpenApiDocs.getAllowedPlugins'
+ }, {
+ createErrorNotification: false
+ });
+ this.plugins = [...plugins].sort((left, right) => left.localeCompare(right));
+ } catch (_unused) {
+ this.loadError = Object(external_CoreHome_["translate"])('OpenApiDocs_SwaggerPageRequestFailed');
+ } finally {
+ this.isLoading = false;
+ }
+ },
+ createPluginSpecState() {
+ return {
+ loadError: null,
+ request: null,
+ spec: null,
+ status: 'idle'
+ };
+ },
+ getPluginSpecState(plugin) {
+ if (!this.pluginSpecs[plugin]) {
+ this.pluginSpecs[plugin] = this.createPluginSpecState();
+ }
+ return this.pluginSpecs[plugin];
+ },
+ async prefetchPluginSpec(plugin, forceReload = false) {
+ const state = this.getPluginSpecState(plugin);
+ if (!forceReload) {
+ if (state.status === 'loaded') {
+ return state.spec;
+ }
+ if (state.request) {
+ return state.request;
+ }
+ }
+ state.status = 'loading';
+ state.loadError = null;
+ state.request = (async () => {
+ try {
+ const spec = await external_CoreHome_["AjaxHelper"].fetch({
+ method: 'OpenApiDocs.getOpenApiSpec',
+ pluginName: plugin,
+ format: 'json'
+ }, {
+ createErrorNotification: false
+ });
+ state.spec = spec;
+ state.status = 'loaded';
+ return spec;
+ } catch (_unused2) {
+ state.spec = null;
+ state.status = 'error';
+ state.loadError = Object(external_CoreHome_["translate"])('OpenApiDocs_SwaggerPageSpecLoadFailed');
+ return null;
+ } finally {
+ state.request = null;
+ }
+ })();
+ return state.request;
+ },
+ togglePlugin(plugin) {
+ if (this.expandedPluginName === plugin) {
+ this.expandedPluginName = null;
+ return;
+ }
+ this.expandedPluginName = plugin;
+ const state = this.getPluginSpecState(plugin);
+ if (state.status !== 'loaded') {
+ this.prefetchPluginSpec(plugin, state.status === 'error');
+ }
+ }
+ }
+}));
+// CONCATENATED MODULE: ./plugins/OpenApiDocs/vue/src/SwaggerPage/SwaggerPage.vue?vue&type=script&lang=ts
+
+// EXTERNAL MODULE: ./plugins/OpenApiDocs/vue/src/SwaggerPage/SwaggerPage.vue?vue&type=style&index=0&id=7d1e8f2e&scoped=true&lang=css
+var SwaggerPagevue_type_style_index_0_id_7d1e8f2e_scoped_true_lang_css = __webpack_require__("0edb");
+
+// CONCATENATED MODULE: ./plugins/OpenApiDocs/vue/src/SwaggerPage/SwaggerPage.vue
+
+
+
+
+
+SwaggerPagevue_type_script_lang_ts.render = render
+SwaggerPagevue_type_script_lang_ts.__scopeId = "data-v-7d1e8f2e"
+
+/* harmony default export */ var SwaggerPage = (SwaggerPagevue_type_script_lang_ts);
+// CONCATENATED MODULE: ./plugins/OpenApiDocs/vue/src/index.ts
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+// CONCATENATED MODULE: ./node_modules/@vue/cli-service/lib/commands/build/entry-lib-no-default.js
+
+
+
+
+/***/ })
+
+/******/ });
+});
+//# sourceMappingURL=OpenApiDocs.umd.js.map
\ No newline at end of file
diff --git a/vue/dist/OpenApiDocs.umd.min.js b/vue/dist/OpenApiDocs.umd.min.js
new file mode 100644
index 0000000..0cd877b
--- /dev/null
+++ b/vue/dist/OpenApiDocs.umd.min.js
@@ -0,0 +1,8 @@
+(function(e,t){"object"===typeof exports&&"object"===typeof module?module.exports=t(require("CoreHome"),require("vue")):"function"===typeof define&&define.amd?define(["CoreHome"],t):"object"===typeof exports?exports["OpenApiDocs"]=t(require("CoreHome"),require("vue")):e["OpenApiDocs"]=t(e["CoreHome"],e["Vue"])})("undefined"!==typeof self?self:this,(function(e,t){return function(e){var t={};function n(o){if(t[o])return t[o].exports;var r=t[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"===typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)n.d(o,r,function(t){return e[t]}.bind(null,r));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e["default"]}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="plugins/OpenApiDocs/vue/dist/",n(n.s="fae3")}({"003e":function(e,t,n){},"0edb":function(e,t,n){"use strict";n("cd14")},"19dc":function(t,n){t.exports=e},"848d":function(e,t,n){"use strict";n("003e")},"8bbf":function(e,n){e.exports=t},cd14:function(e,t,n){},fae3:function(e,t,n){"use strict";if(n.r(t),n.d(t,"SwaggerPage",(function(){return M})),"undefined"!==typeof window){var o=window.document.currentScript,r=o&&o.src.match(/(.+\/)[^/]+\.js(\?.*)?$/);r&&(n.p=r[1])}var i=n("8bbf");const a=e=>(Object(i["pushScopeId"])("data-v-7d1e8f2e"),e=e(),Object(i["popScopeId"])(),e),s={class:"page"},c=["innerHTML"],l=["innerHTML"],u=["href"],p={key:1},d={key:2},g={class:"searchBar"},m=a(()=>Object(i["createElementVNode"])("span",{class:"searchIcon icon-search"},null,-1)),h=["placeholder"],y={key:0,class:"emptyText"},b={key:1,class:"pluginList"},f=["onMouseenter"],O=["aria-expanded","onFocus","onClick"],S={class:"pluginHeader"},j={class:"pluginName"},v={class:"card-content pluginBody"};function w(e,t,n,o,r,a){const w=Object(i["resolveComponent"])("ContentBlock"),P=Object(i["resolveComponent"])("ActivityIndicator"),k=Object(i["resolveComponent"])("Alert"),C=Object(i["resolveComponent"])("SwaggerUiPanel"),E=Object(i["resolveDirective"])("content-intro");return Object(i["openBlock"])(),Object(i["createElementBlock"])("div",s,[Object(i["withDirectives"])((Object(i["openBlock"])(),Object(i["createElementBlock"])("div",null,[Object(i["createElementVNode"])("h2",null,Object(i["toDisplayString"])(e.translate("OpenApiDocs_SwaggerApi")),1)])),[[E]]),Object(i["createVNode"])(w,{"content-title":e.translate("OpenApiDocs_ReportingApiReference")},{default:Object(i["withCtx"])(()=>[Object(i["createElementVNode"])("p",null,Object(i["toDisplayString"])(e.translate("OpenApiDocs_ReportingApiSummary")),1),Object(i["createElementVNode"])("p",{innerHTML:e.$sanitize(e.reportingApiMoreInformation)},null,8,c)]),_:1},8,["content-title"]),Object(i["createVNode"])(w,{"content-title":e.translate("OpenApiDocs_UserAuthentication")},{default:Object(i["withCtx"])(()=>[Object(i["createElementVNode"])("p",{innerHTML:e.$sanitize(e.userAuthenticationHelp)},null,8,l),Object(i["createElementVNode"])("p",null,[Object(i["createElementVNode"])("a",{href:e.userSecurityUrl},Object(i["toDisplayString"])(e.translate("OpenApiDocs_UserAuthenticationManageTokens")),9,u)])]),_:1},8,["content-title"]),Object(i["createVNode"])(w,null,{default:Object(i["withCtx"])(()=>[Object(i["createVNode"])(P,{loading:e.isLoading},null,8,["loading"]),e.loadError?(Object(i["openBlock"])(),Object(i["createBlock"])(k,{key:0,severity:"danger"},{default:Object(i["withCtx"])(()=>[Object(i["createTextVNode"])(Object(i["toDisplayString"])(e.loadError),1)]),_:1})):e.isLoading||0!==e.plugins.length?e.isLoading?Object(i["createCommentVNode"])("",!0):(Object(i["openBlock"])(),Object(i["createElementBlock"])("div",d,[Object(i["createElementVNode"])("div",g,[m,Object(i["withDirectives"])(Object(i["createElementVNode"])("input",{"onUpdate:modelValue":t[0]||(t[0]=t=>e.searchTerm=t),type:"text",class:"searchInput browser-default",placeholder:e.translate("OpenApiDocs_SwaggerPageSearchPlaceholder")},null,8,h),[[i["vModelText"],e.searchTerm]])]),0===e.filteredPlugins.length?(Object(i["openBlock"])(),Object(i["createElementBlock"])("p",y,Object(i["toDisplayString"])(e.translate("OpenApiDocs_SwaggerPageSearchNoResults")),1)):(Object(i["openBlock"])(),Object(i["createElementBlock"])("div",b,[(Object(i["openBlock"])(!0),Object(i["createElementBlock"])(i["Fragment"],null,Object(i["renderList"])(e.plugins,t=>Object(i["withDirectives"])((Object(i["openBlock"])(),Object(i["createElementBlock"])("div",{key:t,class:Object(i["normalizeClass"])(["card","pluginCard",{"pluginCard--expanded":e.expandedPluginName===t}]),onMouseenter:n=>e.prefetchPluginSpec(t)},[Object(i["createElementVNode"])("button",{type:"button",class:"pluginToggle","aria-expanded":e.expandedPluginName===t?"true":"false",onFocus:n=>e.prefetchPluginSpec(t),onClick:n=>e.togglePlugin(t)},[Object(i["createElementVNode"])("span",S,[Object(i["createElementVNode"])("span",{class:Object(i["normalizeClass"])(["pluginChevron",e.expandedPluginName===t?"icon-chevron-down":"icon-chevron-right"])},null,2),Object(i["createElementVNode"])("span",j,Object(i["toDisplayString"])(t),1)])],40,O),Object(i["createVNode"])(i["Transition"],{name:"pluginBodyTransition",onBeforeEnter:e.onPluginBodyBeforeEnter,onEnter:e.onPluginBodyEnter,onBeforeLeave:e.onPluginBodyBeforeLeave,onLeave:e.onPluginBodyLeave,onAfterEnter:e.resetPluginBodyTransitionStyles,onAfterLeave:e.resetPluginBodyTransitionStyles},{default:Object(i["withCtx"])(()=>[Object(i["withDirectives"])(Object(i["createElementVNode"])("div",v,[Object(i["createVNode"])(C,{plugin:t,"piwik-url":e.piwikUrl,spec:e.getPluginSpecState(t).spec,"is-loading":"loading"===e.getPluginSpecState(t).status,"spec-load-error":e.getPluginSpecState(t).loadError},null,8,["plugin","piwik-url","spec","is-loading","spec-load-error"])],512),[[i["vShow"],e.expandedPluginName===t]])]),_:2},1032,["onBeforeEnter","onEnter","onBeforeLeave","onLeave","onAfterEnter","onAfterLeave"])],42,f)),[[i["vShow"],e.filteredPluginSet.has(t)]])),128))]))])):(Object(i["openBlock"])(),Object(i["createElementBlock"])("p",p,Object(i["toDisplayString"])(e.translate("OpenApiDocs_SwaggerPagePluginEmpty")),1))]),_:1})])}var P=n("19dc");const k={key:0,class:"swaggerLoader"},C=["id"];function E(e,t,n,o,r,a){const s=Object(i["resolveComponent"])("ActivityIndicator"),c=Object(i["resolveComponent"])("Alert");return Object(i["openBlock"])(),Object(i["createElementBlock"])(i["Fragment"],null,[!e.isLoading||e.spec||e.displayError?Object(i["createCommentVNode"])("",!0):(Object(i["openBlock"])(),Object(i["createElementBlock"])("div",k,[Object(i["createVNode"])(s,{loading:!0})])),e.displayError?(Object(i["openBlock"])(),Object(i["createBlock"])(c,{key:1,severity:"danger"},{default:Object(i["withCtx"])(()=>[Object(i["createTextVNode"])(Object(i["toDisplayString"])(e.displayError),1)]),_:1})):Object(i["createCommentVNode"])("",!0),Object(i["createElementVNode"])("div",{id:e.swaggerContainerId,class:Object(i["normalizeClass"])(["swaggerMount",{"swaggerMount--ready":e.isReady}])},null,10,C)],64)}const B="__matomoActiveCopySuccessState",A="__matomoSummaryPathClickHandlerAttached",L="/index.php?module=API&method=",x=".opblock-tag, .opblock-summary, .expand-operation, .opblock-summary-control",T='',N='';var D=Object(i["defineComponent"])({components:{ActivityIndicator:P["ActivityIndicator"],Alert:P["Alert"]},props:{plugin:{type:String,required:!0},piwikUrl:{type:String,default:null},spec:{type:Object,default:null},isLoading:{type:Boolean,default:!1},specLoadError:{type:String,default:null}},data(){return{isReady:!1,loadError:null}},computed:{displayError(){return this.specLoadError||this.loadError},swaggerContainerId(){return"swagger-ui-"+this.plugin}},watch:{spec:{immediate:!0,handler(e){e?this.renderSwaggerUi():this.resetSwaggerUi()}}},beforeUnmount(){const e=this.getSwaggerRoot();e&&this.clearCopySuccessState(e)},methods:{getSwaggerRoot(){return document.getElementById(this.swaggerContainerId)},getSpecWithCurrentInstanceUrl(e){return this.piwikUrl?Object.assign(Object.assign({},e),{},{servers:[{url:this.piwikUrl}]}):e},shortenSummaryPaths(e){const t=e.querySelectorAll(".opblock-summary-path");t.forEach(e=>{const t=e.getAttribute("data-path");t&&t.startsWith(L)&&(e.textContent=t.substring(L.length),e.setAttribute("title",t))})},updateFlatSingleTag(e){const t=e.querySelectorAll(".opblock-tag-section");if(t.forEach(e=>{e.classList.remove("matomo-flat-tag")}),1!==t.length)return;const n=t[0];n.querySelector(":scope > .opblock-tag")&&n.classList.add("matomo-flat-tag")},applyMatomoCopyIcons(e){const t=e.querySelectorAll(".opblock-summary .view-line-link.copy-to-clipboard");t.forEach(e=>{e.classList.contains("matomo-copy-success")||(e.innerHTML=T)})},normalizeSwaggerUi(e){this.shortenSummaryPaths(e),this.updateFlatSingleTag(e),this.applyMatomoCopyIcons(e)},getSummaryPathCopyControl(e){return null===e||void 0===e?void 0:e.closest(".opblock-summary .view-line-link.copy-to-clipboard")},getFlatTagHeader(e){return null===e||void 0===e?void 0:e.closest(".opblock-tag-section.matomo-flat-tag > .opblock-tag")},clearCopySuccessState(e){const t=e[B];if(!t)return;const{element:n}=t;window.clearTimeout(t.resetTimeoutId),n.innerHTML=T,n.classList.remove("matomo-copy-success"),n.classList.remove("matomo-copy-reset"),window.requestAnimationFrame(()=>{n.classList.add("matomo-copy-reset"),window.setTimeout(()=>{n.classList.remove("matomo-copy-reset")},300)}),e[B]=null},showCopySuccessState(e,t){this.clearCopySuccessState(e),t.innerHTML=N,t.classList.remove("matomo-copy-reset"),t.classList.add("matomo-copy-success"),e[B]={element:t,resetTimeoutId:window.setTimeout(()=>{var n;(null===(n=e[B])||void 0===n?void 0:n.element)===t&&this.clearCopySuccessState(e)},3e3)}},attachSwaggerInteractionHandlers(e){e&&!e[A]&&(e[A]=!0,e.addEventListener("click",t=>{const n=t.target,o=this.getFlatTagHeader(n);if(o)return null!==n&&void 0!==n&&n.closest("a")||t.preventDefault(),void t.stopPropagation();const r=this.getSummaryPathCopyControl(n);r&&window.setTimeout(()=>{r.isConnected&&this.showCopySuccessState(e,r)},0),null!==n&&void 0!==n&&n.closest(x)&&window.setTimeout(()=>{this.normalizeSwaggerUi(e)},0)},!0))},resetSwaggerUi(){const e=this.getSwaggerRoot();this.isReady=!1,this.loadError=null,e&&(this.clearCopySuccessState(e),e.innerHTML="")},renderSwaggerUi(){var e;const t=window.SwaggerUIBundle,n=this.getSwaggerRoot();if(this.isReady=!1,this.loadError=null,!t||!n||!this.spec){if(!this.spec)return;return this.isReady=!0,void(this.loadError=Object(P["translate"])("OpenApiDocs_SwaggerPageSpecLoadFailed"))}n.innerHTML="",t({dom_id:"#"+this.swaggerContainerId,spec:this.getSpecWithCurrentInstanceUrl(this.spec),deepLinking:!1,docExpansion:"list",defaultModelsExpandDepth:-1,layout:"BaseLayout",tagsSorter:"alpha",presets:null!==(e=t.presets)&&void 0!==e&&e.apis?[t.presets.apis]:[],onComplete:()=>{window.setTimeout(()=>{this.normalizeSwaggerUi(n),this.isReady=!0},0),this.attachSwaggerInteractionHandlers(n)}})}}});n("848d");D.render=E,D.__scopeId="data-v-7e9c9564";var _=D,I=Object(i["defineComponent"])({props:{piwikUrl:{type:String,default:null}},components:{ActivityIndicator:P["ActivityIndicator"],Alert:P["Alert"],ContentBlock:P["ContentBlock"],SwaggerUiPanel:_},directives:{ContentIntro:P["ContentIntro"]},computed:{reportingApiMoreInformation(){return Object(P["translate"])("OpenApiDocs_ReportingApiMoreInformation",Object(P["externalLink"])("https://matomo.org/docs/analytics-api"),"",Object(P["externalLink"])("https://developer.matomo.org/api-reference/reporting-api"),"")},userAuthenticationHelp(){const e=Object(P["translate"])("OpenApiDocs_UserAuthenticationUsingTokenAuth","","","token_auth"),t=Object(P["translate"])("CoreHome_LearnMoreFullStop",Object(P["externalLink"])("https://developer.matomo.org/api-reference/reporting-api#authenticate-to-the-api-via-token_auth-parameter"),"");return`${e} ${t}`},userSecurityUrl(){return`?${P["MatomoUrl"].stringify(Object.assign(Object.assign({},P["MatomoUrl"].urlParsed.value),{},{module:"UsersManager",action:"userSecurity"}))}#/#authtokens`},filteredPlugins(){const e=this.searchTerm.trim().toLowerCase();return e?this.plugins.filter(t=>t.toLowerCase().includes(e)):this.plugins},filteredPluginSet(){return new Set(this.filteredPlugins)}},data(){return{expandedPluginName:null,isLoading:!1,loadError:null,plugins:[],pluginSpecs:{},searchTerm:""}},created(){this.fetchPlugins()},watch:{searchTerm(e){this.expandedPluginName&&!this.matchesSearch(this.expandedPluginName,e)&&(this.expandedPluginName=null)}},methods:{matchesSearch(e,t){const n=(null!==t&&void 0!==t?t:this.searchTerm).trim().toLowerCase();return e.toLowerCase().includes(n)},forceReflow(e){e.getBoundingClientRect()},getPluginBodyTransitionDuration(e){return Math.min(400,Math.max(180,Math.round(e/4)))},resetPluginBodyTransitionStyles(e){const t=e;t.style.height="",t.style.transitionDuration="",t.style.overflow=""},setPluginBodyTransitionState(e,t){e.style.height=t,e.style.overflow="hidden"},transitionPluginBody(e,t,n){this.setPluginBodyTransitionState(e,t),e.style.transitionDuration=this.getPluginBodyTransitionDuration(e.scrollHeight)+"ms",this.forceReflow(e),e.style.height=n},onPluginBodyBeforeEnter(e){this.setPluginBodyTransitionState(e,"0")},onPluginBodyEnter(e){const t=e;this.transitionPluginBody(t,"0",t.scrollHeight+"px")},onPluginBodyBeforeLeave(e){const t=e;this.setPluginBodyTransitionState(t,t.scrollHeight+"px")},onPluginBodyLeave(e){const t=e;this.transitionPluginBody(t,t.scrollHeight+"px","0")},async fetchPlugins(){this.expandedPluginName=null,this.isLoading=!0,this.loadError=null,this.plugins=[],this.pluginSpecs={},this.searchTerm="";try{const e=await P["AjaxHelper"].fetch({method:"OpenApiDocs.getAllowedPlugins"},{createErrorNotification:!1});this.plugins=[...e].sort((e,t)=>e.localeCompare(t))}catch(e){this.loadError=Object(P["translate"])("OpenApiDocs_SwaggerPageRequestFailed")}finally{this.isLoading=!1}},createPluginSpecState(){return{loadError:null,request:null,spec:null,status:"idle"}},getPluginSpecState(e){return this.pluginSpecs[e]||(this.pluginSpecs[e]=this.createPluginSpecState()),this.pluginSpecs[e]},async prefetchPluginSpec(e,t=!1){const n=this.getPluginSpecState(e);if(!t){if("loaded"===n.status)return n.spec;if(n.request)return n.request}return n.status="loading",n.loadError=null,n.request=(async()=>{try{const t=await P["AjaxHelper"].fetch({method:"OpenApiDocs.getOpenApiSpec",pluginName:e,format:"json"},{createErrorNotification:!1});return n.spec=t,n.status="loaded",t}catch(t){return n.spec=null,n.status="error",n.loadError=Object(P["translate"])("OpenApiDocs_SwaggerPageSpecLoadFailed"),null}finally{n.request=null}})(),n.request},togglePlugin(e){if(this.expandedPluginName===e)return void(this.expandedPluginName=null);this.expandedPluginName=e;const t=this.getPluginSpecState(e);"loaded"!==t.status&&this.prefetchPluginSpec(e,"error"===t.status)}}});n("0edb");I.render=w,I.__scopeId="data-v-7d1e8f2e";var M=I;
+/*!
+ * Matomo - free/libre analytics platform
+ *
+ * @link https://matomo.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */}})}));
+//# sourceMappingURL=OpenApiDocs.umd.min.js.map
\ No newline at end of file
diff --git a/vue/dist/umd.metadata.json b/vue/dist/umd.metadata.json
new file mode 100644
index 0000000..9ecfcc0
--- /dev/null
+++ b/vue/dist/umd.metadata.json
@@ -0,0 +1,5 @@
+{
+ "dependsOn": [
+ "CoreHome"
+ ]
+}
\ No newline at end of file
diff --git a/vue/lib/swagger-ui/swagger-ui-bundle.js b/vue/lib/swagger-ui/swagger-ui-bundle.js
new file mode 100644
index 0000000..5ea59d2
--- /dev/null
+++ b/vue/lib/swagger-ui/swagger-ui-bundle.js
@@ -0,0 +1,2 @@
+/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */
+!function webpackUniversalModuleDefinition(s,o){"object"==typeof exports&&"object"==typeof module?module.exports=o():"function"==typeof define&&define.amd?define([],o):"object"==typeof exports?exports.SwaggerUIBundle=o():s.SwaggerUIBundle=o()}(this,(()=>(()=>{var s={67526(s,o){"use strict";o.byteLength=function byteLength(s){var o=getLens(s),i=o[0],a=o[1];return 3*(i+a)/4-a},o.toByteArray=function toByteArray(s){var o,i,_=getLens(s),w=_[0],x=_[1],C=new u(function _byteLength(s,o,i){return 3*(o+i)/4-i}(0,w,x)),j=0,L=x>0?w-4:w;for(i=0;i