Browse Source

Init dashboard

Juergen 'eTM' Mangler 2 years ago
commit
08016933a1
11 changed files with 1591 additions and 0 deletions
  1. 529 0
      dashboard
  2. 1 0
      dashboard.conf
  3. 146 0
      dashboard.xml
  4. 80 0
      defaultContent/index.html
  5. 470 0
      defaultContent/js/ui.js
  6. 7 0
      defaultContent/ui.css
  7. 79 0
      defaultContent/visus.html
  8. 61 0
      template/index.html
  9. 27 0
      template/js/config.js
  10. 115 0
      template/js/ui.js
  11. 76 0
      template/template.html

+ 529 - 0
dashboard

@@ -0,0 +1,529 @@
+#!/usr/bin/ruby
+#
+# This file is part of centurio.work/ing/commands.
+#
+# centurio.work/ing/commands is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# centurio.work/ing/commands is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# centurio.work/ing/commands (file COPYING in the main directory). If not, see
+# <http://www.gnu.org/licenses/>.
+
+require 'rubygems'
+require 'json'
+require 'xml/smart'
+require 'riddl/server'
+require 'fileutils'
+require 'typhoeus'
+require 'sqlite3'
+
+
+class ConfigSites < Riddl::Implementation
+  def response  
+    Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','index.html')))
+  end
+end
+
+
+
+
+class GetAllConfigs < Riddl::Implementation
+  def response  
+    Dir.chdir( __dir__  + '/data')
+    Riddl::Parameter::Complex.new('list','application/json',JSON::pretty_generate(Dir.glob('*/').sort_by{|x| x.downcase}))
+  end
+end
+
+
+class GetAllVisus < Riddl::Implementation
+  def response  
+  
+    Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'data', @r[-2],'visus.html')))
+  end
+end
+
+
+class Get < Riddl::Implementation
+  def response
+    #Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','template.html')))
+    Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'data', @r.last,'index.html')))
+  end
+end
+
+
+
+class GetAllData < Riddl::Implementation
+  def response
+    
+    fname = File.join('data',@r[-2],'data.db')
+    db = SQLite3::Database.open fname
+    if !db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='Entries';").empty?
+
+    
+      #db.execute "CREATE TABLE IF NOT EXISTS Entries(seriennummer TEXT PRIMARY KEY, produktcode TEXT, activity TEXT, cpeeInstance INTEGER)"
+
+      db.results_as_hash = true
+      alldata = {};
+      alldata["data"] = (db.execute "Select * from Entries ORDER BY __orderID__ ASC")
+    
+    
+      Riddl::Parameter::Complex.new('value','application/json',JSON.dump(alldata))
+    
+    else 
+      Riddl::Parameter::Complex.new('value','application/json',JSON.dump({}))
+    
+    end
+    
+  end
+end
+
+
+class GetNData < Riddl::Implementation
+  def response    
+    fname = File.join('data',@r[-2],'data.db')
+    db = SQLite3::Database.open fname
+    if !db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='Entries';").empty?
+      db.results_as_hash = true
+      alldata = {};
+      alldata["data"] = (db.execute "Select * from Entries ORDER BY __orderID__ ASC LIMIT 1 OFFSET " + @r[-1])
+      Riddl::Parameter::Complex.new('value','application/json',JSON.dump(alldata))
+    else 
+      Riddl::Parameter::Complex.new('value','application/json',JSON.dump({}))
+    end
+  end
+end
+
+class DeleteNData < Riddl::Implementation
+  def response
+    fname = File.join('data',@r[-2],'data.db')
+    db = SQLite3::Database.open fname
+    if !db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='Entries';").empty?
+      db.results_as_hash = true
+      alldata = {};
+      alldata["data"] = (db.execute "Select * from Entries ORDER BY __orderID__ ASC LIMIT 1 OFFSET " + @r[-1])
+      if alldata["data"] != []
+      
+      
+        id = alldata["data"][0]["__orderID__"].to_s
+        db.execute("DELETE FROM Entries WHERE __orderID__ = '" + id + "'")
+      end
+    else 
+      nil
+    end
+    @a[0].send("reset")
+  end
+end
+
+# used when executed from e.g. website, in such a case no cpee instance is given
+class AddEntry < Riddl::Implementation
+  def response
+    newEntry = JSON.parse(@p[0].value.read)
+    
+    fname = File.join('data',@r.last,'data.db')
+    db = SQLite3::Database.open fname
+    
+    # in case primary key is given
+    # PRIMARY KEY   Momentan noch nicht implementiert
+    if newEntry.has_key?("pkvalue")
+      db.execute "CREATE TABLE IF NOT EXISTS Entries(__orderID__ INTEGER PRIMARY KEY AUTOINCREMENT, alldata TEXT, pkvalue TEXT, cpeeInstance INTEGER)"
+      db.execute("INSERT OR REPLACE INTO Entries (alldata, pkvalue, cpeeInstance) VALUES (?,?,?)",JSON.dump(newEntry["alldata"]), newEntry["pkvalue"], "Direkt")
+      hash = {__orderID__: db.execute("select last_insert_rowid()")[0], alldata: JSON.dump(newEntry["alldata"]), pkvalue: newEntry["pkvalue"], cpeeInstance: "Direkt"};
+    else
+      db.execute "CREATE TABLE IF NOT EXISTS Entries(__orderID__ INTEGER PRIMARY KEY AUTOINCREMENT, alldata TEXT, cpeeInstance INTEGER)"
+      db.execute("INSERT OR REPLACE INTO Entries (alldata, cpeeInstance) VALUES (?,?)",JSON.dump(newEntry["alldata"]), "Direkt")
+      hash = {__orderID__: db.execute("select last_insert_rowid()")[0], alldata: JSON.dump(newEntry["alldata"]), cpeeInstance: "Direkt"};
+    end
+    
+    
+    
+    @a[0].send(JSON.dump(hash))
+    nil
+    
+  end
+end
+
+class Put < Riddl::Implementation
+  def self::putentry(r, p, h)
+  
+    Dir.mkdir(File.join('data',r.last)) rescue nil
+    Dir.mkdir(File.join('data',r.last, "js")) rescue nil
+    Dir.mkdir(File.join('data',r.last, "visus")) rescue nil
+    
+    
+    #prevent overriding ui and index in case user changed something there
+    if !File.file?(File.join('data',r.last,'js/ui.js'))
+      File.write(File.join('data',r.last,'js/ui.js'),File.open("defaultContent/js/ui.js").read)
+    end
+    
+    if !File.file?(File.join('data',r.last,'index.html'))
+      file = File.open("defaultContent/index.html").read
+      newfile = file.gsub("!replaceThisString!", r.last)
+
+      File.write(File.join('data',r.last,'index.html'), newfile)
+    end
+    
+    #prevent overriding visualization
+    if !File.file?(File.join('data',r.last,'visus.html'))
+      File.write(File.join('data',r.last,'visus.html'),File.open("defaultContent/visus.html").read)
+    end
+    
+    
+    fname = File.join('data',r.last,'data.db')
+    db = SQLite3::Database.open fname
+    
+    
+    #in case primary key is given
+    if p[1] != nil 
+      keyfunction = ""
+      if p[2] != nil      
+        keyfunction = p[2].value
+      end
+      db.execute "CREATE TABLE IF NOT EXISTS Entries(__orderID__ INTEGER PRIMARY KEY AUTOINCREMENT, alldata TEXT, pkvalue TEXT " + keyfunction + ", cpeeInstance INTEGER)"
+      db.execute("INSERT OR REPLACE INTO Entries (alldata, pkvalue, cpeeInstance) VALUES (?,?,?)",p[0].value, p[1].value, h['CPEE_INSTANCE'])
+      hash = {__orderID__: db.execute("select last_insert_rowid()")[0], alldata: p[0].value, pkvalue: p[1].value, cpeeInstance: h['CPEE_INSTANCE']};
+    else
+      db.execute "CREATE TABLE IF NOT EXISTS Entries(__orderID__ INTEGER PRIMARY KEY AUTOINCREMENT, alldata TEXT, cpeeInstance INTEGER)"
+      db.execute("INSERT OR REPLACE INTO Entries (alldata, cpeeInstance) VALUES (?,?)",p[0].value, h['CPEE_INSTANCE'])
+      hash = {__orderID__: db.execute("select last_insert_rowid()")[0], alldata: p[0].value, cpeeInstance: h['CPEE_INSTANCE']};
+    end
+    hash;
+  end
+  
+  
+  def response  
+    hash = Put::putentry(@r, @p, @h)
+    @a[0].send(JSON.dump(hash))
+    nil
+  end
+end
+
+
+class PutPrio < Riddl::Implementation
+  def response
+    hash = Put::putentry(@r, @p, @h) #add entry    
+    hash = Move::moveentry(hash[:__orderID__][0], 0,@r) #move to front
+    @a[0].send("reset")
+    nil
+  end
+end
+
+
+class PutPrioN < Riddl::Implementation
+  def response
+    
+    #move all back
+    fname = File.join('data',@r.last,'data.db')
+    db = SQLite3::Database.open fname
+    #Updating entries directly + X would result in unique constraint failed
+    #Therefore updates first to negative values then *-1
+    db.execute "UPDATE Entries SET __orderID__= 0 - (__orderID__ + " + @p[1].value  + ")"
+    db.execute "UPDATE Entries SET __orderID__= __orderID__ *-1"
+
+    #Add entries
+    i = 0
+    while i <  @p[1].value.to_i  do
+      #db.execute("INSERT OR REPLACE INTO Entries (__orderID__, alldata, cpeeInstance) VALUES (?,?,?)",i, @p[0].value, @h['CPEE_INSTANCE'])
+      
+      #in case primary key is given
+      if @p[2] != nil
+        db.execute("INSERT OR REPLACE INTO Entries (__orderID__, alldata, pkvalue, cpeeInstance) VALUES (?,?,?,?)",i, @p[0].value, @p[2].value, @h['CPEE_INSTANCE'])
+      else
+        db.execute("INSERT OR REPLACE INTO Entries (__orderID__, alldata, cpeeInstance) VALUES (?,?,?)",i, @p[0].value, @h['CPEE_INSTANCE'])
+      end
+      i +=1
+    end
+    
+    @a[0].send("reset")
+    nil
+  end
+end
+
+
+
+class Move < Riddl::Implementation
+  def self::moveentry(from, to,r)
+    fname = File.join('data',r.last,'data.db')
+    db = SQLite3::Database.open fname
+    
+        
+    #getMaxID
+    result = db.execute "SELECT MAX(__orderID__) FROM Entries"
+    if(result[0][0] == nil)
+      maxImgId = 0
+      else
+      maxImgId = result[0][0] +1
+    end
+    
+    i = 0
+    if to < from
+      while to < from do
+      
+        #nutze max ID zum tauschen
+        db.execute("UPDATE Entries SET __orderID__ = ? WHERE  __orderID__ = ?", [maxImgId, from - 1])
+        db.execute("UPDATE Entries SET __orderID__ = ? WHERE  __orderID__ = ?", [from - 1, from])
+        db.execute("UPDATE Entries SET __orderID__ = ? WHERE  __orderID__ = ?", [from, maxImgId])
+        
+        from -=1
+      end
+    elsif from < to
+    
+      while from < to do
+      
+        #nutze max ID zum tauschen
+        db.execute("UPDATE Entries SET __orderID__ = ? WHERE  __orderID__ = ?", [maxImgId, from + 1])
+        db.execute("UPDATE Entries SET __orderID__ = ? WHERE  __orderID__ = ?", [from + 1, from])
+        db.execute("UPDATE Entries SET __orderID__ = ? WHERE  __orderID__ = ?", [from, maxImgId])
+        
+        from +=1
+      end
+    
+    end
+    nil
+  end
+  
+  def response
+    movement = JSON.parse(@p[0].value.read)
+
+    from = movement["From"]
+    to = movement["To"]
+    hash = Move::moveentry(from, to,@r)
+    nil
+  end
+end
+
+class Update < Riddl::Implementation
+  def response
+    fname = File.join('data',@r.last,'data.db')
+    db = SQLite3::Database.open fname
+    
+    updateData = JSON.parse(@p[0].value.read)
+    
+    orderID = updateData["ID"]
+    alldata = JSON.generate(updateData["alldata"])
+    db.execute("UPDATE Entries SET alldata = ? WHERE  __orderID__ = ?", [alldata, orderID])
+    nil
+  end
+end
+
+
+class Delete < Riddl::Implementation
+  def response
+    fname = File.join('data',@r.last,'data.db')
+    db = SQLite3::Database.open fname
+    alldata = @p[0].value.read
+    db.execute("DELETE FROM Entries WHERE alldata = '" + alldata + "'")
+    nil
+  end
+end
+
+class DeleteID < Riddl::Implementation
+  def self::remove(dbfolder, id)
+  
+    fname = File.join('data',dbfolder,'data.db')
+    db = SQLite3::Database.open fname
+    db.execute("DELETE FROM Entries WHERE __orderID__ = '" + id.to_s + "'")
+  
+  end
+  def response
+    DeleteID::remove(@r.last, @p[0].value.read)
+    nil
+  end
+end
+
+class DeleteIDDirect < Riddl::Implementation
+  def response
+    DeleteID::remove(@r.last, @p[0].value)
+    nil
+  end
+end
+
+
+class DeletePK < Riddl::Implementation
+  def response
+  
+    fname = File.join('data',@r.last,'data.db')
+    db = SQLite3::Database.open fname
+    pkvalue = @p[0].value
+    db.execute("DELETE FROM Entries WHERE pkvalue = '" + pkvalue + "'")
+    
+    nil
+  end
+end
+
+
+class SearchPK < Riddl::Implementation
+  def response
+    
+    fname = File.join('data',@r[-4],'data.db')
+    db = SQLite3::Database.open fname
+    pkvalue = @r.last    
+    if !db.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='Entries';").empty?
+      db.results_as_hash = true
+      alldata = {};
+      alldata["data"] = (db.execute "Select * from Entries WHERE pkvalue = '" + pkvalue + "'")
+      Riddl::Parameter::Complex.new('value','application/json',JSON.dump(alldata))
+    else 
+      Riddl::Parameter::Complex.new('value','application/json',JSON.dump({}))
+    end
+  end
+end
+
+
+class GetNext < Riddl::Implementation
+  def response
+    fname = File.join('data',@r[-2],'data.db')
+    db = SQLite3::Database.open fname
+    
+    #get value
+    db.results_as_hash = true
+    alldata = {};
+    alldata["data"] = (db.execute "Select * from Entries ORDER BY __orderID__ ASC LIMIT 1")[0]
+    
+    Riddl::Parameter::Complex.new('value','application/json',JSON.dump(alldata))
+  end
+end 
+
+class DeleteNext < Riddl::Implementation
+  def response
+    fname = File.join('data',@r[-2],'data.db')
+    db = SQLite3::Database.open fname
+    
+    #get value
+    db.results_as_hash = true
+    alldata = {};
+    alldata["data"] = (db.execute "Select * from Entries ORDER BY __orderID__ ASC LIMIT 1")[0]
+    
+    #remove from DB
+    DeleteID::remove(@r[-2], alldata["data"]["__orderID__"])
+    
+    @a[0].send("reset")
+    Riddl::Parameter::Complex.new('value','application/json',JSON.dump(alldata))
+  end
+end 
+
+
+
+
+
+
+
+class SSE < Riddl::SSEImplementation #{{{
+  def onopen
+    signals = @a[0]
+    signals.add self
+    send 'started'
+    true
+  end
+
+  def onclose
+    signals = @a[0]
+    signals.remove self
+    nil
+  end
+end #}}}
+
+class Signaling # {{{
+  def initialize
+    @binding = []
+  end
+
+  def add(binding)
+    @binding << binding
+  end
+  def remove(binding)
+    @binding.delete(binding)
+  end
+  def length
+    @binding.length
+  end
+
+  def send(value)
+    @binding.each do |b|
+      b.send(value)
+    end
+  end
+end #}}}
+
+server = Riddl::Server.new(File.join(__dir__,'/dashboard.xml'), :host => 'localhost') do |opts|
+  accessible_description true
+  cross_site_xhr true
+
+  opts[:signals] = {}
+
+  parallel do
+    loop do
+      opts[:signals].each do |k,v|
+        v.send('keepalive')
+      end
+      sleep 5
+    end
+  end
+
+  on resource do
+    run ConfigSites if get
+    
+      on resource 'getconfigs' do
+        run GetAllConfigs if get
+      end
+    
+    
+    
+    on resource do |r|
+      idx = r[:r][0]
+      opts[:signals][idx] ||= Signaling.new
+      
+      
+      run Get if get
+      run Put, opts[:signals][idx] if put 'input'
+      run PutPrio, opts[:signals][idx] if put 'inputprio'
+      run PutPrioN, opts[:signals][idx] if put 'inputNprio'
+      run Move if put 'move'
+      run Update if put 'update'
+      run AddEntry, opts[:signals][idx] if post 'directADD'
+      run Delete if delete 'deleteMsg'
+      run DeleteID if delete 'deleteByID'
+      run DeletePK if delete 'deletePK'
+      run DeleteIDDirect if delete
+      
+      
+      on resource '\d+' do
+        run GetNData, opts if get
+        run DeleteNData, opts[:signals][idx] if delete
+      end
+      
+      on resource 'sse' do
+        run SSE, opts[:signals][idx] if sse
+      end
+      
+      on resource 'data.db' do
+        run GetAllData if get
+      end
+      
+      
+      on resource 'search' do
+        on resource 'PK' do
+          on resource '.*' do
+            run SearchPK if get
+          end
+        end
+      end
+      
+      on resource 'visus' do
+        run GetAllVisus if get
+      end
+      
+      on resource 'next' do
+        run GetNext, opts[:signals][idx] if get
+        run DeleteNext, opts[:signals][idx] if delete
+      end
+      
+    end
+  end
+end.loop!

