You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by ro...@apache.org on 2020/06/22 08:27:21 UTC
[cloudstack-primate] branch master updated: primate: Add support
for UI customisation (#372)
This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack-primate.git
The following commit(s) were added to refs/heads/master by this push:
new 4398032 primate: Add support for UI customisation (#372)
4398032 is described below
commit 439803245c0082736da9f6e89ccb563862d373b5
Author: Hoang Nguyen <ho...@unitech.vn>
AuthorDate: Mon Jun 22 15:27:14 2020 +0700
primate: Add support for UI customisation (#372)
- New config.json global config file
- Customisation: API endpoint, app name, doc link, logo, error and banner images, theme
- Basic external plugin support to allow users to write UI plugins in any framework, build and import/plug a html file as integration
Signed-off-by: Rohit Yadav <ro...@shapeblue.com>
Co-authored-by: Rohit Yadav <ro...@shapeblue.com>
---
docs/customize.md | 77 ++++++++++++++++++
package-lock.json | 103 +++++++++++++++++++-----
package.json | 1 +
{src => public}/assets/403.png | Bin
{src => public}/assets/404.png | Bin
{src => public}/assets/500.png | Bin
{src => public}/assets/banner.svg | 0
{src => public}/assets/error.png | Bin
{src => public}/assets/logo.svg | 0
{src => public}/assets/success.png | Bin
public/config.json | 47 +++++++++++
public/example.html | 12 +++
src/App.vue | 8 +-
src/components/header/Logo.vue | 10 ++-
src/components/header/UserMenu.vue | 8 +-
src/components/widgets/Breadcrumb.vue | 6 +-
src/config/router.js | 18 ++++-
src/config/settings.js | 4 -
src/core/use.js | 1 +
src/layouts/UserLayout.vue | 12 ++-
src/main.js | 20 +++--
src/permission.js | 4 +-
src/{App.vue => style/vars.less} | 46 +++++------
src/utils/domUtil.js | 4 -
src/utils/request.js | 2 -
src/views/AutogenView.vue | 4 +-
src/views/auth/Login.vue | 3 +-
src/views/compute/DeployVM.vue | 37 +--------
src/views/exception/ExceptionPage.vue | 29 +++++--
src/views/image/RegisterOrUploadTemplate.vue | 26 ++----
src/{App.vue => views/plugins/IFramePlugin.vue} | 26 ++----
theme.config.js | 50 ++++++++++++
vue.config.js | 11 ++-
33 files changed, 394 insertions(+), 175 deletions(-)
diff --git a/docs/customize.md b/docs/customize.md
new file mode 100644
index 0000000..09a86a1
--- /dev/null
+++ b/docs/customize.md
@@ -0,0 +1,77 @@
+# UI customization
+Use a `public/config.json` (or `dist/config.json` after build) file for customizing theme, logos,...
+
+## Images
+Change the image of the logo, login banner, error page, etc.
+```json
+{
+ "logo": "assets/logo.svg",
+ "banner": "assets/banner.svg",
+ "error": {
+ "404": "assets/404.png",
+ "403": "assets/403.png",
+ "500": "assets/500.png"
+ }
+}
+```
+
+- `logo` changes the logo top-left side image.
+- `banner` changes the login banner image.
+- `error.404` change the image of error Page not found.
+- `error.403` change the image of error Forbidden.
+- `error.500` change the image of error Internal Server Error.
+
+## Theme
+Customize themes like colors, border color, etc.
+```json
+{
+ "theme": {
+ "@primary-color": "#1890ff",
+ "@success-color": "#52c41a",
+ "@processing-color": "#1890ff",
+ "@warning-color": "#faad14",
+ "@error-color": "#f5222d",
+ "@font-size-base": "14px",
+ "@heading-color": "rgba(0, 0, 0, 0.85)",
+ "@text-color": "rgba(0, 0, 0, 0.65)",
+ "@text-color-secondary": "rgba(0, 0, 0, 0.45)",
+ "@disabled-color": "rgba(0, 0, 0, 0.25)",
+ "@border-color-base": "#d9d9d9",
+ "@logo-width": "256px",
+ "@logo-height": "64px",
+ "@banner-width": "700px",
+ "@banner-height": "110px",
+ "@error-width": "256px",
+ "@error-height": "256px"
+ }
+}
+```
+
+- `@primary-color` change the major background color of the page (background button, icon hover, etc).
+- `@success-color` change success state color.
+- `@processing-color` change processing state color. Exp: progress status.
+- `@warning-color` change warning state color.
+- `@error-color` change error state color.
+- `@heading-color` change table header color.
+- `@text-color` change in major text color.
+- `@text-color-secondary` change of secondary text color (breadcrumb icon).
+- `@disabled-color` change disable state color (disabled button, switch, etc).
+- `@border-color-base` change in major border color.
+- `@logo-width` change the width of the logo top-left side.
+- `@logo-height` change the height of the logo top-left side.
+- `@banner-width` changes the width of the login banner.
+- `@banner-height` changes the height of the login banner.
+- `@error-width` changes the width of the error image.
+- `@error-height` changes the height of the error image.
+
+Assorted primary theme colours:
+
+- Blue: #1890FF
+- Red: #F5222D
+- Yellow: #FAAD14
+- Cyan: #13C2C2
+- Green: #52C41A
+- Purple: #722ED1
+
+Also, to add other properties, we can add new properties into `theme.config.js` based on the Ant Design Vue Less variable.
+Refer: https://www.antdv.com/docs/vue/customize-theme/#Ant-Design-Vue-Less-variables
diff --git a/package-lock.json b/package-lock.json
index 7f356ff..9855867 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6767,7 +6767,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
"requires": {
"color-convert": "^1.9.0"
}
@@ -6814,6 +6813,44 @@
"warning": "^4.0.0"
}
},
+ "antd-theme-generator": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/antd-theme-generator/-/antd-theme-generator-1.2.4.tgz",
+ "integrity": "sha512-27HCj4NTpbQZGNkz1Ip7RF1p85iSN4izf5rY6rQvytM2shvve9qVLnIwHGdNWvsMPrgOPH5wlu8bauKnh7+6dg==",
+ "requires": {
+ "glob": "^7.1.3",
+ "hash.js": "^1.1.5",
+ "less": "^3.9.0",
+ "less-plugin-npm-import": "^2.1.0",
+ "postcss": "^6.0.21",
+ "strip-css-comments": "^4.1.0"
+ },
+ "dependencies": {
+ "postcss": {
+ "version": "6.0.23",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz",
+ "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==",
+ "requires": {
+ "chalk": "^2.4.1",
+ "source-map": "^0.6.1",
+ "supports-color": "^5.4.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ }
+ }
+ },
+ "antd-theme-webpack-plugin": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/antd-theme-webpack-plugin/-/antd-theme-webpack-plugin-1.3.6.tgz",
+ "integrity": "sha512-3cxWiblpZYbkZoghQODjcejQhx0hTu8aSOilWH2nZFtdOoa0ZFXT6u60uzghZiwqFuYqgv0ylMfYGElOmTUdiw==",
+ "requires": {
+ "antd-theme-generator": "^1.2.4"
+ }
+ },
"any-observable": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
@@ -9547,7 +9584,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -10256,7 +10292,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
"requires": {
"color-name": "1.1.3"
}
@@ -10264,8 +10299,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
- "dev": true
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"color-string": {
"version": "1.5.3",
@@ -12400,7 +12434,6 @@
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
"integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
- "dev": true,
"requires": {
"prr": "~1.0.1"
}
@@ -14680,8 +14713,7 @@
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
- "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
- "dev": true
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"has-symbol-support-x": {
"version": "1.4.2",
@@ -14765,7 +14797,6 @@
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
- "dev": true,
"requires": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
@@ -15253,7 +15284,6 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
"integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
- "dev": true,
"optional": true
},
"import-cwd": {
@@ -15894,6 +15924,11 @@
"has": "^1.0.3"
}
},
+ "is-regexp": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz",
+ "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA=="
+ },
"is-resolvable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz",
@@ -17403,7 +17438,6 @@
"version": "3.11.1",
"resolved": "https://registry.npmjs.org/less/-/less-3.11.1.tgz",
"integrity": "sha512-tlWX341RECuTOvoDIvtFqXsKj072hm3+9ymRBe76/mD6O5ZZecnlAOVDlWAleF2+aohFrxNidXhv2773f6kY7g==",
- "dev": true,
"requires": {
"clone": "^2.1.2",
"errno": "^0.1.1",
@@ -17420,14 +17454,12 @@
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
- "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
- "dev": true
+ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
- "dev": true,
"optional": true
}
}
@@ -17457,6 +17489,30 @@
}
}
},
+ "less-plugin-npm-import": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/less-plugin-npm-import/-/less-plugin-npm-import-2.1.0.tgz",
+ "integrity": "sha1-gj5phskzGKmBccqFiEi2vq1Vvz4=",
+ "requires": {
+ "promise": "~7.0.1",
+ "resolve": "~1.1.6"
+ },
+ "dependencies": {
+ "promise": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-7.0.4.tgz",
+ "integrity": "sha1-Nj6EpMNsg1a4kP7WLJHOhdAu1Tk=",
+ "requires": {
+ "asap": "~2.0.3"
+ }
+ },
+ "resolve": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs="
+ }
+ }
+ },
"leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@@ -18397,8 +18453,7 @@
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
- "dev": true
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.40.0",
@@ -18480,8 +18535,7 @@
"minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
- "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
- "dev": true
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimalistic-crypto-utils": {
"version": "1.0.1",
@@ -21428,7 +21482,6 @@
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
- "dev": true,
"optional": true,
"requires": {
"asap": "~2.0.3"
@@ -21477,8 +21530,7 @@
"prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
- "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
- "dev": true
+ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
},
"ps-list": {
"version": "4.1.0",
@@ -23989,6 +24041,14 @@
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true
},
+ "strip-css-comments": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/strip-css-comments/-/strip-css-comments-4.1.0.tgz",
+ "integrity": "sha512-azjRwrqk7nK21LU7QuL7DpDyPjvRROQvqPrNyyz6emdzbOh6fsNTvkSvUiThBLzC6+MN90rFu296VbPb/KV+3A==",
+ "requires": {
+ "is-regexp": "^2.1.0"
+ }
+ },
"strip-dirs": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz",
@@ -24097,7 +24157,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
"requires": {
"has-flag": "^3.0.0"
}
diff --git a/package.json b/package.json
index 745e568..5d55c58 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/vue-fontawesome": "^0.1.9",
"ant-design-vue": "~1.6.2",
+ "antd-theme-webpack-plugin": "^1.3.4",
"axios": "^0.19.2",
"core-js": "^3.6.5",
"enquire.js": "^2.1.6",
diff --git a/src/assets/403.png b/public/assets/403.png
similarity index 100%
rename from src/assets/403.png
rename to public/assets/403.png
diff --git a/src/assets/404.png b/public/assets/404.png
similarity index 100%
rename from src/assets/404.png
rename to public/assets/404.png
diff --git a/src/assets/500.png b/public/assets/500.png
similarity index 100%
rename from src/assets/500.png
rename to public/assets/500.png
diff --git a/src/assets/banner.svg b/public/assets/banner.svg
similarity index 100%
rename from src/assets/banner.svg
rename to public/assets/banner.svg
diff --git a/src/assets/error.png b/public/assets/error.png
similarity index 100%
rename from src/assets/error.png
rename to public/assets/error.png
diff --git a/src/assets/logo.svg b/public/assets/logo.svg
similarity index 100%
rename from src/assets/logo.svg
rename to public/assets/logo.svg
diff --git a/src/assets/success.png b/public/assets/success.png
similarity index 100%
rename from src/assets/success.png
rename to public/assets/success.png
diff --git a/public/config.json b/public/config.json
new file mode 100644
index 0000000..f36e77c
--- /dev/null
+++ b/public/config.json
@@ -0,0 +1,47 @@
+{
+ "apiBase": "/client/api",
+ "docBase": "http://docs.cloudstack.apache.org/en/latest",
+ "appTitle": "CloudStack",
+ "logo": "assets/logo.svg",
+ "banner": "assets/banner.svg",
+ "error": {
+ "404": "assets/404.png",
+ "403": "assets/403.png",
+ "500": "assets/500.png"
+ },
+ "theme": {
+ "@primary-color": "#1890ff",
+ "@processing-color": "#1890ff",
+ "@success-color": "#52c41a",
+ "@warning-color": "#faad14",
+ "@error-color": "#f5222d",
+ "@font-size-base": "14px",
+ "@heading-color": "rgba(0, 0, 0, 0.85)",
+ "@text-color": "rgba(0, 0, 0, 0.65)",
+ "@text-color-secondary": "rgba(0, 0, 0, 0.45)",
+ "@disabled-color": "rgba(0, 0, 0, 0.25)",
+ "@border-color-base": "#d9d9d9",
+ "@border-radius-base": "4px",
+ "@box-shadow-base": "0 2px 8px rgba(0, 0, 0, 0.15)",
+ "@logo-width": "256px",
+ "@logo-height": "64px",
+ "@login-banner-width": "700px",
+ "@login-banner-height": "110px",
+ "@error-width": "256px",
+ "@error-height": "256px"
+ },
+ "keyboardOptions": {
+ "us": "label.standard.us.keyboard",
+ "uk": "label.uk.keyboard",
+ "fr": "label.french.azerty.keyboard",
+ "jp": "label.japanese.keyboard",
+ "sc": "label.simplified.chinese.keyboard"
+ },
+ "plugins": [
+ {
+ "name": "ExamplePlugin",
+ "icon": "appstore",
+ "path": "example.html"
+ }
+ ]
+}
diff --git a/public/example.html b/public/example.html
new file mode 100644
index 0000000..594baf1
--- /dev/null
+++ b/public/example.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en-gb">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
+ <title>Example Plugin</title>
+ </head>
+ <body>
+ This is an example iframe plugin, please configure the config.json to remove this in production environment.
+ </body>
+</html>
diff --git a/src/App.vue b/src/App.vue
index f21947b..fa75a8e 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -31,11 +31,13 @@ export default {
mixins: [AppDeviceEnquire],
data () {
return {
- locale: enUS
+ locale: enUS,
+ configs: {}
}
},
- mounted () {
-
+ created () {
+ window.less.modifyVars(this.$config.theme)
+ console.log('config and theme applied')
}
}
</script>
diff --git a/src/components/header/Logo.vue b/src/components/header/Logo.vue
index 37e1e9c..ba879e4 100644
--- a/src/components/header/Logo.vue
+++ b/src/components/header/Logo.vue
@@ -17,7 +17,14 @@
<template>
<div class="logo">
- <img class="logo-image" src="~@/assets/logo.svg"/>
+ <img
+ v-if="$config.logo"
+ :style="{
+ width: $config.theme['@logo-width'],
+ height: $config.theme['@logo-height']
+ }"
+ :src="$config.logo"
+ class="logo-image" />
</div>
</template>
@@ -59,7 +66,6 @@ export default {
}
.logo-image {
- width: 256px;
display: inline-block;
vertical-align: middle;
}
diff --git a/src/components/header/UserMenu.vue b/src/components/header/UserMenu.vue
index b697f29..b30110a 100644
--- a/src/components/header/UserMenu.vue
+++ b/src/components/header/UserMenu.vue
@@ -33,7 +33,7 @@
</router-link>
</a-menu-item>
<a-menu-item class="user-menu-item" key="1" disabled>
- <a :href="docBase" target="_blank">
+ <a :href="$config.docBase" target="_blank">
<a-icon class="user-menu-item-icon" type="question-circle-o"></a-icon>
<span class="user-menu-item-name">{{ $t('label.help') }}</span>
</a>
@@ -51,7 +51,6 @@
</template>
<script>
-import config from '@/config/settings'
import HeaderNotice from './HeaderNotice'
import TranslationMenu from './TranslationMenu'
import { mapActions, mapGetters } from 'vuex'
@@ -62,11 +61,6 @@ export default {
TranslationMenu,
HeaderNotice
},
- data () {
- return {
- docBase: config.docBase
- }
- },
methods: {
...mapActions(['Logout']),
...mapGetters(['nickname', 'avatar']),
diff --git a/src/components/widgets/Breadcrumb.vue b/src/components/widgets/Breadcrumb.vue
index c13c0ea..053bc3c 100644
--- a/src/components/widgets/Breadcrumb.vue
+++ b/src/components/widgets/Breadcrumb.vue
@@ -39,7 +39,7 @@
<a
v-if="item.meta.docHelp"
style="margin-right: 12px"
- :href="docBase + '/' + $route.meta.docHelp"
+ :href="$config.docBase + '/' + $route.meta.docHelp"
target="_blank">
<a-icon type="question-circle-o"></a-icon>
</a>
@@ -52,7 +52,6 @@
</template>
<script>
-import config from '@/config/settings'
export default {
name: 'Breadcrumb',
@@ -67,8 +66,7 @@ export default {
data () {
return {
name: '',
- breadList: [],
- docBase: config.docBase
+ breadList: []
}
},
created () {
diff --git a/src/config/router.js b/src/config/router.js
index 672764e..cad2aee 100644
--- a/src/config/router.js
+++ b/src/config/router.js
@@ -18,6 +18,8 @@
// eslint-disable-next-line
import { UserLayout, BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts'
import AutogenView from '@/views/AutogenView.vue'
+import IFramePlugin from '@/views/plugins/IFramePlugin.vue'
+import Vue from 'vue'
import compute from '@/config/section/compute'
import storage from '@/config/section/storage'
@@ -167,7 +169,7 @@ function generateRouterMap (section) {
}
export function asyncRouterMap () {
- return [{
+ const routerMap = [{
path: '/',
name: 'index',
component: BasicLayout,
@@ -255,6 +257,20 @@ export function asyncRouterMap () {
{
path: '*', redirect: '/exception/404', hidden: true
}]
+
+ const plugins = Vue.prototype.$config.plugins
+ if (plugins && plugins.length > 0) {
+ plugins.map(plugin => {
+ routerMap[0].children.push({
+ path: '/plugins/' + plugin.name,
+ name: plugin.name,
+ component: IFramePlugin,
+ meta: { title: plugin.name, icon: plugin.icon, path: plugin.path }
+ })
+ })
+ }
+
+ return routerMap
}
export const constantRouterMap = [
diff --git a/src/config/settings.js b/src/config/settings.js
index 95c3225..6e599e7 100644
--- a/src/config/settings.js
+++ b/src/config/settings.js
@@ -25,10 +25,6 @@ export default {
autoHideHeader: false, // auto hide header
invertedMode: true,
multiTab: false, // enable to have tab/route history stuff
- // CloudStack options
- apiBase: '/client/api',
- docBase: 'http://docs.cloudstack.apache.org/en/latest',
- appTitle: 'CloudStack',
// vue-ls options
storageOptions: {
namespace: 'primate__', // key prefix
diff --git a/src/core/use.js b/src/core/use.js
index 014d06e..89c4f2e 100644
--- a/src/core/use.js
+++ b/src/core/use.js
@@ -24,6 +24,7 @@ import Antd from 'ant-design-vue'
import Viser from 'viser-vue'
import VueCropper from 'vue-cropper'
import 'ant-design-vue/dist/antd.less'
+import '@/style/vars.less'
// ext library
import VueClipboard from 'vue-clipboard2'
diff --git a/src/layouts/UserLayout.vue b/src/layouts/UserLayout.vue
index 61ce2f3..f5bb5a4 100644
--- a/src/layouts/UserLayout.vue
+++ b/src/layouts/UserLayout.vue
@@ -19,7 +19,15 @@
<div id="userLayout" :class="['user-layout', device]">
<div class="user-layout-container">
<div class="user-layout-header">
- <img src="~@/assets/banner.svg" class="user-layout-logo" alt="logo">
+ <img
+ v-if="$config.banner"
+ :style="{
+ width: $config.theme['@banner-width'],
+ height: $config.theme['@banner-height']
+ }"
+ :src="$config.banner"
+ class="user-layout-logo"
+ alt="logo">
</div>
<route-view></route-view>
</div>
@@ -65,8 +73,6 @@ export default {
}
&-logo {
- width: 95%;
- max-width: 450px;
border-style: none;
margin: 0 auto 2rem;
display: block;
diff --git a/src/main.js b/src/main.js
index 267a0c4..11ee548 100644
--- a/src/main.js
+++ b/src/main.js
@@ -20,7 +20,6 @@ import App from './App.vue'
import router from './router'
import store from './store'
import i18n from './locales'
-import { VueAxios } from './utils/request'
import bootstrap from './core/bootstrap'
import './core/use'
@@ -28,16 +27,21 @@ import './core/ext'
import './permission' // permission control
import './utils/filter' // global filter
import { pollJobPlugin, notifierPlugin } from './utils/plugins'
+import { VueAxios } from './utils/request'
Vue.config.productionTip = false
Vue.use(VueAxios, router)
Vue.use(pollJobPlugin)
Vue.use(notifierPlugin)
-new Vue({
- router,
- store,
- i18n,
- created: bootstrap,
- render: h => h(App)
-}).$mount('#app')
+fetch('config.json').then(response => response.json()).then(config => {
+ Vue.prototype.$config = config
+ Vue.axios.defaults.baseURL = config.apiBase
+ new Vue({
+ router,
+ store,
+ i18n,
+ created: bootstrap,
+ render: h => h(App)
+ }).$mount('#app')
+})
diff --git a/src/permission.js b/src/permission.js
index 4d37e0d..65fcb2b 100644
--- a/src/permission.js
+++ b/src/permission.js
@@ -25,7 +25,7 @@ import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import message from 'ant-design-vue/es/message'
import notification from 'ant-design-vue/es/notification'
-import { setDocumentTitle, domTitle } from '@/utils/domUtil'
+import { setDocumentTitle } from '@/utils/domUtil'
import { ACCESS_TOKEN, APIS } from '@/store/mutation-types'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
@@ -36,7 +36,7 @@ router.beforeEach((to, from, next) => {
// start progress bar
NProgress.start()
if (to.meta && typeof to.meta.title !== 'undefined') {
- const title = i18n.t(to.meta.title) + ' - ' + domTitle
+ const title = i18n.t(to.meta.title) + ' - ' + Vue.prototype.$config.appTitle
setDocumentTitle(title)
}
const validLogin = Vue.ls.get(ACCESS_TOKEN) || Cookies.get('userid') || Cookies.get('userid', { path: '/client' })
diff --git a/src/App.vue b/src/style/vars.less
similarity index 64%
copy from src/App.vue
copy to src/style/vars.less
index f21947b..3d6845f 100644
--- a/src/App.vue
+++ b/src/style/vars.less
@@ -15,32 +15,32 @@
// specific language governing permissions and limitations
// under the License.
-<template>
- <a-config-provider :locale="locale">
- <div id="app">
- <router-view/>
- </div>
- </a-config-provider>
-</template>
+@import "~ant-design-vue/lib/style/themes/default.less";
-<script>
-import enUS from 'ant-design-vue/lib/locale-provider/en_US'
-import { AppDeviceEnquire } from '@/utils/mixin'
+@logo-width: 256px;
+@logo-height: 64px;
+@banner-width: 450px;
+@banner-height: 110px;
+@error-width: 256px;
+@error-height: 256px;
-export default {
- mixins: [AppDeviceEnquire],
- data () {
- return {
- locale: enUS
- }
- },
- mounted () {
+.ant-layout-sider-children .logo-image {
+ width: @logo-width;
+ height: @logo-height;
+}
+.user-layout {
+ &-logo {
+ width: @banner-width;
+ height: @banner-height;
}
}
-</script>
-<style>
-#app {
- height: 100%;
+
+.exception {
+ .img {
+ img {
+ width: @error-width;
+ height: @error-height;
+ }
+ }
}
-</style>
diff --git a/src/utils/domUtil.js b/src/utils/domUtil.js
index 50ab68c..898bc59 100644
--- a/src/utils/domUtil.js
+++ b/src/utils/domUtil.js
@@ -15,8 +15,6 @@
// specific language governing permissions and limitations
// under the License.
-import config from '@/config/settings'
-
export const setDocumentTitle = function (title) {
document.title = title
const ua = navigator.userAgent
@@ -34,5 +32,3 @@ export const setDocumentTitle = function (title) {
document.body.appendChild(i)
}
}
-
-export const domTitle = config.appTitle
diff --git a/src/utils/request.js b/src/utils/request.js
index 1131a6e..9dd0217 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -17,7 +17,6 @@
import Vue from 'vue'
import axios from 'axios'
-import config from '@/config/settings'
import router from '@/router'
import store from '@/store'
import { VueAxios } from './axios'
@@ -25,7 +24,6 @@ import notification from 'ant-design-vue/es/notification'
import { CURRENT_PROJECT } from '@/store/mutation-types'
const service = axios.create({
- baseURL: config.apiBase,
timeout: 600000
})
diff --git a/src/views/AutogenView.vue b/src/views/AutogenView.vue
index 9f87310..029f92b 100644
--- a/src/views/AutogenView.vue
+++ b/src/views/AutogenView.vue
@@ -118,7 +118,7 @@
<a
v-if="currentAction.docHelp || $route.meta.docHelp"
style="margin-left: 5px"
- :href="docBase + '/' + (currentAction.docHelp || $route.meta.docHelp)"
+ :href="$config.docBase + '/' + (currentAction.docHelp || $route.meta.docHelp)"
target="_blank">
<a-icon type="question-circle-o"></a-icon>
</a>
@@ -304,7 +304,6 @@
import { api } from '@/api'
import { mixinDevice } from '@/utils/mixin.js'
import { genericCompare } from '@/utils/sort.js'
-import config from '@/config/settings'
import store from '@/store'
import Breadcrumb from '@/components/widgets/Breadcrumb'
@@ -336,7 +335,6 @@ export default {
data () {
return {
apiName: '',
- docBase: config.docBase,
loading: false,
actionLoading: false,
columns: [],
diff --git a/src/views/auth/Login.vue b/src/views/auth/Login.vue
index 0434f33..1421fc2 100644
--- a/src/views/auth/Login.vue
+++ b/src/views/auth/Login.vue
@@ -111,7 +111,6 @@
<script>
import { api } from '@/api'
import { mapActions } from 'vuex'
-import config from '@/config/settings'
import TranslationMenu from '@/components/header/TranslationMenu'
export default {
@@ -195,7 +194,7 @@ export default {
})
} else if (customActiveKey === 'saml') {
state.loginBtn = false
- var samlUrl = config.apiBase + '?command=samlSso'
+ var samlUrl = this.$config.apiBase + '?command=samlSso'
if (values.idp) {
samlUrl += ('&idpid=' + values.idp)
}
diff --git a/src/views/compute/DeployVM.vue b/src/views/compute/DeployVM.vue
index 2446516..ce7084b 100644
--- a/src/views/compute/DeployVM.vue
+++ b/src/views/compute/DeployVM.vue
@@ -683,10 +683,11 @@ export default {
return options
},
keyboardSelectOptions () {
- return this.options.keyboards.map((keyboard) => {
+ const keyboardOpts = this.$config.keyboardOptions || {}
+ return Object.keys(keyboardOpts).map((keyboard) => {
return {
- label: this.$t(keyboard.description),
- value: keyboard.id
+ label: this.$t(keyboardOpts[keyboard]),
+ value: keyboard
}
})
}
@@ -805,7 +806,6 @@ export default {
})
}
- this.fetchKeyboard()
this.fetchBootTypes()
this.fetchBootModes()
Vue.nextTick().then(() => {
@@ -826,35 +826,6 @@ export default {
})
await this.fetchAllTemplates()
},
- fetchKeyboard () {
- const keyboardType = []
- keyboardType.push({
- id: '',
- description: ''
- })
- keyboardType.push({
- id: 'us',
- description: 'label.standard.us.keyboard'
- })
- keyboardType.push({
- id: 'uk',
- description: 'label.uk.keyboard'
- })
- keyboardType.push({
- id: 'fr',
- description: 'label.french.azerty.keyboard'
- })
- keyboardType.push({
- id: 'jp',
- description: 'label.japanese.keyboard'
- })
- keyboardType.push({
- id: 'sc',
- description: 'label.simplified.chinese.keyboard'
- })
-
- this.$set(this.options, 'keyboards', keyboardType)
- },
fetchBootTypes () {
const bootTypes = []
diff --git a/src/views/exception/ExceptionPage.vue b/src/views/exception/ExceptionPage.vue
index be591b0..841d78e 100644
--- a/src/views/exception/ExceptionPage.vue
+++ b/src/views/exception/ExceptionPage.vue
@@ -18,13 +18,31 @@
<template>
<div class="exception">
<div class="img" v-if="type == '403'">
- <img src="@/assets/403.png"/>
+ <img
+ v-if="$config.error['403']"
+ :src="$config.error['403']"
+ :style="{
+ width: $config.theme['@error-width'],
+ height: $config.theme['@error-height']
+ }"/>
</div>
<div class="img" v-if="type == '404'">
- <img src="@/assets/404.png"/>
+ <img
+ v-if="$config.error['404']"
+ :src="$config.error['404']"
+ :style="{
+ width: $config.theme['@error-width'],
+ height: $config.theme['@error-height']
+ }"/>
</div>
<div class="img" v-if="type == '500'">
- <img src="@/assets/500.png"/>
+ <img
+ v-if="$config.error['500']"
+ :src="$config.error['500']"
+ :style="{
+ width: $config.theme['@error-width'],
+ height: $config.theme['@error-height']
+ }"/>
</div>
<div class="content">
<h1>{{ config[type].title }}</h1>
@@ -71,10 +89,6 @@ export default {
display: inline-block;
padding-right: 52px;
zoom: 1;
- img {
- height: 256px;
- max-width: 256px;
- }
}
.content {
display: inline-block;
@@ -102,7 +116,6 @@ export default {
padding-right: unset;
img {
- height: 40%;
max-width: 80%;
}
}
diff --git a/src/views/image/RegisterOrUploadTemplate.vue b/src/views/image/RegisterOrUploadTemplate.vue
index 45ad66f..a4f90c9 100644
--- a/src/views/image/RegisterOrUploadTemplate.vue
+++ b/src/views/image/RegisterOrUploadTemplate.vue
@@ -653,29 +653,17 @@ export default {
},
fetchKeyboardType () {
const keyboardType = []
+ const keyboardOpts = this.$config.keyboardOptions || {}
keyboardType.push({
id: '',
description: ''
})
- keyboardType.push({
- id: 'us',
- description: 'label.standard.us.keyboard'
- })
- keyboardType.push({
- id: 'uk',
- description: 'label.uk.keyboard'
- })
- keyboardType.push({
- id: 'fr',
- description: 'label.french.azerty.keyboard'
- })
- keyboardType.push({
- id: 'jp',
- description: 'label.japanese.keyboard'
- })
- keyboardType.push({
- id: 'sc',
- description: 'label.simplified.chinese.keyboard'
+
+ Object.keys(keyboardOpts).forEach(keyboard => {
+ keyboardType.push({
+ id: keyboard,
+ description: this.$t(keyboardOpts[keyboard])
+ })
})
this.$set(this.keyboardType, 'opts', keyboardType)
diff --git a/src/App.vue b/src/views/plugins/IFramePlugin.vue
similarity index 70%
copy from src/App.vue
copy to src/views/plugins/IFramePlugin.vue
index f21947b..3e07ba7 100644
--- a/src/App.vue
+++ b/src/views/plugins/IFramePlugin.vue
@@ -16,31 +16,15 @@
// under the License.
<template>
- <a-config-provider :locale="locale">
- <div id="app">
- <router-view/>
- </div>
- </a-config-provider>
+ <div>
+ <iframe :src="$route.meta.path" width="100%" frameBorder="0" style="height: 90vh">
+ </iframe>
+ </div>
</template>
<script>
-import enUS from 'ant-design-vue/lib/locale-provider/en_US'
-import { AppDeviceEnquire } from '@/utils/mixin'
export default {
- mixins: [AppDeviceEnquire],
- data () {
- return {
- locale: enUS
- }
- },
- mounted () {
-
- }
+ name: 'IFramePlugin'
}
</script>
-<style>
-#app {
- height: 100%;
-}
-</style>
diff --git a/theme.config.js b/theme.config.js
new file mode 100644
index 0000000..2462674
--- /dev/null
+++ b/theme.config.js
@@ -0,0 +1,50 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+const path = require('path')
+const AntDesignThemePlugin = require('antd-theme-webpack-plugin')
+
+function resolve (dir) {
+ return path.join(__dirname, dir)
+}
+
+const options = {
+ stylesDir: resolve('./src/style'),
+ antDir: resolve('./node_modules/ant-design-vue'),
+ varFile: resolve('./src/style/vars.less'),
+ themeVariables: [
+ '@primary-color',
+ '@success-color',
+ '@warning-color',
+ '@processing-color',
+ '@error-color',
+ '@heading-color',
+ '@text-color',
+ '@text-color-secondary',
+ '@disabled-color',
+ '@border-color-base',
+ '@border-radius-base',
+ '@box-shadow-base'
+ ],
+ indexFileName: 'index.html',
+ publicPath: '.',
+ generateOnce: false
+}
+
+const createThemeColorReplacerPlugin = () => new AntDesignThemePlugin(options)
+
+module.exports = createThemeColorReplacerPlugin
diff --git a/vue.config.js b/vue.config.js
index 4421a02..e92841a 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -20,13 +20,14 @@ const webpack = require('webpack')
const fs = require('fs')
const packageJson = fs.readFileSync('./package.json')
const version = JSON.parse(packageJson).version || 'master'
+const createThemeColorReplacerPlugin = require('./theme.config')
function resolve (dir) {
return path.join(__dirname, dir)
}
// vue.config.js
-module.exports = {
+const vueConfig = {
publicPath: './',
/*
Vue-cli3:
@@ -56,6 +57,7 @@ module.exports = {
chainWebpack: (config) => {
config.resolve.alias
+ .set('@public', resolve('public'))
.set('@$', resolve('src'))
.set('@api', resolve('src/api'))
.set('@assets', resolve('src/assets'))
@@ -103,11 +105,8 @@ module.exports = {
loaderOptions: {
less: {
modifyVars: {
- // Refer:
// https://ant.design/docs/spec/colors
// https://vue.ant.design/docs/vue/customize-theme/
- 'primary-color': '#1890ff',
- 'link-color': '#1890ff'
},
javascriptEnabled: true
}
@@ -149,3 +148,7 @@ module.exports = {
}
}
}
+
+vueConfig.configureWebpack.plugins.push(createThemeColorReplacerPlugin())
+
+module.exports = vueConfig