Merge branch 'develop'

This commit is contained in:
jysperm
2014-10-15 23:08:16 +08:00
40 changed files with 651 additions and 613 deletions

View File

@@ -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

View File

@@ -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
View File

@@ -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"

View File

@@ -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'

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -39,7 +39,11 @@
"auto": "自动"
},
"error_code": {
"username_exist": "用户名已存在"
"username_exist": "用户名已存在",
"email_exist": "邮箱已存在",
"invalid_username": "用户名不符合要求",
"invalid_password": "密码不符合要求",
"invalid_email": "邮箱不符合要求"
},
"account": {
"": "帐号",

View File

@@ -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

View File

@@ -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

View File

@@ -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}"

View File

@@ -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',

View File

@@ -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

View File

@@ -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

View File

@@ -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) ->

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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')

View File

@@ -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)

View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -1,4 +1,11 @@
{
"name": "Linux",
"description": "Linux 是 RP 主机的基础服务,负责进行资源限制"
"": "Linux",
"server_monitor": "服务器状态",
"description": "Linux 是 RP 主机的基础服务,负责进行资源限制",
"widget": {
"hour_cpu": "一小时 CPU 时间",
"hour_memory": "一小时内存占用",
"storage": "储存空间",
"month_transfer": "月流量"
}
}

View File

@@ -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) ->

View File

@@ -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

View File

@@ -1,5 +1,11 @@
td {
.progress {
margin-bottom: 0;
.linux-widget-table {
tr td:first-child {
width: 200px;
}
td {
.progress {
margin-bottom: 0;
}
}
}

View File

@@ -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, '&nbsp;')
append footer
script(src='/script/panel.js')

View File

@@ -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')

View File

@@ -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

View File

@@ -1,6 +1,7 @@
{
"site_name": "RP 主机",
"taobao": "淘宝",
"official_blog": "官方博客",
"view": {
"payment_tips": "拍下对应宝贝后付款即可,购买时注意选择服务器节点选项,备注填写你的用户名。",
"go_pay": "淘宝购买",

View File

@@ -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'

View File

@@ -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 {}

View File

@@ -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

View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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