├── VERSION ├── contrib └── .dummy ├── AUTHORS ├── NEWS ├── ChangeLog ├── git-tag.sh ├── archlinux ├── p-rout.tmpfiles.conf ├── p-rout-dump.timer ├── p-rout-dump.service ├── p-rout.install ├── p-rout-view.service ├── p-rout-collect.service ├── PKGBUILD └── README ├── example ├── ens4v1_static ├── hosts └── dnsmasq.conf ├── re ├── README ├── known_events ├── known_statuses ├── event_2014-04-05T06:11:59_v5.1.4.json ├── 2014-04-05T07:42:01_v5.1.4.json ├── status_2014-04-05T07:42:01_v5.1.4.json ├── 2014-04-05T07:42:01_v5.1.4.xml ├── status_2014-04-05T07:42:01_v5.1.4.xml └── event_2014-04-05T06:11:59_v5.1.4.xml ├── .gitignore ├── p-rout-dump.sh ├── configure.ac ├── Makefile.am ├── COPYING ├── README ├── p-rout-prune.scm ├── p-rout-restore.scm ├── p-rout-collect.scm ├── INSTALL ├── test └── p-rout-test-dump.sql └── datetimepicker_css.js /VERSION: -------------------------------------------------------------------------------- 1 | 0.27 2 | -------------------------------------------------------------------------------- /contrib/.dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Bert Burgemeister 2 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | This file is here to make autoconf happy. 2 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | This file is here to make autoconf happy. 2 | -------------------------------------------------------------------------------- /git-tag.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | git tag $(head -n1 VERSION) 3 | -------------------------------------------------------------------------------- /archlinux/p-rout.tmpfiles.conf: -------------------------------------------------------------------------------- 1 | d /run/p-rout 0775 root root - 2 | -------------------------------------------------------------------------------- /example/ens4v1_static: -------------------------------------------------------------------------------- 1 | Description='The whole internet as PowerRouter sees it' 2 | Interface=ens4v1 3 | Connection=ethernet 4 | IP=static 5 | Address=('10.11.0.3/16') 6 | DNS=('10.11.0.3') 7 | -------------------------------------------------------------------------------- /re/README: -------------------------------------------------------------------------------- 1 | This directory contains pairs of files with different representations 2 | of the same original data, so we can guess how to map identifiers like 3 | "param_0" to names like "grid frequency". 4 | -------------------------------------------------------------------------------- /archlinux/p-rout-dump.timer: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Dump p-rout SQL data 3 | 4 | [Timer] 5 | OnCalendar=*-*-* 00:00:00 6 | AccuracySec=1h 7 | Persistent=true 8 | 9 | [Install] 10 | WantedBy=basic.target 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | log-db/ 3 | configure 4 | config.status 5 | build-aux/ 6 | autom4te.cache/ 7 | Makefile 8 | *.in 9 | *.m4 10 | *.tar.gz 11 | archlinux/pkg/ 12 | archlinux/src/ 13 | archlinux/p-rout-git/ 14 | -------------------------------------------------------------------------------- /archlinux/p-rout-dump.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Dump p-rout SQL data 3 | 4 | [Service] 5 | Type=oneshot 6 | ExecStart=/usr/bin/p-rout-dump.sh 7 | Nice=19 8 | IOSchedulingClass=best-effort 9 | IOSchedulingPriority=7 10 | -------------------------------------------------------------------------------- /archlinux/p-rout.install: -------------------------------------------------------------------------------- 1 | post_install() { 2 | systemd-tmpfiles --create p-rout.conf 3 | echo "For configuration instructions see ${pkgdir}/usr/share/doc/p-rout/README" 4 | } 5 | 6 | post_upgrade() { 7 | post_install "$1" 8 | } 9 | -------------------------------------------------------------------------------- /p-rout-dump.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | dumpdir=${1-'/var/lib/p-rout/'} 4 | filename=dump$(date +%Y%m%d-%H%M).sql.gz 5 | 6 | find $dumpdir/ -name dump*-*.sql.gz -mtime +14 -delete 7 | pg_dump -Z 9 -U p-rout -f $dumpdir/$filename -w p_rout 8 | -------------------------------------------------------------------------------- /example/hosts: -------------------------------------------------------------------------------- 1 | # 2 | # /etc/hosts: static lookup table for host names 3 | # 4 | 5 | # 6 | 127.0.0.1 localhost.localdomain localhost 7 | ::1 localhost.localdomain localhost 8 | 9 | 10.11.0.3 logging1.powerrouter.com 10 | 11 | # End of file 12 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_INIT([p-rout], [0.0.1], [trebbu@googlemail.com]) 2 | AC_CONFIG_FILES([Makefile]) 3 | AC_CONFIG_AUX_DIR(build-aux) 4 | 5 | GUILE_SITE_DIR 6 | GUILE_PROGS 7 | GUILE_MODULE_REQUIRED([json]) 8 | GUILE_MODULE_REQUIRED([dbi dbi]) 9 | 10 | AM_INIT_AUTOMAKE([color-tests -Wall -Wno-portability]) 11 | 12 | AC_OUTPUT 13 | -------------------------------------------------------------------------------- /re/known_events: -------------------------------------------------------------------------------- 1 | 4EDC9A0C1000530002020000000000000000000012010000000D00000000120000000E000100250900008813 maintenance charge start 2 | C9C39B0C880029000100000000000000090000002B0A05000101FFFFB80F000012001D191D1D1A1A00000000 maintenance charge end 3 | E9D39C0C880028000000000000000000FBFFFFFFBC090000000000000000000016001A191A1A000000000000 B025-W:LOW STATE OF CHARGE 4 | -------------------------------------------------------------------------------- /archlinux/p-rout-view.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=P-rout-view Daemon 3 | Requires=postgresql.service 4 | After=network.target postgresql.service 5 | 6 | [Service] 7 | Type=forking 8 | PIDFile=/var/run/p-rout/p-rout-view.pid 9 | ExecStart=/usr/bin/p-rout-view.scm 10 | ExecStopPost=/bin/rm -f /var/run/p-rout/p-rout-view.pid 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /archlinux/p-rout-collect.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=P-rout-collect Daemon 3 | Requires=postgresql.service 4 | After=network.target postgresql.service 5 | 6 | [Service] 7 | Type=forking 8 | PIDFile=/var/run/p-rout/p-rout-collect.pid 9 | ExecStart=/usr/bin/p-rout-collect.scm 10 | ExecStopPost=/bin/rm -f /var/run/p-rout/p-rout-collect.pid 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = 2 | ACLOCAL_AMFLAGS = -I build-aux 3 | PKG_LIST_VERSION=$(shell echo $(PACKAGE_VERSION) | sed "s/\./ /g") 4 | SOURCES = p-rout-collect.scm p-rout-view.scm p-rout-restore.scm p-rout-prune.scm 5 | bin_SCRIPTS = $(SOURCES) p-rout-dump.sh 6 | EXTRA_DIST = $(SOURCES) $(NOCOMP_SOURCES) 7 | GOBJECTS = $(SOURCES:%.scm=%.go) 8 | CLEANFILES = $(GOBJECTS) 9 | GUILE_WARNINGS = -Wunbound-variable -Warity-mismatch -Wformat 10 | SUFFIXES = .scm .go 11 | .scm.go: 12 | $(GUILE_TOOLS) compile $(GUILE_WARNINGS) -o "$@" "$<" 13 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, 2015 Bert Burgemeister trebbu@googlemail.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /re/known_statuses: -------------------------------------------------------------------------------- 1 | SELECT status FROM logs.module_statuses 2 | WHERE module_id = 3 | GROUP BY status ORDER BY status; 4 | 5 | 6 | DC/AC; module_id = 9 7 | 8 | status meaning 9 | ------------------ 10 | 1041 11 | 1043 12 | 16147 13 | 3091 14 | 3347 15 | 7187 16 | 7699 17 | 7955 18 | 19 | 20 | 21 | Grid sensor; module_id = 11 22 | 23 | status meaning 24 | ------------------ 25 | 1299 26 | 1555 27 | 28 | 29 | Solar; module_id = 12 30 | 31 | status meaning 32 | ------------------ 33 | 144 34 | 16 35 | 17 36 | 32769 37 | 32771 38 | 32773 39 | 32789 40 | 32835 41 | 33859 42 | 49219 43 | 50243 44 | 45 | 46 | Platform; module_id = 16 47 | 48 | status meaning 49 | ------------------ 50 | 16403 51 | 18451 52 | 18691 53 | 18707 54 | 19219 55 | 19731 56 | 20243 57 | 20499 58 | 22547 59 | 22803 60 | 23827 61 | 62 | 63 | 64 | Battery; module_id = 136 65 | 66 | status meaning 67 | ------------------ 68 | 16641 idle 69 | 16643 discharge 70 | 16707 charge; discharge 71 | 17731 charge weakly; discharge weakly 72 | 18755 charge; discharge weakly 73 | 19779 charge 74 | 20737 idle 75 | 20801 idle 76 | 20803 charge weakly; discharge weakly 77 | 22785 idle 78 | 22787 discharge weakly 79 | 22851 charge 80 | -------------------------------------------------------------------------------- /re/event_2014-04-05T06:11:59_v5.1.4.json: -------------------------------------------------------------------------------- 1 | { 2 | "header": 3 | { 4 | "powerrouter_id":"NOT0THE0REAL0ONE", 5 | "verification":0, 6 | "time_send":"2014-04-05T08:11:55+01:00", 7 | "ver":1}, 8 | "event":"4A38C60B880019000000010000000000320000005F09AF0000000000000000000D001E191F1E000000000000"} 9 | { 10 | "header": 11 | { 12 | "powerrouter_id":"NOT0THE0REAL0ONE", 13 | "verification":0, 14 | "time_send":"2014-04-05T10:42:49+01:00", 15 | "ver":1}, 16 | "event":"A85BC60B0C00100001000200000000000000000000000100000050001A90760000009001B9D30000C8000000"} 17 | { 18 | "header": 19 | { 20 | "powerrouter_id":"NOT0THE0REAL0ONE", 21 | "verification":0, 22 | "time_send":"2014-04-05T10:42:53+01:00", 23 | "ver":1}, 24 | "event":"AD5BC60B0C001000030082000000000000000000000001000000500057A500000000860137A50000C8000000"} 25 | { 26 | "header": 27 | { 28 | "powerrouter_id":"NOT0THE0REAL0ONE", 29 | "verification":0, 30 | "time_send":"2014-04-05T10:46:55+01:00", 31 | "ver":1}, 32 | "event":"9E5CC60B1000050002020000000000000000000016011100000F00000000120000000C000100180900008A13"} 33 | { 34 | "header": 35 | { 36 | "powerrouter_id":"NOT0THE0REAL0ONE", 37 | "verification":0, 38 | "time_send":"2014-04-05T12:31:07+01:00", 39 | "ver":1}, 40 | "event":"0A75C60B10006E000202010000000000C6FDFFFF16011100000D00000000120000000C000100360900008913"} 41 | { 42 | "header": 43 | { 44 | "powerrouter_id":"NOT0THE0REAL0ONE", 45 | "verification":0, 46 | "time_send":"2014-04-05T13:23:09+01:00", 47 | "ver":1}, 48 | "event":"3C81C60B10006D000202010000000000E6FFFFFF16011100000D00000000120000000C0001003A0900008813"} 49 | -------------------------------------------------------------------------------- /re/2014-04-05T07:42:01_v5.1.4.json: -------------------------------------------------------------------------------- 1 | {"header": 2 | { 3 | "powerrouter_id":"NOT0THE0REAL0ONE", 4 | "time_send":"2014-04-05T07:42:01+01:00", 5 | "version":3, 6 | "period":60 7 | }, 8 | "module_statuses": 9 | [ 10 | { 11 | "module_id":16, 12 | "status":18707, 13 | "version":1, 14 | "param_0":4998, 15 | "param_1":2397, 16 | "param_2":170, 17 | "param_3":13, 18 | "param_4":92279, 19 | "param_5":78318 20 | }, 21 | { 22 | "module_id":9, 23 | "status":7955, 24 | "version":1, 25 | "param_0":4999, 26 | "param_1":2394, 27 | "param_2":-443, 28 | "param_3":243437, 29 | "param_4":855, 30 | "param_5":2391, 31 | "param_6":0, 32 | "param_7":1825, 33 | "param_8":38975, 34 | "param_9":-446, 35 | "param_10":313 36 | }, 37 | { 38 | "module_id":136, 39 | "status":16707, 40 | "version":1, 41 | "param_0":2403, 42 | "param_1":2151, 43 | "param_2":513, 44 | "param_3":83889, 45 | "param_4":94961, 46 | "param_5":53, 47 | "param_6":15, 48 | "param_7":129, 49 | "param_8":450, 50 | "param_9":2895, 51 | "param_10":4499, 52 | "param_11":2300, 53 | "param_12":0 54 | }, 55 | { 56 | "module_id":12, 57 | "version":1, 58 | "status":32771, 59 | "param_0":0, 60 | "param_1":1, 61 | "param_2":0, 62 | "param_3":28, 63 | "param_4":100, 64 | "param_5":37896, 65 | "param_6":4, 66 | "param_7":17, 67 | "param_8":304363, 68 | "param_9":140, 69 | "param_10":17, 70 | "param_11":304391 71 | }, 72 | { 73 | "module_id":11, 74 | "status":1299, 75 | "version":1, 76 | "param_0":2343, 77 | "param_1":0, 78 | "param_2":0, 79 | "param_3":15800, 80 | "param_4":2391, 81 | "param_5":65, 82 | "param_6":-30, 83 | "param_7":119500, 84 | "param_8":2365, 85 | "param_9":32, 86 | "param_10":43, 87 | "param_11":109300 88 | } 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /re/status_2014-04-05T07:42:01_v5.1.4.json: -------------------------------------------------------------------------------- 1 | {"header": 2 | { 3 | "powerrouter_id":"NOT0THE0REAL0ONE", 4 | "time_send":"2014-04-05T07:42:01+01:00", 5 | "version":3, 6 | "period":60 7 | }, 8 | "module_statuses": 9 | [ 10 | { 11 | "module_id":16, 12 | "status":18707, 13 | "version":1, 14 | "param_0":4998, 15 | "param_1":2397, 16 | "param_2":170, 17 | "param_3":13, 18 | "param_4":92279, 19 | "param_5":78318 20 | }, 21 | { 22 | "module_id":9, 23 | "status":7955, 24 | "version":1, 25 | "param_0":4999, 26 | "param_1":2394, 27 | "param_2":-443, 28 | "param_3":243437, 29 | "param_4":855, 30 | "param_5":2391, 31 | "param_6":0, 32 | "param_7":1825, 33 | "param_8":38975, 34 | "param_9":-446, 35 | "param_10":313 36 | }, 37 | { 38 | "module_id":136, 39 | "status":16707, 40 | "version":1, 41 | "param_0":2403, 42 | "param_1":2151, 43 | "param_2":513, 44 | "param_3":83889, 45 | "param_4":94961, 46 | "param_5":53, 47 | "param_6":15, 48 | "param_7":129, 49 | "param_8":450, 50 | "param_9":2895, 51 | "param_10":4499, 52 | "param_11":2300, 53 | "param_12":0 54 | }, 55 | { 56 | "module_id":12, 57 | "version":1, 58 | "status":32771, 59 | "param_0":0, 60 | "param_1":1, 61 | "param_2":0, 62 | "param_3":28, 63 | "param_4":100, 64 | "param_5":37896, 65 | "param_6":4, 66 | "param_7":17, 67 | "param_8":304363, 68 | "param_9":140, 69 | "param_10":17, 70 | "param_11":304391 71 | }, 72 | { 73 | "module_id":11, 74 | "status":1299, 75 | "version":1, 76 | "param_0":2343, 77 | "param_1":0, 78 | "param_2":0, 79 | "param_3":15800, 80 | "param_4":2391, 81 | "param_5":65, 82 | "param_6":-30, 83 | "param_7":119500, 84 | "param_8":2365, 85 | "param_9":32, 86 | "param_10":43, 87 | "param_11":109300 88 | } 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /archlinux/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Bert Burgemeister 2 | pkgname=p-rout-git 3 | pkgver=defined_below 4 | pkgrel=1 5 | pkgdesc="Collect and view data of a Nedap PowerRouter." 6 | url="https://github.com/trebb/p-rout/" 7 | arch=('any') 8 | license=('MIT') 9 | backup=('usr/lib/systemd/system/p-rout-view.service') 10 | depends=('guile-lib' 11 | 'guile-dbd-postgresql' 12 | 'guile-json' 13 | 'dnsmasq' 14 | 'postgresql' 15 | 'gnuplot') 16 | makedepends=('git') 17 | install=p-rout.install 18 | source=("${pkgname}"::'git://github.com/trebb/p-rout.git' 19 | 'p-rout-collect.service' 20 | 'p-rout-view.service' 21 | 'p-rout-dump.timer' 22 | 'p-rout-dump.service' 23 | 'p-rout.tmpfiles.conf') 24 | md5sums=('SKIP' 25 | 'SKIP' 26 | 'SKIP' 27 | 'SKIP' 28 | 'SKIP' 29 | 'SKIP') 30 | 31 | pkgver() { 32 | cd $pkgname 33 | git describe --long --tags | sed 's/-/.r/; s/-/./' 34 | } 35 | 36 | build() { 37 | cd "$srcdir/$pkgname" 38 | autoreconf -vif 39 | ./configure --prefix=/usr 40 | make 41 | } 42 | 43 | package() { 44 | cd "$srcdir/$pkgname" 45 | make DESTDIR="$pkgdir/" install 46 | 47 | install -D -m644 \ 48 | "${srcdir}/p-rout.tmpfiles.conf" \ 49 | "${pkgdir}/usr/lib/tmpfiles.d/p-rout.conf" 50 | 51 | install -m755 -d "${pkgdir}/usr/lib/systemd/system/" 52 | install -m644 \ 53 | "${srcdir}/p-rout-collect.service" \ 54 | "${srcdir}/p-rout-view.service" \ 55 | "${srcdir}/p-rout-dump.service" \ 56 | "${srcdir}/p-rout-dump.timer" \ 57 | "${pkgdir}/usr/lib/systemd/system/" 58 | 59 | install -D -m644 \ 60 | "${srcdir}/${pkgname}/COPYING" \ 61 | "${pkgdir}/usr/share/licenses/${pkgname}/COPYING" 62 | 63 | install -m755 -d "${pkgdir}/usr/share/p-rout/" 64 | install -m644 \ 65 | "${srcdir}/${pkgname}/datetimepicker_css.js" \ 66 | "${srcdir}/${pkgname}/VERSION" \ 67 | "${pkgdir}/usr/share/p-rout/" 68 | 69 | install -D -m644 \ 70 | "${srcdir}/${pkgname}/README" \ 71 | "${pkgdir}/usr/share/doc/p-rout/README" 72 | 73 | install -m755 -d "${pkgdir}/usr/share/doc/p-rout/example/" 74 | install -m644 \ 75 | "${srcdir}/${pkgname}/example/dnsmasq.conf" \ 76 | "${srcdir}/${pkgname}/example/ens4v1_static" \ 77 | "${srcdir}/${pkgname}/example/hosts" \ 78 | "${pkgdir}/usr/share/doc/p-rout/example/" 79 | 80 | install -m755 -d "${pkgdir}/var/lib/p-rout/" 81 | } 82 | -------------------------------------------------------------------------------- /archlinux/README: -------------------------------------------------------------------------------- 1 | Packaging for Arch Linux 2 | ======================== 3 | 4 | (1) Prepare the temporary packaging directory: 5 | 6 | mkdir /tmp/packaging 7 | cd /tmp/packaging 8 | git clone ssh://aur@aur.archlinux.org/p-rout-git.git 9 | 10 | (2) From the directory containing this README, copy everything except 11 | this README into /tmp/packaging/p-rout-git/. 12 | 13 | (3) In /tmp/packaging/p-rout-git/, run 14 | 15 | updpkgsums 16 | mksrcinfo 17 | 18 | (4) Publish the new package: 19 | 20 | cd /tmp/packaging/p-rout-git/ 21 | git add . 22 | git commit -m "New version" 23 | git push 24 | 25 | 26 | Installing on Arch Linux 27 | ======================== 28 | 29 | (1) Install dependencies: 30 | 31 | pacman -S base-devel git wget dnsmasq gnuplot postgresql 32 | 33 | (2) Create a user: 34 | 35 | useradd -m p-rout 36 | passwd p-rout 37 | visudo 38 | 39 | in visudo add the following line: 40 | p-rout ALL=(ALL) ALL 41 | 42 | (3) Initialize PostgreSQL: 43 | 44 | su postgres 45 | cd 46 | initdb -D /var/lib/postgres/data 47 | exit 48 | 49 | (4) Start PostgreSQL: 50 | 51 | systemctl start postgresql 52 | systemctl enable postgresql 53 | 54 | (5) Create database and user: 55 | 56 | su postgres 57 | cd 58 | createdb p_rout 59 | createuser -sP p-rout 60 | (set password=p-rout) 61 | exit 62 | 63 | (6) Get packages from AUR: 64 | 65 | su p-rout 66 | cd 67 | wget https://aur.archlinux.org/cgit/aur.git/snapshot/guile-dbi.tar.gz 68 | wget https://aur.archlinux.org/cgit/aur.git/snapshot/guile-dbd-postgresql.tar.gz 69 | wget https://aur.archlinux.org/cgit/aur.git/snapshot/guile-lib.tar.gz 70 | wget https://aur.archlinux.org/cgit/aur.git/snapshot/guile-json.tar.gz 71 | wget https://aur.archlinux.org/cgit/aur.git/snapshot/p-rout-git.tar.gz 72 | 73 | (7) Unpack tar files from (6): 74 | 75 | tar -xzf .tar.gz 76 | 77 | (8) Build and install the packages: 78 | 79 | cd 80 | makepkg -Ai 81 | 82 | 83 | Updating p-rout on Arch Linux 84 | ============================= 85 | 86 | (1) Repeat the installation: 87 | 88 | su p-rout 89 | cd 90 | rm -rf p-rout-git* 91 | wget https://aur.archlinux.org/cgit/aur.git/snapshot/p-rout-git.tar.gz 92 | tar -xzf p-rout-git.tar.gz 93 | cd p-rout-git 94 | makepkg -i 95 | exit 96 | 97 | (2) Restart the daemons: 98 | 99 | systemctl daemon-reload 100 | systemctl restart p-rout-collect p-rout-view 101 | -------------------------------------------------------------------------------- /re/2014-04-05T07:42:01_v5.1.4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 513.0 4 | 44.9900016784668 5 | 28.950000762939453 6 | 21.510000228881836 7 | 0.0 8 | 23.0 9 | 94961 10 | 83889 11 | 45.0 12 | 12.899999618530273 13 | 53.0 14 | 16707 15 | 24.030000686645508 16 | -446.0 17 | 389.75 18 | 855 19 | 243437 20 | 49.9900016784668 21 | -443.0 22 | 239.39999389648438 23 | 1825 24 | 0.0 25 | 239.10000610351562 26 | 7955 27 | 31.299999237060547 28 | 0.0 29 | 0.6499999761581421 30 | 0.3199999928474426 31 | 15800 32 | 119500 33 | 109300 34 | 0.0 35 | -30.0 36 | 43.0 37 | 1299 38 | 234.3000030517578 39 | 239.10000610351562 40 | 236.5 41 | 78318 42 | 92279 43 | 49.97999954223633 44 | 13.0 45 | 239.6999969482422 46 | 18707 47 | 17.0 48 | 0.03999999910593033 49 | 304391 50 | 304363 51 | 17.0 52 | 17.0 53 | 32771 54 | 14.0 55 | 378.9599914550781 56 | 57 | -------------------------------------------------------------------------------- /re/status_2014-04-05T07:42:01_v5.1.4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 513.0 4 | 44.9900016784668 5 | 28.950000762939453 6 | 21.510000228881836 7 | 0.0 8 | 23.0 9 | 94961 10 | 83889 11 | 45.0 12 | 12.899999618530273 13 | 53.0 14 | 16707 15 | 24.030000686645508 16 | -446.0 17 | 389.75 18 | 855 19 | 243437 20 | 49.9900016784668 21 | -443.0 22 | 239.39999389648438 23 | 1825 24 | 0.0 25 | 239.10000610351562 26 | 7955 27 | 31.299999237060547 28 | 0.0 29 | 0.6499999761581421 30 | 0.3199999928474426 31 | 15800 32 | 119500 33 | 109300 34 | 0.0 35 | -30.0 36 | 43.0 37 | 1299 38 | 234.3000030517578 39 | 239.10000610351562 40 | 236.5 41 | 78318 42 | 92279 43 | 49.97999954223633 44 | 13.0 45 | 239.6999969482422 46 | 18707 47 | 17.0 48 | 0.03999999910593033 49 | 304391 50 | 304363 51 | 17.0 52 | 17.0 53 | 32771 54 | 14.0 55 | 378.9599914550781 56 | 57 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | P-rout 2 | ====== 3 | 4 | P-rout may be useful for owners of one of the various types of a 5 | solar energy appliance called PowerRouter, manufactured by Nedap 6 | N.V. 7 | 8 | The appliance is connected to the Internet and uploads data to an HTTP 9 | server (logging1.powerrouter.com) that is owned by the manufacturer, 10 | who provides another HTTP server where customers can view their data. 11 | 12 | P-rout does basically the same while keeping everything on the owner's 13 | premises. It provides a data-collecting HTTP server where one or more 14 | PowerRouters can upload. Data is stored in a PostgreSQL database and 15 | can be reviewed on a second HTTP server. 16 | 17 | P-rout provides an optional raw data interface that can be accessed by 18 | the home automation software FHEM (http://fhem.org). 19 | 20 | The software is intended to be installed on a single-board computer 21 | with two network interfaces. 22 | 23 | 24 | Installation 25 | ============ 26 | 27 | If you are on Arch Linux, cd archlinux and follow the README there. 28 | 29 | If not on Arch Linux and there is already a configure file, run as 30 | usual 31 | 32 | ./configure && make && make install. 33 | 34 | Otherwise, call 35 | 36 | autoreconf -vif 37 | 38 | first which should create the configure file. 39 | 40 | 41 | Configuration (on Arch Linux) 42 | ============================= 43 | 44 | (1) P-rout in its default configuration expects a local PostgreSQL 45 | database named 'p_rout' where user 'p-rout' (password 'p-rout') 46 | has administrative rights. 47 | 48 | (2) Connect your first network interface to your LAN. Configure it and 49 | find out its IP number. Make sure the IP number isn't allowed to 50 | change. (Let's suppose it's 192.168.178.1) 51 | 52 | (3) Connect the PowerRouter(s) to a dedicated (second) network 53 | interface and find out the name of the interface. (Let's suppose 54 | it's called ens4v1.) 55 | 56 | (4) Use ./example/ens4v1_static as a template for 57 | /etc/netctl/ens4v1_static. Edit the line that says 58 | 59 | Interface=ens4v1 60 | 61 | accordingly and rename the file to reflect the new interface name. 62 | Run 63 | 64 | netctl enable ens4v1_static, 65 | netctl start ens4v1_static. 66 | 67 | (5) Copy ./example/hosts over /etc/hosts. 68 | 69 | (6) Use ./example/dnsmasq.conf as a template for /etc/dnsmasq.conf. 70 | Edit the line that says 71 | 72 | interface=ens4v1 73 | 74 | accordingly. Activate dnsmasq: 75 | 76 | systemctl enable dnsmasq, 77 | systemctl start dnsmasq. 78 | 79 | (7) In /usr/lib/systemd/system/p-rout-view.service edit the line that 80 | says 81 | 82 | ExecStart=/usr/bin/p-rout-view.scm 83 | 84 | into something like 85 | 86 | ExecStart=/usr/bin/p-rout-view.scm --addr=192.168.178.1 87 | 88 | or perhaps 89 | 90 | ExecStart=/usr/bin/p-rout-view.scm --addr=192.168.178.1 --port=8080 91 | 92 | using the IP from (2). Call 93 | 94 | p-rout-view.scm --help 95 | 96 | to learn about other options. 97 | 98 | (8) Start P-rout services: 99 | 100 | systemctl enable p-rout-collect p-rout-view p-rout-dump.timer, 101 | systemctl start p-rout-collect p-rout-view p-rout-dump.timer. 102 | 103 | (9) (Optional) Change hostname: put a hostname of your choice into 104 | /etc/hostname; reboot. 105 | 106 | (10) Point your browser to http://192.168.178.1/view. Enjoy. 107 | 108 | (11) (Optional) For FHEM users (home automation software, 109 | http://fhem.org) 110 | 111 | Find your fhem.cfg. Using the IP from (2), add auto-generated 112 | configuration for accessing P-rout: 113 | 114 | p-rout-view.scm --addr=192.168.178.1 --fhem-cfg >> path/to/fhem.cfg 115 | 116 | 117 | (12) You may want to copy dumps of p-rout's collected data to a safe 118 | place. The dumps are created daily in /var/lib/p-rout/ and get 119 | deleted once they are older than 14 days. 120 | 121 | (13) Restoring a database dump from an earlier instance of p-rout: 122 | 123 | p-rout-restore.scm .gz. 124 | 125 | Stop p-rout-collect while doing this. 126 | 127 | Restoring a dump multiple times into the same database leaves you 128 | with multiple instances of every record. To get rid of such 129 | duplicates, run 130 | 131 | p-rout-prune.scm. 132 | 133 | (No need to stop p-rout-collect here.) 134 | 135 | 136 | Bugs 137 | ==== 138 | 139 | - After a restart of postgresql, both p-rout-collect and p-rout-view 140 | need to be restarted too. 141 | -------------------------------------------------------------------------------- /re/event_2014-04-05T06:11:59_v5.1.4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50 4 | 2014-04-05T06:11:59Z 5 | 2399 6 | 0 °C/0 °C 7 | 0 °C/0 °C 8 | 0 °C/0 °C 9 | 175 10 | 0 11 | 0 12 | 0 13 | 0 14 | 13 15 | 25 °C/30 °C 16 | 30 °C/31 °C 17 | 1 18 | 25 19 | 4A38C60B880019000000010000000000320000005F09AF0000000000000000000D001E191F1E000000000000 20 | 0 21 | 22 | 136 23 | 0 24 | 0 25 | 2014-04-05T08:11:55Z 26 | 2014-04-05T08:11:54Z 27 | 28 | 29 | 0 30 | 2014-04-05T08:42:54Z 31 | 0 32 | 0 33 | 200 34 | 0 35 | 1 36 | 0 37 | 80 38 | -28646 39 | 118 40 | 0 41 | 400 42 | -11335 43 | 2 44 | 16 45 | A85BC60B0C00100001000200000000000000000000000100000050001A90760000009001B9D30000C8000000 46 | 0 47 | 48 | 12 49 | 1 50 | 0 51 | 2014-04-05T10:42:49Z 52 | 2014-04-05T10:42:48Z 53 | 54 | 55 | 0 56 | 2014-04-05T08:42:57Z 57 | 0 58 | 0 59 | 200 60 | 0 61 | 1 62 | 0 63 | 80 64 | -23209 65 | 0 66 | 0 67 | 390 68 | -23241 69 | 130 70 | 16 71 | AD5BC60B0C001000030082000000000000000000000001000000500057A500000000860137A50000C8000000 72 | 0 73 | 74 | 12 75 | 3 76 | 0 77 | 2014-04-05T10:42:53Z 78 | 2014-04-05T10:42:53Z 79 | 80 | 81 | 0 82 | 2014-04-05T08:46:59Z 83 | 278 84 | 2328 85 | 0 86 | 5002 87 | 17 88 | 3840 89 | 0 90 | 0 91 | 18 92 | 0 93 | 12 94 | 1 95 | 0 96 | 5 97 | 9E5CC60B1000050002020000000000000000000016011100000F00000000120000000C000100180900008A13 98 | 0 99 | 100 | 16 101 | 2 102 | 2 103 | 2014-04-05T10:46:55Z 104 | 2014-04-05T10:46:54Z 105 | 106 | 107 | -570 108 | 2014-04-05T10:31:12Z 109 | 278 110 | 2358 111 | 0 112 | 5001 113 | 17 114 | 3328 115 | 0 116 | 0 117 | 18 118 | 0 119 | 12 120 | 1 121 | 1 122 | 110 123 | 0A75C60B10006E000202010000000000C6FDFFFF16011100000D00000000120000000C000100360900008913 124 | 0 125 | 126 | 16 127 | 2 128 | 2 129 | 2014-04-05T12:31:07Z 130 | 2014-04-05T12:31:06Z 131 | 132 | 133 | -26 134 | 2014-04-05T11:23:14Z 135 | 278 136 | 2362 137 | 0 138 | 5000 139 | 17 140 | 3328 141 | 0 142 | 0 143 | 18 144 | 0 145 | 12 146 | 1 147 | 1 148 | 109 149 | 3C81C60B10006D000202010000000000E6FFFFFF16011100000D00000000120000000C0001003A0900008813 150 | 0 151 | 152 | 16 153 | 2 154 | 2 155 | 2014-04-05T13:23:09Z 156 | 2014-04-05T13:23:08Z 157 | 158 | 159 | -------------------------------------------------------------------------------- /p-rout-prune.scm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/guile -s 2 | !# 3 | 4 | ;;;; Copyright (c) 2014, 2015 Bert Burgemeister trebbu@googlemail.com 5 | ;;;; 6 | ;;;; Permission is hereby granted, free of charge, to any person 7 | ;;;; obtaining a copy of this software and associated documentation 8 | ;;;; files (the "Software"), to deal in the Software without 9 | ;;;; restriction, including without limitation the rights to use, 10 | ;;;; copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | ;;;; copies of the Software, and to permit persons to whom the 12 | ;;;; Software is furnished to do so, subject to the following 13 | ;;;; conditions: 14 | ;;;; 15 | ;;;; The above copyright notice and this permission notice shall be 16 | ;;;; included in all copies or substantial portions of the Software. 17 | ;;;; 18 | ;;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ;;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | ;;;; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | ;;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | ;;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | ;;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | ;;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | ;;;; OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | (use-modules (rnrs bytevectors) 28 | (dbi dbi) 29 | (srfi srfi-1) 30 | (srfi srfi-19) 31 | (ice-9 getopt-long) 32 | (ice-9 pretty-print) 33 | (ice-9 match) 34 | (ice-9 rdelim) 35 | (ice-9 popen)) 36 | 37 | ;;; Return first line of file at path, or return #f 38 | (define (read-line-from-file path) 39 | (catch 40 | #t 41 | (lambda () (call-with-input-file path (lambda (in) (read-line in)))) 42 | (lambda (k . args) #f))) 43 | 44 | (define option-spec 45 | '((help (single-char #\h)) 46 | (version (single-char #\V)) 47 | (verbose (single-char #\v)) 48 | (db-connection (value #t)) 49 | (log-dir (value #t)))) 50 | 51 | (define options (getopt-long (command-line) option-spec)) 52 | 53 | (when (option-ref options 'help #f) 54 | (display (car (command-line))) 55 | (display "\ 56 | [options] 57 | -h, --help Display this help 58 | -V, --version Display version number 59 | -v, --verbose Display debugging output 60 | --db-connection :::[:port] (default: 61 | p-rout:p-rout:p_rout:/run/postgres:localhost) 62 | --log-dir Log directory (default: \"log-r\") 63 | ") 64 | (exit)) 65 | 66 | (when (option-ref options 'version #f) 67 | (display (or (read-line-from-file "./VERSION") 68 | (read-line-from-file "/usr/share/p-rout/VERSION") 69 | "unknown")) 70 | (newline) 71 | (exit)) 72 | 73 | (define +record-id-column+ "p_rout_id") 74 | (define +verbose+ (option-ref options 'verbose #f)) 75 | (define +db-connection+ 76 | (option-ref options 77 | 'db-connection 78 | "p-rout:p-rout:p_rout:/run/postgres:localhost")) 79 | (define +log-dir+ (option-ref options 'log-dir "log-r")) 80 | (define *db* #f) 81 | 82 | ;;; Put a log entry into file +log-dir+/.log 83 | (define (file-log basename . message-parts) 84 | (system* "mkdir" "-p" +log-dir+) 85 | (let ((logfile 86 | (string-append +log-dir+ "/" (or basename "unexpected") ".log")) 87 | (out #f)) 88 | (dynamic-wind 89 | (lambda () (set! out (open-file logfile "a"))) 90 | (lambda () 91 | (display (now) out) 92 | (when +verbose+ 93 | (display (now))) 94 | (for-each 95 | (lambda (part) 96 | (display " " out) 97 | (display part out) 98 | (when +verbose+ 99 | (display " ") 100 | (display part))) 101 | message-parts) 102 | (newline out) 103 | (when +verbose+ 104 | (newline))) 105 | (lambda () (close out))))) 106 | 107 | (define (now) (date->string (current-date) "~4")) 108 | 109 | (define (dot-append . strings) (string-join strings ".")) 110 | 111 | ;;; Send SQL query to database 112 | ;;; ignore-codes are expected database error codes that don't cause 113 | ;;; log entries 114 | (define (logged-query logfile query . ignore-codes) 115 | ;; guile-dbd-postgresql v2.1.4 refuses to do anything until we've read 116 | ;; any previous results 117 | (while (dbi-get_row *db*)) 118 | (when +verbose+ 119 | (file-log logfile query)) 120 | (dbi-query *db* query) 121 | (match (dbi-get_status *db*) 122 | ((code . message) 123 | (if (member code `(0 ,@ignore-codes)) 124 | #f 125 | (begin 126 | (file-log logfile message) 127 | (cons code message)))) 128 | (unexpected 129 | (file-log logfile "weird status message: " unexpected) 130 | unexpected))) 131 | 132 | ;;; Return colon-separated string db-connection as a list 133 | (define (db-connection-list db-connection) 134 | (string-split +db-connection+ #\:)) 135 | 136 | (define (db-user) 137 | (first (db-connection-list +db-connection+))) 138 | (define (db-password) 139 | (second (db-connection-list +db-connection+))) 140 | (define (db-name) 141 | (third (db-connection-list +db-connection+))) 142 | (define (db-path-or-ip) 143 | (fourth (db-connection-list +db-connection+))) 144 | (define (db-host) 145 | (fifth (db-connection-list +db-connection+))) 146 | (define (db-port) 147 | (if (= 6 (length (db-connection-list +db-connection+))) 148 | (sixth (db-connection-list +db-connection+)) 149 | "5432")) 150 | 151 | ;;; Return a list of all user tables in *db* that have a +record-id-column+ 152 | (define (tables) 153 | (logged-query 154 | "prune" 155 | (string-append 156 | "SELECT t.table_schema || '.' || t.table_name" 157 | " FROM information_schema.tables AS t" 158 | " JOIN information_schema.columns AS c" 159 | " USING (table_name, table_schema)" 160 | " WHERE table_type = 'BASE TABLE'" 161 | " AND t.table_schema NOT IN ('pg_catalog', 'information_schema')" 162 | " AND c.column_name = '" +record-id-column+ "'")) 163 | (do ((table (dbi-get_row *db*) (dbi-get_row *db*)) 164 | (tables '())) 165 | ((not table) tables) 166 | (set! tables (cons (cdar table) tables)))) 167 | 168 | ;;; Return a list of columns of schema.table in *db* 169 | (define (columns schema table) 170 | (logged-query 171 | "prune" 172 | (string-append 173 | "SELECT column_name" 174 | " FROM information_schema.columns" 175 | " WHERE table_schema = '" schema "' AND table_name = '" table "'")) 176 | (do ((column (dbi-get_row *db*) (dbi-get_row *db*)) 177 | (columns '())) 178 | ((not column) columns) 179 | (set! columns (cons (cdar column) columns)))) 180 | 181 | ;;; Remove rows from schema.table that are duplicates (ignoring column 182 | ;;; +record-id-column+), keeping the one with the smallest 183 | ;;; +record-id-column+ 184 | (define (delete-duplicate-rows schema table) 185 | (logged-query 186 | "prune" 187 | (string-append 188 | "DELETE FROM " (dot-append schema table) 189 | " USING " (dot-append schema table) " p_rout_alias" 190 | " WHERE " 191 | (string-join 192 | (map (lambda (column) 193 | (string-append 194 | "(" (dot-append schema table column) 195 | " = " (dot-append "p_rout_alias" column) 196 | " OR (" (dot-append schema table column) " IS NULL" 197 | " AND " (dot-append "p_rout_alias" column) " IS NULL)" 198 | ")")) 199 | (delete +record-id-column+ (columns schema table))) 200 | " AND ") 201 | " AND " (dot-append schema table +record-id-column+) 202 | " < " (dot-append "p_rout_alias" +record-id-column+)))) 203 | 204 | ;;; Remove duplicate rows from all tables 205 | (define (delete-all-duplicates) 206 | (for-each 207 | (lambda (schema+table) 208 | (let* ((schema+table-list (string-split schema+table #\.)) 209 | (schema (first schema+table-list)) 210 | (table (second schema+table-list))) 211 | (delete-duplicate-rows schema table))) 212 | (tables))) 213 | 214 | ;;; Return a list of all columns in *db*, each in its own list 215 | ;;; (schema table column) 216 | (define (all-columns) 217 | (append-map 218 | (lambda (schema+table) 219 | (let* ((schema+table-list (string-split schema+table #\.)) 220 | (schema (first schema+table-list)) 221 | (table (second schema+table-list))) 222 | (map (lambda (column) 223 | (list schema table column)) 224 | (columns schema table)))) 225 | (tables))) 226 | 227 | ;;; Create an index named .__index if necessary 228 | (define (create-index schema table column) 229 | (logged-query "prune" (string-append 230 | "SELECT " 231 | "t.relname AS table_name, " 232 | "i.relname AS index_name, " 233 | "a.attname AS column_name, " 234 | "n.nspname AS schema " 235 | "FROM " 236 | "pg_namespace n, pg_class t, " 237 | "pg_class i, " 238 | "pg_index ix, " 239 | "pg_attribute a " 240 | "WHERE " 241 | "n.oid = t.relnamespace " 242 | "AND t.oid = ix.indrelid " 243 | "AND i.oid = ix.indexrelid " 244 | "AND a.attrelid = t.oid " 245 | "AND a.attnum = ANY(ix.indkey) " 246 | "AND t.relkind = 'r' " 247 | "AND n.nspname = '" schema "' " 248 | "AND t.relname = '" table "' " 249 | "AND a.attname = '" column "'")) 250 | (unless (dbi-get_row *db*) 251 | (logged-query "prune" (string-append 252 | "CREATE INDEX " table "_" column "_prune_index " 253 | "ON " schema "." table " (" column ")")))) 254 | 255 | (define (drop-index schema table column) 256 | (logged-query "prune" (string-append 257 | "DROP INDEX IF EXISTS " schema "." table "_" column "_prune_index "))) 258 | 259 | (define (create-indexes) 260 | (for-each (lambda (args) 261 | (apply create-index args)) 262 | (all-columns)) 263 | (logged-query "prune" "VACUUM ANALYZE")) 264 | 265 | (define (drop-indexes) 266 | (for-each (lambda (args) 267 | (apply drop-index args)) 268 | (all-columns))) 269 | 270 | (dynamic-wind 271 | (lambda () (set! *db* (dbi-open "postgresql" +db-connection+))) 272 | (lambda () 273 | (create-indexes) 274 | (delete-all-duplicates)) 275 | (lambda () 276 | (drop-indexes) 277 | (dbi-close *db*))) 278 | -------------------------------------------------------------------------------- /p-rout-restore.scm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/guile -s 2 | !# 3 | 4 | ;;;; Copyright (c) 2014, 2015 Bert Burgemeister trebbu@googlemail.com 5 | ;;;; 6 | ;;;; Permission is hereby granted, free of charge, to any person 7 | ;;;; obtaining a copy of this software and associated documentation 8 | ;;;; files (the "Software"), to deal in the Software without 9 | ;;;; restriction, including without limitation the rights to use, 10 | ;;;; copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | ;;;; copies of the Software, and to permit persons to whom the 12 | ;;;; Software is furnished to do so, subject to the following 13 | ;;;; conditions: 14 | ;;;; 15 | ;;;; The above copyright notice and this permission notice shall be 16 | ;;;; included in all copies or substantial portions of the Software. 17 | ;;;; 18 | ;;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ;;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | ;;;; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | ;;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | ;;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | ;;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | ;;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | ;;;; OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | (use-modules (rnrs bytevectors) 28 | (dbi dbi) 29 | (srfi srfi-1) 30 | (srfi srfi-19) 31 | (ice-9 getopt-long) 32 | (ice-9 pretty-print) 33 | (ice-9 match) 34 | (ice-9 rdelim) 35 | (ice-9 popen)) 36 | 37 | ;;; Return first line of file at path, or return #f 38 | (define (read-line-from-file path) 39 | (catch 40 | #t 41 | (lambda () (call-with-input-file path (lambda (in) (read-line in)))) 42 | (lambda (k . args) #f))) 43 | 44 | (define option-spec 45 | '((help (single-char #\h)) 46 | (version (single-char #\V)) 47 | (verbose (single-char #\v)) 48 | (db-connection (value #t)) 49 | (log-dir (value #t)))) 50 | 51 | (define options (getopt-long (command-line) option-spec)) 52 | 53 | (when (option-ref options 'help #f) 54 | (display (car (command-line))) 55 | (display "\ 56 | [options] .sql.gz 57 | -h, --help Display this help 58 | -V, --version Display version number 59 | -v, --verbose Display debugging output 60 | --db-connection :::[:port] (default: 61 | p-rout:p-rout:p_rout:/run/postgres:localhost) 62 | --log-dir Log directory (default: \"log-r\") 63 | ") 64 | (exit)) 65 | 66 | (when (option-ref options 'version #f) 67 | (display (or (read-line-from-file "./VERSION") 68 | (read-line-from-file "/usr/share/p-rout/VERSION") 69 | "unknown")) 70 | (newline) 71 | (exit)) 72 | 73 | (define +record-id-column+ "p_rout_id") 74 | (define +verbose+ (option-ref options 'verbose #f)) 75 | (define +db-connection+ 76 | (option-ref options 77 | 'db-connection 78 | "p-rout:p-rout:p_rout:/run/postgres:localhost")) 79 | (define +log-dir+ (option-ref options 'log-dir "log-r")) 80 | (define +dump-file+ (let ((args (option-ref options '() #f))) ;default broken 81 | (if (null? args) #f (car args)))) 82 | (define *db* #f) 83 | (define *temp-db* #f) 84 | (define +temp-db-name-base+ "p_rout_temp") 85 | (define *temp-db-name* #f) 86 | 87 | ;;; Put a log entry into file +log-dir+/.log 88 | (define (file-log basename . message-parts) 89 | (system* "mkdir" "-p" +log-dir+) 90 | (let ((logfile 91 | (string-append +log-dir+ "/" (or basename "unexpected") ".log")) 92 | (out #f)) 93 | (dynamic-wind 94 | (lambda () (set! out (open-file logfile "a"))) 95 | (lambda () 96 | (display (now) out) 97 | (when +verbose+ 98 | (display (now))) 99 | (for-each 100 | (lambda (part) 101 | (display " " out) 102 | (display part out) 103 | (when +verbose+ 104 | (display " ") 105 | (display part))) 106 | message-parts) 107 | (newline out) 108 | (when +verbose+ 109 | (newline))) 110 | (lambda () (close out))))) 111 | 112 | (define (now) (date->string (current-date) "~4")) 113 | 114 | (define (dot-append . strings) (string-join strings ".")) 115 | 116 | ;;; Tell whether program is currently running 117 | (define (running-process? program) 118 | (let ((ps-port #f)) 119 | (dynamic-wind 120 | (lambda () 121 | (set! ps-port 122 | (open-input-pipe (string-append "ps -C " program " -o pid=")))) 123 | (lambda () 124 | (not (eof-object? (read-line ps-port)))) 125 | (lambda () 126 | (close-pipe ps-port))))) 127 | 128 | ;;; Exit if program is currently running 129 | (define (exit-if-running-process program) 130 | (when (running-process? program) 131 | (display 132 | (string-append "\ 133 | There seems to be a running process by the name of " program " \ 134 | which may attempt to write into the database during the restore operation. 135 | This could mess up the database; therefore, I'm giving up now. 136 | ")) 137 | (exit))) 138 | 139 | ;;; Exit on missing command line argument 140 | (define (exit-if-missing-filename) 141 | (unless +dump-file+ 142 | (display "no dump file given\n") 143 | (exit))) 144 | 145 | ;;; Send SQL query to database 146 | ;;; ignore-codes are expected database error codes that don't cause 147 | ;;; log entries 148 | (define (logged-query dbi-db logfile query . ignore-codes) 149 | ;; guile-dbd-postgresql v2.1.4 refuses to do anything until we've read 150 | ;; any previous results 151 | (while (dbi-get_row dbi-db)) 152 | (when +verbose+ 153 | (file-log logfile query)) 154 | (dbi-query dbi-db query) 155 | (match (dbi-get_status dbi-db) 156 | ((code . message) 157 | (if (member code `(0 ,@ignore-codes)) 158 | #f 159 | (begin 160 | (file-log logfile message) 161 | (cons code message)))) 162 | (unexpected 163 | (file-log logfile "weird status message: " unexpected) 164 | unexpected))) 165 | 166 | ;;; List of all databases in current DB cluster 167 | (define (databases) 168 | (logged-query *db* 169 | "restore" 170 | "SELECT datname FROM pg_database WHERE datistemplate = false") 171 | (do ((database (dbi-get_row *db*) (dbi-get_row *db*)) 172 | (databases '())) 173 | ((not database) databases) 174 | (set! databases (cons (cdar database) databases)))) 175 | 176 | ;;; Return colon-separated string db-connection as a list 177 | (define (db-connection-list db-connection) 178 | (string-split +db-connection+ #\:)) 179 | 180 | (define (db-user) 181 | (first (db-connection-list +db-connection+))) 182 | (define (db-password) 183 | (second (db-connection-list +db-connection+))) 184 | (define (db-name) 185 | (third (db-connection-list +db-connection+))) 186 | (define (db-path-or-ip) 187 | (fourth (db-connection-list +db-connection+))) 188 | (define (db-host) 189 | (fifth (db-connection-list +db-connection+))) 190 | (define (db-port) 191 | (if (= 6 (length (db-connection-list +db-connection+))) 192 | (sixth (db-connection-list +db-connection+)) 193 | "5432")) 194 | 195 | ;;; Return database object for a freshly created DB; store DB name 196 | ;;; in *temp-db-name* 197 | (define (temp-db) 198 | (do ((i 0 (1+ i)) 199 | (tempname +temp-db-name-base+)) 200 | ((not (member tempname (databases))) 201 | (set! *temp-db-name* tempname) 202 | (logged-query 203 | *db* "restore" (string-append "CREATE DATABASE " *temp-db-name*)) 204 | (dbi-get_row *db*) ;waiting for the new DB 205 | (dbi-open "postgresql" (string-join (list (db-user) 206 | (db-password) 207 | *temp-db-name* 208 | (db-path-or-ip) 209 | (db-host) 210 | (db-port)) 211 | ":"))) 212 | (set! tempname (string-append +temp-db-name-base+ (number->string i))))) 213 | 214 | ;;; Pipe gzipped sql-file via psql into db-name 215 | (define (psql-apply-dump db-name sql-file) 216 | (system (string-append 217 | "zcat " sql-file 218 | " | psql --username=" (db-user) 219 | " --no-password --host=" (db-host) 220 | " --port=" (db-port) " " 221 | db-name))) 222 | 223 | ;;; Dump source-db and pipe that via psql into destination-db 224 | (define (dump-into destination-db-name source-db-name) 225 | (system (string-append 226 | "pg_dump --username=" (db-user) 227 | " --no-password --host=" (db-host) 228 | " --port=" (db-port) " " source-db-name 229 | " | psql --username=" (db-user) 230 | " --no-password --host=" (db-host) 231 | " --port=" (db-port) " " 232 | destination-db-name))) 233 | 234 | ;;; Return a list of all user tables in db that have a +record-id-column+ 235 | (define (tables db) 236 | (logged-query 237 | db 238 | "restore" 239 | (string-append 240 | "SELECT t.table_schema || '.' || t.table_name" 241 | " FROM information_schema.tables AS t" 242 | " JOIN information_schema.columns AS c" 243 | " USING (table_name, table_schema)" 244 | " WHERE table_type = 'BASE TABLE'" 245 | " AND t.table_schema NOT IN ('pg_catalog', 'information_schema')" 246 | " AND c.column_name = '" +record-id-column+ "'")) 247 | (do ((table (dbi-get_row db) (dbi-get_row db)) 248 | (tables '())) 249 | ((not table) tables) 250 | (set! tables (cons (cdar table) tables)))) 251 | 252 | ;;; Maximum value of +record-id-column+ in db 253 | (define (max-record-id-column db) 254 | (if (null? (tables db)) 255 | 0 256 | (apply 257 | max 258 | (map (lambda (table) 259 | (logged-query 260 | db "restore" (string-append 261 | "SELECT max(" +record-id-column+ ") FROM " table)) 262 | (cdar (dbi-get_row db))) 263 | (tables db))))) 264 | 265 | (define (increase-record-id-column db table amount) 266 | (logged-query 267 | db 268 | "restore" 269 | (string-append 270 | "UPDATE " table 271 | " SET " +record-id-column+ 272 | " = " +record-id-column+ 273 | " + " (number->string amount)))) 274 | 275 | ;;; Add amount to all values of +record-id-column+ in db 276 | (define (increase-record-id-columns db amount) 277 | (for-each 278 | (lambda (table) 279 | (increase-record-id-column db table amount)) 280 | (tables db))) 281 | 282 | (exit-if-missing-filename) 283 | (exit-if-running-process "p-rout-collect.scm") 284 | 285 | (dynamic-wind 286 | (lambda () (set! *db* (dbi-open "postgresql" +db-connection+))) 287 | (lambda () 288 | (dynamic-wind 289 | (lambda () (set! *temp-db* (temp-db))) 290 | (lambda () 291 | (psql-apply-dump *temp-db-name* +dump-file+) 292 | (increase-record-id-columns 293 | *temp-db* 294 | (+ 1000 (max-record-id-column *db*))) 295 | (dump-into (db-name) *temp-db-name*) 296 | (display "\ 297 | 298 | Hint: Messages saying 299 | ERROR: already exists 300 | can be ignored. 301 | ")) 302 | (lambda () 303 | (dbi-close *temp-db*) 304 | (logged-query 305 | *db* "restore" (string-append "DROP DATABASE " *temp-db-name*))))) 306 | (lambda () (dbi-close *db*))) 307 | -------------------------------------------------------------------------------- /p-rout-collect.scm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/guile -s 2 | !# 3 | 4 | ;;;; Copyright (c) 2014, 2015 Bert Burgemeister trebbu@googlemail.com 5 | ;;;; 6 | ;;;; Permission is hereby granted, free of charge, to any person 7 | ;;;; obtaining a copy of this software and associated documentation 8 | ;;;; files (the "Software"), to deal in the Software without 9 | ;;;; restriction, including without limitation the rights to use, 10 | ;;;; copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | ;;;; copies of the Software, and to permit persons to whom the 12 | ;;;; Software is furnished to do so, subject to the following 13 | ;;;; conditions: 14 | ;;;; 15 | ;;;; The above copyright notice and this permission notice shall be 16 | ;;;; included in all copies or substantial portions of the Software. 17 | ;;;; 18 | ;;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | ;;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | ;;;; OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | ;;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | ;;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | ;;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | ;;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | ;;;; OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | 28 | ;;;; We handle POST requests whose URI ends with ".json". Suppose we 29 | ;;;; are receiving a request with a URI path of /some/path/foo.json 30 | ;;;; and a body like this: 31 | ;;;; 32 | ;;;; { "table_one" : 33 | ;;;; [ { "column1" : 5001, 34 | ;;;; "column2" : 1 35 | ;;;; }, 36 | ;;;; { "column1" : 9, 37 | ;;;; "column3" : 1, 38 | ;;;; "column4" : 342 39 | ;;;; } 40 | ;;;; ], 41 | ;;;; "table_two" : 42 | ;;;; { "column1" : 3, 43 | ;;;; "column2" : "2014-04-20T22:09:15+01:00" 44 | ;;;; }, 45 | ;;;; "third_table" : "more stuff" 46 | ;;;; } 47 | ;;;; 48 | ;;;; The following will happen: 49 | ;;;; 50 | ;;;; - Store two rows in foo.table_one. 51 | ;;;; (1) set column1 = "5001", column2 = "1" 52 | ;;;; (2) set column1 = "3", column3 = "1", column4 = "342" 53 | ;;;; - Store one row in foo.table_two. 54 | ;;;; (1) set column1 = "3", column2 = "2014-04-20T22:09:15+01:00" 55 | ;;;; - Store one row in foo.third_table. 56 | ;;;; (1) set column data = "more stuff" 57 | ;;;; - Every table has an additional column whose name is in 58 | ;;;; +record-id-column+ which is unique for each record. 59 | ;;;; - Database schemas ("foo" in this example) and tables will be 60 | ;;;; created and columns will be added as necessary. 61 | 62 | ;; On Debian, we may need to 63 | ;; wget http://download.savannah.gnu.org/releases/guile-json/guile-json-0.3.1.tar.gz, 64 | ;; wget http://download.gna.org/guile-dbi/guile-dbi-2.1.5.tar.gz, 65 | ;; wget http://download.gna.org/guile-dbi/guile-dbd-postgresql-2.1.4.tar.gz 66 | ;; ./configure; make; make install, respectively, 67 | ;; ldconfig, 68 | ;; and to 69 | ;; (add-to-load-path "/usr/local/share/guile/site/") 70 | 71 | (use-modules (web server) 72 | (web request) 73 | (web response) 74 | (web uri) 75 | (rnrs bytevectors) 76 | (json) 77 | (dbi dbi) 78 | (srfi srfi-1) 79 | (srfi srfi-19) 80 | (ice-9 getopt-long) 81 | (ice-9 pretty-print) 82 | (ice-9 match) 83 | (ice-9 rdelim)) 84 | 85 | ;;; Return first line of file at path, or return #f 86 | (define (read-line-from-file path) 87 | (catch 88 | #t 89 | (lambda () (call-with-input-file path (lambda (in) (read-line in)))) 90 | (lambda (k . args) #f))) 91 | 92 | (define option-spec 93 | '((help (single-char #\h)) 94 | (version (single-char #\V)) 95 | (verbose (single-char #\v)) 96 | (addr (single-char #\a) (value #t)) 97 | (port (single-char #\p) (value #t)) 98 | (db-connection (value #t)) 99 | (log-dir (value #t)) 100 | (no-daemon) 101 | (pid-file (value #t)))) 102 | 103 | (define options (getopt-long (command-line) option-spec)) 104 | 105 | (when (option-ref options 'help #f) 106 | (display (car (command-line))) 107 | (display "\ 108 | [options] 109 | -h, --help Display this help 110 | -V, --version Display version number 111 | -v, --verbose Display debugging output 112 | -a, --addr Address to listen on 113 | -p, --port Port to listen on 114 | --db-connection :::[:port] (default: 115 | p-rout:p-rout:p_rout:/run/postgres:localhost) 116 | --log-dir Log directory (default: \"log\") 117 | --no-daemon Remain in foreground 118 | --pid-file Store daemon's PID here (default: 119 | /var/run/p-rout/p-rout-collect.pid) 120 | ") 121 | (exit)) 122 | 123 | (when (option-ref options 'version #f) 124 | (display (or (read-line-from-file "./VERSION") 125 | (read-line-from-file "/usr/share/p-rout/VERSION") 126 | "unknown")) 127 | (newline) 128 | (exit)) 129 | 130 | (define +record-id-column+ "p_rout_id") 131 | (define +verbose+ (option-ref options 'verbose #f)) 132 | (define +db-connection+ 133 | (option-ref options 134 | 'db-connection 135 | "p-rout:p-rout:p_rout:/run/postgres:localhost")) 136 | (define +log-dir+ (option-ref options 'log-dir "log")) 137 | (define +addr+ (option-ref options 'addr "10.11.0.3")) 138 | (define +port+ (string->number (option-ref options 'port "80"))) 139 | (define +no-daemon+ (option-ref options 'no-daemon #f)) 140 | (define +pid-file+ (option-ref options 'pid-file "/var/run/p-rout/p-rout-collect.pid")) 141 | (define *db* #f) 142 | 143 | ;;; The main HTTP handler 144 | (define (p-rout-collector request body) 145 | (cond ((eq? (request-method request) 'POST) 146 | (json-handler request body)) 147 | (else (not-found-handler request body)))) 148 | 149 | ;;; Unanticipated access 150 | (define (not-found-handler request body) 151 | (file-log #f "Unexpected request:" request body) 152 | (values (build-response #:code 404) 153 | (string-append "Not found: " 154 | (uri->string (request-uri request))))) 155 | 156 | ;;; The purpose of this HTTP server 157 | (define (json-handler request body) 158 | (when +verbose+ 159 | (file-log 160 | "json-handler" "Handling request " request (utf8->string body))) 161 | (store-record (uri-path (request-uri request)) 162 | (json-string->scm (utf8->string body))) 163 | (values (build-response #:code 201 164 | #:reason-phrase "Created" 165 | #:headers `((content-type . (application/json)) 166 | (charset . "utf-8") 167 | (server . "nginx") 168 | (cache-control . (no-cache 169 | private 170 | (max-age . 0) 171 | must-revalidate)) 172 | (date . ,(current-date)) 173 | (connection . (keep-alive)))) 174 | (scm->json-string (json (object ("status" "ok") 175 | ("next-log-level" 2)))))) 176 | 177 | ;;; Convert JSON hashtable into a list structure 178 | (define (ht->l x) 179 | (cond 180 | ((hash-table? x) 181 | (hash-map->list (lambda (a b) (cons a (ht->l b))) x)) 182 | ((list? x) 183 | (map (lambda (item) (ht->l item)) x)) 184 | (else x))) 185 | 186 | ;;; Extract a list of toplevel names from JSON hashtable 187 | (define (ht->tablenames json) 188 | (map car (ht->l json))) 189 | 190 | ;;; Extract from JSON hashtable data for one table 191 | (define (ht->table-content json tablename) 192 | (cdar (filter (lambda (x) (equal? tablename (car x))) 193 | (ht->l json)))) 194 | 195 | ;;; Put a log entry into file +log-dir+/.log 196 | (define (file-log basename . message-parts) 197 | (system* "mkdir" "-p" +log-dir+) 198 | (let ((logfile 199 | (string-append +log-dir+ "/" (or basename "unexpected") ".log")) 200 | (out #f)) 201 | (dynamic-wind 202 | (lambda () (set! out (open-file logfile "a"))) 203 | (lambda () 204 | (display (now) out) 205 | (when +verbose+ 206 | (display (now))) 207 | (for-each 208 | (lambda (part) 209 | (display " " out) 210 | (display part out) 211 | (when +verbose+ 212 | (display " ") 213 | (display part))) 214 | message-parts) 215 | (newline out) 216 | (when +verbose+ 217 | (newline))) 218 | (lambda () (close out))))) 219 | 220 | (define (now) (date->string (current-date) "~4")) 221 | 222 | (define (dot-append . strings) (string-join strings ".")) 223 | 224 | ;;; Send SQL query to database 225 | ;;; ignore-codes are expected database error codes that don't cause 226 | ;;; log entries 227 | (define (logged-query logfile query . ignore-codes) 228 | ;; guile-dbd-postgresql v2.1.4 refuses to do anything until we've read 229 | ;; any previous results 230 | (while (dbi-get_row *db*)) 231 | (when +verbose+ 232 | (file-log logfile query)) 233 | (dbi-query *db* query) 234 | (match (dbi-get_status *db*) 235 | ((code . message) 236 | (if (member code `(0 ,@ignore-codes)) 237 | #f 238 | (begin 239 | (file-log logfile message) 240 | (cons code message)))) 241 | (unexpected 242 | (file-log logfile "weird status message: " unexpected) 243 | unexpected))) 244 | 245 | ;;; Make a bunch of SQL tables if necessary 246 | (define (create-tables schema tablenames) 247 | (logged-query "db" (string-append "CREATE SCHEMA IF NOT EXISTS " schema)) 248 | (for-each 249 | (lambda (tablename) 250 | (logged-query (dot-append schema tablename) 251 | (string-join `("CREATE TABLE IF NOT EXISTS" 252 | ,(dot-append schema tablename) 253 | "(" ,+record-id-column+ "integer)")))) 254 | tablenames)) 255 | 256 | (define (column-names schema table) 257 | (logged-query (dot-append schema table) 258 | (string-join 259 | `("SELECT * FROM" ,(dot-append schema table) "LIMIT 1"))) 260 | (let ((result (dbi-get_row *db*))) 261 | (if result 262 | (map car result) 263 | '()))) 264 | 265 | ;;; Add a bunch of columns to an SQL table 266 | (define (add-columns schema table columnnames) 267 | (let* ((present-columns (column-names schema table)) 268 | (missing-columns (lset-difference 269 | string= columnnames present-columns))) 270 | (for-each 271 | (lambda (columnname) 272 | (logged-query (dot-append schema table) 273 | (string-join `("ALTER TABLE" 274 | ,(dot-append schema table) 275 | "ADD COLUMN" ,columnname "text")))) 276 | missing-columns))) 277 | 278 | ;;; Extract from table-content the set of column names 279 | (define (columnnames table-content) 280 | (let* ((result '()) 281 | (collect-columnnames 282 | (lambda (name-value-pair) 283 | (set! result 284 | (cons (car name-value-pair) result))))) 285 | (map-rows collect-columnnames table-content) 286 | (sort (delete-duplicates result) stringstring val) 308 | (string-join `("'" ,val "'") ""))) 309 | (cons record-id values)) 310 | ", "))) 311 | (logged-query (dot-append schema table) 312 | (string-join `("INSERT INTO" 313 | ,(dot-append schema table) 314 | "(" ,names ") VALUES (" ,vals ")"))))) 315 | 316 | ;;; "/some/path/foo.json" -> "foo" 317 | (define (uri-path->basename uri-path) 318 | (let ((uri-extension ".json") 319 | (filename (last (split-and-decode-uri-path uri-path)))) 320 | (if (string-suffix? uri-extension filename) 321 | (string-drop-right filename (string-length uri-extension)) 322 | #f))) 323 | 324 | ;;; Put data from JSON payload into SQL database whose name is derived 325 | ;;; from uri-path 326 | (define (store-record uri-path payload) 327 | (let ((schema (uri-path->basename uri-path))) 328 | (if schema 329 | (add-record schema payload) 330 | (file-log #f "Unexpected POST request:" uri-path)))) 331 | 332 | ;;; Store one record into SQL database 333 | (define (add-record schema json) 334 | (let ((tablenames (ht->tablenames json))) 335 | (create-tables schema tablenames) 336 | (logged-query "db" "BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE") 337 | (let ((next-id 338 | (apply 339 | max 340 | (map 341 | (lambda (table) 342 | (logged-query (dot-append schema table) 343 | (string-join `("SELECT max(" 344 | ,+record-id-column+ 345 | ") AS last_id FROM" 346 | ,(dot-append schema table)))) 347 | (let ((last-id (dbi-get_row *db*))) 348 | (match last-id 349 | ((("last_id" . latest-row)) 350 | (if (number? latest-row) 351 | (+ 1 latest-row) 352 | 0)) 353 | (_ 0)))) 354 | tablenames)))) 355 | (for-each 356 | (lambda (table) 357 | (let ((table-content (ht->table-content json table))) 358 | (add-columns schema 359 | table 360 | (columnnames table-content)) 361 | (let ((names (map-rows car table-content)) 362 | (values (map-rows cdr table-content))) 363 | (match names 364 | (((_ ...) ...) 365 | (for-each 366 | (lambda (names values) 367 | (add-row schema table names values next-id)) 368 | names values)) 369 | ((_ ...) 370 | (add-row schema table names values next-id)))))) 371 | tablenames)) 372 | (logged-query "db" "COMMIT TRANSACTION"))) 373 | 374 | (unless +no-daemon+ 375 | (let ((pid (primitive-fork))) 376 | (cond ((> pid 0) 377 | (with-output-to-file +pid-file+ 378 | (lambda () (display pid))) 379 | (primitive-exit 0)) 380 | ((< pid 0) 381 | (primitive-exit 1)) 382 | (else 383 | (umask 0) 384 | (setsid))))) 385 | 386 | (dynamic-wind 387 | (lambda () (set! *db* (dbi-open "postgresql" +db-connection+))) 388 | (lambda () 389 | (logged-query "db" "SET client_min_messages = WARNING;") 390 | (run-server p-rout-collector 391 | 'http 392 | `(#:port ,+port+ #:addr ,(inet-pton AF_INET +addr+)))) 393 | (lambda () (dbi-close *db*))) 394 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Installation Instructions 2 | ************************* 3 | 4 | Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, 5 | Inc. 6 | 7 | Copying and distribution of this file, with or without modification, 8 | are permitted in any medium without royalty provided the copyright 9 | notice and this notice are preserved. This file is offered as-is, 10 | without warranty of any kind. 11 | 12 | Basic Installation 13 | ================== 14 | 15 | Briefly, the shell command `./configure && make && make install' 16 | should configure, build, and install this package. The following 17 | more-detailed instructions are generic; see the `README' file for 18 | instructions specific to this package. Some packages provide this 19 | `INSTALL' file but do not implement all of the features documented 20 | below. The lack of an optional feature in a given package is not 21 | necessarily a bug. More recommendations for GNU packages can be found 22 | in *note Makefile Conventions: (standards)Makefile Conventions. 23 | 24 | The `configure' shell script attempts to guess correct values for 25 | various system-dependent variables used during compilation. It uses 26 | those values to create a `Makefile' in each directory of the package. 27 | It may also create one or more `.h' files containing system-dependent 28 | definitions. Finally, it creates a shell script `config.status' that 29 | you can run in the future to recreate the current configuration, and a 30 | file `config.log' containing compiler output (useful mainly for 31 | debugging `configure'). 32 | 33 | It can also use an optional file (typically called `config.cache' 34 | and enabled with `--cache-file=config.cache' or simply `-C') that saves 35 | the results of its tests to speed up reconfiguring. Caching is 36 | disabled by default to prevent problems with accidental use of stale 37 | cache files. 38 | 39 | If you need to do unusual things to compile the package, please try 40 | to figure out how `configure' could check whether to do them, and mail 41 | diffs or instructions to the address given in the `README' so they can 42 | be considered for the next release. If you are using the cache, and at 43 | some point `config.cache' contains results you don't want to keep, you 44 | may remove or edit it. 45 | 46 | The file `configure.ac' (or `configure.in') is used to create 47 | `configure' by a program called `autoconf'. You need `configure.ac' if 48 | you want to change it or regenerate `configure' using a newer version 49 | of `autoconf'. 50 | 51 | The simplest way to compile this package is: 52 | 53 | 1. `cd' to the directory containing the package's source code and type 54 | `./configure' to configure the package for your system. 55 | 56 | Running `configure' might take a while. While running, it prints 57 | some messages telling which features it is checking for. 58 | 59 | 2. Type `make' to compile the package. 60 | 61 | 3. Optionally, type `make check' to run any self-tests that come with 62 | the package, generally using the just-built uninstalled binaries. 63 | 64 | 4. Type `make install' to install the programs and any data files and 65 | documentation. When installing into a prefix owned by root, it is 66 | recommended that the package be configured and built as a regular 67 | user, and only the `make install' phase executed with root 68 | privileges. 69 | 70 | 5. Optionally, type `make installcheck' to repeat any self-tests, but 71 | this time using the binaries in their final installed location. 72 | This target does not install anything. Running this target as a 73 | regular user, particularly if the prior `make install' required 74 | root privileges, verifies that the installation completed 75 | correctly. 76 | 77 | 6. You can remove the program binaries and object files from the 78 | source code directory by typing `make clean'. To also remove the 79 | files that `configure' created (so you can compile the package for 80 | a different kind of computer), type `make distclean'. There is 81 | also a `make maintainer-clean' target, but that is intended mainly 82 | for the package's developers. If you use it, you may have to get 83 | all sorts of other programs in order to regenerate files that came 84 | with the distribution. 85 | 86 | 7. Often, you can also type `make uninstall' to remove the installed 87 | files again. In practice, not all packages have tested that 88 | uninstallation works correctly, even though it is required by the 89 | GNU Coding Standards. 90 | 91 | 8. Some packages, particularly those that use Automake, provide `make 92 | distcheck', which can by used by developers to test that all other 93 | targets like `make install' and `make uninstall' work correctly. 94 | This target is generally not run by end users. 95 | 96 | Compilers and Options 97 | ===================== 98 | 99 | Some systems require unusual options for compilation or linking that 100 | the `configure' script does not know about. Run `./configure --help' 101 | for details on some of the pertinent environment variables. 102 | 103 | You can give `configure' initial values for configuration parameters 104 | by setting variables in the command line or in the environment. Here 105 | is an example: 106 | 107 | ./configure CC=c99 CFLAGS=-g LIBS=-lposix 108 | 109 | *Note Defining Variables::, for more details. 110 | 111 | Compiling For Multiple Architectures 112 | ==================================== 113 | 114 | You can compile the package for more than one kind of computer at the 115 | same time, by placing the object files for each architecture in their 116 | own directory. To do this, you can use GNU `make'. `cd' to the 117 | directory where you want the object files and executables to go and run 118 | the `configure' script. `configure' automatically checks for the 119 | source code in the directory that `configure' is in and in `..'. This 120 | is known as a "VPATH" build. 121 | 122 | With a non-GNU `make', it is safer to compile the package for one 123 | architecture at a time in the source code directory. After you have 124 | installed the package for one architecture, use `make distclean' before 125 | reconfiguring for another architecture. 126 | 127 | On MacOS X 10.5 and later systems, you can create libraries and 128 | executables that work on multiple system types--known as "fat" or 129 | "universal" binaries--by specifying multiple `-arch' options to the 130 | compiler but only a single `-arch' option to the preprocessor. Like 131 | this: 132 | 133 | ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ 134 | CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ 135 | CPP="gcc -E" CXXCPP="g++ -E" 136 | 137 | This is not guaranteed to produce working output in all cases, you 138 | may have to build one architecture at a time and combine the results 139 | using the `lipo' tool if you have problems. 140 | 141 | Installation Names 142 | ================== 143 | 144 | By default, `make install' installs the package's commands under 145 | `/usr/local/bin', include files under `/usr/local/include', etc. You 146 | can specify an installation prefix other than `/usr/local' by giving 147 | `configure' the option `--prefix=PREFIX', where PREFIX must be an 148 | absolute file name. 149 | 150 | You can specify separate installation prefixes for 151 | architecture-specific files and architecture-independent files. If you 152 | pass the option `--exec-prefix=PREFIX' to `configure', the package uses 153 | PREFIX as the prefix for installing programs and libraries. 154 | Documentation and other data files still use the regular prefix. 155 | 156 | In addition, if you use an unusual directory layout you can give 157 | options like `--bindir=DIR' to specify different values for particular 158 | kinds of files. Run `configure --help' for a list of the directories 159 | you can set and what kinds of files go in them. In general, the 160 | default for these options is expressed in terms of `${prefix}', so that 161 | specifying just `--prefix' will affect all of the other directory 162 | specifications that were not explicitly provided. 163 | 164 | The most portable way to affect installation locations is to pass the 165 | correct locations to `configure'; however, many packages provide one or 166 | both of the following shortcuts of passing variable assignments to the 167 | `make install' command line to change installation locations without 168 | having to reconfigure or recompile. 169 | 170 | The first method involves providing an override variable for each 171 | affected directory. For example, `make install 172 | prefix=/alternate/directory' will choose an alternate location for all 173 | directory configuration variables that were expressed in terms of 174 | `${prefix}'. Any directories that were specified during `configure', 175 | but not in terms of `${prefix}', must each be overridden at install 176 | time for the entire installation to be relocated. The approach of 177 | makefile variable overrides for each directory variable is required by 178 | the GNU Coding Standards, and ideally causes no recompilation. 179 | However, some platforms have known limitations with the semantics of 180 | shared libraries that end up requiring recompilation when using this 181 | method, particularly noticeable in packages that use GNU Libtool. 182 | 183 | The second method involves providing the `DESTDIR' variable. For 184 | example, `make install DESTDIR=/alternate/directory' will prepend 185 | `/alternate/directory' before all installation names. The approach of 186 | `DESTDIR' overrides is not required by the GNU Coding Standards, and 187 | does not work on platforms that have drive letters. On the other hand, 188 | it does better at avoiding recompilation issues, and works well even 189 | when some directory options were not specified in terms of `${prefix}' 190 | at `configure' time. 191 | 192 | Optional Features 193 | ================= 194 | 195 | If the package supports it, you can cause programs to be installed 196 | with an extra prefix or suffix on their names by giving `configure' the 197 | option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. 198 | 199 | Some packages pay attention to `--enable-FEATURE' options to 200 | `configure', where FEATURE indicates an optional part of the package. 201 | They may also pay attention to `--with-PACKAGE' options, where PACKAGE 202 | is something like `gnu-as' or `x' (for the X Window System). The 203 | `README' should mention any `--enable-' and `--with-' options that the 204 | package recognizes. 205 | 206 | For packages that use the X Window System, `configure' can usually 207 | find the X include and library files automatically, but if it doesn't, 208 | you can use the `configure' options `--x-includes=DIR' and 209 | `--x-libraries=DIR' to specify their locations. 210 | 211 | Some packages offer the ability to configure how verbose the 212 | execution of `make' will be. For these packages, running `./configure 213 | --enable-silent-rules' sets the default to minimal output, which can be 214 | overridden with `make V=1'; while running `./configure 215 | --disable-silent-rules' sets the default to verbose, which can be 216 | overridden with `make V=0'. 217 | 218 | Particular systems 219 | ================== 220 | 221 | On HP-UX, the default C compiler is not ANSI C compatible. If GNU 222 | CC is not installed, it is recommended to use the following options in 223 | order to use an ANSI C compiler: 224 | 225 | ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" 226 | 227 | and if that doesn't work, install pre-built binaries of GCC for HP-UX. 228 | 229 | HP-UX `make' updates targets which have the same time stamps as 230 | their prerequisites, which makes it generally unusable when shipped 231 | generated files such as `configure' are involved. Use GNU `make' 232 | instead. 233 | 234 | On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot 235 | parse its `' header file. The option `-nodtk' can be used as 236 | a workaround. If GNU CC is not installed, it is therefore recommended 237 | to try 238 | 239 | ./configure CC="cc" 240 | 241 | and if that doesn't work, try 242 | 243 | ./configure CC="cc -nodtk" 244 | 245 | On Solaris, don't put `/usr/ucb' early in your `PATH'. This 246 | directory contains several dysfunctional programs; working variants of 247 | these programs are available in `/usr/bin'. So, if you need `/usr/ucb' 248 | in your `PATH', put it _after_ `/usr/bin'. 249 | 250 | On Haiku, software installed for all users goes in `/boot/common', 251 | not `/usr/local'. It is recommended to use the following options: 252 | 253 | ./configure --prefix=/boot/common 254 | 255 | Specifying the System Type 256 | ========================== 257 | 258 | There may be some features `configure' cannot figure out 259 | automatically, but needs to determine by the type of machine the package 260 | will run on. Usually, assuming the package is built to be run on the 261 | _same_ architectures, `configure' can figure that out, but if it prints 262 | a message saying it cannot guess the machine type, give it the 263 | `--build=TYPE' option. TYPE can either be a short name for the system 264 | type, such as `sun4', or a canonical name which has the form: 265 | 266 | CPU-COMPANY-SYSTEM 267 | 268 | where SYSTEM can have one of these forms: 269 | 270 | OS 271 | KERNEL-OS 272 | 273 | See the file `config.sub' for the possible values of each field. If 274 | `config.sub' isn't included in this package, then this package doesn't 275 | need to know the machine type. 276 | 277 | If you are _building_ compiler tools for cross-compiling, you should 278 | use the option `--target=TYPE' to select the type of system they will 279 | produce code for. 280 | 281 | If you want to _use_ a cross compiler, that generates code for a 282 | platform different from the build platform, you should specify the 283 | "host" platform (i.e., that on which the generated programs will 284 | eventually be run) with `--host=TYPE'. 285 | 286 | Sharing Defaults 287 | ================ 288 | 289 | If you want to set default values for `configure' scripts to share, 290 | you can create a site shell script called `config.site' that gives 291 | default values for variables like `CC', `cache_file', and `prefix'. 292 | `configure' looks for `PREFIX/share/config.site' if it exists, then 293 | `PREFIX/etc/config.site' if it exists. Or, you can set the 294 | `CONFIG_SITE' environment variable to the location of the site script. 295 | A warning: not all `configure' scripts look for a site script. 296 | 297 | Defining Variables 298 | ================== 299 | 300 | Variables not defined in a site shell script can be set in the 301 | environment passed to `configure'. However, some packages may run 302 | configure again during the build, and the customized values of these 303 | variables may be lost. In order to avoid this problem, you should set 304 | them in the `configure' command line, using `VAR=value'. For example: 305 | 306 | ./configure CC=/usr/local2/bin/gcc 307 | 308 | causes the specified `gcc' to be used as the C compiler (unless it is 309 | overridden in the site shell script). 310 | 311 | Unfortunately, this technique does not work for `CONFIG_SHELL' due to 312 | an Autoconf limitation. Until the limitation is lifted, you can use 313 | this workaround: 314 | 315 | CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash 316 | 317 | `configure' Invocation 318 | ====================== 319 | 320 | `configure' recognizes the following options to control how it 321 | operates. 322 | 323 | `--help' 324 | `-h' 325 | Print a summary of all of the options to `configure', and exit. 326 | 327 | `--help=short' 328 | `--help=recursive' 329 | Print a summary of the options unique to this package's 330 | `configure', and exit. The `short' variant lists options used 331 | only in the top level, while the `recursive' variant lists options 332 | also present in any nested packages. 333 | 334 | `--version' 335 | `-V' 336 | Print the version of Autoconf used to generate the `configure' 337 | script, and exit. 338 | 339 | `--cache-file=FILE' 340 | Enable the cache: use and save the results of the tests in FILE, 341 | traditionally `config.cache'. FILE defaults to `/dev/null' to 342 | disable caching. 343 | 344 | `--config-cache' 345 | `-C' 346 | Alias for `--cache-file=config.cache'. 347 | 348 | `--quiet' 349 | `--silent' 350 | `-q' 351 | Do not print messages saying which checks are being made. To 352 | suppress all normal output, redirect it to `/dev/null' (any error 353 | messages will still be shown). 354 | 355 | `--srcdir=DIR' 356 | Look for the package's source code in directory DIR. Usually 357 | `configure' can determine that directory automatically. 358 | 359 | `--prefix=DIR' 360 | Use DIR as the installation prefix. *note Installation Names:: 361 | for more details, including other options available for fine-tuning 362 | the installation locations. 363 | 364 | `--no-create' 365 | `-n' 366 | Run the configure checks, but stop before creating any output 367 | files. 368 | 369 | `configure' also accepts some other, not widely useful, options. Run 370 | `configure --help' for more details. 371 | -------------------------------------------------------------------------------- /test/p-rout-test-dump.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- PostgreSQL database dump 3 | -- 4 | 5 | SET statement_timeout = 0; 6 | SET lock_timeout = 0; 7 | SET client_encoding = 'UTF8'; 8 | SET standard_conforming_strings = on; 9 | SET check_function_bodies = false; 10 | SET client_min_messages = warning; 11 | 12 | -- 13 | -- Name: events; Type: SCHEMA; Schema: -; Owner: p-rout 14 | -- 15 | 16 | CREATE SCHEMA events; 17 | 18 | 19 | ALTER SCHEMA events OWNER TO "p-rout"; 20 | 21 | -- 22 | -- Name: logs; Type: SCHEMA; Schema: -; Owner: p-rout 23 | -- 24 | 25 | CREATE SCHEMA logs; 26 | 27 | 28 | ALTER SCHEMA logs OWNER TO "p-rout"; 29 | 30 | -- 31 | -- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: 32 | -- 33 | 34 | CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; 35 | 36 | 37 | -- 38 | -- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: 39 | -- 40 | 41 | COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; 42 | 43 | 44 | SET search_path = events, pg_catalog; 45 | 46 | SET default_tablespace = ''; 47 | 48 | SET default_with_oids = false; 49 | 50 | -- 51 | -- Name: event; Type: TABLE; Schema: events; Owner: p-rout; Tablespace: 52 | -- 53 | 54 | CREATE TABLE event ( 55 | p_rout_id integer, 56 | data text 57 | ); 58 | 59 | 60 | ALTER TABLE events.event OWNER TO "p-rout"; 61 | 62 | -- 63 | -- Name: header; Type: TABLE; Schema: events; Owner: p-rout; Tablespace: 64 | -- 65 | 66 | CREATE TABLE header ( 67 | p_rout_id integer, 68 | powerrouter_id text, 69 | time_send text, 70 | ver text, 71 | verification text 72 | ); 73 | 74 | 75 | ALTER TABLE events.header OWNER TO "p-rout"; 76 | 77 | SET search_path = logs, pg_catalog; 78 | 79 | -- 80 | -- Name: header; Type: TABLE; Schema: logs; Owner: p-rout; Tablespace: 81 | -- 82 | 83 | CREATE TABLE header ( 84 | p_rout_id integer, 85 | period text, 86 | powerrouter_id text, 87 | time_send text, 88 | version text 89 | ); 90 | 91 | 92 | ALTER TABLE logs.header OWNER TO "p-rout"; 93 | 94 | -- 95 | -- Name: module_statuses; Type: TABLE; Schema: logs; Owner: p-rout; Tablespace: 96 | -- 97 | 98 | CREATE TABLE module_statuses ( 99 | p_rout_id integer, 100 | module_id text, 101 | param_0 text, 102 | param_1 text, 103 | param_10 text, 104 | param_11 text, 105 | param_12 text, 106 | param_2 text, 107 | param_3 text, 108 | param_4 text, 109 | param_5 text, 110 | param_6 text, 111 | param_7 text, 112 | param_8 text, 113 | param_9 text, 114 | status text, 115 | version text 116 | ); 117 | 118 | 119 | ALTER TABLE logs.module_statuses OWNER TO "p-rout"; 120 | 121 | SET search_path = events, pg_catalog; 122 | 123 | -- 124 | -- Data for Name: event; Type: TABLE DATA; Schema: events; Owner: p-rout 125 | -- 126 | 127 | INSERT INTO event (p_rout_id, data) VALUES (1, 'D6305E0C880028000000000000000000A3FFFFFF9F0AF7FF00000000000000001E0026192927000000000000'); 128 | INSERT INTO event (p_rout_id, data) VALUES (2, 'D6305E0C8800210000000000000000006FF1D3009F0AF7FF00000000000000001E0026192927000000000000'); 129 | INSERT INTO event (p_rout_id, data) VALUES (3, 'D7305E0C880022000000000000000000FB2DF9FF9F0AF7FF00000000000000001E0026192927000000000000'); 130 | 131 | 132 | -- 133 | -- Data for Name: header; Type: TABLE DATA; Schema: events; Owner: p-rout 134 | -- 135 | 136 | INSERT INTO header (p_rout_id, powerrouter_id, time_send, ver, verification) VALUES (1, 'POWERROUTERIDXXX', '2014-07-29T16:13:45+01:00', '1', '0'); 137 | INSERT INTO header (p_rout_id, powerrouter_id, time_send, ver, verification) VALUES (2, 'POWERROUTERIDXXX', '2014-07-29T16:13:47+01:00', '1', '0'); 138 | INSERT INTO header (p_rout_id, powerrouter_id, time_send, ver, verification) VALUES (3, 'POWERROUTERIDXXX', '2014-07-29T16:13:47+01:00', '1', '0'); 139 | 140 | 141 | SET search_path = logs, pg_catalog; 142 | 143 | -- 144 | -- Data for Name: header; Type: TABLE DATA; Schema: logs; Owner: p-rout 145 | -- 146 | 147 | INSERT INTO header (p_rout_id, period, powerrouter_id, time_send, version) VALUES (1, '60', 'POWERROUTERIDXXX', '2014-07-29T16:14:01+01:00', '3'); 148 | INSERT INTO header (p_rout_id, period, powerrouter_id, time_send, version) VALUES (2, '60', 'POWERROUTERIDXXX', '2014-07-29T16:15:01+01:00', '3'); 149 | INSERT INTO header (p_rout_id, period, powerrouter_id, time_send, version) VALUES (3, '60', 'POWERROUTERIDXXX', '2014-07-29T16:16:01+01:00', '3'); 150 | INSERT INTO header (p_rout_id, period, powerrouter_id, time_send, version) VALUES (4, '60', 'POWERROUTERIDXXX', '2014-07-29T16:17:01+01:00', '3'); 151 | INSERT INTO header (p_rout_id, period, powerrouter_id, time_send, version) VALUES (5, '60', 'POWERROUTERIDXXX', '2014-07-29T16:18:01+01:00', '3'); 152 | INSERT INTO header (p_rout_id, period, powerrouter_id, time_send, version) VALUES (6, '60', 'POWERROUTERIDXXX', '2014-07-29T16:19:01+01:00', '3'); 153 | INSERT INTO header (p_rout_id, period, powerrouter_id, time_send, version) VALUES (7, '60', 'POWERROUTERIDXXX', '2014-07-29T16:20:01+01:00', '3'); 154 | INSERT INTO header (p_rout_id, period, powerrouter_id, time_send, version) VALUES (8, '60', 'POWERROUTERIDXXX', '2014-07-29T16:21:01+01:00', '3'); 155 | INSERT INTO header (p_rout_id, period, powerrouter_id, time_send, version) VALUES (9, '60', 'POWERROUTERIDXXX', '2014-07-29T16:22:01+01:00', '3'); 156 | INSERT INTO header (p_rout_id, period, powerrouter_id, time_send, version) VALUES (10, '60', 'POWERROUTERIDXXX', '2014-07-29T16:23:01+01:00', '3'); 157 | 158 | 159 | -- 160 | -- Data for Name: module_statuses; Type: TABLE DATA; Schema: logs; Owner: p-rout 161 | -- 162 | 163 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (1, '16', '4997', '2367', NULL, NULL, NULL, '400', '-1935', '1247552', '2032120', NULL, NULL, NULL, NULL, '18691', '1'); 164 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (1, '9', '4998', '2362', '547', NULL, NULL, '-2285', '2868821', '9099', '2349', '0', '2729', '40937', '-2268', '16147', '1'); 165 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (1, '136', '2720', '-99', '15500', '2412', '0', '-29', '531391', '630187', '100', '240', '302', '498', '2720', '19779', '1'); 166 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (1, '12', '29825', '619', '2533', '3362934', NULL, '1849', '1421522', '560', '30159', '227', '685', '1941412', '520', '49219', '1'); 167 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (1, '11', '2293', '42', '115', '1592000', NULL, '27', '428200', '2348', '886', '-2077', '1078300', '2324', '85', '1555', '1'); 168 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (2, '16', '4998', '2367', NULL, NULL, NULL, '400', '-1911', '1247585', '2032120', NULL, NULL, NULL, NULL, '18691', '1'); 169 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (2, '9', '4998', '2363', '547', NULL, NULL, '-2280', '2868859', '9099', '2350', '0', '2729', '40935', '-2263', '16147', '1'); 170 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (2, '136', '2720', '-97', '15500', '2412', '0', '-28', '531391', '630188', '100', '240', '302', '498', '2720', '19779', '1'); 171 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (2, '12', '29924', '619', '2525', '3362976', NULL, '1846', '1421553', '570', '30104', '225', '679', '1941423', '520', '49219', '1'); 172 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (2, '11', '2296', '44', '115', '1592000', NULL, '30', '428200', '2348', '878', '-2056', '1078300', '2320', '84', '1555', '1'); 173 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (3, '16', '4998', '2365', NULL, NULL, NULL, '400', '-1916', '1247617', '2032120', NULL, NULL, NULL, NULL, '18691', '1'); 174 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (3, '9', '4998', '2361', '547', NULL, NULL, '-2273', '2868897', '9099', '2347', '0', '2729', '40922', '-2257', '16147', '1'); 175 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (3, '136', '2720', '-95', '15500', '2412', '0', '-24', '531391', '630188', '100', '240', '302', '498', '2720', '19779', '1'); 176 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (3, '12', '29825', '618', '2515', '3363018', NULL, '1844', '1421584', '570', '29904', '224', '670', '1941434', '520', '49219', '1'); 177 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (3, '11', '2294', '44', '114', '1592000', NULL, '30', '428200', '2343', '881', '-2060', '1078300', '2323', '85', '1555', '1'); 178 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (4, '16', '4998', '2368', NULL, NULL, NULL, '400', '-1919', '1247649', '2032120', NULL, NULL, NULL, NULL, '18691', '1'); 179 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (4, '9', '4997', '2363', '546', NULL, NULL, '-2261', '2868935', '9099', '2350', '0', '2729', '40934', '-2246', '16147', '1'); 180 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (4, '136', '2719', '-99', '15500', '2412', '0', '-28', '531391', '630189', '100', '240', '302', '498', '2720', '19779', '1'); 181 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (4, '12', '29174', '632', '2507', '3363060', NULL, '1843', '1421614', '570', '29907', '222', '664', '1941446', '520', '49219', '1'); 182 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (4, '11', '2298', '42', '111', '1592000', NULL, '25', '428200', '2348', '877', '-2055', '1078300', '2320', '85', '1555', '1'); 183 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (5, '16', '4999', '2358', NULL, NULL, NULL, '400', '-1711', '1247681', '2032120', NULL, NULL, NULL, NULL, '18691', '1'); 184 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (5, '9', '4999', '2356', '547', NULL, NULL, '-2264', '2868973', '9099', '2343', '0', '2729', '40945', '-2248', '16147', '1'); 185 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (5, '136', '2719', '-95', '15500', '2412', '0', '-30', '531391', '630189', '100', '240', '302', '498', '2720', '19779', '1'); 186 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (5, '12', '29074', '636', '2511', '3363101', NULL, '1851', '1421645', '570', '30154', '218', '659', '1941456', '510', '49219', '1'); 187 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (5, '11', '2306', '43', '111', '1592000', NULL, '27', '428200', '2339', '812', '-1849', '1078300', '2320', '84', '1555', '1'); 188 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (6, '16', '4999', '2362', NULL, NULL, NULL, '400', '-1753', '1247710', '2032120', NULL, NULL, NULL, NULL, '18691', '1'); 189 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (6, '9', '4999', '2358', '547', NULL, NULL, '-2285', '2869010', '9099', '2345', '0', '2729', '40932', '-2269', '16147', '1'); 190 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (6, '136', '2722', '-98', '15500', '2412', '0', '-28', '531391', '630190', '100', '240', '302', '498', '2720', '19779', '1'); 191 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (6, '12', '29178', '642', '2530', '3363142', NULL, '1875', '1421675', '570', '30153', '217', '655', '1941467', '510', '49219', '1'); 192 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (6, '11', '2307', '43', '116', '1592000', NULL, '28', '428200', '2343', '823', '-1897', '1078300', '2319', '85', '1555', '1'); 193 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (7, '16', '4998', '2364', NULL, NULL, NULL, '400', '-1799', '1247739', '2032120', NULL, NULL, NULL, NULL, '18691', '1'); 194 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (7, '9', '4999', '2360', '547', NULL, NULL, '-2323', '2869048', '9099', '2346', '0', '2729', '40917', '-2307', '16147', '1'); 195 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (7, '136', '2719', '-100', '15500', '2412', '0', '-23', '531391', '630190', '100', '240', '302', '498', '2720', '19779', '1'); 196 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (7, '12', '29124', '659', '2573', '3363185', NULL, '1919', '1421707', '570', '30304', '216', '653', '1941478', '510', '49219', '1'); 197 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (7, '11', '2311', '43', '109', '1592000', NULL, '29', '428200', '2343', '841', '-1937', '1078300', '2321', '80', '1555', '1'); 198 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (8, '16', '4998', '2364', NULL, NULL, NULL, '400', '-1789', '1247769', '2032120', NULL, NULL, NULL, NULL, '18691', '1'); 199 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (8, '9', '4998', '2360', '548', NULL, NULL, '-2314', '2869087', '9099', '2347', '0', '2729', '40931', '-2299', '16147', '1'); 200 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (8, '136', '2720', '-95', '15500', '2412', '0', '-21', '531391', '630191', '100', '240', '302', '499', '2720', '19779', '1'); 201 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (8, '12', '29224', '658', '2560', '3363229', NULL, '1917', '1421740', '570', '29903', '215', '644', '1941489', '510', '49219', '1'); 202 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (8, '11', '2313', '44', '114', '1592000', NULL, '29', '428200', '2342', '829', '-1932', '1078300', '2325', '85', '1555', '1'); 203 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (9, '16', '4999', '2361', NULL, NULL, NULL, '400', '-1833', '1247799', '2032120', NULL, NULL, NULL, NULL, '18691', '1'); 204 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (9, '9', '4999', '2357', '549', NULL, NULL, '-2333', '2869126', '9099', '2344', '0', '2729', '40934', '-2318', '16147', '1'); 205 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (9, '136', '2719', '-98', '15500', '2412', '0', '-22', '531391', '630191', '100', '240', '302', '499', '2720', '19779', '1'); 206 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (9, '12', '29228', '663', '2578', '3363272', NULL, '1939', '1421772', '570', '30104', '212', '639', '1941500', '510', '49219', '1'); 207 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (9, '11', '2310', '42', '106', '1592000', NULL, '25', '428200', '2342', '854', '-1964', '1078300', '2330', '81', '1555', '1'); 208 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (10, '16', '4997', '2360', NULL, NULL, NULL, '400', '-1818', '1247829', '2032120', NULL, NULL, NULL, NULL, '18691', '1'); 209 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (10, '9', '4997', '2357', '549', NULL, NULL, '-2328', '2869165', '9099', '2343', '0', '2729', '40919', '-2312', '16147', '1'); 210 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (10, '136', '2719', '-97', '15500', '2412', '0', '-23', '531391', '630191', '100', '240', '302', '498', '2720', '19779', '1'); 211 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (10, '12', '29074', '668', '2573', '3363314', NULL, '1943', '1421804', '570', '30154', '208', '630', '1941511', '510', '49219', '1'); 212 | INSERT INTO module_statuses (p_rout_id, module_id, param_0, param_1, param_10, param_11, param_12, param_2, param_3, param_4, param_5, param_6, param_7, param_8, param_9, status, version) VALUES (10, '11', '2309', '42', '109', '1592000', NULL, '25', '428200', '2340', '851', '-1952', '1078300', '2328', '80', '1555', '1'); 213 | 214 | 215 | -- 216 | -- Name: header_p_rout_id_index; Type: INDEX; Schema: logs; Owner: p-rout; Tablespace: 217 | -- 218 | 219 | CREATE INDEX header_p_rout_id_index ON header USING btree (p_rout_id); 220 | 221 | 222 | -- 223 | -- Name: header_time_send_index; Type: INDEX; Schema: logs; Owner: p-rout; Tablespace: 224 | -- 225 | 226 | CREATE INDEX header_time_send_index ON header USING btree (time_send); 227 | 228 | 229 | -- 230 | -- Name: module_statuses_p_rout_id_index; Type: INDEX; Schema: logs; Owner: p-rout; Tablespace: 231 | -- 232 | 233 | CREATE INDEX module_statuses_p_rout_id_index ON module_statuses USING btree (p_rout_id); 234 | 235 | 236 | -- 237 | -- Name: public; Type: ACL; Schema: -; Owner: postgres 238 | -- 239 | 240 | REVOKE ALL ON SCHEMA public FROM PUBLIC; 241 | REVOKE ALL ON SCHEMA public FROM postgres; 242 | GRANT ALL ON SCHEMA public TO postgres; 243 | GRANT ALL ON SCHEMA public TO PUBLIC; 244 | 245 | 246 | -- 247 | -- PostgreSQL database dump complete 248 | -- 249 | 250 | -------------------------------------------------------------------------------- /example/dnsmasq.conf: -------------------------------------------------------------------------------- 1 | # Configuration file for dnsmasq. 2 | # 3 | # Format is one option per line, legal options are the same 4 | # as the long options legal on the command line. See 5 | # "/usr/sbin/dnsmasq --help" or "man 8 dnsmasq" for details. 6 | 7 | # Listen on this specific port instead of the standard DNS port 8 | # (53). Setting this to zero completely disables DNS function, 9 | # leaving only DHCP and/or TFTP. 10 | #port=5353 11 | 12 | # The following two options make you a better netizen, since they 13 | # tell dnsmasq to filter out queries which the public DNS cannot 14 | # answer, and which load the servers (especially the root servers) 15 | # unnecessarily. If you have a dial-on-demand link they also stop 16 | # these requests from bringing up the link unnecessarily. 17 | 18 | # Never forward plain names (without a dot or domain part) 19 | #domain-needed 20 | # Never forward addresses in the non-routed address spaces. 21 | #bogus-priv 22 | 23 | # Uncomment these to enable DNSSEC validation and caching: 24 | # (Requires dnsmasq to be built with DNSSEC option.) 25 | #conf-file=/usr/share/dnsmasq/trust-anchors.conf 26 | #dnssec 27 | 28 | # Replies which are not DNSSEC signed may be legitimate, because the domain 29 | # is unsigned, or may be forgeries. Setting this option tells dnsmasq to 30 | # check that an unsigned reply is OK, by finding a secure proof that a DS 31 | # record somewhere between the root and the domain does not exist. 32 | # The cost of setting this is that even queries in unsigned domains will need 33 | # one or more extra DNS queries to verify. 34 | #dnssec-check-unsigned 35 | 36 | # Uncomment this to filter useless windows-originated DNS requests 37 | # which can trigger dial-on-demand links needlessly. 38 | # Note that (amongst other things) this blocks all SRV requests, 39 | # so don't use it if you use eg Kerberos, SIP, XMMP or Google-talk. 40 | # This option only affects forwarding, SRV records originating for 41 | # dnsmasq (via srv-host= lines) are not suppressed by it. 42 | #filterwin2k 43 | 44 | # Change this line if you want dns to get its upstream servers from 45 | # somewhere other that /etc/resolv.conf 46 | #resolv-file= 47 | 48 | # By default, dnsmasq will send queries to any of the upstream 49 | # servers it knows about and tries to favour servers to are known 50 | # to be up. Uncommenting this forces dnsmasq to try each query 51 | # with each server strictly in the order they appear in 52 | # /etc/resolv.conf 53 | #strict-order 54 | 55 | # If you don't want dnsmasq to read /etc/resolv.conf or any other 56 | # file, getting its servers from this file instead (see below), then 57 | # uncomment this. 58 | #no-resolv 59 | 60 | # If you don't want dnsmasq to poll /etc/resolv.conf or other resolv 61 | # files for changes and re-read them then uncomment this. 62 | #no-poll 63 | 64 | # Add other name servers here, with domain specs if they are for 65 | # non-public domains. 66 | #server=/localnet/192.168.0.1 67 | 68 | # Example of routing PTR queries to nameservers: this will send all 69 | # address->name queries for 192.168.3/24 to nameserver 10.1.2.3 70 | #server=/3.168.192.in-addr.arpa/10.1.2.3 71 | 72 | # Add local-only domains here, queries in these domains are answered 73 | # from /etc/hosts or DHCP only. 74 | #local=/localnet/ 75 | 76 | # Add domains which you want to force to an IP address here. 77 | # The example below send any host in double-click.net to a local 78 | # web-server. 79 | #address=/double-click.net/127.0.0.1 80 | 81 | # --address (and --server) work with IPv6 addresses too. 82 | #address=/www.thekelleys.org.uk/fe80::20d:60ff:fe36:f83 83 | 84 | # Add the IPs of all queries to yahoo.com, google.com, and their 85 | # subdomains to the vpn and search ipsets: 86 | #ipset=/yahoo.com/google.com/vpn,search 87 | 88 | # You can control how dnsmasq talks to a server: this forces 89 | # queries to 10.1.2.3 to be routed via eth1 90 | # server=10.1.2.3@eth1 91 | 92 | # and this sets the source (ie local) address used to talk to 93 | # 10.1.2.3 to 192.168.1.1 port 55 (there must be a interface with that 94 | # IP on the machine, obviously). 95 | # server=10.1.2.3@192.168.1.1#55 96 | 97 | # If you want dnsmasq to change uid and gid to something other 98 | # than the default, edit the following lines. 99 | #user= 100 | #group= 101 | 102 | # If you want dnsmasq to listen for DHCP and DNS requests only on 103 | # specified interfaces (and the loopback) give the name of the 104 | # interface (eg eth0) here. 105 | # Repeat the line for more than one interface. 106 | interface=ens4v1 107 | # Or you can specify which interface _not_ to listen on 108 | #except-interface= 109 | # Or which to listen on by address (remember to include 127.0.0.1 if 110 | # you use this.) 111 | #listen-address= 112 | # If you want dnsmasq to provide only DNS service on an interface, 113 | # configure it as shown above, and then use the following line to 114 | # disable DHCP and TFTP on it. 115 | #no-dhcp-interface= 116 | 117 | # On systems which support it, dnsmasq binds the wildcard address, 118 | # even when it is listening on only some interfaces. It then discards 119 | # requests that it shouldn't reply to. This has the advantage of 120 | # working even when interfaces come and go and change address. If you 121 | # want dnsmasq to really bind only the interfaces it is listening on, 122 | # uncomment this option. About the only time you may need this is when 123 | # running another nameserver on the same machine. 124 | #bind-interfaces 125 | 126 | # If you don't want dnsmasq to read /etc/hosts, uncomment the 127 | # following line. 128 | #no-hosts 129 | # or if you want it to read another file, as well as /etc/hosts, use 130 | # this. 131 | #addn-hosts=/etc/banner_add_hosts 132 | 133 | # Set this (and domain: see below) if you want to have a domain 134 | # automatically added to simple names in a hosts-file. 135 | #expand-hosts 136 | 137 | # Set the domain for dnsmasq. this is optional, but if it is set, it 138 | # does the following things. 139 | # 1) Allows DHCP hosts to have fully qualified domain names, as long 140 | # as the domain part matches this setting. 141 | # 2) Sets the "domain" DHCP option thereby potentially setting the 142 | # domain of all systems configured by DHCP 143 | # 3) Provides the domain part for "expand-hosts" 144 | #domain=thekelleys.org.uk 145 | 146 | # Set a different domain for a particular subnet 147 | #domain=wireless.thekelleys.org.uk,192.168.2.0/24 148 | 149 | # Same idea, but range rather then subnet 150 | #domain=reserved.thekelleys.org.uk,192.68.3.100,192.168.3.200 151 | 152 | # Uncomment this to enable the integrated DHCP server, you need 153 | # to supply the range of addresses available for lease and optionally 154 | # a lease time. If you have more than one network, you will need to 155 | # repeat this for each network on which you want to supply DHCP 156 | # service. 157 | #dhcp-range=192.168.0.50,192.168.0.150,12h 158 | dhcp-range=10.11.0.1,10.11.0.255,12h 159 | 160 | # This is an example of a DHCP range where the netmask is given. This 161 | # is needed for networks we reach the dnsmasq DHCP server via a relay 162 | # agent. If you don't know what a DHCP relay agent is, you probably 163 | # don't need to worry about this. 164 | #dhcp-range=192.168.0.50,192.168.0.150,255.255.255.0,12h 165 | 166 | # This is an example of a DHCP range which sets a tag, so that 167 | # some DHCP options may be set only for this network. 168 | #dhcp-range=set:red,192.168.0.50,192.168.0.150 169 | 170 | # Use this DHCP range only when the tag "green" is set. 171 | #dhcp-range=tag:green,192.168.0.50,192.168.0.150,12h 172 | 173 | # Specify a subnet which can't be used for dynamic address allocation, 174 | # is available for hosts with matching --dhcp-host lines. Note that 175 | # dhcp-host declarations will be ignored unless there is a dhcp-range 176 | # of some type for the subnet in question. 177 | # In this case the netmask is implied (it comes from the network 178 | # configuration on the machine running dnsmasq) it is possible to give 179 | # an explicit netmask instead. 180 | #dhcp-range=192.168.0.0,static 181 | 182 | # Enable DHCPv6. Note that the prefix-length does not need to be specified 183 | # and defaults to 64 if missing/ 184 | #dhcp-range=1234::2, 1234::500, 64, 12h 185 | 186 | # Do Router Advertisements, BUT NOT DHCP for this subnet. 187 | #dhcp-range=1234::, ra-only 188 | 189 | # Do Router Advertisements, BUT NOT DHCP for this subnet, also try and 190 | # add names to the DNS for the IPv6 address of SLAAC-configured dual-stack 191 | # hosts. Use the DHCPv4 lease to derive the name, network segment and 192 | # MAC address and assume that the host will also have an 193 | # IPv6 address calculated using the SLAAC alogrithm. 194 | #dhcp-range=1234::, ra-names 195 | 196 | # Do Router Advertisements, BUT NOT DHCP for this subnet. 197 | # Set the lifetime to 46 hours. (Note: minimum lifetime is 2 hours.) 198 | #dhcp-range=1234::, ra-only, 48h 199 | 200 | # Do DHCP and Router Advertisements for this subnet. Set the A bit in the RA 201 | # so that clients can use SLAAC addresses as well as DHCP ones. 202 | #dhcp-range=1234::2, 1234::500, slaac 203 | 204 | # Do Router Advertisements and stateless DHCP for this subnet. Clients will 205 | # not get addresses from DHCP, but they will get other configuration information. 206 | # They will use SLAAC for addresses. 207 | #dhcp-range=1234::, ra-stateless 208 | 209 | # Do stateless DHCP, SLAAC, and generate DNS names for SLAAC addresses 210 | # from DHCPv4 leases. 211 | #dhcp-range=1234::, ra-stateless, ra-names 212 | 213 | # Do router advertisements for all subnets where we're doing DHCPv6 214 | # Unless overriden by ra-stateless, ra-names, et al, the router 215 | # advertisements will have the M and O bits set, so that the clients 216 | # get addresses and configuration from DHCPv6, and the A bit reset, so the 217 | # clients don't use SLAAC addresses. 218 | #enable-ra 219 | 220 | # Supply parameters for specified hosts using DHCP. There are lots 221 | # of valid alternatives, so we will give examples of each. Note that 222 | # IP addresses DO NOT have to be in the range given above, they just 223 | # need to be on the same network. The order of the parameters in these 224 | # do not matter, it's permissible to give name, address and MAC in any 225 | # order. 226 | 227 | # Always allocate the host with Ethernet address 11:22:33:44:55:66 228 | # The IP address 192.168.0.60 229 | #dhcp-host=11:22:33:44:55:66,192.168.0.60 230 | 231 | # Always set the name of the host with hardware address 232 | # 11:22:33:44:55:66 to be "fred" 233 | #dhcp-host=11:22:33:44:55:66,fred 234 | 235 | # Always give the host with Ethernet address 11:22:33:44:55:66 236 | # the name fred and IP address 192.168.0.60 and lease time 45 minutes 237 | #dhcp-host=11:22:33:44:55:66,fred,192.168.0.60,45m 238 | 239 | # Give a host with Ethernet address 11:22:33:44:55:66 or 240 | # 12:34:56:78:90:12 the IP address 192.168.0.60. Dnsmasq will assume 241 | # that these two Ethernet interfaces will never be in use at the same 242 | # time, and give the IP address to the second, even if it is already 243 | # in use by the first. Useful for laptops with wired and wireless 244 | # addresses. 245 | #dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.60 246 | 247 | # Give the machine which says its name is "bert" IP address 248 | # 192.168.0.70 and an infinite lease 249 | #dhcp-host=bert,192.168.0.70,infinite 250 | 251 | # Always give the host with client identifier 01:02:02:04 252 | # the IP address 192.168.0.60 253 | #dhcp-host=id:01:02:02:04,192.168.0.60 254 | 255 | # Always give the host with client identifier "marjorie" 256 | # the IP address 192.168.0.60 257 | #dhcp-host=id:marjorie,192.168.0.60 258 | 259 | # Enable the address given for "judge" in /etc/hosts 260 | # to be given to a machine presenting the name "judge" when 261 | # it asks for a DHCP lease. 262 | #dhcp-host=judge 263 | 264 | # Never offer DHCP service to a machine whose Ethernet 265 | # address is 11:22:33:44:55:66 266 | #dhcp-host=11:22:33:44:55:66,ignore 267 | 268 | # Ignore any client-id presented by the machine with Ethernet 269 | # address 11:22:33:44:55:66. This is useful to prevent a machine 270 | # being treated differently when running under different OS's or 271 | # between PXE boot and OS boot. 272 | #dhcp-host=11:22:33:44:55:66,id:* 273 | 274 | # Send extra options which are tagged as "red" to 275 | # the machine with Ethernet address 11:22:33:44:55:66 276 | #dhcp-host=11:22:33:44:55:66,set:red 277 | 278 | # Send extra options which are tagged as "red" to 279 | # any machine with Ethernet address starting 11:22:33: 280 | #dhcp-host=11:22:33:*:*:*,set:red 281 | 282 | # Give a fixed IPv6 address and name to client with 283 | # DUID 00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2 284 | # Note the MAC addresses CANNOT be used to identify DHCPv6 clients. 285 | # Note also the they [] around the IPv6 address are obilgatory. 286 | #dhcp-host=id:00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2, fred, [1234::5] 287 | 288 | # Ignore any clients which are not specified in dhcp-host lines 289 | # or /etc/ethers. Equivalent to ISC "deny unknown-clients". 290 | # This relies on the special "known" tag which is set when 291 | # a host is matched. 292 | #dhcp-ignore=tag:!known 293 | 294 | # Send extra options which are tagged as "red" to any machine whose 295 | # DHCP vendorclass string includes the substring "Linux" 296 | #dhcp-vendorclass=set:red,Linux 297 | 298 | # Send extra options which are tagged as "red" to any machine one 299 | # of whose DHCP userclass strings includes the substring "accounts" 300 | #dhcp-userclass=set:red,accounts 301 | 302 | # Send extra options which are tagged as "red" to any machine whose 303 | # MAC address matches the pattern. 304 | #dhcp-mac=set:red,00:60:8C:*:*:* 305 | 306 | # If this line is uncommented, dnsmasq will read /etc/ethers and act 307 | # on the ethernet-address/IP pairs found there just as if they had 308 | # been given as --dhcp-host options. Useful if you keep 309 | # MAC-address/host mappings there for other purposes. 310 | #read-ethers 311 | 312 | # Send options to hosts which ask for a DHCP lease. 313 | # See RFC 2132 for details of available options. 314 | # Common options can be given to dnsmasq by name: 315 | # run "dnsmasq --help dhcp" to get a list. 316 | # Note that all the common settings, such as netmask and 317 | # broadcast address, DNS server and default route, are given 318 | # sane defaults by dnsmasq. You very likely will not need 319 | # any dhcp-options. If you use Windows clients and Samba, there 320 | # are some options which are recommended, they are detailed at the 321 | # end of this section. 322 | 323 | # Override the default route supplied by dnsmasq, which assumes the 324 | # router is the same machine as the one running dnsmasq. 325 | #dhcp-option=3,1.2.3.4 326 | 327 | # Do the same thing, but using the option name 328 | #dhcp-option=option:router,1.2.3.4 329 | 330 | # Override the default route supplied by dnsmasq and send no default 331 | # route at all. Note that this only works for the options sent by 332 | # default (1, 3, 6, 12, 28) the same line will send a zero-length option 333 | # for all other option numbers. 334 | #dhcp-option=3 335 | 336 | # Set the NTP time server addresses to 192.168.0.4 and 10.10.0.5 337 | #dhcp-option=option:ntp-server,192.168.0.4,10.10.0.5 338 | 339 | # Send DHCPv6 option. Note [] around IPv6 addresses. 340 | #dhcp-option=option6:dns-server,[1234::77],[1234::88] 341 | 342 | # Send DHCPv6 option for namservers as the machine running 343 | # dnsmasq and another. 344 | #dhcp-option=option6:dns-server,[::],[1234::88] 345 | 346 | # Ask client to poll for option changes every six hours. (RFC4242) 347 | #dhcp-option=option6:information-refresh-time,6h 348 | 349 | # Set the NTP time server address to be the same machine as 350 | # is running dnsmasq 351 | #dhcp-option=42,0.0.0.0 352 | 353 | # Set the NIS domain name to "welly" 354 | #dhcp-option=40,welly 355 | 356 | # Set the default time-to-live to 50 357 | #dhcp-option=23,50 358 | 359 | # Set the "all subnets are local" flag 360 | #dhcp-option=27,1 361 | 362 | # Send the etherboot magic flag and then etherboot options (a string). 363 | #dhcp-option=128,e4:45:74:68:00:00 364 | #dhcp-option=129,NIC=eepro100 365 | 366 | # Specify an option which will only be sent to the "red" network 367 | # (see dhcp-range for the declaration of the "red" network) 368 | # Note that the tag: part must precede the option: part. 369 | #dhcp-option = tag:red, option:ntp-server, 192.168.1.1 370 | 371 | # The following DHCP options set up dnsmasq in the same way as is specified 372 | # for the ISC dhcpcd in 373 | # http://www.samba.org/samba/ftp/docs/textdocs/DHCP-Server-Configuration.txt 374 | # adapted for a typical dnsmasq installation where the host running 375 | # dnsmasq is also the host running samba. 376 | # you may want to uncomment some or all of them if you use 377 | # Windows clients and Samba. 378 | #dhcp-option=19,0 # option ip-forwarding off 379 | #dhcp-option=44,0.0.0.0 # set netbios-over-TCP/IP nameserver(s) aka WINS server(s) 380 | #dhcp-option=45,0.0.0.0 # netbios datagram distribution server 381 | #dhcp-option=46,8 # netbios node type 382 | 383 | # Send an empty WPAD option. This may be REQUIRED to get windows 7 to behave. 384 | #dhcp-option=252,"\n" 385 | 386 | # Send RFC-3397 DNS domain search DHCP option. WARNING: Your DHCP client 387 | # probably doesn't support this...... 388 | #dhcp-option=option:domain-search,eng.apple.com,marketing.apple.com 389 | 390 | # Send RFC-3442 classless static routes (note the netmask encoding) 391 | #dhcp-option=121,192.168.1.0/24,1.2.3.4,10.0.0.0/8,5.6.7.8 392 | 393 | # Send vendor-class specific options encapsulated in DHCP option 43. 394 | # The meaning of the options is defined by the vendor-class so 395 | # options are sent only when the client supplied vendor class 396 | # matches the class given here. (A substring match is OK, so "MSFT" 397 | # matches "MSFT" and "MSFT 5.0"). This example sets the 398 | # mtftp address to 0.0.0.0 for PXEClients. 399 | #dhcp-option=vendor:PXEClient,1,0.0.0.0 400 | 401 | # Send microsoft-specific option to tell windows to release the DHCP lease 402 | # when it shuts down. Note the "i" flag, to tell dnsmasq to send the 403 | # value as a four-byte integer - that's what microsoft wants. See 404 | # http://technet2.microsoft.com/WindowsServer/en/library/a70f1bb7-d2d4-49f0-96d6-4b7414ecfaae1033.mspx?mfr=true 405 | #dhcp-option=vendor:MSFT,2,1i 406 | 407 | # Send the Encapsulated-vendor-class ID needed by some configurations of 408 | # Etherboot to allow is to recognise the DHCP server. 409 | #dhcp-option=vendor:Etherboot,60,"Etherboot" 410 | 411 | # Send options to PXELinux. Note that we need to send the options even 412 | # though they don't appear in the parameter request list, so we need 413 | # to use dhcp-option-force here. 414 | # See http://syslinux.zytor.com/pxe.php#special for details. 415 | # Magic number - needed before anything else is recognised 416 | #dhcp-option-force=208,f1:00:74:7e 417 | # Configuration file name 418 | #dhcp-option-force=209,configs/common 419 | # Path prefix 420 | #dhcp-option-force=210,/tftpboot/pxelinux/files/ 421 | # Reboot time. (Note 'i' to send 32-bit value) 422 | #dhcp-option-force=211,30i 423 | 424 | # Set the boot filename for netboot/PXE. You will only need 425 | # this is you want to boot machines over the network and you will need 426 | # a TFTP server; either dnsmasq's built in TFTP server or an 427 | # external one. (See below for how to enable the TFTP server.) 428 | #dhcp-boot=pxelinux.0 429 | 430 | # The same as above, but use custom tftp-server instead machine running dnsmasq 431 | #dhcp-boot=pxelinux,server.name,192.168.1.100 432 | 433 | # Boot for Etherboot gPXE. The idea is to send two different 434 | # filenames, the first loads gPXE, and the second tells gPXE what to 435 | # load. The dhcp-match sets the gpxe tag for requests from gPXE. 436 | #dhcp-match=set:gpxe,175 # gPXE sends a 175 option. 437 | #dhcp-boot=tag:!gpxe,undionly.kpxe 438 | #dhcp-boot=mybootimage 439 | 440 | # Encapsulated options for Etherboot gPXE. All the options are 441 | # encapsulated within option 175 442 | #dhcp-option=encap:175, 1, 5b # priority code 443 | #dhcp-option=encap:175, 176, 1b # no-proxydhcp 444 | #dhcp-option=encap:175, 177, string # bus-id 445 | #dhcp-option=encap:175, 189, 1b # BIOS drive code 446 | #dhcp-option=encap:175, 190, user # iSCSI username 447 | #dhcp-option=encap:175, 191, pass # iSCSI password 448 | 449 | # Test for the architecture of a netboot client. PXE clients are 450 | # supposed to send their architecture as option 93. (See RFC 4578) 451 | #dhcp-match=peecees, option:client-arch, 0 #x86-32 452 | #dhcp-match=itanics, option:client-arch, 2 #IA64 453 | #dhcp-match=hammers, option:client-arch, 6 #x86-64 454 | #dhcp-match=mactels, option:client-arch, 7 #EFI x86-64 455 | 456 | # Do real PXE, rather than just booting a single file, this is an 457 | # alternative to dhcp-boot. 458 | #pxe-prompt="What system shall I netboot?" 459 | # or with timeout before first available action is taken: 460 | #pxe-prompt="Press F8 for menu.", 60 461 | 462 | # Available boot services. for PXE. 463 | #pxe-service=x86PC, "Boot from local disk" 464 | 465 | # Loads /pxelinux.0 from dnsmasq TFTP server. 466 | #pxe-service=x86PC, "Install Linux", pxelinux 467 | 468 | # Loads /pxelinux.0 from TFTP server at 1.2.3.4. 469 | # Beware this fails on old PXE ROMS. 470 | #pxe-service=x86PC, "Install Linux", pxelinux, 1.2.3.4 471 | 472 | # Use bootserver on network, found my multicast or broadcast. 473 | #pxe-service=x86PC, "Install windows from RIS server", 1 474 | 475 | # Use bootserver at a known IP address. 476 | #pxe-service=x86PC, "Install windows from RIS server", 1, 1.2.3.4 477 | 478 | # If you have multicast-FTP available, 479 | # information for that can be passed in a similar way using options 1 480 | # to 5. See page 19 of 481 | # http://download.intel.com/design/archives/wfm/downloads/pxespec.pdf 482 | 483 | 484 | # Enable dnsmasq's built-in TFTP server 485 | #enable-tftp 486 | 487 | # Set the root directory for files available via FTP. 488 | #tftp-root=/var/ftpd 489 | 490 | # Make the TFTP server more secure: with this set, only files owned by 491 | # the user dnsmasq is running as will be send over the net. 492 | #tftp-secure 493 | 494 | # This option stops dnsmasq from negotiating a larger blocksize for TFTP 495 | # transfers. It will slow things down, but may rescue some broken TFTP 496 | # clients. 497 | #tftp-no-blocksize 498 | 499 | # Set the boot file name only when the "red" tag is set. 500 | #dhcp-boot=tag:red,pxelinux.red-net 501 | 502 | # An example of dhcp-boot with an external TFTP server: the name and IP 503 | # address of the server are given after the filename. 504 | # Can fail with old PXE ROMS. Overridden by --pxe-service. 505 | #dhcp-boot=/var/ftpd/pxelinux.0,boothost,192.168.0.3 506 | 507 | # If there are multiple external tftp servers having a same name 508 | # (using /etc/hosts) then that name can be specified as the 509 | # tftp_servername (the third option to dhcp-boot) and in that 510 | # case dnsmasq resolves this name and returns the resultant IP 511 | # addresses in round robin fasion. This facility can be used to 512 | # load balance the tftp load among a set of servers. 513 | #dhcp-boot=/var/ftpd/pxelinux.0,boothost,tftp_server_name 514 | 515 | # Set the limit on DHCP leases, the default is 150 516 | #dhcp-lease-max=150 517 | 518 | # The DHCP server needs somewhere on disk to keep its lease database. 519 | # This defaults to a sane location, but if you want to change it, use 520 | # the line below. 521 | #dhcp-leasefile=/var/lib/misc/dnsmasq.leases 522 | 523 | # Set the DHCP server to authoritative mode. In this mode it will barge in 524 | # and take over the lease for any client which broadcasts on the network, 525 | # whether it has a record of the lease or not. This avoids long timeouts 526 | # when a machine wakes up on a new network. DO NOT enable this if there's 527 | # the slightest chance that you might end up accidentally configuring a DHCP 528 | # server for your campus/company accidentally. The ISC server uses 529 | # the same option, and this URL provides more information: 530 | # http://www.isc.org/files/auth.html 531 | #dhcp-authoritative 532 | 533 | # Run an executable when a DHCP lease is created or destroyed. 534 | # The arguments sent to the script are "add" or "del", 535 | # then the MAC address, the IP address and finally the hostname 536 | # if there is one. 537 | #dhcp-script=/bin/echo 538 | 539 | # Set the cachesize here. 540 | #cache-size=150 541 | 542 | # If you want to disable negative caching, uncomment this. 543 | #no-negcache 544 | 545 | # Normally responses which come from /etc/hosts and the DHCP lease 546 | # file have Time-To-Live set as zero, which conventionally means 547 | # do not cache further. If you are happy to trade lower load on the 548 | # server for potentially stale date, you can set a time-to-live (in 549 | # seconds) here. 550 | #local-ttl= 551 | 552 | # If you want dnsmasq to detect attempts by Verisign to send queries 553 | # to unregistered .com and .net hosts to its sitefinder service and 554 | # have dnsmasq instead return the correct NXDOMAIN response, uncomment 555 | # this line. You can add similar lines to do the same for other 556 | # registries which have implemented wildcard A records. 557 | #bogus-nxdomain=64.94.110.11 558 | 559 | # If you want to fix up DNS results from upstream servers, use the 560 | # alias option. This only works for IPv4. 561 | # This alias makes a result of 1.2.3.4 appear as 5.6.7.8 562 | #alias=1.2.3.4,5.6.7.8 563 | # and this maps 1.2.3.x to 5.6.7.x 564 | #alias=1.2.3.0,5.6.7.0,255.255.255.0 565 | # and this maps 192.168.0.10->192.168.0.40 to 10.0.0.10->10.0.0.40 566 | #alias=192.168.0.10-192.168.0.40,10.0.0.0,255.255.255.0 567 | 568 | # Change these lines if you want dnsmasq to serve MX records. 569 | 570 | # Return an MX record named "maildomain.com" with target 571 | # servermachine.com and preference 50 572 | #mx-host=maildomain.com,servermachine.com,50 573 | 574 | # Set the default target for MX records created using the localmx option. 575 | #mx-target=servermachine.com 576 | 577 | # Return an MX record pointing to the mx-target for all local 578 | # machines. 579 | #localmx 580 | 581 | # Return an MX record pointing to itself for all local machines. 582 | #selfmx 583 | 584 | # Change the following lines if you want dnsmasq to serve SRV 585 | # records. These are useful if you want to serve ldap requests for 586 | # Active Directory and other windows-originated DNS requests. 587 | # See RFC 2782. 588 | # You may add multiple srv-host lines. 589 | # The fields are ,,,, 590 | # If the domain part if missing from the name (so that is just has the 591 | # service and protocol sections) then the domain given by the domain= 592 | # config option is used. (Note that expand-hosts does not need to be 593 | # set for this to work.) 594 | 595 | # A SRV record sending LDAP for the example.com domain to 596 | # ldapserver.example.com port 389 597 | #srv-host=_ldap._tcp.example.com,ldapserver.example.com,389 598 | 599 | # A SRV record sending LDAP for the example.com domain to 600 | # ldapserver.example.com port 389 (using domain=) 601 | #domain=example.com 602 | #srv-host=_ldap._tcp,ldapserver.example.com,389 603 | 604 | # Two SRV records for LDAP, each with different priorities 605 | #srv-host=_ldap._tcp.example.com,ldapserver.example.com,389,1 606 | #srv-host=_ldap._tcp.example.com,ldapserver.example.com,389,2 607 | 608 | # A SRV record indicating that there is no LDAP server for the domain 609 | # example.com 610 | #srv-host=_ldap._tcp.example.com 611 | 612 | # The following line shows how to make dnsmasq serve an arbitrary PTR 613 | # record. This is useful for DNS-SD. (Note that the 614 | # domain-name expansion done for SRV records _does_not 615 | # occur for PTR records.) 616 | #ptr-record=_http._tcp.dns-sd-services,"New Employee Page._http._tcp.dns-sd-services" 617 | 618 | # Change the following lines to enable dnsmasq to serve TXT records. 619 | # These are used for things like SPF and zeroconf. (Note that the 620 | # domain-name expansion done for SRV records _does_not 621 | # occur for TXT records.) 622 | 623 | #Example SPF. 624 | #txt-record=example.com,"v=spf1 a -all" 625 | 626 | #Example zeroconf 627 | #txt-record=_http._tcp.example.com,name=value,paper=A4 628 | 629 | # Provide an alias for a "local" DNS name. Note that this _only_ works 630 | # for targets which are names from DHCP or /etc/hosts. Give host 631 | # "bert" another name, bertrand 632 | #cname=bertand,bert 633 | 634 | # For debugging purposes, log each DNS query as it passes through 635 | # dnsmasq. 636 | #log-queries 637 | 638 | # Log lots of extra information about DHCP transactions. 639 | #log-dhcp 640 | 641 | # Include another lot of configuration options. 642 | #conf-file=/etc/dnsmasq.more.conf 643 | #conf-dir=/etc/dnsmasq.d 644 | -------------------------------------------------------------------------------- /datetimepicker_css.js: -------------------------------------------------------------------------------- 1 | //Javasript name: My Date Time Picker 2 | //Date created: 16-Nov-2003 23:19 3 | //Creator: TengYong Ng 4 | //Website: http://www.rainforestnet.com 5 | //Copyright (c) 2003 TengYong Ng 6 | //FileName: DateTimePicker_css.js 7 | //Version: 2.2.4 8 | // Note: Permission given to use and modify this script in ANY kind of applications if 9 | // header lines are left unchanged. 10 | //Permission is granted to redistribute and modify this javascript under a FreeBSD License. 11 | //New Css style version added by Yvan Lavoie (Québec, Canada) 29-Jan-2009 12 | //Formatted for JSLint compatibility by Labsmedia.com (30-Dec-2010) 13 | 14 | 15 | //Global variables 16 | 17 | var winCal; 18 | var dtToday; 19 | var Cal; 20 | var MonthName; 21 | var WeekDayName1; 22 | var WeekDayName2; 23 | var exDateTime;//Existing Date and Time 24 | var selDate;//selected date. version 1.7 25 | var calSpanID = "calBorder"; // span ID 26 | var domStyle = null; // span DOM object with style 27 | var cnLeft = "0";//left coordinate of calendar span 28 | var cnTop = "0";//top coordinate of calendar span 29 | var xpos = 0; // mouse x position 30 | var ypos = 0; // mouse y position 31 | var calHeight = 0; // calendar height 32 | var CalWidth = 208;// calendar width 33 | var CellWidth = 30;// width of day cell. 34 | var TimeMode = 24;// TimeMode value. 12 or 24 35 | var StartYear = 1940; //First Year in drop down year selection 36 | var EndYear = 5; // The last year of pickable date. if current year is 2011, the last year that still picker will be 2016 (2011+5) 37 | var CalPosOffsetX = -1; //X position offset relative to calendar icon, can be negative value 38 | var CalPosOffsetY = 0; //Y position offset relative to calendar icon, can be negative value 39 | 40 | //Configurable parameters start 41 | var SpanBorderColor = "#000000";//span border color 42 | var SpanBgColor = "#FFFFFF"; //span background color 43 | var MonthYearColor = "#cc0033"; //Font Color of Month and Year in Calendar header. 44 | var WeekHeadColor = "#18861B"; //var WeekHeadColor="#18861B";//Background Color in Week header. 45 | var SundayColor = "#C0F64F"; //var SundayColor="#C0F64F";//Background color of Sunday. 46 | var SaturdayColor = "#C0F64F"; //Background color of Saturday. 47 | var WeekDayColor = "#FFEDA6"; //Background color of weekdays. 48 | var FontColor = "blue"; //color of font in Calendar day cell. 49 | var TodayColor = "#ffbd35"; //var TodayColor="#FFFF33";//Background color of today. 50 | var SelDateColor = "#8DD53C"; //var SelDateColor = "#8DD53C";//Backgrond color of selected date in textbox. 51 | var YrSelColor = "#cc0033"; //color of font of Year selector. 52 | var MthSelColor = "#cc0033"; //color of font of Month selector if "MonthSelector" is "arrow". 53 | var HoverColor = "#E0FF38"; //color when mouse move over. 54 | var DisableColor = "#999966"; //color of disabled cell. 55 | var CalBgColor = "#ffffff"; //Background color of Calendar window. 56 | 57 | var WeekChar = 2;//number of character for week day. if 2 then Mo,Tu,We. if 3 then Mon,Tue,Wed. 58 | var DateSeparator = "-";//Date Separator, you can change it to "-" if you want. 59 | var ShowLongMonth = true;//Show long month name in Calendar header. example: "January". 60 | var ShowMonthYear = true;//Show Month and Year in Calendar header. 61 | var ThemeBg = "";//Background image of Calendar window. 62 | var PrecedeZero = true;//Preceding zero [true|false] 63 | var MondayFirstDay = true;//true:Use Monday as first day; false:Sunday as first day. [true|false] //added in version 1.7 64 | var UseImageFiles = false;//Use image files with "arrows" and "close" button 65 | var imageFilesPath = "images2/"; 66 | //Configurable parameters end 67 | 68 | //use the Month and Weekday in your preferred language. 69 | var MonthName = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; 70 | var WeekDayName1 = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; 71 | var WeekDayName2 = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; 72 | 73 | 74 | //end Configurable parameters 75 | 76 | //end Global variable 77 | 78 | 79 | // Calendar prototype 80 | function Calendar(pDate, pCtrl) 81 | { 82 | //Properties 83 | this.Date = pDate.getDate();//selected date 84 | this.Month = pDate.getMonth();//selected month number 85 | this.Year = pDate.getFullYear();//selected year in 4 digits 86 | this.Hours = pDate.getHours(); 87 | 88 | if (pDate.getMinutes() < 10) 89 | { 90 | this.Minutes = "0" + pDate.getMinutes(); 91 | } 92 | else 93 | { 94 | this.Minutes = pDate.getMinutes(); 95 | } 96 | 97 | if (pDate.getSeconds() < 10) 98 | { 99 | this.Seconds = "0" + pDate.getSeconds(); 100 | } 101 | else 102 | { 103 | this.Seconds = pDate.getSeconds(); 104 | } 105 | this.MyWindow = winCal; 106 | this.Ctrl = pCtrl; 107 | this.Format = "ddMMyyyy"; 108 | this.Separator = DateSeparator; 109 | this.ShowTime = false; 110 | this.Scroller = "DROPDOWN"; 111 | if (pDate.getHours() < 12) 112 | { 113 | this.AMorPM = "AM"; 114 | } 115 | else 116 | { 117 | this.AMorPM = "PM"; 118 | } 119 | this.ShowSeconds = false; 120 | this.EnableDateMode = "" 121 | } 122 | 123 | Calendar.prototype.GetMonthIndex = function (shortMonthName) 124 | { 125 | for (var i = 0; i < 12; i += 1) 126 | { 127 | if (MonthName[i].substring(0, 3).toUpperCase() === shortMonthName.toUpperCase()) 128 | { 129 | return i; 130 | } 131 | } 132 | }; 133 | 134 | Calendar.prototype.IncYear = function () { 135 | if (Cal.Year <= dtToday.getFullYear()+EndYear) 136 | Cal.Year += 1; 137 | }; 138 | 139 | Calendar.prototype.DecYear = function () { 140 | if (Cal.Year > StartYear) 141 | Cal.Year -= 1; 142 | }; 143 | 144 | Calendar.prototype.IncMonth = function() { 145 | if (Cal.Year <= dtToday.getFullYear() + EndYear) { 146 | Cal.Month += 1; 147 | if (Cal.Month >= 12) { 148 | Cal.Month = 0; 149 | Cal.IncYear(); 150 | } 151 | } 152 | }; 153 | 154 | Calendar.prototype.DecMonth = function() { 155 | if (Cal.Year >= StartYear) { 156 | Cal.Month -= 1; 157 | if (Cal.Month < 0) { 158 | Cal.Month = 11; 159 | Cal.DecYear(); 160 | } 161 | } 162 | }; 163 | 164 | Calendar.prototype.SwitchMth = function (intMth) 165 | { 166 | Cal.Month = parseInt(intMth, 10); 167 | }; 168 | 169 | Calendar.prototype.SwitchYear = function (intYear) 170 | { 171 | Cal.Year = parseInt(intYear, 10); 172 | }; 173 | 174 | Calendar.prototype.SetHour = function(intHour) { 175 | var MaxHour, 176 | MinHour, 177 | HourExp = new RegExp("^\\d\\d"), 178 | SingleDigit = new RegExp("^\\d{1}$"); 179 | 180 | if (TimeMode === 24) { 181 | MaxHour = 23; 182 | MinHour = 0; 183 | } 184 | else if (TimeMode === 12) { 185 | MaxHour = 12; 186 | MinHour = 1; 187 | } 188 | else { 189 | alert("TimeMode can only be 12 or 24"); 190 | } 191 | 192 | if ((HourExp.test(intHour) || SingleDigit.test(intHour)) && (parseInt(intHour, 10) > MaxHour)) { 193 | intHour = MinHour; 194 | } 195 | 196 | else if ((HourExp.test(intHour) || SingleDigit.test(intHour)) && (parseInt(intHour, 10) < MinHour)) { 197 | intHour = MaxHour; 198 | } 199 | 200 | intHour = parseInt(intHour, 10); 201 | if (SingleDigit.test(intHour)) { 202 | intHour = "0" + intHour; 203 | } 204 | 205 | if (HourExp.test(intHour) && (parseInt(intHour, 10) <= MaxHour) && (parseInt(intHour, 10) >= MinHour)) { 206 | if ((TimeMode === 12) && (Cal.AMorPM === "PM")) { 207 | if (parseInt(intHour, 10) === 12) { 208 | Cal.Hours = 12; 209 | } 210 | else { 211 | Cal.Hours = parseInt(intHour, 10) + 12; 212 | } 213 | } 214 | 215 | else if ((TimeMode === 12) && (Cal.AMorPM === "AM")) { 216 | if (intHour === 12) { 217 | intHour -= 12; 218 | } 219 | 220 | Cal.Hours = parseInt(intHour, 10); 221 | } 222 | 223 | else if (TimeMode === 24) { 224 | Cal.Hours = parseInt(intHour, 10); 225 | } 226 | } 227 | 228 | }; 229 | 230 | Calendar.prototype.SetMinute = function (intMin) 231 | { 232 | var MaxMin = 59, 233 | MinMin = 0, 234 | 235 | SingleDigit = new RegExp("\\d"), 236 | SingleDigit2 = new RegExp("^\\d{1}$"), 237 | MinExp = new RegExp("^\\d{2}$"), 238 | 239 | strMin = 0; 240 | 241 | if ((MinExp.test(intMin) || SingleDigit.test(intMin)) && (parseInt(intMin, 10) > MaxMin)) 242 | { 243 | intMin = MinMin; 244 | } 245 | 246 | else if ((MinExp.test(intMin) || SingleDigit.test(intMin)) && (parseInt(intMin, 10) < MinMin)) 247 | { 248 | intMin = MaxMin; 249 | } 250 | 251 | strMin = intMin + ""; 252 | if (SingleDigit2.test(intMin)) 253 | { 254 | strMin = "0" + strMin; 255 | } 256 | 257 | if ((MinExp.test(intMin) || SingleDigit.test(intMin)) && (parseInt(intMin, 10) <= 59) && (parseInt(intMin, 10) >= 0)) 258 | { 259 | Cal.Minutes = strMin; 260 | } 261 | }; 262 | 263 | Calendar.prototype.SetSecond = function (intSec) 264 | { 265 | var MaxSec = 59, 266 | MinSec = 0, 267 | 268 | SingleDigit = new RegExp("\\d"), 269 | SingleDigit2 = new RegExp("^\\d{1}$"), 270 | SecExp = new RegExp("^\\d{2}$"), 271 | 272 | strSec = 0; 273 | 274 | if ((SecExp.test(intSec) || SingleDigit.test(intSec)) && (parseInt(intSec, 10) > MaxSec)) 275 | { 276 | intSec = MinSec; 277 | } 278 | 279 | else if ((SecExp.test(intSec) || SingleDigit.test(intSec)) && (parseInt(intSec, 10) < MinSec)) 280 | { 281 | intSec = MaxSec; 282 | } 283 | 284 | strSec = intSec + ""; 285 | if (SingleDigit2.test(intSec)) 286 | { 287 | strSec = "0" + strSec; 288 | } 289 | 290 | if ((SecExp.test(intSec) || SingleDigit.test(intSec)) && (parseInt(intSec, 10) <= 59) && (parseInt(intSec, 10) >= 0)) 291 | { 292 | Cal.Seconds = strSec; 293 | } 294 | 295 | }; 296 | 297 | Calendar.prototype.SetAmPm = function (pvalue) 298 | { 299 | this.AMorPM = pvalue; 300 | if (pvalue === "PM") 301 | { 302 | this.Hours = parseInt(this.Hours, 10) + 12; 303 | if (this.Hours === 24) 304 | { 305 | this.Hours = 12; 306 | } 307 | } 308 | 309 | else if (pvalue === "AM") 310 | { 311 | this.Hours -= 12; 312 | } 313 | }; 314 | 315 | Calendar.prototype.getShowHour = function() { 316 | var finalHour; 317 | 318 | if (TimeMode === 12) { 319 | if (parseInt(this.Hours, 10) === 0) { 320 | this.AMorPM = "AM"; 321 | finalHour = parseInt(this.Hours, 10) + 12; 322 | } 323 | 324 | else if (parseInt(this.Hours, 10) === 12) { 325 | this.AMorPM = "PM"; 326 | finalHour = 12; 327 | } 328 | 329 | else if (this.Hours > 12) { 330 | this.AMorPM = "PM"; 331 | if ((this.Hours - 12) < 10) { 332 | finalHour = "0" + ((parseInt(this.Hours, 10)) - 12); 333 | } 334 | else { 335 | finalHour = parseInt(this.Hours, 10) - 12; 336 | } 337 | } 338 | else { 339 | this.AMorPM = "AM"; 340 | if (this.Hours < 10) { 341 | finalHour = "0" + parseInt(this.Hours, 10); 342 | } 343 | else { 344 | finalHour = this.Hours; 345 | } 346 | } 347 | } 348 | 349 | else if (TimeMode === 24) { 350 | if (this.Hours < 10) { 351 | finalHour = "0" + parseInt(this.Hours, 10); 352 | } 353 | else { 354 | finalHour = this.Hours; 355 | } 356 | } 357 | 358 | return finalHour; 359 | }; 360 | 361 | Calendar.prototype.getShowAMorPM = function () 362 | { 363 | return this.AMorPM; 364 | }; 365 | 366 | Calendar.prototype.GetMonthName = function (IsLong) 367 | { 368 | var Month = MonthName[this.Month]; 369 | if (IsLong) 370 | { 371 | return Month; 372 | } 373 | else 374 | { 375 | return Month.substr(0, 3); 376 | } 377 | }; 378 | 379 | Calendar.prototype.GetMonDays = function() { //Get number of days in a month 380 | 381 | var DaysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 382 | if (Cal.IsLeapYear()) { 383 | DaysInMonth[1] = 29; 384 | } 385 | 386 | return DaysInMonth[this.Month]; 387 | }; 388 | 389 | Calendar.prototype.IsLeapYear = function () 390 | { 391 | if ((this.Year % 4) === 0) 392 | { 393 | if ((this.Year % 100 === 0) && (this.Year % 400) !== 0) 394 | { 395 | return false; 396 | } 397 | else 398 | { 399 | return true; 400 | } 401 | } 402 | else 403 | { 404 | return false; 405 | } 406 | }; 407 | 408 | Calendar.prototype.FormatDate = function (pDate) 409 | { 410 | var MonthDigit = this.Month + 1; 411 | if (PrecedeZero === true) 412 | { 413 | if ((pDate < 10) && String(pDate).length===1) //length checking added in version 2.2 414 | { 415 | pDate = "0" + pDate; 416 | } 417 | if (MonthDigit < 10) 418 | { 419 | MonthDigit = "0" + MonthDigit; 420 | } 421 | } 422 | 423 | switch (this.Format.toUpperCase()) 424 | { 425 | case "DDMMYYYY": 426 | return (pDate + DateSeparator + MonthDigit + DateSeparator + this.Year); 427 | case "DDMMMYYYY": 428 | return (pDate + DateSeparator + this.GetMonthName(false) + DateSeparator + this.Year); 429 | case "MMDDYYYY": 430 | return (MonthDigit + DateSeparator + pDate + DateSeparator + this.Year); 431 | case "MMMDDYYYY": 432 | return (this.GetMonthName(false) + DateSeparator + pDate + DateSeparator + this.Year); 433 | case "YYYYMMDD": 434 | return (this.Year + DateSeparator + MonthDigit + DateSeparator + pDate); 435 | case "YYMMDD": 436 | return (String(this.Year).substring(2, 4) + DateSeparator + MonthDigit + DateSeparator + pDate); 437 | case "YYMMMDD": 438 | return (String(this.Year).substring(2, 4) + DateSeparator + this.GetMonthName(false) + DateSeparator + pDate); 439 | case "YYYYMMMDD": 440 | return (this.Year + DateSeparator + this.GetMonthName(false) + DateSeparator + pDate); 441 | default: 442 | return (pDate + DateSeparator + (this.Month + 1) + DateSeparator + this.Year); 443 | } 444 | }; 445 | 446 | // end Calendar prototype 447 | 448 | function GenCell(pValue, pHighLight, pColor, pClickable) 449 | { //Generate table cell with value 450 | var PValue, 451 | PCellStr, 452 | PClickable, 453 | vTimeStr; 454 | 455 | if (!pValue) 456 | { 457 | PValue = ""; 458 | } 459 | else 460 | { 461 | PValue = pValue; 462 | } 463 | 464 | if (pColor === undefined) 465 | pColor = CalBgColor; 466 | 467 | if (pClickable !== undefined){ 468 | PClickable = pClickable; 469 | } 470 | else{ 471 | PClickable = true; 472 | } 473 | 474 | if (Cal.ShowTime) 475 | { 476 | vTimeStr = ' ' + Cal.Hours + ':' + Cal.Minutes; 477 | if (Cal.ShowSeconds) 478 | { 479 | vTimeStr += ':' + Cal.Seconds; 480 | } 481 | if (TimeMode === 12) 482 | { 483 | vTimeStr += ' ' + Cal.AMorPM; 484 | } 485 | } 486 | 487 | else 488 | { 489 | vTimeStr = ""; 490 | } 491 | 492 | if (PValue !== "") 493 | { 494 | if (PClickable === true) { 495 | if (Cal.ShowTime === true) 496 | { PCellStr = ""; } 497 | else { PCellStr = ""; } 498 | } 499 | else 500 | { PCellStr = ""; } 501 | } 502 | else 503 | { PCellStr = ""; } 504 | 505 | return PCellStr; 506 | } 507 | 508 | function RenderCssCal(bNewCal) 509 | { 510 | if (typeof bNewCal === "undefined" || bNewCal !== true) 511 | { 512 | bNewCal = false; 513 | } 514 | var vCalHeader, 515 | vCalData, 516 | vCalTime = "", 517 | vCalClosing = "", 518 | winCalData = "", 519 | CalDate, 520 | 521 | i, 522 | j, 523 | 524 | SelectStr, 525 | vDayCount = 0, 526 | vFirstDay, 527 | 528 | WeekDayName = [],//Added version 1.7 529 | strCell, 530 | 531 | showHour, 532 | ShowArrows = false, 533 | HourCellWidth = "35px", //cell width with seconds. 534 | 535 | SelectAm, 536 | SelectPm, 537 | 538 | funcCalback, 539 | 540 | headID, 541 | e, 542 | cssStr, 543 | style, 544 | cssText, 545 | span; 546 | 547 | calHeight = 0; // reset the window height on refresh 548 | 549 | // Set the default cursor for the calendar 550 | 551 | winCalData = ""; 552 | vCalHeader = "
" + PValue + "" + PValue + ""+PValue+" 
"; 553 | 554 | //Table for Month & Year Selector 555 | 556 | vCalHeader += ""; 622 | 623 | //******************End Month and Year selector in arrow****************************** 624 | 625 | //Calendar header shows Month and Year 626 | if (ShowMonthYear && Cal.Scroller === "DROPDOWN") 627 | { 628 | vCalHeader += ""; 629 | calHeight += 19; 630 | } 631 | 632 | //Week day header 633 | 634 | vCalHeader += ""; 763 | 764 | 765 | //Time picker 766 | if (Cal.ShowTime === true) 767 | { 768 | showHour = Cal.getShowHour(); 769 | 770 | if (Cal.ShowSeconds === false && TimeMode === 24) 771 | { 772 | ShowArrows = true; 773 | HourCellWidth = "10px"; 774 | } 775 | 776 | vCalTime = ""; 826 | calHeight += 31; 827 | vCalClosing += "
"; 557 | //******************Month and Year selector in dropdown list************************ 558 | 559 | if (Cal.Scroller === "DROPDOWN") 560 | { 561 | vCalHeader += ""; 576 | //Year selector 577 | 578 | vCalHeader += "\n"; 592 | calHeight += 30; 593 | } 594 | 595 | //******************End Month and Year selector in dropdown list********************* 596 | 597 | //******************Month and Year selector in arrow********************************* 598 | 599 | else if (Cal.Scroller === "ARROW") 600 | { 601 | if (UseImageFiles) 602 | { 603 | vCalHeader += "\n";//Year scroller (decrease 1 year) 604 | vCalHeader += "\n"; //Month scroller (decrease 1 month) 605 | vCalHeader += ""; //Month and Year 606 | vCalHeader += "\n"; //Month scroller (increase 1 month) 607 | vCalHeader += "\n"; //Year scroller (increase 1 year) 608 | calHeight += 22; 609 | } 610 | else 611 | { 612 | vCalHeader += "";//Year scroller (decrease 1 year) 613 | vCalHeader += "\n";//Month scroller (decrease 1 month) 614 | vCalHeader += "\n"; //Month and Year 615 | vCalHeader += "\n";//Month scroller (increase 1 month) 616 | vCalHeader += "\n";//Year scroller (increase 1 year) 617 | calHeight += 22; 618 | } 619 | } 620 | 621 | vCalHeader += "
"+ Cal.GetMonthName(ShowLongMonth) + " " + Cal.Year + "-<" + Cal.GetMonthName(ShowLongMonth) + " " + Cal.Year + ">+
" + Cal.GetMonthName(ShowLongMonth) + " " + Cal.Year + "
"; 635 | if (MondayFirstDay === true) 636 | { 637 | WeekDayName = WeekDayName2; 638 | } 639 | else 640 | { 641 | WeekDayName = WeekDayName1; 642 | } 643 | for (i = 0; i < 7; i += 1) 644 | { 645 | vCalHeader += ""; 646 | } 647 | 648 | calHeight += 19; 649 | vCalHeader += ""; 650 | //Calendar detail 651 | CalDate = new Date(Cal.Year, Cal.Month); 652 | CalDate.setDate(1); 653 | 654 | vFirstDay = CalDate.getDay(); 655 | 656 | //Added version 1.7 657 | if (MondayFirstDay === true) 658 | { 659 | vFirstDay -= 1; 660 | if (vFirstDay === -1) 661 | { 662 | vFirstDay = 6; 663 | } 664 | } 665 | 666 | //Added version 1.7 667 | vCalData = ""; 668 | calHeight += 19; 669 | for (i = 0; i < vFirstDay; i += 1) 670 | { 671 | vCalData = vCalData + GenCell(); 672 | vDayCount = vDayCount + 1; 673 | } 674 | 675 | //Added version 1.7 676 | for (j = 1; j <= Cal.GetMonDays(); j += 1) 677 | { 678 | if ((vDayCount % 7 === 0) && (j > 1)) 679 | { 680 | vCalData = vCalData + ""; 681 | } 682 | 683 | vDayCount = vDayCount + 1; 684 | //added version 2.1.2 685 | if (Cal.EnableDateMode === "future" && ((j < dtToday.getDate()) && (Cal.Month === dtToday.getMonth()) && (Cal.Year === dtToday.getFullYear()) || (Cal.Month < dtToday.getMonth()) && (Cal.Year === dtToday.getFullYear()) || (Cal.Year < dtToday.getFullYear()))) 686 | { 687 | strCell = GenCell(j, false, DisableColor, false); //Before today's date is not clickable 688 | } 689 | else if (Cal.EnableDateMode === "past" && ((j >= dtToday.getDate()) && (Cal.Month === dtToday.getMonth()) && (Cal.Year === dtToday.getFullYear()) || (Cal.Month > dtToday.getMonth()) && (Cal.Year === dtToday.getFullYear()) || (Cal.Year > dtToday.getFullYear()))) { 690 | strCell = GenCell(j, false, DisableColor, false); //After today's date is not clickable 691 | } 692 | //if End Year + Current Year = Cal.Year. Disable. 693 | else if (Cal.Year > (dtToday.getFullYear()+EndYear)) 694 | { 695 | strCell = GenCell(j, false, DisableColor, false); 696 | } 697 | else if ((j === dtToday.getDate()) && (Cal.Month === dtToday.getMonth()) && (Cal.Year === dtToday.getFullYear())) 698 | { 699 | strCell = GenCell(j, true, TodayColor);//Highlight today's date 700 | } 701 | else 702 | { 703 | if ((j === selDate.getDate()) && (Cal.Month === selDate.getMonth()) && (Cal.Year === selDate.getFullYear())){ 704 | //modified version 1.7 705 | strCell = GenCell(j, true, SelDateColor); 706 | } 707 | else 708 | { 709 | if (MondayFirstDay === true) 710 | { 711 | if (vDayCount % 7 === 0) 712 | { 713 | strCell = GenCell(j, false, SundayColor); 714 | } 715 | else if ((vDayCount + 1) % 7 === 0) 716 | { 717 | strCell = GenCell(j, false, SaturdayColor); 718 | } 719 | else 720 | { 721 | strCell = GenCell(j, null, WeekDayColor); 722 | } 723 | } 724 | else 725 | { 726 | if (vDayCount % 7 === 0) 727 | { 728 | strCell = GenCell(j, false, SaturdayColor); 729 | } 730 | else if ((vDayCount + 6) % 7 === 0) 731 | { 732 | strCell = GenCell(j, false, SundayColor); 733 | } 734 | else 735 | { 736 | strCell = GenCell(j, null, WeekDayColor); 737 | } 738 | } 739 | } 740 | } 741 | 742 | vCalData = vCalData + strCell; 743 | 744 | if ((vDayCount % 7 === 0) && (j < Cal.GetMonDays())) 745 | { 746 | vCalData = vCalData + ""; 747 | calHeight += 19; 748 | } 749 | } 750 | 751 | // finish the table proper 752 | 753 | if (vDayCount % 7 !== 0) 754 | { 755 | while (vDayCount % 7 !== 0) 756 | { 757 | vCalData = vCalData + GenCell(); 758 | vDayCount = vDayCount + 1; 759 | } 760 | } 761 | 762 | vCalData = vCalData + "
" + WeekDayName[i].substr(0, WeekChar) + "
"; 777 | 778 | if (ShowArrows && UseImageFiles) //this is where the up and down arrow control the hour. 779 | { 780 | vCalTime += "\n"; 781 | } 782 | 783 | vCalTime += "\n\n"; 811 | vCalTime += ""; 812 | } 813 | else //if not to show time. 814 | { 815 | vCalTime += "\n\n"; 819 | } 820 | else { 821 | vCalClosing += "x"; 822 | } 823 | vCalClosing += ""; 824 | } 825 | vCalClosing += "
 
