mirror of
https://github.com/HackPlan/RootPanel.git
synced 2026-04-29 20:16:08 +08:00
refactor plugin: shadowsocks
This commit is contained in:
@@ -19,3 +19,10 @@ script: npm test
|
|||||||
services:
|
services:
|
||||||
- mongodb
|
- mongodb
|
||||||
- redis-server
|
- redis-server
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
webhooks:
|
||||||
|
urls:
|
||||||
|
- https://webhooks.gitter.im/e/b718f15367b0c6a71f7c
|
||||||
|
on_success: always
|
||||||
|
on_failure: always
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ module.exports = class LinuxMonitoring
|
|||||||
usage.memory = usage.memory / recent_usages.length / (@monitor_cycle / 1000)
|
usage.memory = usage.memory / recent_usages.length / (@monitor_cycle / 1000)
|
||||||
|
|
||||||
Q.all [
|
Q.all [
|
||||||
@redis.setex 'linux:last_process_list', 3600, JSON.stringify process_list
|
@cache.setex 'linux:last_process_list', 3600, JSON.stringify process_list
|
||||||
@redis.setex 'linux:recent_resources_usage', 60, JSON.stringify recent_usages
|
@cache.setex 'linux:recent_resources_usage', 60, JSON.stringify recent_usages
|
||||||
]
|
]
|
||||||
|
|
||||||
monitoringCpu: (process_list) ->
|
monitoringCpu: (process_list) ->
|
||||||
|
|||||||
@@ -1,77 +1,30 @@
|
|||||||
{_, fs} = app.libs
|
module.exports = class Shadowsocks
|
||||||
{pluggable, config, utils} = app
|
constructor: (@injector) ->
|
||||||
{Financials} = app.models
|
@injector.component 'shadowsocks', new ShadowsocksComponent()
|
||||||
|
|
||||||
shadowsocks = require './shadowsocks'
|
@injector.widget 'panel',
|
||||||
|
repeating:
|
||||||
|
components:
|
||||||
|
shadowsocks: every: true
|
||||||
|
generator: (account, component) ->
|
||||||
|
root.views.render __dirname + '/view/widget'
|
||||||
|
|
||||||
shadowsocksPlugin = module.exports = new Plugin
|
setInterval =>
|
||||||
name: 'shadowsocks'
|
@getManager().monitoring().then (usages) =>
|
||||||
dependencies: ['supervisor', 'linux']
|
Q.all usages.map ({port, bytes}) =>
|
||||||
|
root.billing.usagesBilling @getAccountByPort(port), 'traffic', bytes
|
||||||
|
|
||||||
register_hooks:
|
, 5 * 60 * 1000
|
||||||
'plugin.wiki.pages':
|
|
||||||
t_category: 'plugins.shadowsocks.'
|
|
||||||
t_title: 'README.md'
|
|
||||||
language: 'zh_CN'
|
|
||||||
content_markdown: fs.readFileSync("#{__dirname}/wiki/README.md").toString()
|
|
||||||
|
|
||||||
'view.admin.sidebars':
|
getManager: (node) ->
|
||||||
generator: (req, callback) ->
|
if node
|
||||||
Financials.find
|
return new ShadowsocksManager root.servers.byName node
|
||||||
type: 'usage_billing'
|
else
|
||||||
'payload.service': 'shadowsocks'
|
return new ShadowsocksManager root.servers.master()
|
||||||
created_at:
|
|
||||||
$gte: new Date Date.now() - 30 * 24 * 3600 * 1000
|
|
||||||
, (err, financials) ->
|
|
||||||
time_range =
|
|
||||||
traffic_24hours: 24 * 3600 * 1000
|
|
||||||
traffic_3days: 3 * 24 * 3600 * 1000
|
|
||||||
traffic_7days: 7 * 24 * 3600 * 1000
|
|
||||||
traffic_30days: 30 * 24 * 3600 * 1000
|
|
||||||
|
|
||||||
traffic_result = {}
|
getAccountByPort: (port) ->
|
||||||
|
Component.findOne
|
||||||
for name, range of time_range
|
type: 'shadowsocks.shadowsocks'
|
||||||
logs = _.filter financials, (i) ->
|
'options.port': port
|
||||||
return i.created_at.getTime() > Date.now() - range
|
.then ({account_id}) ->
|
||||||
|
Account.findById account_id
|
||||||
traffic_result[name] = _.reduce logs, (memo, i) ->
|
|
||||||
return memo + i.payload.traffic_mb
|
|
||||||
, 0
|
|
||||||
|
|
||||||
exports.render 'admin/sidebar', req, traffic_result, callback
|
|
||||||
|
|
||||||
initialize: ->
|
|
||||||
app.express.use '/plugin/shadowsocks', require './router'
|
|
||||||
|
|
||||||
started: ->
|
|
||||||
shadowsocks.initSupervisor()
|
|
||||||
|
|
||||||
if @config.monitor_cycle
|
|
||||||
setInterval shadowsocks.monitoring, config.plugins.shadowsocks.monitor_cycle
|
|
||||||
|
|
||||||
shadowsocksPlugin.registerComponent
|
|
||||||
name: 'shadowsocks'
|
|
||||||
|
|
||||||
initialize: shadowsocks.initAccount
|
|
||||||
destroy: shadowsocks.deleteAccount
|
|
||||||
|
|
||||||
register_hooks:
|
|
||||||
'view.panel.scripts':
|
|
||||||
repeating: 'once'
|
|
||||||
path: '/plugin/shadowsocks/script/panel.js'
|
|
||||||
|
|
||||||
'view.panel.styles':
|
|
||||||
repeating: 'once'
|
|
||||||
path: '/plugin/shadowsocks/style/panel.css'
|
|
||||||
|
|
||||||
'view.panel.widgets':
|
|
||||||
generator: (req, callback) ->
|
|
||||||
bucket_of_gb = 1000 * 1000 * 1000 / config.plugins.shadowsocks.billing_bucket
|
|
||||||
price_gb = config.plugins.shadowsocks.price_bucket * bucket_of_gb
|
|
||||||
|
|
||||||
shadowsocks.accountUsage req.account, (result) ->
|
|
||||||
_.extend result,
|
|
||||||
transfer_remainder: req.account.billing.balance / price_gb
|
|
||||||
|
|
||||||
exports.render 'widget', req, result, callback
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
{utils, config} = app
|
|
||||||
{markdown, fs, path, express} = app.libs
|
|
||||||
{requireInService} = app.middleware
|
|
||||||
|
|
||||||
shadowsocks = require './shadowsocks'
|
|
||||||
|
|
||||||
module.exports = exports = express.Router()
|
|
||||||
|
|
||||||
exports.use requireInService 'shadowsocks'
|
|
||||||
|
|
||||||
exports.post '/reset_password', (req, res) ->
|
|
||||||
password = utils.randomString 10
|
|
||||||
|
|
||||||
req.account.update
|
|
||||||
$set:
|
|
||||||
'pluggable.shadowsocks.password': password
|
|
||||||
, ->
|
|
||||||
shadowsocks.updateConfigure ->
|
|
||||||
res.json {}
|
|
||||||
|
|
||||||
exports.post '/switch_method', (req, res) ->
|
|
||||||
unless req.body.method in config.plugins.shadowsocks.available_ciphers
|
|
||||||
return res.error 'invalid_method'
|
|
||||||
|
|
||||||
if req.body.method == req.account.pluggable.shadowsocks.method
|
|
||||||
return res.error 'already_in_method'
|
|
||||||
|
|
||||||
req.account.update
|
|
||||||
$set:
|
|
||||||
'pluggable.shadowsocks.method': req.body.method
|
|
||||||
, ->
|
|
||||||
shadowsocks.updateConfigure ->
|
|
||||||
res.json {}
|
|
||||||
26
plugins/shadowsocks/shadowsocks-component.coffee
Normal file
26
plugins/shadowsocks/shadowsocks-component.coffee
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
module.exports = class ShadowsocksComponent
|
||||||
|
@generatePort: ->
|
||||||
|
port = 10000 + Math.floor Math.random() * 40000
|
||||||
|
|
||||||
|
Component.findOne
|
||||||
|
type: 'shadowsocks.shadowsocks'
|
||||||
|
'options.port': port
|
||||||
|
.then (component) ->
|
||||||
|
if component
|
||||||
|
return ShadowsocksComponent.generatePort()
|
||||||
|
else
|
||||||
|
return port
|
||||||
|
|
||||||
|
initialize: (component) ->
|
||||||
|
|
||||||
|
update: (component) ->
|
||||||
|
|
||||||
|
destroy: (component) ->
|
||||||
|
|
||||||
|
actions: [
|
||||||
|
resetPassword:
|
||||||
|
handler: ->
|
||||||
|
|
||||||
|
setCipher:
|
||||||
|
handler: ->
|
||||||
|
]
|
||||||
@@ -1,260 +1,110 @@
|
|||||||
{_, child_process, async, fs} = app.libs
|
module.exports = class ShadowsocksManager
|
||||||
{logger, utils, config} = app
|
user: 'nobody'
|
||||||
{Account, Financials} = app.models
|
|
||||||
|
|
||||||
supervisor = require '../supervisor/supervisor'
|
constructor: (@server, {@available_ciphers}) ->
|
||||||
|
@supervisor = root.plugins.byName('supervisor').getSupervisor @server.node
|
||||||
|
@cache = root.cache
|
||||||
|
|
||||||
ShadowsocksPlugin = require './index'
|
initialize: ->
|
||||||
|
Q.all @available_ciphers.map (cipher) =>
|
||||||
|
@supervisor.writeConfig "shadowsocks-#{cipher}", program cipher
|
||||||
|
|
||||||
BILLING_BUCKET = config.plugins.shadowsocks?.billing_bucket ? 100 * 1024 * 1024
|
writeConfig: (cipher, users) ->
|
||||||
|
configure = generateConfigure cipher, users
|
||||||
|
|
||||||
exports.initSupervisor = (callback) ->
|
@server.writeFile("/etc/shadowsocks/#{cipher}.json", configure, mode: 640).then =>
|
||||||
supervisor.programsStatus (program_status) ->
|
@supervisor.updateProgram program cipher
|
||||||
async.each config.plugins.shadowsocks.available_ciphers, (method, callback) ->
|
.then =>
|
||||||
program_name = "shadowsocks-#{method}"
|
@supervisor.programControl program cipher
|
||||||
|
|
||||||
if program_name in _.pluck program_status, 'name'
|
addMonitor: ({port}) ->
|
||||||
return callback()
|
@server.command("sudo iptables -I OUTPUT -p tcp --sport #{port}").then =>
|
||||||
|
@saveIptablesRules()
|
||||||
|
|
||||||
exports.writeSupervisorConfigure method, ->
|
removeMonitor: ({port}) ->
|
||||||
supervisor.updateProgram {}, {program_name: program_name}, ->
|
@server.command("sudo iptables -D OUTPUT -p tcp --sport #{port}").then =>
|
||||||
callback()
|
@saveIptablesRules()
|
||||||
|
|
||||||
, callback
|
monitoring: ->
|
||||||
|
Q.all([
|
||||||
|
@cache.getJSON 'shadowsocks:last_traffic'
|
||||||
|
@incomingTraffic()
|
||||||
|
]).then ([last_traffic_records, traffic_records]) ->
|
||||||
|
current_traffic_records = []
|
||||||
|
|
||||||
exports.writeSupervisorConfigure = (method, callback) ->
|
Q.all traffic_records.map ({port, bytes}) ->
|
||||||
program_name = "shadowsocks-#{method}"
|
{bytes: last_bytes} = _.findWhere last_traffic_records,
|
||||||
|
port: port
|
||||||
|
|
||||||
configure = exports.generateConfigure [],
|
current_traffic_records.push
|
||||||
method: method
|
port: port
|
||||||
|
bytes: bytes
|
||||||
|
|
||||||
filename = "/etc/shadowsocks/#{method}.json"
|
if bytes < last_bytes
|
||||||
ShadowsocksPlugin.writeConfigFile filename, configure, {mode: 0o755}, ->
|
return {
|
||||||
supervisor.writeConfig {username: 'nobody'},
|
port: port
|
||||||
program_name: program_name
|
bytes: bytes
|
||||||
command: "ssserver -c #{filename}"
|
}
|
||||||
name: program_name
|
else
|
||||||
autostart: true
|
return {
|
||||||
autorestart: true
|
port: port
|
||||||
stdout_logfile: false
|
bytes: bytes - last_bytes
|
||||||
, ->
|
}
|
||||||
callback()
|
|
||||||
|
|
||||||
exports.generateConfigure = (users, options = {}) ->
|
.tap =>
|
||||||
|
@cache.setex 'shadowsocks:last_traffic', 3600, JSON.stringify current_traffic_records
|
||||||
|
|
||||||
|
incomingTraffic: ->
|
||||||
|
CHAIN_OUTPUT = 'Chain OUTPUT'
|
||||||
|
records = []
|
||||||
|
|
||||||
|
@server.command('sudo iptables -n -v -L -t filter -x --line-numbers').then ({stdout}) ->
|
||||||
|
for line in _.compact stdout.split '\n'
|
||||||
|
is_chain_output = false
|
||||||
|
|
||||||
|
if is_chain_output
|
||||||
|
try
|
||||||
|
[num, pkts, bytes, prot, opt, in_, out, source, destination, proto, port] = line.split /\s+/
|
||||||
|
|
||||||
|
unless num == 'num'
|
||||||
|
port = port.match(/spt:(\d+)/)[1]
|
||||||
|
|
||||||
|
records.push
|
||||||
|
num: parseInt num
|
||||||
|
pkts: parseInt pkts
|
||||||
|
bytes: parseInt bytes
|
||||||
|
port: parseInt port
|
||||||
|
|
||||||
|
catch e
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line[ ... CHAIN_OUTPUT.length] == CHAIN_OUTPUT
|
||||||
|
is_chain_output = true
|
||||||
|
|
||||||
|
return records
|
||||||
|
|
||||||
|
saveIptablesRules: ->
|
||||||
|
@server.command 'sudo iptables-save | sudo tee /etc/iptables.rules'
|
||||||
|
|
||||||
|
program = (cipher) ->
|
||||||
|
return {
|
||||||
|
name: "shadowsocks-#{cipher}"
|
||||||
|
user: @user
|
||||||
|
command: "ssserver -c /etc/shadowsocks/#{cipher}.json"
|
||||||
|
autostart: true
|
||||||
|
autorestart: true
|
||||||
|
}
|
||||||
|
|
||||||
|
generateConfigure = (cipher, users) ->
|
||||||
configure =
|
configure =
|
||||||
server: '0.0.0.0'
|
server: '0.0.0.0'
|
||||||
local_port: 1080
|
local_port: 1080
|
||||||
port_password: {}
|
port_password: {}
|
||||||
timeout: 60
|
timeout: 60
|
||||||
method: options.method ? 'aes-256-cfb'
|
method: cipher
|
||||||
workers: 2
|
workers: 2
|
||||||
|
|
||||||
for user in users
|
for {port, password} in users
|
||||||
configure.port_password[user.port] = user.password
|
configure.port_password[port] = password
|
||||||
|
|
||||||
return JSON.stringify configure
|
return JSON.stringify configure
|
||||||
|
|
||||||
exports.generatePort = (callback) ->
|
|
||||||
port = 10000 + Math.floor Math.random() * 10000
|
|
||||||
|
|
||||||
Account.findOne
|
|
||||||
'pluggable.shadowsocks.port': port
|
|
||||||
, (err, result) ->
|
|
||||||
if result
|
|
||||||
generatePort callback
|
|
||||||
else
|
|
||||||
callback port
|
|
||||||
|
|
||||||
exports.queryIptablesInfo = (callback) ->
|
|
||||||
child_process.exec 'sudo iptables -n -v -L -t filter -x --line-numbers', (err, stdout) ->
|
|
||||||
lines = stdout.split '\n'
|
|
||||||
iptables_info = {}
|
|
||||||
|
|
||||||
do ->
|
|
||||||
CHAIN_OUTPUT = 'Chain OUTPUT'
|
|
||||||
is_chain_output = false
|
|
||||||
|
|
||||||
for item in lines
|
|
||||||
if is_chain_output
|
|
||||||
if item
|
|
||||||
try
|
|
||||||
[num, pkts, bytes, prot, opt, in_, out, source, destination, prot, port] = item.split /\s+/
|
|
||||||
|
|
||||||
unless num == 'num'
|
|
||||||
port = port.match(/spt:(\d+)/)[1]
|
|
||||||
|
|
||||||
iptables_info[port.toString()] =
|
|
||||||
num: parseInt num
|
|
||||||
pkts: parseInt pkts
|
|
||||||
bytes: parseInt bytes
|
|
||||||
port: parseInt port
|
|
||||||
|
|
||||||
catch e
|
|
||||||
continue
|
|
||||||
|
|
||||||
if item[ ... CHAIN_OUTPUT.length] == CHAIN_OUTPUT
|
|
||||||
is_chain_output = true
|
|
||||||
|
|
||||||
callback iptables_info
|
|
||||||
|
|
||||||
exports.initAccount = (account, callback) ->
|
|
||||||
exports.generatePort (port) ->
|
|
||||||
password = utils.randomString 10
|
|
||||||
|
|
||||||
Account.findByIdAndUpdate account._id,
|
|
||||||
$set:
|
|
||||||
'pluggable.shadowsocks':
|
|
||||||
port: port
|
|
||||||
method: _.first config.plugins.shadowsocks.available_ciphers
|
|
||||||
password: password
|
|
||||||
pending_traffic: 0
|
|
||||||
last_traffic_value: 0
|
|
||||||
, (err, account) ->
|
|
||||||
logger.error err if err
|
|
||||||
|
|
||||||
child_process.exec "sudo iptables -I OUTPUT -p tcp --sport #{port}", ->
|
|
||||||
child_process.exec 'sudo iptables-save | sudo tee /etc/iptables.rules', ->
|
|
||||||
exports.updateConfigure ->
|
|
||||||
callback()
|
|
||||||
|
|
||||||
exports.deleteAccount = (account, callback) ->
|
|
||||||
exports.queryIptablesInfo (iptables_info) ->
|
|
||||||
{port} = account.pluggable.shadowsocks
|
|
||||||
|
|
||||||
billing_traffic = iptables_info[port].bytes - account.pluggable.shadowsocks.last_traffic_value
|
|
||||||
billing_traffic = iptables_info[port].bytes if billing_traffic < 0
|
|
||||||
billing_traffic += account.pluggable.shadowsocks.pending_traffic
|
|
||||||
|
|
||||||
amount = billing_traffic / BILLING_BUCKET * config.plugins.shadowsocks.price_bucket
|
|
||||||
|
|
||||||
account.update
|
|
||||||
$unset:
|
|
||||||
'pluggable.shadowsocks': true
|
|
||||||
$inc:
|
|
||||||
'billing.balance': -amount
|
|
||||||
, ->
|
|
||||||
async.series [
|
|
||||||
(callback) ->
|
|
||||||
child_process.exec "sudo iptables -D OUTPUT #{iptables_info[port].num}", callback
|
|
||||||
|
|
||||||
(callback) ->
|
|
||||||
child_process.exec 'sudo iptables-save | sudo tee /etc/iptables.rules', callback
|
|
||||||
|
|
||||||
(callback) ->
|
|
||||||
exports.updateConfigure callback
|
|
||||||
|
|
||||||
], ->
|
|
||||||
if amount > 0
|
|
||||||
Financials.create
|
|
||||||
account_id: account._id
|
|
||||||
type: 'usage_billing'
|
|
||||||
amount: -amount
|
|
||||||
payload:
|
|
||||||
service: 'shadowsocks'
|
|
||||||
traffic_mb: billing_traffic / (1000 * 1000)
|
|
||||||
, ->
|
|
||||||
callback()
|
|
||||||
else
|
|
||||||
callback()
|
|
||||||
|
|
||||||
exports.accountUsage = (account, callback) ->
|
|
||||||
Financials.find
|
|
||||||
account_id: account._id
|
|
||||||
type: 'usage_billing'
|
|
||||||
'payload.service': 'shadowsocks'
|
|
||||||
, (err, financials) ->
|
|
||||||
time_range =
|
|
||||||
traffic_24hours: 24 * 3600 * 1000
|
|
||||||
traffic_7days: 7 * 24 * 3600 * 1000
|
|
||||||
traffic_30days: 30 * 24 * 3600 * 1000
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
|
|
||||||
for name, range of time_range
|
|
||||||
logs = _.filter financials, (i) ->
|
|
||||||
return i.created_at.getTime() > Date.now() - range
|
|
||||||
|
|
||||||
result[name] = _.reduce logs, (memo, i) ->
|
|
||||||
return memo + i.payload.traffic_mb
|
|
||||||
, 0
|
|
||||||
|
|
||||||
callback result
|
|
||||||
|
|
||||||
exports.updateConfigure = (callback) ->
|
|
||||||
async.eachSeries config.plugins.shadowsocks.available_ciphers, (method, callback) ->
|
|
||||||
Account.find
|
|
||||||
'pluggable.shadowsocks.method': method
|
|
||||||
, (err, accounts) ->
|
|
||||||
users = _.map accounts, (account) ->
|
|
||||||
return account.pluggable.shadowsocks
|
|
||||||
|
|
||||||
configure = exports.generateConfigure users,
|
|
||||||
method: method
|
|
||||||
|
|
||||||
filename = "/etc/shadowsocks/#{method}.json"
|
|
||||||
ShadowsocksPlugin.writeConfigFile filename, configure, {mode: 0o755}, ->
|
|
||||||
supervisor.updateProgram {}, {program_name: "shadowsocks-#{method}"}, ->
|
|
||||||
supervisor.programControl {}, {program_name: "shadowsocks-#{method}"}, 'restart', ->
|
|
||||||
callback()
|
|
||||||
|
|
||||||
, ->
|
|
||||||
callback()
|
|
||||||
|
|
||||||
exports.monitoring = ->
|
|
||||||
exports.queryIptablesInfo (iptables_info) ->
|
|
||||||
async.each _.values(iptables_info), (item, callback) ->
|
|
||||||
{port, bytes} = item
|
|
||||||
|
|
||||||
Account.findOne
|
|
||||||
'pluggable.shadowsocks.port': port
|
|
||||||
, (err, account) ->
|
|
||||||
unless account
|
|
||||||
return callback()
|
|
||||||
|
|
||||||
{pending_traffic, last_traffic_value} = account.pluggable.shadowsocks
|
|
||||||
|
|
||||||
new_traffic = bytes - last_traffic_value
|
|
||||||
|
|
||||||
if new_traffic < 0
|
|
||||||
new_traffic = bytes
|
|
||||||
|
|
||||||
new_pending_traffic = pending_traffic + new_traffic
|
|
||||||
|
|
||||||
billing_bucket = Math.floor pending_traffic / BILLING_BUCKET
|
|
||||||
|
|
||||||
new_pending_traffic -= billing_bucket * BILLING_BUCKET
|
|
||||||
|
|
||||||
if billing_bucket > 0
|
|
||||||
amount = billing_bucket * config.plugins.shadowsocks.price_bucket
|
|
||||||
|
|
||||||
account.update
|
|
||||||
$set:
|
|
||||||
'pluggable.shadowsocks.pending_traffic': new_pending_traffic
|
|
||||||
'pluggable.shadowsocks.last_traffic_value': bytes
|
|
||||||
$inc:
|
|
||||||
'billing.balance': -amount
|
|
||||||
, (err) ->
|
|
||||||
logger.error err if err
|
|
||||||
|
|
||||||
Financials.create
|
|
||||||
account_id: account._id
|
|
||||||
type: 'usage_billing'
|
|
||||||
amount: -amount
|
|
||||||
payload:
|
|
||||||
service: 'shadowsocks'
|
|
||||||
traffic_mb: (billing_bucket * BILLING_BUCKET) / (1000 * 1000)
|
|
||||||
, ->
|
|
||||||
callback()
|
|
||||||
|
|
||||||
else if pending_traffic != new_pending_traffic or last_traffic_value != bytes
|
|
||||||
account.update
|
|
||||||
$set:
|
|
||||||
'pluggable.shadowsocks.pending_traffic': new_pending_traffic
|
|
||||||
'pluggable.shadowsocks.last_traffic_value': bytes
|
|
||||||
, (err) ->
|
|
||||||
callback()
|
|
||||||
|
|
||||||
else
|
|
||||||
callback()
|
|
||||||
|
|
||||||
, ->
|
|
||||||
|
|||||||
17
plugins/shadowsocks/view/admin.jade
Normal file
17
plugins/shadowsocks/view/admin.jade
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
prepend sidebar
|
||||||
|
.row
|
||||||
|
header= t('')
|
||||||
|
table.table.table-hover
|
||||||
|
tbody
|
||||||
|
tr
|
||||||
|
td #{(traffic_24hours / 1000).toFixed(1)}G
|
||||||
|
td= t('24hours_ago')
|
||||||
|
tr
|
||||||
|
td #{(traffic_3days / 1000).toFixed(1)}G
|
||||||
|
td= t('3days_ago')
|
||||||
|
tr
|
||||||
|
td #{(traffic_7days / 1000).toFixed(1)}G
|
||||||
|
td= t('7days_ago')
|
||||||
|
tr
|
||||||
|
td #{(traffic_30days / 1000).toFixed(1)}G
|
||||||
|
td= t('30days_ago')
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
.row
|
|
||||||
header= t('')
|
|
||||||
table.table.table-hover
|
|
||||||
tbody
|
|
||||||
tr
|
|
||||||
td #{(traffic_24hours / 1000).toFixed(1)}G
|
|
||||||
td= t('24hours_ago')
|
|
||||||
tr
|
|
||||||
td #{(traffic_3days / 1000).toFixed(1)}G
|
|
||||||
td= t('3days_ago')
|
|
||||||
tr
|
|
||||||
td #{(traffic_7days / 1000).toFixed(1)}G
|
|
||||||
td= t('7days_ago')
|
|
||||||
tr
|
|
||||||
td #{(traffic_30days / 1000).toFixed(1)}G
|
|
||||||
td= t('30days_ago')
|
|
||||||
@@ -11,9 +11,9 @@ module.exports = class Supervisor
|
|||||||
|
|
||||||
getSupervisor: (node) ->
|
getSupervisor: (node) ->
|
||||||
if node
|
if node
|
||||||
return new Supervisor root.servers.byName node
|
return new SupervisorManager root.servers.byName node
|
||||||
else
|
else
|
||||||
return new Supervisor root.servers.master()
|
return new SupervisorManager root.servers.master()
|
||||||
|
|
||||||
class SupervisorComponent
|
class SupervisorComponent
|
||||||
constructor: ({@getSupervisor}) ->
|
constructor: ({@getSupervisor}) ->
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
validator = require 'validator'
|
validator = require 'validator'
|
||||||
|
|
||||||
{mabolo} = root
|
|
||||||
|
|
||||||
status_mapping =
|
status_mapping =
|
||||||
STOPPED: 'stopped'
|
STOPPED: 'stopped'
|
||||||
STARTING: 'running'
|
STARTING: 'running'
|
||||||
@@ -12,7 +10,7 @@ status_mapping =
|
|||||||
FATAL: 'stopped'
|
FATAL: 'stopped'
|
||||||
UNKNOWN: 'stopped'
|
UNKNOWN: 'stopped'
|
||||||
|
|
||||||
module.exports = class Supervisor
|
module.exports = class SupervisorManager
|
||||||
constructor: (@server) ->
|
constructor: (@server) ->
|
||||||
|
|
||||||
writeConfig: (name, programs) ->
|
writeConfig: (name, programs) ->
|
||||||
@@ -47,12 +45,12 @@ configPath = (name) ->
|
|||||||
renderConfig = (programs) ->
|
renderConfig = (programs) ->
|
||||||
renderProgram = (program) ->
|
renderProgram = (program) ->
|
||||||
configuration = """
|
configuration = """
|
||||||
[program:#{user}-#{program.name}]
|
[program:#{program.user}-#{program.name}]
|
||||||
user = #{program.user}
|
user = #{program.user}
|
||||||
command = #{program.command}
|
command = #{program.command}
|
||||||
autostart = #{program.autostart}
|
autostart = #{program.autostart}
|
||||||
autorestart = #{program.autorestart}
|
autorestart = #{program.autorestart}
|
||||||
redirect_stderr = #{program.redirect_stderr}\n
|
redirect_stderr = true\n
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if program.directory
|
if program.directory
|
||||||
Reference in New Issue
Block a user