mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-03 00:13:57 +00:00
packaged plugins
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
"build:app": "cd app && yarn install && yarn build",
|
"build:app": "cd app && yarn install && yarn build",
|
||||||
"build:api": "yarn workspace dbgate-api build",
|
"build:api": "yarn workspace dbgate-api build",
|
||||||
"build:web:docker": "yarn workspace dbgate-web build",
|
"build:web:docker": "yarn workspace dbgate-web build",
|
||||||
"build:plugins:frontend": "yarn workspace dbgate-plugin-csv build:frontend && yarn workspace dbgate-plugin-excel build:frontend && yarn workspace dbgate-plugin-mysql build:frontend && yarn workspace dbgate-plugin-mssql build:frontend && yarn workspace dbgate-plugin-mongo build:frontend && yarn workspace dbgate-plugin-postgres build:frontend &&",
|
"build:plugins:frontend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:frontend",
|
||||||
"build:app:local": "cd app && yarn build:local",
|
"build:app:local": "cd app && yarn build:local",
|
||||||
"start:app:local": "cd app && yarn start:local",
|
"start:app:local": "cd app && yarn start:local",
|
||||||
"setCurrentVersion": "node setCurrentVersion",
|
"setCurrentVersion": "node setCurrentVersion",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"ts:api": "yarn workspace dbgate-api ts",
|
"ts:api": "yarn workspace dbgate-api ts",
|
||||||
"ts:web": "yarn workspace dbgate-web ts",
|
"ts:web": "yarn workspace dbgate-web ts",
|
||||||
"ts": "yarn ts:api && yarn ts:web",
|
"ts": "yarn ts:api && yarn ts:web",
|
||||||
"postinstall": "patch-package && yarn fillNativeModules"
|
"postinstall": "patch-package && yarn fillNativeModules && yarn build:plugins:frontend"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"concurrently": "^5.1.0",
|
"concurrently": "^5.1.0",
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"copyfiles": "^2.2.0",
|
"copyfiles": "^2.2.0",
|
||||||
"prettier": "^2.2.1"
|
"prettier": "^2.2.1",
|
||||||
|
"workspaces-run": "^1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
"express-basic-auth": "^1.2.0",
|
"express-basic-auth": "^1.2.0",
|
||||||
"express-fileupload": "^1.2.0",
|
"express-fileupload": "^1.2.0",
|
||||||
"find-free-port": "^2.0.0",
|
"find-free-port": "^2.0.0",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^9.1.0",
|
||||||
"http": "^0.0.0",
|
"http": "^0.0.0",
|
||||||
"json-stable-stringify": "^1.0.1",
|
"json-stable-stringify": "^1.0.1",
|
||||||
"line-reader": "^0.4.0",
|
"line-reader": "^0.4.0",
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
"build": "webpack"
|
"build": "webpack"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/fs-extra": "^9.0.11",
|
||||||
"@types/lodash": "^4.14.149",
|
"@types/lodash": "^4.14.149",
|
||||||
"dbgate-types": "^4.0.0",
|
"dbgate-types": "^4.0.0",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
@@ -68,4 +69,4 @@
|
|||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"msnodesqlv8": "^2.0.10"
|
"msnodesqlv8": "^2.0.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ const fs = require('fs-extra');
|
|||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { extractPackageName } = require('dbgate-tools');
|
const { extractPackageName } = require('dbgate-tools');
|
||||||
const { pluginsdir, datadir } = require('../utility/directories');
|
const { pluginsdir, datadir, packagedPluginsDir } = require('../utility/directories');
|
||||||
const socket = require('../utility/socket');
|
const socket = require('../utility/socket');
|
||||||
const compareVersions = require('compare-versions');
|
const compareVersions = require('compare-versions');
|
||||||
const requirePlugin = require('../shell/requirePlugin');
|
const requirePlugin = require('../shell/requirePlugin');
|
||||||
const downloadPackage = require('../utility/downloadPackage');
|
const downloadPackage = require('../utility/downloadPackage');
|
||||||
const hasPermission = require('../utility/hasPermission');
|
const hasPermission = require('../utility/hasPermission');
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
// async function loadPackageInfo(dir) {
|
// async function loadPackageInfo(dir) {
|
||||||
// const readmeFile = path.join(dir, 'README.md');
|
// const readmeFile = path.join(dir, 'README.md');
|
||||||
@@ -27,19 +28,22 @@ const hasPermission = require('../utility/hasPermission');
|
|||||||
// };
|
// };
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const preinstallPluginMinimalVersions = {
|
// const preinstallPluginMinimalVersions = {
|
||||||
'dbgate-plugin-mssql': '1.2.2',
|
// 'dbgate-plugin-mssql': '1.2.2',
|
||||||
'dbgate-plugin-mysql': '1.2.2',
|
// 'dbgate-plugin-mysql': '1.2.2',
|
||||||
'dbgate-plugin-postgres': '1.2.2',
|
// 'dbgate-plugin-postgres': '1.2.2',
|
||||||
'dbgate-plugin-mongo': '1.0.1',
|
// 'dbgate-plugin-mongo': '1.0.1',
|
||||||
'dbgate-plugin-csv': '1.0.9',
|
// 'dbgate-plugin-csv': '1.0.9',
|
||||||
'dbgate-plugin-excel': '1.0.8',
|
// 'dbgate-plugin-excel': '1.0.8',
|
||||||
};
|
// };
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
script_meta: 'get',
|
script_meta: 'get',
|
||||||
async script({ packageName }) {
|
async script({ packageName }) {
|
||||||
const file = path.join(pluginsdir(), packageName, 'dist', 'frontend.js');
|
const file1 = path.join(packagedPluginsDir(), packageName, 'dist', 'frontend.js');
|
||||||
|
const file2 = path.join(pluginsdir(), packageName, 'dist', 'frontend.js');
|
||||||
|
// @ts-ignore
|
||||||
|
const file = (await fs.exists(file1)) ? file1 : file2;
|
||||||
const data = await fs.readFile(file, {
|
const data = await fs.readFile(file, {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
});
|
});
|
||||||
@@ -63,10 +67,12 @@ module.exports = {
|
|||||||
const { latest } = infoResp.data['dist-tags'];
|
const { latest } = infoResp.data['dist-tags'];
|
||||||
const manifest = infoResp.data.versions[latest];
|
const manifest = infoResp.data.versions[latest];
|
||||||
const { readme } = infoResp.data;
|
const { readme } = infoResp.data;
|
||||||
|
const isPackaged = await fs.pathExists(path.join(packagedPluginsDir(), packageName));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
readme,
|
readme,
|
||||||
manifest,
|
manifest,
|
||||||
|
isPackaged,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {
|
return {
|
||||||
@@ -88,14 +94,22 @@ module.exports = {
|
|||||||
|
|
||||||
installed_meta: 'get',
|
installed_meta: 'get',
|
||||||
async installed() {
|
async installed() {
|
||||||
const files = await fs.readdir(pluginsdir());
|
const files1 = await fs.readdir(packagedPluginsDir());
|
||||||
|
const files2 = await fs.readdir(pluginsdir());
|
||||||
|
|
||||||
const res = [];
|
const res = [];
|
||||||
for (const packageName of files) {
|
for (const packageName of _.union(files1, files2)) {
|
||||||
const manifest = await fs.readFile(path.join(pluginsdir(), packageName, 'package.json')).then(x => JSON.parse(x));
|
const isPackaged = files1.includes(packageName);
|
||||||
|
const manifest = await fs
|
||||||
|
.readFile(path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'package.json'), {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
})
|
||||||
|
.then(x => JSON.parse(x));
|
||||||
const readmeFile = path.join(pluginsdir(), packageName, 'README.md');
|
const readmeFile = path.join(pluginsdir(), packageName, 'README.md');
|
||||||
if (await fs.exists(readmeFile)) {
|
if (await fs.pathExists(readmeFile)) {
|
||||||
manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' });
|
manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' });
|
||||||
}
|
}
|
||||||
|
manifest.isPackaged = isPackaged;
|
||||||
res.push(manifest);
|
res.push(manifest);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
@@ -106,20 +120,20 @@ module.exports = {
|
|||||||
// );
|
// );
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveRemovePlugins() {
|
// async saveRemovePlugins() {
|
||||||
await fs.writeFile(path.join(datadir(), 'removed-plugins'), this.removedPlugins.join('\n'));
|
// await fs.writeFile(path.join(datadir(), 'removed-plugins'), this.removedPlugins.join('\n'));
|
||||||
},
|
// },
|
||||||
|
|
||||||
install_meta: 'post',
|
install_meta: 'post',
|
||||||
async install({ packageName }) {
|
async install({ packageName }) {
|
||||||
if (!hasPermission(`plugins/install`)) return;
|
if (!hasPermission(`plugins/install`)) return;
|
||||||
const dir = path.join(pluginsdir(), packageName);
|
const dir = path.join(pluginsdir(), packageName);
|
||||||
if (!(await fs.exists(dir))) {
|
if (!(await fs.pathExists(dir))) {
|
||||||
await downloadPackage(packageName, dir);
|
await downloadPackage(packageName, dir);
|
||||||
}
|
}
|
||||||
socket.emitChanged(`installed-plugins-changed`);
|
socket.emitChanged(`installed-plugins-changed`);
|
||||||
this.removedPlugins = this.removedPlugins.filter(x => x != packageName);
|
// this.removedPlugins = this.removedPlugins.filter(x => x != packageName);
|
||||||
await this.saveRemovePlugins();
|
// await this.saveRemovePlugins();
|
||||||
},
|
},
|
||||||
|
|
||||||
uninstall_meta: 'post',
|
uninstall_meta: 'post',
|
||||||
@@ -128,7 +142,7 @@ module.exports = {
|
|||||||
const dir = path.join(pluginsdir(), packageName);
|
const dir = path.join(pluginsdir(), packageName);
|
||||||
await fs.rmdir(dir, { recursive: true });
|
await fs.rmdir(dir, { recursive: true });
|
||||||
socket.emitChanged(`installed-plugins-changed`);
|
socket.emitChanged(`installed-plugins-changed`);
|
||||||
this.removedPlugins.push(packageName);
|
// this.removedPlugins.push(packageName);
|
||||||
await this.saveRemovePlugins();
|
await this.saveRemovePlugins();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -136,7 +150,7 @@ module.exports = {
|
|||||||
async upgrade({ packageName }) {
|
async upgrade({ packageName }) {
|
||||||
if (!hasPermission(`plugins/install`)) return;
|
if (!hasPermission(`plugins/install`)) return;
|
||||||
const dir = path.join(pluginsdir(), packageName);
|
const dir = path.join(pluginsdir(), packageName);
|
||||||
if (await fs.exists(dir)) {
|
if (await fs.pathExists(dir)) {
|
||||||
await fs.rmdir(dir, { recursive: true });
|
await fs.rmdir(dir, { recursive: true });
|
||||||
await downloadPackage(packageName, dir);
|
await downloadPackage(packageName, dir);
|
||||||
}
|
}
|
||||||
@@ -158,46 +172,46 @@ module.exports = {
|
|||||||
return content.driver.getAuthTypes() || null;
|
return content.driver.getAuthTypes() || null;
|
||||||
},
|
},
|
||||||
|
|
||||||
async _init() {
|
// async _init() {
|
||||||
const installed = await this.installed();
|
// const installed = await this.installed();
|
||||||
try {
|
// try {
|
||||||
this.removedPlugins = (await fs.readFile(path.join(datadir(), 'removed-plugins'), { encoding: 'utf-8' })).split(
|
// this.removedPlugins = (await fs.readFile(path.join(datadir(), 'removed-plugins'), { encoding: 'utf-8' })).split(
|
||||||
'\n'
|
// '\n'
|
||||||
);
|
// );
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
this.removedPlugins = [];
|
// this.removedPlugins = [];
|
||||||
}
|
// }
|
||||||
|
|
||||||
for (const packageName of Object.keys(preinstallPluginMinimalVersions)) {
|
// for (const packageName of Object.keys(preinstallPluginMinimalVersions)) {
|
||||||
const installedVersion = installed.find(x => x.name == packageName);
|
// const installedVersion = installed.find(x => x.name == packageName);
|
||||||
if (installedVersion) {
|
// if (installedVersion) {
|
||||||
// plugin installed, test, whether upgrade
|
// // plugin installed, test, whether upgrade
|
||||||
const requiredVersion = preinstallPluginMinimalVersions[packageName];
|
// const requiredVersion = preinstallPluginMinimalVersions[packageName];
|
||||||
if (compareVersions(installedVersion.version, requiredVersion) < 0) {
|
// if (compareVersions(installedVersion.version, requiredVersion) < 0) {
|
||||||
console.log(
|
// console.log(
|
||||||
`Upgrading preinstalled plugin ${packageName}, found ${installedVersion.version}, required version ${requiredVersion}`
|
// `Upgrading preinstalled plugin ${packageName}, found ${installedVersion.version}, required version ${requiredVersion}`
|
||||||
);
|
// );
|
||||||
await this.upgrade({ packageName });
|
// await this.upgrade({ packageName });
|
||||||
} else {
|
// } else {
|
||||||
console.log(
|
// console.log(
|
||||||
`Plugin ${packageName} not upgraded, found ${installedVersion.version}, required version ${requiredVersion}`
|
// `Plugin ${packageName} not upgraded, found ${installedVersion.version}, required version ${requiredVersion}`
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (this.removedPlugins.includes(packageName)) {
|
// if (this.removedPlugins.includes(packageName)) {
|
||||||
// plugin was remvoed, don't install again
|
// // plugin was remvoed, don't install again
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
console.log('Preinstalling plugin', packageName);
|
// console.log('Preinstalling plugin', packageName);
|
||||||
await this.install({ packageName });
|
// await this.install({ packageName });
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
console.error('Error preinstalling plugin', packageName, err);
|
// console.error('Error preinstalling plugin', packageName, err);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { pluginsdir } = require('../utility/directories');
|
const fs = require('fs');
|
||||||
|
const { pluginsdir, packagedPluginsDir } = require('../utility/directories');
|
||||||
const nativeModules = require('../nativeModules');
|
const nativeModules = require('../nativeModules');
|
||||||
|
const _isRunOnSource = require('../utility/_isRunOnSource');
|
||||||
|
|
||||||
const loadedPlugins = {};
|
const loadedPlugins = {};
|
||||||
|
|
||||||
@@ -9,13 +11,25 @@ const dbgateEnv = {
|
|||||||
nativeModules,
|
nativeModules,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getModulePath(packageName) {
|
||||||
|
const packagedModulePath = _isRunOnSource()
|
||||||
|
? path.join(packagedPluginsDir(), packageName, 'src', 'backend', 'index.js')
|
||||||
|
: path.join(packagedPluginsDir(), packageName, 'dist', 'backend.js');
|
||||||
|
|
||||||
|
if (fs.existsSync(packagedModulePath)) {
|
||||||
|
return packagedModulePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(pluginsdir(), packageName, 'dist', 'backend.js');
|
||||||
|
}
|
||||||
|
|
||||||
function requirePlugin(packageName, requiredPlugin = null) {
|
function requirePlugin(packageName, requiredPlugin = null) {
|
||||||
if (!packageName) throw new Error('Missing packageName in plugin');
|
if (!packageName) throw new Error('Missing packageName in plugin');
|
||||||
if (loadedPlugins[packageName]) return loadedPlugins[packageName];
|
if (loadedPlugins[packageName]) return loadedPlugins[packageName];
|
||||||
|
|
||||||
if (requiredPlugin == null) {
|
if (requiredPlugin == null) {
|
||||||
let module;
|
let module;
|
||||||
const modulePath = path.join(pluginsdir(), packageName, 'dist', 'backend.js');
|
const modulePath = getModulePath(packageName);
|
||||||
console.log(`Loading module ${packageName} from ${modulePath}`);
|
console.log(`Loading module ${packageName} from ${modulePath}`);
|
||||||
try {
|
try {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
5
packages/api/src/utility/_isRunOnSource.js
Normal file
5
packages/api/src/utility/_isRunOnSource.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
function _isRunOnSource() {
|
||||||
|
return __filename.endsWith('_isRunOnSource.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = _isRunOnSource;
|
||||||
@@ -2,6 +2,7 @@ const os = require('os');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const cleanDirectory = require('./cleanDirectory');
|
const cleanDirectory = require('./cleanDirectory');
|
||||||
|
const _isRunOnSource = require('./_isRunOnSource');
|
||||||
|
|
||||||
const createDirectories = {};
|
const createDirectories = {};
|
||||||
const ensureDirectory = (dir, clean) => {
|
const ensureDirectory = (dir, clean) => {
|
||||||
@@ -39,6 +40,13 @@ const pluginsdir = dirFunc('plugins');
|
|||||||
const archivedir = dirFunc('archive');
|
const archivedir = dirFunc('archive');
|
||||||
const filesdir = dirFunc('files');
|
const filesdir = dirFunc('files');
|
||||||
|
|
||||||
|
function packagedPluginsDir() {
|
||||||
|
if (_isRunOnSource()) {
|
||||||
|
return path.resolve(__dirname, '../../../../plugins');
|
||||||
|
}
|
||||||
|
return path.resolve(__dirname, '../plugins');
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
datadir,
|
datadir,
|
||||||
jsldir,
|
jsldir,
|
||||||
@@ -48,4 +56,5 @@ module.exports = {
|
|||||||
ensureDirectory,
|
ensureDirectory,
|
||||||
pluginsdir,
|
pluginsdir,
|
||||||
filesdir,
|
filesdir,
|
||||||
|
packagedPluginsDir,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,11 @@
|
|||||||
<div class="ml-2">
|
<div class="ml-2">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="bold">{packageManifest.name}</div>
|
<div class="bold">{packageManifest.name}</div>
|
||||||
<div class="ml-1">{packageManifest.version}</div>
|
{#if packageManifest.isPackaged}
|
||||||
|
<div class="ml-1 builtin">(builtin)</div>
|
||||||
|
{:else}
|
||||||
|
<div class="ml-1">{packageManifest.version}</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{packageManifest.description}
|
{packageManifest.description}
|
||||||
@@ -48,4 +52,8 @@
|
|||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.builtin {
|
||||||
|
color: var(--theme-font-3);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import compareVersions from 'compare-versions';
|
import compareVersions from 'compare-versions';
|
||||||
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
||||||
import Markdown from '../elements/Markdown.svelte';
|
import Markdown from '../elements/Markdown.svelte';
|
||||||
import { extractPluginAuthor, extractPluginIcon } from '../plugins/manifestExtractors';
|
import { extractPluginAuthor, extractPluginIcon } from '../plugins/manifestExtractors';
|
||||||
|
|
||||||
import axiosInstance from '../utility/axiosInstance';
|
import axiosInstance from '../utility/axiosInstance';
|
||||||
@@ -24,6 +24,7 @@ import Markdown from '../elements/Markdown.svelte';
|
|||||||
});
|
});
|
||||||
$: readme = $info?.readme;
|
$: readme = $info?.readme;
|
||||||
$: manifest = $info?.manifest;
|
$: manifest = $info?.manifest;
|
||||||
|
$: isPackaged = $info?.isPackaged;
|
||||||
|
|
||||||
const handleInstall = async () => {
|
const handleInstall = async () => {
|
||||||
axiosInstance.post('plugins/install', { packageName });
|
axiosInstance.post('plugins/install', { packageName });
|
||||||
@@ -59,17 +60,21 @@ import Markdown from '../elements/Markdown.svelte';
|
|||||||
<span> | </span>
|
<span> | </span>
|
||||||
<span>{installedFound ? installedFound.version : manifest.version}</span>
|
<span>{installedFound ? installedFound.version : manifest.version}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1">
|
{#if isPackaged}
|
||||||
{#if hasPermission('plugins/install') && !installedFound}
|
<div class="mt-2">Plugin is part of DbGate installation</div>
|
||||||
<FormStyledButton type="button" value="Install" on:click={handleInstall} />
|
{:else}
|
||||||
{/if}
|
<div class="mt-1">
|
||||||
{#if hasPermission('plugins/install') && installedFound}
|
{#if hasPermission('plugins/install') && !installedFound}
|
||||||
<FormStyledButton type="button" value="Uninstall" on:click={handleUninstall} />
|
<FormStyledButton type="button" value="Install" on:click={handleInstall} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if hasPermission('plugins/install') && installedFound && onlineFound && compareVersions(onlineFound.version, installedFound.version) > 0}
|
{#if hasPermission('plugins/install') && installedFound}
|
||||||
<FormStyledButton type="button" value="Upgrade" on:click={handleUpgrade} />
|
<FormStyledButton type="button" value="Uninstall" on:click={handleUninstall} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
{#if hasPermission('plugins/install') && installedFound && onlineFound && compareVersions(onlineFound.version, installedFound.version) > 0}
|
||||||
|
<FormStyledButton type="button" value="Upgrade" on:click={handleUpgrade} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Markdown source={readme} />
|
<Markdown source={readme} />
|
||||||
|
|||||||
Reference in New Issue
Block a user