Admin component & Fix unit test

This commit is contained in:
jysperm
2015-08-27 19:57:07 +08:00
parent 8d6ee68eec
commit 3bc9d06c97
25 changed files with 190 additions and 114 deletions

View File

@@ -2,6 +2,5 @@
Root = require './core'
Root.findConfig(__dirname).done (config) ->
global.root = new Root config
root.start()
global.root = new Root Root.loadConfig()
root.start()

View File

@@ -1,11 +0,0 @@
$ ->
$('.action-generate-code').click ->
request '/admin/generate_coupon_code',
expired: $('.input-expired').val()
available_times: parseInt $('.input-available_times').val()
type: $('.input-type').val()
meta: JSON.parse $('.input-meta').val()
count: parseInt $('.input-count').val()
, (coupon_codes) ->
for coupon_code in coupon_codes
$('.output-coupon-code').append "#{coupon_code.code}<br />"

View File

@@ -19,19 +19,18 @@ require('node-jsx').install
Class: Root object for control RootPanel, An instance is always available as the `root` global.
###
module.exports = class Root extends EventEmitter
###
Public: Find and load configure file.
@loadConfig: ->
{existsSync} = require 'fs'
* `root_path` {String} e.g. `/home/rpadmin/RootPanel`
defaultPath = path.resolve 'sample/core.config.coffee'
configPath = path.resolve process.env.ROOTPANEL_CONFIG ? 'config.coffee'
Return {Object}.
###
@findConfig: (root_path) ->
configPath = path.resolve root_path, 'config.coffee'
defaultPath = path.resolve root_path, 'sample/core.config.coffee'
fs.exists(configPath).then (exists) ->
return require if exists then configPath else defaultPath
if existsSync configPath
return _.extend require(configPath),
config_path: configPath
else
return _.extend require(defaultPath),
config_path: defaultPath
log: console.log

View File

@@ -36,8 +36,12 @@ router.get '/dashboard', (req, res, next) ->
return _.mapValues _.indexBy(result, 'name'), 'count'
]).done ([accounts, components, tickets, accountsInPlan]) ->
props =
accounts: accounts
components: components
accounts: accounts.map (account) ->
return _.extend account, _id: account._id.toString()
components: components.map (component) ->
return _.extend component,
_id: component._id.toString()
account_id: component.account_id.toString()
tickets: tickets
package: root.package
plans: root.billing.all().map (plan) ->

7
core/test/before.coffee Normal file
View File

@@ -0,0 +1,7 @@
before ->
{Account, Component} = root
Q.all [
Account.remove()
Component.remove()
]

View File

@@ -1,14 +1,16 @@
{createAgent, randomAccount} = helpers
describe 'router.account', ->
agent = createAgent
baseUrl: '/account'
account_id = null
username = null
password = null
email = null
token = null
agent = null
before ->
agent = createAgent
baseUrl: '/account'
it 'GET login', ->
agent.get '/login'

View File

@@ -1,7 +1,10 @@
{createAdminAgent} = helpers
describe 'router.admin', ->
agent = createAdminAgent()
agent = null
before ->
agent = createAdminAgent()
it 'GET /admin/dashboard', ->
agent.get '/admin/dashboard'

View File

@@ -2,12 +2,13 @@
{Account, Component} = root
describe 'router.component', ->
agent = createLoggedAgent
baseUrl: '/components'
agent = null
component_id = null
before ->
agent = createLoggedAgent
baseUrl: '/components'
{BillingPlan} = require '../../billing-manager'
root.billing.plans['sample'].components['built-in.sample'] = {}

View File

@@ -1,11 +1,13 @@
{createLoggedAgent} = helpers
describe 'router.tickets', ->
agent = createLoggedAgent
baseUrl: '/tickets'
agent = null
ticket_id = null
before ->
agent = createLoggedAgent
baseUrl: '/tickets'
it 'POST tickets', ->
agent.post '/',
json:

View File

