diff --git a/.gitignore b/.gitignore index 3acc3bbff..c2ee00353 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,8 @@ modoboa_test .coverage build/ dist/ + +# JS stuff +node_modules +/static/dist +/frontend/webpack-stats.json diff --git a/frontend/.babelrc b/frontend/.babelrc new file mode 100644 index 000000000..9390d164e --- /dev/null +++ b/frontend/.babelrc @@ -0,0 +1,18 @@ +{ + "presets": [ + ["env", { + "modules": false, + "targets": { + "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] + } + }], + "stage-2" + ], + "plugins": ["transform-vue-jsx", "transform-runtime"], + "env": { + "test": { + "presets": ["env", "stage-2"], + "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] + } + } +} diff --git a/frontend/.editorconfig b/frontend/.editorconfig new file mode 100644 index 000000000..9d08a1a82 --- /dev/null +++ b/frontend/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/frontend/.eslintignore b/frontend/.eslintignore new file mode 100644 index 000000000..e2192c5c2 --- /dev/null +++ b/frontend/.eslintignore @@ -0,0 +1,5 @@ +/build/ +/config/ +/dist/ +/*.js +/test/unit/coverage/ diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js new file mode 100644 index 000000000..b8fb6d906 --- /dev/null +++ b/frontend/.eslintrc.js @@ -0,0 +1,30 @@ +// https://eslint.org/docs/user-guide/configuring + +module.exports = { + root: true, + parserOptions: { + parser: 'babel-eslint' + }, + env: { + browser: true, + }, + extends: [ + // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention + // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. + 'plugin:vue/essential', + // https://github.com/standard/standard/blob/master/docs/RULES-en.md + 'standard' + ], + // required to lint *.vue files + plugins: [ + 'vue' + ], + // add your custom rules here + rules: { + // allow async-await + 'generator-star-spacing': 'off', + // allow debugger during development + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', + 'indent': ['error', 4] + } +} diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 000000000..895e84421 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,15 @@ +.DS_Store +node_modules/ +/dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +/test/unit/coverage/ + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/frontend/.postcssrc.js b/frontend/.postcssrc.js new file mode 100644 index 000000000..eee3e92d7 --- /dev/null +++ b/frontend/.postcssrc.js @@ -0,0 +1,10 @@ +// https://github.com/michael-ciniawsky/postcss-load-config + +module.exports = { + "plugins": { + "postcss-import": {}, + "postcss-url": {}, + // to edit target browsers: use "browserslist" field in package.json + "autoprefixer": {} + } +} diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 000000000..d18862530 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,27 @@ +# frontend + +> Vue.js frontend for Modoboa + +## Build Setup + +``` bash +# install dependencies +npm install + +# serve with hot reload at localhost:8080 +npm run dev + +# build for production with minification +npm run build + +# build for production and view the bundle analyzer report +npm run build --report + +# run unit tests +npm run unit + +# run all tests +npm test +``` + +For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). diff --git a/frontend/config/dev.env.js b/frontend/config/dev.env.js new file mode 100644 index 000000000..1e22973ae --- /dev/null +++ b/frontend/config/dev.env.js @@ -0,0 +1,7 @@ +'use strict' +const merge = require('webpack-merge') +const prodEnv = require('./prod.env') + +module.exports = merge(prodEnv, { + NODE_ENV: '"development"' +}) diff --git a/frontend/config/index.js b/frontend/config/index.js new file mode 100644 index 000000000..32f60374c --- /dev/null +++ b/frontend/config/index.js @@ -0,0 +1,76 @@ +'use strict' +// Template version: 1.3.1 +// see http://vuejs-templates.github.io/webpack for documentation. + +const path = require('path') + +module.exports = { + dev: { + + // Paths + assetsSubDirectory: 'static', + assetsPublicPath: 'http://localhost:8080/', + proxyTable: {}, + + // Various Dev Server settings + host: 'localhost', // can be overwritten by process.env.HOST + port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined + autoOpenBrowser: false, + errorOverlay: true, + notifyOnErrors: true, + poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- + + // Use Eslint Loader? + // If true, your code will be linted during bundling and + // linting errors and warnings will be shown in the console. + useEslint: true, + // If true, eslint errors and warnings will also be shown in the error overlay + // in the browser. + showEslintErrorsInOverlay: false, + + /** + * Source Maps + */ + + // https://webpack.js.org/configuration/devtool/#development + devtool: 'cheap-module-eval-source-map', + + // If you have problems debugging vue-files in devtools, + // set this to false - it *may* help + // https://vue-loader.vuejs.org/en/options.html#cachebusting + cacheBusting: true, + + cssSourceMap: true + }, + + build: { + // Template for index.html + index: path.resolve(__dirname, '../dist/index.html'), + + // Paths + assetsRoot: path.resolve(__dirname, '../../modoboa/static'), + assetsSubDirectory: 'admin', + assetsPublicPath: '/sitestatic/', + + /** + * Source Maps + */ + + productionSourceMap: true, + // https://webpack.js.org/configuration/devtool/#production + devtool: '#source-map', + + // Gzip off by default as many popular static hosts such as + // Surge or Netlify already gzip all static assets for you. + // Before setting to `true`, make sure to: + // npm install --save-dev compression-webpack-plugin + productionGzip: false, + productionGzipExtensions: ['js', 'css'], + + // Run the build command with an extra argument to + // View the bundle analyzer report after build finishes: + // `npm run build --report` + // Set to `true` or `false` to always turn it on or off + bundleAnalyzerReport: process.env.npm_config_report + } +} diff --git a/frontend/config/prod.env.js b/frontend/config/prod.env.js new file mode 100644 index 000000000..a6f997616 --- /dev/null +++ b/frontend/config/prod.env.js @@ -0,0 +1,4 @@ +'use strict' +module.exports = { + NODE_ENV: '"production"' +} diff --git a/frontend/config/test.env.js b/frontend/config/test.env.js new file mode 100644 index 000000000..c2824a307 --- /dev/null +++ b/frontend/config/test.env.js @@ -0,0 +1,7 @@ +'use strict' +const merge = require('webpack-merge') +const devEnv = require('./dev.env') + +module.exports = merge(devEnv, { + NODE_ENV: '"testing"' +}) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 000000000..12bce148a --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + frontend + + +
+ + + diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 000000000..d6470f118 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,84 @@ +{ + "name": "frontend", + "version": "1.0.0", + "description": "Vue.js frontend for Modoboa", + "author": "Antoine Nguyen ", + "private": true, + "scripts": { + "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", + "start": "npm run dev", + "unit": "jest --config test/unit/jest.conf.js --coverage", + "test": "npm run unit", + "lint": "eslint --ext .js,.vue src test/unit", + "build": "node build/build.js" + }, + "dependencies": { + "buefy": "^0.6.5", + "vue": "^2.5.2", + "vue-resource": "^1.5.0", + "vue-router": "^3.0.1" + }, + "devDependencies": { + "autoprefixer": "^7.1.2", + "babel-core": "^6.22.1", + "babel-eslint": "^8.2.1", + "babel-helper-vue-jsx-merge-props": "^2.0.3", + "babel-jest": "^21.0.2", + "babel-loader": "^7.1.1", + "babel-plugin-dynamic-import-node": "^1.2.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", + "babel-plugin-transform-runtime": "^6.22.0", + "babel-plugin-transform-vue-jsx": "^3.5.0", + "babel-preset-env": "^1.3.2", + "babel-preset-stage-2": "^6.22.0", + "chalk": "^2.0.1", + "copy-webpack-plugin": "^4.0.1", + "css-loader": "^0.28.0", + "eslint": "^4.15.0", + "eslint-config-standard": "^10.2.1", + "eslint-friendly-formatter": "^3.0.0", + "eslint-loader": "^1.7.1", + "eslint-plugin-import": "^2.7.0", + "eslint-plugin-node": "^5.2.0", + "eslint-plugin-promise": "^3.4.0", + "eslint-plugin-standard": "^3.0.1", + "eslint-plugin-vue": "^4.0.0", + "extract-text-webpack-plugin": "^3.0.0", + "file-loader": "^1.1.4", + "friendly-errors-webpack-plugin": "^1.6.1", + "html-webpack-plugin": "^2.30.1", + "jest": "^22.0.4", + "jest-serializer-vue": "^0.3.0", + "node-notifier": "^5.1.2", + "optimize-css-assets-webpack-plugin": "^3.2.0", + "ora": "^1.2.0", + "portfinder": "^1.0.13", + "postcss-import": "^11.0.0", + "postcss-loader": "^2.0.8", + "postcss-url": "^7.2.1", + "rimraf": "^2.6.0", + "semver": "^5.3.0", + "shelljs": "^0.7.6", + "uglifyjs-webpack-plugin": "^1.1.1", + "url-loader": "^0.5.8", + "vue-jest": "^1.0.2", + "vue-loader": "^13.3.0", + "vue-style-loader": "^3.0.1", + "vue-template-compiler": "^2.5.2", + "webpack": "^3.6.0", + "webpack-bundle-analyzer": "^2.9.0", + "webpack-bundle-tracker": "^0.3.0", + "webpack-dev-server": "^2.9.1", + "webpack-merge": "^4.1.0" + }, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 8" + ] +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 000000000..77ab93e35 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,32 @@ + + + diff --git a/frontend/src/api.js b/frontend/src/api.js new file mode 100644 index 000000000..6e9cd8aa4 --- /dev/null +++ b/frontend/src/api.js @@ -0,0 +1,14 @@ +import Vue from 'vue' +import VueResource from 'vue-resource' + +Vue.use(VueResource) + +var domainResource = Vue.resource('/api/v1/domains{/pk}/') + +export const getDomains = () => { + return domainResource.get() +} + +export const createDomain = (data) => { + return domainResource.save(data) +} diff --git a/frontend/src/components/DomainForm.vue b/frontend/src/components/DomainForm.vue new file mode 100644 index 000000000..88b7a1099 --- /dev/null +++ b/frontend/src/components/DomainForm.vue @@ -0,0 +1,34 @@ + + + diff --git a/frontend/src/components/DomainList.vue b/frontend/src/components/DomainList.vue new file mode 100644 index 000000000..98c56a6e6 --- /dev/null +++ b/frontend/src/components/DomainList.vue @@ -0,0 +1,49 @@ + + + diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 000000000..01b0f4174 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,18 @@ +import Vue from 'vue' +import Buefy from 'buefy' +import 'buefy/lib/buefy.css' + +import App from './App' +import router from './router' + +Vue.use(Buefy) + +Vue.config.productionTip = false + +/* eslint-disable no-new */ +new Vue({ + el: '#app', + router, + components: { App }, + template: '' +}) diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 000000000..fa5e3bfd4 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,21 @@ +import Vue from 'vue' +import Router from 'vue-router' +import DomainList from '@/components/DomainList' +import DomainForm from '@/components/DomainForm' + +Vue.use(Router) + +export default new Router({ + routes: [ + { + path: '/domains/', + name: 'DomainList', + component: DomainList + }, + { + path: '/domains/add/', + name: 'DomainAdd', + component: DomainForm + } + ] +}) diff --git a/frontend/static/.gitkeep b/frontend/static/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/test/unit/.eslintrc b/frontend/test/unit/.eslintrc new file mode 100644 index 000000000..4d5d98f3e --- /dev/null +++ b/frontend/test/unit/.eslintrc @@ -0,0 +1,7 @@ +{ + "env": { + "jest": true + }, + "globals": { + } +} diff --git a/frontend/test/unit/jest.conf.js b/frontend/test/unit/jest.conf.js new file mode 100644 index 000000000..ebd8b611a --- /dev/null +++ b/frontend/test/unit/jest.conf.js @@ -0,0 +1,27 @@ +const path = require('path') + +module.exports = { + rootDir: path.resolve(__dirname, '../../'), + moduleFileExtensions: [ + 'js', + 'json', + 'vue' + ], + moduleNameMapper: { + '^@/(.*)$': '/src/$1' + }, + transform: { + '^.+\\.js$': '/node_modules/babel-jest', + '.*\\.(vue)$': '/node_modules/vue-jest' + }, + snapshotSerializers: ['/node_modules/jest-serializer-vue'], + setupFiles: ['/test/unit/setup'], + mapCoverage: true, + coverageDirectory: '/test/unit/coverage', + collectCoverageFrom: [ + 'src/**/*.{js,vue}', + '!src/main.js', + '!src/router/index.js', + '!**/node_modules/**' + ] +} diff --git a/frontend/test/unit/setup.js b/frontend/test/unit/setup.js new file mode 100644 index 000000000..edbdbae81 --- /dev/null +++ b/frontend/test/unit/setup.js @@ -0,0 +1,3 @@ +import Vue from 'vue' + +Vue.config.productionTip = false diff --git a/modoboa/admin/templates/admin/index.html b/modoboa/admin/templates/admin/index.html index 3f7c3e2a5..19660f463 100644 --- a/modoboa/admin/templates/admin/index.html +++ b/modoboa/admin/templates/admin/index.html @@ -1,29 +1,24 @@ -{% extends "fluid.html" %} - {% load i18n lib_tags static %} {% load render_bundle from webpack_loader %} - -{% block pagetitle %}{% trans "Admin" %}{% endblock %} - -{% block extra_css %} - {% if not debug %} - {% render_bundle 'app' 'css' 'ADMIN' %} - {% endif %} -{% endblock %} - -{% block extra_js %} - {% if not debug %} - {% render_bundle 'manifest' 'js' 'ADMIN' %} - {% render_bundle 'vendor' 'js' 'ADMIN' %} - {% render_bundle 'app' 'js' 'ADMIN' %} - {% else %} - {% render_bundle 'app' 'js' 'ADMIN' %} - {% endif %} -{% endblock %} - -{% block container_content %} - -
-
- -{% endblock %} + + + + + + Modoboa + {% if not debug %} + {% render_bundle 'app' 'css' 'ADMIN' %} + {% endif %} + + +
+ + {% if not debug %} + {% render_bundle 'manifest' 'js' 'ADMIN' %} + {% render_bundle 'vendor' 'js' 'ADMIN' %} + {% render_bundle 'app' 'js' 'ADMIN' %} + {% else %} + {% render_bundle 'app' 'js' 'ADMIN' %} + {% endif %} + + diff --git a/modoboa/admin/urls.py b/modoboa/admin/urls.py index bb299ae19..275ebf06b 100644 --- a/modoboa/admin/urls.py +++ b/modoboa/admin/urls.py @@ -9,11 +9,13 @@ from django.conf.urls import url from . import views from .views import ( alias as alias_views, domain as domain_views, export as export_views, - identity as identity_views, import_ as import_views + identity as identity_views, import_ as import_views, + base as base_views ) urlpatterns = [ - url(r'^$', domain_views.index, name="index"), + # url(r'^$', domain_views.index, name="index"), + url(r'^$', base_views.AdminIndexView.as_view(), name="index"), url(r'^domains/$', domain_views.domains, name="domain_list"), url(r'^domains/(?P\d+)/$', views.DomainDetailView.as_view(), name="domain_detail"), diff --git a/modoboa/admin/views/__init__.py b/modoboa/admin/views/__init__.py index 501c59517..8ab2041a7 100644 --- a/modoboa/admin/views/__init__.py +++ b/modoboa/admin/views/__init__.py @@ -8,6 +8,7 @@ from .alias import AliasDetailView from .dns import DNSBLDomainDetailView, MXDomainDetailView from .domain import DomainDetailView from .identity import AccountDetailView +from .base import AdminIndexView __all__ = [ "AccountDetailView", @@ -15,4 +16,5 @@ __all__ = [ "DNSBLDomainDetailView", "DomainDetailView", "MXDomainDetailView", + "AdminIndexView", ] diff --git a/modoboa/admin/views/base.py b/modoboa/admin/views/base.py new file mode 100644 index 000000000..418a89e5b --- /dev/null +++ b/modoboa/admin/views/base.py @@ -0,0 +1,11 @@ +"""Admin views.""" + +from __future__ import unicode_literals + +from django.views import generic + + +class AdminIndexView(generic.TemplateView): + """Admin index view.""" + + template_name = "admin/index.html"