diff --git a/www2/README.md b/www2/README.md
new file mode 100644
index 0000000..85266a4
--- /dev/null
+++ b/www2/README.md
@@ -0,0 +1,82 @@
+# SWUpdate Web Application
+
+The SWUpdate web application uses the [Node.js](https://nodejs.org/en/) package
+manager and [gulp](https://gulpjs.com/) as build tool.
+I depends on [Bootstrap 4](https://getbootstrap.com/),
+[Font Awesome 5](https://fontawesome.com/) and
+[Dropzone.js](http://www.dropzonejs.com/).
+
+The web application uses WebSocket for the asynchronous communication with the
+backend, allows a visualization of different image sources, automatically
+reloads the web page after a connection lost and restarts the system
+via a post update command.
+
+
+## Install
+
+1. Unpack a released `swupdate-www.tar.gz` archive to your `www` direcotry
+
+        tar -xzf swupdate-www.tar.gz -C /www
+
+2. Run the web application
+
+        swupdate -w "-r /www" -p "reboot"
+
+
+## Customize
+
+You could replace the `logo.png`, `favicon.png` and `background.jpg` images
+inside the `images` directory. Furthermore you could customize the Bootstrap
+colors and settings inside the `scss/bootstrap.scss` file. The latter needs a
+rebuild of the code.
+
+
+## Develop
+
+1. Clone the code:
+
+        git clone https://github.com/sbabic/swupdate-www.git
+        cd swupdate-www
+
+2. Install the dependencies
+
+        npm install
+
+3. Build the web application
+
+        npm run build
+
+4. Run the web application
+
+        swupdate -w "-r ./dist" -p "echo reboot"
+
+5. Pack the web application
+
+        npm run package
+
+
+## Maintainer
+
+Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
+
+
+## Contribute
+
+You can submit your patches (or post questions regarding the project) to the
+swupdate Mailing List:
+
+    swupdate@googlegroups.com
+
+Please read in the corresponding chapter in the SWUpdate documentation
+how to contribute to the project.
+
+Additional run the linter before any commit and add a prefix to the patches
+
+    npm run lint
+
+    git format-patch -s --subject-prefix='swupdate-www][PATCH' <revision range>
+
+
+## License
+
+MIT
diff --git a/www2/css/swupdate.css b/www2/css/swupdate.css
new file mode 100644
index 0000000..a37d3d1
--- /dev/null
+++ b/www2/css/swupdate.css
@@ -0,0 +1,32 @@
+/*!
+ * Copyright (C) 2017-2018 Weidmüller Interface GmbH & Co. KG
+ * Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+body {
+  background-attachment: fixed;
+  background-image: url("../images/background.jpg");
+  background-position: center;
+  background-repeat: no-repeat;
+  background-size: cover;
+}
+
+.dropzone {
+  background: #ffffff;
+  background: rgba(255,255,255,0.75);
+  min-height: 200px;
+}
+
+.dropzone .dz-preview .dz-image {
+  width: 220px;
+}
+
+[data-toggle="collapse"] i:before {
+  content: "\f139";
+}
+
+[data-toggle="collapse"].collapsed i:before {
+  content: "\f13a";
+}
diff --git a/www2/gulpfile.js b/www2/gulpfile.js
new file mode 100755
index 0000000..a99b460
--- /dev/null
+++ b/www2/gulpfile.js
@@ -0,0 +1,121 @@
+/*!
+ * Copyright (C) 2017-2018 Weidmüller Interface GmbH & Co. KG
+ * Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+var cleanCSS = require('gulp-clean-css')
+var del = require('del')
+var gulp = require('gulp')
+var gzip = require('gulp-gzip')
+var imagemin = require('gulp-imagemin')
+var filter = require('gulp-filter')
+var htmlmin = require('gulp-htmlmin')
+var minify = require('gulp-minify')
+var rename = require('gulp-rename')
+var replace = require('gulp-replace')
+var sass = require('gulp-sass')
+var tar = require('gulp-tar')
+var useref = require('gulp-useref')
+
+gulp.task('sass', ['clean'], () => {
+  return gulp.src('scss/*.scss')
+    .pipe(sass().on('error', sass.logError))
+    .pipe(cleanCSS({ compatibility: '*' }))
+    .pipe(rename({
+      suffix: '.min'
+    }))
+    .pipe(gulp.dest('dist/css'))
+})
+
+gulp.task('minify-css', ['clean'], () => {
+  return gulp.src('css/*.css')
+    .pipe(cleanCSS({ compatibility: '*' }))
+    .pipe(rename({
+      suffix: '.min'
+    }))
+    .pipe(gulp.dest('dist/css'))
+})
+
+gulp.task('minify-js', ['clean'], () => {
+  return gulp.src('js/*.js')
+    .pipe(minify({
+      ext: {
+        min: '.min.js'
+      },
+      noSource: true,
+      preserveComments: 'some'
+    }))
+    .pipe(gulp.dest('dist/js'))
+})
+
+gulp.task('minify-html', ['clean'], () => {
+  return gulp.src('*.html')
+    .pipe(replace('node_modules/bootstrap/dist/css', 'css'))
+    .pipe(useref({ noconcat: true }))
+    .pipe(filter('*.html'))
+    .pipe(replace(/node_modules\/.*\/([^/]+)\.css/g, 'css/$1.css'))
+    .pipe(replace(/node_modules\/.*\/([^/]+)\.js/g, 'js/$1.js'))
+    .pipe(replace('.css', '.min.css'))
+    .pipe(replace('.js', '.min.js'))
+    .pipe(htmlmin({ collapseWhitespace: true }))
+    .pipe(gulp.dest('dist'))
+})
+
+gulp.task('copy-css', ['clean'], () => {
+  return gulp.src('*.html')
+    .pipe(useref({ noconcat: true }))
+    .pipe(filter('**/*.css'))
+    .pipe(rename({
+      dirname: 'css',
+      suffix: '.min'
+    }))
+    .pipe(cleanCSS({ compatibility: '*' }))
+    .pipe(gulp.dest('dist'))
+})
+
+gulp.task('copy-js', ['clean'], () => {
+  return gulp.src('*.html')
+    .pipe(useref({ noconcat: true }))
+    .pipe(filter('**/*.js'))
+    .pipe(rename({
+      dirname: 'js'
+    }))
+    .pipe(minify({
+      ext: {
+        min: '.min.js'
+      },
+      noSource: true,
+      preserveComments: 'some'
+    }))
+    .pipe(gulp.dest('dist'))
+})
+
+gulp.task('copy-fonts', ['clean'], () => {
+  return gulp.src([
+    'node_modules/@fortawesome/fontawesome-free-webfonts/webfonts/fa-solid-900.{ttf,woff,woff2}'
+  ])
+  .pipe(gulp.dest('dist/webfonts'))
+})
+
+gulp.task('resize-images', ['clean'], () => {
+  return gulp.src('./images/*')
+    .pipe(imagemin({ verbose: true }))
+    .pipe(gulp.dest('./dist/images'))
+})
+
+gulp.task('package', ['build'], () => {
+  return gulp.src('dist/**')
+    .pipe(tar('swupdate-www.tar'))
+    .pipe(gzip())
+    .pipe(gulp.dest('prod'))
+})
+
+gulp.task('clean', () => {
+  return del('dist/**', { force: true })
+})
+
+gulp.task('build', ['copy-css', 'copy-js', 'copy-fonts', 'sass', 'minify-css', 'minify-js', 'minify-html', 'resize-images'])
+
+gulp.task('default', ['build'])
diff --git a/www2/images/background.jpg b/www2/images/background.jpg
new file mode 100755
index 0000000000000000000000000000000000000000..5cfceda6397ed471dd9a50fb8dd0a850cf089122
GIT binary patch
diff --git a/www2/images/favicon.png b/www2/images/favicon.png
new file mode 100755
index 0000000000000000000000000000000000000000..04c50a937604e51321891e6f48370d3410cec742
GIT binary patch
diff --git a/www2/images/logo.png b/www2/images/logo.png
new file mode 100755
index 0000000000000000000000000000000000000000..04c50a937604e51321891e6f48370d3410cec742
GIT binary patch
diff --git a/www2/index.html b/www2/index.html
new file mode 100755
index 0000000..1ff2bbb
--- /dev/null
+++ b/www2/index.html
@@ -0,0 +1,136 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+  <link rel="icon" href="/favicon.png" type="image/png">
+
+  <link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.css">
+  <!-- build:css -->
+  <link rel="stylesheet" href="node_modules/dropzone/dist/dropzone.css">
+  <link rel="stylesheet" href="node_modules/@fortawesome/fontawesome-free-webfonts/css/fontawesome.css">
+  <link rel="stylesheet" href="node_modules/@fortawesome/fontawesome-free-webfonts/css/fa-solid.css">
+  <link rel="stylesheet" href="css/swupdate.css">
+  <!-- endbuild -->
+
+  <!-- build:js -->
+  <script src="node_modules/jquery/dist/jquery.js"></script>
+  <script src="node_modules/bootstrap/dist/js/bootstrap.js"></script>
+  <script src="node_modules/dropzone/dist/dropzone.js"></script>
+  <!-- endbuild -->
+  <script src="js/swupdate.js"></script>
+
+  <title>SWUpdate - Software Update for Embedded Systems</title>
+</head>
+
+<body>
+  <!-- Navigation Header -->
+  <nav class="navbar sticky-top navbar-expand-sm navbar-dark bg-primary">
+    <div class="container">
+      <a class="navbar-brand" href="#">
+        <img class="d-inline-block align-top" src="images/logo.png" alt="">
+      </a>
+      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
+        <span class="navbar-toggler-icon"></span>
+      </button>
+      <div class="collapse navbar-collapse" id="navbarText">
+        <ul class="navbar-nav ml-auto">
+          <li class="nav-item">
+            <a id="swu-restart" class="nav-link" href="#"><i class="fas fa-sync"></i> Restart System</a>
+          </li>
+        </ul>
+      </div>
+    </div>
+  </nav>
+
+  <!-- Use Bootstrap Grid Container -->
+  <div class="container">
+    <!-- Header -->
+    <div class="jumbotron my-3">
+      <h1 class="display-4">SWUpdate</h1>
+      <p class="lead">Software update for embedded systems</p>
+      <p>Upload an software image below or restart the system at the top right.</p>
+    </div>
+
+    <div class="row my-3">
+      <div class="col">
+        <div class="card">
+          <div class="card-header pb-0">
+            <h5><i class="fas fa-wrench"></i> Software Update</h5>
+          </div>
+          <div class="card-body">
+            <!-- Dropzone Button for drag&drop files -->
+            <form class="dropzone border rounded mb-3" id="dropzone" action="/upload" method="post" enctype="multipart/form-data">
+              <div class="dz-default dz-message">
+                <span class="dz-message">Click hier or drop a software update image.</span>
+              </div>
+            </form>
+
+            <!-- Status -->
+            <div id="swu-idle" class="alert alert-secondary">
+              <i class="fas fa-info-circle"></i> Update not started
+            </div>
+            <div id="swu-run" class="alert alert-warning" style="display: none;">
+              <i class="fas fa-exclamation-triangle"></i>
+              Updating may take a few minutes, please don't turn off the power.
+            </div>
+            <div id="swu-success" class="alert alert-success" style="display: none;">
+              <i class="fas fa-check-circle"></i> Successful updated
+            </div>
+            <div id="swu-failure" class="alert alert-danger" style="display: none;">
+              <i class="fas fa-times-circle"></i> Update failed
+            </div>
+            <div id="swu-done" class="alert alert-info" style="display: none;">
+              <i class="fas fa-sync"></i> Restarting system
+            </div>
+
+            <!-- Progress -->
+            <i id="swu-progress-spinner" class="fas"></i>
+            <strong id="swu-progress-run" style="display: none;">Updating:</strong>
+            <span id="swu-progress-name"></span>
+            <span id="swu-progress-value" class="float-right"></span>
+            <div class="progress">
+              <div id="swu-progress-bar" class="progress-bar progress-bar-striped" role="progressbar"></div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- Messages -->
+    <div class="row my-3">
+      <div class="col">
+        <div class="card">
+          <div class="card-header" id="headingMessages">
+            <a class="h5 collapsed" data-toggle="collapse" href="#collapseMessages" aria-expanded="false" aria-controls="collapseMessages">
+              <i class="fas"></i> Messages
+            </a>
+          </div>
+          <div id="collapseMessages" class="collapse" aria-labelledby="headingMessages">
+            <div class="card-body" id="messages"></div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- Restart -->
+    <div id="swu-restart-modal" class="modal fade" tabindex="-1" role="dialog">
+      <div class="modal-dialog modal-dialog-centered" role="document">
+        <div class="modal-content">
+          <div class="modal-body p-3 mb-2">
+            <div class="media">
+              <i class="fas fa-spinner fa-spin fa-3x align-self-center mr-3"></i>
+              <div class="media-body align-self-center">
+                <h5 class="mt-0 mb-0">The system will restart. Please be patient, as restarting takes about one minute.</h5>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</body>
+
+</html>
diff --git a/www2/js/swupdate.js b/www2/js/swupdate.js
new file mode 100755
index 0000000..f89b47d
--- /dev/null
+++ b/www2/js/swupdate.js
@@ -0,0 +1,155 @@
+/*!
+ * Copyright (C) 2017-2018 Weidmüller Interface GmbH & Co. KG
+ * Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+/* global $, Dropzone, WebSocket */
+
+function restart () {
+  $.post('restart', {}, function (data) {
+    showRestart()
+  })
+}
+
+function showRestart () {
+  $('#swu-restart-modal').modal({backdrop: 'static', keyboard: false})
+  window.setTimeout(tryReload, 3000)
+}
+
+function tryReload () {
+  $.ajax({
+    cache: false,
+    timeout: 1000,
+    success: function (response) {
+      window.location.reload(true)
+    },
+    error: function (xhr, textStatus, errorThrown) {
+      if (textStatus === 'timeout') { tryReload() } else { window.setTimeout(tryReload, 1000) }
+    }
+  })
+}
+
+function updateStatus (status) {
+  $('#swu-idle').hide()
+  $('#swu-success').hide()
+  $('#swu-failure').hide()
+  $('#swu-done').hide()
+  $('#swu-run').hide()
+
+  switch (status) {
+    case 'IDLE':
+      $('#swu-idle').show()
+      break
+    case 'START':
+    case 'RUN':
+      $('#swu-run').show()
+      break
+    case 'SUCCESS':
+      $('#swu-success').show()
+      break
+    case 'FAILURE':
+      $('#swu-failure').show()
+      break
+    case 'DONE':
+      $('#swu-done').show()
+      break
+    default:
+      break
+  }
+}
+
+var updateProgressBarStatus = (function (status) {
+  var s = ''
+
+  return function (status) {
+    $('#swu-progress-bar')
+    .removeClass('bg-danger bg-success progress-bar-animated')
+    $('#swu-progress-spinner')
+    .removeClass('fa-spinner fa-spin')
+    $('#swu-progress-run').hide()
+
+    switch (status) {
+      case 'START':
+        updateProgressBar(0, '', '')
+        break
+      case 'RUN':
+        $('#swu-progress-bar').addClass('progress-bar-animated')
+        $('#swu-progress-spinner')
+          .addClass('fa-spinner fa-spin')
+        $('#swu-progress-run').show()
+        break
+      case 'SUCCESS':
+        $('#swu-progress-bar')
+          .addClass('bg-success')
+        break
+      case 'FAILURE':
+        if (s !== 'START' || s !== 'RUN') { updateProgressBar(0, '', '') }
+        $('#swu-progress-bar')
+          .addClass('bg-danger')
+        break
+      default:
+        break
+    }
+    s = status
+  }
+})()
+
+function updateProgressBar (percent, name, value) {
+  $('#swu-progress-value').text(value)
+  $('#swu-progress-name').text(name)
+  $('#swu-progress-bar')
+    .css('width', percent + '%')
+    .attr('aria-valuenow', percent)
+}
+
+Dropzone.options.dropzone = {
+  timeout: 0,
+  clickable: true,
+  acceptedFiles: '.swu'
+}
+
+window.onload = function () {
+  var protocol
+
+  $('#swu-restart').click(restart)
+
+  if (window.location.protocol === 'https:') { protocol = 'wss:' } else { protocol = 'ws:' }
+
+  var ws = new WebSocket(protocol + '//' + window.location.host + '/ws')
+
+  ws.onopen = function (event) {
+    updateStatus('IDLE')
+  }
+
+  ws.onclose = function (event) {
+    showRestart()
+  }
+
+  ws.onmessage = function (event) {
+    var msg = JSON.parse(event.data)
+
+    switch (msg.type) {
+      case 'message':
+        var p = $('<p></p>')
+        p.text(msg.text)
+        p.addClass('mb-1')
+        if (msg.level <= 3) { p.addClass('text-danger') }
+        $('#messages').append(p)
+        break
+      case 'status':
+        updateStatus(msg.status)
+        updateProgressBarStatus(msg.status)
+        break
+      case 'source':
+        break
+      case 'step':
+        var percent = Math.round((100 * (Number(msg.step) - 1) + Number(msg.percent)) / Number(msg.number))
+        var value = percent + '%' + ' (' + msg.step + ' of ' + msg.number + ')'
+
+        updateProgressBar(percent, msg.name, value)
+        break
+    }
+  }
+}
diff --git a/www2/package.json b/www2/package.json
new file mode 100755
index 0000000..7ded5d0
--- /dev/null
+++ b/www2/package.json
@@ -0,0 +1,47 @@
+{
+  "name": "swupdate",
+  "version": "0.1.0",
+  "description": "Software Update for Embedded Systems Web Front End",
+  "main": "index.html",
+  "scripts": {
+    "htmlhint": "htmlhint *.html",
+    "standard": "standard --fix *.js",
+    "lint": "standard --fix && htmlhint && sass-lint -v scss/* && csslint css/*",
+    "build": "gulp build",
+    "package": "gulp package",
+    "clean": "gulp clean"
+  },
+  "author": "Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>",
+  "license": "MIT",
+  "devDependencies": {
+    "csslint": "^1.0.5",
+    "del": "^3.0.0",
+    "gulp": "^3.9.1",
+    "gulp-clean-css": "^3.9.0",
+    "gulp-filter": "^5.1.0",
+    "gulp-gzip": "^1.4.1",
+    "gulp-html-replace": "^1.6.2",
+    "gulp-htmlmin": "^4.0.0",
+    "gulp-imagemin": "^4.1.0",
+    "gulp-minify": "^2.1.0",
+    "gulp-rename": "^1.2.2",
+    "gulp-replace": "^0.6.1",
+    "gulp-sass": "^3.1.0",
+    "gulp-tar": "^2.1.0",
+    "gulp-useref": "^3.1.4",
+    "htmlhint": "^0.9.13",
+    "sass-lint": "^1.12.1",
+    "standard": "^10.0.3"
+  },
+  "dependencies": {
+    "@fortawesome/fontawesome-free-webfonts": "^1.0.3",
+    "bootstrap": "^4.0.0",
+    "dropzone": "5.2.0",
+    "jquery": "^3.3.1"
+  },
+  "standard": {
+    "ignore": [
+      "dist"
+    ]
+  }
+}
diff --git a/www2/scss/bootstrap.scss b/www2/scss/bootstrap.scss
new file mode 100644
index 0000000..c4a4dad
--- /dev/null
+++ b/www2/scss/bootstrap.scss
@@ -0,0 +1,18 @@
+/*!
+ * Copyright (C) 2017-2018 Weidmüller Interface GmbH & Co. KG
+ * Stefan Herbrechtsmeier <stefan.herbrechtsmeier@weidmueller.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+$theme-colors: (
+);
+
+$transparent: rgba(255, 255, 255, .75);
+
+$jumbotron-bg: $transparent;
+$card-bg: $transparent;
+
+$progress-height: 1.5rem !default;
+
+@import 'node_modules/bootstrap/scss/bootstrap';
