diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..6eb9f7b --- /dev/null +++ b/autogen.sh @@ -0,0 +1,8 @@ +#!/bin/bash -e +if test -n "svn" -a -d .svn -a -e -x ; then + svn2cl +fi +aclocal + +automake -a +autoconf diff --git a/ax_cxx_compile_stdcxx_11.m4 b/ax_cxx_compile_stdcxx_11.m4 index af37acd..4c831e0 100644 --- a/ax_cxx_compile_stdcxx_11.m4 +++ b/ax_cxx_compile_stdcxx_11.m4 @@ -4,12 +4,16 @@ # # SYNOPSIS # +# AX_REQUIRE_STDCXX_11 +# AX_REQUIRE_STDCXX_14 # AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) +# AX_CXX_COMPILE_STDCXX_14([ext|noext],[mandatory|optional]) # # DESCRIPTION # # Check for baseline language coverage in the compiler for the C++11 -# standard; if necessary, add switches to CXXFLAGS to enable support. +# or C++14 standard; if necessary, add switches to CXXFLAGS to +# enable support. # # The first argument, if specified, indicates whether you insist on an # extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. @@ -131,3 +135,97 @@ AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl AC_SUBST(HAVE_CXX11) fi ]) + +AC_DEFUN([AX_CXX_COMPILE_STDCXX_14], [dnl + m4_if([$1], [], [], + [$1], [ext], [], + [$1], [noext], [], + [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_14])])dnl + m4_if([$2], [], [ax_cxx_compile_cxx14_required=true], + [$2], [mandatory], [ax_cxx_compile_cxx14_required=true], + [$2], [optional], [ax_cxx_compile_cxx14_required=false], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_14])])dnl + AC_LANG_PUSH([C++])dnl + ac_success=no + AC_CACHE_CHECK(whether $CXX supports C++14 features by default, + ax_cv_cxx_compile_cxx14, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [ax_cv_cxx_compile_cxx14=yes], + [ax_cv_cxx_compile_cxx14=no])]) + if test x$ax_cv_cxx_compile_cxx14 = xyes; then + ac_success=yes + fi + + m4_if([$1], [noext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=gnu++14 -std=gnu++0y; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx14_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++14 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + + m4_if([$1], [ext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=c++14 -std=c++0y; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx14_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++14 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx14_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++14 language features is required.]) + fi + else + if test x$ac_success = xno; then + HAVE_CXX14=0 + AC_MSG_NOTICE([No compiler with C++14 support was found]) + AX_CXX_COMPILE_STDCXX_11([$1], [optional]) + else + HAVE_CXX11=1 + HAVE_CXX14=1 + AC_DEFINE(HAVE_CXX14,1, + [define if the compiler supports basic C++14 syntax]) + AC_DEFINE(HAVE_CXX11,1, + [define if the compiler supports basic C++14 syntax]) + fi + AC_SUBST(HAVE_CXX11) + AC_SUBST(HAVE_CXX14) + fi +]) + +AC_DEFUN([AX_REQUIRE_STDCXX_11], [ + if test x${HAVE_CXX11} != x1; then + AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) + fi +]) + +AC_DEFUN([AX_REQUIRE_STDCXX_14], [ + if test x${HAVE_CXX14} != x1; then + AC_MSG_ERROR([*** A compiler with support for C++14 language features is required.]) + fi +]) diff --git a/ax_init_standard_project.m4 b/ax_init_standard_project.m4 index 93b6a8f..4f63768 100644 --- a/ax_init_standard_project.m4 +++ b/ax_init_standard_project.m4 @@ -7,34 +7,32 @@ m4_define([mrw_esyscmd_s], [m4_normalize(m4_esyscmd([$1]))]) # define least version number from subversion's revision number: -# it is taken modulo 256 due to a bug on Apple's MacOSX +# it is taken modulo 256 due to a bug on Apple's SX m4_define(x_least, m4_ifdef([x_least_fix], [x_least_fix], m4_ifdef([x_least_diff], mrw_esyscmd_s([ VCS_REVISION="ERROR-UNDEFINED-REVISION-to-be-built-in-subdirectory-of-checkout" - for path in . .. ../..; do + for path in . .. ../.. ../../..; do if test -d .svn; then + svn upgrade 1>&2 > /dev/null || true VCS_REVISION=$(LANG= svn info $path | sed -n 's/Last Changed Rev: //p') - if test -z "${VCS_REVISION}"; then VCS_REVISION=0; fi - break; + if test -n "${VCS_REVISION}"; then break; fi elif test -d .git; then VCS_REVISION=$(git rev-list --all --count) - if test -z "${VCS_REVISION}"; then VCS_REVISION=0; fi - break; + if test -n "${VCS_REVISION}"; then break; fi fi done echo $ECHO_N $(($VCS_REVISION)) ]), mrw_esyscmd_s([ VCS_REVISION="ERROR-UNDEFINED-REVISION-to-be-built-in-subdirectory-of-checkout" - for path in . .. ../..; do + for path in . .. ../.. ../../..; do if test -d .svn; then + svn upgrade 1>&2 > /dev/null || true VCS_REVISION=$(LANG= svn info $path | sed -n 's/Last Changed Rev: //p') - if test -z "${VCS_REVISION}"; then VCS_REVISION=0; fi - break; + if test -n "${VCS_REVISION}"; then break; fi elif test -d .git; then VCS_REVISION=$(git rev-list --all --count) - if test -z "${VCS_REVISION}"; then VCS_REVISION=0; fi - break; + if test -n "${VCS_REVISION}"; then break; fi fi done # Mac does not support LEAST > 255 @@ -48,15 +46,14 @@ m4_define(x_least, m4_ifdef([x_least_fix], [x_least_fix], # add to x_minor if revision number is > 256 m4_define(x_minor_diff, m4_ifdef([x_least_fix], 0, mrw_esyscmd_s([ VCS_REVISION="ERROR-UNDEFINED-REVISION-to-be-built-in-subdirectory-of-checkout" - for path in . .. ../..; do + for path in . .. ../.. ../../..; do if test -d .svn; then + svn upgrade 1>&2 > /dev/null || true VCS_REVISION=$(LANG= svn info $path | sed -n 's/Last Changed Rev: //p') - if test -z "${VCS_REVISION}"; then VCS_REVISION=0; fi - break; + if test -n "${VCS_REVISION}"; then break; fi elif test -d .git; then VCS_REVISION=$(git rev-list --all --count) - if test -z "${VCS_REVISION}"; then VCS_REVISION=0; fi - break; + if test -n "${VCS_REVISION}"; then break; fi fi; done # Mac does not support LEAST > 255 @@ -132,7 +129,7 @@ AC_DEFUN([AX_SUBST], [ # m4_define(x_minor, MINOR_NUMBER) # project's minor version # m4_include(ax_init_standard_project.m4) # AC_INIT(x_package_name, x_version, x_bugreport, x_package_name) -# AM_INIT_AUTOMAKE([1.9 tar-pax]) +# AM_INIT_AUTOMAKE([1.9 tar-pax parallel-tests color-tests]) # AX_INIT_STANDARD_PROJECT # # you change nothing but: YOUR_PACKAGE_NAME, MAJOR_NUMBER, MINOR_NUMBER @@ -173,17 +170,49 @@ AC_DEFUN([AX_INIT_STANDARD_PROJECT], [ AM_CPPFLAGS+=" '-DMAKE_STRING(X)=\#X' '-DNAMESPACE=${PACKAGE_TARNAME//[^a-zA-Z0-9]/_}'" AX_SUBST(NUMBERS) AX_SUBST(HOME) - README=$(tail -n +3 README) + if test -f README.md; then + README=$(tail -n +3 README.md) + DESCRIPTION=$(head -1 README.md) + else + README=$(tail -n +3 README) + DESCRIPTION=$(head -1 README) + fi + README_ESCAPED=$(echo "$README" | sed ':a;N;$!ba;s/\n/\\n/g;s,",\\",g') + if which pandoc 2>&1 > /dev/null; then + README_HTML=$(echo "$README" | pandoc -f markdown_github -t html | sed ':a;N;$!ba;s,\\\(.\),\\\\\1,g;s/\n/\\n/g;s,",\\",g;s, ,\ \ ,g') + else + README_HTML="${README}" + fi AX_SUBST(README) _AM_SUBST_NOTMAKE([README]) - DESCRIPTION=$(head -1 README) + AX_SUBST(README_ESCAPED) + _AM_SUBST_NOTMAKE([README_ESCAPED]) + AX_SUBST(README_HTML) + _AM_SUBST_NOTMAKE([README_HTML]) AX_SUBST(DESCRIPTION) _AM_SUBST_NOTMAKE([DESCRIPTION]) + LICENSE=$(echo $(head -1 COPYING)) + AX_SUBST(LICENSE) + COPYING=$(.*,\1,') AX_SUBST(AUTHOR) _AM_SUBST_NOTMAKE([AUTHOR]) + AX_SUBST(AUTHOR_NAME) + AX_SUBST(AUTHOR_URL) + AX_SUBST(AUTHOR_MAIL) + PROJECT_URL="${PROJECT_URL:-${AUTHOR_URL}/projects/${PACKAGE_NAME}}" + SOURCE_DOWNLOAD="${SOURCE_DOWNLOAD:-${AUTHOR_URL}/downloads/${PACKAGE_NAME}}" + AX_SUBST(PROJECT_URL) + AX_SUBST(SOURCE_DOWNLOAD) DISTRO=$(lsb_release -sc 2>/dev/null || uname -s 2>/dev/null) AX_SUBST(DISTRO) + ARCH=$((@<:@@<:@ $(uname -sm) =~ 64 @:>@@:>@ && echo amd64) || (@<:@@<:@ $(uname -sm) =~ 'i?86' @:>@@:>@ && echo i386 || uname -sm)) + AX_SUBST(ARCH) DISTRIBUTOR=$(lsb_release -si 2>/dev/null || uname -s 2>/dev/null) case "${DISTRIBUTOR// /-}" in (Ubuntu) UBUNTU=1; AX_SUBST(UBUNTU);; @@ -243,6 +272,25 @@ AC_DEFUN([AX_INIT_STANDARD_PROJECT], [ else AM_CPPFLAGS="${AM_CPPFLAGS} -DQT_NO_DEBUG_OUTPUT -DQT_NO_DEBUG" fi + + AC_ARG_WITH(gcov, + [AS_HELP_STRING([--with-gcov=FILE], + [enable gcov, set gcov file (defaults to gcov)])], + [GCOV="$enableval"], [GCOV="no"]) + AM_CONDITIONAL(COVERAGE, test "$GCOV" != "no") + if test "$GCOV" != "no"; then + if test "$GCOV" == "yes"; then + GCOV=gcov + fi + AC_CHECK_PROG(has_gcov, [$GCOV], [yes], [no]) + if test "$has_gcov" != "yes"; then + AC_MSG_ERROR([gcov: program $GCOV not found]) + fi + AC_MSG_NOTICE([Coverage tests enabled, using ${GCOV}]); + AM_CXXFLAGS="${AM_CXXFLAGS:-} -O0 --coverage -fprofile-arcs -ftest-coverage" + AM_LDFLAGS="${AM_LDFLAGS} -O0 --coverage -fprofile-arcs" + AX_SUBST(GCOV) + fi if test -f ${PACKAGE_NAME}.desktop.in; then AC_CONFIG_FILES([${PACKAGE_NAME}.desktop]) @@ -274,7 +322,7 @@ EOF AC_DEFUN([AX_USE_CXX], [ m4_include(ax_cxx_compile_stdcxx_11.m4) AC_LANG(C++) - AX_CXX_COMPILE_STDCXX_11(noext, optional) + AX_CXX_COMPILE_STDCXX_14(noext, optional) AC_PROG_CXX AC_PROG_CPP @@ -312,6 +360,43 @@ maintainer-clean-cxx-targets: EOF ]) +# use this in configure.ac to support old school C +AC_DEFUN([AX_USE_C], [ + AC_LANG(C) + AC_PROG_CC + AC_PROG_CPP + + AC_CONFIG_FILES([src/makefile]) + + AM_CPPFLAGS+=' -I ${top_srcdir}/src -I ${top_builddir}/src -I ${srcdir} -I ${builddir}' + AM_LDFLAGS+=' -L ${top_srcdir}/src -L ${top_builddir}/src' + + # Get rid of those stupid -g -O2 options! + CXXFLAGS="${CXXFLAGS//-g -O2/}" + CFLAGS="${CFLAGS//-g -O2/}" + + # pass compile flags to make distcheck + AM_DISTCHECK_CONFIGURE_FLAGS="CFLAGS='${CFLAGS}' CPPFLAGS='${CPPFLAGS}' CFLAGS='${CFLAGS}' LDFLAGS='${LDFLAGS}'" + AC_SUBST(AM_DISTCHECK_CONFIGURE_FLAGS) + + AC_SUBST(AM_CFLAGS) + AC_SUBST(AM_CPPFLAGS) + AC_SUBST(AM_LDFLAGS) + AX_ADD_MAKEFILE_TARGET_DEP([maintainer-clean-am], [maintainer-clean-c-targets], [src/makefile.in]) + test -f src/makefile.in && cat >> src/makefile.in <> examples/makefile.in <> makefile.in <> doc/makefile.in <> doc/makefile.in <Perl Documentation

