diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..58307da --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# OCTO管理端 + +## 背景描述 +octo-portal是美团OCTO服务治理体系的管理端,是为各类服务提供节点操作、配置等功能的管理平台。 + +## 主页截图 +![主页概览](./docs/img/outline.png) + + +## 主要功能 +目前提供的功能有服务节点管理,包括服务节点展现,节点启用禁用、节点权重调整、删除节点等。 + + + +## 项目结构 + +#### 前端部分 + +* javascript框架使用[Vue.js](https://vuejs.org),UI框架使用[iView](https://www.iviewui.com) + +#### 后端部分 + +* 标准spring boot工程 + + + +## 项目配置启动 +* 提供前后端独立部署方式,前端启动端口8081,访问后端8080端口获取数据,前端支持热加载。 +* 运行环境: 建议JDK 1.8及以上,建议node版本>=10.7.0 + +* 运行前端 octo-portal-frontend +* 首先在octo-portal-frontend/config/index.js 配置后端ip和端口,然后执行以下命令 + +```bash + +cd octo-portal-frontend + +npm install + +npm run dev + +``` + +* 运行后端 octo-portal-server +* 首先在src/main/resources/application.yaml中配置注册中心地址,然后执行以下命令 + +```bash + +cd octo-portal-server + +mvn clean package -Dmvn.test.skip=true + +cd target + +java -jar octo-portal-server-0.0.1-SNAPSHOT.jar + +``` + +## Copyright and License +[Apache 2.0 License.](/LICENSE) + +## 联系我们 +- Mail: inf.octo.os@meituan.com +- [**Issues**](https://github.com/Meituan-Dianping/octo-portal/issues) \ No newline at end of file diff --git a/docs/img/outline.png b/docs/img/outline.png new file mode 100644 index 0000000..d6b90be Binary files /dev/null and b/docs/img/outline.png differ diff --git a/octo-portal-frontend/.babelrc b/octo-portal-frontend/.babelrc new file mode 100644 index 0000000..9390d16 --- /dev/null +++ b/octo-portal-frontend/.babelrc @@ -0,0 +1,18 @@ +{ + "presets": [ + ["env", { + "modules": false, + "targets": { + "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] + } + }], + "stage-2" + ], + "plugins": ["transform-vue-jsx", "transform-runtime"], + "env": { + "test": { + "presets": ["env", "stage-2"], + "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] + } + } +} diff --git a/octo-portal-frontend/.editorconfig b/octo-portal-frontend/.editorconfig new file mode 100644 index 0000000..9d08a1a --- /dev/null +++ b/octo-portal-frontend/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/octo-portal-frontend/.gitignore b/octo-portal-frontend/.gitignore new file mode 100644 index 0000000..dfb4167 --- /dev/null +++ b/octo-portal-frontend/.gitignore @@ -0,0 +1,17 @@ +.DS_Store +node_modules/ +/dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +/test/unit/coverage/ +/test/e2e/reports/ +selenium-debug.log + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/octo-portal-frontend/.postcssrc.js b/octo-portal-frontend/.postcssrc.js new file mode 100644 index 0000000..eee3e92 --- /dev/null +++ b/octo-portal-frontend/.postcssrc.js @@ -0,0 +1,10 @@ +// https://github.com/michael-ciniawsky/postcss-load-config + +module.exports = { + "plugins": { + "postcss-import": {}, + "postcss-url": {}, + // to edit target browsers: use "browserslist" field in package.json + "autoprefixer": {} + } +} diff --git a/octo-portal-frontend/build/build.js b/octo-portal-frontend/build/build.js new file mode 100644 index 0000000..8f2ad8a --- /dev/null +++ b/octo-portal-frontend/build/build.js @@ -0,0 +1,41 @@ +'use strict' +require('./check-versions')() + +process.env.NODE_ENV = 'production' + +const ora = require('ora') +const rm = require('rimraf') +const path = require('path') +const chalk = require('chalk') +const webpack = require('webpack') +const config = require('../config') +const webpackConfig = require('./webpack.prod.conf') + +const spinner = ora('building for production...') +spinner.start() + +rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { + if (err) throw err + webpack(webpackConfig, (err, stats) => { + spinner.stop() + if (err) throw err + process.stdout.write(stats.toString({ + colors: true, + modules: false, + children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. + chunks: false, + chunkModules: false + }) + '\n\n') + + if (stats.hasErrors()) { + console.log(chalk.red(' Build failed with errors.\n')) + process.exit(1) + } + + console.log(chalk.cyan(' Build complete.\n')) + console.log(chalk.yellow( + ' Tip: built files are meant to be served over an HTTP server.\n' + + ' Opening index.html over file:// won\'t work.\n' + )) + }) +}) diff --git a/octo-portal-frontend/build/check-versions.js b/octo-portal-frontend/build/check-versions.js new file mode 100644 index 0000000..3ef972a --- /dev/null +++ b/octo-portal-frontend/build/check-versions.js @@ -0,0 +1,54 @@ +'use strict' +const chalk = require('chalk') +const semver = require('semver') +const packageConfig = require('../package.json') +const shell = require('shelljs') + +function exec (cmd) { + return require('child_process').execSync(cmd).toString().trim() +} + +const versionRequirements = [ + { + name: 'node', + currentVersion: semver.clean(process.version), + versionRequirement: packageConfig.engines.node + } +] + +if (shell.which('npm')) { + versionRequirements.push({ + name: 'npm', + currentVersion: exec('npm --version'), + versionRequirement: packageConfig.engines.npm + }) +} + +module.exports = function () { + const warnings = [] + + for (let i = 0; i < versionRequirements.length; i++) { + const mod = versionRequirements[i] + + if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { + warnings.push(mod.name + ': ' + + chalk.red(mod.currentVersion) + ' should be ' + + chalk.green(mod.versionRequirement) + ) + } + } + + if (warnings.length) { + console.log('') + console.log(chalk.yellow('To use this template, you must update following to modules:')) + console.log() + + for (let i = 0; i < warnings.length; i++) { + const warning = warnings[i] + console.log(' ' + warning) + } + + console.log() + process.exit(1) + } +} diff --git a/octo-portal-frontend/build/logo.png b/octo-portal-frontend/build/logo.png new file mode 100644 index 0000000..f3d2503 Binary files /dev/null and b/octo-portal-frontend/build/logo.png differ diff --git a/octo-portal-frontend/build/utils.js b/octo-portal-frontend/build/utils.js new file mode 100644 index 0000000..597ebd6 --- /dev/null +++ b/octo-portal-frontend/build/utils.js @@ -0,0 +1,102 @@ +'use strict' +const path = require('path') +const config = require('../config') +const ExtractTextPlugin = require('extract-text-webpack-plugin') +const packageConfig = require('../package.json') + +exports.assetsPath = function (_path) { + const assetsSubDirectory = process.env.NODE_ENV === 'production' + ? config.build.assetsSubDirectory + : config.dev.assetsSubDirectory + + return path.posix.join(assetsSubDirectory, _path) +} + +exports.cssLoaders = function (options) { + options = options || {} + + const cssLoader = { + loader: 'css-loader', + options: { + sourceMap: options.sourceMap + } + } + + const postcssLoader = { + loader: 'postcss-loader', + options: { + sourceMap: options.sourceMap + } + } + + // generate loader string to be used with extract text plugin + function generateLoaders (loader, loaderOptions) { + const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] + + if (loader) { + loaders.push({ + loader: loader + '-loader', + options: Object.assign({}, loaderOptions, { + sourceMap: options.sourceMap + }) + }) + } + + // Extract CSS when that option is specified + // (which is the case during production build) + if (options.extract) { + return ExtractTextPlugin.extract({ + use: loaders, + fallback: 'vue-style-loader' + }) + } else { + return ['vue-style-loader'].concat(loaders) + } + } + + // https://vue-loader.vuejs.org/en/configurations/extract-css.html + return { + css: generateLoaders(), + postcss: generateLoaders(), + //less: generateLoaders('less'), + less: generateLoaders('less',{ javascriptEnabled: true }), + sass: generateLoaders('sass', { indentedSyntax: true }), + scss: generateLoaders('sass'), + stylus: generateLoaders('stylus'), + styl: generateLoaders('stylus') + } +} + +// Generate loaders for standalone style files (outside of .vue) +exports.styleLoaders = function (options) { + const output = [] + const loaders = exports.cssLoaders(options) + + for (const extension in loaders) { + const loader = loaders[extension] + output.push({ + test: new RegExp('\\.' + extension + '$'), + use: loader + }) + } + + return output +} + +exports.createNotifierCallback = () => { + const notifier = require('node-notifier') + + return (severity, errors) => { + if (severity !== 'error') return + + const error = errors[0] + const filename = error.file && error.file.split('!').pop() + + notifier.notify({ + title: packageConfig.name, + message: severity + ': ' + error.name, + subtitle: filename || '', + icon: path.join(__dirname, 'logo.png') + }) + } +} diff --git a/octo-portal-frontend/build/vue-loader.conf.js b/octo-portal-frontend/build/vue-loader.conf.js new file mode 100644 index 0000000..33ed58b --- /dev/null +++ b/octo-portal-frontend/build/vue-loader.conf.js @@ -0,0 +1,22 @@ +'use strict' +const utils = require('./utils') +const config = require('../config') +const isProduction = process.env.NODE_ENV === 'production' +const sourceMapEnabled = isProduction + ? config.build.productionSourceMap + : config.dev.cssSourceMap + +module.exports = { + loaders: utils.cssLoaders({ + sourceMap: sourceMapEnabled, + extract: isProduction + }), + cssSourceMap: sourceMapEnabled, + cacheBusting: config.dev.cacheBusting, + transformToRequire: { + video: ['src', 'poster'], + source: 'src', + img: 'src', + image: 'xlink:href' + } +} diff --git a/octo-portal-frontend/build/webpack.base.conf.js b/octo-portal-frontend/build/webpack.base.conf.js new file mode 100644 index 0000000..1e371d7 --- /dev/null +++ b/octo-portal-frontend/build/webpack.base.conf.js @@ -0,0 +1,86 @@ +'use strict' +const path = require('path') +const utils = require('./utils') +const config = require('../config') +const vueLoaderConfig = require('./vue-loader.conf') + +function resolve (dir) { + return path.join(__dirname, '..', dir) +} + + + +module.exports = { + context: path.resolve(__dirname, '../'), + entry: { + app: './src/main.js' + }, + output: { + path: config.build.assetsRoot, + filename: '[name].js', + publicPath: process.env.NODE_ENV === 'production' + ? config.build.assetsPublicPath + : config.dev.assetsPublicPath + }, + resolve: { + extensions: ['.js', '.vue', '.json'], + alias: { + 'vue$': 'vue/dist/vue.esm.js', + '@': resolve('src'), + } + }, + module: { + rules: [ + { + test: /\.vue$/, + loader: 'vue-loader', + options: vueLoaderConfig + }, + { + test: /\.js$/, + loader: 'babel-loader', + include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] + }, + { + test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('img/[name].[hash:7].[ext]') + } + }, + { + test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('media/[name].[hash:7].[ext]') + } + }, + { + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('fonts/[name].[hash:7].[ext]') + } + }, + // { + // test: /\.less$/, + // loader: "style-loader!css-loader!less-loader", + // } + ] + }, + node: { + // prevent webpack from injecting useless setImmediate polyfill because Vue + // source contains it (although only uses it if it's native). + setImmediate: false, + // prevent webpack from injecting mocks to Node native modules + // that does not make sense for the client + dgram: 'empty', + fs: 'empty', + net: 'empty', + tls: 'empty', + child_process: 'empty' + } +} diff --git a/octo-portal-frontend/build/webpack.dev.conf.js b/octo-portal-frontend/build/webpack.dev.conf.js new file mode 100755 index 0000000..070ae22 --- /dev/null +++ b/octo-portal-frontend/build/webpack.dev.conf.js @@ -0,0 +1,95 @@ +'use strict' +const utils = require('./utils') +const webpack = require('webpack') +const config = require('../config') +const merge = require('webpack-merge') +const path = require('path') +const baseWebpackConfig = require('./webpack.base.conf') +const CopyWebpackPlugin = require('copy-webpack-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') +const portfinder = require('portfinder') + +const HOST = process.env.HOST +const PORT = process.env.PORT && Number(process.env.PORT) + +const devWebpackConfig = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) + }, + // cheap-module-eval-source-map is faster for development + devtool: config.dev.devtool, + + // these devServer options should be customized in /config/index.js + devServer: { + clientLogLevel: 'warning', + historyApiFallback: { + rewrites: [ + { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, + ], + }, + hot: true, + contentBase: false, // since we use CopyWebpackPlugin. + compress: true, + host: HOST || config.dev.host, + port: PORT || config.dev.port, + open: config.dev.autoOpenBrowser, + overlay: config.dev.errorOverlay + ? { warnings: false, errors: true } + : false, + publicPath: config.dev.assetsPublicPath, + proxy: config.dev.proxyTable, + quiet: true, // necessary for FriendlyErrorsPlugin + watchOptions: { + poll: config.dev.poll, + } + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env': require('../config/dev.env') + }), + new webpack.HotModuleReplacementPlugin(), + new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. + new webpack.NoEmitOnErrorsPlugin(), + // https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', + template: 'index.html', + inject: true + }), + // copy custom static assets + new CopyWebpackPlugin([ + { + from: path.resolve(__dirname, '../static'), + to: config.dev.assetsSubDirectory, + ignore: ['.*'] + } + ]) + ] +}) + +module.exports = new Promise((resolve, reject) => { + portfinder.basePort = process.env.PORT || config.dev.port + portfinder.getPort((err, port) => { + if (err) { + reject(err) + } else { + // publish the new Port, necessary for e2e tests + process.env.PORT = port + // add port to devServer config + devWebpackConfig.devServer.port = port + + // Add FriendlyErrorsPlugin + devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ + compilationSuccessInfo: { + messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], + }, + onErrors: config.dev.notifyOnErrors + ? utils.createNotifierCallback() + : undefined + })) + + resolve(devWebpackConfig) + } + }) +}) diff --git a/octo-portal-frontend/build/webpack.prod.conf.js b/octo-portal-frontend/build/webpack.prod.conf.js new file mode 100644 index 0000000..2f17259 --- /dev/null +++ b/octo-portal-frontend/build/webpack.prod.conf.js @@ -0,0 +1,149 @@ +'use strict' +const path = require('path') +const utils = require('./utils') +const webpack = require('webpack') +const config = require('../config') +const merge = require('webpack-merge') +const baseWebpackConfig = require('./webpack.base.conf') +const CopyWebpackPlugin = require('copy-webpack-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const ExtractTextPlugin = require('extract-text-webpack-plugin') +const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') +const UglifyJsPlugin = require('uglifyjs-webpack-plugin') + +const env = process.env.NODE_ENV === 'testing' + ? require('../config/test.env') + : require('../config/prod.env') + +const webpackConfig = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ + sourceMap: config.build.productionSourceMap, + extract: true, + usePostCSS: true + }) + }, + devtool: config.build.productionSourceMap ? config.build.devtool : false, + output: { + path: config.build.assetsRoot, + filename: utils.assetsPath('js/[name].[chunkhash].js'), + chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') + }, + plugins: [ + // http://vuejs.github.io/vue-loader/en/workflow/production.html + new webpack.DefinePlugin({ + 'process.env': env + }), + new UglifyJsPlugin({ + uglifyOptions: { + compress: { + warnings: false + } + }, + sourceMap: config.build.productionSourceMap, + parallel: true + }), + // extract css into its own file + new ExtractTextPlugin({ + filename: utils.assetsPath('css/[name].[contenthash].css'), + // Setting the following option to `false` will not extract CSS from codesplit chunks. + // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. + // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, + // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 + allChunks: true, + }), + // Compress extracted CSS. We are using this plugin so that possible + // duplicated CSS from different components can be deduped. + new OptimizeCSSPlugin({ + cssProcessorOptions: config.build.productionSourceMap + ? { safe: true, map: { inline: false } } + : { safe: true } + }), + // generate dist index.html with correct asset hash for caching. + // you can customize output by editing /index.html + // see https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: process.env.NODE_ENV === 'testing' + ? 'index.html' + : config.build.index, + template: 'index.html', + inject: true, + minify: { + removeComments: true, + collapseWhitespace: true, + removeAttributeQuotes: true + // more options: + // https://github.com/kangax/html-minifier#options-quick-reference + }, + // necessary to consistently work with multiple chunks via CommonsChunkPlugin + chunksSortMode: 'dependency' + }), + // keep module.id stable when vendor modules does not change + new webpack.HashedModuleIdsPlugin(), + // enable scope hoisting + new webpack.optimize.ModuleConcatenationPlugin(), + // split vendor js into its own file + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks (module) { + // any required modules inside node_modules are extracted to vendor + return ( + module.resource && + /\.js$/.test(module.resource) && + module.resource.indexOf( + path.join(__dirname, '../node_modules') + ) === 0 + ) + } + }), + // extract webpack runtime and module manifest to its own file in order to + // prevent vendor hash from being updated whenever app bundle is updated + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest', + minChunks: Infinity + }), + // This instance extracts shared chunks from code splitted chunks and bundles them + // in a separate chunk, similar to the vendor chunk + // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk + new webpack.optimize.CommonsChunkPlugin({ + name: 'app', + async: 'vendor-async', + children: true, + minChunks: 3 + }), + + // copy custom static assets + new CopyWebpackPlugin([ + { + from: path.resolve(__dirname, '../static'), + to: config.build.assetsSubDirectory, + ignore: ['.*'] + } + ]) + ] +}) + +if (config.build.productionGzip) { + const CompressionWebpackPlugin = require('compression-webpack-plugin') + + webpackConfig.plugins.push( + new CompressionWebpackPlugin({ + asset: '[path].gz[query]', + algorithm: 'gzip', + test: new RegExp( + '\\.(' + + config.build.productionGzipExtensions.join('|') + + ')$' + ), + threshold: 10240, + minRatio: 0.8 + }) + ) +} + +if (config.build.bundleAnalyzerReport) { + const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin + webpackConfig.plugins.push(new BundleAnalyzerPlugin()) +} + +module.exports = webpackConfig diff --git a/octo-portal-frontend/config/dev.env.js b/octo-portal-frontend/config/dev.env.js new file mode 100644 index 0000000..1e22973 --- /dev/null +++ b/octo-portal-frontend/config/dev.env.js @@ -0,0 +1,7 @@ +'use strict' +const merge = require('webpack-merge') +const prodEnv = require('./prod.env') + +module.exports = merge(prodEnv, { + NODE_ENV: '"development"' +}) diff --git a/octo-portal-frontend/config/index.js b/octo-portal-frontend/config/index.js new file mode 100644 index 0000000..4498413 --- /dev/null +++ b/octo-portal-frontend/config/index.js @@ -0,0 +1,74 @@ +'use strict' +// Template version: 1.3.1 +// see http://vuejs-templates.github.io/webpack for documentation. + +const path = require('path') + +module.exports = { + dev: { + + // Paths + assetsSubDirectory: 'static', + assetsPublicPath: '/', + proxyTable: { + '/': { + target: 'http://localhost:8080/', + changeOrigin: true, + } + }, + + // Various Dev Server settings + host: 'localhost', // can be overwritten by process.env.HOST + port: 8081, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined + autoOpenBrowser: false, + errorOverlay: true, + notifyOnErrors: true, + poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- + + + /** + * Source Maps + */ + + // https://webpack.js.org/configuration/devtool/#development + devtool: 'cheap-module-eval-source-map', + + // If you have problems debugging vue-files in devtools, + // set this to false - it *may* help + // https://vue-loader.vuejs.org/en/options.html#cachebusting + cacheBusting: true, + + cssSourceMap: true + }, + + build: { + // Template for index.html + index: path.resolve(__dirname, '../dist/index.html'), + + // Paths + assetsRoot: path.resolve(__dirname, '../dist'), + assetsSubDirectory: 'static', + assetsPublicPath: '/', + + /** + * Source Maps + */ + + productionSourceMap: true, + // https://webpack.js.org/configuration/devtool/#production + devtool: '#source-map', + + // Gzip off by default as many popular static hosts such as + // Surge or Netlify already gzip all static assets for you. + // Before setting to `true`, make sure to: + // npm install --save-dev compression-webpack-plugin + productionGzip: false, + productionGzipExtensions: ['js', 'css'], + + // Run the build command with an extra argument to + // View the bundle analyzer report after build finishes: + // `npm run build --report` + // Set to `true` or `false` to always turn it on or off + bundleAnalyzerReport: process.env.npm_config_report + } +} diff --git a/octo-portal-frontend/config/prod.env.js b/octo-portal-frontend/config/prod.env.js new file mode 100644 index 0000000..a6f9976 --- /dev/null +++ b/octo-portal-frontend/config/prod.env.js @@ -0,0 +1,4 @@ +'use strict' +module.exports = { + NODE_ENV: '"production"' +} diff --git a/octo-portal-frontend/config/test.env.js b/octo-portal-frontend/config/test.env.js new file mode 100644 index 0000000..c2824a3 --- /dev/null +++ b/octo-portal-frontend/config/test.env.js @@ -0,0 +1,7 @@ +'use strict' +const merge = require('webpack-merge') +const devEnv = require('./dev.env') + +module.exports = merge(devEnv, { + NODE_ENV: '"testing"' +}) diff --git a/octo-portal-frontend/index.html b/octo-portal-frontend/index.html new file mode 100644 index 0000000..a0b4070 --- /dev/null +++ b/octo-portal-frontend/index.html @@ -0,0 +1,13 @@ + + + + + + OCTO服务治理平台 + + + +
+ + + diff --git a/octo-portal-frontend/package.json b/octo-portal-frontend/package.json new file mode 100644 index 0000000..46eab10 --- /dev/null +++ b/octo-portal-frontend/package.json @@ -0,0 +1,83 @@ +{ + "name": "octo-portal-frontend", + "version": "1.0.0", + "description": "A Vue.js project", + "author": "", + "private": true, + "scripts": { + "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", + "start": "npm run dev", + "unit": "jest --config test/unit/jest.conf.js --coverage", + "e2e": "node test/e2e/runner.js", + "test": "npm run unit && npm run e2e", + "build": "node build/build.js" + }, + "dependencies": { + "axios": "^0.18.0", + "iview": "^3.1.5", + "vue": "^2.5.2", + "vue-router": "^3.0.1", + "vuex": "^3.0.1" + }, + "devDependencies": { + "autoprefixer": "^7.1.2", + "axios-mock-adapter": "^1.15.0", + "babel-core": "^6.22.1", + "babel-helper-vue-jsx-merge-props": "^2.0.3", + "babel-jest": "^21.0.2", + "babel-loader": "^7.1.1", + "babel-plugin-dynamic-import-node": "^1.2.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", + "babel-plugin-transform-runtime": "^6.22.0", + "babel-plugin-transform-vue-jsx": "^3.5.0", + "babel-preset-env": "^1.3.2", + "babel-preset-stage-2": "^6.22.0", + "babel-register": "^6.22.0", + "chalk": "^2.0.1", + "chromedriver": "^2.27.2", + "copy-webpack-plugin": "^4.0.1", + "cross-spawn": "^5.0.1", + "css-loader": "^0.28.0", + "extract-text-webpack-plugin": "^3.0.0", + "file-loader": "^1.1.4", + "friendly-errors-webpack-plugin": "^1.6.1", + "html-webpack-plugin": "^2.30.1", + "jest": "^22.0.4", + "jest-serializer-vue": "^0.3.0", + "less": "^3.8.1", + "less-loader": "^4.1.0", + "nightwatch": "^0.9.12", + "node-notifier": "^5.1.2", + "optimize-css-assets-webpack-plugin": "^3.2.0", + "ora": "^1.2.0", + "portfinder": "^1.0.13", + "postcss-import": "^11.0.0", + "postcss-loader": "^2.0.8", + "postcss-url": "^7.2.1", + "rimraf": "^2.6.0", + "selenium-server": "^3.0.1", + "semver": "^5.3.0", + "shelljs": "^0.7.6", + "style-loader": "^0.23.1", + "uglifyjs-webpack-plugin": "^1.1.1", + "url-loader": "^0.5.8", + "vue-jest": "^1.0.2", + "vue-loader": "^13.3.0", + "vue-style-loader": "^3.0.1", + "vue-template-compiler": "^2.5.2", + "webpack": "^3.6.0", + "webpack-bundle-analyzer": "^2.9.0", + "webpack-dev-server": "^2.9.1", + "webpack-merge": "^4.1.0" + }, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 8" + ] +} diff --git a/octo-portal-frontend/src/App.vue b/octo-portal-frontend/src/App.vue new file mode 100644 index 0000000..61d1205 --- /dev/null +++ b/octo-portal-frontend/src/App.vue @@ -0,0 +1,43 @@ + + + + + + diff --git a/octo-portal-frontend/src/api/api.js b/octo-portal-frontend/src/api/api.js new file mode 100644 index 0000000..5d8ba96 --- /dev/null +++ b/octo-portal-frontend/src/api/api.js @@ -0,0 +1,37 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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 axios from 'axios' + +export const getServiceList = params => { + return axios.get('/api/app', {params: params}); +}; + +export const getProvider = params => { + return axios.get('/api/provider/info', {params: params}); +}; +export const deleteProvider = params => { + return axios.post('/api/provider/info', params); +}; + +export const updateProviderStatus = params => { + return axios.put('/api/provider/status', params); +}; + +export const updateProviderWeight = params => { + return axios.put('/api/provider/weight', params); +}; + diff --git a/octo-portal-frontend/src/common/util.js b/octo-portal-frontend/src/common/util.js new file mode 100644 index 0000000..2c7ac50 --- /dev/null +++ b/octo-portal-frontend/src/common/util.js @@ -0,0 +1,67 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +export const formatUnixtimestamp = (time) => { + var timestamp = new Date(time * 1000); + var year = 1900 + timestamp.getYear(); + var month = "0" + (timestamp.getMonth() + 1); + var date = "0" + timestamp.getDate(); + var hour = "0" + timestamp.getHours(); + var minute = "0" + timestamp.getMinutes(); + var second = "0" + timestamp.getSeconds(); + return year + "-" + month.substring(month.length-2, month.length) + "-" + date.substring(date.length-2, date.length) + + " " + hour.substring(hour.length-2, hour.length) + ":" + + minute.substring(minute.length-2, minute.length) + ":" + + second.substring(second.length-2, second.length); +} + +export const getSiderMenuByRouters = (list) => { + let res = [] + for(let i = 0; i < list.length; i ++) { + let item = list[i] + let obj = { + icon: (item.meta && item.meta.icon) || '', + name: item.name, + meta: item.meta + } + if (hasChild(item)) { + obj.children = getSiderMenuByRouters(item.children) + } + res.push(obj) + } + return res +} + +export const hasChild = (item) => { + return item.children && item.children.length !== 0 +} + +export const getHeaderBreadCrumbList = (route) => { + let routeArr = route.matched + let headerBreadCrumbList = routeArr.map(item => { + let headerBreadCrumb = { + icon: (item.meta && item.meta.icon) || '', + name: (item.meta && item.meta.title) || item.name, + to: item.path + } + return headerBreadCrumb + }) + return headerBreadCrumbList +} + +export const showTitle = (item) => { + return (item.meta && item.meta.title) || item.name +} diff --git a/octo-portal-frontend/src/components/Main.vue b/octo-portal-frontend/src/components/Main.vue new file mode 100644 index 0000000..eafc07c --- /dev/null +++ b/octo-portal-frontend/src/components/Main.vue @@ -0,0 +1,129 @@ + + + + + + diff --git a/octo-portal-frontend/src/components/content/extention-1/extention1_1.vue b/octo-portal-frontend/src/components/content/extention-1/extention1_1.vue new file mode 100644 index 0000000..f9692f9 --- /dev/null +++ b/octo-portal-frontend/src/components/content/extention-1/extention1_1.vue @@ -0,0 +1,31 @@ + + + + + + + diff --git a/octo-portal-frontend/src/components/content/extention-1/extention1_2.vue b/octo-portal-frontend/src/components/content/extention-1/extention1_2.vue new file mode 100644 index 0000000..1ba8330 --- /dev/null +++ b/octo-portal-frontend/src/components/content/extention-1/extention1_2.vue @@ -0,0 +1,31 @@ + + + + + + + diff --git a/octo-portal-frontend/src/components/content/extention-1/extention1_3.vue b/octo-portal-frontend/src/components/content/extention-1/extention1_3.vue new file mode 100644 index 0000000..ba1fac9 --- /dev/null +++ b/octo-portal-frontend/src/components/content/extention-1/extention1_3.vue @@ -0,0 +1,31 @@ + + + + + + + diff --git a/octo-portal-frontend/src/components/content/performance/performance.vue b/octo-portal-frontend/src/components/content/performance/performance.vue new file mode 100644 index 0000000..c5f59cd --- /dev/null +++ b/octo-portal-frontend/src/components/content/performance/performance.vue @@ -0,0 +1,31 @@ + + + + + + + diff --git a/octo-portal-frontend/src/components/content/serviceManage/outline.vue b/octo-portal-frontend/src/components/content/serviceManage/outline.vue new file mode 100644 index 0000000..6d76232 --- /dev/null +++ b/octo-portal-frontend/src/components/content/serviceManage/outline.vue @@ -0,0 +1,31 @@ + + + + + + + diff --git a/octo-portal-frontend/src/components/content/serviceManage/provider.vue b/octo-portal-frontend/src/components/content/serviceManage/provider.vue new file mode 100644 index 0000000..0c50ef4 --- /dev/null +++ b/octo-portal-frontend/src/components/content/serviceManage/provider.vue @@ -0,0 +1,332 @@ + + + + diff --git a/octo-portal-frontend/src/components/header/diy-bread-crumb/diy-bread-crumb.vue b/octo-portal-frontend/src/components/header/diy-bread-crumb/diy-bread-crumb.vue new file mode 100644 index 0000000..b8c6c0f --- /dev/null +++ b/octo-portal-frontend/src/components/header/diy-bread-crumb/diy-bread-crumb.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/octo-portal-frontend/src/components/header/diy-bread-crumb/index.js b/octo-portal-frontend/src/components/header/diy-bread-crumb/index.js new file mode 100644 index 0000000..14ef44e --- /dev/null +++ b/octo-portal-frontend/src/components/header/diy-bread-crumb/index.js @@ -0,0 +1,18 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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 diyBreadCrumb from './diy-bread-crumb.vue' +export default diyBreadCrumb diff --git a/octo-portal-frontend/src/components/header/header-bar.vue b/octo-portal-frontend/src/components/header/header-bar.vue new file mode 100644 index 0000000..d135db9 --- /dev/null +++ b/octo-portal-frontend/src/components/header/header-bar.vue @@ -0,0 +1,47 @@ + + + + diff --git a/octo-portal-frontend/src/components/header/index.js b/octo-portal-frontend/src/components/header/index.js new file mode 100644 index 0000000..451ed68 --- /dev/null +++ b/octo-portal-frontend/src/components/header/index.js @@ -0,0 +1,18 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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 HeaderBar from './header-bar' +export default HeaderBar diff --git a/octo-portal-frontend/src/components/header/sider-active/index.js b/octo-portal-frontend/src/components/header/sider-active/index.js new file mode 100644 index 0000000..e2098fc --- /dev/null +++ b/octo-portal-frontend/src/components/header/sider-active/index.js @@ -0,0 +1,18 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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 siderActive from './sider-active.vue' +export default siderActive diff --git a/octo-portal-frontend/src/components/header/sider-active/sider-active.vue b/octo-portal-frontend/src/components/header/sider-active/sider-active.vue new file mode 100644 index 0000000..e00776d --- /dev/null +++ b/octo-portal-frontend/src/components/header/sider-active/sider-active.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/octo-portal-frontend/src/components/sider/mixin.js b/octo-portal-frontend/src/components/sider/mixin.js new file mode 100644 index 0000000..b0f83ef --- /dev/null +++ b/octo-portal-frontend/src/components/sider/mixin.js @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +export default { + props: { + parentItem: { + type: Object, + default: () => {} + }, + theme: String, + iconSize: Number + }, + computed: { + parentName () { + return this.parentItem.name + }, + children () { + return this.parentItem.children + }, + textColor () { + return this.theme === 'dark' ? '#fff' : '#474e5e' + } + }, + methods: { + showTitle (item) { + return (item.meta && item.meta.title) || item.name + }, + hasChildren (item) { + return item.children && item.children.length > 0 + }, + getName (item, hasChildren) { + return (hasChildren ? item.children[0].name : item.name ) + } + } +} diff --git a/octo-portal-frontend/src/components/sider/sidebar-collapsed-menu.vue b/octo-portal-frontend/src/components/sider/sidebar-collapsed-menu.vue new file mode 100644 index 0000000..8999e6d --- /dev/null +++ b/octo-portal-frontend/src/components/sider/sidebar-collapsed-menu.vue @@ -0,0 +1,55 @@ + + + + diff --git a/octo-portal-frontend/src/components/sider/sidebar-menu-item.vue b/octo-portal-frontend/src/components/sider/sidebar-menu-item.vue new file mode 100644 index 0000000..b604a0d --- /dev/null +++ b/octo-portal-frontend/src/components/sider/sidebar-menu-item.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/octo-portal-frontend/src/components/sider/sidebar-menu.vue b/octo-portal-frontend/src/components/sider/sidebar-menu.vue new file mode 100644 index 0000000..23cfa21 --- /dev/null +++ b/octo-portal-frontend/src/components/sider/sidebar-menu.vue @@ -0,0 +1,130 @@ + + + + + + + diff --git a/octo-portal-frontend/src/config/index.js b/octo-portal-frontend/src/config/index.js new file mode 100644 index 0000000..e6c53d3 --- /dev/null +++ b/octo-portal-frontend/src/config/index.js @@ -0,0 +1,25 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +export default { + nodeStatus: { + dead: 0, + starting: 1, + alive: 2, + stopping: 3, + stopped: 4 + } +} diff --git a/octo-portal-frontend/src/index.less b/octo-portal-frontend/src/index.less new file mode 100644 index 0000000..e53433f --- /dev/null +++ b/octo-portal-frontend/src/index.less @@ -0,0 +1,21 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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 '~iview/src/styles/index.less'; + +@menu-dark-title: #001429; +@menu-dark-active-bg: #000b15; +@layout-sider-background: #001429; diff --git a/octo-portal-frontend/src/main.js b/octo-portal-frontend/src/main.js new file mode 100644 index 0000000..059ab70 --- /dev/null +++ b/octo-portal-frontend/src/main.js @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +// The Vue build version to load with the `import` command +// (runtime-only or standalone) has been set in webpack.base.conf with an alias. +import Vue from 'vue' +import App from './App' +import router from './router' +import store from './store' +import config from './config' +import iView from 'iview' +import './index.less' + +Vue.use(iView) + +Vue.config.productionTip = false + +Vue.prototype.$config = config + +/* eslint-disable no-new */ +new Vue({ + el: '#app', + router, + store, + components: { App }, + template: '' +}) diff --git a/octo-portal-frontend/src/mock/index.js b/octo-portal-frontend/src/mock/index.js new file mode 100644 index 0000000..00a8f1c --- /dev/null +++ b/octo-portal-frontend/src/mock/index.js @@ -0,0 +1,19 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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 mock from './mock'; + +export default mock; diff --git a/octo-portal-frontend/src/mock/mock.js b/octo-portal-frontend/src/mock/mock.js new file mode 100644 index 0000000..c0b1432 --- /dev/null +++ b/octo-portal-frontend/src/mock/mock.js @@ -0,0 +1,82 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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 MockAdapter from 'axios-mock-adapter'; +import axios from 'axios'; + +export default { + bootstrap() { + let mock = new MockAdapter(axios); + mock.onGet('/test').reply(200, { + city: [ + { + value: 'New York', + label: 'New York' + }, + { + value: 'Beijing', + label: 'Beijing' + } + ] + }); + + mock.onGet('/provider').reply(200, { + tableData: [ + { + ip: '127.0.0.1', + port: '8080', + version: 'dorado-1.0', + weight: '10', + status: '1', + lastUpdateTime: '1111' + }, + { + ip: '127.0.0.1', + port: '8080', + version: 'dorado-1.0', + weight: '10', + status: '1', + lastUpdateTime: '1111' + }, + { + ip: '127.0.0.1', + port: '8080', + version: 'dorado-1.0', + weight: '10', + status: '1', + lastUpdateTime: '1111' + }, + { + ip: '127.0.0.1', + port: '8080', + version: 'dorado-1.0', + weight: '10', + status: '1', + lastUpdateTime: '1111' + }, + { + ip: '127.0.0.1', + port: '8080', + version: 'dorado-1.0', + weight: '10', + status: '1', + lastUpdateTime: '1111' + } + ] + }) + + } +} diff --git a/octo-portal-frontend/src/router/index.js b/octo-portal-frontend/src/router/index.js new file mode 100644 index 0000000..ce2e44e --- /dev/null +++ b/octo-portal-frontend/src/router/index.js @@ -0,0 +1,27 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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 Vue from 'vue' +import Router from 'vue-router' +import routes from './routers' + +Vue.use(Router) + +export default new Router({ + routes +}) + + diff --git a/octo-portal-frontend/src/router/routers.js b/octo-portal-frontend/src/router/routers.js new file mode 100644 index 0000000..3f2d82d --- /dev/null +++ b/octo-portal-frontend/src/router/routers.js @@ -0,0 +1,111 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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 Main from '@/components/Main' + +export default [ + { + path: '/', + name: '_provider', + redirect: '/provider', + meta: { + icon: 'md-book', + title: '服务管理' + }, + component: Main, + children: [ + { + path: '/provider', + name: 'provider', + meta: { + icon: 'md-folder', + title: '服务提供者' + }, + component: () => import('@/components/content/serviceManage/provider.vue') + }, + { + path: '/outline', + name: 'outline', + meta: { + icon: 'md-notifications', + title: '服务概要' + }, + component: () => import('@/components/content/serviceManage/outline.vue') + }, + + ] + }, + { + path: '/data', + name: 'data', + redirect: '/performance', + meta: { + icon: 'md-create', + title: '数据分析' + }, + component: Main, + children: [ + { + path: '/performance', + name: 'performance', + meta: { + icon: 'md-calculator', + title: '性能指标' + }, + component: () => import('@/components/content/performance/performance.vue') + } + ] + }, + { + path: '/extention1', + name: 'extention1', + redirect: '/extention1_1', + meta: { + icon: 'md-desktop', + title: '扩展' + }, + component: Main, + children: [ + { + path: '/extention1_1', + name: 'extention1_1', + meta: { + icon: 'md-document', + title: '扩展1' + }, + component: () => import('@/components/content/extention-1/extention1_1.vue') + }, + { + path: '/extention1_2', + name: 'extention1_2', + meta: { + icon: 'md-egg', + title: '扩展2' + }, + component: () => import('@/components/content/extention-1/extention1_2.vue') + }, + { + path: '/extention1_3', + name: 'extention1_4', + meta: { + icon: 'md-flag', + title: '扩展3' + }, + component: () => import('@/components/content/extention-1/extention1_3.vue') + } + ] + } +] diff --git a/octo-portal-frontend/src/store/index.js b/octo-portal-frontend/src/store/index.js new file mode 100644 index 0000000..c2db573 --- /dev/null +++ b/octo-portal-frontend/src/store/index.js @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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 Vue from 'vue' +import Vuex from 'vuex' +import routers from '@/router/routers' + +import { + getSiderMenuByRouters, + getHeaderBreadCrumbList, +} from "@/common/util"; + +Vue.use(Vuex) + +export default new Vuex.Store({ + state: { + headerBreadCrumbList: [], + }, + getters: { + menuList: () => getSiderMenuByRouters(routers) + }, + mutations: { + setHeaderBreadCrumb (state, route) { + state.headerBreadCrumbList = getHeaderBreadCrumbList(route) + } + }, + actions: { + }, + modules: { + } +}) diff --git a/octo-portal-frontend/static/.gitkeep b/octo-portal-frontend/static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/octo-portal-frontend/static/favicon.ico b/octo-portal-frontend/static/favicon.ico new file mode 100644 index 0000000..2e12a9e Binary files /dev/null and b/octo-portal-frontend/static/favicon.ico differ diff --git a/octo-portal-server/.gitignore b/octo-portal-server/.gitignore new file mode 100644 index 0000000..82eca33 --- /dev/null +++ b/octo-portal-server/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/octo-portal-server/pom.xml b/octo-portal-server/pom.xml new file mode 100644 index 0000000..e9b5f16 --- /dev/null +++ b/octo-portal-server/pom.xml @@ -0,0 +1,112 @@ + + + + 4.0.0 + + com.meituan.octo + octo-portal-server + 0.0.1-SNAPSHOT + jar + + octo-portal-server + octo-portal-server + + + org.springframework.boot + spring-boot-starter-parent + 1.5.9.RELEASE + + + + + UTF-8 + UTF-8 + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.apache.curator + curator-framework + 2.7.1 + + + slf4j-api + org.slf4j + + + + + org.apache.curator + curator-recipes + 2.7.1 + + + slf4j-api + org.slf4j + + + + + + + + org.slf4j + slf4j-api + 1.7.16 + + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.6.2 + + + + org.apache.logging.log4j + log4j-api + 2.6.2 + + + org.apache.logging.log4j + log4j-core + 2.6.2 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/OctoPortalServerApplication.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/OctoPortalServerApplication.java new file mode 100644 index 0000000..e2df702 --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/OctoPortalServerApplication.java @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class OctoPortalServerApplication { + + public static void main(String[] args) { + SpringApplication.run(OctoPortalServerApplication.class, args); + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/controller/AppController.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/controller/AppController.java new file mode 100644 index 0000000..b65b21e --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/controller/AppController.java @@ -0,0 +1,44 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.controller; + +import com.meituan.octo.portal.service.AppService; +import com.meituan.octo.portal.util.JsonUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import java.util.List; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/app") +public class AppController { + + @Autowired + AppService appService; + + @RequestMapping(method = RequestMethod.GET) + public String getAppList() { + List appList = appService.getAppList(); + if (appList != null) { + return JsonUtil.createSuccess(appList); + } else { + return JsonUtil.createFail("服务获取失败"); + } + } + +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/controller/ProviderController.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/controller/ProviderController.java new file mode 100644 index 0000000..77b2c27 --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/controller/ProviderController.java @@ -0,0 +1,87 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.controller; + +import com.meituan.octo.portal.model.ProviderDesc; +import com.meituan.octo.portal.model.param.ProviderInfoParam; +import com.meituan.octo.portal.model.param.ProviderStatusParam; +import com.meituan.octo.portal.model.param.ProviderWeightParam; +import com.meituan.octo.portal.service.ProviderService; +import com.meituan.octo.portal.util.JsonUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/provider") +public class ProviderController { + + @Autowired + ProviderService providerService; + + /** + * 获取服务下所有节点信息 + */ + @RequestMapping(value = "/info", method = RequestMethod.GET) + public String getProviders(@RequestParam(value = "env") int env, + @RequestParam(value = "serviceName") String serviceName) { + List providerDescList = providerService.getProviders(env, serviceName); + return JsonUtil.createSuccess(providerDescList); + } + + /** + * 调整权重 + */ + @RequestMapping(value = "/weight", method = RequestMethod.PUT) + public String updateProviderWeight(@RequestBody ProviderWeightParam providerWeightParam) { + int env = providerWeightParam.getEnv(); + String serviceName = providerWeightParam.getServiceName(); + String ip = providerWeightParam.getIp(); + int port = providerWeightParam.getPort(); + double fweight = providerWeightParam.getWeight(); + + return providerService.updateProviderWeight(env, serviceName, ip, port, fweight); + } + + /** + * 启用禁用状态调整 + */ + @RequestMapping(value = "/status", method = RequestMethod.PUT) + public String updateProviderStatus(@RequestBody ProviderStatusParam provierStatusParam) { + int env = provierStatusParam.getEnv(); + String serviceName = provierStatusParam.getServiceName(); + String ip = provierStatusParam.getIp(); + int port = provierStatusParam.getPort(); + int newStatus = provierStatusParam.getStatus(); + + return providerService.updateProviderStatus(env, serviceName, ip, port, newStatus); + } + + /** + * 删除节点 + */ + @RequestMapping(value = "/info", method = RequestMethod.POST) + public String deleteProvider(@RequestBody ProviderInfoParam providerInfoParam) { + int env = providerInfoParam.getEnv(); + String serviceName = providerInfoParam.getServiceName(); + String ip = providerInfoParam.getIp(); + int port = providerInfoParam.getPort(); + + return providerService.deleteProvider(env, serviceName, ip, port); + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/model/ProviderDesc.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/ProviderDesc.java new file mode 100644 index 0000000..ff9440d --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/ProviderDesc.java @@ -0,0 +1,157 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.model; + +import java.util.Map; + +public class ProviderDesc { + private String appkey; + private String version; + private String ip; + private int port; + private int weight; + private int status; + private int env; + private int warmup; + private long lastUpdateTime; + private double fweight; + private int heartbeatSupport; + private String protocol; + private int role; + private int serverType; + private Map serviceInfo; + + public String getAppkey() { + return appkey; + } + + public void setAppkey(String appkey) { + this.appkey = appkey; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + this.weight = weight; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public int getEnv() { + return env; + } + + public void setEnv(int env) { + this.env = env; + } + + public int getWarmup() { + return warmup; + } + + public void setWarmup(int warmup) { + this.warmup = warmup; + } + + public long getLastUpdateTime() { + return lastUpdateTime; + } + + public void setLastUpdateTime(long lastUpdateTime) { + this.lastUpdateTime = lastUpdateTime; + } + + public double getFweight() { + return fweight; + } + + public void setFweight(double fweight) { + this.fweight = fweight; + } + + public int getHeartbeatSupport() { + return heartbeatSupport; + } + + public void setHeartbeatSupport(int heartbeatSupport) { + this.heartbeatSupport = heartbeatSupport; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public int getRole() { + return role; + } + + public void setRole(int role) { + this.role = role; + } + + public int getServerType() { + return serverType; + } + + public void setServerType(int serverType) { + this.serverType = serverType; + } + + public Map getServiceInfo() { + return serviceInfo; + } + + public void setServiceInfo(Map serviceInfo) { + this.serviceInfo = serviceInfo; + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/model/ProviderNodeData.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/ProviderNodeData.java new file mode 100644 index 0000000..31f57dd --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/ProviderNodeData.java @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ +package com.meituan.octo.portal.model; + + +public class ProviderNodeData { + private String appkey; + private long lastUpdateTime; + + public String getAppkey() { + return appkey; + } + + public void setAppkey(String appkey) { + this.appkey = appkey; + } + + public long getLastUpdateTime() { + return lastUpdateTime; + } + + public void setLastUpdateTime(long lastUpdateTime) { + this.lastUpdateTime = lastUpdateTime; + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/model/ServiceDetail.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/ServiceDetail.java new file mode 100644 index 0000000..74354ed --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/ServiceDetail.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.model; + +public class ServiceDetail { + + private int unifiedProto; + + public int getUnifiedProto() { + return unifiedProto; + } + + public void setUnifiedProto(int unifiedProto) { + this.unifiedProto = unifiedProto; + } + + + @Override + public String toString() { + return "ServiceDetail{" + + "unifiedProto=" + + unifiedProto + + '}'; + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/model/param/ProviderInfoParam.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/param/ProviderInfoParam.java new file mode 100644 index 0000000..03c5099 --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/param/ProviderInfoParam.java @@ -0,0 +1,55 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ +package com.meituan.octo.portal.model.param; + +public class ProviderInfoParam { + private int env; + private String serviceName; + private String ip; + private int port; + + public int getEnv() { + return env; + } + + public void setEnv(int env) { + this.env = env; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/model/param/ProviderStatusParam.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/param/ProviderStatusParam.java new file mode 100644 index 0000000..efec78b --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/param/ProviderStatusParam.java @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.model.param; + + +public class ProviderStatusParam { + private int env; + private String serviceName; + private String ip; + private int port; + private int status; + + public int getEnv() { + return env; + } + + public void setEnv(int env) { + this.env = env; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/model/param/ProviderWeightParam.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/param/ProviderWeightParam.java new file mode 100644 index 0000000..d723f86 --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/model/param/ProviderWeightParam.java @@ -0,0 +1,66 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.model.param; + + +public class ProviderWeightParam { + private int env; + private String serviceName; + private String ip; + private int port; + private double weight; + + public int getEnv() { + return env; + } + + public void setEnv(int env) { + this.env = env; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/service/AppService.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/service/AppService.java new file mode 100644 index 0000000..6e06995 --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/service/AppService.java @@ -0,0 +1,24 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.service; + +import java.util.List; + + +public interface AppService { + List getAppList(); +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/service/ProviderService.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/service/ProviderService.java new file mode 100644 index 0000000..ac1a7f4 --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/service/ProviderService.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.service; + +import com.meituan.octo.portal.model.ProviderDesc; + +import java.util.List; + +public interface ProviderService { + List getProviders(int env, String servicename); + + String updateProviderStatus(int env, String serviceName, String ip, int port, int newStatus); + + String updateProviderWeight(int env, String serviceName, String ip, int port, double fweight); + + String deleteProvider(int env, String serviceName, String ip, int port); +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/service/curator/ZookeeperClient.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/service/curator/ZookeeperClient.java new file mode 100644 index 0000000..f5896de --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/service/curator/ZookeeperClient.java @@ -0,0 +1,135 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.service.curator; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.apache.zookeeper.data.Stat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class ZookeeperClient { + private static final Logger LOGGER = LoggerFactory.getLogger(ZookeeperClient.class); + + private static CuratorFramework client; + + private String address; + + public ZookeeperClient(@Value("${zk.address}") String address) { + this.address = address; + try { + ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3); + client = CuratorFrameworkFactory.builder() + .connectString(address) + .retryPolicy(retryPolicy) + .connectionTimeoutMs(5 * 1000) + .sessionTimeoutMs(5 * 1000) + .build(); + client.start(); + } catch (RuntimeException e) { + LOGGER.error("ZookeeperClient[{}] init failed.", address, e); + } + } + + public boolean isExist(String path) { + try { + if (client.checkExists().forPath(path) != null) { + return true; + } + } catch (Exception e) { + LOGGER.error("ZookeeperClient[{}] isExist failed, path={}", address, path, e); + } + return false; + } + + + public String getData(String path) throws Exception { + try { + byte[] data = client.getData().forPath(path); + return data == null ? "" : new String(data, "UTF-8"); + } catch (Exception e) { + LOGGER.error("ZookeeperClient[{}] getNodeData: {} failed.", address, path, e); + throw e; + } + } + + public void setData(String path, String data) throws Exception { + if (!isExist(path)) { + return; + } + try { + byte[] bytes = data == null ? new byte[0] : data.getBytes("UTF-8"); + Stat stat = client.checkExists().forPath(path); + client.setData().withVersion(stat.getVersion()).forPath(path, bytes); + } catch (Exception e) { + LOGGER.error("ZookeeperClient[{}] setData:{} failed.", address, path, e); + throw e; + } + } + + public void create(String path) throws Exception { + if (isExist(path)) { + return; + } + try { + client.create().creatingParentsIfNeeded().forPath(path); + } catch (Exception e) { + LOGGER.error("ZookeeperClient[{}] createWithParent:{} failed.", address, path, e); + throw e; + } + } + + public void createWithData(String path, String data) throws Exception { + if (isExist(path)) { + return; + } + try { + byte[] bytes = data == null ? new byte[0] : data.getBytes("UTF-8"); + client.create().creatingParentsIfNeeded().forPath(path, bytes); + } catch (Exception e) { + LOGGER.error("ZookeeperClient[{}] createWithParent:{} failed.", address, path, e); + throw e; + } + } + + public void delete(String path) throws Exception { + if (!isExist(path)) { + return; + } + try { + client.delete().guaranteed().deletingChildrenIfNeeded().forPath(path); + } catch (Exception e) { + LOGGER.error("ZookeeperClient[{}] deletePath:{} failed.", address, path, e); + throw e; + } + } + + public List getChildren(String parentPath) throws Exception { + try { + return client.getChildren().forPath(parentPath); + } catch (Exception e) { + LOGGER.error("ZookeeperClient[{}] getChildren failed, parentPath:{}.", address, parentPath, e); + throw e; + } + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/service/impl/AppServiceImpl.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/service/impl/AppServiceImpl.java new file mode 100644 index 0000000..730a291 --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/service/impl/AppServiceImpl.java @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.service.impl; + +import com.meituan.octo.portal.service.AppService; +import com.meituan.octo.portal.service.curator.ZookeeperClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +@Component +public class AppServiceImpl implements AppService { + private static final Logger LOGGER = LoggerFactory.getLogger(AppServiceImpl.class); + private static final String ZK_ROOT_PATH = "/octo/nameservice"; + + @Autowired + private ZookeeperClient zkClient; + + @Override + public List getAppList() { + String prodPath = ZK_ROOT_PATH + "/prod"; + try { + List appList = zkClient.getChildren(prodPath); + return appList; + } catch (Exception e) { + LOGGER.error("get appList failed: {}", e); + return Collections.emptyList(); + } + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/service/impl/ProviderServiceImpl.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/service/impl/ProviderServiceImpl.java new file mode 100644 index 0000000..82ce02a --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/service/impl/ProviderServiceImpl.java @@ -0,0 +1,142 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.service.impl; + +import com.meituan.octo.portal.model.ProviderDesc; +import com.meituan.octo.portal.model.ProviderNodeData; +import com.meituan.octo.portal.service.ProviderService; +import com.meituan.octo.portal.service.curator.ZookeeperClient; +import com.meituan.octo.portal.util.ZkPathUtil; +import com.meituan.octo.portal.util.JsonUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + + +@Component +public class ProviderServiceImpl implements ProviderService { + + private static final Logger LOGGER = LoggerFactory.getLogger(ProviderServiceImpl.class); + private static final String ZK_ROOT_PATH = "/octo/nameservice"; + + @Autowired + private ZookeeperClient zkClient; + + @Override + public List getProviders(int env, String serviceName) { + String envStr = ZkPathUtil.toEnvStr(env); + String providerPath = ZK_ROOT_PATH + "/" + envStr + "/" + serviceName + "/provider"; + List providerDescList = new ArrayList<>(); + try { + List providerNodes = zkClient.getChildren(providerPath); + for (String node : providerNodes) { + String nodeData = zkClient.getData(providerPath + "/" + node); + ProviderDesc providerDesc = JsonUtil.parseJson(nodeData, ProviderDesc.class); + providerDescList.add(providerDesc); + } + return providerDescList; + } catch (Exception e) { + LOGGER.error("get providers failed: {}", e); + } + return providerDescList; + } + + @Override + public String updateProviderStatus(int env, String serviceName, String ip, int port, int newStatus) { + String envStr = ZkPathUtil.toEnvStr(env); + String providerPath = ZK_ROOT_PATH + "/" + envStr + "/" + serviceName + "/provider"; + String providerDescPath = ZK_ROOT_PATH + "/" + envStr + "/" + serviceName + "/provider" + "/" + ip + ":" + port; + try { + long curTime = System.currentTimeMillis() / 1000; + + String parentData = zkClient.getData(providerPath); + ProviderNodeData providerNodeData = JsonUtil.parseJson(parentData, ProviderNodeData.class); + if (providerNodeData != null) { + providerNodeData.setLastUpdateTime(curTime); + zkClient.setData(providerPath, JsonUtil.toString(providerNodeData)); + } + + String nodeData = zkClient.getData(providerDescPath); + ProviderDesc providerDesc = JsonUtil.parseJson(nodeData, ProviderDesc.class); + + providerDesc.setStatus(newStatus); + providerDesc.setLastUpdateTime(curTime); + zkClient.setData(providerDescPath, JsonUtil.toString(providerDesc)); + return JsonUtil.createSuccess(null); + } catch (Exception e) { + LOGGER.error("update provider status failed: {}", e); + return JsonUtil.createFail("update provider status failed."); + } + } + + @Override + public String updateProviderWeight(int env, String serviceName, String ip, int port, double fweight) { + String envStr = ZkPathUtil.toEnvStr(env); + String providerPath = ZK_ROOT_PATH + "/" + envStr + "/" + serviceName + "/provider"; + String providerDescPath = ZK_ROOT_PATH + "/" + envStr + "/" + serviceName + "/provider" + "/" + ip + ":" + port; + try { + long curTime = System.currentTimeMillis() / 1000; + + String parentData = zkClient.getData(providerPath); + ProviderNodeData providerNodeData = JsonUtil.parseJson(parentData, ProviderNodeData.class); + if (providerNodeData != null) { + providerNodeData.setLastUpdateTime(curTime); + zkClient.setData(providerPath, JsonUtil.toString(providerNodeData)); + } + + String nodeData = zkClient.getData(providerDescPath); + ProviderDesc providerDesc = JsonUtil.parseJson(nodeData, ProviderDesc.class); + providerDesc.setFweight(fweight); + providerDesc.setWeight(new Double(fweight).intValue()); + providerDesc.setLastUpdateTime(curTime); + zkClient.setData(providerDescPath, JsonUtil.toString(providerDesc)); + + return JsonUtil.createSuccess(null); + } catch (Exception e) { + LOGGER.error("update provider weight failed: {}", e); + return JsonUtil.createFail("update provider weight failed."); + } + } + + @Override + public String deleteProvider(int env, String serviceName, String ip, int port) { + String envStr = ZkPathUtil.toEnvStr(env); + String providerPath = ZK_ROOT_PATH + "/" + envStr + "/" + serviceName + "/provider"; + String providerDescPath = ZK_ROOT_PATH + "/" + envStr + "/" + serviceName + "/provider" + "/" + ip + ":" + port; + try { + long curTime = System.currentTimeMillis() / 1000; + + String parentData = zkClient.getData(providerPath); + ProviderNodeData providerNodeData = JsonUtil.parseJson(parentData, ProviderNodeData.class); + if (providerNodeData != null) { + providerNodeData.setLastUpdateTime(curTime); + zkClient.setData(providerPath, JsonUtil.toString(providerNodeData)); + } + + zkClient.delete(providerDescPath); + + return JsonUtil.createSuccess(null); + } catch (Exception e) { + LOGGER.error("delete provider {} failed: {}", ip + ":" + port, e); + return JsonUtil.createFail("delete provider failed."); + } + } +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/util/JsonUtil.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/util/JsonUtil.java new file mode 100644 index 0000000..9cdaacc --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/util/JsonUtil.java @@ -0,0 +1,69 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class JsonUtil { + private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtil.class); + private static ObjectMapper objectMapper = new ObjectMapper(); + + static { + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + public static String toString(Object object) { + try { + return objectMapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + LOGGER.warn("json serialize fail, object: " + object, e); + return null; + } + } + + public static T parseJson(String json, Class clazz) { + try { + return objectMapper.readValue(json, clazz); + } catch (IOException e) { + LOGGER.warn("json deserialize fail, json" + json, e); + return null; + } + } + + public static String createSuccess(Object data) { + Map result = new HashMap<>(); + result.put("success", true); + result.put("data", data); + return JsonUtil.toString(result); + } + + public static String createFail(String message) { + Map result = new HashMap<>(); + result.put("success", false); + result.put("message", message); + return JsonUtil.toString(result); + } + +} diff --git a/octo-portal-server/src/main/java/com/meituan/octo/portal/util/ZkPathUtil.java b/octo-portal-server/src/main/java/com/meituan/octo/portal/util/ZkPathUtil.java new file mode 100644 index 0000000..5cc5ca2 --- /dev/null +++ b/octo-portal-server/src/main/java/com/meituan/octo/portal/util/ZkPathUtil.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ + +package com.meituan.octo.portal.util; + +public class ZkPathUtil { + public static String toEnvStr(int env) { + switch (env) { + case 3: + return "prod"; + case 2: + return "stage"; + case 1: + return "test"; + default: + return "prod"; + } + } +} diff --git a/octo-portal-server/src/main/resources/application.yaml b/octo-portal-server/src/main/resources/application.yaml new file mode 100644 index 0000000..ef9c5c0 --- /dev/null +++ b/octo-portal-server/src/main/resources/application.yaml @@ -0,0 +1,2 @@ +zk: + address: diff --git a/octo-portal-server/src/test/java/com/meituan/octo/portal/OctoPortalServerApplicationTests.java b/octo-portal-server/src/test/java/com/meituan/octo/portal/OctoPortalServerApplicationTests.java new file mode 100644 index 0000000..6172da8 --- /dev/null +++ b/octo-portal-server/src/test/java/com/meituan/octo/portal/OctoPortalServerApplicationTests.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 Meituan Dianping. All rights reserved. + * + * Licensed 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. + */ +package com.meituan.octo.portal; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class OctoPortalServerApplicationTests { + + @Test + public void contextLoads() { + } + +}