mirror of
https://github.com/HackPlan/RootPanel.git
synced 2026-01-13 07:01:20 +08:00
Merge branch 'develop'
This commit is contained in:
34
INSTALL.md
34
INSTALL.md
@@ -10,8 +10,7 @@
|
||||
vi /etc/hosts
|
||||
|
||||
apt-get install mongodb=1:2.4.9-1ubuntu2
|
||||
apt-get install nodejs git nginx redis-server ntp supervisor
|
||||
apt-get install python g++ make screen wget zip unzip iftop vim curl htop iptraf nethogs
|
||||
apt-get install python g++ make nodejs git nginx redis-server ntp supervisor
|
||||
|
||||
npm install coffee-script -g
|
||||
|
||||
@@ -36,22 +35,22 @@
|
||||
|
||||
vi /etc/nginx/sites-enabled/rpadmin
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server ipv6only=on;
|
||||
rewrite ^/(.*)$ http://DOMAIN/#redirect permanent;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
server_name DOMAIN;
|
||||
|
||||
location / {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass http://unix:/home/rpadmin/rootpanel.sock:/;
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server ipv6only=on;
|
||||
rewrite ^/(.*)$ http://rp.rpvhost.net/#redirect permanent;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
server_name rp.rpvhost.net;
|
||||
|
||||
location / {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass http://unix:/home/rpadmin/rootpanel.sock:/;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useradd -m rpadmin
|
||||
usermod -G rpadmin -a www-data
|
||||
@@ -142,6 +141,7 @@
|
||||
### Runtime
|
||||
|
||||
# Shell
|
||||
aot-get install screen wget zip unzip iftop vim curl htop iptraf nethogs
|
||||
apt-get install libcurl4-openssl-dev axel unrar-free emacs subversion subversion-tools tmux mercurial postfix
|
||||
|
||||
# Golang
|
||||
|
||||
@@ -56,7 +56,8 @@ RootPanel 是一个高度插件化的,基于 Linux 的虚拟服务销售平台
|
||||
* Akiyori 42 lines 0.5%
|
||||
* Tianhao Xiao 17 lines 0.2%
|
||||
|
||||
贡献须知:当你向 RootPanel 贡献代码时,即代表你同意授予 RootPanel 维护团队永久的,不可撤回的代码使用权,包括但不限于出售计划中的商业授权;
|
||||
贡献须知:当你向 RootPanel 贡献代码时,即代表你同意授予 RootPanel 维护团队永久的,不可撤回的代码使用权,包括但不限于以闭源的形式出售商业授权.
|
||||
|
||||
在你首次向 RootPanel 贡献代码时,我们还会人工向你确认一次上述协议。
|
||||
|
||||
## 许可协议
|
||||
|
||||
2
Vagrantfile
vendored
2
Vagrantfile
vendored
@@ -2,11 +2,11 @@
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
# based on ubuntu/trusty64
|
||||
config.vm.box = "jysperm/rootpanel"
|
||||
config.vm.hostname = "rp.rpvhost.net"
|
||||
|
||||
config.vm.network "private_network", ip: "192.168.33.10"
|
||||
config.vm.network "public_network", bridge: 'en0: Wi-Fi (AirPort)'
|
||||
|
||||
config.vm.synced_folder ".", "/vagrant",
|
||||
owner: "rpadmin", group: "rpadmin"
|
||||
|
||||
14
app.coffee
14
app.coffee
@@ -3,6 +3,7 @@ nodemailer = require 'nodemailer'
|
||||
path = require 'path'
|
||||
harp = require 'harp'
|
||||
fs = require 'fs'
|
||||
morgan = require 'morgan'
|
||||
moment = require 'moment-timezone'
|
||||
redis = require 'redis'
|
||||
express = require 'express'
|
||||
@@ -58,7 +59,7 @@ exports.run = ->
|
||||
app.db = db
|
||||
|
||||
app.redis = redis.createClient 6379, '127.0.0.1',
|
||||
auth_pass: config.redis_password
|
||||
auth_pass: config.redis.password
|
||||
|
||||
app.mailer = nodemailer.createTransport config.email.account
|
||||
|
||||
@@ -83,7 +84,7 @@ exports.run = ->
|
||||
|
||||
app.use connect.json()
|
||||
app.use connect.urlencoded()
|
||||
app.use connect.logger()
|
||||
app.use morgan('dev')
|
||||
app.use require('cookie-parser')()
|
||||
|
||||
app.use require 'middleware-injector'
|
||||
@@ -107,8 +108,8 @@ exports.run = ->
|
||||
|
||||
app.use (req, res, next) ->
|
||||
unless req.method == 'GET'
|
||||
unless csrf.verify req.session.csrf_secret, req.params.csrf_token
|
||||
res.status(403).send
|
||||
unless csrf.verify req.session.csrf_secret, req.body.csrf_token
|
||||
return res.status(403).send
|
||||
error: 'invalid_csrf_token'
|
||||
|
||||
next()
|
||||
@@ -140,6 +141,8 @@ exports.run = ->
|
||||
|
||||
next()
|
||||
|
||||
app.use app.middleware.accountInfo
|
||||
|
||||
app.set 'views', path.join(__dirname, 'core/view')
|
||||
app.set 'view engine', 'jade'
|
||||
|
||||
@@ -154,7 +157,8 @@ exports.run = ->
|
||||
app.pluggable.initializePlugins()
|
||||
|
||||
app.get '/', (req, res) ->
|
||||
res.redirect '/panel/'
|
||||
unless res.headerSent
|
||||
res.redirect '/panel/'
|
||||
|
||||
app.use harp.mount './core/static'
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
stringify = require 'json-stable-stringify'
|
||||
async = require 'async'
|
||||
_ = require 'underscore'
|
||||
|
||||
@@ -122,7 +123,12 @@ exports.joinPlan = (req, account, plan_name, callback) ->
|
||||
hook.action req, callback
|
||||
, callback
|
||||
, ->
|
||||
callback()
|
||||
unless stringify(original_account.resources_limit) == stringify(account.resources_limit)
|
||||
async.each pluggable.selectHook(account, 'account.resources_limit_changed'), (hook, callback) ->
|
||||
hook.action account, callback
|
||||
, callback
|
||||
else
|
||||
callback()
|
||||
|
||||
exports.leavePlan = (req, account, plan_name, callback) ->
|
||||
leaved_services = _.reject account.billing.services, (service_name) ->
|
||||
@@ -132,6 +138,8 @@ exports.leavePlan = (req, account, plan_name, callback) ->
|
||||
|
||||
return false
|
||||
|
||||
original_account = account
|
||||
|
||||
modifier =
|
||||
$pull:
|
||||
'billing.plans': plan_name
|
||||
@@ -147,11 +155,16 @@ exports.leavePlan = (req, account, plan_name, callback) ->
|
||||
new: true
|
||||
, (err, account) ->
|
||||
async.each leaved_services, (service_name, callback) ->
|
||||
async.each pluggable.selectHook(account, "service.#{service_name}.disable"), (hook, callback) ->
|
||||
async.each pluggable.selectHook(original_account, "service.#{service_name}.disable"), (hook, callback) ->
|
||||
hook.action req, callback
|
||||
, callback
|
||||
, ->
|
||||
callback()
|
||||
unless stringify(original_account.resources_limit) == stringify(account.resources_limit)
|
||||
async.each pluggable.selectHook(account, 'account.resources_limit_changed'), (hook, callback) ->
|
||||
hook.action account, callback
|
||||
, callback
|
||||
else
|
||||
callback()
|
||||
|
||||
exports.calcResourcesLimit = (plans) ->
|
||||
limit = {}
|
||||
|
||||
@@ -27,7 +27,7 @@ exports.try = (key, options, setter, callback) ->
|
||||
redis.get key, (err, value) ->
|
||||
if value != undefined
|
||||
if options.is_json
|
||||
value = JSON.prase value
|
||||
value = JSON.parse value
|
||||
|
||||
callback value
|
||||
|
||||
@@ -39,6 +39,16 @@ exports.try = (key, options, setter, callback) ->
|
||||
options.command key, value, ->
|
||||
callback value
|
||||
|
||||
exports.delete = (key, param, callback) ->
|
||||
unless callback
|
||||
callback = param
|
||||
param = {}
|
||||
|
||||
key = exports.hashKey key, param
|
||||
|
||||
redis.del key, ->
|
||||
callback()
|
||||
|
||||
exports.SET = ->
|
||||
return (key, value, callback) ->
|
||||
redis.set key, value, callback
|
||||
|
||||
@@ -39,7 +39,11 @@
|
||||
"auto": "自动"
|
||||
},
|
||||
"error_code": {
|
||||
"username_exist": "用户名已存在"
|
||||
"username_exist": "用户名已存在",
|
||||
"email_exist": "邮箱已存在",
|
||||
"invalid_username": "用户名不符合要求",
|
||||
"invalid_password": "密码不符合要求",
|
||||
"invalid_email": "邮箱不符合要求"
|
||||
},
|
||||
"account": {
|
||||
"": "帐号",
|
||||
|
||||
@@ -29,17 +29,14 @@ exports.errorHandling = (req, res, next) ->
|
||||
exports.accountInfo = (req, res, next) ->
|
||||
req.inject [exports.parseToken], ->
|
||||
authenticator.authenticate req.token, (err, account) ->
|
||||
req.account = account
|
||||
if account
|
||||
req.account = account
|
||||
res.locals.account = _.extend account,
|
||||
inGroup: (group_name) ->
|
||||
return group_name in account.groups
|
||||
|
||||
next()
|
||||
|
||||
exports.renderAccount = (req, res, next) ->
|
||||
req.inject [exports.accountInfo], ->
|
||||
res.locals.account = _.extend req.account,
|
||||
inGroup: (group_name) ->
|
||||
return group_name in req.account?.groups
|
||||
|
||||
next()
|
||||
|
||||
exports.requireAuthenticate = (req, res, next) ->
|
||||
req.inject [exports.accountInfo, exports.errorHandling], ->
|
||||
if req.account
|
||||
|
||||
@@ -98,7 +98,7 @@ exports.register = (account, callback) ->
|
||||
tokens: []
|
||||
|
||||
async.each pluggable.selectHook(account, 'account.before_register'), (hook, callback) ->
|
||||
hook.filter req, callback
|
||||
hook.filter account, callback
|
||||
, ->
|
||||
exports.insert account, (err, result) ->
|
||||
callback _.first result
|
||||
|
||||
@@ -10,12 +10,19 @@ config = require './../config'
|
||||
|
||||
exports.plugins = {}
|
||||
|
||||
hookHelper = (options) ->
|
||||
return _.extend [], options
|
||||
|
||||
exports.hooks =
|
||||
account:
|
||||
# filter: function(req, callback(is_allow))
|
||||
username_filter: []
|
||||
# filter: function(req, callback)
|
||||
before_register: []
|
||||
# filter: function(username, callback(is_allow))
|
||||
username_filter: hookHelper
|
||||
always_notice: true
|
||||
# filter: function(account, callback)
|
||||
before_register: hookHelper
|
||||
always_notice: true
|
||||
# action: function(account, callback)
|
||||
resources_limit_changed: []
|
||||
|
||||
billing:
|
||||
# widget_generator: function(req, callback(html))
|
||||
@@ -56,6 +63,17 @@ exports.hooks =
|
||||
# t_category, t_title, language, content_markdown
|
||||
pages: []
|
||||
|
||||
exports.createHookPoint = (hook_name) ->
|
||||
keys = hook_name.split '.'
|
||||
|
||||
pointer = exports.hooks
|
||||
|
||||
for item in keys
|
||||
if pointer[item] == undefined
|
||||
pointer[item] = {}
|
||||
|
||||
pointer = pointer[item]
|
||||
|
||||
exports.registerHook = (hook_name, plugin, payload) ->
|
||||
keys = hook_name.split '.'
|
||||
last_key = keys.pop()
|
||||
@@ -87,6 +105,10 @@ exports.selectHook = (account, hook_name) ->
|
||||
return _.filter pointer, (hook) ->
|
||||
if hook.plugin_info.type == 'extension'
|
||||
return true
|
||||
else if pointer.always_notice or hook.always_notice
|
||||
return true
|
||||
else if !account
|
||||
return false
|
||||
else if hook.plugin_info.name in account.billing.services
|
||||
return true
|
||||
else
|
||||
@@ -114,9 +136,6 @@ exports.initializePlugins = (callback) ->
|
||||
if fs.existsSync path.join(plugin_path, 'static')
|
||||
app.use harp.mount("/plugin/#{name}", path.join(plugin_path, 'static'))
|
||||
|
||||
if plugin.router
|
||||
app.use ("/plugin/#{name}"), plugin.router
|
||||
|
||||
callback plugin
|
||||
|
||||
initializeExtension = (plugin, callback) ->
|
||||
@@ -145,6 +164,9 @@ exports.createHelpers = (plugin) ->
|
||||
plugin.registerHook = (hook_name, payload) ->
|
||||
return exports.registerHook hook_name, plugin, payload
|
||||
|
||||
plugin.registerServiceHook = (hook_name, payload) ->
|
||||
return plugin.registerHook "service.#{plugin.name}.#{hook_name}", payload
|
||||
|
||||
plugin.t = (req) ->
|
||||
return (name) ->
|
||||
full_name = "plugins.#{plugin.name}.#{name}"
|
||||
|
||||
@@ -8,13 +8,13 @@ _ = require 'underscore'
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
exports.get '/register', renderAccount, (req, res) ->
|
||||
exports.get '/register', (req, res) ->
|
||||
res.render 'account/register'
|
||||
|
||||
exports.get '/login', renderAccount, (req, res) ->
|
||||
exports.get '/login', (req, res) ->
|
||||
res.render 'account/login'
|
||||
|
||||
exports.get '/preferences', requireAuthenticate, renderAccount, (req, res) ->
|
||||
exports.get '/preferences', requireAuthenticate, (req, res) ->
|
||||
res.render 'account/preferences'
|
||||
|
||||
exports.post '/register', errorHandling, (req, res) ->
|
||||
@@ -27,8 +27,8 @@ exports.post '/register', errorHandling, (req, res) ->
|
||||
unless utils.rx.password.test req.body.password
|
||||
return res.error 'invalid_password'
|
||||
|
||||
async.each pluggable.selectHook(req.account, 'account.username_filter'), (hook, callback) ->
|
||||
hook.filter req, (is_allow) ->
|
||||
async.each pluggable.selectHook(null, 'account.username_filter'), (hook, callback) ->
|
||||
hook.filter req.body.username, (is_allow) ->
|
||||
if is_allow
|
||||
callback()
|
||||
else
|
||||
@@ -44,21 +44,22 @@ exports.post '/register', errorHandling, (req, res) ->
|
||||
username: req.body.username
|
||||
, (err, account) ->
|
||||
if account
|
||||
return res.error 'username_exist'
|
||||
|
||||
callback account
|
||||
callback 'username_exist'
|
||||
else
|
||||
callback()
|
||||
|
||||
email: (callback) ->
|
||||
mAccount.findOne
|
||||
email: req.body.email
|
||||
, (err, account) ->
|
||||
if account
|
||||
return res.error 'email_exist'
|
||||
|
||||
callback account
|
||||
callback 'email_exist'
|
||||
else
|
||||
callback()
|
||||
|
||||
, (err) ->
|
||||
return if err
|
||||
if err
|
||||
return res.error err
|
||||
|
||||
mAccount.register _.pick(req.body, 'username', 'email', 'password'), (account) ->
|
||||
authenticator.createToken account, 'full_access',
|
||||
|
||||
@@ -3,19 +3,19 @@ express = require 'express'
|
||||
async = require 'async'
|
||||
_ = require 'underscore'
|
||||
|
||||
{requireAdminAuthenticate, renderAccount} = require './../middleware'
|
||||
{requireAdminAuthenticate} = app.middleware
|
||||
{plaggable} = app
|
||||
{mAccount, mTicket, mBalanceLog, mCouponCode} = app.models
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
exports.get '/', requireAdminAuthenticate, renderAccount, (req, res) ->
|
||||
exports.get '/', requireAdminAuthenticate, (req, res) ->
|
||||
mAccount.find().toArray (err, accounts) ->
|
||||
return res.render 'admin',
|
||||
accounts: accounts
|
||||
coupon_code_types: _.keys mCouponCode.type_meta
|
||||
|
||||
exports.get '/ticket', requireAdminAuthenticate, renderAccount, (req, res) ->
|
||||
exports.get '/ticket', requireAdminAuthenticate, (req, res) ->
|
||||
async.parallel
|
||||
pending: (callback) ->
|
||||
mTicket.find
|
||||
|
||||
@@ -2,13 +2,13 @@ express = require 'express'
|
||||
async = require 'async'
|
||||
_ = require 'underscore'
|
||||
|
||||
{requireAuthenticate, renderAccount} = require './../middleware'
|
||||
{requireAuthenticate} = app.middleware
|
||||
{mAccount, mBalanceLog} = app.models
|
||||
{pluggable, billing, config} = app
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
exports.get '/pay', requireAuthenticate, renderAccount, (req, res) ->
|
||||
exports.get '/pay', requireAuthenticate, (req, res) ->
|
||||
LIMIT = 10
|
||||
|
||||
async.parallel
|
||||
@@ -57,7 +57,7 @@ exports.get '/pay', requireAuthenticate, renderAccount, (req, res) ->
|
||||
res.render 'panel/pay', _.extend result,
|
||||
nodes: _.values(config.nodes)
|
||||
|
||||
exports.get '/', requireAuthenticate, renderAccount, (req, res) ->
|
||||
exports.get '/', requireAuthenticate, (req, res) ->
|
||||
billing.triggerBilling req.account, (account) ->
|
||||
view_data =
|
||||
account: account
|
||||
|
||||
@@ -2,7 +2,7 @@ express = require 'express'
|
||||
async = require 'async'
|
||||
_ = require 'underscore'
|
||||
|
||||
{requireAuthenticate, renderAccount, getParam, constructObjectID} = app.middleware
|
||||
{requireAuthenticate, getParam, constructObjectID} = app.middleware
|
||||
{mAccount, mTicket} = app.models
|
||||
{config, notification} = app
|
||||
|
||||
@@ -22,7 +22,7 @@ loadTicket = (req, res, next) ->
|
||||
|
||||
next()
|
||||
|
||||
exports.get '/list', requireAuthenticate, renderAccount, (req, res) ->
|
||||
exports.get '/list', requireAuthenticate, (req, res) ->
|
||||
mTicket.find
|
||||
$or: [
|
||||
account_id: req.account._id
|
||||
@@ -36,10 +36,10 @@ exports.get '/list', requireAuthenticate, renderAccount, (req, res) ->
|
||||
res.render 'ticket/list',
|
||||
tickets: tickets
|
||||
|
||||
exports.get '/create', requireAuthenticate, renderAccount, (req, res) ->
|
||||
exports.get '/create', requireAuthenticate, (req, res) ->
|
||||
res.render 'ticket/create'
|
||||
|
||||
exports.get '/view', renderAccount, getParam, loadTicket, (req, res) ->
|
||||
exports.get '/view', getParam, loadTicket, (req, res) ->
|
||||
{ticket} = req
|
||||
|
||||
async.map ticket.members, (member_id, callback) ->
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
$ ->
|
||||
$.ajaxSetup
|
||||
contentType: 'application/json; charset=UTF-8'
|
||||
|
||||
window.i18n_data = {}
|
||||
|
||||
window.t = (name) ->
|
||||
keys = name.split '.'
|
||||
|
||||
result = window.i18n_data
|
||||
|
||||
for item in keys
|
||||
unless result[item] == undefined
|
||||
result = result[item]
|
||||
|
||||
if result == undefined
|
||||
return name
|
||||
else
|
||||
return result
|
||||
|
||||
window.tErr = (name) ->
|
||||
return "error_code.#{name}"
|
||||
|
||||
window.request = (url, param, options, callback) ->
|
||||
unless callback
|
||||
callback = options
|
||||
|
||||
jQueryMethod = $[options.method ? 'post']
|
||||
|
||||
param.csrf_token = $('body').data 'csrf-token'
|
||||
|
||||
jQueryMethod url, JSON.stringify param
|
||||
.fail (jqXHR) ->
|
||||
if jqXHR.responseJSON?.error
|
||||
alert window.t "error_code.#{jqXHR.responseJSON.error}"
|
||||
else
|
||||
alert jqXHR.statusText
|
||||
.success callback
|
||||
|
||||
@@ -1,4 +1,44 @@
|
||||
$ ->
|
||||
$.ajaxSetup
|
||||
contentType: 'application/json; charset=UTF-8'
|
||||
|
||||
window.i18n_data = {}
|
||||
|
||||
window.t = (name) ->
|
||||
keys = name.split '.'
|
||||
|
||||
result = window.i18n_data
|
||||
|
||||
for item in keys
|
||||
if result[item] == undefined
|
||||
return name
|
||||
else
|
||||
result = result[item]
|
||||
|
||||
if result == undefined
|
||||
return name
|
||||
else
|
||||
return result
|
||||
|
||||
window.tErr = (name) ->
|
||||
return "error_code.#{name}"
|
||||
|
||||
window.request = (url, param, options, callback) ->
|
||||
unless callback
|
||||
callback = options
|
||||
|
||||
jQueryMethod = $[options.method ? 'post']
|
||||
|
||||
param.csrf_token = $('body').data 'csrf-token'
|
||||
|
||||
jQueryMethod url, JSON.stringify param
|
||||
.fail (jqXHR) ->
|
||||
if jqXHR.responseJSON?.error
|
||||
alert window.t "error_code.#{jqXHR.responseJSON.error}"
|
||||
else
|
||||
alert jqXHR.statusText
|
||||
.success callback
|
||||
|
||||
client_version = localStorage.getItem 'locale_version'
|
||||
latest_version = $('body').data 'locale-version'
|
||||
|
||||
@@ -9,7 +49,7 @@ $ ->
|
||||
window.i18n_data = result
|
||||
|
||||
localStorage.setItem 'locale_version', latest_version
|
||||
localStorage.setItem 'locale_content', JSON.stringify result
|
||||
localStorage.setItem 'locale_cache', JSON.stringify result
|
||||
|
||||
$('nav a').each ->
|
||||
if $(@).attr('href') == location.pathname
|
||||
|
||||
@@ -34,3 +34,8 @@ exports.randomString = (length) ->
|
||||
|
||||
exports.hashPassword = (password, password_salt) ->
|
||||
return exports.sha256 password_salt + exports.sha256(password)
|
||||
|
||||
exports.wrapAsync = (func) ->
|
||||
return (callback) ->
|
||||
func (result) ->
|
||||
callback null, result
|
||||
|
||||
@@ -10,7 +10,7 @@ block main
|
||||
.form-group
|
||||
label.col-sm-2.col-md-offset-1.control-label= t('view.preferences.qq')
|
||||
.col-sm-5
|
||||
input.input-qq.form-control(type='text', value=account.settings.qq)
|
||||
input.input-qq.form-control(type='text', value=account.preferences.qq)
|
||||
.form-group
|
||||
.col-sm-offset-3
|
||||
button.action-save.btn.btn-lg.btn-success(type='button')= t('common.save')
|
||||
@@ -66,4 +66,4 @@ block main
|
||||
|
||||
|
||||
append footer
|
||||
script(src='/script/account/setting.js')
|
||||
script(src='/script/account/preferences.js')
|
||||
|
||||
@@ -24,10 +24,10 @@ html
|
||||
for hook in selectHook('view.layout.menu_bar')
|
||||
if hook.target
|
||||
li
|
||||
a(href=hook.href, target=hook.target)= hook.body
|
||||
a(href=hook.href, target=hook.target)= t(hook.t_body)
|
||||
else
|
||||
li
|
||||
a(href=hook.href)= hook.body
|
||||
a(href=hook.href)= t(hook.t_body)
|
||||
ul.nav.navbar-nav.navbar-right
|
||||
if account
|
||||
li
|
||||
@@ -83,7 +83,6 @@ html
|
||||
script(src='http://cdn.staticfile.org/jquery-cookie/1.4.1/jquery.cookie.min.js')
|
||||
script(src='http://cdn.staticfile.org/underscore.js/1.7.0/underscore-min.js')
|
||||
script(src='http://cdn.staticfile.org/twitter-bootstrap/3.2.0/js/bootstrap.min.js')
|
||||
script(src='/script/global.js')
|
||||
script(src='/script/layout.js')
|
||||
for hook in selectHook('view.layout.scripts')
|
||||
script(src=hook.path)
|
||||
|
||||
@@ -26,9 +26,9 @@ block main
|
||||
td= t(plan.t_description)
|
||||
td
|
||||
if plan.is_enable
|
||||
button.action-leave-plan.btn.btn-danger.btn-sm= t('plan.join')
|
||||
button.action-leave-plan.btn.btn-danger.btn-sm= t('plan.leave')
|
||||
else
|
||||
button.action-join-plan.btn.btn-success.btn-sm= t('plan.leave')
|
||||
button.action-join-plan.btn.btn-success.btn-sm= t('plan.join')
|
||||
|
||||
if selectHook('view.panel.switch_buttons').length
|
||||
#service-switch.row
|
||||
@@ -40,7 +40,7 @@ block main
|
||||
button(data-name=hook.name).btn.btn-success #{t('common.enable')} #{t('plugins.' + hook.name + '.name')}
|
||||
|
||||
for widget_html in widgets_html
|
||||
!= widget_html
|
||||
.row!= widget_html
|
||||
|
||||
prepend sidebar
|
||||
.row
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"cookie-parser": "^1.3.3",
|
||||
"csrf": "^2.0.1",
|
||||
"connect-redis": "^2.0.1",
|
||||
"express-session": "^1.8.2"
|
||||
"express-session": "^1.8.2",
|
||||
"morgan": "^1.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ module.exports = pluggable.createHelpers exports =
|
||||
type: 'extension'
|
||||
|
||||
exports.registerHook 'account.before_register',
|
||||
filter: (req, callback) ->
|
||||
filter: (account, callback) ->
|
||||
bitcoin_secret = utils.randomSalt()
|
||||
|
||||
bitcoin.genAddress bitcoin_secret, (address) ->
|
||||
req.account.pluggable.bitcoin =
|
||||
account.pluggable.bitcoin =
|
||||
bitcoin_deposit_address: address
|
||||
bitcoin_secret: bitcoin_secret
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
child_process = require 'child_process'
|
||||
os = require 'os'
|
||||
fs = require 'fs'
|
||||
|
||||
monitor = require './monitor'
|
||||
{renderAccount, requireAuthenticate} = require './middleware'
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
app.get '/public/monitor', renderAccount, requireAuthenticate, (req, res) ->
|
||||
async.parallel
|
||||
resources_usage: (callback) ->
|
||||
monitor.monitoringStorage ->
|
||||
resources_usage = []
|
||||
|
||||
for username, usage of monitor.resources_usage
|
||||
resources_usage.push
|
||||
username: username
|
||||
cpu: usage.cpu
|
||||
memory: usage.memory
|
||||
storage: parseInt monitor.storage_usage[username]?.size_used ? 0
|
||||
process: _.filter(monitor.last_plist, (i) -> i.user == username).length
|
||||
|
||||
callback null, resources_usage
|
||||
|
||||
system: (callback) ->
|
||||
async.parallel
|
||||
system: (callback) ->
|
||||
fs.readFile '/etc/issue', (err, content) ->
|
||||
callback err, content.toString().replace(/\\\w/g, '').trim()
|
||||
|
||||
, (err, result) ->
|
||||
callback null, _.extend result,
|
||||
hostname: os.hostname()
|
||||
cpu: os.cpus()[0]['model']
|
||||
uptime: os.uptime()
|
||||
loadavg: _.map(os.loadavg(), (i) -> i.toFixed(2)).join(', ')
|
||||
address: os.networkInterfaces()['eth0'][0].address
|
||||
time: new Date()
|
||||
|
||||
storage: (callback) ->
|
||||
child_process.exec "df -h", (err, stdout) ->
|
||||
disks = {}
|
||||
|
||||
for line in stdout.split('\n')
|
||||
[dev, size, used, available, used_per, mounted] = _.compact(line.split(' '))
|
||||
|
||||
disks[mounted] =
|
||||
dev: dev
|
||||
size: parseInt size?.match(/\d+/)
|
||||
used: parseInt used?.match(/\d+/)
|
||||
available: available
|
||||
used_per: used_per
|
||||
|
||||
root_disk = disks['/']
|
||||
|
||||
used = root_disk.used
|
||||
total = root_disk.size
|
||||
free = total - used
|
||||
|
||||
used_per = (used / total * 100).toFixed()
|
||||
free_per = 100 - used_per
|
||||
|
||||
callback null,
|
||||
used: used
|
||||
free: free
|
||||
total: total
|
||||
|
||||
used_per: used_per
|
||||
free_per: free_per
|
||||
|
||||
memory: monitor.loadMemoryInfo
|
||||
|
||||
, (err, result) ->
|
||||
jade.renderFile path.join(__dirname, 'view/monitor.jade'), _.extend result,
|
||||
last_plist: monitor.last_plist
|
||||
_: _
|
||||
, (err, html) ->
|
||||
res.send html
|
||||
@@ -1,27 +1,70 @@
|
||||
service = require './service'
|
||||
_ = require 'underscore'
|
||||
async = require 'async'
|
||||
|
||||
{pluggable, config} = app
|
||||
{requireAuthenticate} = app.middleware
|
||||
{wrapAsync} = app.utils
|
||||
|
||||
linux = require './linux'
|
||||
monitor = require './monitor'
|
||||
|
||||
{pluggable} = app
|
||||
|
||||
app.view_hook.menu_bar.push
|
||||
href: '/public/monitor/'
|
||||
html: '服务器状态'
|
||||
|
||||
module.exports =
|
||||
module.exports = pluggable.createHelpers exports =
|
||||
name: 'linux'
|
||||
type: 'service'
|
||||
|
||||
service: service
|
||||
exports.registerHook 'view.layout.menu_bar',
|
||||
href: '/public/monitor/'
|
||||
t_body: 'plugins.linux.server_monitor'
|
||||
|
||||
panel:
|
||||
widget: service.widget
|
||||
style:'/style/panel.css'
|
||||
exports.registerHook 'account.username_filter',
|
||||
filter: (username, callback) ->
|
||||
linux.getPasswdMap (passwd_map) ->
|
||||
if username in _.values passwd_map
|
||||
callback false
|
||||
else
|
||||
callback true
|
||||
|
||||
pluggable.account.username_filter.push (account, callback) ->
|
||||
monitor.loadPasswd (passwd_cache) ->
|
||||
if req.body.username in _.values(passwd_cache)
|
||||
return callback false
|
||||
exports.registerHook 'view.panel.styles',
|
||||
path: '/plugin/linux/style/panel.css'
|
||||
|
||||
callback true
|
||||
exports.registerHook 'view.panel.widgets',
|
||||
generator: (req, callback) ->
|
||||
linux.getResourceUsageByAccount req.account, (resources_usage) ->
|
||||
resources_usage ?=
|
||||
username: req.account.username
|
||||
cpu: 0
|
||||
memory: 0
|
||||
storage: 0
|
||||
process: 0
|
||||
|
||||
exports.render 'widget', req,
|
||||
usage: resources_usage
|
||||
, (html) ->
|
||||
callback html
|
||||
|
||||
exports.registerHook 'account.resources_limit_changed',
|
||||
always_notice: true
|
||||
action: (account, callback) ->
|
||||
linux.setResourceLimit account, callback
|
||||
|
||||
exports.registerServiceHook 'enable',
|
||||
action: (req, callback) ->
|
||||
linux.createUser req.account, ->
|
||||
linux.setResourceLimit req.account, callback
|
||||
|
||||
exports.registerServiceHook 'disable',
|
||||
action: (req, callback) ->
|
||||
linux.deleteUser req.account, callback
|
||||
|
||||
app.get '/public/monitor', requireAuthenticate, (req, res) ->
|
||||
async.parallel
|
||||
resources_usage: wrapAsync linux.getResourceUsageByAccounts
|
||||
system: wrapAsync linux.getSystemInfo
|
||||
storage: wrapAsync linux.getStorageInfo
|
||||
process_list: wrapAsync linux.getProcessList
|
||||
|
||||
, (err, result) ->
|
||||
exports.render 'monitor', req, result, (html) ->
|
||||
res.send html
|
||||
|
||||
monitor.run()
|
||||
|
||||
275
plugin/linux/linux.coffee
Normal file
275
plugin/linux/linux.coffee
Normal file
@@ -0,0 +1,275 @@
|
||||
child_process = require 'child_process'
|
||||
os = require 'os'
|
||||
fs = require 'fs'
|
||||
async = require 'async'
|
||||
_ = require 'underscore'
|
||||
|
||||
{cache} = app
|
||||
{wrapAsync} = app.utils
|
||||
|
||||
monitor = require './monitor'
|
||||
|
||||
exports.createUser = (account, callback) ->
|
||||
async.series [
|
||||
(callback) ->
|
||||
child_process.exec "sudo useradd -m -s /bin/bash #{account.username}", callback
|
||||
|
||||
(callback) ->
|
||||
child_process.exec "sudo usermod -G #{account.username} -a www-data", callback
|
||||
|
||||
], (err) ->
|
||||
console.error err if err
|
||||
cache.delete 'linux.getPasswdMap', ->
|
||||
callback()
|
||||
|
||||
exports.deleteUser = (account, callback) ->
|
||||
async.series [
|
||||
(callback) ->
|
||||
child_process.exec "sudo pkill -u #{account.username}", ->
|
||||
callback()
|
||||
|
||||
(callback) ->
|
||||
child_process.exec "sudo userdel -rf #{account.username}", callback
|
||||
|
||||
(callback) ->
|
||||
child_process.exec "sudo groupdel #{account.username}", callback
|
||||
|
||||
], (err) ->
|
||||
console.error err if err
|
||||
cache.delete 'linux.getPasswdMap', ->
|
||||
callback()
|
||||
|
||||
exports.setResourceLimit = (account, callback) ->
|
||||
unless 'linux' in account.billing.services
|
||||
return callback()
|
||||
|
||||
storage_limit = account.resources_limit.storage
|
||||
soft_limit = (storage_limit * 1024 * 0.8).toFixed()
|
||||
hard_limit = (storage_limit * 1024 * 1.2).toFixed()
|
||||
soft_inode_limit = (storage_limit * 64 * 0.8).toFixed()
|
||||
hard_inode_limit = (storage_limit * 64 * 1.2).toFixed()
|
||||
|
||||
child_process.exec "sudo setquota -u #{account.username} #{soft_limit} #{hard_limit} #{soft_inode_limit} #{hard_inode_limit} -a", (err) ->
|
||||
console.error err if err
|
||||
callback()
|
||||
|
||||
exports.getPasswdMap = (callback) ->
|
||||
cache.try 'linux.getPasswdMap',
|
||||
command: cache.SETEX 120
|
||||
is_json: true
|
||||
, (callback) ->
|
||||
fs.readFile '/etc/passwd', (err, content) ->
|
||||
console.error err if err
|
||||
result = {}
|
||||
|
||||
for line in _.compact(content.toString().split '\n')
|
||||
[username, password, uid] = line.split ':'
|
||||
result[uid] = username
|
||||
|
||||
callback result
|
||||
, callback
|
||||
|
||||
exports.getMemoryInfo = (callback) ->
|
||||
cache.try 'linux.getProcessList',
|
||||
command: cache.SETEX 3
|
||||
is_json: true
|
||||
, (callback) ->
|
||||
fs.readFile '/proc/meminfo', (err, content) ->
|
||||
console.error err if err
|
||||
mapping = {}
|
||||
|
||||
for line in content.toString().split('\n')
|
||||
[key, value] = line.split ':'
|
||||
if value
|
||||
mapping[key.trim()] = parseInt (parseInt(value.trim().match(/\d+/)) / 1024).toFixed()
|
||||
|
||||
used = mapping['MemTotal'] - mapping['MemFree'] - mapping['Buffers'] - mapping['Cached']
|
||||
used_per = (used / mapping['MemTotal'] * 100).toFixed()
|
||||
cached_per = (mapping['Cached'] / mapping['MemTotal'] * 100).toFixed()
|
||||
buffers_per = (mapping['Buffers'] / mapping['MemTotal'] * 100).toFixed()
|
||||
free_per = 100 - used_per - cached_per - buffers_per
|
||||
|
||||
swap_free_per = (mapping['SwapFree'] / mapping['SwapTotal'] * 100).toFixed()
|
||||
swap_used_per = 100 - swap_free_per
|
||||
|
||||
callback
|
||||
used: used
|
||||
cached: mapping['Cached']
|
||||
buffers: mapping['Buffers']
|
||||
free: mapping['MemFree']
|
||||
total: mapping['MemTotal']
|
||||
swap_used: mapping['SwapTotal'] - mapping['SwapFree']
|
||||
swap_free: mapping['SwapFree']
|
||||
swap_total: mapping['SwapTotal']
|
||||
|
||||
used_per: used_per
|
||||
cached_per: cached_per
|
||||
buffers_per: buffers_per
|
||||
free_per: free_per
|
||||
|
||||
swap_used_per: swap_used_per
|
||||
swap_free_per: swap_free_per
|
||||
|
||||
, callback
|
||||
|
||||
exports.getProcessList = (callback) ->
|
||||
cache.try 'linux.getProcessList',
|
||||
command: cache.SETEX 5
|
||||
is_json: true
|
||||
, (callback) ->
|
||||
exports.getPasswdMap (passwd_map) ->
|
||||
child_process.exec "sudo ps awufxn", (err, stdout) ->
|
||||
console.error err if err
|
||||
|
||||
callback _.map stdout.split('\n')[1 ... -1], (item) ->
|
||||
result = /^\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/.exec item
|
||||
|
||||
return {
|
||||
user: do ->
|
||||
if passwd_map[result[1]]
|
||||
return passwd_map[result[1]]
|
||||
else
|
||||
return result[1]
|
||||
|
||||
time: do ->
|
||||
[minute, second] = result[10].split ':'
|
||||
return parseInt(minute) * 60 + parseInt(second)
|
||||
|
||||
pid: parseInt result[2]
|
||||
cpu_per: parseInt result[3]
|
||||
mem_per: parseInt result[4]
|
||||
vsz: parseInt result[5]
|
||||
rss: parseInt result[6]
|
||||
tty: result[7]
|
||||
stat: result[8]
|
||||
start: result[9]
|
||||
command: result[11]
|
||||
}
|
||||
|
||||
, callback
|
||||
|
||||
exports.getStorageQuota = (callback) ->
|
||||
cache.try 'linux.getStorageQuota',
|
||||
command: cache.SETEX 60
|
||||
is_json: true
|
||||
, (callback) ->
|
||||
child_process.exec "sudo repquota -a", (err, stdout) ->
|
||||
console.error err if err
|
||||
lines = _.filter stdout.split('\n')[5...-1], (i) -> i
|
||||
|
||||
lines = _.map lines, (line) ->
|
||||
fields = _.filter line.split(' '), (i) -> i and i != ' '
|
||||
[username, __, size_used, size_soft, size_hard, inode_used, inode_soft, inode_hard, inode_grace] = fields
|
||||
|
||||
if /days/.test inode_used
|
||||
[size_grace, inode_used, inode_soft, inode_hard, inode_grace] = [inode_used, inode_soft, inode_hard, inode_grace]
|
||||
|
||||
return {
|
||||
username: username
|
||||
size_used: parseFloat (parseInt(size_used) / 1024 / 1024).toFixed(1)
|
||||
inode_used: parseInt inode_used
|
||||
}
|
||||
|
||||
callback _.indexBy lines, 'username'
|
||||
|
||||
, callback
|
||||
|
||||
exports.getSystemInfo = (callback) ->
|
||||
cache.try 'linux.getSystemInfo',
|
||||
command: cache.SETEX 30
|
||||
is_json: true
|
||||
, (callback) ->
|
||||
async.parallel
|
||||
system: (callback) ->
|
||||
fs.readFile '/etc/issue', (err, content) ->
|
||||
callback err, content.toString().replace(/\\\w/g, '').trim()
|
||||
|
||||
address: (callback) ->
|
||||
result = []
|
||||
|
||||
for name, info of os.networkInterfaces()
|
||||
for item in info
|
||||
unless item.internal
|
||||
result.push item.address
|
||||
|
||||
callback result
|
||||
|
||||
, (err, result) ->
|
||||
console.error err if err
|
||||
|
||||
callback _.extend result,
|
||||
hostname: os.hostname()
|
||||
cpu: os.cpus()[0]['model']
|
||||
uptime: os.uptime()
|
||||
loadavg: _.map os.loadavg(), (i) -> parseFloat(i.toFixed(2))
|
||||
time: new Date()
|
||||
|
||||
, callback
|
||||
|
||||
exports.getStorageInfo = (callback) ->
|
||||
cache.try 'linux.getStorageInfo',
|
||||
command: cache.SETEX 30
|
||||
is_json: true
|
||||
, (callback) ->
|
||||
child_process.exec "df -h", (err, stdout) ->
|
||||
console.error err if err
|
||||
disks = {}
|
||||
|
||||
for line in stdout.split('\n')
|
||||
[dev, size, used, available, used_per, mounted] = _.compact(line.split(' '))
|
||||
|
||||
disks[mounted] =
|
||||
dev: dev
|
||||
size: parseInt size?.match(/\d+/)
|
||||
used: parseInt used?.match(/\d+/)
|
||||
available: available
|
||||
used_per: used_per
|
||||
|
||||
root_disk = disks['/']
|
||||
|
||||
used = root_disk.used
|
||||
total = root_disk.size
|
||||
free = total - used
|
||||
|
||||
used_per = (used / total * 100).toFixed()
|
||||
free_per = 100 - used_per
|
||||
|
||||
callback
|
||||
used: used
|
||||
free: free
|
||||
total: total
|
||||
|
||||
used_per: used_per
|
||||
free_per: free_per
|
||||
|
||||
, callback
|
||||
|
||||
exports.getResourceUsageByAccounts = (callback) ->
|
||||
cache.try 'linux.getStorageInfo',
|
||||
command: cache.SETEX 20
|
||||
is_json: true
|
||||
, (callback) ->
|
||||
async.parallel
|
||||
storage_quota: wrapAsync exports.getStorageQuota
|
||||
process_list: wrapAsync exports.getProcessList
|
||||
|
||||
, (err, result) ->
|
||||
console.error err if err
|
||||
resources_usage_by_accounts = []
|
||||
|
||||
for username, usage of monitor.resources_usage
|
||||
resources_usage_by_accounts.push
|
||||
username: username
|
||||
cpu: usage.cpu
|
||||
memory: usage.memory
|
||||
storage: result.storage_quota[username]?.size_used ? 0
|
||||
process: _.filter(result.process_list, (i) -> i.user == username).length
|
||||
|
||||
callback resources_usage_by_accounts
|
||||
|
||||
, callback
|
||||
|
||||
exports.getResourceUsageByAccount = (account, callback) ->
|
||||
exports.getResourceUsageByAccounts (resources_usage_by_accounts) ->
|
||||
callback _.findWhere resources_usage_by_accounts,
|
||||
username: account.username
|
||||
@@ -1,4 +1,11 @@
|
||||
{
|
||||
"name": "Linux",
|
||||
"description": "Linux 是 RP 主机的基础服务,负责进行资源限制"
|
||||
"": "Linux",
|
||||
"server_monitor": "服务器状态",
|
||||
"description": "Linux 是 RP 主机的基础服务,负责进行资源限制",
|
||||
"widget": {
|
||||
"hour_cpu": "一小时 CPU 时间",
|
||||
"hour_memory": "一小时内存占用",
|
||||
"storage": "储存空间",
|
||||
"month_transfer": "月流量"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,119 +1,30 @@
|
||||
child_process = require 'child_process'
|
||||
os = require 'os'
|
||||
fs = require 'fs'
|
||||
async = require 'async'
|
||||
_ = require 'underscore'
|
||||
|
||||
config = require '../../config.coffee'
|
||||
{config} = app
|
||||
{mAccount} = app.models
|
||||
|
||||
mAccount = require '../../core/model/account'
|
||||
linux = require './linux'
|
||||
|
||||
REDIS_KEY = 'rp:linux:resources_usage'
|
||||
REDIS_OVERVIEW = 'rp:linux:overview'
|
||||
ITEM_IN_RESOURCES_LIST = 3600 * 1000 / config.plugins.linux.monitor_cycle
|
||||
|
||||
exports.last_plist = []
|
||||
passwd_cache = {}
|
||||
last_plist = []
|
||||
|
||||
exports.resources_usage = {}
|
||||
exports.storage_usage = {}
|
||||
|
||||
exports.run = ->
|
||||
exports.monitoring()
|
||||
setInterval exports.monitoring, config.plugins.linux.monitor_cycle
|
||||
exports.monitoring ->
|
||||
setInterval ->
|
||||
exports.monitoring ->
|
||||
, config.plugins.linux.monitor_cycle
|
||||
|
||||
exports.loadMemoryInfo = (callback) ->
|
||||
fs.readFile '/proc/meminfo', (err, content) ->
|
||||
mapping = {}
|
||||
exports.monitoring = (callback) ->
|
||||
REDIS_KEY = "#{config.redis.prefix}:linux.recent_resources_usage"
|
||||
ITEM_IN_RESOURCES_LIST = 3600 * 1000 / config.plugins.linux.monitor_cycle
|
||||
|
||||
for line in content.toString().split('\n')
|
||||
[key, value] = line.split ':'
|
||||
if value
|
||||
mapping[key.trim()] = parseInt (parseInt(value.trim().match(/\d+/)) / 1024).toFixed()
|
||||
|
||||
used = mapping['MemTotal'] - mapping['MemFree'] - mapping['Buffers'] - mapping['Cached']
|
||||
used_per = (used / mapping['MemTotal'] * 100).toFixed()
|
||||
cached_per = (mapping['Cached'] / mapping['MemTotal'] * 100).toFixed()
|
||||
buffers_per = (mapping['Buffers'] / mapping['MemTotal'] * 100).toFixed()
|
||||
free_per = 100 - used_per - cached_per - buffers_per
|
||||
|
||||
swap_free_per = (mapping['SwapFree'] / mapping['SwapTotal'] * 100).toFixed()
|
||||
swap_used_per = 100 - swap_free_per
|
||||
|
||||
callback null,
|
||||
used: used
|
||||
cached: mapping['Cached']
|
||||
buffers: mapping['Buffers']
|
||||
free: mapping['MemFree']
|
||||
total: mapping['MemTotal']
|
||||
swap_used: mapping['SwapTotal'] - mapping['SwapFree']
|
||||
swap_free: mapping['SwapFree']
|
||||
swap_total: mapping['SwapTotal']
|
||||
|
||||
used_per: used_per
|
||||
cached_per: cached_per
|
||||
buffers_per: buffers_per
|
||||
free_per: free_per
|
||||
|
||||
swap_used_per: swap_used_per
|
||||
swap_free_per: swap_free_per
|
||||
|
||||
exports.loadPasswd = (callback) ->
|
||||
app.redis.get 'rp:passwd_cache', (err, result) ->
|
||||
if result
|
||||
passwd_cache = JSON.parse result
|
||||
callback passwd_cache
|
||||
else
|
||||
fs.readFile '/etc/passwd', (err, content) ->
|
||||
throw err if err
|
||||
content = content.toString().split '\n'
|
||||
|
||||
passwd_cache = {}
|
||||
|
||||
for line in content
|
||||
if line
|
||||
[username, password, uid] = line.split ':'
|
||||
passwd_cache[uid] = username
|
||||
|
||||
app.redis.setex 'rp:passwd_cache', 120, JSON.stringify(passwd_cache), ->
|
||||
callback passwd_cache
|
||||
|
||||
exports.getProcessList = (callback) ->
|
||||
app.redis.get 'rp:process_list', (err, plist) ->
|
||||
if plist
|
||||
callback JSON.parse plist
|
||||
else
|
||||
exports.loadPasswd ->
|
||||
child_process.exec "ps awufxn", (err, stdout, stderr) ->
|
||||
plist = stdout.split('\n')[1...-1]
|
||||
|
||||
plist = _.map plist, (item) ->
|
||||
rx = /^\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/
|
||||
result = rx.exec item
|
||||
return {
|
||||
user: do ->
|
||||
if passwd_cache[result[1]]
|
||||
return passwd_cache[result[1]]
|
||||
else
|
||||
return result[1]
|
||||
pid: parseInt result[2]
|
||||
cpu_per: parseInt result[3]
|
||||
mem_per: result[4]
|
||||
vsz: result[5]
|
||||
rss: parseInt result[6]
|
||||
tty: result[7]
|
||||
stat: result[8]
|
||||
start: result[9]
|
||||
time: do ->
|
||||
[minute, second] = result[10].split ':'
|
||||
return parseInt(minute) * 60 + parseInt(second)
|
||||
command: result[11]
|
||||
}
|
||||
|
||||
app.redis.setex 'rp:process_list', 5, JSON.stringify(plist), ->
|
||||
callback plist
|
||||
|
||||
exports.monitoring = ->
|
||||
exports.loadPasswd ->
|
||||
exports.getProcessList (plist) ->
|
||||
linux.getMemoryInfo (err, memory_info) ->
|
||||
linux.getProcessList (plist) ->
|
||||
plist = _.reject plist, (item) ->
|
||||
return item.rss == 0
|
||||
|
||||
@@ -125,88 +36,57 @@ exports.monitoring = ->
|
||||
exports.monitoringMemory plist, callback
|
||||
|
||||
, (err, result) ->
|
||||
app.redis.get REDIS_KEY, (err, resources_usage_list) ->
|
||||
resources_usage_list = JSON.parse(resources_usage_list) ? []
|
||||
resources_usage_list.push result
|
||||
resources_usage_list = _.last resources_usage_list, ITEM_IN_RESOURCES_LIST
|
||||
app.redis.get REDIS_KEY, (err, recent_resources_usage) ->
|
||||
recent_resources_usage = JSON.parse(recent_resources_usage) ? []
|
||||
recent_resources_usage.push result
|
||||
recent_resources_usage = _.last recent_resources_usage, ITEM_IN_RESOURCES_LIST
|
||||
|
||||
account_usage = {}
|
||||
resources_usage = {}
|
||||
|
||||
addAccountUsage = (account_name, type, value) ->
|
||||
account_usage[account_name] ?= {}
|
||||
IncreaseAccountUsage = (username, type, value) ->
|
||||
resources_usage[username] ?= {}
|
||||
|
||||
if account_usage[account_name][type]
|
||||
account_usage[account_name][type] += value
|
||||
if resources_usage[username][type]
|
||||
resources_usage[username][type] += value
|
||||
else
|
||||
account_usage[account_name][type] = value
|
||||
resources_usage[username][type] = value
|
||||
|
||||
for item in resources_usage_list
|
||||
for item in recent_resources_usage
|
||||
for account_name, cpu_usage of item.cpu
|
||||
addAccountUsage account_name, 'cpu', cpu_usage
|
||||
IncreaseAccountUsage account_name, 'cpu', cpu_usage
|
||||
|
||||
for account_name, memory_usage of item.memory
|
||||
addAccountUsage account_name, 'memory', memory_usage
|
||||
IncreaseAccountUsage account_name, 'memory', memory_usage
|
||||
|
||||
for account_name, usage of account_usage
|
||||
usage.memory = usage.memory / resources_usage_list.length / config.plugins.linux.monitor_cycle * 1000
|
||||
for username, usage of resources_usage
|
||||
base = recent_resources_usage.length / config.plugins.linux.monitor_cycle * 1000
|
||||
usage.memory = parseFloat (usage.memory / base).toFixed(1)
|
||||
|
||||
exports.resources_usage = account_usage
|
||||
async.each _.keys(resources_usage), (username, callback) ->
|
||||
mAccount.search username, (err, account) ->
|
||||
unless account
|
||||
return callback()
|
||||
|
||||
app.redis.setex REDIS_OVERVIEW, 60, JSON.stringify(account_usage), ->
|
||||
async.each _.keys(account_usage), (account_name, callback) ->
|
||||
mAccount.byUsername account_name, (err, account) ->
|
||||
unless account
|
||||
return callback()
|
||||
if os.loadavg()[0] > 1
|
||||
if resources_usage[username].cpu > account.resources_limit.cpu
|
||||
child_process.exec "sudo pkill -SIGKILL -u #{username}", ->
|
||||
|
||||
if os.loadavg()[0] > 1
|
||||
if account_usage[account_name].cpu > account.attribute.resources_limit.cpu
|
||||
child_process.exec "sudo pkill -SIGKILL -u #{account_name}", ->
|
||||
if memory_info.used_per > 75
|
||||
if resources_usage[username].memory > account.resources_limit.memory
|
||||
child_process.exec "sudo pkill -SIGKILL -u #{username}", ->
|
||||
|
||||
exports.loadMemoryInfo (err, memory_info) ->
|
||||
if account_usage[account_name].memory > account.attribute.resources_limit.memory
|
||||
if memory_info.used_per > 70
|
||||
child_process.exec "sudo pkill -SIGKILL -u #{account_name}", ->
|
||||
|
||||
callback()
|
||||
, ->
|
||||
app.redis.set REDIS_KEY, JSON.stringify(resources_usage_list), ->
|
||||
exports.last_plist = plist
|
||||
|
||||
exports.monitoringStorage = (callback) ->
|
||||
app.redis.get 'rp:storage_usage', (err, result) ->
|
||||
if result
|
||||
callback JSON.parse result
|
||||
else
|
||||
child_process.exec "sudo repquota -a", (err, stdout, stderr) ->
|
||||
lines = stdout.split('\n')[5...-1]
|
||||
lines = _.filter lines, (i) -> i
|
||||
|
||||
lines = _.map lines, (line) ->
|
||||
fields = _.filter line.split(' '), (i) -> i and i != ' '
|
||||
[username, __, size_used, size_soft, size_hard, inode_used, inode_soft, inode_hard, inode_grace] = fields
|
||||
|
||||
if /days/.test inode_used
|
||||
[size_grace, inode_used, inode_soft, inode_hard, inode_grace] = [inode_used, inode_soft, inode_hard, inode_grace]
|
||||
|
||||
return {
|
||||
username: username
|
||||
size_used: size_used
|
||||
inode_used: inode_used
|
||||
}
|
||||
|
||||
exports.storage_usage = {}
|
||||
|
||||
for item in lines
|
||||
exports.storage_usage[item.username] = item
|
||||
|
||||
app.redis.setex 'rp:storage_usage', 3, JSON.stringify(exports.storage_usage), ->
|
||||
callback()
|
||||
callback()
|
||||
, ->
|
||||
app.redis.set REDIS_KEY, JSON.stringify(recent_resources_usage), ->
|
||||
exports.resources_usage = resources_usage
|
||||
last_plist = plist
|
||||
callback()
|
||||
|
||||
exports.monitoringCpu = (plist, callback) ->
|
||||
total_time = {}
|
||||
|
||||
findLastProcess = (process) ->
|
||||
return _.find exports.last_plist, (i) ->
|
||||
return _.find last_plist, (i) ->
|
||||
return i.pid == process.pid and i.user == process.user and i.command == process.command
|
||||
|
||||
addTime = (account_name, time) ->
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
jade = require 'jade'
|
||||
path = require 'path'
|
||||
monitor = require './monitor'
|
||||
|
||||
module.exports =
|
||||
enable: (account, callback) ->
|
||||
callback()
|
||||
|
||||
delete: (account, callback) ->
|
||||
callback()
|
||||
|
||||
widget: (account, callback) ->
|
||||
mysql = require '../mysql/service'
|
||||
mongodb = require '../mongodb/service'
|
||||
|
||||
monitor.monitoringStorage ->
|
||||
async.parallel
|
||||
mysql: _.partial(mysql.storage, account)
|
||||
mongodb: _.partial(mongodb.storage, account)
|
||||
, (err, plugin_storage) ->
|
||||
jade.renderFile path.join(__dirname, 'view/widget.jade'),
|
||||
account: account
|
||||
resources_usage: do ->
|
||||
usage = monitor.resources_usage[account.username] ? {cpu: 0, memory: 0}
|
||||
return {
|
||||
cpu:
|
||||
now_per: (usage.cpu / account.attribute.resources_limit.cpu * 100).toFixed()
|
||||
now: usage.cpu.toFixed(1)
|
||||
limit: account.attribute.resources_limit.cpu
|
||||
memory:
|
||||
now_per: (usage.memory / account.attribute.resources_limit.memory * 100).toFixed()
|
||||
now: usage.memory.toFixed(1)
|
||||
limit: account.attribute.resources_limit.memory
|
||||
}
|
||||
|
||||
storage_usage: do ->
|
||||
usage = monitor.storage_usage[account.username] ? {}
|
||||
usage.size_used = parseInt(usage?.size_used ? 0) + plugin_storage.mysql * 1000 + plugin_storage.mongodb * 1000
|
||||
now_per = (usage.size_used / 1000 / account.attribute.resources_limit.storage * 100).toFixed()
|
||||
return {
|
||||
now_per: now_per
|
||||
now: (usage.size_used / 1000).toFixed(1)
|
||||
limit: account.attribute.resources_limit.storage
|
||||
color: if now_per < 90 then 'success' else 'danger'
|
||||
}
|
||||
|
||||
, (err, html) ->
|
||||
throw err if err
|
||||
callback html
|
||||
@@ -1,5 +1,11 @@
|
||||
td {
|
||||
.progress {
|
||||
margin-bottom: 0;
|
||||
.linux-widget-table {
|
||||
tr td:first-child {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
td {
|
||||
.progress {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ prepend head
|
||||
|
||||
append head
|
||||
link(rel='stylesheet', href='/style/panel.css')
|
||||
link(rel='stylesheet', href='/style/public/monitor.css')
|
||||
link(rel='stylesheet', href='/plugin/linux/style/monitor.css')
|
||||
|
||||
block content
|
||||
#content.container
|
||||
@@ -119,6 +119,3 @@ block content
|
||||
td= process.start
|
||||
td= process.time
|
||||
td!= _.escape(process.command).replace(/ /g, ' ')
|
||||
|
||||
append footer
|
||||
script(src='/script/panel.js')
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
header Linux
|
||||
table.table.table-hover
|
||||
- limit = account.resources_limit
|
||||
|
||||
mixin displayProgressBar(now, limit, unit)
|
||||
- per = parseInt((now / limit * 100).toFixed())
|
||||
- color = per < 85 ? 'success' : 'danger'
|
||||
|
||||
.progress
|
||||
.progress-bar(class="progress-bar-#{color}", role='progressbar', aria-valuenow='#{per}',
|
||||
aria-valuemin='0', aria-valuemax='100', style='width: #{per}%;')
|
||||
span #{now} / #{limit} #{unit}
|
||||
|
||||
header= t('')
|
||||
table.table.table-hover.linux-widget-table
|
||||
tbody
|
||||
tr
|
||||
td(style='width: 200px;') 一小时 CPU 时间
|
||||
td= t('widget.hour_cpu')
|
||||
td
|
||||
.progress
|
||||
.progress-bar.progress-bar-success(role='progressbar', aria-valuenow='#{resources_usage.cpu.now_per}', aria-valuemin='0', aria-valuemax='100', style='width: #{resources_usage.cpu.now_per}%;')
|
||||
span #{resources_usage.cpu.now} / #{resources_usage.cpu.limit} s
|
||||
mixin displayProgressBar(usage.cpu, limit.cpu, 's')
|
||||
tr
|
||||
td 一小时内存占用
|
||||
td= t('widget.hour_memory')
|
||||
td
|
||||
.progress
|
||||
.progress-bar.progress-bar-success(role='progressbar', aria-valuenow='#{(resources_usage.memory.now_per}', aria-valuemin='0', aria-valuemax='100', style='width: #{resources_usage.memory.now_per}%;')
|
||||
span #{resources_usage.memory.now} / #{resources_usage.memory.limit} M
|
||||
mixin displayProgressBar(usage.memory, limit.memory, 'M')
|
||||
tr
|
||||
td 储存空间
|
||||
td= t('widget.storage')
|
||||
td
|
||||
.progress
|
||||
.progress-bar(class='progress-bar-#{storage_usage.color}',role='progressbar', aria-valuenow='#{storage_usage.now_per}', aria-valuemin='0', aria-valuemax='100', style='width: #{storage_usage.now_per}%;')
|
||||
span #{storage_usage.now} / #{storage_usage.limit} M
|
||||
mixin displayProgressBar(usage.storage, limit.storage, 'M')
|
||||
tr
|
||||
td 月流量
|
||||
td= t('widget.month_transfer')
|
||||
td
|
||||
.progress
|
||||
.progress-bar.progress-bar-success(role='progressbar', aria-valuenow='0', aria-valuemin='0', aria-valuemax='100', style='width: 0%;')
|
||||
span 0 / #{account.attribute.resources_limit.transfer} G
|
||||
mixin displayProgressBar(0, limit.transfer, 'G')
|
||||
|
||||
@@ -2,7 +2,6 @@ path = require 'path'
|
||||
jade = require 'jade'
|
||||
|
||||
{pluggable, config} = app
|
||||
{renderAccount} = app.middleware
|
||||
|
||||
module.exports = pluggable.createHelpers exports =
|
||||
name: 'rpvhost'
|
||||
@@ -11,7 +10,7 @@ module.exports = pluggable.createHelpers exports =
|
||||
exports.registerHook 'view.layout.menu_bar',
|
||||
href: '//blog.rpvhost.net'
|
||||
target: '_blank'
|
||||
body: '官方博客'
|
||||
t_body: 'plugins.rpvhost.official_blog'
|
||||
|
||||
exports.registerHook 'billing.payment_methods',
|
||||
widget_generator: (req, callback) ->
|
||||
@@ -23,5 +22,6 @@ exports.registerHook 'view.pay.display_payment_details',
|
||||
callback exports.t(req) 'view.payment_details',
|
||||
order_id: deposit_log.payload.order_id
|
||||
|
||||
app.get '/', renderAccount, (req, res) ->
|
||||
res.render path.join(__dirname, './view/index')
|
||||
app.get '/', (req, res) ->
|
||||
exports.render 'index', req, {}, (html) ->
|
||||
res.send html
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"site_name": "RP 主机",
|
||||
"taobao": "淘宝",
|
||||
"official_blog": "官方博客",
|
||||
"view": {
|
||||
"payment_tips": "拍下对应宝贝后付款即可,购买时注意选择服务器节点选项,备注填写你的用户名。",
|
||||
"go_pay": "淘宝购买",
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
action = require './action'
|
||||
service = require './service'
|
||||
linux = require '../linux/linux'
|
||||
|
||||
module.exports =
|
||||
module.exports = pluggable.createHelpers exports =
|
||||
name: 'ssh'
|
||||
type: 'service'
|
||||
|
||||
dependencies: ['linux']
|
||||
|
||||
action: action
|
||||
service: service
|
||||
exports.registerHook 'view.panel.scripts',
|
||||
path: '/plugin/ssh/style/panel.js'
|
||||
|
||||
panel:
|
||||
widget: service.widget
|
||||
script: '/script/panel.js'
|
||||
exports.registerHook 'view.panel.widgets',
|
||||
generator: (req, callback) ->
|
||||
linux.getProcessList (process_list) ->
|
||||
process_list = _.filter process_list, (i) ->
|
||||
return i.user == account.username
|
||||
|
||||
for item in plist
|
||||
item.command = (/^[^A-Za-z0.9//]*(.*)/.exec(item.command))[1]
|
||||
|
||||
exports.render 'widget', req,
|
||||
process_list: process_list
|
||||
, (html) ->
|
||||
callback html
|
||||
|
||||
app.use '/plugin/ssh', require './router'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
child_process = require 'child_process'
|
||||
|
||||
plugin = require '../../core/pluggable'
|
||||
{requireInService} = require '../../core/middleware'
|
||||
{requireInService} = app.middleware
|
||||
{cache} = app
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
@@ -11,13 +11,15 @@ exports.post '/update_password', (req, res) ->
|
||||
unless req.body.password or not /^[A-Za-z0-9\-_]+$/.test req.body.password
|
||||
return res.error 'invalid_password'
|
||||
|
||||
child_process.exec "echo '#{req.account.username}:#{req.body.password}' | sudo chpasswd", (err, stdout, stderr) ->
|
||||
throw err if err
|
||||
child_process.exec "echo '#{req.account.username}:#{req.body.password}' | sudo chpasswd", (err) ->
|
||||
console.error err if err
|
||||
res.json {}
|
||||
|
||||
exports.post '/kill', (req, res) ->
|
||||
pid = parseInt req.body.pid
|
||||
child_process.exec "sudo su #{req.account.username} -c 'kill #{pid}'", (err, stdout, stderr) ->
|
||||
throw err if err
|
||||
app.redis.del 'rp:process_list', ->
|
||||
|
||||
child_process.exec "sudo su #{req.account.username} -c 'kill #{pid}'", (err) ->
|
||||
console.error err if err
|
||||
|
||||
cache.delete 'linux.getProcessList', ->
|
||||
res.json {}
|
||||
@@ -1,55 +0,0 @@
|
||||
child_process = require 'child_process'
|
||||
jade = require 'jade'
|
||||
path = require 'path'
|
||||
|
||||
plugin = require '../../core/pluggable'
|
||||
monitor = require '../linux/monitor'
|
||||
|
||||
module.exports =
|
||||
enable: (account, callback) ->
|
||||
child_process.exec "sudo useradd -m -s /bin/bash #{account.username}", (err, stdout, stderr) ->
|
||||
throw err if err
|
||||
async.parallel [
|
||||
(callback) ->
|
||||
child_process.exec "sudo usermod -G #{account.username} -a www-data", callback
|
||||
|
||||
(callback) ->
|
||||
soft_limit = (account.attribute.resources_limit.storage * 1024 * 0.8).toFixed()
|
||||
hard_limit = (account.attribute.resources_limit.storage * 1024 * 1.2).toFixed()
|
||||
soft_inode_limit = (account.attribute.resources_limit.storage * 64 * 0.8).toFixed()
|
||||
hard_inode_limit = (account.attribute.resources_limit.storage * 64 * 1.2).toFixed()
|
||||
child_process.exec "sudo setquota -u #{account.username} #{soft_limit} #{hard_limit} #{soft_inode_limit} #{hard_inode_limit} -a", callback
|
||||
|
||||
], (err) ->
|
||||
throw err if err
|
||||
callback()
|
||||
|
||||
delete: (account, callback) ->
|
||||
async.series [
|
||||
(callback) ->
|
||||
child_process.exec "sudo pkill -u #{account.username}", ->
|
||||
callback()
|
||||
|
||||
(callback) ->
|
||||
child_process.exec "sudo userdel -rf #{account.username}", ->
|
||||
callback()
|
||||
|
||||
(callback) ->
|
||||
child_process.exec "sudo groupdel #{account.username}", ->
|
||||
callback()
|
||||
], (err) ->
|
||||
throw err if err
|
||||
callback()
|
||||
|
||||
widget: (account, callback) ->
|
||||
monitor.getProcessList (plist) ->
|
||||
plist = _.filter plist, (i) ->
|
||||
return i.user == account.username
|
||||
|
||||
for item in plist
|
||||
item.command = (/^[^A-Za-z0.9//]*(.*)/.exec(item.command))[1]
|
||||
|
||||
jade.renderFile path.join(__dirname, 'view/widget.jade'),
|
||||
plist: plist
|
||||
, (err, html) ->
|
||||
callback html
|
||||
@@ -2,7 +2,6 @@ path = require 'path'
|
||||
fs = require 'fs'
|
||||
|
||||
{pluggable} = app
|
||||
{renderAccount} = app.middleware
|
||||
|
||||
wiki = require './wiki'
|
||||
|
||||
@@ -12,7 +11,7 @@ module.exports = pluggable.createHelpers exports =
|
||||
|
||||
exports.registerHook 'view.layout.menu_bar',
|
||||
href: '/wiki/'
|
||||
body: '用户手册'
|
||||
t_body: 'plugins.wiki.'
|
||||
|
||||
for category_name in fs.readdirSync("#{__dirname}/../../WIKI")
|
||||
for file_name in fs.readdirSync("#{__dirname}/../../WIKI/#{category_name}")
|
||||
@@ -22,6 +21,6 @@ for category_name in fs.readdirSync("#{__dirname}/../../WIKI")
|
||||
language: 'zh_CN'
|
||||
content_markdown: fs.readFileSync("#{__dirname}/../../WIKI/#{category_name}/#{file_name}").toString()
|
||||
|
||||
app.get '/wiki', renderAccount, wiki.index
|
||||
app.get '/wiki', wiki.index
|
||||
|
||||
app.get '/wiki/:category/:title', renderAccount, wiki.page
|
||||
app.get '/wiki/:category/:title', wiki.page
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module.exports =
|
||||
web:
|
||||
name: 'RootPanel'
|
||||
url: 'http://rpvhost.net'
|
||||
t_name: 'plugins.rpvhost.site_name'
|
||||
url: 'http://rp.rpvhost.net'
|
||||
listen: '/home/rpadmin/rootpanel.sock'
|
||||
google_analytics_id: 'UA-49193300-2'
|
||||
google_analytics_id: ''
|
||||
|
||||
account:
|
||||
cookie_time: 30 * 24 * 3600 * 1000
|
||||
@@ -60,8 +60,6 @@ module.exports =
|
||||
password: 'password'
|
||||
prefix: 'RP'
|
||||
|
||||
redis_password: 'password'
|
||||
|
||||
email:
|
||||
send_from: 'robot@rpvhost.net'
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module.exports =
|
||||
web:
|
||||
t_name: 'plugins.rpvhost.site_name'
|
||||
url: 'http://rpvhost.net'
|
||||
url: 'http://rp.rpvhost.net'
|
||||
listen: '/home/rpadmin/rootpanel.sock'
|
||||
google_analytics_id: 'UA-49193300-2'
|
||||
google_analytics_id: ''
|
||||
|
||||
account:
|
||||
cookie_time: 30 * 24 * 3600 * 1000
|
||||
@@ -35,8 +35,13 @@ module.exports =
|
||||
unit: 24 * 3600 * 1000
|
||||
price: 10 / 30
|
||||
|
||||
services: []
|
||||
resources: {}
|
||||
services: ['ssh', 'linux']
|
||||
|
||||
resources:
|
||||
cpu: 144
|
||||
storage: 520
|
||||
transfer: 39
|
||||
memory: 27
|
||||
|
||||
mongodb:
|
||||
user: 'rpadmin'
|
||||
@@ -50,8 +55,6 @@ module.exports =
|
||||
password: 'password'
|
||||
prefix: 'RP'
|
||||
|
||||
redis_password: 'password'
|
||||
|
||||
email:
|
||||
send_from: 'robot@rpvhost.net'
|
||||
|
||||
@@ -67,3 +70,6 @@ module.exports =
|
||||
|
||||
rpvhost:
|
||||
taobao_item_id: '38370649858'
|
||||
|
||||
linux:
|
||||
monitor_cycle: 30 * 1000
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
module.exports =
|
||||
web:
|
||||
name: 'GreenShadow'
|
||||
url: 'http://greenshadow.net'
|
||||
listen: '/home/rpadmin/rootpanel.sock'
|
||||
google_analytics_id: 'UA-49193300-3'
|
||||
|
||||
account:
|
||||
cookie_time: 30 * 24 * 3600 * 1000
|
||||
|
||||
i18n:
|
||||
defaultLanguage: 'zh_CN'
|
||||
availableLanguage: ['zh_CN', 'en']
|
||||
|
||||
plugin:
|
||||
available_extensions: ['rpvhost']
|
||||
available_services: ['shadowsocks']
|
||||
|
||||
billing:
|
||||
taobao_item_id: '41040606505'
|
||||
|
||||
force_unsubscribe:
|
||||
when_balance_below: 0
|
||||
when_arrears_above: 0
|
||||
|
||||
cyclical_billing: 3600 * 1000
|
||||
daily_billing_cycle: 24 * 3600 * 1000
|
||||
|
||||
plans:
|
||||
shadowsocks:
|
||||
t_name: 'ShadowSocks'
|
||||
t_service: '按量付费'
|
||||
t_resources: '0.6 CNY / G'
|
||||
services: ['shadowsocks']
|
||||
resources: {}
|
||||
|
||||
mongodb:
|
||||
user: 'rpadmin'
|
||||
password: 'password'
|
||||
host: 'localhost'
|
||||
name: 'RootPanel'
|
||||
|
||||
redis_password: 'password'
|
||||
|
||||
email:
|
||||
send_from: 'robot@rpvhost.net'
|
||||
|
||||
account:
|
||||
service: 'Postmark'
|
||||
auth:
|
||||
user: 'postmark-api-token'
|
||||
pass: 'postmark-api-token'
|
||||
|
||||
bitcoin:
|
||||
coinbase_api_key: 'coinbase-simple-api-key'
|
||||
|
||||
plugins:
|
||||
rpvhost:
|
||||
index_page: false
|
||||
|
||||
shadowsocks:
|
||||
price_bucket: 0.06
|
||||
monitor_cycle: 60 * 1000
|
||||
billing_bucket: 100 * 1024 * 1024
|
||||
Reference in New Issue
Block a user