+ 1 - 0
dashboard.conf

@@ -0,0 +1 @@
+:port: 8307

+ 146 - 0
dashboard.xml

@@ -0,0 +1,146 @@
+<!--
+  This file is part of centurio.work/wiz.
+
+  centurio.work/wiz is free software: you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by the Free
+  Software Foundation, either version 3 of the License, or (at your option) any
+  later version.
+
+  centurio.work/wiz is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+  more details.
+
+  You should have received a copy of the GNU General Public License along with
+  centurio.work/wiz (file LICENSE in the main directory). If not, see
+  <http://www.gnu.org/licenses/>.
+-->
+
+<description xmlns="http://riddl.org/ns/description/1.0" xmlns:ann="http://riddl.org/ns/annotation/1.0" xmlns:xi="http://www.w3.org/2001/XInclude" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+  <message name="input">
+    <parameter name="alldata" type="string"/>
+    <optional>
+      <parameter name="primarykey" type="string"/>
+      <optional>
+        <parameter name="primarykeyConstraint" type="string"/>
+      </optional>
+    </optional>
+  </message>
+  <message name="inputprio">
+    <parameter name="inputprio" type="string"/>
+    <optional>
+      <parameter name="primarykey" type="string"/>
+    </optional>
+  </message>
+  <message name="inputNprio">
+    <parameter name="inputprio" type="string"/>
+    <parameter name="anzahl" type="integer"/>
+    <optional>
+      <parameter name="primarykey" type="string"/>
+    </optional>
+  </message>
+  <message name="deletePK">
+    <parameter name="deletePK" type="string"/>
+  </message>
+  <message name="deleteMsg">
+    <parameter name="value" mimetype="application/json"/>
+    <optional>
+      <parameter name="primarykey" type="string"/>
+    </optional>
+  </message>  
+  <message name="ui">
+    <parameter name="ui" mimetype="text/html"/>
+  </message>
+  <message name="url">
+    <parameter name="url" mimetype="text/html"/>
+  </message>
+  <message name="htmlform">
+    <parameter name="htmlform" mimetype="text/plain"/>
+  </message>
+  <message name="callback">
+    <header name="CPEE-CALLBACK" type="boolean"/>
+  </message>
+  <message name="text">
+    <parameter name="text" mimetype="text/plain"/>
+  </message>
+  <message name="lang">
+    <parameter name="lang" type="string"/>
+  </message>
+  <message name="opa">
+    <parameter name="op" fixed="error"/>
+    <parameter name="reason" type="string"/>
+  </message>
+  <message name="opb">
+    <parameter name="op" fixed="result"/>
+    <parameter name="value" mimetype="application/json"/>
+  </message>
+  <message name="json">
+    <parameter name="value" mimetype="application/json"/>
+  </message>
+  <message name="form">
+    <parameter name="form" mimetype="application/json"/>
+  </message>
+  <message name="move">
+    <parameter name="move" mimetype="application/json"/>
+  </message>
+  <message name="update">
+    <parameter name="update" mimetype="application/json"/>
+  </message>
+  <message name="directADD">
+    <parameter name="directADD" mimetype="application/json"/>
+  </message>
+  <message name="deleteByID">
+    <parameter name="deleteByID" mimetype="application/json"/>
+  </message>
+  
+  
+  
+  <resource>
+    <get out="ui"/>
+    <resource relative='getconfigs'>
+      <get out="json"/>
+    </resource>
+    <resource>
+      <get out="ui"/>
+      <put in='input' />
+      <put in='inputprio' />
+      <put in='inputNprio' />
+      <put in='move' />
+      <put in='update' />
+      <post in='directADD' />
+      <delete in='deleteMsg' />
+      <delete in='deleteByID' />
+      <delete in='deletePK' />
+      <delete />
+      
+      <resource relative="\d+">
+        <get out="json"/>
+        <delete />
+      </resource>
+      <resource relative='sse'>
+        <sse/>
+      </resource>
+      <resource relative='data.db'>
+        <get out="json"/>
+      </resource>
+      
+      <resource relative='search'>
+        <resource relative='PK'>
+          <resource relative=".*">
+            <get out="json"/>
+          </resource>
+        </resource>
+      </resource>
+      
+      <resource relative='visus'>
+        <get out="ui"/>
+      </resource>
+      <resource relative='next'>
+        <get out="json"/>
+        <delete out="json"/>
+      </resource>
+    </resource>
+      
+      
+  </resource>
+</description>