@@ -2,8 +2,6 @@ var React = require('react');
var {Table, Button, DropdownButton, MenuItem, Modal, Input} = require('react-bootstrap');
var _ = require('lodash');
var agent = require('../scripts/agent.coffee');
var Cookies = require('js-cookie');
var $ = require('jquery');
module.exports = AdminAccounts = React.createClass({
getInitialState: function() {
@@ -14,12 +12,6 @@ module.exports = AdminAccounts = React.createClass({
};
},
componentDidMount: function() {
$.ajaxSetup({
headers: {'X-Token': Cookies.get('token')}
});
},
showAccountDetails: function(account_id) {
this.setState({
accountDetailsModal: account_id

View File

@@ -1,8 +1,14 @@
Backbone = require 'backbone'
Cookies = require 'js-cookie'
React = require 'react'
$ = require 'jquery'
AdminDashboard = require './dashboard.jsx'
$.ajaxSetup
headers:
'X-Token': Cookies.get('token')
getInitializeProps = ->
return JSON.parse $('#initialize-props').html()

View File

@@ -0,0 +1,3 @@
.tab-pane {
padding-top: 10px;
}

View File

@@ -0,0 +1,99 @@
var React = require('react');
var {Table, Button, DropdownButton, MenuItem, Modal} = require('react-bootstrap');
var _ = require('lodash');
var agent = require('../scripts/agent.coffee');
module.exports = AdminComponents = React.createClass({
getInitialState: function() {
return {
components: this.props.components,
componentDetailsModal: null
};
},
showComponentDetails: function(component_id) {
this.setState({
componentDetailsModal: component_id
});
},
closeComponentDetails: function() {
this.setState({
componentDetailsModal: null
});
},
deleteComponent: function(component_id) {
agent.delete(`/components/${component_id}`).then( () => {
this.setState({
components: this.state.components.filter(function(originalComponent) {
return originalComponent._id != component_id;
})
});
});
},
render: function() {
return (
<Table hover>
<thead>
<tr>
<th>用户名</th>
<th>类型</th>
<th>节点</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{this.state.components.map( component => {
var account = _.findWhere(this.props.accounts, {
_id: component.account_id
});
if (!account) {
account = {
username: 'Not found'
};
}
var showComponentDetails = this.showComponentDetails.bind(this, component._id);
var deleteComponent = this.deleteComponent.bind(this, component._id);
if (this.state.componentDetailsModal == component._id) {
var componentDetailsModal = <Modal show={true} onHide={this.closeComponentDetails} bsSize='large'>
<Modal.Header closeButton>
<Modal.Title>{component._id}</Modal.Title>
</Modal.Header>
<Modal.Body>
<pre>{JSON.stringify(component, null, ' ')}</pre>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.closeComponentDetails}>关闭</Button>
</Modal.Footer>
</Modal>;
}
return (
<tr key={component._id}>
<td>{account.username}</td>
<td>{component.type}</td>
<td>{component.node}</td>
<td>{component.status}</td>
<td>
<Button bsStyle='info' bsSize='small' onClick={showComponentDetails}>
详情
</Button>
<DropdownButton title='操作' bsStyle='primary' bsSize='small'>
<MenuItem onSelect={deleteComponent}>删除元件</MenuItem>
</DropdownButton>
{componentDetailsModal}
</td>
</tr>
);
})}
</tbody>
</Table>
);
}
});

View File

@@ -1,22 +0,0 @@
table.table.table-hover
thead
th 用户
th 元件模板
th 节点
th 状态
tbody
for component in components
tr
td= component.account_id
td= component.type
td= component.node
td= component.status
td
button.btn.btn-info.btn-sm.action-details(type='button')= t('common.details')
.btn-group
button(type='button', data-toggle='dropdown').btn.btn-primary.btn-sm.dropdown-toggle
| #{t('common.actions')} &nbsp;
span.caret
ul.dropdown-menu
li
a.action-destroy-component(href='#') 删除元件

View File

@@ -1,29 +0,0 @@
form.form-horizontal
.form-group
label.col-sm-2.control-label= t('view.admin.expired')
.col-sm-5
input.input-expired.form-control(type='text', placeholder=t('view.admin.empty_expired_tips'))
.form-group
label.col-sm-2.control-label= t('view.admin.available_times')
.col-sm-5
input.input-available_times.form-control(type='text', value='1')
.form-group
label.col-sm-2.control-label= t('common.type')
.col-sm-5
select.input-type.form-control
for type in root.couponTypes.all()
option= type.name
.form-group
label.col-sm-2.control-label= t('view.admin.count')
.col-sm-5
input.input-count.form-control(type='text', value='1')
.form-group
label.col-sm-2.control-label= t('view.admin.meta')
.col-sm-5
input.input-meta.form-control(type='text', value='{"amount": 5, "category": "2014"}')
.form-group
label.col-sm-2.control-label
.col-sm-5
button.action-generate-code.btn.btn-lg.btn-primary(type='button')= t('common.generate')
pre.output-coupon-code

View File

@@ -1,7 +1,9 @@
var React = require('react');
var {TabbedArea, TabPane} = require('react-bootstrap');
var AdminExtensions = require('./extensions.jsx');
var AdminAccounts = require('./accounts.jsx');
var AdminComponents = require('./components.jsx');
module.exports = AdminDashboard = React.createClass({
render: function() {
@@ -17,8 +19,10 @@ module.exports = AdminDashboard = React.createClass({
<AdminAccounts {...this.props} />
</TabPane>
<TabPane eventKey='tickets' tab='工单'></TabPane>
<TabPane eventKey='coupons' tab='优惠和兑换'></TabPane>
<TabPane eventKey='compontents' tab='元件'></TabPane>
<TabPane eventKey='orders' tab='订单'></TabPane>
<TabPane eventKey='compontents' tab='元件'>
<AdminComponents {...this.props} />
</TabPane>
<TabPane eventKey='logs' tab='系统日志'></TabPane>
</TabbedArea>
);

View File

@@ -1,4 +1,4 @@
var {Row, Col, Panel} = require('react-bootstrap');
var {Grid, Row, Col, Panel} = require('react-bootstrap');
var React = require('react');
var _ = require('lodash');
@@ -7,9 +7,11 @@ module.exports = AdminExtensions = React.createClass({
var {plugins, plans} = this.props;
return (
<div>
<Grid fluid>
<Row>
<header>付费方案</header>
<Col md={12}>
<header>付费方案</header>
</Col>
{plans.map(function(plan) {
return (
<Col md={3} key={plan.name}>
@@ -24,7 +26,9 @@ module.exports = AdminExtensions = React.createClass({
})}
</Row>
<Row>
<header>插件</header>
<Col md={12}>
<header>插件</header>
</Col>
{plugins.map(function(plugin) {
return (
<Col md={6} key={plugin.name}>
@@ -42,7 +46,7 @@ module.exports = AdminExtensions = React.createClass({
);
})}
</Row>
</div>
</Grid>
);
}
});

View File

@@ -2,6 +2,9 @@ extends ../layout
prepend header
title= helpers.title('admin.admin_panel')
append header
link(rel='stylesheet', href='/public/admin.css')
block main
!= mainBlock

View File

@@ -4,6 +4,8 @@ module.exports = class Builtin extends root.Plugin
activate: ->
@injector.couponType 'cash', cashCoupon
@injector.paymentProvider 'manual', manualPayment
@injector.router('/').get '/', (req, res) ->
res.redirect '/panel/'
@@ -45,3 +47,6 @@ sampleComponent =
initialize: (component) ->
destroy: (component) ->
manualPayment =
populateFinancial: (req, financial) ->

View File

@@ -14,8 +14,6 @@ module.exports =
plugins:
'built-in':
enable: true
linux:
enable: true
server:
ssh:

View File

@@ -5,6 +5,8 @@ Q = require 'q'
expect = chai.expect
{config} = root
methods = ['get', 'post', 'delete', 'put', 'patch', 'head', 'options']
module.exports = (agent_options) ->
@@ -73,6 +75,6 @@ printHttpResponse = ({httpVersion, statusCode, statusMessage, headers, body}) ->
else if headers['content-type']?.match /application\/json/
body = JSON.stringify body, null, ' '
message += "\n#{body}"
message += "\n#{body}\n"
return message

View File

@@ -60,6 +60,8 @@ createAdminAgent = (options) ->
root.Account.search(agent.account.username).then (account) ->
account.joinGroup 'root'
return agent
module.exports = {
ifEnabled
unlessTravis

View File

@@ -1,17 +1,10 @@
process.env.NODE_ENV = 'test'
process.env.ROOTPANEL_CONFIG ?= 'sample/core.config.coffee'
chai = require 'chai'
_ = require 'lodash'
Q = require 'q'
Root = require '../core'
global.config = require '../sample/core.config.coffee'
global.helpers = require './helpers'
global.root = new Root config
root.start()
_.extend global,
expect: chai.expect
Q: Q
@@ -21,3 +14,13 @@ chai.should()
chai.config.includeStack = true
Q.longStackSupport = true
Root = require '../core'
config = Root.loadConfig()
config.mongodb.name = 'RootPanel-test'
global.root = new Root config
global.helpers = require './helpers'
root.start()