added prototype web interface written in nim

This commit is contained in:
2017-09-24 17:20:28 +02:00
parent 1aecc6628e
commit bf8b765117
11 changed files with 359 additions and 29 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "nim/htmlutils"]
path = nim/htmlutils
url = ssh://git@nuc/mnt/git/htmlutils

5
nim/build.nims Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env nim
task build, "builds an example":
setCommand "js"
#os.copyfile("")

4
nim/build.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
set -e
nim js jpacrepo.nim
cp nimcache/jpacrepo.js static

1
nim/htmlutils Submodule

Submodule nim/htmlutils added at 37365137d3

269
nim/jpacrepo.nim Normal file
View File

@@ -0,0 +1,269 @@
import dom
import htmlutils.utils
import json
import streams
import tables
import strutils
import sets
from sequtils import map, apply
var pkgMap : OrderedTable[string, JsonNode]
type DownloadPanel = ref object
badge : Node
listgroup : Node
pkgs : HashSet[string]
body : Node
proc newDownloadPanel(parent : Node) : DownloadPanel =
let dp = DownloadPanel()
dp.pkgs = initSet[string]()
htmlDocument(document, parent):
"div":
class ["panel", "panel-default"]
"div":
class ["panel-heading"]
"h3":
class ["panel-title"]
"a":
#attrs {"data-toggle": "collapse", "href": "#dlist"}
text "Package list "
"span":
class ["badge", "pull-right"]
style {"display" :"none"}
cb:
dp.badge = node
"ul":
class ["list-group"]
cb:
dp.listgroup = node
"div":
class ["panel-body"]
style {
"text-align" : "right",
"display" :"none"
}
"button":
class ["btn", "btn-primary"]
attrs {"type" : "button"}
"span":
class ["glyphicon", "glyphicon-download"]
cb:
node.appendChild(document.createTextNode(" Download"))
cb:
dp.body = node
dp
proc updateBadge(dp : DownloadPanel) =
let st = if dp.pkgs.len() > 0: "block" else: "none"
dp.body.style.display = st
dp.badge.style.display = st
dp.badge.textContent = $dp.pkgs.len()
proc addPkg(dp : DownloadPanel, pkgfile : string) =
if not dp.pkgs.contains(pkgfile):
dp.pkgs.incl(pkgfile)
htmlDocument(document, dp.listgroup):
"li":
var listElement : Node
class ["list-group-item"]
text pkgfile
"span":
class ["glyphicon", "glyphicon-remove", "pull-right"]
cb:
let fn = proc(e : Event) =
dp.pkgs.excl(pkgfile)
listElement.remove()
dp.updateBadge
node.addEventListener("click", fn)
cb:
listElement = node
dp.updateBadge
proc createDropdown(parent : Node, data :seq[string], onchange : proc(value : string)) =
htmlDocument(document, parent):
"div":
var button : Node
class ["dropdown"]
"button":
class ["btn", "btn-default", "dropdown-toggle"]
attrs {"data-toggle": "dropdown", "type": "button"}
cb:
for line in data:
node.textContent = line
break
button = node
"ul":
class ["dropdown-menu"]
cb:
for line in data:
htmlDocument(document, node):
"li":
"a":
text line
cb:
let fn = proc(e: Event) =
button.textContent = node.textContent
onchange($node.textContent)
node.addEventListener("click", fn)
type PkgTable = ref object
addButton : Node
proc newPkgTable(parent: Node, searchString : string) : PkgTable =
var pkgtable = PkgTable()
var fragments = newSeq[string]()
for fragment in searchString.splitWhitespace():
fragments.add(fragment)
var searchResult = newOrderedTable[string,JsonNode]()
for key, value in pkgMap:
for fragment in fragments:
if fragment in key:
searchResult.add(key,value)
for table in document.querySelectorAll("table.pkgtable"):
table.parentNode.removeChild(table)
htmlDocument(document, parent):
"table":
class ["table", "table-striped","pkgtable"]
"thead":
"tr":
"th":
"button":
class ["btn", "btn-default"]
attrs {"type": "button"}
"span":
class ["glyphicon", "glyphicon-plus"]
cb:
let txt = document.createTextNode(" Add")
node.appendChild(txt)
pkgtable.addButton = node
"th":
text "Name"
"th":
text "Version"
"th":
text "Arch"
"tbody":
cb:
var i = 0
for name, versions in searchResult:
closureScope:
htmlDocument(document, node):
"tr":
var archCell : Node
"td":
"div":
class ["checkbox"]
"label":
"input":
attrs {"type" : "checkbox"}
"td":
text $name
"td":
cb:
var data = newSeq[string]()
for version, arches in versions:
data.add(version)
let vs = versions
let change_callback = proc(newValue : string) =
archCell.removeChildren()
var newdata = newSeq[string]()
for arch, pkgname in vs[newValue]:
newdata.add(arch)
createDropdown(archCell, newdata, proc(s : string) = discard)
createDropdown(node, data, change_callback)
"td":
cb:
archCell = node
var data = newSeq[string]()
var arches : JsonNode
for v, a in versions:
arches = a
for arch, pkgname in arches:
data.add(arch)
createDropdown(node, data, proc(s : string) = discard)
pkgtable
var dp : DownloadPanel
# var pkgTable : PkgTable
htmlDocument document, document.body:
# "img":
# attrs {"src" : "img/background.bpg"}
# style {
# "width" : "100%",
# "position" : "fixed",
# "bottom" : "0",
# "left" : "0",
# "z-index" : "-10"
# }
"div":
var table : Node
style {"background-color" : "rgba(255, 255, 255, 0.25)"}
class ["container"]
"div":
style {
"margin-top" : "20px",
"background-color" : "rgba(224, 224, 224, 0.5)"
}
class ["jumbotron"]
"h1":
text "Jpacrepo"
"p":
text "Personal archlinux package repository"
"div":
"form":
class ["form-horizontal"]
"div":
class ["form-group"]
"label":
class ["control-label", "col-sm-2"]
text " Search package"
"div":
class ["col-sm-10"]
"input":
class ["form-control"]
attrs {"type" : "text"}
cb :
proc add2DownloadList(e : Event) =
let rows = table.querySelectorAll("tbody tr")
for row in rows:
let cbox = row.querySelector("td:first-child input")
if cbox.checked:
let pkgname = $row.querySelector("td:nth-child(2)").textContent
let version = $row.querySelector("td:nth-child(3) button").textContent
let arch = $row.querySelector("td:nth-child(4) button").textContent
dp.addPkg(pkgMap[pkgname][version][arch].getStr)
proc oninput(e : Event) =
if pkgMap.len == 0 or node.value.len < 2: return
let pkgtable = newPkgTable(table, $node.value)
pkgtable.addButton.addEventListener("click", add2DownloadList)
node.addEventListener("input", oninput)
"div":
class ["row"]
"div":
class ["col-sm-3"]
cb:
dp = newDownloadPanel(node)
"div":
class ["col-sm-9"]
cb:
table = node
let r = newXMLHTTPRequest()
let load_cb = proc(e : Event) =
let response = parseJson($r.responseText)
pkgMap = response.getFields()
# var keys = newSeq[string]()
# for key in response.getFields().keys():
# keys.add(key)
# echo keys
r.addEventListener("load", load_cb)
r.open("get", "http://oggio88.soon.it/jpacrepo/rest/pkg/map")
r.setRequestHeader("Accept", "application/json")
r.send()

