diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..6e867f9 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": [ "@babel/env", "@babel/preset-react" ], + "plugins": [ "@babel/plugin-proposal-class-properties" ] +} diff --git a/config/default.json b/config/default.json index 13fc7a1..c5f7bf3 100644 --- a/config/default.json +++ b/config/default.json @@ -1,20 +1,18 @@ { "db": { "photos": { - "host": "sqlite:db/photos.db", - "options": { - "logging" : false, - "timezone": "+00:00", - "operatorsAliases": false - } + "dialect": "sqlite", + "host": "db/photos.db", + "logging" : false, + "timezone": "+00:00", + "operatorsAliases": 0 }, "users": { - "host": "sqlite:db/users.db", - "options": { - "logging" : false, - "timezone": "+00:00", - "operatorsAliases": false - } + "dialect": "sqlite", + "host": "db/users.db", + "logging" : false, + "timezone": "+00:00", + "operatorsAliases": 0 } }, "ldap": { diff --git a/package.json b/package.json index 9c223a1..96be536 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,13 @@ "main": "server/app.js", "scripts": { "start": "node ./server/app.js", - "faces": "node ./server/face-recognizer.js" + "faces": "node ./server/face-recognizer.js", + "dev": "webpack-dev-server --mode development --host 0.0.0.0 --config webpack.dev.js", + "build": "webpack --config webpack.prod.js", + "commit-build": "./commit-build.sh", + "watch": "webpack --config webpack.prod.js --watch", + "update": "./update.sh", + "backend": "NODE_CONFIG_ENV='production' node server/app.js" }, "repository": { "type": "git", @@ -17,20 +23,25 @@ "face-recognition": "^0.9.4" }, "dependencies": { - "@tensorflow/tfjs-core": "^1.5.1", - "@tensorflow/tfjs-node": "^1.5.1", + "@tensorflow/tfjs-core": "^1.5.2", + "@tensorflow/tfjs-node": "^1.5.2", + "animakit-expander": "^2.1.4", "bluebird": "^3.7.2", "body-parser": "^1.19.0", + "bootstrap": "^4.4.1", "canvas": "^2.6.1", - "config": "^1.31.0", + "config": "^3.1.0", "connect-sqlite3": "^0.9.11", "cookie-parser": "^1.4.4", + "core-js": "^3.2.1", "exif-reader": "github:paras20xx/exif-reader", "express": "^4.17.1", "express-session": "^1.17.0", "face-api.js": "^0.22.0", - "handlebars": "^4.5.3", - "ldapauth-fork": "^4.2.0", + "googleapis": "^40.0.0", + "handlebars": "^4.7.2", + "jira-connector": "^2.10.0", + "ldapauth-fork": "^4.3.0", "ldapjs": "^1.0.2", "mariasql": "^0.2.6", "moment": "^2.24.0", @@ -38,13 +49,38 @@ "morgan": "^1.9.1", "mustache": "^3.2.1", "node-fetch": "^2.6.0", - "nodemailer": "^4.7.0", + "node-gzip": "^1.1.2", + "nodemailer": "^6.3.0", "qs": "^6.9.1", - "sequelize": "^4.44.3", + "react-app-polyfill": "^1.0.2", + "react-bootstrap": "^1.0.0-beta.16", + "react-date-range": "^1.0.0-beta", + "react-markdown": "^4.2.2", + "react-router-dom": "^5.0.1", + "react-scroll": "^1.7.14", + "react-syntax-highlighter": "^11.0.2", + "sequelize": "^5.21.3", "sequelize-mysql": "^1.7.0", "sharp": "^0.20.8", "sqlite3": "^4.1.1" }, + "devDependencies": { + "@babel/cli": "^7.1.0", + "@babel/core": "^7.1.0", + "@babel/plugin-proposal-class-properties": "^7.4.4", + "@babel/preset-env": "^7.1.0", + "@babel/preset-react": "^7.0.0", + "babel-loader": "^8.0.2", + "css-loader": "^1.0.0", + "file-loader": "^4.1.0", + "react": "^16.8", + "react-dom": "^16.8", + "style-loader": "^0.23.0", + "webpack": "^4.19.1", + "webpack-cli": "^3.1.1", + "webpack-dev-server": "^3.3.1", + "webpack-merge": "^4.2.1" + }, "jshintConfig": { "undef": true, "unused": true, diff --git a/server/db/photos.js b/server/db/photos.js index 4319904..756add6 100755 --- a/server/db/photos.js +++ b/server/db/photos.js @@ -20,7 +20,7 @@ const fs = require('fs'), function init() { const db = { - sequelize: new Sequelize(config.get("db.photos.host"), config.get("db.photos.options")), + sequelize: new Sequelize(config.get("db.photos")), Sequelize: Sequelize }; diff --git a/server/db/users.js b/server/db/users.js index 6e9555b..f7c6c65 100644 --- a/server/db/users.js +++ b/server/db/users.js @@ -18,7 +18,7 @@ const Sequelize = require('sequelize'), function init() { const db = { - sequelize: new Sequelize(config.get("db.users.host"), config.get("db.users.options")), + sequelize: new Sequelize(config.get("db.users")), Sequelize: Sequelize }; diff --git a/src/App.css b/src/App.css new file mode 100755 index 0000000..785f1a3 --- /dev/null +++ b/src/App.css @@ -0,0 +1,180 @@ +body { + font-family: 'Droid Sans', 'Arial Narrow', Arial, sans-serif; +} + +#spinner { + display: none; + position: fixed; + left: 50%; + top: 50%; + width: 60px; + height: 60px; + margin-left: -30px; + margin-top: -30px; + z-index: 100; + background-image: url(../frontend/icons-512.png); + background-color: #0071C5; + border-radius: 50%; +} + +#spinner.spin { + display: inline-block; + -webkit-animation:spin 1s linear infinite; + -moz-animation:spin 1s linear infinite; + animation:spin 1s linear infinite; +} + +@-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } } +@-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } +@keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } } + +.Header { + background-color: #252525; + color: #ffffff; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 20; + display: flex; + box-sizing: border-box; + flex-direction: column; + user-select: none; +} + +.Header .Group { + height: 60px; + display: flex; + padding-left: 1em; + padding-right: calc(1em + 52px); + background-color: #0071C5; + align-items: center; +} + +.Header .Group .Subtitle { + font-size: 0.7em; + line-height: 1.5em; +} + +.Header .Group .Heading { + margin: 0 auto; + width: calc(100% - 8em); + max-width: 90em; +} + +.Header .Pages { + height: 60px; + display: flex; + margin: 0 auto; + width: calc(100% - 8em); + max-width: 90em; +/* justify-content: space-between;*/ + font-size: 1.2em; + align-items: center; +} + +.Header .Pages .Link { + display: inline-flex; + align-items: center; + height: 100%; + box-sizing: border-box; + padding: 0.5em 1em; + border-bottom: 3px solid transparent; + cursor: pointer; +} + +.Header .Pages .Link.Highlight { + border-bottom-color: #0071C5 !important; +} + +.Header .Pages .Link.Active, +.Header .Pages .Link:hover { + border-bottom-color: #003663; + /*border-bottom-color: #00A7FF;*/ +} + +a.Link { + font-family: Oswald; + text-decoration: none; + color: white; +} + +.Footer { + position: fixed; + background-color: #252525; + color: #ffffff; + height: 64px; + bottom: 0; + left: 0; + right: 0; + font-size: 0.9em; + font-family: Droid Sans; + z-index: 20; +} + +.Footer .Copyright { + position: absolute; + line-height: 64px; + text-align: center; + left: 64px; + right: 64px; +} + +.Body { + position: absolute; + margin-top: 120px; /* .Header's two 60px chunks */ + margin-bottom: 64px; + top: 0; + bottom: 0; + left: 0; + right: 0; + box-sizing: border-box; + background-image: linear-gradient(#090B1A, #131524); + color: #ffffff; + z-index: 0; +} + +.Body > * { + box-sizing: border-box; +} + +.Main { + display: inline-flex; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-image: url(../frontend/icons-512.png); + background-repeat: no-repeat; + background-position: center; +} + + +.Content { + position: relative; + box-sizing: border-box; + margin: 1em auto; + width: calc(100% - 8em); + max-width: 90em; + color: black; + z-index: 10; + background-color: #fff; + overflow-y: scroll; + scroll-behavior: smooth; + padding: 1.5em 1.5em 0 1.5em; + box-sizing: border-box; + transition: top 1s ease-in-out; + top: -100%; +} + +.Content #MinBox { + position: relative; + box-sizing: border-box; + min-height: calc(100% - 2.5em); + padding-bottom: 1.3em; +} + +.OnScreen { + top: 0px; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js new file mode 100755 index 0000000..c5d7bd2 --- /dev/null +++ b/src/App.js @@ -0,0 +1,91 @@ +/* Polyfills for IE */ +import 'react-app-polyfill/ie11'; +import 'core-js/features/array/find'; +import 'core-js/features/array/includes'; +import 'core-js/features/number/is-nan'; + +/* App starts here */ +import React from "react"; +import { withRouter, NavLink, Route, Switch } from "react-router-dom"; +import Modal from "react-bootstrap/Modal"; +import Button from "react-bootstrap/Button"; + +import "bootstrap/dist/css/bootstrap.min.css"; + +/* Custom components */ + +import './modest.css'; +import "./App.css"; + +class Header extends React.Component { + constructor(props) { + super(props); + } + + componentDidMount() { + } + + render() { + return ( +
+ +
Header
+
+ ); + } +} + +class Logo extends React.Component { + render() { + return
+ } +} + +class Footer extends React.Component { + constructor(props) { + super(props); + } + + render() { + return ( +
+
Copyright 2020 James Ketrenos
+
+ ); + } +} + + +function noChange() {}; + +class App extends React.Component { + constructor(props) { + super(props); + } + + componentDidMount() { + const location = this.props.history.location.pathname; + console.log(`App.mounted at ${location}`); + } + + render() { + console.log("App"); + return ( +
(this.app = ref) }> +
+
+
+ +
}/> +
}/> +
}/> + +
+
+
+
+ ); + } +} + +export default withRouter(App); diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..e1be483 --- /dev/null +++ b/src/index.js @@ -0,0 +1,15 @@ +import React from "react"; +import { render } from "react-dom"; +import { BrowserRouter } from "react-router-dom"; +import App from "./App.js"; + +if (process.env.NODE_ENV !== 'production') { + console.log('DEVELOPMENT mode!'); +} + +render( + + + , + document.getElementById("root") +) \ No newline at end of file diff --git a/src/modest.css b/src/modest.css new file mode 100644 index 0000000..4393fc9 --- /dev/null +++ b/src/modest.css @@ -0,0 +1,200 @@ +@media print { + *, + *:before, + *:after { + background: transparent !important; + color: #000 !important; + box-shadow: none !important; + text-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } +} + +pre, +code { + font-family: Menlo, Monaco, "Courier New", monospace; +} + +pre { + padding: .5rem; + line-height: 1.25; + overflow-x: scroll; +} + +a, +a:visited { + color: #3498db; +} + +a:hover, +a:focus, +a:active { + color: #2980b9; +} + +.modest-no-decoration { + text-decoration: none; +} + +html { + font-size: 12px; +} + +@media screen and (min-width: 32rem) and (max-width: 48rem) { + html { + font-size: 15px; + } +} + +@media screen and (min-width: 48rem) { + html { + font-size: 16px; + } +} + +p, +.modest-p { + font-size: 1rem; + margin-bottom: 1.3rem; +} + +h1, +.modest-h1, +h2, +.modest-h2, +h3, +.modest-h3, +h4, +.modest-h4 { + margin: 1.414rem 0 .5rem; + font-weight: inherit; + line-height: 1.42; +} + +h1, +.modest-h1 { + margin-top: 0; + font-size: 3.998rem; +} + +h2, +.modest-h2 { + font-size: 2.827rem; +} + +h3, +.modest-h3 { + font-size: 1.999rem; +} + +h4, +.modest-h4 { + font-size: 1.414rem; +} + +h5, +.modest-h5 { + font-size: 1.121rem; +} + +h6, +.modest-h6 { + font-size: .88rem; +} + +small, +.modest-small { + font-size: .707em; +} + +/* https://github.com/mrmrs/fluidity */ + +img, +canvas, +iframe, +video, +svg, +select, +textarea { + max-width: 100%; +} + +@import url(https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300,300italic,700); + +@import url(https://fonts.googleapis.com/css?family=Arimo:700,700italic); + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: Arimo, Helvetica, sans-serif; +} + +h1, +h2, +h3 { + border-bottom: 2px solid #fafafa; + margin-bottom: 1.15rem; + padding-bottom: .5rem; + text-align: left; +} + +blockquote { + border-left: 8px solid #fafafa; + padding: 1rem; +} + +pre, +code { + background-color: #fafafa; +} diff --git a/webpack.common.js b/webpack.common.js new file mode 100644 index 0000000..2f14635 --- /dev/null +++ b/webpack.common.js @@ -0,0 +1,33 @@ +const path = require("path"); +const webpack = require("webpack"); + +module.exports = { + entry: "./src/index.js", + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /(node_modules|bower_components)/, + loader: "babel-loader", + options: { presets: ["@babel/env"] } + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"] + }, + { + test: /\.(png|svg|jpg|md)$/, + use: [ "file-loader" ] + } + ] + }, + resolve: { + extensions: ["*", ".js", ".jsx"] + }, + output: { + path: path.resolve(__dirname, "dist/"), + publicPath: "./dist/", + filename: "bundle.js" + } +}; + diff --git a/webpack.dev.js b/webpack.dev.js new file mode 100644 index 0000000..fc08e2e --- /dev/null +++ b/webpack.dev.js @@ -0,0 +1,18 @@ +const path = require("path"); +const merge = require('webpack-merge') +const common = require('./webpack.common.js'); +const webpack = require('webpack'); + +module.exports = merge(common, { + mode: "development", + devServer: { + contentBase: path.join(__dirname, "/"), + port: 8765, + publicPath: "http://localhost:8765/dist/", + hotOnly: true, + disableHostCheck: true, + historyApiFallback: true + }, + plugins: [new webpack.HotModuleReplacementPlugin()] +}); + diff --git a/webpack.prod.js b/webpack.prod.js new file mode 100644 index 0000000..65c5e7f --- /dev/null +++ b/webpack.prod.js @@ -0,0 +1,8 @@ +const merge = require('webpack-merge') +const common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: "production", + devtool: 'source-map' +}); +