"; 784 | vCalTime += ":"; 785 | vCalTime += ""; 786 | 787 | if (Cal.ShowSeconds) 788 | { 789 | vCalTime += ":"; 790 | vCalTime += ""; 791 | } 792 | 793 | if (TimeMode === 12) 794 | { 795 | SelectAm = (Cal.AMorPM === "AM") ? "Selected" : ""; 796 | SelectPm = (Cal.AMorPM === "PM") ? "Selected" : ""; 797 | 798 | vCalTime += ""; 799 | vCalTime += ""; 803 | } 804 | 805 | if (ShowArrows && UseImageFiles) //this is where the up and down arrow to change the "Minute". 806 | { 807 | vCalTime += "
"; 808 | } 809 | 810 | vCalTime += "
 
"; 816 | //close button 817 | if (UseImageFiles) { 818 | vCalClosing += "
\n"; 828 | 829 | //end time picker 830 | funcCalback = "function callback(id, datum) {"; 831 | funcCalback += " var CalId = document.getElementById(id);if (datum=== 'undefined') { var d = new Date(); datum = d.getDate() + '/' +(d.getMonth()+1) + '/' + d.getFullYear(); } window.calDatum=datum;CalId.value=datum;"; 832 | funcCalback += " if(Cal.ShowTime){"; 833 | funcCalback += " CalId.value+=' '+Cal.getShowHour()+':'+Cal.Minutes;"; 834 | funcCalback += " if (Cal.ShowSeconds) CalId.value+=':'+Cal.Seconds;"; 835 | funcCalback += " if (TimeMode === 12) CalId.value+=''+Cal.getShowAMorPM();"; 836 | funcCalback += "}if(CalId.onchange!=undefined) CalId.onchange();CalId.focus();winCal.style.visibility='hidden';}"; 837 | 838 | 839 | // determines if there is enough space to open the cal above the position where it is called 840 | if (ypos > calHeight) 841 | { 842 | ypos = ypos - calHeight; 843 | } 844 | 845 | if (!winCal) 846 | { 847 | headID = document.getElementsByTagName("head")[0]; 848 | 849 | // add javascript function to the span cal 850 | e = document.createElement("script"); 851 | e.type = "text/javascript"; 852 | e.language = "javascript"; 853 | e.text = funcCalback; 854 | headID.appendChild(e); 855 | // add stylesheet to the span cal 856 | 857 | cssStr = ".calTD {font-family: verdana; font-size: 12px; text-align: center; border:0; }\n"; 858 | cssStr += ".calR {font-family: verdana; font-size: 12px; text-align: center; font-weight: bold;}"; 859 | 860 | style = document.createElement("style"); 861 | style.type = "text/css"; 862 | style.rel = "stylesheet"; 863 | if (style.styleSheet) 864 | { // IE 865 | style.styleSheet.cssText = cssStr; 866 | } 867 | 868 | else 869 | { // w3c 870 | cssText = document.createTextNode(cssStr); 871 | style.appendChild(cssText); 872 | } 873 | 874 | headID.appendChild(style); 875 | // create the outer frame that allows the cal. to be moved 876 | span = document.createElement("span"); 877 | span.id = calSpanID; 878 | span.style.position = "absolute"; 879 | span.style.left = (xpos + CalPosOffsetX) + 'px'; 880 | span.style.top = (ypos - CalPosOffsetY) + 'px'; 881 | span.style.width = CalWidth + 'px'; 882 | span.style.border = "solid 1pt " + SpanBorderColor; 883 | span.style.padding = "0"; 884 | span.style.cursor = "move"; 885 | span.style.backgroundColor = SpanBgColor; 886 | span.style.zIndex = 100; 887 | document.body.appendChild(span); 888 | winCal = document.getElementById(calSpanID); 889 | } 890 | 891 | else 892 | { 893 | winCal.style.visibility = "visible"; 894 | winCal.style.Height = calHeight; 895 | 896 | // set the position for a new calendar only 897 | if (bNewCal === true) 898 | { 899 | winCal.style.left = (xpos + CalPosOffsetX) + 'px'; 900 | winCal.style.top = (ypos - CalPosOffsetY) + 'px'; 901 | } 902 | } 903 | 904 | winCal.innerHTML = winCalData + vCalHeader + vCalData + vCalTime + vCalClosing; 905 | return true; 906 | } 907 | 908 | 909 | function NewCssCal(pCtrl, pFormat, pScroller, pShowTime, pTimeMode, pShowSeconds, pEnableDateMode) 910 | { 911 | // get current date and time 912 | 913 | dtToday = new Date(); 914 | Cal = new Calendar(dtToday); 915 | 916 | if (pShowTime !== undefined) 917 | { 918 | if (pShowTime) { 919 | Cal.ShowTime = true; 920 | } 921 | else { 922 | Cal.ShowTime = false; 923 | } 924 | 925 | if (pTimeMode) 926 | { 927 | pTimeMode = parseInt(pTimeMode, 10); 928 | } 929 | if (pTimeMode === 12 || pTimeMode === 24) 930 | { 931 | TimeMode = pTimeMode; 932 | } 933 | else 934 | { 935 | TimeMode = 24; 936 | } 937 | 938 | if (pShowSeconds !== undefined) 939 | { 940 | if (pShowSeconds) 941 | { 942 | Cal.ShowSeconds = true; 943 | } 944 | else 945 | { 946 | Cal.ShowSeconds = false; 947 | } 948 | } 949 | else 950 | { 951 | Cal.ShowSeconds = false; 952 | } 953 | 954 | } 955 | 956 | if (pCtrl !== undefined) 957 | { 958 | Cal.Ctrl = pCtrl; 959 | } 960 | 961 | if (pFormat!== undefined && pFormat !=="") 962 | { 963 | Cal.Format = pFormat.toUpperCase(); 964 | } 965 | else 966 | { 967 | Cal.Format = "MMDDYYYY"; 968 | } 969 | 970 | if (pScroller!== undefined && pScroller!=="") 971 | { 972 | if (pScroller.toUpperCase() === "ARROW") 973 | { 974 | Cal.Scroller = "ARROW"; 975 | } 976 | else 977 | { 978 | Cal.Scroller = "DROPDOWN"; 979 | } 980 | } 981 | 982 | if (pEnableDateMode !== undefined && (pEnableDateMode === "future" || pEnableDateMode === "past")) { 983 | Cal.EnableDateMode= pEnableDateMode; 984 | } 985 | 986 | exDateTime = document.getElementById(pCtrl).value; //Existing Date Time value in textbox. 987 | 988 | if (exDateTime) 989 | { //Parse existing Date String 990 | var Sp1 = exDateTime.indexOf(DateSeparator, 0),//Index of Date Separator 1 991 | Sp2 = exDateTime.indexOf(DateSeparator, parseInt(Sp1, 10) + 1),//Index of Date Separator 2 992 | tSp1,//Index of Time Separator 1 993 | tSp2,//Index of Time Separator 2 994 | strMonth, 995 | strDate, 996 | strYear, 997 | intMonth, 998 | YearPattern, 999 | strHour, 1000 | strMinute, 1001 | strSecond, 1002 | winHeight, 1003 | offset = parseInt(Cal.Format.toUpperCase().lastIndexOf("M"), 10) - parseInt(Cal.Format.toUpperCase().indexOf("M"), 10) - 1, 1004 | strAMPM = ""; 1005 | //parse month 1006 | 1007 | if (Cal.Format.toUpperCase() === "DDMMYYYY" || Cal.Format.toUpperCase() === "DDMMMYYYY") 1008 | { 1009 | if (DateSeparator === "") 1010 | { 1011 | strMonth = exDateTime.substring(2, 4 + offset); 1012 | strDate = exDateTime.substring(0, 2); 1013 | strYear = exDateTime.substring(4 + offset, 8 + offset); 1014 | } 1015 | else 1016 | { 1017 | if (exDateTime.indexOf("D*") !== -1) 1018 | { //DTG 1019 | strMonth = exDateTime.substring(8, 11); 1020 | strDate = exDateTime.substring(0, 2); 1021 | strYear = "20" + exDateTime.substring(11, 13); //Hack, nur für Jahreszahlen ab 2000 1022 | } 1023 | else 1024 | { 1025 | strMonth = exDateTime.substring(Sp1 + 1, Sp2); 1026 | strDate = exDateTime.substring(0, Sp1); 1027 | strYear = exDateTime.substring(Sp2 + 1, Sp2 + 5); 1028 | } 1029 | } 1030 | } 1031 | 1032 | else if (Cal.Format.toUpperCase() === "MMDDYYYY" || Cal.Format.toUpperCase() === "MMMDDYYYY"){ 1033 | if (DateSeparator === ""){ 1034 | strMonth = exDateTime.substring(0, 2 + offset); 1035 | strDate = exDateTime.substring(2 + offset, 4 + offset); 1036 | strYear = exDateTime.substring(4 + offset, 8 + offset); 1037 | } 1038 | else{ 1039 | strMonth = exDateTime.substring(0, Sp1); 1040 | strDate = exDateTime.substring(Sp1 + 1, Sp2); 1041 | strYear = exDateTime.substring(Sp2 + 1, Sp2 + 5); 1042 | } 1043 | } 1044 | 1045 | else if (Cal.Format.toUpperCase() === "YYYYMMDD" || Cal.Format.toUpperCase() === "YYYYMMMDD") 1046 | { 1047 | if (DateSeparator === ""){ 1048 | strMonth = exDateTime.substring(4, 6 + offset); 1049 | strDate = exDateTime.substring(6 + offset, 8 + offset); 1050 | strYear = exDateTime.substring(0, 4); 1051 | } 1052 | else{ 1053 | strMonth = exDateTime.substring(Sp1 + 1, Sp2); 1054 | strDate = exDateTime.substring(Sp2 + 1, Sp2 + 3); 1055 | strYear = exDateTime.substring(0, Sp1); 1056 | } 1057 | } 1058 | 1059 | else if (Cal.Format.toUpperCase() === "YYMMDD" || Cal.Format.toUpperCase() === "YYMMMDD") 1060 | { 1061 | if (DateSeparator === "") 1062 | { 1063 | strMonth = exDateTime.substring(2, 4 + offset); 1064 | strDate = exDateTime.substring(4 + offset, 6 + offset); 1065 | strYear = exDateTime.substring(0, 2); 1066 | } 1067 | else 1068 | { 1069 | strMonth = exDateTime.substring(Sp1 + 1, Sp2); 1070 | strDate = exDateTime.substring(Sp2 + 1, Sp2 + 3); 1071 | strYear = exDateTime.substring(0, Sp1); 1072 | } 1073 | } 1074 | 1075 | if (isNaN(strMonth)){ 1076 | intMonth = Cal.GetMonthIndex(strMonth); 1077 | } 1078 | else{ 1079 | intMonth = parseInt(strMonth, 10) - 1; 1080 | } 1081 | if ((parseInt(intMonth, 10) >= 0) && (parseInt(intMonth, 10) < 12)) { 1082 | Cal.Month = intMonth; 1083 | } 1084 | //end parse month 1085 | 1086 | //parse year 1087 | YearPattern = /^\d{4}$/; 1088 | if (YearPattern.test(strYear)) { 1089 | if ((parseInt(strYear, 10)>=StartYear) && (parseInt(strYear, 10)<= (dtToday.getFullYear()+EndYear))) 1090 | Cal.Year = parseInt(strYear, 10); 1091 | } 1092 | //end parse year 1093 | 1094 | //parse Date 1095 | if ((parseInt(strDate, 10) <= Cal.GetMonDays()) && (parseInt(strDate, 10) >= 1)) { 1096 | Cal.Date = strDate; 1097 | } 1098 | //end parse Date 1099 | 1100 | //parse time 1101 | 1102 | if (Cal.ShowTime === true) 1103 | { 1104 | //parse AM or PM 1105 | if (TimeMode === 12) 1106 | { 1107 | strAMPM = exDateTime.substring(exDateTime.length - 2, exDateTime.length); 1108 | Cal.AMorPM = strAMPM; 1109 | } 1110 | 1111 | tSp1 = exDateTime.indexOf(":", 0); 1112 | tSp2 = exDateTime.indexOf(":", (parseInt(tSp1, 10) + 1)); 1113 | if (tSp1 > 0) 1114 | { 1115 | strHour = exDateTime.substring(tSp1, tSp1 - 2); 1116 | Cal.SetHour(strHour); 1117 | 1118 | strMinute = exDateTime.substring(tSp1 + 1, tSp1 + 3); 1119 | Cal.SetMinute(strMinute); 1120 | 1121 | strSecond = exDateTime.substring(tSp2 + 1, tSp2 + 3); 1122 | Cal.SetSecond(strSecond); 1123 | 1124 | } 1125 | else if (exDateTime.indexOf("D*") !== -1) 1126 | { //DTG 1127 | strHour = exDateTime.substring(2, 4); 1128 | Cal.SetHour(strHour); 1129 | strMinute = exDateTime.substring(4, 6); 1130 | Cal.SetMinute(strMinute); 1131 | 1132 | } 1133 | } 1134 | 1135 | } 1136 | selDate = new Date(Cal.Year, Cal.Month, Cal.Date);//version 1.7 1137 | RenderCssCal(true); 1138 | } 1139 | 1140 | function closewin(id) { 1141 | if (Cal.ShowTime === true) { 1142 | var MaxYear = dtToday.getFullYear() + EndYear; 1143 | var beforeToday = 1144 | (Cal.Date < dtToday.getDate()) && 1145 | (Cal.Month === dtToday.getMonth()) && 1146 | (Cal.Year === dtToday.getFullYear()) 1147 | || 1148 | (Cal.Month < dtToday.getMonth()) && 1149 | (Cal.Year === dtToday.getFullYear()) 1150 | || 1151 | (Cal.Year < dtToday.getFullYear()); 1152 | 1153 | if ((Cal.Year <= MaxYear) && (Cal.Year >= StartYear) && (Cal.Month === selDate.getMonth()) && (Cal.Year === selDate.getFullYear())) { 1154 | if (Cal.EnableDateMode === "future") { 1155 | if (beforeToday === false) { 1156 | callback(id, Cal.FormatDate(Cal.Date)); 1157 | } 1158 | } 1159 | else 1160 | callback(id, Cal.FormatDate(Cal.Date)); 1161 | } 1162 | } 1163 | 1164 | var CalId = document.getElementById(id); 1165 | CalId.focus(); 1166 | winCal.style.visibility = 'hidden'; 1167 | } 1168 | 1169 | function changeBorder(element, col, oldBgColor) 1170 | { 1171 | if (col === 0) 1172 | { 1173 | element.style.background = HoverColor; 1174 | element.style.borderColor = "black"; 1175 | element.style.cursor = "pointer"; 1176 | } 1177 | 1178 | else 1179 | { 1180 | if (oldBgColor) 1181 | { 1182 | element.style.background = oldBgColor; 1183 | } 1184 | else 1185 | { 1186 | element.style.background = "white"; 1187 | } 1188 | element.style.borderColor = "white"; 1189 | element.style.cursor = "auto"; 1190 | } 1191 | } 1192 | 1193 | function selectDate(element, date) { 1194 | Cal.Date = date; 1195 | selDate = new Date(Cal.Year, Cal.Month, Cal.Date); 1196 | element.style.background = SelDateColor; 1197 | RenderCssCal(); 1198 | } 1199 | 1200 | function pickIt(evt) 1201 | { 1202 | var objectID, 1203 | dom, 1204 | de, 1205 | b; 1206 | // accesses the element that generates the event and retrieves its ID 1207 | if (document.addEventListener) 1208 | { // w3c 1209 | objectID = evt.target.id; 1210 | if (objectID.indexOf(calSpanID) !== -1) 1211 | { 1212 | dom = document.getElementById(objectID); 1213 | cnLeft = evt.pageX; 1214 | cnTop = evt.pageY; 1215 | 1216 | if (dom.offsetLeft) 1217 | { 1218 | cnLeft = (cnLeft - dom.offsetLeft); 1219 | cnTop = (cnTop - dom.offsetTop); 1220 | } 1221 | } 1222 | 1223 | // get mouse position on click 1224 | xpos = (evt.pageX); 1225 | ypos = (evt.pageY); 1226 | } 1227 | 1228 | else 1229 | { // IE 1230 | objectID = event.srcElement.id; 1231 | cnLeft = event.offsetX; 1232 | cnTop = (event.offsetY); 1233 | 1234 | // get mouse position on click 1235 | de = document.documentElement; 1236 | b = document.body; 1237 | 1238 | xpos = event.clientX + (de.scrollLeft || b.scrollLeft) - (de.clientLeft || 0); 1239 | ypos = event.clientY + (de.scrollTop || b.scrollTop) - (de.clientTop || 0); 1240 | } 1241 | 1242 | // verify if this is a valid element to pick 1243 | if (objectID.indexOf(calSpanID) !== -1) 1244 | { 1245 | domStyle = document.getElementById(objectID).style; 1246 | } 1247 | 1248 | if (domStyle) 1249 | { 1250 | domStyle.zIndex = 100; 1251 | return false; 1252 | } 1253 | 1254 | else 1255 | { 1256 | domStyle = null; 1257 | return; 1258 | } 1259 | } 1260 | 1261 | 1262 | 1263 | function dragIt(evt) 1264 | { 1265 | if (domStyle) 1266 | { 1267 | if (document.addEventListener) 1268 | { //for IE 1269 | domStyle.left = (event.clientX - cnLeft + document.body.scrollLeft) + 'px'; 1270 | domStyle.top = (event.clientY - cnTop + document.body.scrollTop) + 'px'; 1271 | } 1272 | else 1273 | { //Firefox 1274 | domStyle.left = (evt.clientX - cnLeft + document.body.scrollLeft) + 'px'; 1275 | domStyle.top = (evt.clientY - cnTop + document.body.scrollTop) + 'px'; 1276 | } 1277 | } 1278 | } 1279 | 1280 | // performs a single increment or decrement 1281 | function nextStep(whatSpinner, direction) 1282 | { 1283 | if (whatSpinner === "Hour") 1284 | { 1285 | if (direction === "plus") 1286 | { 1287 | Cal.SetHour(Cal.Hours + 1); 1288 | RenderCssCal(); 1289 | } 1290 | else if (direction === "minus") 1291 | { 1292 | Cal.SetHour(Cal.Hours - 1); 1293 | RenderCssCal(); 1294 | } 1295 | } 1296 | else if (whatSpinner === "Minute") 1297 | { 1298 | if (direction === "plus") 1299 | { 1300 | Cal.SetMinute(parseInt(Cal.Minutes, 10) + 1); 1301 | RenderCssCal(); 1302 | } 1303 | else if (direction === "minus") 1304 | { 1305 | Cal.SetMinute(parseInt(Cal.Minutes, 10) - 1); 1306 | RenderCssCal(); 1307 | } 1308 | } 1309 | 1310 | } 1311 | 1312 | // starts the time spinner 1313 | function startSpin(whatSpinner, direction) 1314 | { 1315 | document.thisLoop = setInterval(function () 1316 | { 1317 | nextStep(whatSpinner, direction); 1318 | }, 125); //125 ms 1319 | } 1320 | 1321 | //stops the time spinner 1322 | function stopSpin() 1323 | { 1324 | clearInterval(document.thisLoop); 1325 | } 1326 | 1327 | function dropIt() 1328 | { 1329 | stopSpin(); 1330 | 1331 | if (domStyle) 1332 | { 1333 | domStyle = null; 1334 | } 1335 | } 1336 | 1337 | // Default events configuration 1338 | 1339 | document.onmousedown = pickIt; 1340 | document.onmousemove = dragIt; 1341 | document.onmouseup = dropIt; 1342 | --------------------------------------------------------------------------------