├── src ├── tmp │ └── .gitignore ├── public │ └── assets │ │ └── img │ │ ├── logo.png │ │ ├── user-13.jpg │ │ ├── login-bg │ │ └── bg-7.jpg │ │ ├── transparent │ │ ├── black-0.1.png │ │ ├── black-0.2.png │ │ ├── black-0.3.png │ │ ├── black-0.4.png │ │ ├── black-0.5.png │ │ ├── black-0.6.png │ │ ├── black-0.7.png │ │ ├── black-0.8.png │ │ ├── black-0.9.png │ │ ├── white-0.1.png │ │ ├── white-0.2.png │ │ ├── white-0.3.png │ │ ├── white-0.4.png │ │ ├── white-0.5.png │ │ ├── white-0.6.png │ │ ├── white-0.7.png │ │ ├── white-0.8.png │ │ ├── white-0.9.png │ │ └── white-0.98.png │ │ └── screenshots │ │ ├── koa-2-dashboard.jpeg │ │ └── koa-2-login-osx.jpeg ├── middleware │ ├── index.js │ └── checkauth.js ├── routes │ ├── main.js │ ├── open.js │ ├── auth.js │ ├── index.js │ └── mock.js ├── app.js ├── config │ ├── passport.js │ ├── config.js │ └── base.js ├── lib │ └── db.js ├── models │ └── account.js └── views │ └── main.swig ├── .babelrc ├── start.js ├── app ├── store │ ├── history.js │ └── configureStore.js ├── reducers │ ├── index.js │ ├── authed.js │ └── main.js ├── index.js ├── constants │ └── ActionTypes.js ├── components │ ├── Github.js │ ├── Exception.js │ ├── Header.js │ ├── Leftbar.js │ ├── Summary.js │ └── Trend.js ├── containers │ ├── App.js │ ├── DashboardContainer.js │ └── LoginContainer.js └── actions │ ├── main.js │ └── authed.js ├── Dockerfile ├── test └── test.spec.js ├── webpack.prod.config.js ├── webpack.config.js ├── README.md ├── package.json └── .gitignore /src/tmp/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["stage-0", "es2015"] 3 | } -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | require("babel-core/register"); 2 | require("babel-polyfill"); 3 | 4 | require("./src/app.js"); 5 | -------------------------------------------------------------------------------- /src/public/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/logo.png -------------------------------------------------------------------------------- /src/public/assets/img/user-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/user-13.jpg -------------------------------------------------------------------------------- /src/public/assets/img/login-bg/bg-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/login-bg/bg-7.jpg -------------------------------------------------------------------------------- /src/public/assets/img/transparent/black-0.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/black-0.1.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/black-0.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/black-0.2.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/black-0.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/black-0.3.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/black-0.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/black-0.4.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/black-0.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/black-0.5.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/black-0.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/black-0.6.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/black-0.7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/black-0.7.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/black-0.8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/black-0.8.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/black-0.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/black-0.9.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/white-0.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/white-0.1.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/white-0.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/white-0.2.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/white-0.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/white-0.3.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/white-0.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/white-0.4.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/white-0.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/white-0.5.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/white-0.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/white-0.6.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/white-0.7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/white-0.7.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/white-0.8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/white-0.8.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/white-0.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/white-0.9.png -------------------------------------------------------------------------------- /src/public/assets/img/transparent/white-0.98.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/transparent/white-0.98.png -------------------------------------------------------------------------------- /src/public/assets/img/screenshots/koa-2-dashboard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/screenshots/koa-2-dashboard.jpeg -------------------------------------------------------------------------------- /src/public/assets/img/screenshots/koa-2-login-osx.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superalsrk/koa2-boilerplate/HEAD/src/public/assets/img/screenshots/koa-2-login-osx.jpeg -------------------------------------------------------------------------------- /app/store/history.js: -------------------------------------------------------------------------------- 1 | import { useRouterHistory} from 'react-router'; 2 | import { createHashHistory } from 'history'; 3 | 4 | const appHistory = useRouterHistory(createHashHistory)({ queryKey: false }) 5 | 6 | export default appHistory -------------------------------------------------------------------------------- /app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import authed from './authed'; 3 | import main from './main'; 4 | 5 | 6 | const rootReducer = combineReducers({ 7 | authed, 8 | main 9 | }) 10 | 11 | export default rootReducer -------------------------------------------------------------------------------- /src/middleware/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import compose from 'koa-compose'; 4 | import checkauth from './checkauth'; 5 | 6 | export default function middleware() { 7 | return compose( 8 | [ 9 | checkauth() 10 | ] 11 | ) 12 | } -------------------------------------------------------------------------------- /src/routes/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Router from 'koa-router'; 4 | 5 | const router = new Router(); 6 | 7 | router.get('/', async (ctx, next) => { 8 | ctx.body = { 9 | "status" : "home" 10 | } 11 | }) 12 | 13 | router.get('/app', async (ctx, next) => { 14 | ctx.body = { 15 | "status" : "app" 16 | } 17 | }) 18 | 19 | 20 | export default router; -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6.3.0 2 | 3 | MAINTAINER superalsrk "https://github.com/superalsrk" 4 | 5 | RUN mkdir -p /var/app 6 | 7 | WORKDIR /var/app 8 | 9 | COPY . /var/app/ 10 | 11 | RUN npm install 12 | 13 | RUN npm run build 14 | 15 | RUN npm install -g pm2 16 | 17 | ENV NODE_ENV production 18 | 19 | EXPOSE 5000 20 | 21 | # USER nobody 22 | WORKDIR /var/app 23 | 24 | CMD ["pm2", "start", "start.js", "--no-daemon"] -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | 5 | 6 | import configureStore from './store/configureStore' 7 | import App from './containers/App' 8 | 9 | 10 | const store = configureStore(); 11 | 12 | 13 | render( 14 | 15 | 16 | , 17 | document.getElementById('page-container') 18 | ) -------------------------------------------------------------------------------- /app/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | export const AUTH_SUCCESS = 'AUTH_SUCCESS' 2 | export const AUTH_FAILED = 'AUTH_FAILED' 3 | 4 | export const AUTH_STATUS_SUCCESS = 'AUTH_STATUS_SUCCESS' 5 | export const AUTH_STATUS_FAILED = 'AUTH_STATUS_FAIL' 6 | 7 | export const MAIN_RECEIVE_SUMMARY = 'MAIN_RECEIVE_SUMMARY' 8 | export const MAIN_RECEIVE_LINECHART = 'MAIN_RECEIVE_LINECHART' 9 | export const MAIN_LINECHART_DELETED = 'MAIN_LINECHART_DELETED' -------------------------------------------------------------------------------- /app/reducers/authed.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import * as types from '../constants/ActionTypes'; 3 | 4 | 5 | const auth = (state = [], action) => { 6 | switch(action.type) { 7 | case types.AUTH_SUCCESS: 8 | return state; 9 | case types.AUTH_FAILED: 10 | return state; 11 | default: 12 | return state; 13 | } 14 | } 15 | 16 | export default auth; 17 | 18 | -------------------------------------------------------------------------------- /src/middleware/checkauth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default function checkauth() { 4 | return async function (ctx, next) { 5 | 6 | if (ctx.isAuthenticated() 7 | || ctx.path.indexOf('/auth/') >= 0 8 | || ctx.path.indexOf('/open/') >= 0 9 | || ctx.path==='/' 10 | || ctx.path.indexOf('.html') >= 0) { 11 | await next() 12 | } else { 13 | ctx.body = { 14 | "status" : 401 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/components/Github.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import GitHubForkRibbon from 'react-github-fork-ribbon'; 3 | 4 | class Github extends React.Component { 5 | 6 | render() { 7 | return ( 8 | 12 | Fork me on GitHub 13 | 14 | ) 15 | } 16 | } 17 | 18 | export default Github 19 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Koa from 'koa'; 4 | import baseconfig from './config/base'; 5 | import middleware from './middleware'; 6 | import routes from './routes'; 7 | import config from './config/config'; 8 | import log4js from 'log4js'; 9 | 10 | const app = new Koa(); 11 | const LOG = log4js.getLogger('file') 12 | 13 | 14 | //configure basic app 15 | baseconfig(app) 16 | 17 | //configure custom middleware 18 | app.use(middleware()) 19 | 20 | //configure custom routes 21 | app.use(routes()) 22 | 23 | app.listen(config.app.port); 24 | LOG.info("Server started, listening on port: " + config.app.port); 25 | 26 | export default app -------------------------------------------------------------------------------- /app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import thunkMiddleware from 'redux-thunk'; 3 | import rootReducer from '../reducers'; 4 | import createLogger from 'redux-logger'; 5 | 6 | 7 | const middlewares = [thunkMiddleware] 8 | 9 | console.log(process.env) 10 | 11 | if (process.env.NODE_ENV === `development`) { 12 | 13 | middlewares.push(createLogger()); 14 | } 15 | 16 | 17 | const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore); 18 | 19 | export default function configureStore(preloadedState) { 20 | const store = createStoreWithMiddleware(rootReducer, preloadedState); 21 | return store 22 | } -------------------------------------------------------------------------------- /src/routes/open.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Router from 'koa-router'; 4 | import NodeExcel from 'excel-export'; 5 | 6 | const router = new Router(); 7 | 8 | router.get('/export', async (ctx, next) => { 9 | let conf = {} 10 | conf.cols = [{ 11 | caption:'Header', 12 | type:'string', 13 | width: 145 14 | }] 15 | 16 | conf.rows = [['value01'], ['value02'], ['value03']] 17 | 18 | ctx.type = 'application/vnd.openxmlformats' 19 | ctx.attachment( "Report.xlsx") 20 | ctx.body = new Buffer(NodeExcel.execute(conf), 'binary') 21 | }) 22 | 23 | router.get('/test', async(ctx, next) => { 24 | ctx.body = { 25 | 'status' : 'done' 26 | } 27 | }) 28 | 29 | export default router; -------------------------------------------------------------------------------- /src/config/passport.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import passport from 'koa-passport'; 4 | import AccountModel from '../models/account'; 5 | 6 | passport.serializeUser(function(user, done) { 7 | done(null, user.id) 8 | }) 9 | 10 | passport.deserializeUser(function(id, done) { 11 | AccountModel.findOne(id, function(err, user) { 12 | done(err, user) 13 | }) 14 | }) 15 | 16 | var LocalStrategy = require('passport-local').Strategy 17 | 18 | passport.use(new LocalStrategy(function(username, password, done) { 19 | 20 | AccountModel.verify(username, password) 21 | .then(function(result) { 22 | if(result != null) { 23 | done(null, result) 24 | } else { 25 | done(null, false) 26 | } 27 | }) 28 | })) 29 | -------------------------------------------------------------------------------- /src/lib/db.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import mysql from 'mysql'; 4 | 5 | const db = {} 6 | 7 | const mysqlPool = mysql.createPool({ 8 | connectionLimit : 10, 9 | host : 'localhost', 10 | user : 'test', 11 | password : 'test', 12 | database : 'test' 13 | }); 14 | 15 | db.pool = mysqlPool; 16 | 17 | db.query = async function(sql, value) { 18 | return new Promise((resolve, reject) => { 19 | mysqlPool.query(sql, value, function(err, results, fields) { 20 | if(err) { 21 | reject(Error(err)) 22 | } else { 23 | resolve(results) 24 | } 25 | }) 26 | }) 27 | } 28 | 29 | db.queryAsync = function(sql, value, cb) { 30 | mysqlPool.query(sql, value, function(err, results, fields) { 31 | cb(err, results, fields) 32 | }) 33 | } 34 | 35 | export default db; 36 | -------------------------------------------------------------------------------- /app/containers/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import LoginContainer from '../containers/LoginContainer'; 4 | import DashboardContainer from '../containers/DashboardContainer'; 5 | import ExceptionComponent from '../components/Exception'; 6 | 7 | import { Router, Route , Redirect} from 'react-router'; 8 | import history from '../store/history'; 9 | 10 | class App extends React.Component { 11 | 12 | componentDidMount() { 13 | 14 | } 15 | 16 | render() { 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ) 27 | } 28 | } 29 | 30 | export default App -------------------------------------------------------------------------------- /src/routes/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Router from 'koa-router'; 4 | import passport from 'koa-passport'; 5 | 6 | const router = new Router(); 7 | 8 | router.get('/login', async (ctx, next) => { 9 | ctx.body = { 10 | "status" : "login page" 11 | } 12 | }) 13 | 14 | 15 | router.post('/login', async (ctx, next) => { 16 | let middleware = passport.authenticate('local', async(user, info) => { 17 | if (user === false) { 18 | ctx.body = { 19 | 'status' : 400 20 | } 21 | } else { 22 | await ctx.login(user) 23 | ctx.body = { 24 | user: user 25 | } 26 | } 27 | }) 28 | await middleware.call(this, ctx, next) 29 | }) 30 | 31 | router.get('/logout', async (ctx, newt) => { 32 | ctx.logout() 33 | ctx.redirect('/') 34 | }) 35 | 36 | router.get('/status', async (ctx, next) => { 37 | ctx.body = { 38 | "isLogin" : ctx.isAuthenticated() 39 | } 40 | }) 41 | 42 | export default router; 43 | -------------------------------------------------------------------------------- /app/components/Exception.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import GitHubForkRibbon from 'react-github-fork-ribbon'; 3 | 4 | class Exception extends React.Component { 5 | componentDidMount() { 6 | document.body.classList.remove('bg-white') 7 | } 8 | 9 | render() { 10 | return ( 11 | 12 |
13 |
404
14 |
15 |
We couldn't find it...
16 |
17 | The page you're looking for doesn't exist.