+ 80 - 0
defaultContent/index.html

@@ -0,0 +1,80 @@
+<!--
+  This file is part of centurio.work/out/frame.
+
+  centurio.work/out/frame is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by the Free
+  Software Foundation, either version 3 of the License, or (at your option) any
+  later version.
+
+  centurio.work/out/frame is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+  more details.
+
+  You should have received a copy of the GNU General Public License along with
+  centurio.work/out/frame (file LICENSE in the main directory). If not, see
+  <http://www.gnu.org/licenses/>.
+-->
+
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+    <title>Dashboard</title>
+
+    <!-- libs, do not modify. When local than load local libs. -->
+    <script type="text/javascript" src="/js_libs/jquery.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.browser.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.svg.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.svgdom.min.js"></script>
+    <script type="text/javascript" src="/js_libs/vkbeautify.js"></script>
+    <script type="text/javascript" src="/js_libs/util.js"></script>
+    <script type="text/javascript" src="/js_libs/printf.js"></script>
+    <script type="text/javascript" src="/js_libs/strftime.min.js"></script>
+    <script type="text/javascript" src="/js_libs/parsequery.js"></script>
+    <script type="text/javascript" src="/js_libs/underscore.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.caret.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.cookie.js"></script>
+
+    <!-- custom stuff, play arround  -->
+    <script type="text/javascript" src="../data/dashboardAnstehende/js/ui.js"></script>
+    <link rel="stylesheet" href="../data/dashboardAnstehende/ui.css" type="text/css"/>
+    <link class='custom' rel="stylesheet" href="" type="text/css"/>
+    <script>
+      if (location.href.match(/\/$/) == null) {
+        location.href = location.href + '/';
+      }
+    </script>
+<style>
+
+a {  margin: 0.5em; height: 5em; border: 0.3em solid black; border-radius: 50%; background-color: rgb(252, 229, 0); font-weight: bold; font-size: 1.3em; padding: 0.4em 0.5em 0.4em 0.5em;}
+
+a, a:link, a:visited, a:hover, a:active {
+  text-decoration: none;
+  color: black;
+}
+
+tr {height: 5em;}
+
+.menge {font-weight: bold; font-size: 1.3em;}
+.posbez {padding-left: 2em;}
+.tableheader {font-weight: bold; text-align: center; font-size: 1.3em; margin-top:1em;}
+
+.tableentryred{background-color: red;}  /*Airkey*/
+
+table{
+  border-spacing: 0px;
+}
+</style>
+  </head>
+  <body is="x-ui">
+    <div id="container">
+	
+      <div id="data">
+        
+      </div>
+	  
+    </div>
+
+  </body>
+</html>

