From 642f90db1eeaa41b80738afe24ac9d31ca2d5703 Mon Sep 17 00:00:00 2001 From: martin Date: Thu, 31 May 2018 16:01:32 +0100 Subject: [PATCH] init --- .gitignore | 148 +++++++++++++++++++++ docker-compose.yml | 50 +++++++ setup/clustering/docker-compose.yml | 60 +++++++++ setup/clustering/setup/postgres/start.sh | 13 ++ setup/clustorgres/Dockerfile | 152 +++++++++++++++++++++ setup/clustorgres/docker-entrypoint.sh | 162 +++++++++++++++++++++++ setup/mountain/Dockerfile | 1 + setup/postgres/Dockerfile | 0 setup/postgres/init.sql | 54 ++++++++ setup/postgres/setup.sh | 4 + setup/redis/redis.conf | 0 setup/web/Dockerfile | 7 + setup/web/ecosystem.json | 33 +++++ setup/web/gulp/backbone.js | 38 ++++++ setup/web/gulp/build.js | 59 +++++++++ setup/web/gulpfile.js | 7 + setup/web/package.json | 63 +++++++++ setup/web/server.js | 49 +++++++ setup/web/server/db-connector.js | 22 +++ setup/web/server/db-testdata.js | 40 ++++++ setup/web/server/name-gen.js | 61 +++++++++ setup/web/src/css/custom.scss | 137 +++++++++++++++++++ setup/web/src/index.html | 44 ++++++ setup/web/src/js/DataList.js | 97 ++++++++++++++ setup/web/src/js/app.js | 19 +++ 25 files changed, 1320 insertions(+) create mode 100644 .gitignore create mode 100644 docker-compose.yml create mode 100644 setup/clustering/docker-compose.yml create mode 100644 setup/clustering/setup/postgres/start.sh create mode 100644 setup/clustorgres/Dockerfile create mode 100644 setup/clustorgres/docker-entrypoint.sh create mode 100644 setup/mountain/Dockerfile create mode 100644 setup/postgres/Dockerfile create mode 100644 setup/postgres/init.sql create mode 100644 setup/postgres/setup.sh create mode 100644 setup/redis/redis.conf create mode 100644 setup/web/Dockerfile create mode 100644 setup/web/ecosystem.json create mode 100644 setup/web/gulp/backbone.js create mode 100644 setup/web/gulp/build.js create mode 100644 setup/web/gulpfile.js create mode 100644 setup/web/package.json create mode 100644 setup/web/server.js create mode 100644 setup/web/server/db-connector.js create mode 100644 setup/web/server/db-testdata.js create mode 100644 setup/web/server/name-gen.js create mode 100644 setup/web/src/css/custom.scss create mode 100644 setup/web/src/index.html create mode 100644 setup/web/src/js/DataList.js create mode 100644 setup/web/src/js/app.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f82af37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,148 @@ +# Created by .ignore support plugin (hsz.mobi) +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +.idea/ +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml +/src/bundle.js +/src/bundle.js.map +/setup/web/live/ +/work/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f89c966 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,50 @@ +version: '3' + +services: + + PostgreSQL: + image: postgres:alpine + container_name: PostgreSQL + ports: + - 5432:5432 + restart: always + env_file: .env + volumes: + - "$ROOT/postgresql:/var/lib/postgresql/data" + - "./setup/clustering/setup/postgres/start.sh:/docker-entrypoint-initdb.d/start.sh" + - ./setup/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql + + adminer: + image: adminer + container_name: adminer + restart: always + env_file: .env + depends_on: + - PostgreSQL + ports: + - 8080:8080 + + redis: + image: redis:alpine + container_name: redis + restart: always + volumes: + - "$ROOT/redis:/data" + + web: + build: ./setup/web/. + container_name: web + command: npm run start + volumes: + - /user/app + ports: + - 9000:9000 + depends_on: + - PostgreSQL + - redis + environment: + - HOST=${HOST} + - DATABASE=${POSTGRES_DB} + - USER=${POSTGRES_USER} + - PASSWORD=${POSTGRES_PASSWORD} + - DATABASE_URL=${DATABASE_URL} diff --git a/setup/clustering/docker-compose.yml b/setup/clustering/docker-compose.yml new file mode 100644 index 0000000..1569bb8 --- /dev/null +++ b/setup/clustering/docker-compose.yml @@ -0,0 +1,60 @@ +version: '3' + +services: + + primary: + hostname: 'primary' + image: crunchydata/crunchy-postgres:centos7-10.3-1.8.2 + ports: + - 5432 + restart: always + env_file: .env + volumes: + - pg-primary-vol:/var/lib/postgresql/data + - ./setup/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql + environment: + - POSTGRES_USER=comcardeuser + - POSTGRES_PASSWORD=example + - POSTGRES_DB=comcarde + - PGHOST=/tmp + - MAX_CONNECTIONS=10 + - MAX_WAL_SENDERS=5 + - PG_MODE=primary + - PG_PRIMARY_USER=primaryuser + - PG_PRIMARY_PASSWORD=password + - PG_DATABASE=testdb + - PG_USER=testuser + - PG_PASSWORD=password + - PG_ROOT_PASSWORD=password + - PG_PRIMARY_PORT=5432 + networks: + - replication + - front + deploy: + placement: + constraints: + - node.labels.type == primary + - node.role == worker + + + adminer: + image: adminer + container_name: adminer + restart: always + env_file: .env + depends_on: + - primary + ports: + - 8080:8080 + networks: + - front + + + +networks: + replication: + front: + +volumes: + pg-primary-vol: + pg-replica-vol: diff --git a/setup/clustering/setup/postgres/start.sh b/setup/clustering/setup/postgres/start.sh new file mode 100644 index 0000000..e68bad8 --- /dev/null +++ b/setup/clustering/setup/postgres/start.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + + +env_check_err "POSTGRES_PASSWORD" + +file_env 'POSTGRES_PASSWORD' + +echo "$POSTGRES_PASSWORD" + + + + + diff --git a/setup/clustorgres/Dockerfile b/setup/clustorgres/Dockerfile new file mode 100644 index 0000000..2842a24 --- /dev/null +++ b/setup/clustorgres/Dockerfile @@ -0,0 +1,152 @@ +# vim:set ft=dockerfile: +FROM alpine:3.7 + +# alpine includes "postgres" user/group in base install +# /etc/passwd:22:postgres:x:70:70::/var/lib/postgresql:/bin/sh +# /etc/group:34:postgres:x:70: +# the home directory for the postgres user, however, is not created by default +# see https://github.com/docker-library/postgres/issues/274 +LABEL name="martind2000/clustergres" + +RUN set -ex; \ + postgresHome="$(getent passwd postgres)"; \ + postgresHome="$(echo "$postgresHome" | cut -d: -f6)"; \ + [ "$postgresHome" = '/var/lib/postgresql' ]; \ + mkdir -p "$postgresHome"; \ + chown -R postgres:postgres "$postgresHome" + +# su-exec (gosu-compatible) is installed further down + +# make the "en_US.UTF-8" locale so postgres will be utf-8 enabled by default +# alpine doesn't require explicit locale-file generation +ENV LANG en_US.utf8 + +RUN mkdir /docker-entrypoint-initdb.d + +ENV PG_MAJOR 10 +ENV PG_VERSION 10.4 +ENV PG_SHA256 1b60812310bd5756c62d93a9f93de8c28ea63b0df254f428cd1cf1a4d9020048 + +RUN set -ex \ + \ + && apk add --no-cache --virtual .fetch-deps \ + ca-certificates \ + openssl \ + tar \ + \ + && wget -O postgresql.tar.bz2 "https://ftp.postgresql.org/pub/source/v$PG_VERSION/postgresql-$PG_VERSION.tar.bz2" \ + && echo "$PG_SHA256 *postgresql.tar.bz2" | sha256sum -c - \ + && mkdir -p /usr/src/postgresql \ + && tar \ + --extract \ + --file postgresql.tar.bz2 \ + --directory /usr/src/postgresql \ + --strip-components 1 \ + && rm postgresql.tar.bz2 \ + \ + && apk add --no-cache --virtual .build-deps \ + bison \ + coreutils \ + dpkg-dev dpkg \ + flex \ + gcc \ +# krb5-dev \ + libc-dev \ + libedit-dev \ + libxml2-dev \ + libxslt-dev \ + make \ +# openldap-dev \ + openssl-dev \ +# configure: error: prove not found + perl-utils \ +# configure: error: Perl module IPC::Run is required to run TAP tests + perl-ipc-run \ +# perl-dev \ +# python-dev \ +# python3-dev \ +# tcl-dev \ + util-linux-dev \ + zlib-dev \ + \ + && cd /usr/src/postgresql \ +# update "DEFAULT_PGSOCKET_DIR" to "/var/run/postgresql" (matching Debian) +# see https://anonscm.debian.org/git/pkg-postgresql/postgresql.git/tree/debian/patches/51-default-sockets-in-var.patch?id=8b539fcb3e093a521c095e70bdfa76887217b89f + && awk '$1 == "#define" && $2 == "DEFAULT_PGSOCKET_DIR" && $3 == "\"/tmp\"" { $3 = "\"/var/run/postgresql\""; print; next } { print }' src/include/pg_config_manual.h > src/include/pg_config_manual.h.new \ + && grep '/var/run/postgresql' src/include/pg_config_manual.h.new \ + && mv src/include/pg_config_manual.h.new src/include/pg_config_manual.h \ + && gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" \ +# explicitly update autoconf config.guess and config.sub so they support more arches/libcs + && wget -O config/config.guess 'https://git.savannah.gnu.org/cgit/config.git/plain/config.guess?id=7d3d27baf8107b630586c962c057e22149653deb' \ + && wget -O config/config.sub 'https://git.savannah.gnu.org/cgit/config.git/plain/config.sub?id=7d3d27baf8107b630586c962c057e22149653deb' \ +# configure options taken from: +# https://anonscm.debian.org/cgit/pkg-postgresql/postgresql.git/tree/debian/rules?h=9.5 + && ./configure \ + --build="$gnuArch" \ +# "/usr/src/postgresql/src/backend/access/common/tupconvert.c:105: undefined reference to `libintl_gettext'" +# --enable-nls \ + --enable-integer-datetimes \ + --enable-thread-safety \ + --enable-tap-tests \ +# skip debugging info -- we want tiny size instead +# --enable-debug \ + --disable-rpath \ + --with-uuid=e2fs \ + --with-gnu-ld \ + --with-pgport=5432 \ + --with-system-tzdata=/usr/share/zoneinfo \ + --prefix=/usr/local \ + --with-includes=/usr/local/include \ + --with-libraries=/usr/local/lib \ + \ +# these make our image abnormally large (at least 100MB larger), which seems uncouth for an "Alpine" (ie, "small") variant :) +# --with-krb5 \ +# --with-gssapi \ +# --with-ldap \ +# --with-tcl \ +# --with-perl \ +# --with-python \ +# --with-pam \ + --with-openssl \ + --with-libxml \ + --with-libxslt \ + && make -j "$(nproc)" world \ + && make install-world \ + && make -C contrib install \ + \ + && runDeps="$( \ + scanelf --needed --nobanner --format '%n#p' --recursive /usr/local \ + | tr ',' '\n' \ + | sort -u \ + | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ + )" \ + && apk add --no-cache --virtual .postgresql-rundeps \ + $runDeps \ + bash \ + su-exec \ +# tzdata is optional, but only adds around 1Mb to image size and is recommended by Django documentation: +# https://docs.djangoproject.com/en/1.10/ref/databases/#optimizing-postgresql-s-configuration + tzdata \ + && apk del .fetch-deps .build-deps \ + && cd / \ + && rm -rf \ + /usr/src/postgresql \ + /usr/local/share/doc \ + /usr/local/share/man \ + && find /usr/local -name '*.a' -delete + +# make the sample config easier to munge (and "correct by default") +RUN sed -ri "s!^#?(listen_addresses)\s*=\s*\S+.*!\1 = '*'!" /usr/local/share/postgresql/postgresql.conf.sample + +RUN mkdir -p /var/run/postgresql && chown -R postgres:postgres /var/run/postgresql && chmod 2777 /var/run/postgresql + +ENV PGDATA /var/lib/postgresql/data +RUN mkdir -p "$PGDATA" && chown -R postgres:postgres "$PGDATA" && chmod 777 "$PGDATA" # this 777 will be replaced by 700 at runtime (allows semi-arbitrary "--user" values) +VOLUME /var/lib/postgresql/data + +COPY docker-entrypoint.sh /usr/local/bin/ +RUN ln -s usr/local/bin/docker-entrypoint.sh / # backwards compat +ENTRYPOINT ["docker-entrypoint.sh"] + +EXPOSE 5432 +CMD ["postgres"] diff --git a/setup/clustorgres/docker-entrypoint.sh b/setup/clustorgres/docker-entrypoint.sh new file mode 100644 index 0000000..0b0daf8 --- /dev/null +++ b/setup/clustorgres/docker-entrypoint.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash +set -Eeo pipefail +# TODO swap to -Eeuo pipefail above (after handling all potentially-unset variables) + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +if [ "${1:0:1}" = '-' ]; then + set -- postgres "$@" +fi + +# allow the container to be started with `--user` +if [ "$1" = 'postgres' ] && [ "$(id -u)" = '0' ]; then + mkdir -p "$PGDATA" + chown -R postgres "$PGDATA" + chmod 700 "$PGDATA" + + mkdir -p /var/run/postgresql + chown -R postgres /var/run/postgresql + chmod 775 /var/run/postgresql + + # Create the transaction log directory before initdb is run (below) so the directory is owned by the correct user + if [ "$POSTGRES_INITDB_WALDIR" ]; then + mkdir -p "$POSTGRES_INITDB_WALDIR" + chown -R postgres "$POSTGRES_INITDB_WALDIR" + chmod 700 "$POSTGRES_INITDB_WALDIR" + fi + + exec su-exec postgres "$BASH_SOURCE" "$@" +fi + +if [ "$1" = 'postgres' ]; then + mkdir -p "$PGDATA" + chown -R "$(id -u)" "$PGDATA" 2>/dev/null || : + chmod 700 "$PGDATA" 2>/dev/null || : + + # look specifically for PG_VERSION, as it is expected in the DB dir + if [ ! -s "$PGDATA/PG_VERSION" ]; then + # "initdb" is particular about the current user existing in "/etc/passwd", so we use "nss_wrapper" to fake that if necessary + # see https://github.com/docker-library/postgres/pull/253, https://github.com/docker-library/postgres/issues/359, https://cwrap.org/nss_wrapper.html + if ! getent passwd "$(id -u)" &> /dev/null && [ -e /usr/lib/libnss_wrapper.so ]; then + export LD_PRELOAD='/usr/lib/libnss_wrapper.so' + export NSS_WRAPPER_PASSWD="$(mktemp)" + export NSS_WRAPPER_GROUP="$(mktemp)" + echo "postgres:x:$(id -u):$(id -g):PostgreSQL:$PGDATA:/bin/false" > "$NSS_WRAPPER_PASSWD" + echo "postgres:x:$(id -g):" > "$NSS_WRAPPER_GROUP" + fi + + file_env 'POSTGRES_INITDB_ARGS' + if [ "$POSTGRES_INITDB_WALDIR" ]; then + export POSTGRES_INITDB_ARGS="$POSTGRES_INITDB_ARGS --waldir $POSTGRES_INITDB_WALDIR" + fi + eval "initdb --username=postgres $POSTGRES_INITDB_ARGS" + + # unset/cleanup "nss_wrapper" bits + if [ "${LD_PRELOAD:-}" = '/usr/lib/libnss_wrapper.so' ]; then + rm -f "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP" + unset LD_PRELOAD NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP + fi + + # check password first so we can output the warning before postgres + # messes it up + file_env 'POSTGRES_PASSWORD' + if [ "$POSTGRES_PASSWORD" ]; then + pass="PASSWORD '$POSTGRES_PASSWORD'" + authMethod=md5 + else + # The - option suppresses leading tabs but *not* spaces. :) + cat >&2 <<-'EOWARN' + **************************************************** + WARNING: No password has been set for the database. + This will allow anyone with access to the + Postgres port to access your database. In + Docker's default configuration, this is + effectively any other container on the same + system. + + Use "-e POSTGRES_PASSWORD=password" to set + it in "docker run". + **************************************************** + EOWARN + + pass= + authMethod=trust + fi + + { + echo + echo "host all all all $authMethod" + } >> "$PGDATA/pg_hba.conf" + + # internal start of server in order to allow set-up using psql-client + # does not listen on external TCP/IP and waits until start finishes + PGUSER="${PGUSER:-postgres}" \ + pg_ctl -D "$PGDATA" \ + -o "-c listen_addresses=''" \ + -w start + + file_env 'POSTGRES_USER' 'postgres' + file_env 'POSTGRES_DB' "$POSTGRES_USER" + + psql=( psql -v ON_ERROR_STOP=1 ) + + if [ "$POSTGRES_DB" != 'postgres' ]; then + "${psql[@]}" --username postgres <<-EOSQL + CREATE DATABASE "$POSTGRES_DB" ; + EOSQL + echo + fi + + if [ "$POSTGRES_USER" = 'postgres' ]; then + op='ALTER' + else + op='CREATE' + fi + "${psql[@]}" --username postgres <<-EOSQL + $op USER "$POSTGRES_USER" WITH SUPERUSER $pass ; + EOSQL + echo + + psql+=( --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" ) + + echo + for f in /docker-entrypoint-initdb.d/*; do + case "$f" in + *.sh) echo "$0: running $f"; . "$f" ;; + *.sql) echo "$0: running $f"; "${psql[@]}" -f "$f"; echo ;; + *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${psql[@]}"; echo ;; + *) echo "$0: ignoring $f" ;; + esac + echo + done + + PGUSER="${PGUSER:-postgres}" \ + pg_ctl -D "$PGDATA" -m fast -w stop + + echo + echo 'PostgreSQL init process complete; ready for start up.' + echo + fi +fi + +exec "$@" diff --git a/setup/mountain/Dockerfile b/setup/mountain/Dockerfile new file mode 100644 index 0000000..67fd379 --- /dev/null +++ b/setup/mountain/Dockerfile @@ -0,0 +1 @@ +FROM alpine diff --git a/setup/postgres/Dockerfile b/setup/postgres/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/setup/postgres/init.sql b/setup/postgres/init.sql new file mode 100644 index 0000000..66fb33d --- /dev/null +++ b/setup/postgres/init.sql @@ -0,0 +1,54 @@ +-- SEQUENCE: public.test_id_seq + +-- DROP SEQUENCE public.test_id_seq; + +CREATE SEQUENCE public.test_id_seq; + +ALTER SEQUENCE public.test_id_seq + OWNER TO comcardeuser; + + + + + +-- Table: public.test + +-- DROP TABLE public.test; + +CREATE TABLE public.test +( + id integer NOT NULL DEFAULT nextval('test_id_seq'::regclass), + company_name character varying(100) COLLATE pg_catalog."default", + CONSTRAINT test_pkey PRIMARY KEY (id) +) +WITH ( + OIDS = FALSE +) +TABLESPACE pg_default; + +ALTER TABLE public.test + OWNER to comcardeuser; + + + +-- FUNCTION: public.insert_company(character varying) + +-- DROP FUNCTION public.insert_company(character varying); + +CREATE OR REPLACE FUNCTION public.insert_company( + _name character varying) + RETURNS void + LANGUAGE 'plpgsql' + + COST 100 + VOLATILE +AS $BODY$ + +BEGIN + INSERT into test(company_name) Values(_name); +END; + +$BODY$; + +ALTER FUNCTION public.insert_company(character varying) + OWNER TO comcardeuser; diff --git a/setup/postgres/setup.sh b/setup/postgres/setup.sh new file mode 100644 index 0000000..37b2cf8 --- /dev/null +++ b/setup/postgres/setup.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e + +echo "Build Postgres" diff --git a/setup/redis/redis.conf b/setup/redis/redis.conf new file mode 100644 index 0000000..e69de29 diff --git a/setup/web/Dockerfile b/setup/web/Dockerfile new file mode 100644 index 0000000..0013e99 --- /dev/null +++ b/setup/web/Dockerfile @@ -0,0 +1,7 @@ +FROM node:alpine + +WORKDIR /usr/app + +COPY . . + +RUN npm install --unsafe-perm diff --git a/setup/web/ecosystem.json b/setup/web/ecosystem.json new file mode 100644 index 0000000..6d15fd9 --- /dev/null +++ b/setup/web/ecosystem.json @@ -0,0 +1,33 @@ +{ + /** + * Application configuration section + * http://pm2.keymetrics.io/docs/usage/application-declaration/ + */ + apps: [ + // First application + { + "name": "Jubilee", + "script": "server.js", + "cwd": "/home/martind2000/dev/jubilee", + "watch": false, + "ignore_watch" : ["node_modules"], + "merge_logs" : true, + "autorestart" : true, + "restart_delay" : 3500, + "max_memory_restart" : "300M", + env: { + COMMON_VARIABLE: "true" + }, + env_production: { + NODE_ENV: "production" + } + } + ], + /** + * Deployment section + * http://pm2.keymetrics.io/docs/usage/deployment/ + */ + deploy: { + + } +} diff --git a/setup/web/gulp/backbone.js b/setup/web/gulp/backbone.js new file mode 100644 index 0000000..ea38c71 --- /dev/null +++ b/setup/web/gulp/backbone.js @@ -0,0 +1,38 @@ +'use strict'; + +const browserify = require('browserify'); +const gulp = require('gulp'); +const source = require('vinyl-source-stream'); +const buffer = require('vinyl-buffer'); +const uglify = require('gulp-uglify-es').default; +const sourcemaps = require('gulp-sourcemaps'); +const gutil = require('gulp-util'); +const rename = require('gulp-rename'); +const stripDebug = require('gulp-strip-debug'); + + +gulp.task('bundleBackbone', function () { + // set up the browserify instance on a task basis + const b = browserify({ + 'debug': true, + 'entries': './src/js/app.js' + }); + + return b.bundle() + .pipe(source('app.js')) + .pipe(buffer()) + // .pipe(stripDebug()) + .pipe(rename('bundle.js')) + + .pipe(sourcemaps.init({ 'loadMaps': true })) + // Add transformation tasks to the pipeline here. + // .pipe(uglify()) + .on('error', gutil.log) + .pipe(sourcemaps.write('.')) + .pipe(gulp.dest('./live/js')); +}); + +gulp.task('buildBackbone', [ 'bundleBackbone'], function() { + gulp.watch('src/js/**/*.js', [ 'bundleBackbone']); +}); + diff --git a/setup/web/gulp/build.js b/setup/web/gulp/build.js new file mode 100644 index 0000000..8289d9b --- /dev/null +++ b/setup/web/gulp/build.js @@ -0,0 +1,59 @@ +const gulp = require('gulp'), + + autoprefixer = require('gulp-autoprefixer'), + cssnano = require('gulp-cssnano'), + uglify = require('gulp-uglify'), + + rename = require('gulp-rename'), + concat = require('gulp-concat'), + cache = require('gulp-cache'), + htmlmin = require('gulp-htmlmin'), + inject = require('gulp-inject'), + del = require('del'), + htmlreplace = require('gulp-html-replace'); + +const scss = require('gulp-scss'); +const sass = require('gulp-sass'); +const googleWebFonts = require('gulp-google-webfonts'); + +const fontOptions = { }; + +gulp.task('styles', function() { + return gulp.src(['node_modules/backbone.modal/backbone.modal.css', 'node_modules/backbone.modal/backbone.modal.theme.css']) + .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4')) + + /* .pipe(gulp.dest('dist/css'))*/ + /* .pipe(rename({suffix: '.min'}))*/ + .pipe(concat('style.min.css')) + .pipe(cssnano()) + .pipe(gulp.dest('live/css')); +}); + +gulp.task('copy', function() { + gulp.src(['src/img/**/*']).pipe(gulp.dest('live/img')); + gulp.src(['src/gfx/**/*']).pipe(gulp.dest('live/gfx')); + gulp.src(['src/index.html']).pipe(gulp.dest('live')); +}); + +gulp.task('clean', function() { + return del(['live']); +}); + +gulp.task('customMUI', function() { + return gulp.src(['src/css/custom.scss']) + .pipe(sass({ 'outputStyle': 'compressed' }).on('error', sass.logError)) + // .pipe(cssnano()) + .pipe(rename('mui.custom.css')) + // .pipe(gulp.dest(`${dest}/css`)); + .pipe(gulp.dest('live/css')); +}); + +gulp.task('vendor', function() { + return gulp.src([ + 'node_modules/muicss/dist/js/mui.min.js' + ]) + .pipe(concat('vendor.js')) + + /* .pipe(uglify({ 'mangle': false }))*/ + .pipe(gulp.dest(`live/js`)); +}); diff --git a/setup/web/gulpfile.js b/setup/web/gulpfile.js new file mode 100644 index 0000000..882d48f --- /dev/null +++ b/setup/web/gulpfile.js @@ -0,0 +1,7 @@ +const gulp = require('gulp'); + +const requireDir = require('require-dir'); + +requireDir('./gulp'); + +gulp.task('default', ['bundleBackbone', 'styles', 'copy', 'customMUI', 'vendor']); diff --git a/setup/web/package.json b/setup/web/package.json new file mode 100644 index 0000000..ab66ca2 --- /dev/null +++ b/setup/web/package.json @@ -0,0 +1,63 @@ +{ + "name": "pgtest", + "version": "1.0.0", + "description": "", + "main": "server.js", + "scripts": { + "test": "mocha", + "postinstall": "gulp default", + "start": "node server.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "backbone": "^1.3.3", + "browserify": "^16.1.0", + "eslint": "^4.19.0", + "express": "^4.16.3", + "hh-mm-ss": "^1.2.0", + "jquery": "^3.3.1", + "lodash": "^4.17.5", + "log4js": "^2.5.3", + "memory-cache": "^0.2.0", + "moment": "^2.21.0", + "muicss": "^0.9.38", + "pg-promise": "^8.4.4", + "request-json": "^0.6.3", + "strict-uri-encode": "^2.0.0", + "string": "^3.3.3", + "sugar": "^2.0.4", + "uglifyify": "^4.0.5", + "underscore": "^1.9.0" + }, + "devDependencies": { + "expect.js": "^0.3.1", + "gulp": "^3.9.1", + "gulp-google-webfonts": "^1.0.0", + "gulp-rename": "^1.2.2", + "gulp-sass": "^3.1.0", + "gulp-scss": "^1.4.0", + "gulp-sourcemaps": "^2.6.4", + "gulp-strip-debug": "^3.0.0", + "gulp-uglify-es": "^1.0.1", + "lodash.assign": "^4.2.0", + "mocha": "^5.0.4", + "node-fetch": "^2.1.1", + "require-dir": "^1.0.0", + "requirejs": "^2.3.5", + "sinon": "^4.4.6", + "vinyl-buffer": "^1.0.1", + "vinyl-source-stream": "^2.0.0", + "watchify": "^3.11.0", + "whatwg-fetch": "^2.0.4", + "gulp-autoprefixer": "^5.0.0", + "gulp-bump": "^3.1.0", + "gulp-cache": "^1.0.2", + "gulp-concat": "^2.6.1", + "gulp-cssnano": "^2.1.2", + "gulp-html-replace": "^1.6.2", + "gulp-htmlmin": "^4.0.0", + "gulp-inject": "^4.3.1", + "gulp-uglify": "^3.0.0" + } +} diff --git a/setup/web/server.js b/setup/web/server.js new file mode 100644 index 0000000..23f66a6 --- /dev/null +++ b/setup/web/server.js @@ -0,0 +1,49 @@ +const express = require('express'); +const path = require('path'); +const logger = require('log4js').getLogger('Server'); + + +const db = require('./server/db-connector').dbConnection; +const dbTestdata = require('./server/db-testdata')(db); + +const nameGen = require('./server/name-gen'); + +logger.level = 'debug'; + +const app = express(); +const port = process.env.PORT || 9000; + +const sitePath = 'live'; + +app.use(express.static(path.join(__dirname, sitePath))); + +// app.get('/weather', cache('15 minutes'), (req, res) => { + +const asyncMiddleware = fn => + (req, res, next) => { + Promise.resolve(fn(req, res, next)) + .catch(next); + }; + +app.get('/data', (req, res) => { + dbTestdata.sqlGetSimpleList().then((d) => { + res.send(d); + }).catch((e) => { + logger.error(e); + }); +}); + +app.listen(port, (err) => { + if (err) + return logger.error('Server error:', err); + + logger.info(`Test Server is listening on ${port}`); + + const i = setInterval(()=>{ + dbTestdata.sqlInsertData(nameGen.gen()).then((d) => { + logger.debug(d); + }).catch((e) => { + logger.error(e); + }); + }, 15000) +}); diff --git a/setup/web/server/db-connector.js b/setup/web/server/db-connector.js new file mode 100644 index 0000000..352c588 --- /dev/null +++ b/setup/web/server/db-connector.js @@ -0,0 +1,22 @@ +const pgp = require('pg-promise')(); + +const connectionString = process.env.DATABASE_URL; + + +/*const cn = { + connectionString:connectionString +};*/ + +const cn = { + host: process.env.HOST, + port: 5432, + database: process.env.DATABASE, + user: process.env.USER, + password: process.env.PASSWORD +}; + + + +console.log('>> connectionString', cn); +exports.dbConnection = pgp(cn); + diff --git a/setup/web/server/db-testdata.js b/setup/web/server/db-testdata.js new file mode 100644 index 0000000..88df3b3 --- /dev/null +++ b/setup/web/server/db-testdata.js @@ -0,0 +1,40 @@ +const logger = require('log4js').getLogger('db-testdata'); +logger.level = 'debug'; +module.exports = function(db) { + const module = {}; + + module.sqlGetSimpleList = function(id) { + return new Promise(function(resolve, reject) { + + db.manyOrNone('select * from "test";', [id]) + + .then(function(d) { + // console.log(d); + return resolve(d); + }) + .catch((err)=> { + // console.log(err); + return reject(err); + }); + }); + }; + + + module.sqlInsertData = function(name) { + + logger.info('Insert ', name); + return new Promise(function(resolve, reject) { + db.func('insert_company', + [name]) + .then(()=> { + return resolve('ok'); + }) + .catch((err)=> { + return reject(err); + }); + }); + }; + + + return module; +}; diff --git a/setup/web/server/name-gen.js b/setup/web/server/name-gen.js new file mode 100644 index 0000000..ec7cfc7 --- /dev/null +++ b/setup/web/server/name-gen.js @@ -0,0 +1,61 @@ +/** + * Created by mdonnel on 20/04/2017. + */ + +Array.prototype.random = function () { + return this[Math.floor((Math.random() * this.length))]; +}; + + +const left = ['Alabama', + 'Alaska', + 'Arizona', + 'Maryland', + 'Nevada', + 'Mexico', + 'Texas', + 'Utah', + 'Glasgow', + 'Inverness', + 'Edinburgh', + 'Dumbarton', + 'Balloch', + 'Renton', + 'Cardross', + 'Dundee', + 'Paisley', + 'Hamilton', + 'Greenock', + 'Falkirk', + 'Irvine', + 'Renfrew', + 'Erskine', + 'London', + 'Hammersmith', + 'Islington', + 'Silver', 'Black', 'Yellow', 'Purple', 'White', 'Pink', 'Red', 'Orange', 'Brown', 'Green', 'Blue', + 'Amber', 'Aqua', 'Azure', 'Bronze', 'Coral', 'Copper', 'Crimson', 'Cyan', 'Ginger', 'Gold', 'Indigo', 'Jade' + + ]; + + const right = ['Aganju', 'Cygni', 'Akeron', 'Antares', 'Aragoth', 'Ardus', 'Carpenter', + 'Cooper', 'Dahin', 'Capella', 'Endriago', 'Gallina', 'Fenris', 'Freya', 'Glenn', 'Grissom', + 'Jotunheim', 'Kailaasa', 'Lagarto', 'Muspelheim', 'Nifleheim', 'Primus', 'Vega', 'Ragnarok', + 'Shepard', 'Slayton', 'Tarsis', 'Mercury', 'Venus', 'Mars', 'Earth', 'Terra', 'Jupiter', + 'Saturn', 'Uranus', 'Neptune', 'Pluto', 'Europa', 'Ganymede', 'Callisto', 'Titan', 'Juno', + 'Eridanus', 'Scorpius', 'Crux', 'Cancer', 'Taurus', 'Lyra', 'Andromeda', 'Virgo', 'Aquarius', + 'Cygnus', 'Corvus', 'Taurus', 'Draco', 'Perseus', 'Pegasus', 'Gemini', 'Columbia', 'Bootes', + 'Orion', 'Deneb', 'Merope', 'Agate', 'Amber', 'Beryl', 'Calcite', 'Citrine', 'Coral', 'Diamond', + 'Emerald', 'Garnet', 'Jade', 'Lapis', 'Moonstone', 'Obsidian', 'Onyx', 'Opal', 'Pearl', 'Quartz', + 'Ruby', 'Sapphire', 'Topaz', 'Iron', 'Lead', 'Nickel', 'Copper', 'Zinc', 'Tin', 'Manes', 'Argon', + 'Neon', 'Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf', 'Hotel', 'India', 'Juliett', + 'Kilo', 'Lima', 'Mike', 'November', 'Oscar', 'Papa', 'Quebec', 'Romeo', 'Sierra', 'Tango', 'Uniform', + 'Victor', 'Whisky', 'Xray', 'Yankee', 'Zulu']; + + +function gen(){ + return (`${left.random() } ${ right.random() }`); +} + + +module.exports = {gen}; diff --git a/setup/web/src/css/custom.scss b/setup/web/src/css/custom.scss new file mode 100644 index 0000000..a6daa85 --- /dev/null +++ b/setup/web/src/css/custom.scss @@ -0,0 +1,137 @@ +// import MUI colors +@import "./node_modules/muicss/lib/sass/mui/colors"; + +// customize MUI variables +$mui-primary-color: mui-color('pink', '500'); +$mui-primary-color-dark: mui-color('blue-grey', '700'); +$mui-primary-color-light: mui-color('blue-grey', '100'); + +$mui-accent-color: mui-color('deep-purple', '900'); +$mui-accent-color-dark: mui-color('indigo', 'A100'); +$mui-accent-color-light: mui-color('indigo', 'A400'); + +$mui-base-font-family: 'Roboto', "Helvetica Neue", Helvetica, Arial, Verdana, "Trebuchet MS"; +$mui-base-font-weight: 400; + +$mui-appbar-font-color: mui-color('black') !default; + +$mui-link-font-color: mui-color('pink', '900') !default; + +// import MUI SASS +@import "./node_modules/muicss/lib/sass/mui"; + +body { + background-color: mui-color('grey', '100'); +} + +#header { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 2; + transition: left 0.2s; +} + +ul { + margin: 0; + padding: 0; +} + +li { + display: inline; + margin: 0; + padding: 0 4px 0 0; +} + +.dates { + padding: 2px; + border: solid 1px #80007e; + background-color: #ffffff; +} + +#btc, #fx, #trend { + font-size: 85%; +} + +.up, .ontime, .trendUp { + color: mui-color('green') !important; +} + +.down, .delayed, .trendDown { + color: $mui-text-danger !important; +} + +.nochange { + color: #000000; +} + +.password { + border: 1px solid mui-color('grey', '400'); + background-color: mui-color('grey', '200'); + font-family: monospace; + white-space: pre; +} + +.trendUp:before { + content: "▲"; +} + +.trendDown:before { + content: '▼' +} + +.card { + position: relative; + background-color: #fff; + min-height: 48px; + margin: 8px; + border-bottom-color: #666666; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12); +} + +.itemRow { + background-color: #fff; + min-height: 48px; + border-bottom-color: mui-color('grey', '200'); + border-bottom-width: 1px; + border-bottom-style: solid; + line-height: 48px; +} + +.cardTitle { + border-bottom-color: mui-color('grey', '200'); + border-bottom-width: 1px; + border-bottom-style: solid; +} + +.entry { + height: 36px; + margin: 6px 0; + vertical-align: middle; +} + +.time { + font-family: 'Roboto'; +} + +.titleBar { + font-family: 'Gotham Light'; + font-size: 125%; +} + + +.tableBody { + transition: all 0.5s; + -webkit-transition: all 0.5s; + +} + +.unsliced { + height: 455px; +} + +.sliced { + height: 300px; + +} diff --git a/setup/web/src/index.html b/setup/web/src/index.html new file mode 100644 index 0000000..7da1f10 --- /dev/null +++ b/setup/web/src/index.html @@ -0,0 +1,44 @@ + + + + + + + PG TEST + + + + + + + + + +
+
+
+ + +
+
+
+
Data
+
+
+
+
+ + + + + + + + + + + diff --git a/setup/web/src/js/DataList.js b/setup/web/src/js/DataList.js new file mode 100644 index 0000000..da4e8d5 --- /dev/null +++ b/setup/web/src/js/DataList.js @@ -0,0 +1,97 @@ +const $ = require('jquery'); +const _ = require('underscore'); +const Backbone = require('backbone'); +const request = require('request'); + +const DataItem = Backbone.Model.extend({ + +}); + +const DataCollection = Backbone.Collection.extend({ + 'model': DataItem +}); + +// const dataCollection = new dataCollection(); + +const DataItemView = Backbone.View.extend({ + 'tagName': 'div', + 'className' : 'mui-row', + 'template': _.template(` +
<%=id%>
+
<%=company_name%>
+ `), + 'initialize': function() { + this.render(); + }, + 'render': function() { + this.$el.html(this.template(this.model.toJSON())); + } +}); + +const DataListModel = Backbone.Model.extend({ + 'defaults' : function (obj) { + // return a new object + return { + 'update' : new Date().getTime() + }; + }, 'initialize': function() { + this.dataCollection = new DataCollection(); + this.listenTo(this, 'change:update', this.onChange); + this.getData(); + }, + 'onChange': function() { + this.getData(); + }, + 'getData': function() { + request({ + 'url': `${window.loc.origin}/data`, + 'method': 'GET', 'qs': { + + } + }, function(err, res, body) { + if (err) + console.error(err); + else { + const fsJSON = JSON.parse(body); + + this.dataCollection.reset(fsJSON); + + this.logUpdate(); + } + }.bind(this)); + }, 'logUpdate': function() { + const time = new Date().getTime() ; + + this.set('time', time); + + this.timerID = setTimeout( + () => this.tick(), + 30 * 1000 + ); + }, + 'tick': function() { + this.set('update', new Date().getTime()); + } + +}); + +const DataListView = Backbone.View.extend({ + 'initialize': function(options) { + this.model.dataCollection.bind('reset', this.render, this); + + }, + 'render' : function() { + + this.$el.empty(); + + this.model.dataCollection.each(function(item) { + const niView = new DataItemView({ 'model': item }); + + this.$el.append(niView.el); + }, this); + + } + +}); + +module.exports = { DataListModel, DataListView }; diff --git a/setup/web/src/js/app.js b/setup/web/src/js/app.js new file mode 100644 index 0000000..890fd81 --- /dev/null +++ b/setup/web/src/js/app.js @@ -0,0 +1,19 @@ +require('muicss'); +const $ = require('jquery'); +const _ = require('underscore'); +const Backbone = require('backbone'); + +const { DataListModel, DataListView } = require('./DataList'); + +var app = app || {}; + +(function () { + + window.loc = new URL(window.location); + + app.dataList = new DataListView({ 'model': new DataListModel(), 'el':'#data' }); + +})(); + + +