18 | Perhaps, there pages will help find what you're looking for. 19 |
20 |
21 | Go Back to Home Page 22 |
23 |
24 |
25 | ) 26 | } 27 | } 28 | 29 | export default Exception 30 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import compose from 'koa-compose'; 4 | import Router from 'koa-router'; 5 | 6 | 7 | import RouterMain from './main'; 8 | import RouterAuth from './auth'; 9 | import RouterOpen from './open'; 10 | import RouterMock from './mock'; 11 | 12 | const router = new Router(); 13 | 14 | 15 | router.get('/', async (ctx, next) => { 16 | // ctx.type = 'html' 17 | // ctx.body = require('fs').createReadStream(__dirname + '/../public/main.html') 18 | 19 | await ctx.render('./main') 20 | }) 21 | 22 | 23 | router.use('/api', RouterMain.routes(), RouterMain.allowedMethods()) 24 | router.use('/auth', RouterAuth.routes(), RouterAuth.allowedMethods()) 25 | router.use('/open', RouterOpen.routes(), RouterOpen.allowedMethods()) 26 | router.use('/mock', RouterMock.routes(), RouterMock.allowedMethods()) 27 | 28 | router.get('*', async (ctx, next) => { 29 | ctx.body = { status : 404 } 30 | }) 31 | 32 | export default function routes() { 33 | return compose( 34 | [ 35 | router.routes(), 36 | router.allowedMethods() 37 | ] 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /app/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | class Header extends React.Component { 4 | 5 | render() { 6 | return ( 7 | 27 | ) 28 | } 29 | } 30 | 31 | export default Header 32 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const path = require("path"); 3 | const _ = require("lodash"); 4 | 5 | let env = process.env.NODE_ENV = process.env.NODE_ENV || "development"; 6 | 7 | let base = { 8 | app: { 9 | root: path.normalize(path.join(__dirname, "/..")), 10 | env: env, 11 | }, 12 | }; 13 | 14 | let specific = { 15 | development: { 16 | app: { 17 | port: 5000, 18 | name: "koa2-boilerplate - Dev", 19 | excluded : "excluded_path" 20 | }, 21 | mysql: { 22 | host: 'localhost', 23 | port : 3306, 24 | user : 'test', 25 | password : 'test', 26 | database : 'test' 27 | } 28 | }, 29 | production: { 30 | app: { 31 | port: process.env.PORT || 5000, 32 | name: "koa2-boilerplate", 33 | excluded : "excluded_path" 34 | }, 35 | mysql: { 36 | host: 'localhost', 37 | port : 3306, 38 | user : 'test', 39 | password : 'test', 40 | database : 'test' 41 | } 42 | }, 43 | }; 44 | 45 | module.exports = _.merge(base, specific[env]); -------------------------------------------------------------------------------- /app/actions/main.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes'; 2 | 3 | import 'whatwg-fetch'; 4 | 5 | export function fetchSummaryData() { 6 | return dispatch => { 7 | fetch('/mock/summary', { 8 | credentials: 'same-origin' 9 | }) 10 | .then( tresponse => { 11 | return tresponse.json() 12 | }) 13 | .then( tjson => { 14 | 15 | dispatch({ 16 | type : types.MAIN_RECEIVE_SUMMARY, 17 | data : tjson.data 18 | }) 19 | }) 20 | } 21 | } 22 | 23 | export function fetchMainChartData() { 24 | return dispatch => { 25 | fetch('/mock/linechart', { 26 | credentials: 'same-origin' 27 | }) 28 | .then( tresponse => { 29 | return tresponse.json() 30 | }) 31 | .then( tjson => { 32 | 33 | dispatch({ 34 | type : types.MAIN_RECEIVE_LINECHART, 35 | data : tjson.data 36 | }) 37 | }) 38 | } 39 | } 40 | 41 | export function deleteLineChart() { 42 | return { 43 | type : types.MAIN_LINECHART_DELETED, 44 | } 45 | } -------------------------------------------------------------------------------- /src/config/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import compose from 'koa-compose'; 4 | import convert from 'koa-convert'; 5 | import cors from 'kcors'; 6 | import Serve from 'koa-static'; 7 | import Logger from 'koa-logger'; 8 | import mount from 'koa-mount'; 9 | import bodyParser from 'koa-bodyparser'; 10 | import session from 'koa-generic-session'; 11 | import views from 'koa-views'; 12 | 13 | import _ from './passport'; 14 | import passport from 'koa-passport'; 15 | 16 | import log4js from 'log4js'; 17 | 18 | export default function middleware(app) { 19 | 20 | app.proxy = true; 21 | 22 | log4js.configure({ 23 | appenders: [ 24 | { type: 'console' }, 25 | { type: 'dateFile', filename: __dirname + '/../tmp/boilerplate.log' , "pattern":"-yyyy-MM-dd-hh.log","alwaysIncludePattern":false, category: 'file' } 26 | ], 27 | replaceConsole: true 28 | }); 29 | 30 | app.use(cors({ credentials: true })); 31 | app.use(convert(Logger())) 32 | app.use(bodyParser()) 33 | app.use(mount("/", convert(Serve(__dirname + '/../public/')))); 34 | 35 | app.keys = ['superalsrk-session-key']; 36 | app.use(convert(session())) 37 | 38 | app.use(passport.initialize()) 39 | app.use(passport.session()) 40 | 41 | app.use(views(__dirname + '/../views', {extension: 'swig'})) 42 | 43 | } -------------------------------------------------------------------------------- /src/models/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import db from '../lib/db'; 4 | import log4js from 'log4js'; 5 | 6 | const Account = {} 7 | const LOG = log4js.getLogger('file'); 8 | 9 | Account.findOne = function (id, cb) { 10 | // db.queryAsync('select * from t_account where id = ?', [id], function(err, results) { 11 | // if(results == null || results.length == 0) { 12 | // err = Error('empty') 13 | // } 14 | // cb(err, results[0]) 15 | // }) 16 | 17 | //Mock Scripts 18 | const account = {"id": 1, "username" : "test", "password" : "test"} 19 | cb(null, account) 20 | 21 | } 22 | 23 | Account.verify = async function(username, password) { 24 | 25 | //let account = await db.query('select * from t_account where email = ?', [username]) 26 | 27 | LOG.warn(JSON.stringify({ 28 | 'username' : username, 29 | 'password' : password 30 | })) 31 | 32 | //Mock Scripts 33 | let account = [{"id": 1, "username" : "test", "password" : "test"}] 34 | 35 | if(account == null || account.length != 1) { 36 | return null; 37 | } else{ 38 | if((account[0].password == password) && (account[0].username == username)) { 39 | return account[0]; 40 | } else { 41 | return null; 42 | } 43 | } 44 | } 45 | 46 | export default Account; -------------------------------------------------------------------------------- /app/reducers/main.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes'; 2 | 3 | const initState = { 4 | 5 | avgTime : '0', 6 | bounceRate : '0', 7 | totalVisitors : '0', 8 | uniqueVisitors : '0', 9 | uv : [], 10 | pv : [], 11 | legends : [], 12 | 13 | chartShow : true 14 | 15 | } 16 | 17 | 18 | const main = (state = initState, action) => { 19 | switch(action.type) { 20 | case types.MAIN_RECEIVE_SUMMARY: { 21 | return Object.assign({}, state, { 22 | avgTime : action.data.avgTime, 23 | bounceRate : action.data.bounceRate, 24 | totalVisitors : action.data.totalVisitors, 25 | uniqueVisitors : action.data.uniqueVisitors, 26 | }); 27 | } 28 | 29 | case types.MAIN_RECEIVE_LINECHART: { 30 | return Object.assign({}, state, { 31 | uv : action.data.uv, 32 | pv : action.data.pv, 33 | legends : action.data.legends 34 | }) 35 | } 36 | 37 | case types.MAIN_LINECHART_DELETED : { 38 | return Object.assign({}, state, { 39 | chartShow : false 40 | }) 41 | } 42 | 43 | default: 44 | return state; 45 | } 46 | } 47 | 48 | export default main; 49 | -------------------------------------------------------------------------------- /test/test.spec.js: -------------------------------------------------------------------------------- 1 | import app from '../src/app'; 2 | import supertest from 'supertest' 3 | 4 | const request = supertest.agent(app.listen()) 5 | 6 | describe('Test Open API', function () { 7 | it('should be "{status : test}"', function (done) { 8 | request 9 | .get('/open/test') 10 | .expect('Content-Type', 'application/json; charset=utf-8') 11 | .expect(200) 12 | .expect({'status' : 'done'}) 13 | .end(function(err, res) { 14 | if(err) return done(err) 15 | done() 16 | }) 17 | }) 18 | }) 19 | 20 | 21 | describe('Test Auth API', function() { 22 | it('should not auth', function(done) { 23 | request 24 | .get('/api/app') 25 | .expect(200) 26 | .expect({'status' : 401}) 27 | .end(function(err, res) { 28 | if(err) return done(err) 29 | done() 30 | }) 31 | }) 32 | 33 | it('should login success', function(done) { 34 | request 35 | .post('/auth/login') 36 | .send({'username':'test', 'password':'test'}) 37 | .expect(200) 38 | .expect({'user':{'id':1, 'username':'test', 'password':'test'}}) 39 | .end(function(err, res) { 40 | if(err) return done(err) 41 | done() 42 | }) 43 | }) 44 | 45 | it('should auth', function(done) { 46 | request 47 | .get('/api/app') 48 | .expect(200) 49 | .expect({'status' : 'app'}) 50 | .end(function(err, res) { 51 | if(err) return done(err) 52 | done() 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /src/views/main.swig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Koa2-boilerplate 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | {% if process.env.NODE_ENV !== "development" %} 25 | 26 | 27 | {% else %} 28 | 29 | 30 | {% endif %} 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/components/Leftbar.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | 3 | class Leftbar extends React.Component { 4 | 5 | render() { 6 | return ( 7 | 45 | ) 46 | } 47 | } 48 | 49 | export default Leftbar; -------------------------------------------------------------------------------- /app/containers/DashboardContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { connect } from 'react-redux'; 4 | import { checkAuth } from '../actions/authed'; 5 | 6 | import Header from '../components/Header'; 7 | import Leftbar from '../components/Leftbar'; 8 | import Github from '../components/Github'; 9 | import Summary from '../components/Summary'; 10 | import Trend from '../components/Trend'; 11 | 12 | class DashboardContainer extends React.Component { 13 | componentDidMount() { 14 | document.body.classList.remove('bg-white') 15 | document.getElementById('page-container').classList.add('page-sidebar-fixed') 16 | document.getElementById('page-container').classList.add('page-header-fixed') 17 | 18 | if(this.props.route.path != '/404') { 19 | this.props.dispatch(checkAuth()) 20 | } 21 | 22 | } 23 | 24 | render() { 25 | 26 | return ( 27 |
28 | 29 |
30 | 31 |
32 | 33 |
34 |

Dashboard header small text goes here...

35 | 36 | 37 |
38 |
39 | ) 40 | } 41 | } 42 | 43 | 44 | function mapStateToProps(state) { 45 | 46 | return { 47 | main : state.main 48 | } 49 | } 50 | 51 | export default connect(mapStateToProps)(DashboardContainer) -------------------------------------------------------------------------------- /app/actions/authed.js: -------------------------------------------------------------------------------- 1 | import * as types from '../constants/ActionTypes'; 2 | import $ from 'jquery'; 3 | import history from '../store/history'; 4 | 5 | import 'whatwg-fetch'; 6 | 7 | export function checkAuth() { 8 | return dispatch => { 9 | fetch('/auth/status', { 10 | credentials: 'same-origin' 11 | }) 12 | .then( tresponse => { 13 | return tresponse.json() 14 | }) 15 | .then( tjson => { 16 | 17 | if(tjson.isLogin == false) { 18 | history.push('/signin') 19 | } 20 | }) 21 | } 22 | } 23 | 24 | export function authUser(authinfo) { 25 | return dispatch => { 26 | $.ajax('/auth/login', { 27 | type: "POST", 28 | contentType: "application/json; charset=utf-8", 29 | data : JSON.stringify(authinfo), 30 | async : true, 31 | success: function(data, status, xhr) { 32 | if(data.status == 400) { 33 | alert('Login errro, username/password is test/test') 34 | dispatch({ 35 | type : types.AUTH_FAILED 36 | }) 37 | } else { 38 | dispatch({ 39 | type : types.AUTH_SUCCESS 40 | }) 41 | history.push('/main') 42 | } 43 | }, 44 | xhrFields: { 45 | withCredentials: true 46 | }, 47 | crossDomain: true 48 | }); 49 | } 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin; 3 | var path = require('path'); 4 | 5 | module.exports = { 6 | 7 | entry: { 8 | main : './app/index', 9 | vendor : [ 10 | 'react', 11 | 'redux', 12 | 'react-redux', 13 | 'react-dom', 14 | 'jquery', 15 | 'whatwg-fetch' 16 | ] 17 | }, 18 | output: { 19 | path: path.join(__dirname, 'src/public/assets'), 20 | filename: '[name].js', 21 | publicPath: 'http://localhost:3000/static/', 22 | }, 23 | plugins: [ 24 | 25 | new webpack.optimize.CommonsChunkPlugin('vendor', './vendor.bundle.js'), 26 | new webpack.optimize.UglifyJsPlugin({ 27 | compress: { 28 | //supresses warnings, usually from module minification 29 | warnings: false 30 | } 31 | }), 32 | new webpack.DefinePlugin({ 33 | "process.env": { 34 | NODE_ENV: JSON.stringify("production") 35 | }}) 36 | 37 | ], 38 | module: { 39 | loaders: [ 40 | { 41 | test: /(\.jsx|\.js)$/, 42 | loaders: ['babel?presets[]=es2015&presets[]=react'], 43 | exclude: /node_modules/, 44 | include: __dirname 45 | }, 46 | { 47 | test: /\.(css|less)$/, 48 | loader: 'null' 49 | }, 50 | { test: /\.woff2?$/, loader: 'null' }, 51 | { test: /\.ttf$/, loader: 'null' }, 52 | { test: /\.eot$/, loader: 'null' }, 53 | { test: /\.svg$/, loader: 'null' }, 54 | { test: /\.(png|jpg|jpeg|gif|webp)$/i, loader: 'url?limit=200' }, 55 | { test: /\.json$/, loader: 'json' } 56 | ], 57 | }, 58 | resolve: { 59 | extensions: ['', '.js', '.jsx'], 60 | } 61 | }; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin; 3 | var path = require('path'); 4 | 5 | module.exports = { 6 | devtool: 'cheap-module-eval-source-map', 7 | entry: { 8 | 9 | main : [ 10 | 'webpack-hot-middleware/client?http:localhost:9000', 11 | 'webpack/hot/only-dev-server', 12 | './app/index' 13 | ], 14 | vendor : [ 15 | 'react', 16 | 'redux', 17 | 'react-redux', 18 | 'react-dom', 19 | 'jquery', 20 | 'whatwg-fetch', 21 | ] 22 | }, 23 | output: { 24 | publicPath: "http://0.0.0.0:9000/assets/", 25 | path: path.join(__dirname, 'src/public/assets'), 26 | filename: '[name].js' 27 | }, 28 | plugins: [ 29 | 30 | new webpack.optimize.CommonsChunkPlugin('vendor', './vendor.bundle.js'), 31 | new webpack.DefinePlugin({ 32 | "process.env": { 33 | NODE_ENV: JSON.stringify("development") 34 | }}), 35 | new webpack.HotModuleReplacementPlugin() 36 | ], 37 | module: { 38 | loaders: [ 39 | { 40 | test: /(\.jsx|\.js)$/, 41 | loaders: ['babel?presets[]=es2015&presets[]=react'], 42 | exclude: /node_modules/, 43 | include: __dirname 44 | }, 45 | { 46 | test: /\.(css|less)$/, 47 | loader: 'null' 48 | }, 49 | { test: /\.woff2?$/, loader: 'null' }, 50 | { test: /\.ttf$/, loader: 'null' }, 51 | { test: /\.eot$/, loader: 'null' }, 52 | { test: /\.svg$/, loader: 'null' }, 53 | { test: /\.(png|jpg|jpeg|gif|webp)$/i, loader: 'url?limit=200' }, 54 | { test: /\.json$/, loader: 'json' } 55 | ], 56 | }, 57 | resolve: { 58 | extensions: ['', '.js', '.jsx'], 59 | } 60 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | koa2-boilerplate 2 | ==== 3 | 4 | ![MIT](https://img.shields.io/npm/l/express.svg) ![node version](https://img.shields.io/badge/node-v6.3.0-green.svg) [![docker build](https://img.shields.io/docker/automated/superalsrk/koa2-boilerplate.svg)](https://hub.docker.com/r/superalsrk/koa2-boilerplate/builds/) [![](https://images.microbadger.com/badges/image/superalsrk/koa2-boilerplate.svg)](https://microbadger.com/images/superalsrk/koa2-boilerplate "Get your own image badge on microbadger.com") 5 | 6 | This boilerplate shows ways to 7 | 8 | + Use mysql in Koa2 9 | + CORS Ajax request 10 | + Ajax Login and Ajax Logout in Koa2, based on [koa-passport](https://github.com/rkusa/koa-passport) and [passport-local](https://github.com/jaredhanson/passport-local) 11 | + Export excel in Koa2, based on [excel-export](https://github.com/functionscope/Node-Excel-Export) 12 | + Serve static files in Koa2 13 | + Integrate with Redux App 14 | + Use charts(D3.js, Echarts, plot) library in Redux App 15 | + Deploy Koa2 app with docker 16 | 17 | ### Preview 18 | 19 | [Online Demo](http://koa2-boilerplate.tarax.cn) 20 | 21 | ### Usage 22 | 23 | ```bash 24 | $ npm install 25 | 26 | # Start application 27 | $ npm run dev 28 | 29 | # Run test cases 30 | $ npm run test 31 | 32 | # Generate test report 33 | $ npm run report 34 | ``` 35 | 36 | You can also use docker to start this boilerplate 37 | 38 | ``` 39 | $ docker pull superalsrk/koa2-boilerplate 40 | $ docker run -d -p 5000:5000 superalsrk/koa2-boilerplate 41 | ``` 42 | 43 | ### Develop & Deploy 44 | 45 | + [中文版](https://github.com/superalsrk/koa2-boilerplate/wiki/develop-and-deploy.md) 46 | + [English](https://github.com/superalsrk/koa2-boilerplate/wiki/develop-and-deploy-en.md) 47 | 48 | ## Screenshots 49 | 50 | + Login Page 51 | 52 | ![Login Page](./src/public/assets/img/screenshots/koa-2-login-osx.jpeg) 53 | 54 | + Dashboard 55 | 56 | ![Dashboard](./src/public/assets/img/screenshots/koa-2-dashboard.jpeg) 57 | 58 | ### License 59 | 60 | MIT 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/routes/mock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Router from 'koa-router'; 4 | import NodeExcel from 'excel-export'; 5 | 6 | const router = new Router(); 7 | 8 | 9 | router.get('/summary', async (ctx, next) => { 10 | 11 | let pv = Math.floor(Math.random() * 2000000) + 1500000; 12 | let uv = Math.floor(pv/3) 13 | let bounceRate = (Math.random()*30 + 15) + '' 14 | 15 | let milliFormat = (input) => { 16 | return input && input.toString() 17 | .replace(/(^|\s)\d+/g, (m) => m.replace(/(?=(?!\b)(\d{3})+$)/g, ',')) 18 | } 19 | ctx.body = { 20 | 'status' : 200, 21 | 'data' : { 22 | 'totalVisitors' : milliFormat(pv), 23 | 'bounceRate' : bounceRate.substr(0,5) + '%', 24 | 'uniqueVisitors' : milliFormat(uv), 25 | 'avgTime' : '00:12:23' 26 | } 27 | } 28 | }) 29 | 30 | router.get('/export/pv', async (ctx, next) => { 31 | let conf = {} 32 | conf.cols = [{ 33 | caption:'Header', 34 | type:'string', 35 | width: 145 36 | }] 37 | 38 | conf.rows = [['value01'], ['value02'], ['value03']] 39 | 40 | ctx.type = 'application/vnd.openxmlformats' 41 | ctx.attachment( "Report-PV.xlsx") 42 | ctx.body = new Buffer(NodeExcel.execute(conf), 'binary') 43 | }) 44 | 45 | router.get('/export/uv', async (ctx, next) => { 46 | let conf = {} 47 | conf.cols = [{ 48 | caption:'Header', 49 | type:'string', 50 | width: 145 51 | }] 52 | 53 | conf.rows = [['value01'], ['value02'], ['value03']] 54 | 55 | ctx.type = 'application/vnd.openxmlformats' 56 | ctx.attachment( "Report-UV.xlsx") 57 | ctx.body = new Buffer(NodeExcel.execute(conf), 'binary') 58 | }) 59 | 60 | 61 | router.get('/linechart', async (ctx, next) => { 62 | 63 | let origin = [] 64 | for (let i = 0; i < 20; i++) { 65 | origin.push(Math.floor(Math.random() * 300) + 90 ) 66 | } 67 | 68 | let pv = origin.map( (i, index) => {return [index+1, i]}) 69 | let uv = origin.map( (i, index) => {return [index+1, Math.floor(i/3.0)]}) 70 | let legends = [[1, ""], [2, ""], [3, "May 15"], [4, ""], [5, ""], [6, "May 19"], [7, ""], [8, ""], [9, "May 22"], [10, ""], [11, ""], [12, "May 25"], [13, ""], [14, ""], [15, "May 28"], [16, ""], [17, ""], [18, "May 31"], [19, ""], [20, ""]]; 71 | 72 | ctx.body = { 73 | 'status' : 200, 74 | 'data' : { 75 | 'pv' : pv, 76 | 'uv' : uv, 77 | 'legends' : legends 78 | } 79 | } 80 | }) 81 | 82 | 83 | export default router; -------------------------------------------------------------------------------- /app/components/Summary.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { fetchSummaryData } from '../actions/main'; 3 | 4 | 5 | 6 | const propTypes = { 7 | dispatch: PropTypes.func.isRequired 8 | }; 9 | 10 | class Summary extends React.Component { 11 | 12 | componentDidMount() { 13 | const { dispatch } = this.props; 14 | dispatch(fetchSummaryData()) 15 | } 16 | 17 | render() { 18 | 19 | 20 | return ( 21 |
22 |
23 |
24 |
25 |
26 |

TOTAL VISITORS

27 |

{ this.props.main.totalVisitors }

28 |
29 |
30 | View Detail 31 |
32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 |

BOUNCE RATE

40 |

{ this.props.main.bounceRate }

41 |
42 |
43 | View Detail 44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |

UNIQUE VISITORS

53 |

{ this.props.main.uniqueVisitors }

54 |
55 |
56 | View Detail 57 |
58 |
59 |
60 | 61 |
62 |
63 |
64 |
65 |

AVG TIME ON SITE

66 |

{ this.props.main.avgTime }

67 |
68 |
69 | View Detail 70 |
71 |
72 |
73 |
74 | ) 75 | } 76 | } 77 | 78 | Summary.propTypes = propTypes; 79 | export default Summary 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa2-boilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "start.js", 6 | "scripts": { 7 | "dev": "webpack-dev-server --progress --colors --hot --host 0.0.0.0 --port 9000 --content-base ./server/public --config ./webpack.config.js | nodemon start.js", 8 | "build": "NODE_ENV=production webpack -p --config ./webpack.prod.config.js", 9 | "test": "mocha --compilers js:babel-core/register --require babel-polyfill", 10 | "report": "mocha --compilers js:babel-core/register --require babel-polyfill --reporter mochawesome" 11 | }, 12 | "keywords": [ 13 | "koa2-boilerplate", 14 | "koa2 export excel", 15 | "koa2 login with passport.js", 16 | "koa2 cors rest request", 17 | "koa2 with redux or vue", 18 | "koa2 with docker" 19 | ], 20 | "author": "SRK.Lyu (http://stackbox.org)", 21 | "license": "MIT", 22 | "dependencies": { 23 | "amqplib": "^0.4.2", 24 | "bcryptjs": "^2.3.0", 25 | "excel-export": "^0.5.1", 26 | "fivebeans": "^1.4.1", 27 | "handlebars": "^4.0.5", 28 | "hashids": "^1.1.0", 29 | "kcors": "^2.1.1", 30 | "koa": "^2.0.0", 31 | "koa-bodyparser": "^3.1.0", 32 | "koa-compose": "^3.1.0", 33 | "koa-convert": "^1.2.0", 34 | "koa-generic-session": "^1.11.3", 35 | "koa-logger": "^1.3.0", 36 | "koa-mount": "^2.0.0", 37 | "koa-passport": "^2.2.1", 38 | "koa-redis": "^2.1.1", 39 | "koa-router": "^7.0.1", 40 | "koa-static": "^2.0.0", 41 | "koa-views": "^5.0.2", 42 | "lodash": "^4.14.1", 43 | "log4js": "^0.6.38", 44 | "mysql": "^2.11.1", 45 | "node-excel": "^0.5.0", 46 | "passport-local": "^1.0.0", 47 | "path": "^0.12.7", 48 | "request": "^2.73.0", 49 | "swig": "^1.4.2" 50 | }, 51 | "devDependencies": { 52 | "babel-cli": "^6.10.1", 53 | "babel-core": "^6.10.4", 54 | "babel-loader": "^6.2.4", 55 | "babel-polyfill": "^6.9.1", 56 | "babel-preset-es2015": "^6.9.0", 57 | "babel-preset-react": "^6.11.1", 58 | "babel-preset-stage-0": "^6.5.0", 59 | "css-loader": "^0.23.1", 60 | "file-loader": "^0.9.0", 61 | "jquery": "^3.1.0", 62 | "mocha": "^3.0.2", 63 | "mochawesome": "^1.5.1", 64 | "nodemon": "^1.10.0", 65 | "react": "^15.3.0", 66 | "react-dom": "^15.3.0", 67 | "react-github-fork-ribbon": "^0.4.4", 68 | "react-hot-loader": "^1.3.0", 69 | "react-redux": "^4.4.5", 70 | "react-router": "^2.5.2", 71 | "react-router-redux": "^4.0.5", 72 | "redux": "^3.5.2", 73 | "redux-logger": "^2.6.1", 74 | "redux-router": "^1.0.0", 75 | "redux-thunk": "^2.1.0", 76 | "serve-favicon": "^2.3.0", 77 | "should": "^11.1.0", 78 | "style-loader": "^0.13.1", 79 | "supertest": "^2.0.0", 80 | "url-loader": "^0.5.7", 81 | "webpack": "^1.13.1", 82 | "webpack-dev-middleware": "^1.6.1", 83 | "webpack-dev-server": "^1.14.1", 84 | "webpack-hot-middleware": "^2.12.2", 85 | "whatwg-fetch": "^1.0.0" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,eclipse,intellij 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # node-waf configuration 29 | .lock-wscript 30 | 31 | # Compiled binary addons (http://nodejs.org/api/addons.html) 32 | build/Release 33 | 34 | # Dependency directories 35 | node_modules 36 | jspm_packages 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | # Optional REPL history 42 | .node_repl_history 43 | 44 | .log 45 | 46 | 47 | ### Eclipse ### 48 | 49 | .metadata 50 | bin/ 51 | *.tmp 52 | *.bak 53 | *.swp 54 | *~.nib 55 | local.properties 56 | .settings/ 57 | .loadpath 58 | .recommenders 59 | 60 | # Eclipse Core 61 | .project 62 | 63 | # External tool builders 64 | .externalToolBuilders/ 65 | 66 | # Locally stored "Eclipse launch configurations" 67 | *.launch 68 | 69 | # PyDev specific (Python IDE for Eclipse) 70 | *.pydevproject 71 | 72 | # CDT-specific (C/C++ Development Tooling) 73 | .cproject 74 | 75 | # JDT-specific (Eclipse Java Development Tools) 76 | .classpath 77 | 78 | # Java annotation processor (APT) 79 | .factorypath 80 | 81 | # PDT-specific (PHP Development Tools) 82 | .buildpath 83 | 84 | # sbteclipse plugin 85 | .target 86 | 87 | # Tern plugin 88 | .tern-project 89 | 90 | # TeXlipse plugin 91 | .texlipse 92 | 93 | # STS (Spring Tool Suite) 94 | .springBeans 95 | 96 | # Code Recommenders 97 | .recommenders/ 98 | 99 | 100 | ### Intellij ### 101 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 102 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 103 | 104 | # User-specific stuff: 105 | .idea/workspace.xml 106 | .idea/tasks.xml 107 | .idea/dictionaries 108 | .idea/vcs.xml 109 | .idea/jsLibraryMappings.xml 110 | 111 | # Sensitive or high-churn files: 112 | .idea/dataSources.ids 113 | .idea/dataSources.xml 114 | .idea/dataSources.local.xml 115 | .idea/sqlDataSources.xml 116 | .idea/dynamic.xml 117 | .idea/uiDesigner.xml 118 | 119 | # Gradle: 120 | .idea/gradle.xml 121 | .idea/libraries 122 | 123 | # Mongo Explorer plugin: 124 | .idea/mongoSettings.xml 125 | 126 | ## File-based project format: 127 | *.iws 128 | 129 | ## Plugin-specific files: 130 | 131 | # IntelliJ 132 | /out/ 133 | 134 | # mpeltonen/sbt-idea plugin 135 | .idea_modules/ 136 | 137 | # JIRA plugin 138 | atlassian-ide-plugin.xml 139 | 140 | # Crashlytics plugin (for Android Studio and IntelliJ) 141 | com_crashlytics_export_strings.xml 142 | crashlytics.properties 143 | crashlytics-build.properties 144 | fabric.properties 145 | 146 | ### Intellij Patch ### 147 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 148 | 149 | # *.iml 150 | # modules.xml 151 | # .idea/misc.xml 152 | # *.ipr 153 | vendor.bundle.js 154 | 155 | mochawesome-reports 156 | -------------------------------------------------------------------------------- /app/containers/LoginContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { authUser } from '../actions/authed'; 4 | 5 | import Github from '../components/Github'; 6 | 7 | class LoginContainer extends React.Component { 8 | onSubmit(event) { 9 | event.preventDefault(); 10 | this.props.dispatch(authUser({username:this.refs.username.value, password :this.refs.password.value})) 11 | } 12 | 13 | componentDidMount() { 14 | document.body.classList.add('bg-white') 15 | document.getElementById('page-container').classList.remove('page-sidebar-fixed') 16 | document.getElementById('page-container').classList.remove('page-header-fixed') 17 | } 18 | 19 | render() { 20 | return ( 21 | 22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 |

Announcing the Color Admin app

30 |

31 | Download the Color Admin app for iPhone®, iPad®, and Android™. Lorem ipsum dolor sit amet, consectetur adipiscing elit. 32 |

33 |
34 |
35 | 36 |
37 | 38 |
39 |
40 | Color Admin 41 | responsive bootstrap 3 admin template 42 |
43 |
44 | 45 |
46 |
47 | 48 |
49 |
50 |
51 | 52 |
53 |
54 | 55 |
56 |
57 | 60 |
61 |
62 | 63 |
64 |
65 | Not a member yet? Click here to register. 66 |
67 |
68 |

69 | © Color Admin All Right Reserved 2015 70 |

71 |
72 |
73 |
74 |
75 | ) 76 | } 77 | } 78 | 79 | export default connect()(LoginContainer) -------------------------------------------------------------------------------- /app/components/Trend.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { fetchMainChartData , deleteLineChart } from '../actions/main'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | class Trend extends React.Component { 6 | 7 | componentDidMount() { 8 | //this.renderChart() 9 | this.props.dispatch(fetchMainChartData()) 10 | } 11 | 12 | componentDidUpdate(prevProps) { 13 | console.log('Did update', prevProps, this.props) 14 | 15 | if(prevProps.main.pv && prevProps.main.pv === this.props.main.pv) { 16 | return 17 | } 18 | 19 | this.renderChart(this.props.main.pv, this.props.main.uv, this.props.main.legends) 20 | 21 | } 22 | 23 | 24 | 25 | renderChart(pv=[], uv=[], legends=[]) { 26 | var chartDiv = this.refs.interchart; 27 | 28 | var t = pv; 29 | var n = uv; 30 | var r = legends; 31 | 32 | const blue = "#348fe2" 33 | const green = "#00acac"; 34 | 35 | $.plot(chartDiv, [{ 36 | data: t, 37 | label: "Page Views", 38 | color: blue, 39 | lines: { 40 | show: true, 41 | fill: false, 42 | lineWidth: 2 43 | }, 44 | points: { 45 | show: true, 46 | radius: 3, 47 | fillColor: "#fff" 48 | }, 49 | shadowSize: 0 50 | }, { 51 | data: n, 52 | label: "Visitors", 53 | color: green, 54 | lines: { 55 | show: true, 56 | fill: false, 57 | lineWidth: 2 58 | }, 59 | points: { 60 | show: true, 61 | radius: 3, 62 | fillColor: "#fff" 63 | }, 64 | shadowSize: 0 65 | }], { 66 | xaxis: { 67 | ticks: r, 68 | tickDecimals: 0, 69 | tickColor: "#ddd" 70 | }, 71 | yaxis: { 72 | ticks: 10, 73 | tickColor: "#ddd", 74 | min: 0, 75 | max: 400 76 | }, 77 | grid: { 78 | hoverable: true, 79 | clickable: true, 80 | tickColor: "#ddd", 81 | borderWidth: 1, 82 | backgroundColor: "#fff", 83 | borderColor: "#ddd" 84 | }, 85 | legend: { 86 | labelBoxBorderColor: "#ddd", 87 | margin: 10, 88 | noColumns: 1, 89 | show: true 90 | } 91 | }); 92 | } 93 | 94 | 95 | render() { 96 | 97 | if(this.props.main.chartShow) { 98 | return ( 99 |
100 |
101 |
102 |
103 | 107 |

Website Analytics (Last 7 Days)

108 |
109 |
110 |
111 |
112 |
113 | Export UV 114 | Export PV 115 |
116 |
117 |
118 |
119 | ) 120 | } else { 121 | return ( 122 |
123 | ) 124 | } 125 | } 126 | } 127 | 128 | export default Trend 129 | --------------------------------------------------------------------------------