+ 470 - 0
defaultContent/js/ui.js

@@ -0,0 +1,470 @@
+
+
+function getAllData(){
+  $( "#data" ).empty();
+  
+
+
+  var url_string = window.location.href
+  var url = new URL(url_string);
+  if(!url.searchParams.has("display")){
+    var urlarray = ["aktivieren","aktivierenPrio","stornieren","moveTop","moveX","edit"]
+  }else{
+
+    var urlparams = url.searchParams.get("display").replace("/","");
+    var urlarray = urlparams.split(",");
+  }
+
+  $.ajax({
+    type: "GET",
+    url: 'data.db',
+    success: function(ret) {      
+      if(!$(ret.data).length == 0){
+        container = document.getElementById("data");
+        entry = document.createElement("div");
+        
+        tablehead = "<table><tbody id='tablecontent'><tr>";
+                
+        urlarray.forEach(function(element) { tablehead += "<th></th>" });
+        
+        
+        if(ret.data[0].hasOwnProperty('pkvalue')){
+          tablehead += "<th class='primarykey'>Primary Key</th>";
+        }
+        
+        var joinkey = urlarray.length;
+        
+        for (var i = 0; i < Object.keys(JSON.parse(ret.data[0].alldata)).length; i++) {
+          tablehead += "<th class='editable'>" + Object.keys(JSON.parse(ret.data[0].alldata))[i]
+          
+          if(url.searchParams.has("showfilter")){
+            tablehead +=  '<br><input type="text" id="searchtable' + ++joinkey +'" onkeyup="searchtable(' + joinkey + ')" placeholder="Search for ' + Object.keys(JSON.parse(ret.data[0].alldata))[i] + '">'
+          }
+         tablehead += '</th>'
+        }
+        tablehead += "</tr></tbody></table>";
+        
+        $(entry).html(tablehead); 
+        $(container).append(entry);
+        
+        
+        container = document.getElementById("tablecontent");
+        entry = document.createElement("tr");
+        $(entry).attr("id","AddEntry");
+        $(entry).attr("class","tableentry");
+        
+        
+        tableedit = "<td class='addEntry'><a onclick='addEntry()' href='#'>Add</a></td>"
+        urlarray.forEach(function(element) {  });
+        
+        for (var i = 1; i < urlarray.length; i++) { //start at 1 as add is already set
+          tableedit += "<td></td>"
+        }
+        
+        if(ret.data[0].hasOwnProperty('pkvalue')){
+          tableedit += "<td class='editableprimarykey'>Primary Key</td>";
+        }
+        tableedit += "<td class='editable'>" + Object.keys(JSON.parse(ret.data[0].alldata)).join("</td><td class='editable'>")  + "</td>"
+        
+        $(entry).append(tableedit); 
+        $("#tablecontent").append(entry); 
+        
+        
+        $.each(ret.data, function(i, item) {
+          setSingleData(item, urlarray, i, url);
+        });
+      }
+      
+    }
+    
+  });
+}
+function searchtable(key) {
+  console.log("AAA");
+  // Declare variables
+  var input, filter, table, tr, td, i, txtValue;
+  input = document.getElementById("searchtable" +key);
+  filter = input.value.toUpperCase();
+  table = document.getElementById("tablecontent");
+  tr = table.getElementsByTagName("tr");
+
+  // Loop through all table rows, and hide those who don't match the search query
+  for (i = 0; i < tr.length; i++) {
+    td = tr[i].getElementsByTagName("td")[key];
+    if (td) {
+      txtValue = td.textContent || td.innerText;
+      if (txtValue.toUpperCase().indexOf(filter) > -1) {
+        tr[i].style.display = "";
+      } else {
+        tr[i].style.display = "none";
+      }
+    }
+  }
+}
+function sortTable(n) {
+  var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
+  table = document.getElementById("tablecontent");
+  switching = true;
+  // Set the sorting direction to ascending:
+  dir = "asc";
+  /* Make a loop that will continue until
+  no switching has been done: */
+  while (switching) {
+    // Start by saying: no switching is done:
+    switching = false;
+    rows = table.rows;
+    /* Loop through all table rows (except the
+    first, which contains table headers): */
+    for (i = 1; i < (rows.length - 1); i++) {
+      // Start by saying there should be no switching:
+      shouldSwitch = false;
+      /* Get the two elements you want to compare,
+      one from current row and one from the next: */
+      x = rows[i].getElementsByTagName("TD")[n];
+      y = rows[i + 1].getElementsByTagName("TD")[n];
+      /* Check if the two rows should switch place,
+      based on the direction, asc or desc: */
+      if (dir == "asc") {
+        if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
+          // If so, mark as a switch and break the loop:
+          shouldSwitch = true;
+          break;
+        }
+      } else if (dir == "desc") {
+        if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
+          // If so, mark as a switch and break the loop:
+          shouldSwitch = true;
+          break;
+        }
+      }
+    }
+    if (shouldSwitch) {
+      /* If a switch has been marked, make the switch
+      and mark that a switch has been done: */
+      rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
+      switching = true;
+      // Each time a switch is done, increase this count by 1:
+      switchcount ++;
+    } else {
+      /* If no switching has been done AND the direction is "asc",
+      set the direction to "desc" and run the while loop again. */
+      if (switchcount == 0 && dir == "asc") {
+        dir = "desc";
+        switching = true;
+      }
+    }
+  }
+}
+
+
+
+function deleteDataElement(dataelement){
+  console.log("Löschen")
+  console.log(dataelement)
+  $.ajax({
+    type: "DELETE",
+    url: location.protocol + '//' + location.host + location.pathname,
+    contentType: "application/json",
+		headers: {"content-id": "deleteByID"},
+    data: JSON.stringify(dataelement),
+    success: function(ret) {
+      getAllData();
+    }
+  });
+}
+
+//if used in frames send row to frames
+function sendDataElementBack(dataelement, type){
+  parent.sendJson(window.name,{ type: type, dataelement: dataelement })
+  //deleteDataElement(dataelement.__orderID__)
+}
+
+
+
+function move(from, to){
+  $.ajax({
+    type: "PUT",
+    url: location.protocol + '//' + location.host + location.pathname,
+		headers: {"content-id": "move"},
+    contentType: "application/json",
+    data: JSON.stringify({"From": from, "To": to}),
+    success: function(ret) {
+      getAllData();
+    }
+  });
+}
+
+
+
+function setSingleData(singleData, urlarray, i = -1, url = ""){
+  
+  container = document.getElementById("tablecontent");
+  entry = document.createElement("tr");
+  $(entry).attr("id",singleData.__orderID__);
+  $(entry).attr("class","tableentry");
+  
+  
+  
+  
+  if(url.searchParams.has("colorize")){
+    if(i == 0){
+      var tableheader = url.searchParams.has("colorize") ? url.searchParams.get("colorize").replace("/","") : "";
+      var colorizestart = url.searchParams.has("colorize2") ? url.searchParams.get("colorizestart").replace("/","") : "";
+      var colorizecol = url.searchParams.has("colorize2") ? url.searchParams.get("colorizecol").replace("/","") : "";
+      
+      var table2header = url.searchParams.has("colorize2") ? url.searchParams.get("colorize2").replace("/","") : "";
+      var colorize2start = url.searchParams.has("colorize2start") ? url.searchParams.get("colorize2start").replace("/","") : "";
+      var colorize2col = url.searchParams.has("colorize2col") ? url.searchParams.get("colorize2col").replace("/","") : "";
+      
+      if(JSON.parse(singleData.alldata).hasOwnProperty(tableheader)){
+        if(JSON.parse(singleData.alldata)[tableheader].startsWith(colorizestart)){
+          $(entry).attr("style","background-color: #" + colorizecol);
+        }
+      }
+      if(JSON.parse(singleData.alldata).hasOwnProperty(table2header)){
+        if(JSON.parse(singleData.alldata)[table2header].startsWith(colorize2start)){
+          $(entry).attr("style","background-color: #" + colorize2col);
+        }
+      }
+    }
+  }
+  
+  tableentry = "";
+  if(urlarray.includes("aktivieren")){
+    tableentry += "<td><a onclick='sendDataElementBack(" + JSON.stringify(singleData) +", \"normal\")' href='#'>Aktivieren</a></td>"
+  }
+  if(urlarray.includes("aktivierenPrio")){
+    tableentry += "<td><a onclick='sendDataElementBack(" + JSON.stringify(singleData) +", \"prio\")' href='#'>Aktivieren Prio</a></td>"
+  }
+  if(urlarray.includes("stornieren")){
+    tableentry += "<td><a onclick='deleteDataElement(" + singleData.__orderID__ +")' href='#'>Stornieren</a></td>"
+  }
+  if(urlarray.includes("moveTop")){
+    tableentry += "<td><a onclick='move(" + singleData.__orderID__ +",0)' href='#'>MoveTop</a></td>"
+  }
+  if(urlarray.includes("moveX")){
+    tableentry += "<td><a onclick='move(" + singleData.__orderID__ +",7)' href='#'>MovetoX</a></td>"
+  }
+  if(urlarray.includes("edit")){
+    tableentry += "<td class='edit'><a onclick='edit(" + singleData.__orderID__ +")' href='#'>Edit</a></td>"
+  }
+
+
+  if(singleData.hasOwnProperty('pkvalue')){
+    tableentry += "<td>" + singleData.pkvalue + "</td>";
+  }
+  tableentry += "<td class='editable'>" +  Object.values(JSON.parse(singleData.alldata)).join("</td><td class='editable'>")  + "</td>"
+  $(entry).html(tableentry); 
+
+  $(entry).hide().fadeIn(2000);
+  $("#tablecontent tr:last").before(entry);
+}
+
+function getUpdateData(id){
+  //Json aufbauen
+  var i = 0;
+  var obj = {};
+  $("#tablecontent th[class='editable'").each(function( index ) {
+    
+    curname = $( this ).html();
+    curvalue = $("#" + id + " .editable:eq(" + i + ") input").val();
+    
+    if(isNaN(curvalue)){
+      obj[curname] = curvalue;
+    }else{
+      obj[curname] = parseInt(curvalue, 10);
+    }
+
+    ++i;
+  });  
+  console.log("TEST" + JSON.stringify(obj));
+  update(id, obj)
+}
+
+function update(id, obj){
+  $.ajax({
+    type: "PUT",
+    url: location.protocol + '//' + location.host + location.pathname,
+		headers: {"content-id": "update"},
+    contentType: "application/json",
+    data: JSON.stringify({"ID": id, "alldata": obj}),
+    success: function(ret) {
+      getAllData();
+    }
+  });
+  
+}
+
+function edit(id){
+  console.log("#" + id + " .editable");
+  
+  $("#" + id + " .edit").each(function( index ) {
+    $( this ).html("<a onclick='getUpdateData(" + id +")' href='#'>Update</a>");
+  });
+  
+  $("#" + id + " .editable").each(function( index ) {
+    if(isNaN($( this ).text())){
+      $( this ).html("<input type='text' value='" + $( this ).text() +"'>");
+    }else{
+      $( this ).html("<input type='number' value='" + $( this ).text() +"'>");
+    }
+  });
+}
+
+
+function addEntry() {
+    
+  $("#AddEntry .addEntry").each(function( index ) {
+    $( this ).html("<a onclick='getNewData(\"AddEntry\")' href='#'>Add</a>");
+  });
+  
+  $("#AddEntry .editableprimarykey").each(function( index ) {
+    $( this ).html("<input type='text' value='" + $( this ).text() +"'>");
+  });
+  
+  
+  $("#AddEntry .editable").each(function( index ) {
+    $( this ).html("<input type='text' value='" + $( this ).text() +"'>");
+  });
+  
+}
+
+function getNewData(id){
+  //Json aufbauen
+  var i = 0;
+  var obj = {};
+  $("#tablecontent th[class='editable'").each(function( index ) {
+    
+    curname = $( this ).html();
+    curvalue = $("#" + id + " .editable:eq(" + i + ") input").val();
+    
+    if(isNaN(curvalue)){
+      obj[curname] = curvalue;
+    }else{
+      obj[curname] = parseInt(curvalue, 10);
+    }
+
+    ++i;
+  });
+  
+  pk = $("#" + id + " .editableprimarykey input").val()
+  obj = {alldata: obj, pkvalue: pk}
+  addNewData(id, obj)
+}
+
+function addNewData(id, obj){
+
+  $.ajax({
+    type: "POST",
+    url: location.protocol + '//' + location.host + location.pathname,
+		headers: {"content-id": "directADD"},
+    contentType: "application/json",
+    data: JSON.stringify(obj),
+    success: function(ret) {
+      getAllData();
+    }
+  });
+  
+}
+
+function openlink(menuitem){
+  var menu = { name: menuitem };
+
+  $.ajax({
+    type: "PUT",
+    url: window.name,
+    contentType: "application/json",
+    data: JSON.stringify(menu),
+    success: function (data) {
+      
+    }
+  });
+}
+
+function showDocument() {
+  
+  $.ajax({
+    type: "GET",
+    url: location.protocol + '//' + location.host + location.pathname + 'style.url',
+    success: function(ret) {
+      $('head link.custom').attr('href',ret);
+    }
+  });
+  /*
+  $.ajax({
+    type: "GET",
+    url: 'info.json',
+    success: function(ret) {
+      makeGrid(ret.x_amount, ret.y_amount);
+      //set name
+      document.title = ret.document_name;
+      $.ajax({
+        type: "GET",
+        url: 'frames.json',
+        success: function(ret2) {
+          for (i of ret2.data) {
+            makeFrame(i.lx,i.ly,i.rx,i.ry, i.url, i.callback, i.default, i.showbutton, i.style);
+          } 
+        }
+      });
+    },
+    error: function() {
+      reason = '';
+      clearDocument();
+    }
+  });*/
+}
+
+function clearDocument() {
+  console.log('rrrr');
+  $('#languages').addClass('hidden');
+  $('#nope').removeClass('hidden');
+  $('#control .added').remove();
+  $('#content .added').remove();
+  $('#reason').text(reason);
+}
+
+
+function init() {
+  es = new EventSource('sse/');
+  es.onopen = function() {
+    getAllData();
+    // load
+  };
+  es.onmessage = function(e) {
+    console.log("Got SSE");
+    if (e.data == 'new') {
+      reason = '';
+      showDocument();
+    }
+    if (e.data == 'reset') {
+      reason = '';
+      showDocument();
+    }
+    else{
+      if(e.data == "update"){
+        alert("update")
+      }
+      if(e.data != "keepalive" && e.data != "started"){
+        try {
+          var prdata = JSON.parse(e.data)
+          //alert("LastIf" + prdata.seriennummer)
+          setSingleData(prdata);
+        }
+        catch (e) {
+        }
+      }
+    }
+  };
+  es.onerror = function() {
+    reason = 'Server down.';
+    clearDocument();
+    setTimeout(init, 10000);
+  };
+}
+
+
+$(document).ready(function() {
+  init();
+});
+