13
nim/static/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE HTML>
<html>
<head>
<link rel="stylesheet" href="jpacrepo.css" type="text/css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="jpacrepo.js" defer></script>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,11 @@
package com.oggio88.jpacrepo.annotation;
import javax.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface CORSManaged
{
}

View File

@@ -33,6 +33,8 @@ public class ApplicationContext
public boolean invalidateCache = true;
public boolean cors_enabled;
public ApplicationContext(String propertyFile)
{
systemProperties = new Properties();
@@ -65,6 +67,10 @@ public class ApplicationContext
}
}
}
String prop = System.getProperty("org.oggio88.jpacrepo.cors");
if (prop != null)
cors_enabled = Boolean.parseBoolean(prop);
}
public Properties getSystemProperties()

View File

@@ -24,6 +24,7 @@ public class ApplicationConfig extends Application
{
HashSet<Class<?>> c = new HashSet<>();
c.add(PacmanWebService.class);
c.add(CORSFilter.class);
classes = Collections.unmodifiableSet(c);
}

View File

@@ -0,0 +1,35 @@
package com.oggio88.jpacrepo.service;
import com.oggio88.jpacrepo.annotation.CORSManaged;
import com.oggio88.jpacrepo.context.ApplicationContext;
import com.oggio88.jpacrepo.context.DefaultConfiguration;
import javax.inject.Inject;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.util.logging.Logger;
@Provider
@CORSManaged
public class CORSFilter implements ContainerResponseFilter
{
private final Logger log = Logger.getLogger(CORSFilter.class.getName());
@Inject
@DefaultConfiguration
ApplicationContext ctx;
@Override
public void filter(ContainerRequestContext containerRequestContext,
ContainerResponseContext containerResponseContext) throws IOException
{
if (!ctx.cors_enabled)
{
containerResponseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
}
}
}

