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 <rohit.yadav@shapeblue.com> Co-authored-by: Rohit Yadav <rohit.yadav@shapeblue.com>
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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']),
|
||||
|
|
|
|||
|
|
@ -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 () {
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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' })
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
// 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.
|
||||
|
||||
@import "~ant-design-vue/lib/style/themes/default.less";
|
||||
|
||||
@logo-width: 256px;
|
||||
@logo-height: 64px;
|
||||
@banner-width: 450px;
|
||||
@banner-height: 110px;
|
||||
@error-width: 256px;
|
||||
@error-height: 256px;
|
||||
|
||||
.ant-layout-sider-children .logo-image {
|
||||
width: @logo-width;
|
||||
height: @logo-height;
|
||||
}
|
||||
|
||||
.user-layout {
|
||||
&-logo {
|
||||
width: @banner-width;
|
||||
height: @banner-height;
|
||||
}
|
||||
}
|
||||
|
||||
.exception {
|
||||
.img {
|
||||
img {
|
||||
width: @error-width;
|
||||
height: @error-height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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: [],
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
||||
|
|
|
|||
|
|
@ -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%;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
// 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.
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<iframe :src="$route.meta.path" width="100%" frameBorder="0" style="height: 90vh">
|
||||
</iframe>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'IFramePlugin'
|
||||
}
|
||||
</script>
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||