+ 7 - 0
defaultContent/ui.css

@@ -0,0 +1,7 @@
+table th {
+    position: -webkit-sticky;
+    position: sticky;
+    top: 0;
+    z-index: 1; 
+    background: #fff;
+}

+ 79 - 0
defaultContent/visus.html

@@ -0,0 +1,79 @@
+<!--
+  This file is part of centurio.work/out/frame.
+
+  centurio.work/out/frame is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by the Free
+  Software Foundation, either version 3 of the License, or (at your option) any
+  later version.
+
+  centurio.work/out/frame is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+  more details.
+
+  You should have received a copy of the GNU General Public License along with
+  centurio.work/out/frame (file LICENSE in the main directory). If not, see
+  <http://www.gnu.org/licenses/>.
+-->
+
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+    <title>Dashboard</title>
+
+    <!-- libs, do not modify. When local than load local libs. -->
+    <script type="text/javascript" src="/js_libs/jquery.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.browser.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.svg.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.svgdom.min.js"></script>
+    <script type="text/javascript" src="/js_libs/vkbeautify.js"></script>
+    <script type="text/javascript" src="/js_libs/util.js"></script>
+    <script type="text/javascript" src="/js_libs/printf.js"></script>
+    <script type="text/javascript" src="/js_libs/strftime.min.js"></script>
+    <script type="text/javascript" src="/js_libs/parsequery.js"></script>
+    <script type="text/javascript" src="/js_libs/underscore.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.caret.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.cookie.js"></script>
+
+    <!-- custom stuff, play arround  -->
+    <script type="text/javascript" src="../../data/eMonMain22/js/visus.js"></script>
+    <script src="https://d3js.org/d3.v7.min.js"></script>
+
+    <link rel="stylesheet" href="../../data/eMonMain22/ui.css" type="text/css"/>
+    <link class='custom' rel="stylesheet" href="" type="text/css"/>
+    <script>
+    </script>
+    <style>
+    /*
+    svg {
+  font: 10px sans-serif;
+}
+
+text.title {
+  font: 300 78px Helvetica Neue;
+  fill: #666;
+}
+
+.rule line {
+  stroke: #fff;
+  stroke-opacity: .2;
+  shape-rendering: crispEdges;
+}
+
+.rule:first-child line {
+  stroke: #000;
+  stroke-opacity: 1;
+}
+
+rect {
+  fill: #e377c2;
+}
+*/
+
+</style>
+  </head>
+  <body is="x-ui">
+    <div id="data2"></div>
+  </body>
+</html>