Perl Documentation

    " > perldoc/index.html + for p in \${PERL_SOURCES:%=perldoc/%}; do \ + echo '
  • '"\$\${p#perldoc/}"'
  • ' >> perldoc/index.html; \ + done + echo "
" >> perldoc/index.html + +perldoc/%: + pods2html --notoc --empty --index index @top_srcdir@/\${@:perldoc/%=%} \$[@] + +distclean-perldoc: + -rm -r perldoc +maintainer-clean-perldoc: + -rm makefile.in +install-data-perldoc: + test -d \$(DESTDIR)\${docdir} || mkdir -p \$(DESTDIR)\${docdir} + chmod -R u+w \$(DESTDIR)\${docdir} + cp -r perldoc \$(DESTDIR)\${docdir}/ +uninstall-perldoc: + -chmod -R u+w \$(DESTDIR)\${docdir} + -rm -rf \$(DESTDIR)\${docdir}/perldoc +#### End: $0 +EOF +]) + # require a specific package, with fallback: test for a header # - parameter: # $1 = unique id (no special characters) diff --git a/bootstrap.sh b/bootstrap.sh index ffee6de..f44efb9 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -23,17 +23,22 @@ docker=0 buildtarget="" overwrite=0 rebuild=0 +novcs=0 +excludevcs=() rebuildfiles=() while test $# -gt 0; do case "$1" in (--configure|-c) configure=1;; (--docker|-d) docker=1;; (--build|-b) configure=1; build=1; buildtarget+=" distcheck";; - (--target|-t) shift; configure=1; build=1; buildtarget+=" $1";; + (--all|-a) shift; configure=1; build=1; buildtarget+=" all";; (--clean) shift; configure=1; build=1; buildtarget+=" maintainer-clean";; + (--target|-t) shift; configure=1; build=1; buildtarget+=" $1";; (--overwrite|-o) overwrite=1;; (--rebuild|-r) rebuild=1;; (--rebuild-file|-f) shift; rebuildfiles+=("$1");; + (--no-vcs|-n) novcs=1;; + (--exclude-vcs|-x) shift; excludevcs+=("$1");; (--version|-v) echo "$Id$"; exit;; @@ -47,10 +52,14 @@ OPTIONS --configure, -c call ./configure after initialization --docker, -d build and run tests in a docker instance --build, -b build, also call ./configure && make distcheck + --all, -a same as -b, but make target all + --clean same as -b, but make target maintainer-clean --target, -t same as -b, but specify target instead of distcheck --overwrite, -o overwrite all basic files (bootstrap.sh, m4-macros) --rebuild, -r force rebuild of generated files, even if modified --rebuild-file, -f rebild specific file (can be added multiple times) + --no-vcs, -n do not automatically add files to version control + --exclude-vcs, -x exclude specific file from version control --help, -h show this help --version, -v show version and date of this file @@ -109,18 +118,22 @@ RUNNING If you run ${MY_NAME}, it first generates the necessary files (see below), then first runs make distclean if a makefile exists. After this it calles aclocal, libtoolize, automake, autoconf and - optionally ./configure. + optionally ./configure. If necessary, files are added to version + control. GENERATED FILES This script copies the following files into your project environment: * ${MY_NAME} + * autogen.sh - just the basics to initialize auto tools and create configure * ax_init_standard_project.m4 - auxiliary macro definition file * ax_cxx_compile_stdcxx_11.m4 - auxiliary macro definition file * ax_check_qt.m4 - auxiliary macro definition file + * makefile_test.inc.am - makefile to be included in tests * resolve-debbuilddeps.sh - script to install debian package dependencies * resolve-rpmbuilddeps.sh - script to install RPM package dependencies * build-in-docker.sh - script to build the project encapsulated in a docker container + * build-in-docker.conf - additional configuration for build-in-docker.sh * build-resource-file.sh - build resource.qrc file from a resource directory * sql-to-dot.sed - script to convert SQL schema files to graphviz dot in doxygen * mac-create-app-bundle.sh - script to create apple mac os-x app-bundle @@ -136,9 +149,14 @@ GENERATED FILES * src/version.cxx - if you enabled AX_USE_CXX * html/makefile.am - if you enabled AX_BUILD_HTML * scripts/makefile.am - if you enabled AX_USE_SCRIPTS - * doc/makefile.am - if you enabled AX_USE_DOXYGEN + * doc/makefile.am - if you enabled AX_USE_DOXYGEN or AX_USE_PERLDOC * doc/doxyfile.in - if you enabled AX_USE_DOXYGEN - * test/makefile.am - if you enabled AX_BUILD_TEST or AX_USE_CPPUNIT + * doc/header.html.in - if you enabled AX_USE_DOXYGEN + * doc/footer.html.in - if you enabled AX_USE_DOXYGEN + * doc/style.css - if you enabled AX_USE_DOXYGEN + * doc/plantuml.jar - if you enable AX_USE_DOXYGEN + * test/makefile.am - if you enabled AX_USE_CPPUNIT and AX_USE_CXX + * test/${DEFAULT_PROJECT_NAME#lib}.cxx - if you enabled AX_BUILD_TEST or AX_USE_CPPUNIT * examples/makefile.am - if you enabled AX_BUILD_EXAMPLES * debian/changelog.in - if you enabled AX_USE_DEBIAN_PACKAGING * debian/control.in - if you enabled AX_USE_DEBIAN_PACKAGING @@ -179,6 +197,7 @@ FILES TO EDIT * src/makefile.am * html/makefile.am * test/makefile.am + * test/${DEFAULT_PROJECT_NAME}.cxx * examples/makefile.am FILE DEPENDENCIES @@ -189,8 +208,8 @@ FILE DEPENDENCIES * test/makefile.am depends on AX_USE_LIBTOOL * html/makefile.am depends on AX_BUILD_HTML * doc/doxyfile.in depends on AX_BUILD_EXAMPLES - * debian/control.in depends on AX_USE_DOXYGEN, AX_USE_CPPUNIT - AX_CXX_QT, AX_CHECK_QT, AX_REQUIRE_QT, AX_USE_LIBTOOL + * debian/control.in depends on AX_USE_DOXYGEN, AX_USE_PERLDOC, + AX_USE_CPPUNIT AX_CXX_QT, AX_CHECK_QT, AX_REQUIRE_QT, AX_USE_LIBTOOL * debian/${DEFAULT_PROJECT_NAME}.install depends on AX_USE_LIBTOOL * debian/${DEFAULT_PROJECT_NAME}.dirs depends on AX_USE_LIBTOOL * debian/${DEFAULT_PROJECT_NAME}-dev.install depends on AX_USE_LIBTOOL @@ -224,6 +243,7 @@ FILES * Enable LibTool library creation: AX_USE_LIBTOOL * Enable Scripts: AX_USE_SCRIPTS * Enable Doxygen documentation generation: AX_USE_DOXYGEN + * Enable Perldoc documentation generation: AX_USE_PERLDOC * Enable Debian packaging by calling "make deb": AX_USE_DEBIAN_PACKAGING * Enable RPM packaging by calling "make rpm": AX_USE_RPM_PACKAGING * Enable C++ testing using CppUnit: AX_USE_CPPUNIT @@ -345,7 +365,7 @@ contains() { checkdir() { if ! test -d "$1"; then # create path run mkdir -p "$1" - if test -n "${VCS}"; then + if test -n "${VCS}" -a $novcs -eq 0 && ! contains "$1" "${excludevcs[@]}"; then run ${VCS} add "$1" fi fi @@ -360,13 +380,17 @@ checkfile() { } to() { + mode="u=rw,g=rw,o=r" while test $# -gt 0; do - mode="u=rw,g=rw,o=r" case "$1" in (--condition) shift # test for a tag, abort if not set if ! testtag "$1"; then return 0 fi;; + (--unless) shift # test for a tag, abort if set + if testtag "$1"; then + return 0 + fi;; (--mode) shift # test for a tag, abort if not set mode="$1";; (*) break;; @@ -392,9 +416,9 @@ to() { else echo -e " \e[32msuccess\e[0m" fi - chmod $mode $1 + run chmod $mode $1 if test $exists -eq 0; then - if test -n "${VCS}"; then + if test -n "${VCS}" -a $novcs -eq 0 && ! contains "$1" "${excludevcs[@]}"; then run ${VCS} add "$1" if test "${VCS}" = "svn"; then run svn propset svn:keywords "Id" "$1" @@ -417,9 +441,12 @@ copy() { source="${0%/*}/$1" fi fi + if test "${1%/*}" != "$1"; then + test -d "${1%/*}" || svn mkdir "${1%/*}" + fi run cp "${source}" "$1" if test $exists -eq 0; then - if test -n "${VCS}"; then + if test -n "${VCS}" -a $novcs -eq 0 && ! contains "$1" "${excludevcs[@]}"; then run ${VCS} add "$1" if test "${VCS}" = "svn"; then run svn propset svn:keywords "Id" "$1" @@ -452,23 +479,27 @@ doxyadd() { vcs2cl() { exists=0 - if test -f "Changelog"; then + if test -f "ChangeLog"; then exists=1 + else + touch "ChangeLog" fi - if test "${VCS}" = "git"; then - ${VCS}2cl > ChangeLog - elif test -n "${VCS}"; then - ${VCS}2cl + if test -x $(which ${VCS}2cl); then + if test "${VCS}" = "git"; then + ${VCS}2cl > ChangeLog + elif test -n "${VCS}"; then + ${VCS}2cl + fi fi if test $exists -eq 0; then - if test -n "${VCS}"; then + if test -n "${VCS}" -a $novcs -eq 0 && ! contains "ChangeLog" "${excludevcs[@]}"; then run ${VCS} add ChangeLog fi fi } # Check if we are in subversion root, if so, create trunk, branches, tags: -if test "${VCS}" = "svn"; then +if test "${VCS}" = "svn" -a $novcs -eq 0; then if test "$(LANG= svn info | sed -n 's,Relative URL: *,,p')" = "^/"; then svn mkdir trunk branches tags cd trunk @@ -480,6 +511,7 @@ copy ${MY_NAME} copy ax_init_standard_project.m4 copy ax_cxx_compile_stdcxx_11.m4 copy ax_check_qt.m4 +copy makefile_test.inc.am copy resolve-debbuilddeps.sh copy resolve-rpmbuilddeps.sh copy build-in-docker.sh @@ -502,7 +534,11 @@ ${DEFAULT_PROJECT_NAME} add description for ${DEFAULT_PROJECT_NAME} EOF to configure.ac < #include @@ -652,7 +710,8 @@ class ${PackageName}: public QMainWindow, protected Ui::${PackageName} { #endif EOF - to --condition AX_USE_CXX src/${PACKAGE_NAME}.ui < ${PackageName} @@ -734,7 +793,7 @@ EOF to --condition AX_USE_CXX src/version.cxx < +#include +#include +#include +#include +#include + +/// @todo Rename DummyTest and DummyTest::dummy() +/// @todo Write test cases +class DummyTest: public CppUnit::TestFixture { + public: + void dummy() { + } + CPPUNIT_TEST_SUITE(DummyTest); + CPPUNIT_TEST(dummy); + CPPUNIT_TEST_SUITE_END(); +}; +CPPUNIT_TEST_SUITE_REGISTRATION(DummyTest); + +int main(int argc, char** argv) try { + std::ofstream ofs((*argv+std::string(".xml")).c_str()); + CppUnit::TextUi::TestRunner runner; + runner.setOutputter(new CppUnit::XmlOutputter(&runner.result(), ofs)); + runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest()); + return runner.run() ? 0 : 1; + } catch (std::exception& e) { + std::cerr<<"***Exception: "< + + + + + + +\$projectname: \$title +\$title + + + +\$treeview +\$search +\$mathjax + +\$extrastylesheet + + +
+ +
+
+ +
\$projectname \$projectnumber
+
\$projectbrief
+
+ +
+EOF +to --condition AX_USE_DOXYGEN doc/footer.html.in < + +
+ + +EOF +to --condition AX_USE_DOXYGEN doc/style.css <@AUTHOR_NAME@\\n"' doxyadd ALIASES '"license=\\par License\\n"' doxyadd ALIASES '"classmutex=\\par Reentrant:\\nAccess is locked with class static mutex @c "' doxyadd ALIASES '"instancemutex=\\par Reentrant:\\nAccess is locked with per instance mutex @c "' doxyadd ALIASES '"mutex=\\par Reentrant:\\nAccess is locked with mutex @c "' doxyadd ALIASES '"api=\\xrefitem api \\"API Call\\" \\"\\""' + doxyadd ALIASES '"description=@DESCRIPTION@"' + doxyadd ALIASES '"readme=@README_HTML@"' + doxyadd ALIASES '"author=@AUTHOR_NAME@"' + doxyreplace PLANTUML_JAR_PATH '"@top_srcdir@/doc/plantuml.jar"' doxyreplace ENABLE_PREPROCESSING YES doxyreplace MACRO_EXPANSION YES doxyadd PREDEFINED '"NAMESPACE=@PACKAGE_NAME@"' @@ -866,9 +1052,20 @@ if testtag AX_USE_DOXYGEN; then if testtag AX_BUILD_TEST AX_USE_CPPUNIT; then doxyadd INPUT "@top_srcdir@/test" fi + if testtag AX_USE_NODEJS; then + doxyadd INPUT "@top_srcdir@/nodejs" + doxyadd EXCLUDE "@top_srcdir@/nodejs/node_modules" + doxyadd EXCLUDE "@top_srcdir@/nodejs/public/javascripts/ext" + fi doxyreplace FILE_PATTERNS '*.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.idl *.ddl *.odl *.h *.hh *.hxx *.hpp *.h++ *.cs *.d *.php *.php4 *.php5 *.phtml *.inc *.m *.markdown *.md *.mm *.dox *.py *.f90 *.f *.for *.tcl *.vhd *.vhdl *.ucf *.qsf *.as *.js *.wt *.sql' doxyreplace RECURSIVE YES - doxyreplace EXCLUDE_PATTERNS "moc_* uic_* qrc_*" + doxyreplace EXCLUDE_PATTERNS "moc_* uic_* qrc_* version.[ch]xx" + doxyreplace HTML_HEADER header.html + doxyreplace HTML_FOOTER footer.html + doxyreplace HTML_EXTRA_STYLESHEET style.css + doxyreplace HTML_DYNAMIC_SECTIONS YES + doxyreplace DISABLE_INDEX NO + doxyreplace GENERATE_TREEVIEW YES if testtag AX_BUILD_EXAMPLES; then doxyreplace EXAMPLE_PATH @top_srcdir@/examples fi @@ -877,7 +1074,6 @@ if testtag AX_USE_DOXYGEN; then doxyreplace SOURCE_BROWSER YES doxyreplace INLINE_SOURCES YES doxyreplace GENERATE_TESTLIST YES - doxyreplace GENERATE_TREEVIEW NO doxyreplace SEARCHENGINE NO doxyreplace GENERATE_HTML YES doxyreplace GENERATE_LATEX NO @@ -904,7 +1100,7 @@ if testtag AX_USE_DEBIAN_PACKAGING; then -- @AUTHOR@ @BUILD_DATE@ EOF - BUILD_DEPENDS="debhelper, ${VCSDEPENDS} pkg-config, automake, libtool, autotools-dev, lsb-release $(if testtag AX_USE_DOXYGEN; then echo -n ", doxygen, graphviz, mscgen"; fi; if testtag AX_USE_CPPUNIT; then echo -n ", libcppunit-dev"; fi; if testtag AX_CXX_QT || testtag AX_CHECK_QT AX_REQUIRE_QT; then echo -n ", qt5-default | libqt4-core | libqtcore4, qt5-qmake | qt4-qmake, qtbase5-dev | libqt4-dev, qtbase5-dev-tools | qt4-dev-tools, qttools5-dev-tools | qt4-dev-tools, qttools5-dev-tools | qt4-dev-tools"; fi)" + BUILD_DEPENDS="debhelper, ${VCSDEPENDS} pkg-config, automake, libtool, autotools-dev, pandoc, lsb-release $(if testtag AX_USE_DOXYGEN; then echo -n ", doxygen, graphviz, mscgen"; fi; if testtag AX_USE_PERLDOC; then echo -n ", libpod-tree-perl"; fi; if testtag AX_USE_PLANTUML; then echo -n ", default-jre-headless|default-jre"; fi; if testtag AX_USE_CPPUNIT; then echo -n ", libcppunit-dev"; fi; if testtag AX_CXX_QT || testtag AX_CHECK_QT AX_REQUIRE_QT; then echo -n ", qt5-default | libqt4-core | libqtcore4, qt5-qmake | qt4-qmake, qtbase5-dev | libqt4-dev, qtbase5-dev-tools | qt4-dev-tools, qttools5-dev-tools | qt4-dev-tools, qttools5-dev-tools | qt4-dev-tools"; fi)" to debian/control.in < ChangeLog";; +esac) +fi +aclocal +$(if testtag AX_USE_LIBTOOL; then echo libtoolize --force; fi) +automake -a +autoconf +EOF to makefile.am< mode: apt or yum, default: ${mode}" echo " -i, --image use given docker image instead of ${img}" echo " -t, --targets targets specify build targets, default: ${targets}" echo " -r, --repo add given apt repository" @@ -34,6 +36,8 @@ while test $# -gt 0; do echo " -c, --cmd execute commands as root in docker" echo " -w, --wait on error keep docker container and wait for enter" echo + echo " The option -i must be after -m, because mode sets a new default image" + echo echo " The options -r -k -e -d -p -c can be repeated several times." echo echo " The options -r -p -c allow an if-then-else contruct" @@ -60,6 +64,17 @@ while test $# -gt 0; do echo exit 0 ;; + (-m|--mode) shift; + mode="$1" + case "$mode" in + (apt) img="ubuntu:latest";; + (yum) img="centos:latest";; + (*) + echo "**** ERROR: unknown mode '$1', try --help" 1>&2 + exit 1 + ;; + esac + ;; (-i|--image) shift; img="$1" ;; @@ -167,34 +182,56 @@ function ifthenelse() { set -x -OPTIONS='-o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confnew -y --force-yes --no-install-suggests --no-install-recommends' docker pull $img -DOCKER_ID=$(docker run -d ${dirs[@]} ${envs[@]} -w /workdir $img sleep infinity) +DOCKER_ID=$(docker run -d ${dirs[@]} ${envs[@]} -e HOME="${HOME}" -w /workdir $img sleep infinity) trap 'traperror '"${DOCKER_ID}"' "$? ${PIPESTATUS[@]}" $LINENO $BASH_LINENO "$BASH_COMMAND" "${FUNCNAME[@]}" "${FUNCTION}"' SIGINT INT TERM EXIT -for f in 'libpam-systemd:amd64' 'policykit*' 'colord'; do - docker exec -it ${DOCKER_ID} bash -c "echo 'Package: $f' >> /etc/apt/preferences" - docker exec -it ${DOCKER_ID} bash -c "echo 'Pin-Priority: -100' >> /etc/apt/preferences" - docker exec -it ${DOCKER_ID} bash -c "echo >> /etc/apt/preferences" -done -docker exec ${DOCKER_ID} apt-get update ${OPTIONS} -docker exec ${DOCKER_ID} apt-get upgrade ${OPTIONS} -docker exec ${DOCKER_ID} apt-get install ${OPTIONS} python-software-properties software-properties-common apt-transport-https dpkg-dev lsb-release || \ - docker exec ${DOCKER_ID} apt-get install ${OPTIONS} software-properties-common apt-transport-https dpkg-dev lsb-release || \ - docker exec ${DOCKER_ID} apt-get install ${OPTIONS} python-software-properties apt-transport-https dpkg-dev lsb-release; -for repo in "${repos[@]}"; do - ifthenelse "${repo}" "apt-add-repository ARG" -done -for key in "${keys[@]}"; do - wget -O- "$key" \ - | docker exec -i ${DOCKER_ID} apt-key add - -done -docker exec ${DOCKER_ID} apt-get update ${OPTIONS} -for package in "${packages[@]}"; do - ifthenelse "${package}" "apt-get install ${OPTIONS} ARG" -done -for command in "${commands[@]}"; do - ifthenelse "${command}" "ARG" -done -docker exec ${DOCKER_ID} ./resolve-debbuilddeps.sh -docker exec -u $(id -u) ${DOCKER_ID} test -d .svn && svn upgrade || true -docker exec -u $(id -u) ${DOCKER_ID} ./bootstrap.sh -t "${targets}" +case $mode in + (apt) + OPTIONS='-o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confnew -y --force-yes --no-install-suggests --no-install-recommends' + for f in 'libpam-systemd:amd64' 'policykit*' 'colord'; do + docker exec ${DOCKER_ID} bash -c "echo 'Package: $f' >> /etc/apt/preferences" + docker exec ${DOCKER_ID} bash -c "echo 'Pin-Priority: -100' >> /etc/apt/preferences" + docker exec ${DOCKER_ID} bash -c "echo >> /etc/apt/preferences" + done + docker exec ${DOCKER_ID} apt-get update ${OPTIONS} + docker exec ${DOCKER_ID} apt-get upgrade ${OPTIONS} + docker exec ${DOCKER_ID} apt-get install ${OPTIONS} python-software-properties software-properties-common apt-transport-https dpkg-dev lsb-release || \ + docker exec ${DOCKER_ID} apt-get install ${OPTIONS} software-properties-common apt-transport-https dpkg-dev lsb-release || \ + docker exec ${DOCKER_ID} apt-get install ${OPTIONS} python-software-properties apt-transport-https dpkg-dev lsb-release; + for repo in "${repos[@]}"; do + ifthenelse "${repo}" "apt-add-repository ARG" + done + for key in "${keys[@]}"; do + wget -O- "$key" \ + | docker exec -i ${DOCKER_ID} apt-key add - + done + docker exec ${DOCKER_ID} apt-get update ${OPTIONS} + for package in "${packages[@]}"; do + ifthenelse "${package}" "apt-get install ${OPTIONS} ARG" + done + for command in "${commands[@]}"; do + ifthenelse "${command}" "ARG" + done + docker exec ${DOCKER_ID} ./resolve-debbuilddeps.sh + ;; + (yum) + ./bootstrap.sh -t dist + if [[ "$img" =~ "centos" ]]; then + docker exec ${DOCKER_ID} yum install -y redhat-lsb + docker exec -i ${DOCKER_ID} bash -c 'cat > /etc/yum.repos.d/wandisco-svn.repo' <> /etc/yum.repos.d/wandisco-svn.repo' + docker exec -i ${DOCKER_ID} bash -c 'cat >> /etc/yum.repos.d/wandisco-svn.repo' < + + + + diff --git a/doc/header.html.in b/doc/header.html.in new file mode 100644 index 0000000..311876f --- /dev/null +++ b/doc/header.html.in @@ -0,0 +1,33 @@ + + + + + + + +$projectname: $title +$title + + + +$treeview +$search +$mathjax + +$extrastylesheet + + +
+ +
+
+ +
$projectname $projectnumber
+
$projectbrief
+
+ +
diff --git a/doc/plantuml.jar b/doc/plantuml.jar new file mode 100644 index 0000000..9502c23 Binary files /dev/null and b/doc/plantuml.jar differ diff --git a/doc/style.css b/doc/style.css new file mode 100644 index 0000000..303c151 --- /dev/null +++ b/doc/style.css @@ -0,0 +1,38 @@ +#titlearea { + display: flex; + justify-content: space-between; + align-items: flex-begin; +} +#titlearea nav { + padding: 0; + margin: 0; +} +#titlearea nav a { + background-color: lightgray; + border: 1px solid gray; + color: black; + padding: 1ex; + margin: 0; +} +img, object { + max-width: 100% !important; +} +@media (max-width: 50em) { + #navrow1, #navrow2 { + display: block + } + #side-nav, #splitbar, .ui-resizable-handle ui-resizable-e, .ui-resizable-handle ui-resizable-s { + display: none; + } + #doc-content { + margin-left: 0 !important; + } +} +@media (min-width: 50em) { + #navrow1, #navrow2 { + display: none; + } + #side-nav, #splitbar, .ui-resizable-handle ui-resizable-e, .ui-resizable-handle ui-resizable-s { + display: block + } +} diff --git a/makefile_test.inc.am b/makefile_test.inc.am new file mode 100644 index 0000000..baf81c4 --- /dev/null +++ b/makefile_test.inc.am @@ -0,0 +1,9 @@ +## @id $Id$ + +## 1 2 3 4 5 6 7 8 +## 45678901234567890123456789012345678901234567890123456789012345678901234567890 + +%.gcda: % + gcov $< + +CLEANFILES += ${CLEANFILES} ${TEST:%=%.gcno} ${TEST:%=%.gcda} *.gcov diff --git a/nodejs/authentication/index.js b/nodejs/authentication/index.js index e172023..62ca5b8 100644 --- a/nodejs/authentication/index.js +++ b/nodejs/authentication/index.js @@ -1,69 +1,49 @@ module.exports = function(config) { - const crypto = require('crypto'); - const password = crypto.randomBytes(256); - var cookie = require('cookie-encryption'); - // const cipher = crypto.createCipher('aes256', password); - // const decipher = crypto.createDecipher('aes256', password); - // var encrypted = cipher.update(JSON.stringify(user), 'utf8', 'base64') - // + cipher.final('base64'); - // console.log("encrypted", encrypted); - // var decrypted = decipher.update(encrypted, 'base64', 'utf8') + decipher.final('utf8'); - // console.log("decrypted", decrypted); - - var authentication = function (req, res, next) { - return next(); - } - - if (config) { - - var cipher = config.cookies && config.cookies.cipher ? config.cookies.cipher : "aes256"; + authentication = function (username, password, success, fail) { - authentication = function (req, res, next) { - - function unauthorized(res) { - res.setHeader('WWW-Authenticate', 'Basic realm=Authorization Required'); - res.status(401).send('Not logged in. Login'); - }; - - var user = require('basic-auth')(req); - var vault = cookie('credentials'); - - if (!user || !user.name || !user.pass) { - return unauthorized(res); - }; - - if (config.passwords && config.passwords[user.name]) { - if (crypto.getHashes().indexOf(config.passwords[user.name][0])>=0) { - if (crypto.createHash(config.passwords[user.name][0]) - .update(user.pass, 'utf8').digest('hex') === config.passwords[user.name][1]) { - return next(); + if (config) { + const crypto = require('crypto'); + if (config.passwords && config.passwords[username]) { + if (crypto.getHashes().indexOf(config.passwords[username][0])>=0) { + if (crypto.createHash(config.passwords[username][0]).update(password, 'utf8').digest('hex') === config.passwords[username][1]) { + success(); + return; + } else { + fail(); + return; } } else { console.log("**** HASH NOT FOUND ****"); - console.log(config.passwords[user.name][0]); + console.log(config.passwords[username][0]); console.log(crypto.getHashes()); + fail(); + return; } } if (config.ldap) try { var LdapAuth = require('ldapauth'); var auth = new LdapAuth(config.ldap); - auth.authenticate(user.name, user.pass, function(err, usr) { + auth.authenticate(username, password, function(err, usr) { auth.close(function(err) {}) if (err) { console.log("**** ERROR: LDAP Authentication failed:", err); - return unauthorized(res); + fail(); + return; } console.log("**** SUCCESS: LDAP Authentication:"); - return next(); + success(); + return; }); return; // need to block here! } catch (e) { console.log("**** Error: LDAP failed: ", e, e.stack); + fail(); + return; } - return unauthorized(res); - }; - + } + fail(); + return; } return authentication; diff --git a/nodejs/etc/servicedock.json b/nodejs/etc/servicedock.json index c9ac713..45ea196 100644 --- a/nodejs/etc/servicedock.json +++ b/nodejs/etc/servicedock.json @@ -1,9 +1,6 @@ { "port": 8888, "restrict": { - "cookies": { - "cipher": "aes256" - }, "passwords": { "marc": ["sha256", "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"], "foo": ["sha256", "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9"] diff --git a/nodejs/package.json.in b/nodejs/package.json.in index 7419d3b..641e521 100644 --- a/nodejs/package.json.in +++ b/nodejs/package.json.in @@ -1,33 +1,31 @@ { - "name": "@PACKAGE_NAME@", - "version": "@PACKAGE_VERSION@", - "private": true, - "description": "Docker as a Service", - "main": "servicedock.js", - "devDependencies": {}, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Marc Wäckerlin", - "license": "LGPL3", - "path": { - "prefix": "@PREFIX@", - "sysconf": "@SYSCONFDIR@", - "pkgdata": "@PKGDATADIR@", - "localstate": "@LOCALSTATEDIR@", - "log": "@LOCALSTATEDIR@/log/@PACKAGE_NAME@.log", - "config": "@SYSCONFDIR@/@PACKAGE_NAME@.json", - "nodejs": "@PKGDATADIR@/nodejs" - }, - "dependencies": { - "express": "~2.5.8", - "stylus": "~0.53.0", - "ejs": ">= 0.0.1", - "socket.io": "~1.4.4", - "pty.js": "~0.3.0", - "async": "~1.5.2", - "basic-auth": "~1.0.3", - "ldapauth": "~2.2.4", - "cookie-encryption": "~1.4.2" - } + "name": "@PACKAGE_NAME@", + "version": "@PACKAGE_VERSION@", + "private": true, + "dependencies": { + "express": "~2.5.8", + "stylus": "~0.53.0", + "ejs": ">= 0.0.1", + "socket.io": "~1.4.4", + "pty.js": "~0.3.0", + "async": "~1.5.2", + "socketio-auth": "0.0.5" + }, + "description": "@DESCRIPTION@", + "main": "@PACKAGE_NAME@.js", + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "@AUTHOR@", + "license": "@LICENSE@", + "path": { + "prefix": "@PREFIX@", + "sysconf": "@SYSCONFDIR@", + "pkgdata": "@PKGDATADIR@", + "localstate": "@LOCALSTATEDIR@", + "log": "@LOCALSTATEDIR@/log/@PACKAGE_NAME@.log", + "config": "@SYSCONFDIR@/@PACKAGE_NAME@.json", + "nodejs": "@PKGDATADIR@/nodejs" + } } diff --git a/nodejs/public/javascripts/servicedock.js b/nodejs/public/javascripts/servicedock.js index f6ff27c..f6f2d03 100644 --- a/nodejs/public/javascripts/servicedock.js +++ b/nodejs/public/javascripts/servicedock.js @@ -1,10 +1,10 @@ /*! @file - @id $Id$ + @id $Id$ - This is the main application as it is fully run in the user's browser. + This is the main application as it is fully run in the user's browser. -*/ + */ // 1 2 3 4 5 6 7 8 // 45678901234567890123456789012345678901234567890123456789012345678901234567890 @@ -14,225 +14,247 @@ var focused = null; var docker = new Docker(); function htmlenc(html) { - return $('
').text(html).html(); + return $('
').text(html).html(); } function htmldec(data) { - return $('
').html(data).text(); + return $('
').html(data).text(); } /// Show error messsage /** Fades in an error message and logs to console. - @param data (optional) The error can be a string or any structure. - Strings are shown to the user, structures are logged only. - @param stay (optional) If not given as @c true, reloads page after 5s. */ + @param data (optional) The error can be a string or any structure. + Strings are shown to the user, structures are logged only. + @param stay (optional) If not given as @c true, reloads page after 5s. */ function error(data) { - $("#status").fadeOut("slow", function() { - $("#status").addClass("error") - $("#status").removeClass("notice") - $("#status").removeClass("success") - if (data) { - if (typeof data == 'string') { - $("#status").html(data); - console.log("error: "+data); - } else { - $("#status").html('unknown error: '+JSON.stringify(data)); - console.log("error: ", data); - console.log((new Error('stacktrace'))); - } - } else { - $("#status").html('error'); - console.log("error"); - } - $("#status").fadeIn("slow"); - }); + $("#status").fadeOut("slow", function() { + $("#status").addClass("error") + $("#status").removeClass("notice") + $("#status").removeClass("success") + if (data) { + if (typeof data == 'string') { + $("#status").html(data); + console.log("error: "+data); + } else { + $("#status").html('unknown error: '+JSON.stringify(data)); + console.log("error: ", data); + console.log((new Error('stacktrace'))); + } + } else { + $("#status").html('error'); + console.log("error"); + } + $("#status").fadeIn("slow"); + }); } /// Show notice messsage /** Fades in an notice message and logs to console. - @param text (optional) The data is a string. */ + @param text (optional) The data is a string. */ function notice(text) { - $("#status").fadeOut("slow", function() { - $("#status").addClass("notice") - $("#status").removeClass("error") - $("#status").removeClass("success") - if (text) { - $("#status").html(text); - console.log("notice: "+text); - } else { - $("#status").html(''); - console.log("notice"); - } - $("#status").fadeIn("slow"); - }); + $("#status").fadeOut("slow", function() { + $("#status").addClass("notice") + $("#status").removeClass("error") + $("#status").removeClass("success") + if (text) { + $("#status").html(text); + console.log("notice: "+text); + } else { + $("#status").html(''); + console.log("notice"); + } + $("#status").fadeIn("slow"); + }); } /// Show notice messsage /** Fades in an success message and logs to console. - @param text (optional) The data is a string. */ + @param text (optional) The data is a string. */ function success(text) { - $("#status").fadeOut("slow", function() { - $("#status").addClass("success") - $("#status").removeClass("error") - $("#status").removeClass("notice") - if (text) { - $("#status").html(text); - console.log("success: "+text); - } else { - $("#status").html(''); - console.log("success"); - } - $("#status").fadeIn("slow"); - }); + $("#status").fadeOut("slow", function() { + $("#status").addClass("success") + $("#status").removeClass("error") + $("#status").removeClass("notice") + if (text) { + $("#status").html(text); + console.log("success: "+text); + } else { + $("#status").html(''); + console.log("success"); + } + $("#status").fadeIn("slow"); + }); } /// Show status message in the main screen area /** @param text Text is a message or some complex HTML from the server. - @param msg The success message text */ + @param msg The success message text */ function status(text, msg) { - $("#main").hide(); - $("#main").html(text); - if (msg) success(msg); - else setTimeout("$('#status').fadeOut('slow')", 5000); - zoom(0); - stats(); - $("#main").show(); - $("form input:first-child").focus(); - docker.containers.contextmenu("#main"); + $("#main").hide(); + $("#main").html(text); + if (msg) success(msg); + else setTimeout("$('#status').fadeOut('slow')", 5000); + zoom(0); + $("#main").show(); + $("form input:first-child").focus(); + docker.containers.contextmenu("#main"); } function emit(signal, data) { - console.log("<-snd "+signal, data); - socket.emit(signal, data); -} - -function connected() { - console.log("server connected"); - $("#connectionstatus #bad").hide(); - $("#connectionstatus #good").show(); - success("server connected"); + console.log("<-snd "+signal, data); + socket.emit(signal, data); +} + +function connect() { + $("#server").html($("#username").val()+'@'+window.location.hostname) + console.log("server connect"); + $("#connectionstatus #bad").hide(); + $("#connectionstatus #authentication").show(); + $("#connectionstatus #good").hide(); + success("login to server"); + socket.emit('authentication', { + username: $("#username").val(), + password: $("#password").val() + }); +} + +function authenticated() { + $("#server").html($("#username").val()+'@'+window.location.hostname) + console.log("server authenticated"); + $("#connectionstatus #bad").hide(); + $("#connectionstatus #authentication").hide(); + $("#connectionstatus #good").show(); + success("server connected"); + start(); +} + +function unauthorized() { + $("#server").html($("#username").val()+'@'+window.location.hostname) + console.log("authentication failed"); + $("#connectionstatus #good").hide(); + $("#connectionstatus #authentication").hide(); + $("#connectionstatus #bad").show(); + error("authentication failed", true); } function disconnected() { - console.log("server disconnected"); - $("#connectionstatus #good").hide(); - $("#connectionstatus #bad").show(); - error("server disconnected", true); -} - -function connectionstatus() { - if (socket.connected) connected(); else disconnected(); + $("#server").html($("#username").val()+'@'+window.location.hostname) + console.log("server disconnected"); + $("#connectionstatus #good").hide(); + $("#connectionstatus #authentication").hide(); + $("#connectionstatus #bad").show(); + error("server disconnected", true); } /// Toggle Menu Display function togglemenu() { - $("#menu").toggle(); + $("#menu").toggle(); } /// Upload a configuration and send it to server function upload(evt) { - if (!window.FileReader) - return error("your browser does not support file upload", true); - for (var i=0, f; f=evt.target.files[i]; ++i) { - var file = f; - var reader = new FileReader(); - reader.onload = function(evt) { - if (evt.target.error) return error("error reading file", true); - if (evt.target.readyState==0) return notice("waiting for data …"); - if (evt.target.readyState==1) return notice("loading data …"); - tobecreated = JSON.parse(evt.target.result); - showCreate(); - } - reader.readAsText(file); + if (!window.FileReader) + return error("your browser does not support file upload", true); + for (var i=0, f; f=evt.target.files[i]; ++i) { + var file = f; + var reader = new FileReader(); + reader.onload = function(evt) { + if (evt.target.error) return error("error reading file", true); + if (evt.target.readyState==0) return notice("waiting for data …"); + if (evt.target.readyState==1) return notice("loading data …"); + tobecreated = JSON.parse(evt.target.result); + showCreate(); } + reader.readAsText(file); + } } var tobecreated = {}; function previewCreate() { - var name = $('#name').val(); - var nodes = Object.create(tobecreated); - if (name != '') { - nodes[name] = { - status: docker.containers.Status.Preview, - id: null, - name: name, - image: { - name: $('#image').val(), - id: null - }, - ports: $('#createports option').map(function() {return $(this).data();}).get(), - env: $('#createvars option').map(function() {return $(this).val();}).get(), - volumes: $('#createvolumes option').map(function() {return $(this).data();}).get(), - volumesfrom: $('#createvolumefroms option').map(function() {return $(this).val();}).get(), - links: $('#createlinks option').map(function() {return $(this).data();}).get(), - entrypoint: $('#createentrypoints option').map(function() {return $(this).val();}).get(), - cmd: $('#createcommands option').map(function() {return $(this).val();}).get(), - }; - $('#doappend').unbind().click(function() { - tobecreated[name] = Object.create(nodes[name]); - tobecreated[name].status = docker.containers.Status.Prepared; - $('#createpreview').append(''); - $('#create form input[type=text]').val(''); - $('#create form select.collect option').remove(); - previewCreate(); - }); - $('#doremove').unbind().click(function() { - $(this).siblings('#createpreview').children('option:selected').each(function(a,b,c) { - delete tobecreated[b.innerHTML]; - }).remove(); - previewCreate(); - }); - $('#dosend').unbind().click(function() { - if (Object.keys(tobecreated).length>0) { - emit("create", docker.containers.configuration(tobecreated)); - tobecreated = {}; - showImage(); - } - }); - } - if ($('#image').val()!='') { - var img = docker.images.get($('#image').val()); - if (img) { - $('#portintdata').empty().append(img.ports.map(function(i) { - var option = document.createElement('option'); - option.value = i.replace(/\/.*/g, ''); - return option; - })); - $('#varnamedata').empty().append(img.env.map(function(i) { - var option = document.createElement('option'); - option.value = i.replace(/=.*/g, ''); - return option; - })); - $('#varvaluedata').empty().append(img.env.map(function(i) { - var option = document.createElement('option'); - option.value = i.replace(/^[^=]*=/g, ''); - return option; - })); - $('#volumeextdata').empty(); - $('#volumeintdata').empty(); - for (i in img.volumes) { - var option = document.createElement('option'); - option.value = i; - $('#volumeextdata').append(option); - $('#volumeintdata').append(option); - } - $('#volumesfromdata').empty().append(docker.containers.names(tobecreated).map(function(i) { - var option = document.createElement('option'); - option.value = i; - return option; - })); - $('#linkcontainerdata').empty().append(docker.containers.names(tobecreated).map(function(i) { - var option = document.createElement('option'); - option.value = i; - return option; - })); - } + var name = $('#name').val(); + var nodes = Object.create(tobecreated); + if (name != '') { + nodes[name] = { + status: docker.containers.Status.Preview, + id: null, + name: name, + image: { + name: $('#image').val(), + id: null + }, + ports: $('#createports option').map(function() {return $(this).data();}).get(), + env: $('#createvars option').map(function() {return $(this).val();}).get(), + volumes: $('#createvolumes option').map(function() {return $(this).data();}).get(), + volumesfrom: $('#createvolumefroms option').map(function() {return $(this).val();}).get(), + links: $('#createlinks option').map(function() {return $(this).data();}).get(), + entrypoint: $('#createentrypoints option').map(function() {return $(this).val();}).get(), + cmd: $('#createcommands option').map(function() {return $(this).val();}).get(), + }; + $('#doappend').unbind().click(function() { + tobecreated[name] = Object.create(nodes[name]); + tobecreated[name].status = docker.containers.Status.Prepared; + $('#createpreview').append(''); + $('#create form input[type=text]').val(''); + $('#create form select.collect option').remove(); + previewCreate(); + }); + $('#doremove').unbind().click(function() { + $(this).siblings('#createpreview').children('option:selected').each(function(a,b,c) { + delete tobecreated[b.innerHTML]; + }).remove(); + previewCreate(); + }); + $('#dosend').unbind().click(function() { + if (Object.keys(tobecreated).length>0) { + emit("create", docker.containers.configuration(tobecreated)); + tobecreated = {}; + showImage(); + } + }); + } + if ($('#image').val()!='') { + var img = docker.images.get($('#image').val()); + if (img) { + $('#portintdata').empty().append(img.ports.map(function(i) { + var option = document.createElement('option'); + option.value = i.replace(/\/.*/g, ''); + return option; + })); + $('#varnamedata').empty().append(img.env.map(function(i) { + var option = document.createElement('option'); + option.value = i.replace(/=.*/g, ''); + return option; + })); + $('#varvaluedata').empty().append(img.env.map(function(i) { + var option = document.createElement('option'); + option.value = i.replace(/^[^=]*=/g, ''); + return option; + })); + $('#volumeextdata').empty(); + $('#volumeintdata').empty(); + for (i in img.volumes) { + var option = document.createElement('option'); + option.value = i; + $('#volumeextdata').append(option); + $('#volumeintdata').append(option); + } + $('#volumesfromdata').empty().append(docker.containers.names(tobecreated).map(function(i) { + var option = document.createElement('option'); + option.value = i; + return option; + })); + $('#linkcontainerdata').empty().append(docker.containers.names(tobecreated).map(function(i) { + var option = document.createElement('option'); + option.value = i; + return option; + })); } - if (name=='' && nodes) for (name in nodes) break; - if (name && name!='') $('#preview').html(Viz("digraph {\n"+" rankdir="+rankdir+";\n" - +docker.containers.subgraph(name, nodes) - +"\n}")); - else $('#preview').html(''); + } + if (name=='' && nodes) for (name in nodes) break; + if (name && name!='') $('#preview').html(Viz("digraph {\n"+" rankdir="+rankdir+";\n" + +docker.containers.subgraph(name, nodes) + +"\n}")); + else $('#preview').html(''); } function create() { @@ -240,389 +262,401 @@ function create() { var zoomlevel = 0; function zoom(incr = 0) { - zoomlevel = (zoomlevel+incr)%2; - switch (zoomlevel) { - case 0: { - $("#main svg, #preview svg").css("width", "auto"); - $("#main svg, #preview svg").css("height", "auto"); - $("#main svg, #preview svg").css("max-width", "100%"); - $("#main svg, #preview svg").css("max-height", "100%"); - } break; - case 1: { - $("#main svg, #preview svg").css("width", "100%"); - $("#main svg, #preview svg").css("height", "auto"); - $("#main svg, #preview svg").css("max-width", "100%"); - $("#main svg, #preview svg").css("max-height", "none"); - } break; - case 2: { - $("#main.svg, #preview svg").css("width", "auto"); - $("#main.svg, #preview svg").css("height", "100%"); - $("#main.svg, #preview svg").css("max-width", "none"); - $("#main.svg, #preview svg").css("max-height", "100%"); - } break; - } + zoomlevel = (zoomlevel+incr)%2; + switch (zoomlevel) { + case 0: { + $("#main svg, #preview svg").css("width", "auto"); + $("#main svg, #preview svg").css("height", "auto"); + $("#main svg, #preview svg").css("max-width", "100%"); + $("#main svg, #preview svg").css("max-height", "100%"); + } break; + case 1: { + $("#main svg, #preview svg").css("width", "100%"); + $("#main svg, #preview svg").css("height", "auto"); + $("#main svg, #preview svg").css("max-width", "100%"); + $("#main svg, #preview svg").css("max-height", "none"); + } break; + case 2: { + $("#main.svg, #preview svg").css("width", "auto"); + $("#main.svg, #preview svg").css("height", "100%"); + $("#main.svg, #preview svg").css("max-width", "none"); + $("#main.svg, #preview svg").css("max-height", "100%"); + } break; + } } var viz = null; var vizmore = null; var rankdir = "LR"; function rotateviz() { - if (!viz) return; - if (rankdir == "LR") - rankdir = "TB"; - else - rankdir = "LR"; - showviz(); - previewCreate(); + if (!viz) return; + if (rankdir == "LR") + rankdir = "TB"; + else + rankdir = "LR"; + showviz(); + previewCreate(); } function showviz(vizpath, more) { - if (!vizpath) { - vizpath = viz; - more = vizmore; - } else { - viz = vizpath; - vizmore = more; - } - res = "digraph {\n"+" rankdir="+rankdir+";\n"+viz+"\n}"; - try { - status(more?Viz(res)+more:Viz(res)); - $('#main a > ellipse + text').attr('font-size', '12'); - $('#main a > ellipse + text + text') - .attr('font-weight', 'bold') - .attr('font-size', '16') - .each(function() {$(this).attr('y', parseFloat($(this).attr('y'))+1.0)}); - $('#main a > ellipse + text + text + text').attr('font-size', '12'); - } catch(e) { - (res = res.split("\n")).forEach(function(v, i, a) { - a[i] = ("000"+(i+1)).slice(-3)+": "+v; - }); - status("

Exception Caught:

"+e+"

"+res.join("\n")+"
"); - } + if (!vizpath) { + vizpath = viz; + more = vizmore; + } else { + viz = vizpath; + vizmore = more; + } + res = "digraph {\n"+" rankdir="+rankdir+";\n"+viz+"\n}"; + try { + status(more?Viz(res)+more:Viz(res)); + $('#main a > ellipse + text').attr('font-size', '12'); + $('#main a > ellipse + text + text') + .attr('font-weight', 'bold') + .attr('font-size', '16') + .each(function() {$(this).attr('y', parseFloat($(this).attr('y'))+1.0)}); + $('#main a > ellipse + text + text + text').attr('font-size', '12'); + } catch(e) { + (res = res.split("\n")).forEach(function(v, i, a) { + a[i] = ("000"+(i+1)).slice(-3)+": "+v; + }); + status("

Exception Caught:

"+e+"

"+res.join("\n")+"
"); + } } function details(name) { - if (name) focused = name; - else if (!focused) return overview(); - showviz(docker.containers.subgraph(focused)); + if (name) focused = name; + else if (!focused) return overview(); + showviz(docker.containers.subgraph(focused)); } /// Convert number of bytes to readable text function size(num) { - if (num>0.6*1024) { - if (num>0.6*1024*1024) { - if (num>0.6*1024*1024*1024) { - if (num>0.6*1024*1024*1024*1024) { - return Math.round(num/1024/1024/1024/1024)+"TB"; - } else { - return Math.round(num/1024/1024/1024)+"GB"; - } - } else { - return Math.round(num/1024/1024)+"MB"; - } + if (num>0.6*1024) { + if (num>0.6*1024*1024) { + if (num>0.6*1024*1024*1024) { + if (num>0.6*1024*1024*1024*1024) { + return Math.round(num/1024/1024/1024/1024)+"TB"; } else { - return Math.round(num/1024)+"kB"; + return Math.round(num/1024/1024/1024)+"GB"; } + } else { + return Math.round(num/1024/1024)+"MB"; + } } else { - return num+"B"; + return Math.round(num/1024)+"kB"; } + } else { + return num+"B"; + } } var oldoldstats = null; var oldstats = null; function stats(data) { - console.log("->rcv stats"); - if (!data && oldstats && oldoldstats) { - data = oldstats; - oldstats = oldoldstats; - } - if (oldstats) for (name in data) { - var s = data[name]; - var o = oldstats[name]; - if (!o|| !s) continue; - $('#main text + text:contains("'+name+'") + text') - .html('cpu: ' - +(Math.round((s.cpuacct.usage.data-o.cpuacct.usage.data) - /(s.cpuacct.usage.date-o.cpuacct.usage.date) - /100) - /100) - +'% mem: ' - +size(s.memory.usage_in_bytes.data)); - } - oldoldstats = oldstats; - oldstats = data; + console.log("->rcv stats"); + if (!data && oldstats && oldoldstats) { + data = oldstats; + oldstats = oldoldstats; + } + if (oldstats) for (name in data) { + var s = data[name]; + var o = oldstats[name]; + if (!o|| !s) continue; + $('#main text + text:contains("'+name+'") + text') + .html('cpu: ' + +(Math.round((s.cpuacct.usage.data-o.cpuacct.usage.data) + /(s.cpuacct.usage.date-o.cpuacct.usage.date) + /100) + /100) + +'% mem: ' + +size(s.memory.usage_in_bytes.data)); + } + oldoldstats = oldstats; + oldstats = data; } function images(i) { - console.log("->rcv images"); - docker.images.set(i); - $('#imagedata').empty().append(docker.images.tags().map(function(i) { - var option = document.createElement('option'); - option.value = i; - return option; - })); + console.log("->rcv images"); + docker.images.set(i); + $('#imagedata').empty().append(docker.images.tags().map(function(i) { + var option = document.createElement('option'); + option.value = i; + return option; + })); } function containers(c) { - console.log("->rcv containers"); - docker.containers.set(c); - if (focused && docker.containers.exists(focused)) - details(focused); - else - overview(); + console.log("->rcv containers"); + docker.containers.set(c); + if (focused && docker.containers.exists(focused)) + details(focused); + else + overview(); +} + +function showLogin() { + $("#close").hide(); + $("#console").hide(); + $("#create").hide(); + $("#imagetools").hide(); + $("#login").show(); + $("#logs").hide(); + $("#main").hide(); } function showImage() { - $("#create").hide(); - $("#logs").hide(); - $("#console").hide(); - $("#close").hide(); - $("#imagetools").show(); - $("#main").show(); + $("#close").hide(); + $("#console").hide(); + $("#create").hide(); + $("#imagetools").show(); + $("#login").hide(); + $("#logs").hide(); + $("#main").show(); } function showCreate() { - $("#main").hide(); - $("#logs").hide(); - $("#console").hide(); - $("#imagetools").show(); - $("#close").show(); - $("#create").show(); - previewCreate(); + $("#close").show(); + $("#console").hide(); + $("#create").show(); + $("#imagetools").show(); + $("#login").hide(); + $("#logs").hide(); + $("#main").hide(); + previewCreate(); } function showConsole() { - $("#main").hide(); - $("#create").hide(); - $("#logs").hide(); - $("#imagetools").hide(); - $("#console").show(); - $("#close").show(); - // $("#command").focus(); - // $("#command").val(""); - // if ($("#screen").val()!="") $("#screen").append("\n"); + $("#close").show(); + $("#console").show(); + $("#create").hide(); + $("#imagetools").hide(); + $("#login").hide(); + $("#logs").hide(); + $("#main").hide(); + // $("#command").focus(); + // $("#command").val(""); + // if ($("#screen").val()!="") $("#screen").append("\n"); } function showLogs() { - $("#main").hide(); - $("#create").hide(); - $("#console").hide(); - $("#imagetools").hide(); - $("#close").show(); - $("#logs").show(); + $("#close").show(); + $("#console").hide(); + $("#create").hide(); + $("#imagetools").hide(); + $("#login").hide(); + $("#logs").show(); + $("#main").hide(); } function logs(data) { - console.log("->rcv logs("+data.name+")"); - if (data.type=='done') { - $("#logs").append('\nDONE'); - } else { - $("#logs").append(''+htmlenc(data.text)+''); - } + console.log("->rcv logs("+data.name+")"); + if (data.type=='done') { + $("#logs").append('\nDONE'); + } else { + $("#logs").append(''+htmlenc(data.text)+''); + } } function strInsert(str, pos, txt) { - return str.slice(0, pos)+txt+str.slice(pos); + return str.slice(0, pos)+txt+str.slice(pos); } function ansifilter(data) { - console.log("ansifilter"); - var res = data; - var pos = -1; - var spans = 0; - while ((pos=res.indexOf("\x1B[")) >= 0) { - var end = res.indexOf("m", pos+2); - if (end>0) { - var control= res.slice(pos+2, end); - res = res.slice(0, pos)+res.slice(end+1); - control.split(';').forEach(function(c) { - switch (parseInt(c)) { - // set - case 1: res = strInsert(res, pos, ''); ++spans; break; - case 2: res = strInsert(res, pos, ''); ++spans; break; - case 4: res = strInsert(res, pos, ''); ++spans; break; - case 5: res = strInsert(res, pos, ''); ++spans; break; - case 7: res = strInsert(res, pos, ''); ++spans; break; - case 8: res = strInsert(res, pos, '"); break; - case 21: if (spans) --spans; res = strInsert(res, pos, ""); break; - case 22: if (spans) --spans; res = strInsert(res, pos, ""); break; - case 23: if (spans) --spans; res = strInsert(res, pos, ""); break; - case 25: if (spans) --spans; res = strInsert(res, pos, ""); break; - case 27: if (spans) --spans; res = strInsert(res, pos, ""); break; - case 28: if (spans) --spans; res = strInsert(res, pos, ""); break; - // fg colors - case 39: res = strInsert(res, pos, ''); ++spans; break; - case 30: res = strInsert(res, pos, ''); ++spans; break; - case 31: res = strInsert(res, pos, ''); ++spans; break; - case 32: res = strInsert(res, pos, ''); ++spans; break; - case 33: res = strInsert(res, pos, ''); ++spans; break; - case 34: res = strInsert(res, pos, ''); ++spans; break; - case 35: res = strInsert(res, pos, ''); ++spans; break; - case 36: res = strInsert(res, pos, ''); ++spans; break; - case 37: res = strInsert(res, pos, ''); ++spans; break; - case 90: res = strInsert(res, pos, ''); ++spans; break; - case 91: res = strInsert(res, pos, ''); ++spans; break; - case 92: res = strInsert(res, pos, ''); ++spans; break; - case 93: res = strInsert(res, pos, ''); ++spans; break; - case 94: res = strInsert(res, pos, ''); ++spans; break; - case 95: res = strInsert(res, pos, ''); ++spans; break; - case 96: res = strInsert(res, pos, ''); ++spans; break; - case 97: res = strInsert(res, pos, ''); ++spans; break; - // bg colors - case 49: res = strInsert(res, pos, ''); ++spans; break; - case 40: res = strInsert(res, pos, ''); ++spans; break; - case 41: res = strInsert(res, pos, ''); ++spans; break; - case 42: res = strInsert(res, pos, ''); ++spans; break; - case 43: res = strInsert(res, pos, ''); ++spans; break; - case 44: res = strInsert(res, pos, ''); ++spans; break; - case 45: res = strInsert(res, pos, ''); ++spans; break; - case 46: res = strInsert(res, pos, ''); ++spans; break; - case 47: res = strInsert(res, pos, ''); ++spans; break; - case 100: res = strInsert(res, pos, ''); ++spans; break; - case 101: res = strInsert(res, pos, ''); ++spans; break; - case 102: res = strInsert(res, pos, ''); ++spans; break; - case 103: res = strInsert(res, pos, ''); ++spans; break; - case 104: res = strInsert(res, pos, ''); ++spans; break; - case 105: res = strInsert(res, pos, ''); ++spans; break; - case 106: res = strInsert(res, pos, ''); ++spans; break; - case 107: res = strInsert(res, pos, ''); ++spans; break; - - } - }); - } else { - break; + console.log("ansifilter"); + var res = data; + var pos = -1; + var spans = 0; + while ((pos=res.indexOf("\x1B[")) >= 0) { + var end = res.indexOf("m", pos+2); + if (end>0) { + var control= res.slice(pos+2, end); + res = res.slice(0, pos)+res.slice(end+1); + control.split(';').forEach(function(c) { + switch (parseInt(c)) { + // set + case 1: res = strInsert(res, pos, ''); ++spans; break; + case 2: res = strInsert(res, pos, ''); ++spans; break; + case 4: res = strInsert(res, pos, ''); ++spans; break; + case 5: res = strInsert(res, pos, ''); ++spans; break; + case 7: res = strInsert(res, pos, ''); ++spans; break; + case 8: res = strInsert(res, pos, '"); break; + case 21: if (spans) --spans; res = strInsert(res, pos, ""); break; + case 22: if (spans) --spans; res = strInsert(res, pos, ""); break; + case 23: if (spans) --spans; res = strInsert(res, pos, ""); break; + case 25: if (spans) --spans; res = strInsert(res, pos, ""); break; + case 27: if (spans) --spans; res = strInsert(res, pos, ""); break; + case 28: if (spans) --spans; res = strInsert(res, pos, ""); break; + // fg colors + case 39: res = strInsert(res, pos, ''); ++spans; break; + case 30: res = strInsert(res, pos, ''); ++spans; break; + case 31: res = strInsert(res, pos, ''); ++spans; break; + case 32: res = strInsert(res, pos, ''); ++spans; break; + case 33: res = strInsert(res, pos, ''); ++spans; break; + case 34: res = strInsert(res, pos, ''); ++spans; break; + case 35: res = strInsert(res, pos, ''); ++spans; break; + case 36: res = strInsert(res, pos, ''); ++spans; break; + case 37: res = strInsert(res, pos, ''); ++spans; break; + case 90: res = strInsert(res, pos, ''); ++spans; break; + case 91: res = strInsert(res, pos, ''); ++spans; break; + case 92: res = strInsert(res, pos, ''); ++spans; break; + case 93: res = strInsert(res, pos, ''); ++spans; break; + case 94: res = strInsert(res, pos, ''); ++spans; break; + case 95: res = strInsert(res, pos, ''); ++spans; break; + case 96: res = strInsert(res, pos, ''); ++spans; break; + case 97: res = strInsert(res, pos, ''); ++spans; break; + // bg colors + case 49: res = strInsert(res, pos, ''); ++spans; break; + case 40: res = strInsert(res, pos, ''); ++spans; break; + case 41: res = strInsert(res, pos, ''); ++spans; break; + case 42: res = strInsert(res, pos, ''); ++spans; break; + case 43: res = strInsert(res, pos, ''); ++spans; break; + case 44: res = strInsert(res, pos, ''); ++spans; break; + case 45: res = strInsert(res, pos, ''); ++spans; break; + case 46: res = strInsert(res, pos, ''); ++spans; break; + case 47: res = strInsert(res, pos, ''); ++spans; break; + case 100: res = strInsert(res, pos, ''); ++spans; break; + case 101: res = strInsert(res, pos, ''); ++spans; break; + case 102: res = strInsert(res, pos, ''); ++spans; break; + case 103: res = strInsert(res, pos, ''); ++spans; break; + case 104: res = strInsert(res, pos, ''); ++spans; break; + case 105: res = strInsert(res, pos, ''); ++spans; break; + case 106: res = strInsert(res, pos, ''); ++spans; break; + case 107: res = strInsert(res, pos, ''); ++spans; break; + } + }); + } else { + break; } - for (;spans;--spans) res += ""; - console.log(res); - return res.replace(/\r\r\n/g, '\n'); + } + for (;spans;--spans) res += ""; + console.log(res); + return res.replace(/\r\r\n/g, '\n'); } function ascii(txt) { - var res = ""; - for (i=0; ircv bash-data("+data.name+")", data); - if (data.type=='done') { - $("#screen").append('\nDONE'); - } else { - var buff = ""; - console.log("ASCII: ", ascii(data.text)); - for (var i=0; ircv bash-data("+data.name+")", data); + if (data.type=='done') { + $("#screen").append('\nDONE'); + } else { + var buff = ""; + console.log("ASCII: ", ascii(data.text)); + for (var i=0; i'+$(this).siblings("input") - .map(function() { - var val = this.value; - var sep = this.getAttribute('data-separator') || ''; - if (this.type=='checkbox' && !this.checked) { - val=''; sep='' - } - return sep+val; - }).get().join('')+''); - $(this).siblings("input[type=text]").val(''); - previewCreate(); - }); - $("#create form fieldset .remove").unbind().click(function() { - $(this).siblings('select.collect').children('option:selected').remove(); - previewCreate(); - }); + .replace(/&/g, '&') + .replace(/"/g, '"') + +'"'; + }) + return res; + } else return ''; + }).get().join(' ')+'>'+$(this).siblings("input") + .map(function() { + var val = this.value; + var sep = this.getAttribute('data-separator') || ''; + if (this.type=='checkbox' && !this.checked) { + val=''; sep='' + } + return sep+val; + }).get().join('')+''); + $(this).siblings("input[type=text]").val(''); + previewCreate(); + }); + $("#create form fieldset .remove").unbind().click(function() { + $(this).siblings('select.collect').children('option:selected').remove(); + previewCreate(); + }); } function init() { - $("#logout").attr("href", - window.location.protocol+"//X:X@" - +window.location.hostname - +(window.location.port?":":"")+window.location.port - +window.location.pathname); - socket = io.connect(); - socket.io - .on("connect", connected) - .on("reconnect", connected) - .on("disconnect", disconnected) - .on("error", disconnected); - socket - .on("fail", error) - .on("containers", containers) - .on("images", images) - .on("stats", stats) - .on("logs", logs) - .on("bash-data", bash_data); - initForms(); - start(); + socket = io.connect(); + socket + .io + .on("connect", connect) + .on("reconnect", connect) + .on("disconnect", disconnected) + .on("error", disconnected); + socket + .on("authenticated", authenticated) + .on("unauthorized", unauthorized) + .on("fail", error) + .on("containers", containers) + .on("images", images) + .on("stats", stats) + .on("logs", logs) + .on("bash-data", bash_data); + $("#server").html($("#username").value+'@'+window.location.hostname) + initForms(); + showLogin(); } /// On Load, Call @ref start /* - $(window.onbeforeunload = function() { - return "Are you sure you want to navigate away?"; -}); + $(window.onbeforeunload = function() { + return "Are you sure you want to navigate away?"; + }); */ $(init); diff --git a/nodejs/public/stylesheets/servicedock.css b/nodejs/public/stylesheets/servicedock.css index 3bfa98f..cf4cecf 100644 --- a/nodejs/public/stylesheets/servicedock.css +++ b/nodejs/public/stylesheets/servicedock.css @@ -156,6 +156,7 @@ table.docker li+li { #statusbar { position: fixed; + width: 100%; left: 0; right: 0; bottom: 0; @@ -163,8 +164,22 @@ table.docker li+li { padding: 0 1em 0 1em; color: white; } -#status: { - float: right; + +#status { + position: fixed; + right: 0; + bottom: 0; + margin: 0 1em 0 1em; + padding: 0 1em 0 1em; +} +#good { + color: green; +} +#authentication { + color: yellow; +} +#bad { + color: red; } @media (max-width: 45em) { #username { @@ -234,7 +249,7 @@ table.docker li+li { background-color: #777; } -#main, #logs, #console, #create { +#main, #logs, #console, #create, #login { position: fixed; top: 1.5em; left: 0; @@ -246,7 +261,7 @@ table.docker li+li { bottom: 1.5em; } -#main, #create { +#main, #create, #login { background-color: white; } diff --git a/nodejs/servicedock.js b/nodejs/servicedock.js index f8e6034..5fc0352 100644 --- a/nodejs/servicedock.js +++ b/nodejs/servicedock.js @@ -1,69 +1,69 @@ try { - process.on('uncaughtException', function(e) { - console.log("**** UNCAUGHT EXCEPTION ****"); - console.log(e); - console.log(e.stack); - process.exit(1); - }); - - /** - * Module dependencies. - */ + process.on('uncaughtException', function(e) { + console.log("**** UNCAUGHT EXCEPTION ****"); + console.log(e); + console.log(e.stack); + process.exit(1); + }); + + /** + * Module dependencies. + */ - var express = require('express') + var express = require('express') , routes = require(__dirname+'/routes'); - var app = module.exports = express.createServer(); - var io = require('socket.io').listen(app); - var sockets = require(__dirname+'/sockets')(io); - var package = require(__dirname+'/package.json'); - var config = require(package.path.config); - var docker = require(__dirname+'/docker')(app); - var authentication = require(__dirname+'/authentication')(config.restrict); + var app = module.exports = express.createServer(); + var io = require('socket.io').listen(app); + var package = require(__dirname+'/package.json'); + var config = require(package.path.config); + var docker = require(__dirname+'/docker')(app); + //var authentication = require(__dirname+'/authentication')(config.restrict); + var sockets = require(__dirname+'/sockets')(io); - // Configuration - process.argv.forEach(function(val, index) { - if (index<2) {return} - if (index!=2 || typeof val != 'number') { - console.log("**** ERROR: Unexpected Argument - allowed is only a port number"); - process.exit(1); - } - config.port = val; - }); - if (typeof config.port != 'number') { - console.log("**** WARNING: no valid port given, defaults to 8888"); - config.port = 8888; + // Configuration + process.argv.forEach(function(val, index) { + if (index<2) {return} + if (index!=2 || isNaN(val)) { + console.log("**** ERROR: Unexpected Argument - allowed is only a port number"); + process.exit(1); } + config.port = parseInt(val); + }); + if (typeof config.port != 'number') { + console.log("**** WARNING: no valid port given, defaults to 8888"); + config.port = 8888; + } - app.configure(function(){ - app.set('views', __dirname + '/views'); - app.set('view engine', 'ejs'); - app.use(express.bodyParser()); - app.use(express.methodOverride()); - app.use(require('stylus').middleware({ src: __dirname + '/public' })); - app.use(app.router); - app.use(express.static(__dirname + '/public')); - }); + app.configure(function(){ + app.set('views', __dirname + '/views'); + app.set('view engine', 'ejs'); + app.use(express.bodyParser()); + app.use(express.methodOverride()); + app.use(require('stylus').middleware({ src: __dirname + '/public' })); + app.use(app.router); + app.use(express.static(__dirname + '/public')); + }); - app.configure('development', function(){ - app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); - }); + app.configure('development', function(){ + app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); + }); - app.configure('production', function(){ - app.use(express.errorHandler()); - }); + app.configure('production', function(){ + app.use(express.errorHandler()); + }); - // Routes - app.get('/', authentication, routes.index); + // Routes + app.get('/', routes.index); - app.listen(config.port, function() { - console.log("Express server listening on port %d in %s mode", - app.address().port, app.settings.env); - }); + app.listen(config.port, function() { + console.log("Express server listening on port %d in %s mode", + app.address().port, app.settings.env); + }); } catch (e) { - console.log("**** EXCEPTION ****"); - console.log(e); - console.log(e.stack); - process.exit(1); + console.log("**** EXCEPTION ****"); + console.log(e); + console.log(e.stack); + process.exit(1); } diff --git a/nodejs/sockets/index.js b/nodejs/sockets/index.js index 774d7ce..18e1514 100644 --- a/nodejs/sockets/index.js +++ b/nodejs/sockets/index.js @@ -1,298 +1,311 @@ module.exports = function(io) { - var pty = require('pty.js'); - var proc = require('child_process'); - var docker = require(__dirname+'/../docker')(); + var pty = require('pty.js'); + var proc = require('child_process'); + var docker = require(__dirname+'/../docker')(); - var module={}; - var idtoname = {}; - - function broadcast(signal, data) { - console.log("<= signal: "+signal); - io.sockets.emit(signal, data); + var module={}; + var idtoname = {}; + + function broadcast(signal, data) { + console.log("<= signal: "+signal); + io.sockets.emit(signal, data); + } + + function exec(cmd, callback) { + if (cmd.length>40) { + console.log("== "+cmd.slice(0, 30+cmd.slice(30).indexOf(' '))+" ..."); + } else { + console.log("== "+cmd); } + proc.exec(cmd, {maxBuffer: 10*1024*1024}, callback); + } + + function fail(txt, data) { + console.log("** "+txt, data); + } + + var oldcontainer = null; + function containerinspect(error, stdout, stderr) { + if (error || stderr) + return fail("inspect docker containers failed", { + error: error, stderr: stderr, stdout: stdout + }); + idtoname = {}; + JSON.parse(stdout).forEach(function(n) { + if (n.State.Running) idtoname[n.Id] = n.Name.replace(/^\//, ''); + }); + if (oldcontainer && oldcontainer==stdout) return; // do not resend same containers + oldcontainer = stdout; + broadcast("containers", stdout); + } + + function containerlist(error, stdout, stderr) { + if (error || stderr) + return fail("list docker containers failed", { + error: error, stderr: stderr, stdout: stdout + }); + exec("docker inspect "+stdout.trim().replace(/\n/g, " "), containerinspect); + } + + function updatecontainers(error, stdout, stderr) { + if (error || stderr) + return fail("update docker container failed", { + error: error, stderr: stderr, stdout: stdout + }); + exec("docker ps -aq --no-trunc ", containerlist); + } + + var oldimage = null; + function imageinspect(error, stdout, stderr) { + if (error || stderr) + return fail("inspect docker images failed", { + error: error, stderr: stderr, stdout: stdout + }); + if (oldimage && oldimage==stdout) return; // do not resend same images + oldimage = stdout; + broadcast("images", stdout); + } + + function imagelist(error, stdout, stderr) { + if (error || stderr) + return fail("list docker images failed", { + error: error, stderr: stderr, stdout: stdout + }); + exec("docker inspect "+stdout.trim().replace(/\n/g, " "), + imageinspect); + } + + function updateimages(error, stdout, stderr) { + if (error || stderr) + return fail("update docker images failed", { + error: error, stderr: stderr, stdout: stdout + }); + exec("docker images -q --no-trunc", imagelist); + } + + function connection(socket, userdata) { - function exec(cmd, callback) { - if (cmd.length>40) { - console.log("== "+cmd.slice(0, 30+cmd.slice(30).indexOf(' '))+" ..."); - } else { - console.log("== "+cmd); - } - proc.exec(cmd, {maxBuffer: 10*1024*1024}, callback); + console.log("=> new connection from "+userdata.username); + + function emit(signal, data, info) { + if (typeof data == 'string' && !data.match("\n")) { + console.log("<- signal: "+signal+"("+data+")"); + } else { + console.log("<- signal: "+signal); + } + if (info) console.log(info); + socket.emit(signal, data); } function fail(txt, data) { - console.log("** "+txt, data); + console.log("** "+txt, data); + emit("fail", txt); } - var oldcontainer = null; - function containerinspect(error, stdout, stderr) { - if (error || stderr) - return fail("inspect docker containers failed", { - error: error, stderr: stderr, stdout: stdout - }); - idtoname = {}; - JSON.parse(stdout).forEach(function(n) { - if (n.State.Running) idtoname[n.Id] = n.Name.replace(/^\//, ''); - }); - if (oldcontainer && oldcontainer==stdout) return; // do not resend same containers - oldcontainer = stdout; - broadcast("containers", stdout); - } - - function containerlist(error, stdout, stderr) { - if (error || stderr) - return fail("list docker containers failed", { - error: error, stderr: stderr, stdout: stdout - }); - exec("docker inspect "+stdout.trim().replace(/\n/g, " "), containerinspect); + function modify(cmd, name) { + if (!name.match(/^[a-z0-9][-_:.+a-z0-9]*$/i)) + return this.fail("illegal instance name"); + exec("docker "+cmd+" "+name, updatecontainers); } - function updatecontainers(error, stdout, stderr) { - if (error || stderr) - return fail("update docker container failed", { - error: error, stderr: stderr, stdout: stdout + function createContainer(cmds) { + if (cmds.length>0) + exec(cmds.shift(), function(error, stdout, stderr) { + if (error || stderr) + return this.fail("create container failed", { + error: error, stderr: stderr, stdout: stdout }); - exec("docker ps -aq --no-trunc ", containerlist); + createContainer(cmds); + }) + else + updatecontainers(); } - var oldimage = null; - function imageinspect(error, stdout, stderr) { - if (error || stderr) - return fail("inspect docker images failed", { - error: error, stderr: stderr, stdout: stdout - }); - if (oldimage && oldimage==stdout) return; // do not resend same images - oldimage = stdout; - broadcast("images", stdout); + function containers() { + console.log("-> containers"); + if (oldcontainer) emit("containers", oldcontainer); + else updatecontainers(); } - function imagelist(error, stdout, stderr) { - if (error || stderr) - return fail("list docker images failed", { - error: error, stderr: stderr, stdout: stdout - }); - exec("docker inspect "+stdout.trim().replace(/\n/g, " "), - imageinspect); + function images() { + console.log("-> images"); + if (oldimage) emit("images", oldimage); + else updateimages(); } - - function updateimages(error, stdout, stderr) { - if (error || stderr) - return fail("update docker images failed", { - error: error, stderr: stderr, stdout: stdout - }); - exec("docker images -q --no-trunc", imagelist); - } - - function connection(socket) { - - console.log("new connection"); - - function emit(signal, data, info) { - if (typeof data == 'string' && !data.match("\n")) { - console.log("<- signal: "+signal+"("+data+")"); - } else { - console.log("<- signal: "+signal); - } - if (info) console.log(info); - socket.emit(signal, data); - } - - function fail(txt, data) { - console.log("** "+txt, data); - emit("fail", txt); - } - - function modify(cmd, name) { - if (!name.match(/^[a-z0-9][-_:.+a-z0-9]*$/i)) - return this.fail("illegal instance name"); - exec("docker "+cmd+" "+name, updatecontainers); - } - - function createContainer(cmds) { - if (cmds.length>0) - exec(cmds.shift(), function(error, stdout, stderr) { - if (error || stderr) - return this.fail("create container failed", { - error: error, stderr: stderr, stdout: stdout - }); - createContainer(cmds); - }) - else - updatecontainers(); - } - - function containers() { - console.log("-> containers"); - if (oldcontainer) emit("containers", oldcontainer); - else updatecontainers(); - } - - function images() { - console.log("-> images"); - if (oldimage) emit("images", oldimage); - else updateimages(); - } - function start(name) { - console.log("-> start("+name+")"); - modify("start", name); - } + function start(name) { + console.log("-> start("+name+")"); + modify("start", name); + } - function stop(name) { - console.log("-> stop("+name+")"); - modify("stop", name); - } + function stop(name) { + console.log("-> stop("+name+")"); + modify("stop", name); + } - function pause(name) { - console.log("-> pause("+name+")"); - modify("pause", name); - } + function pause(name) { + console.log("-> pause("+name+")"); + modify("pause", name); + } - function unpause(name) { - console.log("-> unpause("+name+")"); - modify("unpause", name); - } + function unpause(name) { + console.log("-> unpause("+name+")"); + modify("unpause", name); + } - function remove(name) { - console.log("-> remove("+name+")"); - modify("rm", name); - } + function remove(name) { + console.log("-> remove("+name+")"); + modify("rm", name); + } - function create(data) { - console.log("-> create"); - var d = new docker.Docker(); - var dc = new d.Containers(); - createContainer(dc.creation(data)); - } + function create(data) { + console.log("-> create"); + var d = new docker.Docker(); + var dc = new d.Containers(); + createContainer(dc.creation(data)); + } - function logs(name) { - console.log("-> logs("+name+")"); - var l = proc.spawn("docker", ["logs", "-f", name]) - .on('close', function(code) { + function logs(name) { + console.log("-> logs("+name+")"); + var l = proc.spawn("docker", ["logs", "-f", name]) + .on('close', function(code) { emit('logs', {name: name, type: 'done'}); - }); - l.stdout.on('data', function(data) { - emit('logs', {name: name, type: 'stdout', text: data.toString()}); - }); - l.stderr.on('data', function(data) { - emit('logs', {name: name, type: 'stderr', text: data.toString()}); - }); - } + }); + l.stdout.on('data', function(data) { + emit('logs', {name: name, type: 'stdout', text: data.toString()}); + }); + l.stderr.on('data', function(data) { + emit('logs', {name: name, type: 'stderr', text: data.toString()}); + }); + } - var bash_connections = {}; + var bash_connections = {}; - function new_bash(name) { - if (!name.match(/^[a-z0-9][-_:.+a-z0-9]*$/i)) - return this.fail("illegal instance name"); - if (bash_connections[name]) return; - bash_connections[name] = - pty.spawn("docker", ["exec", "-it", name, "bash", "-i"]); - bash_connections[name].stdout.on('data', function(data) { - emit('bash-data', {name: name, type: 'stdout', text: data.toString()}); - }); - } + function new_bash(name) { + if (!name.match(/^[a-z0-9][-_:.+a-z0-9]*$/i)) + return this.fail("illegal instance name"); + if (bash_connections[name]) return; + bash_connections[name] = + pty.spawn("docker", ["exec", "-it", name, "bash", "-i"]); + bash_connections[name].stdout.on('data', function(data) { + emit('bash-data', {name: name, type: 'stdout', text: data.toString()}); + }); + } - function bash_start(name) { - console.log("-> bash-start("+name+")"); - new_bash(name); - } - - function bash_input(data) { - console.log("-> bash-input("+data.name+", "+data.text+")"); - new_bash(data.name); - bash_connections[data.name].stdin.resume(); - bash_connections[data.name].stdin.write(data.text); - } + function bash_start(name) { + console.log("-> bash-start("+name+")"); + new_bash(name); + } + + function bash_input(data) { + console.log("-> bash-input("+data.name+", "+data.text+")"); + new_bash(data.name); + bash_connections[data.name].stdin.resume(); + bash_connections[data.name].stdin.write(data.text); + } - function bash_end(name, text) { - console.log("-> bash-end("+name+")"); - if (!bash_connections[name]) return; - bash_connections[name].stdin.close(); - delete bash_connections[name]; bash_connections[name] = null; - } + function bash_end(name, text) { + console.log("-> bash-end("+name+")"); + if (!bash_connections[name]) return; + bash_connections[name].stdin.close(); + delete bash_connections[name]; bash_connections[name] = null; + } - socket - .on("containers", containers) - .on("images", images) - .on("start", start) - .on("stop", stop) - .on("pause", pause) - .on("unpause", unpause) - .on("remove", remove) - .on("create", create) - .on('logs', logs) - .on('bash-start', bash_start) - .on('bash-input', bash_input) - .on('bash-end', bash_end); + socket + .on("containers", containers) + .on("images", images) + .on("start", start) + .on("stop", stop) + .on("pause", pause) + .on("unpause", unpause) + .on("remove", remove) + .on("create", create) + .on('logs', logs) + .on('bash-start', bash_start) + .on('bash-input', bash_input) + .on('bash-end', bash_end); - } - - function stats() { - var res = {}; - var fs = require('fs'); - var async = require('async'); - var base = "/sys/fs/cgroup/"; - var dataPoints = { - "cpuacct": [ - "usage" - ], - "memory": [ - "usage_in_bytes", - "limit_in_bytes", - "max_usage_in_bytes" - ] - }; - async.forEachOf(idtoname, function(name, id, cb1) { - res[name] = {}; - async.forEachOf(dataPoints, function(val1, category, cb2) { - res[name][category] = {}; - async.each(val1, function(element, cb3) { - var file = base + category + '/docker/' + id + '/' + category + '.' + element; - fs.readFile(file, 'utf8', function(err, data) { - res[name][category][element] = { - date: (new Date()).getTime(), - data: {} - }; - if (err) {cb3(); console.log(err); return} - data.trim().split('\n').forEach(function(v, i) { - var vals = v.split(' '); - switch (vals.length) { - case 1: - res[name][category][element].data = parseInt(vals[0]); - break; - case 2: - res[name][category][element].data[vals[0]] = parseInt(vals[1]); - break; - } - }); - cb3(); - }); - }, function(err) { - cb2(); - }); - }, function(err) { - cb1(); + } + + function stats() { + var res = {}; + var fs = require('fs'); + var async = require('async'); + var base = "/sys/fs/cgroup/"; + var dataPoints = { + "cpuacct": [ + "usage" + ], + "memory": [ + "usage_in_bytes", + "limit_in_bytes", + "max_usage_in_bytes" + ] + }; + async.forEachOf(idtoname, function(name, id, cb1) { + res[name] = {}; + async.forEachOf(dataPoints, function(val1, category, cb2) { + res[name][category] = {}; + async.each(val1, function(element, cb3) { + var file = base + category + '/docker/' + id + '/' + category + '.' + element; + fs.readFile(file, 'utf8', function(err, data) { + res[name][category][element] = { + date: (new Date()).getTime(), + data: {} + }; + if (err) {cb3(); console.log(err); return} + data.trim().split('\n').forEach(function(v, i) { + var vals = v.split(' '); + switch (vals.length) { + case 1: + res[name][category][element].data = parseInt(vals[0]); + break; + case 2: + res[name][category][element].data[vals[0]] = parseInt(vals[1]); + break; + } }); + cb3(); + }); }, function(err) { - if (err) return; - broadcast("stats", res); + cb2(); }); - } - - // Handle Connection - io.sockets.on('connection', connection); + }, function(err) { + cb1(); + }); + }, function(err) { + if (err) return; + broadcast("stats", res); + }); + } + + // Handle Connection + require('socketio-auth')(io, { + authenticate: function (socket, data, callback) { + console.log("=> authenticate: ", data.username); + //get credentials sent by the client + var username = data.username; + var password = data.password; + if (username=="hello") + return callback(null, "world" == password); + else + return callback(new Error("wrong credentials")); + }, + postAuthenticate: connection, + timeout: "none" + }); - // Regular Update of Stats - setInterval(function() { - stats(); - }, 1000); - - // Regular Update of Images and Containers - setInterval(function() { - updateimages(); - updatecontainers(); - }, 10000); - - return module; + // Regular Update of Stats + setInterval(function() { + stats(); + }, 1000); + + // Regular Update of Images and Containers + setInterval(function() { + updateimages(); + updatecontainers(); + }, 10000); + + return module; } diff --git a/nodejs/views/index.ejs b/nodejs/views/index.ejs index 0b61b0c..abaf0c3 100644 --- a/nodejs/views/index.ejs +++ b/nodejs/views/index.ejs @@ -31,7 +31,6 @@