Fixbugs, Add tests, Admin dashboard

This commit is contained in:
jysperm
2015-08-12 20:42:47 +08:00
parent b309fd5f99
commit fa924940d8
32 changed files with 333 additions and 167 deletions

View File

@@ -1,14 +1,14 @@
{
"name": "rootpanel",
"dependencies": {
"bootstrap": "~3.3.1",
"jquery-cookie": "~1.4.1",
"underscore": "~1.7.0",
"jquery": "~2.1.2",
"backbone": "~1.1.2",
"moment": "~2.9.0",
"bootstrap": "3.3.1",
"jquery-cookie": "1.4.1",
"underscore": "1.7.0",
"jquery": "2.1.2",
"backbone": "1.1.2",
"moment": "2.9.0",
"jquery-tmpl": "git://github.com/jquery/jquery-tmpl.git#b504c8afba",
"q": "~1.2.0"
"q": "1.2.0"
},
"overrides": {
"jquery-tmpl": {

View File

@@ -1,6 +1,8 @@
_ = require 'lodash'
Q = require 'q'
{Component} = root
###
Class: Billing Plan, Managed by {BillingManager}.
###

View File

@@ -241,11 +241,15 @@ Account.register = ({username, email, password}) ->
preferences:
avatar_url: avatar_url
root.hooks.executeHooks('account.before_register',
root.hooks.executeHooks 'account.before_register',
execute: 'filter'
params: [account]
).then ->
.then ->
account.save()
.tap ->
root.hooks.executeHooks 'account.after_register',
execute: 'action'
params: [account]
###
Public: Create token for this account.
@@ -397,6 +401,11 @@ Account::joinGroup = (group) ->
$addToSet:
groups: group
Account::leaveGroup = (group) ->
@update
$pull:
groups: group
###
Public: Set email.

View File

@@ -111,8 +111,7 @@ Component::setStatus = (status) ->
Return {Promise}.
###
Component::destroy = ->
@populate().then =>
@provider.destroyComponent @
@provider.destroyComponent @
###
Public: Check has specified member.

View File

@@ -10,9 +10,23 @@ class Plugin
name: null
# Public: {String}
path: null
# Public: {Object}
config: {}
# Public: {Array} or {String}
dependencies: []
@create: ({PluginClass, name, path, config}) ->
instance = new PluginClass()
_.extend instance,
name: name
path: path
config: config
instance.injector = new InjectorHelpers instance
return instance
###
Public: Constructor.
@@ -21,6 +35,12 @@ class Plugin
###
constructor: (@injector) ->
###
Public: Resolve path based on plugin directory.
###
resolve: ->
return root.resolve 'plugins', @name, arguments...
###
Public: Get translator of plugin.
@@ -33,20 +53,8 @@ class Plugin
###
Class: Private injector of {Plugin}.
###
class Injector
###
Public: Constructor.
###
constructor: ->
###
Public: Get owner plugin.
Return {Plugin}.
###
plugin: ->
return @owner
class InjectorHelpers
constructor: (@plugin) ->
router: (path) ->
router = new Router()
@@ -55,7 +63,7 @@ class Injector
root.routers.push
path: path
router: router
plugin: @owner
plugin: @plugin
return router
@@ -64,42 +72,42 @@ class Injector
###
hook: (path, options) ->
return root.hooks.register path, _.extend options,
plugin: @owner
plugin: @plugin
###
Public: Register a view, proxy of {ViewRegistry::register}.
###
view: (view, options) ->
return root.views.register view, _.extend options,
plugin: @owner
plugin: @plugin
###
Public: Register a widget, proxy of {WidgetRegistry::register}.
###
widget: (view, options) ->
return root.widgets.register view, _.extend options,
plugin: @owner
plugin: @plugin
###
Public: Register a component, proxy of {ComponentRegistry::register}.
###
component: (name, options) ->
return root.components.register name, _.extend options,
plugin: @owner
plugin: @plugin
###
Public: Register a coupon type, proxy of {CouponTypeRegistry::register}.
###
couponType: (name, options) ->
return root.couponTypes.register name, _.extend options,
plugin: @owner
plugin: @plugin
###
Public: Register a payment provider, proxy of {PaymentProviderRegistry::register}.
###
paymentProvider: (name, options) ->
return root.paymentProviders.register name, _.extend options,
plugin: @owner
plugin: @plugin
###
Manager: Plugin manager,
@@ -126,16 +134,11 @@ module.exports = class PluginManager
if @plugins[name]
throw new Error "Plugin `#{name}` already exists"
Plugin = require path
injector = new Injector()
plugin = new Plugin injector, config
injector.owner = plugin
@plugins[name] = _.extend plugin,
@plugins[name] = plugin = Plugin.create
name: name
path: path
config: config
PluginClass: require path
plugin.activate()
@@ -166,6 +169,12 @@ module.exports = class PluginManager
return {
routers: filter root.routers
hooks: filter root.hooks.getHooksAsArray()
views: filter root.views.getExpansionsAsArray()
widgets: filter root.widgets.getWidgetsAsArray()
components: filter root.components.all()
couponTypes: filter root.couponTypes.all()
paymentProviders: filter root.paymentProviders.all()
}
PluginManager.Plugin = Plugin

View File

@@ -9,8 +9,10 @@ module.exports = class HookRegistry
constructor: ->
@hooks =
account:
# filter: (account) -> Promise
# action: (account) -> Promise
before_register: []
# action: (account) -> Promise
after_register: []
###
Public: Register a hook.
@@ -38,13 +40,13 @@ module.exports = class HookRegistry
Return {Array}.
###
applyHooks: (path, {execute, pluck, req, payload} = {}) ->
applyHooks: (path, {execute, pluck, req, params} = {}) ->
return _.compact @getHooks(path).map (hook) ->
if execute
return hook[execute].call
return hook[execute].apply
req: req
plugin: hook.plugin
, payload
, params
else if pluck
return hook[pluck]
@@ -63,6 +65,9 @@ module.exports = class HookRegistry
Q.all @applyHooks arguments...
getHooks: (path, {array, object} = {}) ->
unless path
return @hooks
words = path.split '.'
last = words.pop()
@@ -78,3 +83,26 @@ module.exports = class HookRegistry
ref[last] ?= {}
return ref[last]
getHooksAsArray: (path) ->
if path
currentPaths = path.split '.'
else
currentPaths = []
result = []
iterator = (hooks) ->
for k, v of hooks
if _.isArray v
for hook in v
result.push _.extend {}, hook,
path: [currentPaths..., k].join '.'
else if _.isObject v
currentPaths.push k
iterator v
currentPaths.pop()
iterator @getHooks path
return result

View File

@@ -84,3 +84,8 @@ module.exports = class ViewRegistry
fs.read(filename).then (source) ->
return jade.compile extendSource(source.toString()),
filename: filename
getExpansionsAsArray: ->
return _.flatten _.map @viewExtends, (expansion, view) ->
return _.extend {}, expansion,
view: view

View File

@@ -158,3 +158,8 @@ module.exports = class WidgetRegistry
.then (result) ->
return _.compact result
getWidgetsAsArray: ->
return _.flatten _.map @widgets, (widget, view) ->
return _.extend {}, widget,
view: view

View File

@@ -34,7 +34,18 @@ router.get '/', (req, res, next) ->
Response {Component}.
###
router.post '/:type', (req, res) ->
router.post '/:type', (req, res, next) ->
{account, params: {type}, body: {name, options}} = req
unless type in root.billing.availableComponents(account)
next new Error 'component_not_available'
root.components.byName(type).create account, root.servers.master(),
name: name
options: options
.done (component) ->
res.send component
, next
###
Router: PATCH /components/:id
@@ -46,6 +57,9 @@ router.patch '/:id', (req, res) ->
###
Router: DELETE /components/:id
###
router.delete '/:id', (req, res) ->
router.delete '/:id', (req, res, next) ->
req.component.destroy().done ->
res.sendStatus 204
, next
router.all '/:id/actions/:action', (req, res) ->

View File

@@ -157,7 +157,7 @@ module.exports = class ServerManager
constructor: (@config) ->
@servers = {}
for name, options of @config
for name, options of @config.servers
@servers[name] = new ServerNode _.extend options,
name: name
@@ -180,5 +180,5 @@ module.exports = class ServerManager
return @servers[name]
master: ->
return _.findWhere @servers,
return _.findWhere @all(),
master: true

View File

@@ -0,0 +1,21 @@
{createAccount} = helpers
describe 'manager.billing', ->
describe 'addMember and removeMember', ->
account = null
plan = null
before ->
plan = root.billing.byName 'sample'
createAccount().then (result) ->
account = result
it '::addMember', ->
plan.addMember account
it '::hasMember', ->
plan.hasMember(account).should.be.true
it '::removeMember', ->
plan.removeMember(account).then ->
plan.hasMember(account).should.be.false

View File

@@ -1,5 +1,7 @@
{createAccount, randomAccount} = helpers
describe 'model.account', ->
Account = require '../../model/account'
{Account} = root
describe '.register', ->
it 'should success', ->
@@ -39,3 +41,14 @@ describe 'model.account', ->
_.findWhere(account.tokens,
code: code
).type.should.be.equal 'full_access'
describe '::inGroup, ::joinGroup, ::leaveGroup', ->
it 'should success', ->
createAccount().then (account) ->
account.joinGroup('test').then ->
account.inGroup('test').should.be.true
account.inGroup('root').should.be.false
.then ->
account.leaveGroup 'test'
.then ->
account.inGroup('test').should.be.false

View File

@@ -1,5 +1,7 @@
{createAccount} = helpers
describe 'model.component', ->
Component = require '../../model/component'
{Component} = root
describe '.createComponent', ->
it 'should success', ->

View File

@@ -1,5 +1,5 @@
describe 'model.coupon-code', ->
CouponCode = require '../../model/coupon-code'
{CouponCode} = root
describe '.createCoupons', ->
it 'should success', ->

View File

@@ -1,7 +1,7 @@
describe 'model.financials', ->
Account = require '../../model/account'
Financials = require '../../model/financials'
{createAccount} = helpers
describe 'model.financials', ->
{Account, Financials} = root
{PaymentProvider} = require root.resolve 'core/registry/payment-provider'
provider = new PaymentProvider name: 'taobao'

View File

@@ -1,5 +1,7 @@
{createAccount} = helpers
describe 'model.notification', ->
Notification = require '../../model/notification'
{Notification} = root
describe '::isGroupNotice', ->
it 'return true', ->

View File

@@ -1,6 +1,7 @@
{createAccount} = helpers
describe 'model.security-log', ->
SecurityLog = require '../../model/security-log'
{Token} = require '../../model/account'
{SecurityLog, Account: {Token}} = root
describe '.createLog', ->
it 'should success', ->

View File

@@ -1,5 +1,7 @@
{createAccount, createAdmin, createTicket} = helpers
describe 'model.ticket', ->
Ticket = require '../../model/ticket'
{Ticket} = root
describe '.createTicket', ->
it 'should success', ->

View File

@@ -0,0 +1,35 @@
{createAccount} = helpers
describe 'registry.component', ->
{Component} = root
describe 'ComponentProvider', ->
describe 'create and destroyComponent', ->
initializeCount = 0
destroyCount = 0
before ->
root.components.register 'mock',
plugin:
name: 'mock'
initialize: (component) ->
initializeCount++
destroy: (component) ->
destroyCount++
it 'should success', ->
provider = root.components.byName 'mock.mock'
createAccount().then (account) ->
provider.create account, root.servers.master(),
name: 'test'
.then (component) ->
initializeCount.should.be.equal 1
component.type.should.be.equal 'mock.mock'
provider.destroyComponent(component).then ->
destroyCount.should.be.equal 1
Component.findById(component._id).then (component) ->
expect(component).to.not.exists

View File

@@ -1,3 +1,5 @@
{createAgent, randomAccount} = helpers
describe 'router.account', ->
agent = createAgent
baseUrl: '/account'

View File

@@ -0,0 +1 @@
describe 'router.component', ->

View File

@@ -1,3 +1,5 @@
{createLoggedAgent} = helpers
describe 'router.tickets', ->
agent = createLoggedAgent
baseUrl: '/tickets'

View File

@@ -2,31 +2,3 @@
h1
| RootPanel  
small= root.package.version
.row
header 付费方案
for plan in root.billing.all()
.col-md-3
.panel.panel-success
.panel-heading
strong= plan.name
.panel-body
p join_freely: #{plan.join_freely}
p components: #{_.keys(plan.components).join()}
p billing: #{_.keys(plan.billing).join()}
p 用户数量0
.row
header 已加载的插件
for plugin in root.plugins.all()
- registered = root.plugins.getRegisteredExtends(plugin)
.col-md-3
.panel.panel-default
.panel-heading
strong= plugin.name
.panel-body
p dependencies: #{_.keys(plugin.dependencies).join()}
p routes: #{_.pluck(registered.routers, 'path').join()}

View File

@@ -0,0 +1,33 @@
.row
header 付费方案
for plan in root.billing.all()
.col-md-3
.panel.panel-success
.panel-heading
strong= plan.name
.panel-body
p join_freely: #{plan.join_freely}
p components: #{_.keys(plan.components).join()}
p billing: #{_.keys(plan.billing).join()}
p 用户数量0
.row
header 已加载的插件
for plugin in root.plugins.all()
- registered = root.plugins.getRegisteredExtends(plugin)
.col-md-6
.panel.panel-default
.panel-heading
strong= plugin.name
.panel-body
p dependencies: #{_.keys(plugin.dependencies).join()}
p routes: #{_.pluck(registered.routers, 'path').join()}
p hooks: #{_.pluck(registered.hooks, 'path').join()}
p views: #{_.pluck(registered.views, 'view').join()}
p widgets: #{_.pluck(registered.widgets, 'view').join()}
p components: #{_.pluck(registered.components, 'name').join()}
p couponTypes: #{_.pluck(registered.couponTypes, 'name').join()}
p paymentProviders: #{_.pluck(registered.paymentProviders, 'name').join()}

View File

@@ -19,67 +19,67 @@
"scripts": {
"prepushlish": "bower install && gulp build",
"start": "coffee app.coffee",
"test": "mocha --compilers coffee:coffee-script/register --require test/env --recursive core/test",
"test": "mocha --compilers coffee:coffee-script/register --require test/init --recursive core/test",
"docker-build": "docker build -t=jysperm/rootpanel .",
"docker-create": "docker run --name rootpanel -P -v `pwd`:/rootpanel -i -t jysperm/rootpanel",
"docker-create": "docker run --name rootpanel -P jysperm/rootpanel",
"docker-start": "docker start -i -a rootpanel"
},
"dependencies": {
"async": "^0.9.0",
"async-q": "^0.2.2",
"body-parser": "^1.9.3",
"bower": "^1.3.12",
"bunyan-mongo": "^0.1.0",
"coffee-script": "^1.8.0",
"connect-redis": "^2.1.0",
"cookie-parser": "^1.3.3",
"counter-cache": "^0.1.0",
"csrf": "^2.0.2",
"deepmerge": "^0.2.7",
"express": "^4.10.4",
"express-bunyan-logger": "^1.1.0",
"express-session": "^1.9.2",
"insight": "^0.5.3",
"jade": "^1.9.2",
"json-stable-stringify": "^1.0.0",
"lodash": "^3.6.0",
"mabolo": "^0.3.1",
"markdown": "^0.5.0",
"moment-timezone": "^0.2.5",
"mongodb": "^2.0.7",
"morgan": "^1.5.0",
"mysql": "^2.5.3",
"negotiator": "^0.4.9",
"nodemailer": "^1.3.0",
"q": "^1.2.0",
"q-io": "^1.12.0",
"redis": "^0.12.1",
"request": "^2.55.0",
"semver": "^4.1.0",
"ssh2": "^0.3.6",
"underscore": "^1.7.0",
"validator": "^3.37.0",
"ioredis": "^1.3.6"
"async": "0.9.0",
"async-q": "0.2.2",
"body-parser": "1.9.3",
"bower": "1.3.12",
"bunyan-mongo": "0.1.0",
"coffee-script": "1.8.0",
"connect-redis": "2.1.0",
"cookie-parser": "1.3.3",
"counter-cache": "0.1.0",
"csrf": "2.0.2",
"deepmerge": "0.2.7",
"express": "4.10.4",
"express-bunyan-logger": "1.1.0",
"express-session": "1.9.2",
"insight": "0.5.3",
"jade": "1.9.2",
"json-stable-stringify": "1.0.0",
"lodash": "3.6.0",
"mabolo": "0.3.2",
"markdown": "0.5.0",
"moment-timezone": "0.2.5",
"mongodb": "2.0.7",
"morgan": "1.5.0",
"mysql": "2.5.3",
"negotiator": "0.4.9",
"nodemailer": "1.3.0",
"q": "1.2.0",
"q-io": "1.12.0",
"redis": "0.12.1",
"request": "2.55.0",
"semver": "4.1.0",
"ssh2": "0.3.6",
"underscore": "1.7.0",
"validator": "3.37.0",
"ioredis": "1.3.6"
},
"devDependencies": {
"chai": "^1.10.0",
"chai": "1.10.0",
"coffee-coverage": "0.4.2",
"del": "^1.1.1",
"gulp": "^3.8.11",
"gulp-coffee": "^2.3.1",
"gulp-concat": "^2.5.2",
"gulp-debug": "^2.0.1",
"gulp-filter": "^2.0.2",
"gulp-less": "^3.0.3",
"gulp-minify-css": "^1.1.0",
"gulp-order": "^1.1.1",
"del": "1.1.1",
"gulp": "3.8.11",
"gulp-coffee": "2.3.1",
"gulp-concat": "2.5.2",
"gulp-debug": "2.0.1",
"gulp-filter": "2.0.2",
"gulp-less": "3.0.3",
"gulp-minify-css": "1.1.0",
"gulp-order": "1.1.1",
"gulp-rsync": "0.0.5",
"gulp-shell": "^0.4.1",
"gulp-uglify": "^1.2.0",
"main-bower-files": "^2.7.0",
"mocha": "^2.0.1",
"mocha-reporter-cov-summary": "^0.1.0",
"run-sequence": "^1.1.0",
"supertest": "^0.15.0"
"gulp-shell": "0.4.1",
"gulp-uglify": "1.2.0",
"main-bower-files": "2.7.0",
"mocha": "2.0.1",
"mocha-reporter-cov-summary": "0.1.0",
"run-sequence": "1.1.0",
"supertest": "0.15.0"
}
}

View File

@@ -1,3 +1,5 @@
{Account, CouponCode} = root
module.exports = class Builtin extends root.Plugin
activate: ->
@injector.couponType 'cash', new CashCoupon()
@@ -5,6 +7,12 @@ module.exports = class Builtin extends root.Plugin
@injector.router('/').get '/', (req, res) ->
res.redirect '/panel/'
@injector.hook 'account.after_register',
action: (account) ->
Account.count().then (count) ->
if count == 1
account.joinGroup 'root'
class CashCoupon
validate: (account, coupon) ->
apply_log = _.find coupon.apply_log, (log) ->

View File

@@ -1,23 +1,24 @@
validator = require 'validator'
module.exports = class Linux
constructor: (@injector, {@monitor_cycle}) ->
{requireAuthenticate} = root.middleware
{requireAuthenticate} = root.middleware
module.exports = class Linux extends root.Plugin
activate: ->
@injector.view 'layout',
filename: __dirname + '/view/layout'
locals: linux: @
filename: @resolve 'view/layout'
locals:
linux: @
@injector.router('/public/monitor').get '/node?', requireAuthenticate, (req, res) =>
server = @getLinuxServer req.params.node
Q.all([
server.system()
server.getStorageUsages()
server.getProcessList()
server.getMemoryUsages()
]).then ([system, storage, processes, memory]) ->
res.render __dirname + '/view/monitor',
Q.all
system: server.system()
storage: server.getStorageUsages()
processes: server.getProcessList()
memory: server.getMemoryUsages()
.then (statistics) ->
res.render @resolve('view/monitor'),
system: system
storage: storage
processes: processes
@@ -50,12 +51,12 @@ module.exports = class Linux
repeating:
every: 'linux'
generator: (account, component) ->
root.views.render __dirname + '/view/widget'
root.views.render @resolve 'view/widget'
if @monitor_cycle
if @config.monitor_cycle
@monitors = root.servers.all().map ({name}) =>
return new LinuxMonitoring @getLinuxServer(name),
monitor_cycle: @monitor_cycle
monitor_cycle: @config.monitor_cycle
getLinuxServer: (node) ->
if node

View File

@@ -1,4 +1,4 @@
{
"name": "rootpanel-linux",
"version": "0.1.0"
"version": "0.9.0"
}

View File

@@ -33,7 +33,7 @@ module.exports =
arrears_above: 0
plans:
all:
sample:
name: 'plans.sample.name'
description: 'plans.sample.description'

View File

@@ -7,7 +7,7 @@ expect = chai.expect
methods = ['get', 'post', 'delete', 'put', 'patch', 'head', 'options']
module.exports = createAgent = (agent_options) ->
module.exports = (agent_options) ->
if _.isNumber config.web.listen
prefix = "http://127.0.0.1:#{config.web.listen}"
else

View File

@@ -1,7 +1,6 @@
request = require 'request'
chai = require 'chai'
_ = require 'lodash'
Q = require 'q'
utils = require '../core/utils'
createAgent = require './agent'
@@ -55,12 +54,6 @@ createLoggedAgent = (options) ->
return agent
module.exports = {
_
Q
chai
utils
expect
ifEnabled
unlessTravis

View File

@@ -1,16 +1,23 @@
process.env.NODE_ENV = 'test'
chai = require 'chai'
_ = require 'lodash'
Q = require 'q'
Root = require '../core'
snippet = require './snippet'
global.config = require '../sample/core.config.coffee'
global.helpers = require './helpers'
global.root = new Root config
root.start()
_.extend global, snippet
_.extend global,
expect: chai.expect
Q: Q
_: _
chai.should()
chai.config.includeStack = true
Q.longStackSupport = true