+ 61 - 0
template/index.html

@@ -0,0 +1,61 @@
+<!--
+  This file is part of centurio.work/out/frame.
+
+  centurio.work/out/frame is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by the Free
+  Software Foundation, either version 3 of the License, or (at your option) any
+  later version.
+
+  centurio.work/out/frame is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+  more details.
+
+  You should have received a copy of the GNU General Public License along with
+  centurio.work/out/frame (file LICENSE in the main directory). If not, see
+  <http://www.gnu.org/licenses/>.
+-->
+
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+    <title>Dashboard</title>
+
+    <!-- libs, do not modify. When local than load local libs. -->
+    <script type="text/javascript" src="/js_libs/jquery.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.browser.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.svg.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.svgdom.min.js"></script>
+    <script type="text/javascript" src="/js_libs/vkbeautify.js"></script>
+    <script type="text/javascript" src="/js_libs/util.js"></script>
+    <script type="text/javascript" src="/js_libs/printf.js"></script>
+    <script type="text/javascript" src="/js_libs/strftime.min.js"></script>
+    <script type="text/javascript" src="/js_libs/parsequery.js"></script>
+    <script type="text/javascript" src="/js_libs/underscore.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.caret.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.cookie.js"></script>
+
+    <!-- custom stuff, play arround  -->
+    <script type="text/javascript" src="js/config.js"></script>
+    <link rel="stylesheet" href="css/ui.css" type="text/css"/>
+    <link class='custom' rel="stylesheet" href="" type="text/css"/>
+    <script>
+      if (location.href.match(/\/$/) == null) {
+        location.href = location.href + '/';
+      }
+    </script>
+  </head>
+  <body is="x-ui">
+    Todo:
+    List all configs
+      Select config
+        Show visualizations
+        create new visualizations (with button omit null values)
+    
+  
+    <select id="selectfolders">
+    </select><br>
+    
+  </body>
+</html>

