mirror of
https://github.com/HackPlan/RootPanel.git
synced 2026-03-26 22:16:28 +08:00
refactor joinPlan, leavePlan; Node, ComponentType, Plan, Plugin interface
This commit is contained in:
12
app.coffee
12
app.coffee
@@ -95,7 +95,6 @@ app.mailer = nodemailer.createTransport config.email.account
|
||||
app.express = express()
|
||||
|
||||
app.models = {}
|
||||
app.classes = {}
|
||||
|
||||
app.config = config
|
||||
app.db = require './core/db'
|
||||
@@ -113,10 +112,15 @@ require './core/model/Component'
|
||||
|
||||
app.templates = require './core/templates'
|
||||
app.billing = require './core/billing'
|
||||
app.clusters = require './core/clusters'
|
||||
app.middleware = require './core/middleware'
|
||||
app.notification = require './core/notification'
|
||||
|
||||
app.interfaces =
|
||||
Node: require './core/interface/Node'
|
||||
Plan: require './core/interface/Plan'
|
||||
ComponentType: require './core/interface/ComponentType'
|
||||
Plugin: require './core/interface/Plugin'
|
||||
|
||||
app.express.use bodyParser.json()
|
||||
app.express.use cookieParser()
|
||||
|
||||
@@ -137,9 +141,9 @@ app.express.use '/coupon', require './core/router/coupon'
|
||||
app.express.use '/admin', require './core/router/admin'
|
||||
app.express.use '/panel', require './core/router/panel'
|
||||
|
||||
app.billing.initPlans()
|
||||
app.clusters.initNodes()
|
||||
app.i18n.init()
|
||||
app.interfaces.Plan.initPlans()
|
||||
app.interfaces.Node.initNodes()
|
||||
app.pluggable.initPlugins()
|
||||
|
||||
app.express.get '/', (req, res) ->
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{config, pluggable, logger} = app
|
||||
{async, _} = app.libs
|
||||
{Account, Financials} = app.models
|
||||
{Account, Financials, Component} = app.models
|
||||
|
||||
billing = exports
|
||||
billing.plans = {}
|
||||
|
||||
billing.start = ->
|
||||
Account.find
|
||||
@@ -84,71 +83,6 @@ exports.generateBilling = (account, plan_name, callback) ->
|
||||
last_billing_at: new_last_billing_at
|
||||
amount_inc: -amount
|
||||
|
||||
exports.joinPlan = (req, account, plan_name, callback) ->
|
||||
original_account = account
|
||||
plan_info = config.plans[plan_name]
|
||||
|
||||
modifier =
|
||||
$addToSet:
|
||||
'billing.plans': plan_name
|
||||
'billing.services':
|
||||
$each: plan_info.services
|
||||
$set:
|
||||
'resources_limit': exports.calcResourcesLimit _.union account.billing.plans, [plan_name]
|
||||
|
||||
modifier.$set["billing.last_billing_at.#{plan_name}"] = new Date()
|
||||
|
||||
Account.findByIdAndUpdate account._id, modifier, (err, account) ->
|
||||
logger.error err if err
|
||||
|
||||
async.each _.difference(account.billing.services, original_account.billing.services), (service_name, callback) ->
|
||||
async.each pluggable.selectHook("service.#{service_name}.enable"), (hook, callback) ->
|
||||
hook.filter account, callback
|
||||
, callback
|
||||
, ->
|
||||
unless _.isEqual original_account.resources_limit, account.resources_limit
|
||||
async.each pluggable.selectHook('account.resources_limit_changed'), (hook, callback) ->
|
||||
hook.filter account, callback
|
||||
, callback
|
||||
else
|
||||
callback()
|
||||
|
||||
exports.leavePlan = (req, account, plan_name, callback) ->
|
||||
leaved_services = _.reject account.billing.services, (service_name) ->
|
||||
for item in _.without(account.billing.plans, plan_name)
|
||||
if service_name in config.plans[item].services
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
original_account = account
|
||||
|
||||
modifier =
|
||||
$pull:
|
||||
'billing.plans': plan_name
|
||||
$pullAll:
|
||||
'billing.services': leaved_services
|
||||
$set:
|
||||
'resources_limit': exports.calcResourcesLimit _.without account.billing.plans, plan_name
|
||||
$unset: {}
|
||||
|
||||
modifier.$unset["billing.last_billing_at.#{plan_name}"] = true
|
||||
|
||||
Account.findByIdAndUpdate account._id, modifier, (err, account) ->
|
||||
logger.error err if err
|
||||
|
||||
async.each leaved_services, (service_name, callback) ->
|
||||
async.each pluggable.selectHook("service.#{service_name}.disable"), (hook, callback) ->
|
||||
hook.filter account, callback
|
||||
, callback
|
||||
, ->
|
||||
unless _.isEqual original_account.resources_limit, account.resources_limit
|
||||
async.each pluggable.selectHook('account.resources_limit_changed'), (hook, callback) ->
|
||||
hook.filter account, callback
|
||||
, callback
|
||||
else
|
||||
callback()
|
||||
|
||||
exports.isForceFreeze = (account) ->
|
||||
if _.isEmpty account.billing.plans
|
||||
return false
|
||||
@@ -180,15 +114,3 @@ exports.calcResourcesLimit = (plans) ->
|
||||
limit[k] += v
|
||||
|
||||
return limit
|
||||
|
||||
exports.initPlans = ->
|
||||
for name, info in config.plans
|
||||
plan = new Plan info
|
||||
exports.plans[name] = plan
|
||||
|
||||
exports.Plan = Plan = class Plan
|
||||
info: null
|
||||
name: null
|
||||
|
||||
constructor: (@info) ->
|
||||
@name = @info.name
|
||||
|
||||
80
core/interface/ComponentType.coffee
Normal file
80
core/interface/ComponentType.coffee
Normal file
@@ -0,0 +1,80 @@
|
||||
{_, async} = app.libs
|
||||
{logger} = app
|
||||
{Node} = app.interfaces
|
||||
{Account, Component} = app.models
|
||||
|
||||
module.exports = class ComponentType
|
||||
@component_types = {}
|
||||
|
||||
@get: (name) ->
|
||||
return @component_types[name]
|
||||
|
||||
constructor: (info) ->
|
||||
_.extend @, info
|
||||
|
||||
pickPayload: (info) ->
|
||||
return info
|
||||
|
||||
# @param callback(err)
|
||||
initialize: (component, callback) ->
|
||||
callback()
|
||||
|
||||
populateComponent: (component) ->
|
||||
if component.toObject
|
||||
component = component.toObject()
|
||||
|
||||
async.parallel
|
||||
account: (callback) ->
|
||||
Account.findById component.account_id, callback
|
||||
|
||||
coworkers: (callback) ->
|
||||
async.each component.coworkers, (coworker, callback) ->
|
||||
Account.findById coworker.account_id, (err, account) ->
|
||||
callback _.extend coworker,
|
||||
account: account
|
||||
, callback
|
||||
|
||||
, (err, result) ->
|
||||
{account, coworkers} = result
|
||||
|
||||
callback _.extend component,
|
||||
account: account
|
||||
coworkers: coworkers
|
||||
physical_node: Node.nodes[component.physical_node]
|
||||
|
||||
# @param callback(err, component)
|
||||
createComponent: (account, info, callback) ->
|
||||
{name, physical_node} = info
|
||||
|
||||
Component.create
|
||||
account_id: account._id
|
||||
component_type: @name
|
||||
payload: @pickPayload info
|
||||
name: name
|
||||
physical_node: physical_node
|
||||
dependencies: {}
|
||||
, (err, component) =>
|
||||
@populateComponent component, (component) =>
|
||||
@initialize component, (err) =>
|
||||
return callback err if err
|
||||
|
||||
Component.markAsStatus component, 'running', callback
|
||||
|
||||
# @param callback(err)
|
||||
destroyComponent: (component, callback) ->
|
||||
Component.markAsStatus component, 'destroying', (err) =>
|
||||
return callback err if err
|
||||
|
||||
@populateComponent component, (component) =>
|
||||
@destroy component, (err) =>
|
||||
return callback err if err
|
||||
|
||||
Component.findByIdAndRemove component._id, callback
|
||||
|
||||
setCoworkers: (component, updates, callback) ->
|
||||
|
||||
transferOwner: ->
|
||||
|
||||
movePhysicalNode: ->
|
||||
|
||||
package: ->
|
||||
@@ -1,14 +1,18 @@
|
||||
{SSHConnection, fs, child_process, _, async} = app.libs
|
||||
{config, logger} = app
|
||||
|
||||
clusters = exports
|
||||
clusters.nodes = {}
|
||||
|
||||
clusters.Node = Node = class Node
|
||||
module.exports = class Node
|
||||
info: null
|
||||
name: null
|
||||
master: false
|
||||
|
||||
@nodes = {}
|
||||
|
||||
@initNodes: ->
|
||||
for name, info of config.nodes
|
||||
@nodes[name] = new @constructor _.extend info,
|
||||
name: name
|
||||
|
||||
@get: (name) ->
|
||||
return @nodes[name]
|
||||
|
||||
constructor: (@info) ->
|
||||
@name = @info.name
|
||||
@master = true if @info.master
|
||||
@@ -157,8 +161,3 @@ clusters.Node = Node = class Node
|
||||
readFileRemote: (filename, callback) ->
|
||||
@runCommandRemote "sudo cat #{filename}", (err, stdout) ->
|
||||
callback err, stdout
|
||||
|
||||
clusters.initNodes = ->
|
||||
for name, info of config.nodes
|
||||
clusters.nodes[name] = new Node _.extend info,
|
||||
name: name
|
||||
18
core/interface/Plan.coffee
Normal file
18
core/interface/Plan.coffee
Normal file
@@ -0,0 +1,18 @@
|
||||
module.exports = class Plan
|
||||
@plans = {}
|
||||
|
||||
@initPlans: ->
|
||||
for name, info of config.plans
|
||||
@plans[name] = new @constructor _.extend info,
|
||||
name: name
|
||||
|
||||
@get: (name) ->
|
||||
return @plans[name]
|
||||
|
||||
constructor: (info) ->
|
||||
_.extend @, info
|
||||
|
||||
for component_type, info of @available_components
|
||||
if info.default
|
||||
unless _.isArray info.default
|
||||
info.default = [info.default]
|
||||
86
core/interface/Plugin.coffee
Normal file
86
core/interface/Plugin.coffee
Normal file
@@ -0,0 +1,86 @@
|
||||
module.exports = class Plugin
|
||||
info: null
|
||||
name: null
|
||||
config: null
|
||||
path: null
|
||||
|
||||
constructor: (@info) ->
|
||||
@name = info.name
|
||||
@config = config.plugins[@name] ? {}
|
||||
@path = path.join __dirname, '../plugin', @name
|
||||
|
||||
for name, payload of info.register_hooks ? {}
|
||||
if payload.register_if
|
||||
unless payload.register_if.apply @
|
||||
continue
|
||||
|
||||
@registerHook name, payload
|
||||
|
||||
if info.initialize
|
||||
info.initialize.apply @
|
||||
|
||||
if fs.existsSync path.join(@path, 'locale')
|
||||
i18n.initPlugin @
|
||||
|
||||
if fs.existsSync path.join(@path, 'static')
|
||||
app.express.use harp.mount "/plugin/#{@name}", path.join(@path, 'static')
|
||||
|
||||
registerComponent: (info) ->
|
||||
component_meta = new ComponentMeta _.extend info,
|
||||
plugin: @
|
||||
|
||||
for name, payload of info.register_hooks ? {}
|
||||
if payload.register_if
|
||||
unless payload.register_if.apply @
|
||||
continue
|
||||
|
||||
@registerComponentHook name, _.extend payload,
|
||||
component_meta: component_meta
|
||||
|
||||
pluggable.components[info.name] = component_meta
|
||||
|
||||
registerHook: (name, payload) ->
|
||||
words = name.split '.'
|
||||
last_word = words.pop()
|
||||
|
||||
hook_path = pluggable.selectHookPath words.join('.')
|
||||
hook_path[last_word] ?= []
|
||||
|
||||
hook_path[last_word].push _.extend payload,
|
||||
plugin: @
|
||||
|
||||
getTranslator: (languages) ->
|
||||
return (name) =>
|
||||
if _.isArray languages
|
||||
t = i18n.getTranslator languages
|
||||
else
|
||||
t = i18n.getTranslatorByReq languages
|
||||
|
||||
full_name = "plugins.#{@name}.#{name}"
|
||||
|
||||
args = _.toArray arguments
|
||||
args[0] = full_name
|
||||
|
||||
full_result = t.apply @, args
|
||||
|
||||
unless full_result == full_name
|
||||
return full_result
|
||||
|
||||
return t.apply @, _.toArray(arguments)
|
||||
|
||||
render: (name, req, view_data, callback) ->
|
||||
template_path = path.join __dirname, '../plugin', @name, 'view', "#{name}.jade"
|
||||
|
||||
locals = _.extend _.clone(req.res.locals), view_data,
|
||||
account: req.account
|
||||
t: @getTranslator req
|
||||
|
||||
jade.renderFile template_path, locals, (err, html) ->
|
||||
logger.error err if err
|
||||
callback html
|
||||
|
||||
renderTemplate: (name, view_data, callback) ->
|
||||
template_path = path.join __dirname, '../plugin', @name, 'view', name
|
||||
|
||||
fs.readFile template_path, (err, template_file) ->
|
||||
callback _.template(template_file.toString()) view_data
|
||||
@@ -1,10 +1,11 @@
|
||||
{pluggable, utils, config, models} = app
|
||||
{_, async, mongoose, mongooseUniqueValidator} = app.libs
|
||||
|
||||
{Financial, SecurityLog} = app.models
|
||||
{Financial, SecurityLog, Component} = app.models
|
||||
{Plan} = app.interfaces
|
||||
|
||||
process.nextTick ->
|
||||
{Financial, SecurityLog} = app.models
|
||||
{Financial, SecurityLog, Component} = app.models
|
||||
{Plan} = app.interfaces
|
||||
|
||||
Token = mongoose.Schema
|
||||
type:
|
||||
@@ -72,9 +73,6 @@ Account = mongoose.Schema
|
||||
type: Object
|
||||
|
||||
billing:
|
||||
services:
|
||||
type: Array
|
||||
|
||||
plans:
|
||||
type: Array
|
||||
|
||||
@@ -247,5 +245,54 @@ Account.methods.createSecurityLog = (type, token, payload, callback) ->
|
||||
payload: payload
|
||||
, callback
|
||||
|
||||
Account.methods.getComponents = (type, callback) ->
|
||||
Component.find
|
||||
type: type
|
||||
, (err, components) ->
|
||||
callback components
|
||||
|
||||
Account.methods.getAvailableComponentsTypes = ->
|
||||
return _.compact _.map @billing.plans, (plan_name) ->
|
||||
return _.keys Plan.get(plan_name).available_components
|
||||
|
||||
# callback(err)
|
||||
Account.methods.joinPlan = (plan, callback) ->
|
||||
@billing.plans.addToSet plan.name
|
||||
|
||||
@save =>
|
||||
async.each _.keys(plan.available_components), (component_type, callback) =>
|
||||
plan_component_info = plan.available_components[component_type]
|
||||
component_type = pluggable.components[component_type]
|
||||
|
||||
unless plan_component_info.default
|
||||
return callback()
|
||||
|
||||
async.each plan_component_info.default, (defaultInfo, callback) =>
|
||||
default_info = defaultInfo @
|
||||
|
||||
component_type.createComponent @,
|
||||
physical_node: default_info.physical_node
|
||||
name: default_info.name ? ''
|
||||
payload: default_info
|
||||
, callback
|
||||
|
||||
, callback
|
||||
|
||||
# callback(err)
|
||||
Account.methods.leavePlan = (plan, callback) ->
|
||||
@billing.plans.pull plan.name
|
||||
available_component_types = @getAvailableComponentsTypes()
|
||||
|
||||
@save =>
|
||||
@getComponents (components) ->
|
||||
async.each components, (component, callback) ->
|
||||
if component.component_type in available_component_types
|
||||
return callback()
|
||||
|
||||
component_type = ComponentType.get component.component_type
|
||||
component_type.destroyComponent component, callback
|
||||
|
||||
, callback
|
||||
|
||||
_.extend app.models,
|
||||
Account: mongoose.model 'Account', Account
|
||||
|
||||
@@ -27,6 +27,11 @@ Component = mongoose.Schema
|
||||
enum: ['readonly', 'readwrite']
|
||||
]
|
||||
|
||||
status:
|
||||
type: String
|
||||
enum: ['running', 'initializing', 'destroying']
|
||||
default: 'initializing'
|
||||
|
||||
payload:
|
||||
type: Object
|
||||
|
||||
@@ -38,5 +43,10 @@ Component = mongoose.Schema
|
||||
type: String
|
||||
enum: []
|
||||
|
||||
Component.statics.markAsStatus = (component, status, callback) ->
|
||||
@findByIdAndUpdate component._id,
|
||||
status: status
|
||||
, callback
|
||||
|
||||
_.extend app.models,
|
||||
Component: mongoose.model 'Component', Component
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
|
||||
pluggable = exports
|
||||
|
||||
pluggable.plugins = {}
|
||||
pluggable.components = {}
|
||||
|
||||
pluggable.hooks =
|
||||
app:
|
||||
# action: function
|
||||
@@ -56,97 +53,6 @@ pluggable.hooks =
|
||||
# path
|
||||
styles: []
|
||||
|
||||
Plugin = class Plugin
|
||||
info: null
|
||||
name: null
|
||||
config: null
|
||||
path: null
|
||||
|
||||
constructor: (@info) ->
|
||||
@name = info.name
|
||||
@config = config.plugins[@name] ? {}
|
||||
@path = path.join __dirname, '../plugin', @name
|
||||
|
||||
for name, payload of info.register_hooks ? {}
|
||||
if payload.register_if
|
||||
unless payload.register_if.apply @
|
||||
continue
|
||||
|
||||
@registerHook name, payload
|
||||
|
||||
if info.initialize
|
||||
info.initialize.apply @
|
||||
|
||||
if fs.existsSync path.join(@path, 'locale')
|
||||
i18n.initPlugin @
|
||||
|
||||
if fs.existsSync path.join(@path, 'static')
|
||||
app.express.use harp.mount "/plugin/#{@name}", path.join(@path, 'static')
|
||||
|
||||
registerComponent: (info) ->
|
||||
component_meta = new ComponentMeta _.extend info,
|
||||
plugin: @
|
||||
|
||||
for path, payload of info.register_hooks ? {}
|
||||
if payload.register_if and payload.register_if.apply @
|
||||
@registerComponentHook path, _.extend payload,
|
||||
component_meta: component_meta
|
||||
|
||||
pluggable.components[info.name] = component_meta
|
||||
|
||||
registerHook: (name, payload) ->
|
||||
words = name.split '.'
|
||||
last_word = words.pop()
|
||||
|
||||
hook_path = pluggable.selectHookPath words.join('.')
|
||||
hook_path[last_word] ?= []
|
||||
|
||||
hook_path[last_word].push _.extend payload,
|
||||
plugin: @
|
||||
|
||||
getTranslator: (languages) ->
|
||||
return (name) =>
|
||||
if _.isArray languages
|
||||
t = i18n.getTranslator languages
|
||||
else
|
||||
t = i18n.getTranslatorByReq languages
|
||||
|
||||
full_name = "plugins.#{@name}.#{name}"
|
||||
|
||||
args = _.toArray arguments
|
||||
args[0] = full_name
|
||||
|
||||
full_result = t.apply @, args
|
||||
|
||||
unless full_result == full_name
|
||||
return full_result
|
||||
|
||||
return t.apply @, _.toArray(arguments)
|
||||
|
||||
render: (name, req, view_data, callback) ->
|
||||
template_path = path.join __dirname, '../plugin', @name, 'view', "#{name}.jade"
|
||||
|
||||
locals = _.extend _.clone(req.res.locals), view_data,
|
||||
account: req.account
|
||||
t: @getTranslator req
|
||||
|
||||
jade.renderFile template_path, locals, (err, html) ->
|
||||
logger.error err if err
|
||||
callback html
|
||||
|
||||
renderTemplate: (name, view_data, callback) ->
|
||||
template_path = path.join __dirname, '../plugin', @name, 'view', name
|
||||
|
||||
fs.readFile template_path, (err, template_file) ->
|
||||
callback _.template(template_file.toString()) view_data
|
||||
|
||||
ComponentMeta = class ComponentMeta
|
||||
info: null
|
||||
name: null
|
||||
|
||||
constructor: (@info) ->
|
||||
@name = @info.name
|
||||
|
||||
pluggable.selectHookPath = (name) ->
|
||||
words = name.split '.'
|
||||
|
||||
@@ -175,7 +81,3 @@ pluggable.initPlugins = ->
|
||||
throw err
|
||||
|
||||
exports.plugins[name] = plugin
|
||||
|
||||
_.extend app.classes,
|
||||
Plugin: Plugin
|
||||
ComponentMeta: ComponentMeta
|
||||
|
||||
@@ -10,7 +10,7 @@ getPasswdMap = (callback) ->
|
||||
|
||||
callback result
|
||||
|
||||
describe 'clusters', ->
|
||||
describe 'interface/Node', ->
|
||||
clusters = null
|
||||
|
||||
master = null
|
||||
@@ -44,17 +44,25 @@ linuxPlugin.registerComponent
|
||||
initialize: linux.createUser
|
||||
destroy: linux.deleteUser
|
||||
|
||||
pickPayload: (info) ->
|
||||
return {
|
||||
username: info.username
|
||||
}
|
||||
|
||||
package: ->
|
||||
unpacking: ->
|
||||
|
||||
register_hooks:
|
||||
'account.resources_limit_changed':
|
||||
repeating: 'every_node'
|
||||
filter: linux.setResourceLimit
|
||||
|
||||
'view.panel.styles':
|
||||
repeating: 'once'
|
||||
path: '/plugin/linux/style/panel.css'
|
||||
|
||||
'view.panel.widgets':
|
||||
repeating: 'every'
|
||||
generator: (req, callback) ->
|
||||
linux.getResourceUsageByAccount req.account, (resources_usage) ->
|
||||
resources_usage ?=
|
||||
|
||||
@@ -4,34 +4,38 @@
|
||||
|
||||
monitor = require './monitor'
|
||||
|
||||
exports.createUser = (account, callback) ->
|
||||
exports.createUser = (component, callback) ->
|
||||
{account, physical_node} = component
|
||||
|
||||
async.series [
|
||||
(callback) ->
|
||||
child_process.exec "sudo useradd -m -s /bin/bash #{account.username}", callback
|
||||
physical_node.runCommand "sudo useradd -m -s /bin/bash #{account.username}", callback
|
||||
|
||||
(callback) ->
|
||||
child_process.exec "sudo usermod -G #{account.username} -a www-data", callback
|
||||
physical_node.runCommand "sudo usermod -G #{account.username} -a www-data", callback
|
||||
|
||||
], (err) ->
|
||||
logger.error err if err
|
||||
cache.delete 'linux.getPasswdMap', callback
|
||||
], callback
|
||||
|
||||
exports.deleteUser = (component, callback) ->
|
||||
{account, physical_node} = component
|
||||
|
||||
exports.deleteUser = (account, callback) ->
|
||||
async.series [
|
||||
(callback) ->
|
||||
child_process.exec "sudo pkill -u #{account.username}", ->
|
||||
physical_node.runCommand "sudo pkill -u #{account.username}", (err) ->
|
||||
logger.warn err if err
|
||||
callback()
|
||||
|
||||
(callback) ->
|
||||
child_process.exec "sudo userdel -rf #{account.username}", ->
|
||||
physical_node.runCommand "sudo userdel -rf #{account.username}", (err) ->
|
||||
logger.warn err if err
|
||||
callback()
|
||||
|
||||
(callback) ->
|
||||
child_process.exec "sudo groupdel #{account.username}", ->
|
||||
physical_node.runCommand "sudo groupdel #{account.username}", (err) ->
|
||||
logger.warn err if err
|
||||
callback()
|
||||
|
||||
], ->
|
||||
cache.delete 'linux.getPasswdMap', callback
|
||||
], callback
|
||||
|
||||
exports.setResourceLimit = (account, callback) ->
|
||||
unless 'linux' in account.billing.services
|
||||
|
||||
Reference in New Issue
Block a user