View File

@@ -1,5 +1,6 @@
package com.oggio88.jpacrepo.service;
import com.oggio88.jpacrepo.annotation.CORSManaged;
import com.oggio88.jpacrepo.context.ApplicationContext;
import com.oggio88.jpacrepo.context.DefaultConfiguration;
import com.oggio88.jpacrepo.model.PkgData;
@@ -8,6 +9,7 @@ import com.oggio88.jpacrepo.model.PkgName;
import com.oggio88.jpacrepo.model.StringList;
import com.oggio88.jpacrepo.pacbase.Parser;
import com.oggio88.jpacrepo.persistence.QueryEngine;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import javax.annotation.Resource;
import javax.ejb.*;
@@ -30,6 +32,7 @@ import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@CORSManaged
@Singleton
@Path("/pkg")
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@@ -59,35 +62,6 @@ public class PacmanWebService
@DefaultConfiguration
private ApplicationContext ctx;
@GET
@Path("search")
public Response getPackage(@QueryParam("name") String name,
@QueryParam("version") String version,
@QueryParam("arch") String arch,
@QueryParam("filename") String filename,
@QueryParam("md5sum") String md5sum)
{
if (md5sum != null)
{
return getPackageByHash(md5sum);
}
else if (filename != null)
{
return getPackageByFileName(filename);
}
else
{
QueryEngine qe = new QueryEngine(PkgData.class);
qe.select();
if (name != null) qe.where("name", "=", name);
if (version != null) qe.where("version", "=", version);
if (arch != null) qe.where("arch", "=", arch);
String query = qe.build();
return manageQueryResult(em.createQuery(query).getResultList());
}
}
@GET
@Path("searchByName/{name}")
public Response searchByName(@PathParam("name") String name)
@@ -139,6 +113,7 @@ public class PacmanWebService
}
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("map")
public Response getPackageMap()
{
@@ -317,6 +292,7 @@ public class PacmanWebService
}
@GET
@CORSManaged
@Path("/search")
public List<PkgData> searchPackage(
@QueryParam("name") String name,
@@ -377,6 +353,12 @@ public class PacmanWebService
return query.getResultList();
}
@POST
public Response foo()
{
TarArchiveouputStream tais = new TarArchiveOutputStream();
}
private Response manageQueryResult(List<PkgData> list)
{
return manageQueryResult(list, false);