+ 27 - 0
template/js/config.js

@@ -0,0 +1,27 @@
+
+
+$(document).on('change', '#selectfolders', function(e) {
+  //getFolders(this.options[e.target.selectedIndex].text);
+  show
+});
+
+function getAllFolders(){ //Get DB 2 HTML //has to be synchronus in order to update before loading the rest
+	$('#selectfolders').find('option').remove()
+  $.ajax({
+	  type: "GET",
+	  url: "getconfigs",
+    async: false,
+	  dataType: "json",
+	  success: function(data) {
+      $.each( data, function( key, value ) {
+        $('#selectfolders').append("<option>" + value.slice(0,-1) +"</option>")
+        
+      });
+    }
+  });
+}
+
+$(document).ready(function() {
+  getAllFolders();
+});
+

+ 115 - 0
template/js/ui.js

@@ -0,0 +1,115 @@
+
+
+function getAllData(){
+  
+  $.ajax({
+    type: "GET",
+    url: 'data.db',
+    success: function(ret) {      
+      $.each(ret.data, function(i, item) {
+          setSingleData(item);
+      });
+    }
+  });
+}
+
+
+function setSingleData(singleData){
+  //remove data
+  $( "#" + singleData.seriennummer ).remove();
+
+  
+  container = document.getElementById(singleData.activity);
+  entry = document.createElement("div");
+  $(entry).attr("id",singleData.seriennummer);
+  $(entry).html("<span>Code: " + singleData.produktcode + "</span><span> SN: " + singleData.seriennummer + "</span>");
+
+  container.appendChild(entry);
+}
+
+function showDocument() {
+  
+  $.ajax({
+    type: "GET",
+    url: 'style.url',
+    success: function(ret) {
+      $('head link.custom').attr('href',ret);
+    }
+  });
+  /*
+  $.ajax({
+    type: "GET",
+    url: 'info.json',
+    success: function(ret) {
+      makeGrid(ret.x_amount, ret.y_amount);
+      //set name
+      document.title = ret.document_name;
+      $.ajax({
+        type: "GET",
+        url: 'frames.json',
+        success: function(ret2) {
+          for (i of ret2.data) {
+            makeFrame(i.lx,i.ly,i.rx,i.ry, i.url, i.callback, i.default, i.showbutton, i.style);
+          } 
+        }
+      });
+    },
+    error: function() {
+      reason = '';
+      clearDocument();
+    }
+  });*/
+}
+
+function clearDocument() {
+  console.log('rrrr');
+  $('#languages').addClass('hidden');
+  $('#nope').removeClass('hidden');
+  $('#control .added').remove();
+  $('#content .added').remove();
+  $('#reason').text(reason);
+}
+
+
+function init() {
+  es = new EventSource('sse/');
+  es.onopen = function() {
+    getAllData();
+    // load
+  };
+  es.onmessage = function(e) {
+    console.log("Got SSE");
+    if (e.data == 'new') {
+      reason = '';
+      showDocument();
+    }
+    if (e.data == 'reset') {
+      reason = '';
+      showDocument();
+    }
+    else{
+      if(e.data == "update"){
+        alert("update")
+      }
+      if(e.data != "keepalive" && e.data != "started"){
+        try {
+          var prdata = JSON.parse(e.data)
+          //alert("LastIf" + prdata.seriennummer)
+          setSingleData(prdata);
+        }
+        catch (e) {
+        }
+      }
+    }
+  };
+  es.onerror = function() {
+    reason = 'Server down.';
+    clearDocument();
+    setTimeout(init, 10000);
+  };
+}
+
+$(document).ready(function() {
+  init();
+});
+

+ 76 - 0
template/template.html

@@ -0,0 +1,76 @@
+<!--
+  This file is part of centurio.work/out/frame.
+
+  centurio.work/out/frame is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by the Free
+  Software Foundation, either version 3 of the License, or (at your option) any
+  later version.
+
+  centurio.work/out/frame is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+  more details.
+
+  You should have received a copy of the GNU General Public License along with
+  centurio.work/out/frame (file LICENSE in the main directory). If not, see
+  <http://www.gnu.org/licenses/>.
+-->
+
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+    <title>Dashboard</title>
+
+    <!-- libs, do not modify. When local than load local libs. -->
+    <script type="text/javascript" src="/js_libs/jquery.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.browser.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.svg.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.svgdom.min.js"></script>
+    <script type="text/javascript" src="/js_libs/vkbeautify.js"></script>
+    <script type="text/javascript" src="/js_libs/util.js"></script>
+    <script type="text/javascript" src="/js_libs/printf.js"></script>
+    <script type="text/javascript" src="/js_libs/strftime.min.js"></script>
+    <script type="text/javascript" src="/js_libs/parsequery.js"></script>
+    <script type="text/javascript" src="/js_libs/underscore.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.caret.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.cookie.js"></script>
+
+    <!-- custom stuff, play arround  -->
+    <script type="text/javascript" src="../js/ui.js"></script>
+    <link rel="stylesheet" href="../css/ui.css" type="text/css"/>
+    <link class='custom' rel="stylesheet" href="" type="text/css"/>
+    <script>
+      if (location.href.match(/\/$/) == null) {
+        location.href = location.href + '/';
+      }
+    </script>
+  </head>
+  <body is="x-ui">
+    <div id="container">
+      
+      <div id="station1">
+        <h1>Station1</h1>
+      </div>
+      
+      
+      <div id="station2">
+        <h1>Station2</h1>
+      
+      </div>
+      
+      
+      <div id="station3">
+        <h1>Station3</h1>
+      
+      </div>
+      
+      <div id="station4">
+        <h1>Station4</h1>
+      
+      </div>
+      
+    </div>
+
+  </body>
+</html>