Added dynamic listeners by hostname, first report template
parent
93abb73b58
commit
442540c05a
|
@ -8,20 +8,27 @@ using ln.type;
|
||||||
using ln.threading;
|
using ln.threading;
|
||||||
using ln.build;
|
using ln.build;
|
||||||
using ln.build.repositories;
|
using ln.build.repositories;
|
||||||
|
using Microsoft.VisualBasic;
|
||||||
|
using ln.application;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace ln.build.server
|
namespace ln.build.server
|
||||||
{
|
{
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
|
|
||||||
static CIService CIService;
|
static CIService CIService;
|
||||||
|
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
|
ArgumentContainer ac = new ArgumentContainer(typeof(Program));
|
||||||
|
|
||||||
CIService = new CIService();
|
CIService = new CIService();
|
||||||
|
ac.AddOptions(CIService);
|
||||||
|
ac.Parse(ref args);
|
||||||
|
|
||||||
CIService.Initialize();
|
CIService.Initialize();
|
||||||
CIService.AddPipeline(new DotNetPipeLine());
|
CIService.AddPipeline(new DotNetPipeLine());
|
||||||
CIService.AddRepositoryInterface(new GiteaRepositoryInterface("https://git.l--n.de"));
|
CIService.AddRepositoryInterface(new GiteaRepositoryInterface("https://git.l--n.de"){ AuthorizationToken = "1d03e9577c404b5b4f46b340147b1d500ff95b2e", });
|
||||||
|
|
||||||
CIService.Start();
|
CIService.Start();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,10 @@ namespace ln.build
|
||||||
{
|
{
|
||||||
public class CIJob : PoolJob
|
public class CIJob : PoolJob
|
||||||
{
|
{
|
||||||
static HttpClient httpClient = new HttpClient();
|
|
||||||
|
|
||||||
public string JobID { get; } = Guid.NewGuid().ToString("N");
|
public string JobID { get; } = Guid.NewGuid().ToString("N");
|
||||||
public string RepositoryURL { get; }
|
public string RepositoryURL { get; }
|
||||||
public string WorkingDirectory { get; set; }
|
public string WorkingDirectory { get; set; }
|
||||||
public CIService CurrentCIService { get; set; }
|
public CIService CIService { get; }
|
||||||
|
|
||||||
public Logger Logger { get; private set; }
|
public Logger Logger { get; private set; }
|
||||||
|
|
||||||
|
@ -33,8 +31,13 @@ namespace ln.build
|
||||||
Dictionary<string,MemoryStream> logStreams = new Dictionary<string, MemoryStream>();
|
Dictionary<string,MemoryStream> logStreams = new Dictionary<string, MemoryStream>();
|
||||||
Dictionary<string,Logger> logStreamLoggers = new Dictionary<string, Logger>();
|
Dictionary<string,Logger> logStreamLoggers = new Dictionary<string, Logger>();
|
||||||
|
|
||||||
public CIJob(string repositoryURL)
|
public CIJob(CIService ciService, string repositoryURL)
|
||||||
|
:this(ciService, null, repositoryURL){ }
|
||||||
|
public CIJob(CIService ciService, RepositoryInterface repositoryInterface ,string repositoryURL)
|
||||||
{
|
{
|
||||||
|
CIService = ciService;
|
||||||
|
RepositoryInterface = repositoryInterface;
|
||||||
|
|
||||||
WorkingDirectory = Path.Combine(Path.GetTempPath(), JobID);
|
WorkingDirectory = Path.Combine(Path.GetTempPath(), JobID);
|
||||||
Logger = new Logger(new FileLogger(Path.Combine(Path.GetTempPath(), String.Format("{0}.log", JobID))));
|
Logger = new Logger(new FileLogger(Path.Combine(Path.GetTempPath(), String.Format("{0}.log", JobID))));
|
||||||
Logger.Backends.Add(Logger.ConsoleLogger);
|
Logger.Backends.Add(Logger.ConsoleLogger);
|
||||||
|
@ -77,28 +80,10 @@ namespace ln.build
|
||||||
public bool ContainsVariable(string varName) => Environment.Contains(varName);
|
public bool ContainsVariable(string varName) => Environment.Contains(varName);
|
||||||
public CIJob SetVariable(string varName,string varValue){ Environment.Set(varName,varValue); return this; }
|
public CIJob SetVariable(string varName,string varValue){ Environment.Set(varName,varValue); return this; }
|
||||||
|
|
||||||
public async void UpdateBuildState(BuildState buildState)
|
public void UpdateBuildState(BuildState buildState)
|
||||||
{
|
{
|
||||||
BuildState = buildState;
|
BuildState = buildState;
|
||||||
|
RepositoryInterface?.UpdateBuildState(this);
|
||||||
string buildStateURL = String.Format("https://git.l--n.de/api/v1/repos/{0}/{1}/statuses/{2}",
|
|
||||||
GetVariable("REPO_OWNER"),
|
|
||||||
GetVariable("REPO_NAME"),
|
|
||||||
GetVariable("COMMIT_ID")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (buildStateURL != null)
|
|
||||||
{
|
|
||||||
JSONObject stateObject = new JSONObject();
|
|
||||||
stateObject.Add("context", "ln.build");
|
|
||||||
stateObject.Add("description", "build job pending");
|
|
||||||
stateObject.Add("state", buildState.ToString().ToLower());
|
|
||||||
stateObject.Add("target_url", JSONNull.Instance);
|
|
||||||
|
|
||||||
HttpResponseMessage response = httpClient.PostAsync(buildStateURL, new StringContent(stateObject.ToString(),Encoding.UTF8,"application/json")).Result;
|
|
||||||
Logger.Log(LogLevel.DEBUG, "UpdateBuildState({0}): {1}", buildState, buildStateURL);
|
|
||||||
Logger.Log(LogLevel.DEBUG, "Response: {0}", response );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void RunJob()
|
public override void RunJob()
|
||||||
|
@ -138,9 +123,9 @@ namespace ln.build
|
||||||
|
|
||||||
public bool DetectPipelines()
|
public bool DetectPipelines()
|
||||||
{
|
{
|
||||||
if (CurrentCIService != null)
|
if (CIService != null)
|
||||||
{
|
{
|
||||||
foreach (PipeLine pipeLine in CurrentCIService.PipeLines)
|
foreach (PipeLine pipeLine in CIService.PipeLines)
|
||||||
{
|
{
|
||||||
if (pipeLine.DetectValidity(this))
|
if (pipeLine.DetectValidity(this))
|
||||||
pipeLines.Add(pipeLine);
|
pipeLines.Add(pipeLine);
|
||||||
|
@ -164,14 +149,9 @@ namespace ln.build
|
||||||
|
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
CurrentCIService.CreateReport(this);
|
CIService.CreateReport(this);
|
||||||
Directory.Delete(WorkingDirectory, true);
|
Directory.Delete(WorkingDirectory, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static CIJob()
|
|
||||||
{
|
|
||||||
httpClient.DefaultRequestHeaders.Add("Authorization","token 1d03e9577c404b5b4f46b340147b1d500ff95b2e");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,42 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using ln.application;
|
||||||
using ln.build.repositories;
|
using ln.build.repositories;
|
||||||
using ln.http;
|
using ln.http;
|
||||||
using ln.http.client;
|
|
||||||
using ln.http.router;
|
using ln.http.router;
|
||||||
using ln.json;
|
using ln.json;
|
||||||
using ln.logging;
|
using ln.logging;
|
||||||
|
using ln.templates.html;
|
||||||
|
using ln.templates.http;
|
||||||
using ln.threading;
|
using ln.threading;
|
||||||
using ln.type;
|
using ln.type;
|
||||||
|
using Microsoft.VisualBasic;
|
||||||
|
|
||||||
namespace ln.build
|
namespace ln.build
|
||||||
{
|
{
|
||||||
public class CIService
|
public class CIService
|
||||||
{
|
{
|
||||||
|
[StaticArgument( Option = 'b', LongOption = "base-directory")]
|
||||||
|
public string BaseDirectory { get; set; }
|
||||||
|
|
||||||
|
[StaticArgument( Option = 'u', LongOption = "base-url")]
|
||||||
|
public string BaseURL { get; set; }
|
||||||
|
|
||||||
|
[StaticArgument( Option = 'h', LongOption = "hostname")]
|
||||||
|
public string HostName { get; set; }
|
||||||
|
|
||||||
public Pool buildPool;
|
public Pool buildPool;
|
||||||
public string ReportsDirectory { get; set; } = "./builds";
|
public string ReportsDirectory { get; set; }
|
||||||
public Endpoint HttpEndpoint { get; } = new Endpoint(IPv6.ANY, 1888);
|
|
||||||
|
|
||||||
Logger webLogger;
|
Logger webLogger;
|
||||||
SimpleRouter httpRouter;
|
SimpleRouter httpRouter;
|
||||||
SimpleRouter hookRouter;
|
SimpleRouter hookRouter;
|
||||||
|
StaticRouter staticsRouter;
|
||||||
|
TemplateRouter templateRouter;
|
||||||
HTTPServer httpServer;
|
HTTPServer httpServer;
|
||||||
|
|
||||||
List<RepositoryInterface> repositoryInterfaces = new List<RepositoryInterface>();
|
List<RepositoryInterface> repositoryInterfaces = new List<RepositoryInterface>();
|
||||||
|
@ -31,11 +47,26 @@ namespace ln.build
|
||||||
public CIService()
|
public CIService()
|
||||||
{
|
{
|
||||||
buildPool = new Pool(2);
|
buildPool = new Pool(2);
|
||||||
ReportsDirectory = Path.GetFullPath(ReportsDirectory);
|
}
|
||||||
|
public CIService(string baseDirectory) : this()
|
||||||
|
{
|
||||||
|
BaseDirectory = baseDirectory;
|
||||||
|
ReportsDirectory = Path.Combine(baseDirectory, "builds" );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
|
BaseDirectory ??= AppContext.BaseDirectory;
|
||||||
|
|
||||||
|
ReportsDirectory ??= Path.Combine(BaseDirectory, "builds" );
|
||||||
|
|
||||||
|
ReportsDirectory = Path.GetFullPath(ReportsDirectory);
|
||||||
|
|
||||||
|
Logging.Log(LogLevel.INFO, "Commandline: {0}", Environment.CommandLine);
|
||||||
|
Logging.Log(LogLevel.INFO, "Working Directory: {0}", Environment.CurrentDirectory);
|
||||||
|
Logging.Log(LogLevel.INFO, "Base Directory: {0}", BaseDirectory);
|
||||||
|
Logging.Log(LogLevel.INFO, "Reports Directory: {0}", ReportsDirectory);
|
||||||
|
|
||||||
Directory.CreateDirectory(ReportsDirectory);
|
Directory.CreateDirectory(ReportsDirectory);
|
||||||
|
|
||||||
InitializeHttpServer();
|
InitializeHttpServer();
|
||||||
|
@ -43,18 +74,46 @@ namespace ln.build
|
||||||
|
|
||||||
private void InitializeHttpServer()
|
private void InitializeHttpServer()
|
||||||
{
|
{
|
||||||
|
if (HostName == null)
|
||||||
|
HostName = FindHostName();
|
||||||
|
|
||||||
|
if (BaseURL == null)
|
||||||
|
BaseURL = string.Format("http://{0}:{1}", HostName, 1888);
|
||||||
|
|
||||||
|
Logging.Log(LogLevel.DEBUG, "Hostname: {0}", HostName);
|
||||||
|
Logging.Log(LogLevel.DEBUG, "BaseURL: {0}", BaseURL);
|
||||||
|
|
||||||
httpRouter = new SimpleRouter();
|
httpRouter = new SimpleRouter();
|
||||||
httpRouter.AddSimpleRoute("/builds/*", new StaticRouter(ReportsDirectory));
|
httpRouter.AddSimpleRoute("/builds/*", new StaticRouter(ReportsDirectory));
|
||||||
|
|
||||||
hookRouter = new SimpleRouter();
|
hookRouter = new SimpleRouter();
|
||||||
|
staticsRouter = new StaticRouter(Path.Combine(BaseDirectory, "html", "static"));
|
||||||
|
templateRouter = new TemplateRouter(new FileSystemTemplateSource(Path.Combine(BaseDirectory, "html", "documents"), false));
|
||||||
|
templateRouter.DefaultTemplatePath = "index.html";
|
||||||
|
templateRouter.OnPrepareRenderContext += (templateRouter, templateDocument, renderContext) => {
|
||||||
|
renderContext.SetScriptObject("CIService", this);
|
||||||
|
};
|
||||||
|
|
||||||
httpRouter.AddSimpleRoute("/hooks/*", hookRouter);
|
httpRouter.AddSimpleRoute("/hooks/*", hookRouter);
|
||||||
|
httpRouter.AddSimpleRoute("/builds/:buildid/*", templateRouter);
|
||||||
|
httpRouter.AddSimpleRoute("/builds/:buildid", templateRouter);
|
||||||
|
httpRouter.AddSimpleRoute("/*", staticsRouter);
|
||||||
|
|
||||||
webLogger = new Logger(Logger.ConsoleLogger);
|
webLogger = new Logger(Logger.ConsoleLogger);
|
||||||
webLogger.Backends.Add(new FileLogger("ln.build.http.log"));
|
webLogger.Backends.Add(new FileLogger("ln.build.http.log"));
|
||||||
|
|
||||||
httpServer = new HTTPServer(HttpEndpoint, new LoggingRouter(httpRouter, webLogger));
|
httpServer = new HTTPServer(new LoggingRouter(httpRouter, webLogger));
|
||||||
|
|
||||||
|
foreach (IPAddress _ip in Dns.GetHostAddresses(HostName))
|
||||||
|
{
|
||||||
|
IPv6 ip6 = _ip;
|
||||||
|
httpServer.AddEndpoint(new Endpoint(ip6, 1888));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetJobURL(CIJob job) => string.Format("{0}/builds/{1}", BaseURL, job.JobID);
|
||||||
|
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
buildPool.Start();
|
buildPool.Start();
|
||||||
|
@ -75,7 +134,6 @@ namespace ln.build
|
||||||
|
|
||||||
public void Enqueue(CIJob job)
|
public void Enqueue(CIJob job)
|
||||||
{
|
{
|
||||||
job.CurrentCIService = this;
|
|
||||||
buildPool.Enqueue(job);
|
buildPool.Enqueue(job);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +166,19 @@ namespace ln.build
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string FindHostName()
|
||||||
|
{
|
||||||
|
return Dns.GetHostEntry("LocalHost").HostName;
|
||||||
|
/*
|
||||||
|
string hostName = IPGlobalProperties.GetIPGlobalProperties().HostName;
|
||||||
|
string domainName = IPGlobalProperties.GetIPGlobalProperties().DomainName;
|
||||||
|
|
||||||
|
if (!hostName.EndsWith(domainName))
|
||||||
|
{
|
||||||
|
hostName = string.Format("{0}.{1}", hostName, domainName);
|
||||||
|
}
|
||||||
|
return hostName;
|
||||||
|
*/ }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Build State</title>
|
||||||
|
<script type="text/javascript" :src="CIService.BaseURL + '/js/vue/vue.js'"></script>
|
||||||
|
<script type="text/javascript" :src="CIService.BaseURL + '/js/ln.js'"></script>
|
||||||
|
<script type="text/javascript" :src="CIService.BaseURL + '/js/lib/ln.promise.js'"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="load-banner">
|
||||||
|
<h1>Please stand by, loading...</h1>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
LN
|
||||||
|
.$fetch('{{ CIService.BaseURL }}/builds/{{ request.GetParameter("buildid","none") }}/build.json')
|
||||||
|
.then((buildjson)=>{
|
||||||
|
var build = JSON.parse(buildjson);
|
||||||
|
|
||||||
|
LN.
|
||||||
|
$fetch('{{ CIService.BaseURL }}/build.html.tmpl')
|
||||||
|
.then((tmpl) => {
|
||||||
|
var e = LN.$(tmpl);
|
||||||
|
var b = document.getElementById('load-banner');
|
||||||
|
var p = b.parentElement;
|
||||||
|
p.appendChild(e);
|
||||||
|
p.removeChild(b);
|
||||||
|
|
||||||
|
var v = new Vue({
|
||||||
|
el: e,
|
||||||
|
data: {
|
||||||
|
buildid: '{{ request.GetParameter("buildid","none") }}',
|
||||||
|
build: build,
|
||||||
|
baseurl: '{{ CIService.BaseURL }}',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<div>
|
||||||
|
<h1>Build Process {{ buildid }}</h1>
|
||||||
|
<span>Current State: {{ build.state }}</span>
|
||||||
|
<div>
|
||||||
|
<h2>Log Files</h2>
|
||||||
|
<span v-for="logname in build.steps">
|
||||||
|
<a
|
||||||
|
:href="baseurl + '/builds/' + buildid + '/' + logname"
|
||||||
|
>{{ logname }}</a><br>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,84 @@
|
||||||
|
(function(){
|
||||||
|
/*\
|
||||||
|
|*|
|
||||||
|
|*| Base64 / binary data / UTF-8 strings utilities (#1)
|
||||||
|
|*|
|
||||||
|
|*| https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||||
|
|*|
|
||||||
|
|*| Author: madmurphy
|
||||||
|
|*|
|
||||||
|
|*| Harald Wolff-Thobaben: Small adaptions to create a static class
|
||||||
|
|*|
|
||||||
|
\*/
|
||||||
|
class Base64
|
||||||
|
{
|
||||||
|
constructor(){
|
||||||
|
}
|
||||||
|
|
||||||
|
static b64ToUint6(nChr){
|
||||||
|
return nChr > 64 && nChr < 91 ?
|
||||||
|
nChr - 65
|
||||||
|
: nChr > 96 && nChr < 123 ?
|
||||||
|
nChr - 71
|
||||||
|
: nChr > 47 && nChr < 58 ?
|
||||||
|
nChr + 4
|
||||||
|
: nChr === 43 ?
|
||||||
|
62
|
||||||
|
: nChr === 47 ?
|
||||||
|
63
|
||||||
|
:
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
static uint6ToB64(nUint6){
|
||||||
|
return nUint6 < 26 ?
|
||||||
|
nUint6 + 65
|
||||||
|
: nUint6 < 52 ?
|
||||||
|
nUint6 + 71
|
||||||
|
: nUint6 < 62 ?
|
||||||
|
nUint6 - 4
|
||||||
|
: nUint6 === 62 ?
|
||||||
|
43
|
||||||
|
: nUint6 === 63 ?
|
||||||
|
47
|
||||||
|
:
|
||||||
|
65;
|
||||||
|
}
|
||||||
|
|
||||||
|
static encode(aBytes){
|
||||||
|
var eqLen = (3 - (aBytes.length % 3)) % 3, sB64Enc = "";
|
||||||
|
|
||||||
|
for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
|
||||||
|
nMod3 = nIdx % 3;
|
||||||
|
nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
|
||||||
|
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
|
||||||
|
sB64Enc += String.fromCharCode(Base64.uint6ToB64(nUint24 >>> 18 & 63), Base64.uint6ToB64(nUint24 >>> 12 & 63), Base64.uint6ToB64(nUint24 >>> 6 & 63), Base64.uint6ToB64(nUint24 & 63));
|
||||||
|
nUint24 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return eqLen === 0 ?
|
||||||
|
sB64Enc
|
||||||
|
:
|
||||||
|
sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "==");
|
||||||
|
}
|
||||||
|
|
||||||
|
static decode(sBase64, nBlockSize) {
|
||||||
|
var
|
||||||
|
sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
|
||||||
|
nOutLen = nBlockSize ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockSize) * nBlockSize : nInLen * 3 + 1 >>> 2, aBytes = new Uint8Array(nOutLen);
|
||||||
|
|
||||||
|
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
|
||||||
|
nMod4 = nInIdx & 3;
|
||||||
|
nUint24 |= Base64.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
|
||||||
|
if (nMod4 === 3 || nInLen - nInIdx === 1) {
|
||||||
|
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
|
||||||
|
aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
|
||||||
|
}
|
||||||
|
nUint24 = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return aBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LN.$add("LN.Base64",Base64);
|
||||||
|
})();
|
|
@ -0,0 +1,51 @@
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
class LNIdentity
|
||||||
|
{
|
||||||
|
constructor(src){
|
||||||
|
if (!src)
|
||||||
|
src = {};
|
||||||
|
this.IdentityName = src.IdentityName || "";
|
||||||
|
this.UniqueID = src.UniqueID || null;
|
||||||
|
this.Roles = src.AssignedRoles || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
findRolesByName(identityName){
|
||||||
|
let roleMask = 0;
|
||||||
|
this.Roles.forEach(role => {
|
||||||
|
if (role.IdentityName == identityName)
|
||||||
|
roleMask = role.Roles;
|
||||||
|
});
|
||||||
|
return roleMask;
|
||||||
|
}
|
||||||
|
findRolesByID(identityUniqueID){
|
||||||
|
let roleMask = 0;
|
||||||
|
this.Roles.forEach(role => {
|
||||||
|
if (role.UniqueID == identityUniqueID)
|
||||||
|
roleMask = role.Roles;
|
||||||
|
});
|
||||||
|
return roleMask;;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRole(role,identityName){
|
||||||
|
let roles = this.findRolesByName(identityName);
|
||||||
|
return (roles & role) == role;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
LNIdentity.VIEW = (1<<0);
|
||||||
|
LNIdentity.USE = LNIdentity.VIEW | (1<<1);
|
||||||
|
LNIdentity.CONTROL = LNIdentity.VIEW | (1<<2);
|
||||||
|
LNIdentity.MANAGE = LNIdentity.CONTROL | (1<<3);
|
||||||
|
LNIdentity.ADMIN = 0x0000FFFF;
|
||||||
|
|
||||||
|
LNIdentity.MANAGEROLES = (1<<16);
|
||||||
|
|
||||||
|
LNIdentity.IMPERSONATE = (1<<24);
|
||||||
|
LNIdentity.OWN = 0x0FFFFFFF;
|
||||||
|
LNIdentity.BE = 0x0000FFFF;
|
||||||
|
LNIdentity.SUPER = 0x7FFFFFFF;
|
||||||
|
|
||||||
|
LN.$add("LN.Identity",LNIdentity);
|
||||||
|
})();
|
|
@ -0,0 +1,59 @@
|
||||||
|
(function(){
|
||||||
|
class LNPromise
|
||||||
|
{
|
||||||
|
constructor(executor, label){
|
||||||
|
this.promise = new Promise(
|
||||||
|
(resolve,reject) => {
|
||||||
|
this.resolve = (v) => {
|
||||||
|
this._s.state = "ready";
|
||||||
|
resolve(v);
|
||||||
|
this.release();
|
||||||
|
},
|
||||||
|
this.reject = (e) => {
|
||||||
|
this._s.state = "failed";
|
||||||
|
reject(e);
|
||||||
|
this.release();
|
||||||
|
}
|
||||||
|
executor && executor(
|
||||||
|
this.resolve,
|
||||||
|
this.reject
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!label)
|
||||||
|
label = "N.D.";
|
||||||
|
|
||||||
|
this._s = {
|
||||||
|
label,
|
||||||
|
state: "waiting"
|
||||||
|
};
|
||||||
|
|
||||||
|
this.idx = LN.$unique();
|
||||||
|
LNPromise.$current[this.idx] = this._s;
|
||||||
|
}
|
||||||
|
|
||||||
|
label(){
|
||||||
|
return this._s.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
state(){
|
||||||
|
return this._s.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
release(){
|
||||||
|
setTimeout(()=>{
|
||||||
|
Vue.delete(LNPromise.$current, this.idx);
|
||||||
|
},1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
then(){
|
||||||
|
return this.promise.then.apply(this.promise, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCurrentPromises(){ return LNPromise.$current; }
|
||||||
|
}
|
||||||
|
LNPromise.$current = {};
|
||||||
|
|
||||||
|
LN.$add("LN.Promise",LNPromise);
|
||||||
|
})();
|
|
@ -0,0 +1,724 @@
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
let nextTooltipKey = 0;
|
||||||
|
var tooltipData = {};
|
||||||
|
|
||||||
|
var tootlipVisible = false;
|
||||||
|
var tooltipEl = LN.$(`<div class="ln-tooltip"></div>`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function findTooltipData(el){
|
||||||
|
let tooltip = null;
|
||||||
|
while (!tooltip)
|
||||||
|
{
|
||||||
|
tooltip = tooltipData[el.dataset['tooltip']];
|
||||||
|
el = el.parentElement;
|
||||||
|
}
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tooltipShow(ev,tooltip){
|
||||||
|
tooltipEl.innerText = tooltip.value;
|
||||||
|
tooltipEl.setAttribute("VISIBLE","");
|
||||||
|
|
||||||
|
tootlipVisible = true;
|
||||||
|
}
|
||||||
|
function tooltipHide(ev,tooltip){
|
||||||
|
tooltipEl.removeAttribute("VISIBLE");
|
||||||
|
tootlipVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tooltipMouseOver(ev){
|
||||||
|
if (!document.body.contains(tooltipEl)){
|
||||||
|
document.body.appendChild(tooltipEl);
|
||||||
|
LN.$idle(()=>{ tooltipMouseOver(ev);});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tooltip = findTooltipData(ev.target);
|
||||||
|
if (!tooltip)
|
||||||
|
console.log(ev);
|
||||||
|
|
||||||
|
if (tooltipEl){
|
||||||
|
tooltipEl.style.left = `${ev.pageX+3}px`;
|
||||||
|
tooltipEl.style.top = `${ev.pageY+3}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tooltip.timeout)
|
||||||
|
clearTimeout(tooltip.timeout);
|
||||||
|
if (tootlipVisible){
|
||||||
|
tooltip.timeout = setTimeout(() => {
|
||||||
|
tooltipHide(ev,tooltip);
|
||||||
|
}, (tooltip.delay ? (tooltip.delay / 4) : 200));
|
||||||
|
} else {
|
||||||
|
tooltip.timeout = setTimeout(() => {
|
||||||
|
tooltipShow(ev,tooltip);
|
||||||
|
}, tooltip.delay || 800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tooltipMouseOut(ev){
|
||||||
|
let tooltip = findTooltipData(ev.target);
|
||||||
|
if (tooltip.timeout)
|
||||||
|
clearTimeout(tooltip.timeout);
|
||||||
|
tooltipHide(ev,tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.directive('tooltip',{
|
||||||
|
bind: function(el, binding, vnode){
|
||||||
|
let tooltip = {
|
||||||
|
value: binding.value,
|
||||||
|
timeout: null,
|
||||||
|
delay: null,
|
||||||
|
};
|
||||||
|
let tooltipKey = nextTooltipKey++;
|
||||||
|
tooltipData[tooltipKey] = tooltip;
|
||||||
|
el.dataset['tooltip'] = tooltipKey;
|
||||||
|
|
||||||
|
el.addEventListener('mousemove',tooltipMouseOver);
|
||||||
|
el.addEventListener('mouseout',tooltipMouseOut);
|
||||||
|
|
||||||
|
},
|
||||||
|
update: function(el, binding, vnode, oldVnode){
|
||||||
|
let tooltip = findTooltipData(el);
|
||||||
|
tooltip.value = binding.value;
|
||||||
|
},
|
||||||
|
unbind: function(el, binding, vnode){
|
||||||
|
el.removeEventListener('mouseover',tooltipMouseOver);
|
||||||
|
el.removeEventListener('mouseout',tooltipMouseOut);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Vue.component('ln-statusbar',{
|
||||||
|
data: function(){
|
||||||
|
return {
|
||||||
|
current: LN.Promise.getCurrentPromises(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
CurrentPromises: function(){
|
||||||
|
return this.current;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div
|
||||||
|
class="ln-statusbar"
|
||||||
|
>
|
||||||
|
<div>{{ LNVue.$_.statusText }}</div>
|
||||||
|
<div style="flex-grow: 0;">
|
||||||
|
<span
|
||||||
|
v-if="!LNVue.$_.identity.UniqueID">NOT LOGGED IN
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="LNVue.$_.identity.UniqueID">{{ LNVue.$_.identity.IdentityName }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div style="flex-grow: 0;">{{ LNVue.$_.socket && LNVue.$_.socket._state }}</div>
|
||||||
|
<div
|
||||||
|
class="ln-background-tasks"
|
||||||
|
>{{ Object.keys(CurrentPromises).length || "No" }} Background Tasks
|
||||||
|
<div
|
||||||
|
v-if="Object.keys(CurrentPromises).length"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="block"
|
||||||
|
v-for="promise in CurrentPromises"
|
||||||
|
:state="promise.state"
|
||||||
|
>{{ promise.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex-grow: 0;">{{ LNVue.$_.Version() }}</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-navitem',{
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
key: String,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div
|
||||||
|
class="ln-navitem"
|
||||||
|
:id="'nav-' + key"
|
||||||
|
>
|
||||||
|
<router-link
|
||||||
|
class="ln-navitem"
|
||||||
|
v-if="value.path"
|
||||||
|
:to="value.path"
|
||||||
|
v-slot="{ href, route, navigate, isActive, isExactActive }"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
:href="href"
|
||||||
|
@click="navigate"
|
||||||
|
>{{ value.label || value.path }}</a>
|
||||||
|
</router-link>
|
||||||
|
<span
|
||||||
|
v-if="!value.path"
|
||||||
|
class="ln-navitem"
|
||||||
|
>{{ value.label }}</span>
|
||||||
|
<div class="ln-nav-children"
|
||||||
|
v-if="value.navigation"
|
||||||
|
>
|
||||||
|
<ln-navitem
|
||||||
|
v-for="(item,key) in value.navigation"
|
||||||
|
v-bind:value="item"
|
||||||
|
v-bind:key="key"
|
||||||
|
></ln-navitem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-navbar',{
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: function(){
|
||||||
|
return LNVue.$_.navigation;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
component: {
|
||||||
|
type: [ Object, String ],
|
||||||
|
default: 'ln-navitem',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div class="ln-navbar">
|
||||||
|
<div
|
||||||
|
:is="component"
|
||||||
|
v-for="(item,key) in value"
|
||||||
|
v-bind:value="item"
|
||||||
|
v-bind:key="key"
|
||||||
|
>{{ item.label || item.path }}</div>
|
||||||
|
</div>`
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-textfield',{
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `<input
|
||||||
|
type="text"
|
||||||
|
v-bind:value="value"
|
||||||
|
v-on:input="$emit('input', $event.target.value)">`,
|
||||||
|
});
|
||||||
|
Vue.component('ln-password',{
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `<input
|
||||||
|
type="password"
|
||||||
|
v-bind:value="value"
|
||||||
|
autocomplete="current-password"
|
||||||
|
v-on:input="$emit('input', $event.target.value)">`,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-textarea',{
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `<textarea v-on:input="$emit('input', $event.target.value)">{{ value }}</textarea>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-number',{
|
||||||
|
model: {
|
||||||
|
prop: "value",
|
||||||
|
event: "input",
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
required: false,
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
float: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
template: `
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
v-bind:value="value"
|
||||||
|
v-on:input="$emit('input', $event.target.value )"
|
||||||
|
v-bind:step="float ? 'any' : 1"
|
||||||
|
>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-slider',{
|
||||||
|
model: {
|
||||||
|
prop: 'value',
|
||||||
|
event: 'change',
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
|
step: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
islogarithmic: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
values: {
|
||||||
|
default: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function(){
|
||||||
|
let d = {
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.values instanceof Array){
|
||||||
|
this.min = 0;
|
||||||
|
this.max = this.values.length - 1;
|
||||||
|
this.step = 1;
|
||||||
|
} else if (this.values instanceof Object){
|
||||||
|
this.min = 0;
|
||||||
|
this.max = Object.keys(this.values).length - 1;
|
||||||
|
this.step = 1;
|
||||||
|
} else if (this.islogarithmic){
|
||||||
|
/* ToDo: Implement */
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(`min=${this.min} max=${this.max} step=${this.step} value=${this.value}`);
|
||||||
|
|
||||||
|
return d;
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
keys: function(){
|
||||||
|
if (this.values instanceof Array)
|
||||||
|
{
|
||||||
|
let keys = new Array(this.values.length);
|
||||||
|
for (let n=0;n<keys.length;n++)
|
||||||
|
keys[n] = n;
|
||||||
|
return keys;
|
||||||
|
} else if (this.values instanceof Object)
|
||||||
|
{
|
||||||
|
return Object.keys(this.values);
|
||||||
|
}
|
||||||
|
console.log("ln-slider.keys: ???");
|
||||||
|
},
|
||||||
|
displayValue: function(){
|
||||||
|
if (this.values)
|
||||||
|
{
|
||||||
|
return this.values[this.value];
|
||||||
|
}
|
||||||
|
return this.mapFromRange(this.value);
|
||||||
|
},
|
||||||
|
sliderValue: {
|
||||||
|
get: function(){ return this.mapToRange(this.value); },
|
||||||
|
set: function(v){ this.value = this.mapFromRange(v); this.$emit('change', this.value); },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
mapToRange: function(v){
|
||||||
|
if (this.values)
|
||||||
|
{
|
||||||
|
if (this.values)
|
||||||
|
{
|
||||||
|
for (let idx=0;idx<this.keys.length;idx++)
|
||||||
|
{
|
||||||
|
if (v == this.keys[idx])
|
||||||
|
{
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("slider position not found!");
|
||||||
|
console.log(this.values);
|
||||||
|
console.log(v);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return this.islogarithmic ? null : v;
|
||||||
|
},
|
||||||
|
mapFromRange: function(v){
|
||||||
|
if (this.values)
|
||||||
|
{
|
||||||
|
return this.keys[v];
|
||||||
|
}
|
||||||
|
return this.islogarithmic ? null : v;
|
||||||
|
},
|
||||||
|
fireChange: function(){
|
||||||
|
this.$emit('change',this.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div class='ln-slider'>
|
||||||
|
<input
|
||||||
|
class="ln-slider"
|
||||||
|
type="range"
|
||||||
|
v-bind:min="min"
|
||||||
|
v-bind:max="max"
|
||||||
|
v-bind:step="step"
|
||||||
|
v-model="sliderValue"
|
||||||
|
@change="fireChange"
|
||||||
|
>{{ displayValue }}
|
||||||
|
</div>`,
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component("ln-file",{
|
||||||
|
props: {
|
||||||
|
file: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
remove: {
|
||||||
|
type: Function,
|
||||||
|
required: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
},
|
||||||
|
template: `<div
|
||||||
|
class="ln-upload-file"
|
||||||
|
>{{ file.name }} ({{ file.size }}bytes)
|
||||||
|
<sym
|
||||||
|
@click="remove && remove(file)"
|
||||||
|
class="trash"
|
||||||
|
style="position: absolute; right: 2px;"
|
||||||
|
></sym>
|
||||||
|
</div>`
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-upload',{
|
||||||
|
model: {
|
||||||
|
prop: "files",
|
||||||
|
event: "change",
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
maxSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
type: Array,
|
||||||
|
default: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function(){ return { maxSize: this.maxSize, files: this.files, }; },
|
||||||
|
methods: {
|
||||||
|
drop: function(dropEvent){
|
||||||
|
dropEvent.preventDefault();
|
||||||
|
|
||||||
|
for (let n=0;n<dropEvent.dataTransfer.files.length;n++)
|
||||||
|
{
|
||||||
|
//let file = dropEvent.dataTransfer.files[n];
|
||||||
|
let item = dropEvent.dataTransfer.items[n];
|
||||||
|
let file = item.getAsFile();
|
||||||
|
|
||||||
|
this.files.push({
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
file: file,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit("change",this.files);
|
||||||
|
console.log("files dropped...");
|
||||||
|
},
|
||||||
|
remove(file){
|
||||||
|
console.log(file);
|
||||||
|
let idx = this.files.indexOf(file);
|
||||||
|
if (idx != -1)
|
||||||
|
{
|
||||||
|
this.files.splice(idx,1);
|
||||||
|
this.$emit("change",this.files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div
|
||||||
|
class="ln-upload"
|
||||||
|
@drop="drop"
|
||||||
|
ondragover="event.preventDefault();"
|
||||||
|
>Dateien hierher ziehen oder auswählen:<br>
|
||||||
|
<input type="file" value=""><br>
|
||||||
|
<ln-file
|
||||||
|
v-for="file in files"
|
||||||
|
v-bind:file="file"
|
||||||
|
v-bind:remove="remove"
|
||||||
|
></ln-file>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-select',{
|
||||||
|
model: {
|
||||||
|
prop: "value",
|
||||||
|
event: "change",
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
items: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
empty: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
render: {
|
||||||
|
type: Function,
|
||||||
|
default: function(value){
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key: {
|
||||||
|
type: Function,
|
||||||
|
default: function(key,value){
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
prepared: {
|
||||||
|
get: function(){
|
||||||
|
let prepared = {};
|
||||||
|
LN.$each(this.items,(element,index) => {
|
||||||
|
prepared[index] = element;
|
||||||
|
});
|
||||||
|
return prepared;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changed: function(ev){
|
||||||
|
this.$emit("change", ev.target.value );
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
template: `<div class="ln-select">
|
||||||
|
<select @change="changed">
|
||||||
|
<option
|
||||||
|
v-if="empty"
|
||||||
|
value=""
|
||||||
|
></option>
|
||||||
|
<option
|
||||||
|
v-for="(item,_key) in prepared"
|
||||||
|
v-bind:value="key(_key,item)"
|
||||||
|
:selected="_key == value"
|
||||||
|
>{{ render(item) }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-identity',{
|
||||||
|
data: function(){
|
||||||
|
return {
|
||||||
|
popupVisible: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
logout(){
|
||||||
|
LNVue.$_.authenticate("",null,"","");
|
||||||
|
this.popupVisible = false;
|
||||||
|
},
|
||||||
|
popup(){
|
||||||
|
this.popupVisible = !this.popupVisible;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `<div class="ln-identity">
|
||||||
|
<div
|
||||||
|
v-if="LNVue.$_.identity.UniqueID"
|
||||||
|
v-tooltip="LNVue.$_.identity.UniqueID"
|
||||||
|
>
|
||||||
|
{{ LNVue.$_.identity.IdentityName }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!LNVue.$_.identity.UniqueID"
|
||||||
|
@click="popup()"
|
||||||
|
>Not logged in
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="no-border fa-solid"
|
||||||
|
@click="popup();"
|
||||||
|
></button>
|
||||||
|
<div
|
||||||
|
class="popup"
|
||||||
|
v-if="popupVisible"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="LNVue.$_.identity.UniqueID"
|
||||||
|
><button
|
||||||
|
@click="logout()"
|
||||||
|
>Logout</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!LNVue.$_.identity.UniqueID"
|
||||||
|
is="ln-login-pane"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-popup',{
|
||||||
|
props: {
|
||||||
|
title: String,
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
show: function(){
|
||||||
|
this.visible = true;
|
||||||
|
},
|
||||||
|
hide: function(){
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `<div class="ln-popup">
|
||||||
|
<div
|
||||||
|
class="ln-popup-frame"
|
||||||
|
><div
|
||||||
|
class="ln-popup-title"
|
||||||
|
v-if="title"
|
||||||
|
>{{ title }}</div>
|
||||||
|
<div class="ln-popup-content">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="ln-popup-footer"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
@click="hide()"
|
||||||
|
>schliessen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component('ln-login-pane',{
|
||||||
|
props: {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
stage1: function(){
|
||||||
|
console.log("login stage1", this.identityName);
|
||||||
|
|
||||||
|
LNVue.$_
|
||||||
|
.requestChallenges(this.identityName)
|
||||||
|
.then((challenges)=>{
|
||||||
|
challenges = challenges.message.Challenges;
|
||||||
|
if (challenges.length > 0){
|
||||||
|
LNVue.$_.identity.IdentityName = this.identityName;
|
||||||
|
LNVue.$_.identity.challenges = challenges;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
logout(){
|
||||||
|
LNVue.$_.authenticate("",null,"","");
|
||||||
|
},
|
||||||
|
authenticateSeededPassword(challenge){
|
||||||
|
let encoder = new TextEncoder();
|
||||||
|
let seed = LNVue.decodeHex(challenge.AuthenticationParameters);
|
||||||
|
let password = encoder.encode(challenge.prove);
|
||||||
|
|
||||||
|
let secretSource = ArrayBuffer.combine(seed, password, seed);
|
||||||
|
|
||||||
|
crypto.subtle.digest("SHA-256",secretSource)
|
||||||
|
.then((secret)=>{
|
||||||
|
let challengebytes = LN.Base64.decode(challenge.Challenge);
|
||||||
|
secret = new Uint8Array(secret);
|
||||||
|
|
||||||
|
let proveSource = ArrayBuffer.combine(challengebytes, secret, challengebytes);
|
||||||
|
|
||||||
|
crypto.subtle.digest("SHA-256",proveSource)
|
||||||
|
.then((prove)=>{
|
||||||
|
prove = LN.Base64.encode(new Uint8Array(prove));
|
||||||
|
LNVue.$_.authenticate(this.identityName,challenge.SecureAttributeID,challenge.Challenge,prove);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
data: ()=>{
|
||||||
|
return {
|
||||||
|
identityName: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: `<div>
|
||||||
|
<div
|
||||||
|
v-if="LNVue.$_.identity.UniqueID"
|
||||||
|
>
|
||||||
|
<div>Logged in as</div>
|
||||||
|
{{ LNVue.$_.identity.IdentityName }}
|
||||||
|
<button @click="logout()">Logout</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!LNVue.$_.identity.UniqueID">
|
||||||
|
<div
|
||||||
|
v-if="LNVue.$_.identity.IdentityName == ''"
|
||||||
|
><form @submit.prevent="stage1();">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<ln-textfield
|
||||||
|
name="username"
|
||||||
|
autocomplete="username"
|
||||||
|
v-model="identityName"></ln-textfield>
|
||||||
|
<button
|
||||||
|
:disabled="identityName == ''"
|
||||||
|
type="submit"
|
||||||
|
>continue</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="LNVue.$_.identity.IdentityName != ''"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="challenge in LNVue.$_.identity.challenges"
|
||||||
|
>
|
||||||
|
<form @submit.prevent="authenticateSeededPassword(challenge); return false;">
|
||||||
|
<input style="display: none;" type="text" name="username" autocomplete="username" :value="LNVue.$_.identity.IdentityName">
|
||||||
|
<div
|
||||||
|
v-if="challenge.SecureAttributeTypeName == 'SeededPassword'"
|
||||||
|
>{{ challenge.SecureAttributeLabel }}
|
||||||
|
<ln-password
|
||||||
|
v-model="challenge.prove"
|
||||||
|
></ln-password>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
:disabled="challenge.prove==''"
|
||||||
|
>login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
})();
|
|
@ -0,0 +1,274 @@
|
||||||
|
(function (){
|
||||||
|
|
||||||
|
let exModule = {
|
||||||
|
label: 'Example Module',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
template: `This is about this example!`,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
navigation: {
|
||||||
|
'99': {
|
||||||
|
label: 'About',
|
||||||
|
href: '/about'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
let defaultOptions = {
|
||||||
|
element: 'body',
|
||||||
|
modules: [ exModule, ],
|
||||||
|
};
|
||||||
|
|
||||||
|
class LNVue
|
||||||
|
{
|
||||||
|
constructor(el,options = {}){
|
||||||
|
this.options = Object.assign({
|
||||||
|
routes: [],
|
||||||
|
data: {},
|
||||||
|
}, options );
|
||||||
|
|
||||||
|
this._el = el;
|
||||||
|
this.data = Object.assign({}, options.data, { LNVue: this, msg: "Hello World" });
|
||||||
|
this.promises = [];
|
||||||
|
|
||||||
|
this.statusText = "LNVue preparing";
|
||||||
|
|
||||||
|
Vue.prototype.$LNVue = this;
|
||||||
|
LNVue.$_ = this;
|
||||||
|
|
||||||
|
this.navigation = {};
|
||||||
|
this.identity = new LN.Identity();
|
||||||
|
|
||||||
|
Promise
|
||||||
|
.all(LNVue.promises)
|
||||||
|
.then(()=>{
|
||||||
|
this.status("LNVue: starting");
|
||||||
|
|
||||||
|
LNVue.vueRouter.addRoutes([{
|
||||||
|
path: "*",
|
||||||
|
component: {
|
||||||
|
template: `<h2>404 Not Found</h2>The URL you tried to reach is not existing.`,
|
||||||
|
},
|
||||||
|
}]);
|
||||||
|
},
|
||||||
|
(cause)=>{
|
||||||
|
this.status("LNVue: start failed: " + cause);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.vue = null;
|
||||||
|
|
||||||
|
LNVue.$instance.resolve(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Start(){
|
||||||
|
Promise
|
||||||
|
.all(this.promises)
|
||||||
|
.then(()=>{
|
||||||
|
LN.$idle(()=>{
|
||||||
|
this.vue = new Vue({
|
||||||
|
el: this._el,
|
||||||
|
data: this.data,
|
||||||
|
router: LNVue.vueRouter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
LN.$idle(()=>{
|
||||||
|
this.socket = new LN.Vue.WebSocket(this);
|
||||||
|
this.socket.open();
|
||||||
|
});
|
||||||
|
LN.$idle(()=>{
|
||||||
|
LNVue.$start.resolve(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
storage(){
|
||||||
|
return window.localStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionID(){
|
||||||
|
if (arguments.length == 1){
|
||||||
|
this.storage().setItem("LNVueSessionID",arguments[0]);
|
||||||
|
console.log("LNVue.SID <= " + arguments[0]);
|
||||||
|
return this;
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
let sid = this.storage().getItem("LNVueSessionID");
|
||||||
|
console.log("LNVue.SID == " + sid);
|
||||||
|
if (!sid)
|
||||||
|
{
|
||||||
|
sid = "00000000-0000-0000-0000-000000000000";
|
||||||
|
}
|
||||||
|
return sid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Version(){ return "0.2alpha"; };
|
||||||
|
getCurrentPromises() {
|
||||||
|
return LN.Promise.getCurrentPromises();
|
||||||
|
}
|
||||||
|
|
||||||
|
status(){
|
||||||
|
if (arguments.length == 1){
|
||||||
|
this.statusText = arguments[0];
|
||||||
|
return this;
|
||||||
|
} else if (arguments.length == 0){
|
||||||
|
return this.statusText;
|
||||||
|
} else
|
||||||
|
throw "LNVue.status(): too many arguments";
|
||||||
|
}
|
||||||
|
|
||||||
|
addModule(modSpec){
|
||||||
|
if (modSpec.navigation instanceof Object){
|
||||||
|
LNVue.deepAssign(modSpec.navigation,this.navigation);
|
||||||
|
}
|
||||||
|
|
||||||
|
LN.$each(modSpec.routes,(key,route)=>{
|
||||||
|
if ((route instanceof Object) && route.url)
|
||||||
|
{
|
||||||
|
let p = new LN.Promise((resolve,reject)=>{
|
||||||
|
LN.$fetch(route.url)
|
||||||
|
.then((src)=>{
|
||||||
|
this.addRoute(key,{ template: src, data: ()=>{ return this.data; }, });
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
(cause)=>{
|
||||||
|
console.log("loading route.url failed: ",cause);
|
||||||
|
});
|
||||||
|
},`addModule(${route.url})`);
|
||||||
|
this.promises.push(p);
|
||||||
|
} else if (route instanceof Object){
|
||||||
|
this.addRoute(key,{ template: route.template, data: ()=>{ return this.data; }, } );
|
||||||
|
} else {
|
||||||
|
this.addRoute(key,{ template: route, data: ()=>{ return this.data; }, } );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addRoute(path,component){
|
||||||
|
LNVue.vueRouter.addRoutes([
|
||||||
|
{ path, component, },
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (this.vue){
|
||||||
|
let route = this.vue.$route;
|
||||||
|
LNVue.vueRouter.replace("/");
|
||||||
|
LNVue.vueRouter.replace(route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Authentication API */
|
||||||
|
|
||||||
|
requestChallenges(identityName,secureAttributeTypeName){
|
||||||
|
return new Promise((resolve,reject)=>{
|
||||||
|
this.socket.request("AuthenticationRequest",{
|
||||||
|
IdentityName: identityName,
|
||||||
|
SecureAttributeTypeName: secureAttributeTypeName,
|
||||||
|
})
|
||||||
|
.then((challenges)=>{
|
||||||
|
resolve(challenges);
|
||||||
|
},
|
||||||
|
(error)=>{
|
||||||
|
console.log("Login challenges could not be retrieved", error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate(identityName,secureAttributeID,challenge,prove){
|
||||||
|
let authenticationProve = {
|
||||||
|
IdentityName: identityName,
|
||||||
|
SecureAttributeUniqueID: secureAttributeID,
|
||||||
|
Challenge: challenge,
|
||||||
|
Prove: prove,
|
||||||
|
};
|
||||||
|
this.socket.request("AuthenticationProve", authenticationProve)
|
||||||
|
.then((identity)=>{
|
||||||
|
this.identity = new LN.Identity(identity.message);
|
||||||
|
},
|
||||||
|
(error)=>{
|
||||||
|
this.identity = new LN.Identity();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc(moduleName,methodName,parameters){
|
||||||
|
return new Promise((resolve,reject)=>{
|
||||||
|
let rpcCall = {
|
||||||
|
module: moduleName,
|
||||||
|
method: methodName,
|
||||||
|
parameters: parameters
|
||||||
|
};
|
||||||
|
this.socket.request("RPCCall",rpcCall)
|
||||||
|
.then(
|
||||||
|
(result)=>{
|
||||||
|
if (result.message.error)
|
||||||
|
{
|
||||||
|
console.log("rpc call failed", result.message.error);
|
||||||
|
reject(result.message.error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
resolve(result.message.Result);
|
||||||
|
},
|
||||||
|
(error)=>{
|
||||||
|
console.log("rpc failed", error);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static $LNVue(){
|
||||||
|
return LNVue.$_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LNVue.$instance = new LN.Promise(()=>{},'LN.Vue Instance Promise');
|
||||||
|
LNVue.$start = new LN.Promise(()=>{},'LN Vue Startup Promise');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
LNVue.vueRouter = new VueRouter({
|
||||||
|
mode: 'history',
|
||||||
|
routes: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
LNVue.deepAssign = function(source,target){
|
||||||
|
LN.$each(source,function(key){
|
||||||
|
if (target[key] instanceof Object){
|
||||||
|
LNVue.deepAssign(src[key],target[key]);
|
||||||
|
} else {
|
||||||
|
target[key] = source[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
LNVue.routes = [];
|
||||||
|
LNVue.promises = [];
|
||||||
|
|
||||||
|
LNVue.encodeHex = (bytes) => bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
|
||||||
|
LNVue.decodeHex = (hexString) => new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
|
||||||
|
|
||||||
|
ArrayBuffer.combine = function(...args){
|
||||||
|
let byteLength = 0;
|
||||||
|
args.forEach((arg,index)=>{
|
||||||
|
byteLength = byteLength + arg.byteLength;
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = new Uint8Array(byteLength);
|
||||||
|
let p = 0;
|
||||||
|
args.forEach((arg,index)=>{
|
||||||
|
for (let n=0;n<arg.byteLength;n++)
|
||||||
|
{
|
||||||
|
result[p++] = arg[n];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log("combine",new Uint8Array(result));
|
||||||
|
return result.buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
LN.$add("LN.Vue",LNVue);
|
||||||
|
LN.$add("LNVue",LNVue);
|
||||||
|
})();
|
|
@ -0,0 +1,98 @@
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
Vue.component('ln-table',{
|
||||||
|
model: {
|
||||||
|
prop: 'selectedRowIndex',
|
||||||
|
event: 'select'
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
columns: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
rows: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selectedRowIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: -1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
selectedrow: {
|
||||||
|
get: function(){
|
||||||
|
return this.selectedRowIndex == -1 ? null : this.currentRows()[this.selectedRowIndex];
|
||||||
|
},
|
||||||
|
set: function(row){
|
||||||
|
let cr = this.currentRows();
|
||||||
|
for (let n=0;n<cr.length;n++)
|
||||||
|
{
|
||||||
|
let r = cr[n];
|
||||||
|
if (r===row)
|
||||||
|
{
|
||||||
|
console.log(n);
|
||||||
|
this.selectRow(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('selectedrow: row not found!');
|
||||||
|
console.log(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: function(){
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
row: function(rowIndex){
|
||||||
|
return rowIndex == -1 ? null : this.currentRows()[rowIndex];
|
||||||
|
},
|
||||||
|
currentRows: function(){
|
||||||
|
return this.rows;
|
||||||
|
},
|
||||||
|
selectRow: function(rowIndex){
|
||||||
|
this.selectedRowIndex = rowIndex; //this.selectedrow === row ? null: row;
|
||||||
|
this.$emit('select', this.selectedRowIndex);
|
||||||
|
this.$emit('selectrow', this.selectedrow);
|
||||||
|
},
|
||||||
|
click: function(ev,rowIndex,columnKey){
|
||||||
|
ev.preventDefault();
|
||||||
|
this.selectRow(rowIndex);
|
||||||
|
this.emit('click', rowIndex, columnKey);
|
||||||
|
},
|
||||||
|
dblclick: function(ev,rowIndex, columnKey){
|
||||||
|
ev.preventDefault();
|
||||||
|
this.emit('dblclick', rowIndex, columnKey);
|
||||||
|
},
|
||||||
|
emit: function(signal,rowIndex,columnKey){
|
||||||
|
this.$emit(signal, { table: null, rowIndex, columnKey, row: this.row(rowIndex), });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<table class="ln-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td
|
||||||
|
v-for="(column,key) in columns"
|
||||||
|
>{{ column.label }}</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="(row,index) in currentRows()"
|
||||||
|
:selected="selectedRowIndex==index"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
v-for="(column,key) in columns"
|
||||||
|
@click="click($event,index,key)"
|
||||||
|
@dblclick="dblclick($event,index,key)"
|
||||||
|
>{{ row[key] }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfooter>
|
||||||
|
</tfooter>
|
||||||
|
</table>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
|
@ -0,0 +1,45 @@
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
// LNVue.prototype.createCredentials = function(_challenge,rp,userId,crossplatform,attestation){
|
||||||
|
// crossplatform = crossplatform ? true : false;
|
||||||
|
// if (!attestation)
|
||||||
|
// attestation = "none";
|
||||||
|
|
||||||
|
// let credCreateOptions = {
|
||||||
|
// challenge: Uint8Array.from(_challenge, c => c.charCodeAt(0)),
|
||||||
|
// rp: {
|
||||||
|
// id: rp,
|
||||||
|
// name: 'LN.Vue Demo Application',
|
||||||
|
// },
|
||||||
|
// user: {
|
||||||
|
// id: Int8Array.from(userId, c => c.charCodeAt(0)),
|
||||||
|
// name: 'SomeUser',
|
||||||
|
// displayName: "Some user for now...",
|
||||||
|
// },
|
||||||
|
// pubKeyCredParams: [
|
||||||
|
// {
|
||||||
|
// type: 'public-key',
|
||||||
|
// alg: -7,
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// authenticatorSelection: {
|
||||||
|
// authenticatorAttachment: crossplatform ? 'cross-platform' : 'platform',
|
||||||
|
// },
|
||||||
|
// timeout: 60000,
|
||||||
|
// attestation: attestation,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// console.log(credCreateOptions);
|
||||||
|
// navigator.credentials.create({
|
||||||
|
// publicKey: credCreateOptions
|
||||||
|
// });
|
||||||
|
|
||||||
|
// };
|
||||||
|
|
||||||
|
// LNVue.prototype.createAuthToken = function(){
|
||||||
|
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})();
|
|
@ -0,0 +1,137 @@
|
||||||
|
(function(){
|
||||||
|
class LNVueWebSocket
|
||||||
|
{
|
||||||
|
constructor(lnvue,o){
|
||||||
|
this.LNVue = lnvue;
|
||||||
|
this.options = Object.assign({},o);
|
||||||
|
|
||||||
|
if (!this.options.url)
|
||||||
|
this.options.url = this.constructURL();
|
||||||
|
|
||||||
|
this._id = 1;
|
||||||
|
this.defaultTimeout = 30000;
|
||||||
|
this.websocket = null;
|
||||||
|
this.callbacks = {};
|
||||||
|
|
||||||
|
this._state = "initialized";
|
||||||
|
this._retry = null;
|
||||||
|
|
||||||
|
this.closing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructURL(){
|
||||||
|
var pageURI = window.location;
|
||||||
|
|
||||||
|
var scheme = pageURI.scheme == "https" ? "wss:" : "ws:";
|
||||||
|
var host = pageURI.host;
|
||||||
|
|
||||||
|
return scheme + "//" + host + "/socket";
|
||||||
|
}
|
||||||
|
|
||||||
|
open(){
|
||||||
|
if (this._retry){
|
||||||
|
clearTimeout(this._retry);
|
||||||
|
}
|
||||||
|
this.closing = false;
|
||||||
|
|
||||||
|
this.websocket = new WebSocket(this.options.url);
|
||||||
|
this.websocket.onopen = (e) =>{
|
||||||
|
console.log("WebSocket connected");
|
||||||
|
this._state = "ONLINE";
|
||||||
|
|
||||||
|
let WSHello = {
|
||||||
|
ApplicationSessionID: this.LNVue.sessionID(),
|
||||||
|
};
|
||||||
|
console.log("WSHello request",WSHello);
|
||||||
|
|
||||||
|
this.request("WSHello",WSHello)
|
||||||
|
.then((wsh)=>{
|
||||||
|
console.log("WSHello response",wsh);
|
||||||
|
this.LNVue.sessionID(wsh.message.ApplicationSessionID);
|
||||||
|
this.LNVue.identity = new LN.Identity(wsh.message.SessionIdentity);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
this.websocket.onclose = (e)=>{ this._onclose(e); this._state = "OFFLINE"; };
|
||||||
|
this.websocket.onerror = (e)=>{ this._onerror(e); this._state = "ERROR"; };
|
||||||
|
this.websocket.onmessage = (e)=>{ this._onmessage(e); };
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(){
|
||||||
|
if (this.websocket){
|
||||||
|
this.closing = true;
|
||||||
|
this.websocket.close(200,"close() called");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
request(msgtype,msg,timeout){
|
||||||
|
let message = {
|
||||||
|
id: this._id++,
|
||||||
|
type: msgtype,
|
||||||
|
message: msg,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!timeout)
|
||||||
|
timeout = this.defaultTimeout;
|
||||||
|
|
||||||
|
if (timeout != -1){
|
||||||
|
return new Promise((resolve,reject)=>{
|
||||||
|
let to = setTimeout(()=>{
|
||||||
|
delete this.callbacks[message.id];
|
||||||
|
reject("timed out");
|
||||||
|
},timeout);
|
||||||
|
|
||||||
|
this.callbacks[message.id] = (msgtype,msg)=>{
|
||||||
|
clearTimeout(to);
|
||||||
|
delete this.callbacks[message.id];
|
||||||
|
if (msgtype == "error")
|
||||||
|
reject(msg);
|
||||||
|
else
|
||||||
|
resolve({type: msgtype,message: msg});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.websocket.send(
|
||||||
|
JSON.stringify(message)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
new Promise((resolve,reject)=>{
|
||||||
|
this.websocket.send(
|
||||||
|
JSON.stringify(message)
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_onclose(evt){
|
||||||
|
this.websocket = null;
|
||||||
|
this.options.onclose && this.options.onclose(evt);
|
||||||
|
if (!this.closing)
|
||||||
|
{
|
||||||
|
this._retry = setTimeout(() => {
|
||||||
|
this._retry = null;
|
||||||
|
console.log("reconnect...")
|
||||||
|
this.open();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_onerror(evt){
|
||||||
|
this.options.onerror && this.options.onerror(evt);
|
||||||
|
}
|
||||||
|
_onmessage(evt){
|
||||||
|
try
|
||||||
|
{
|
||||||
|
let j = JSON.parse(evt.data);
|
||||||
|
let cb = this.callbacks[ j.id ];
|
||||||
|
cb && cb(j.type,j.message);
|
||||||
|
} catch(exc){
|
||||||
|
console.log(exc,evt.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LN.$add("LN.Vue.WebSocket",LNVueWebSocket);
|
||||||
|
})();
|
|
@ -0,0 +1,107 @@
|
||||||
|
(function(globals){
|
||||||
|
let _unique = new Date().getTime();
|
||||||
|
let __idles__ = [];
|
||||||
|
|
||||||
|
class LN
|
||||||
|
{
|
||||||
|
constructor(opts){
|
||||||
|
}
|
||||||
|
|
||||||
|
static $each(oa,cb){
|
||||||
|
if (oa instanceof Array){
|
||||||
|
let result = [];
|
||||||
|
oa.forEach((value,index)=>{
|
||||||
|
result.push(cb.call(value,index,value));
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
} else if (oa instanceof Object){
|
||||||
|
let result = {};
|
||||||
|
Object.keys(oa).forEach((key)=>{
|
||||||
|
if (oa.hasOwnProperty(key)){
|
||||||
|
result[key] = cb.call(oa[key],key,oa[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static $idle(cb,thisval = null){
|
||||||
|
let scheduled = __idles__.length > 0;
|
||||||
|
let n=0;
|
||||||
|
|
||||||
|
for (;n<__idles__.length;n++){
|
||||||
|
let idle = __idles__[n];
|
||||||
|
if ((idle[0] == cb) && (idle[1] == thisval))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (n == __idles__.length)
|
||||||
|
__idles__.push([cb,thisval]);
|
||||||
|
|
||||||
|
if (!scheduled)
|
||||||
|
setTimeout(()=>{
|
||||||
|
while (__idles__.length > 0){
|
||||||
|
let idle = __idles__.pop();
|
||||||
|
idle[0].call(idle[1]);
|
||||||
|
}
|
||||||
|
},0);
|
||||||
|
}
|
||||||
|
static $unique(){
|
||||||
|
return _unique++;
|
||||||
|
}
|
||||||
|
static $fetch(url,cb){
|
||||||
|
return new LN.Promise((resolve,reject)=>{
|
||||||
|
fetch(url)
|
||||||
|
.then((response => {
|
||||||
|
if (response.status.toString().startsWith("2"))
|
||||||
|
{
|
||||||
|
let t = response.text();
|
||||||
|
cb && cb(t,null);
|
||||||
|
resolve(t);
|
||||||
|
} else {
|
||||||
|
cb && cb(null, response.statusText);
|
||||||
|
reject(response.statusText);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}, `fetch(${url})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static $resolve(v){
|
||||||
|
if (v instanceof Function)
|
||||||
|
return v();
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static $add(classpath,c){
|
||||||
|
let p = classpath.split(".");
|
||||||
|
if (p.length < 1)
|
||||||
|
throw "invalid classpath";
|
||||||
|
|
||||||
|
let container = globals;
|
||||||
|
while (p.length > 1){
|
||||||
|
let next = p.shift();
|
||||||
|
if (!container[next])
|
||||||
|
container[next] = {}
|
||||||
|
container = container[next];
|
||||||
|
}
|
||||||
|
|
||||||
|
let prev = container[p[0]];
|
||||||
|
container[p[0]] = c;
|
||||||
|
|
||||||
|
if (prev)
|
||||||
|
{
|
||||||
|
LN.$each(prev,(key,value)=>{
|
||||||
|
c[key] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static $(selector){
|
||||||
|
let el = document.createElement("parse");
|
||||||
|
el.innerHTML = selector;
|
||||||
|
return el.firstChild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LN.prototypes = {};
|
||||||
|
|
||||||
|
LN.$add("LN", LN);
|
||||||
|
})(window);
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -13,12 +13,19 @@
|
||||||
<PackageTags>build build-server</PackageTags>
|
<PackageTags>build build-server</PackageTags>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="html/**" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="LibGit2Sharp" Version="0.26.2" />
|
<PackageReference Include="LibGit2Sharp" Version="0.26.2" />
|
||||||
<PackageReference Include="ln.logging" Version="1.0.1" />
|
<PackageReference Include="ln.logging" Version="1.0.1" />
|
||||||
<PackageReference Include="ln.threading" Version="0.1.0" />
|
<PackageReference Include="ln.threading" Version="0.1.0" />
|
||||||
<PackageReference Include="ln.json" Version="1.0.0" />
|
<PackageReference Include="ln.json" Version="1.0.0" />
|
||||||
<PackageReference Include="ln.http" Version="0.1.2" />
|
<PackageReference Include="ln.http" Version="0.1.2" />
|
||||||
|
<PackageReference Include="ln.templates" Version="0.1.1" />
|
||||||
|
<PackageReference Include="ln.templates.http" Version="0.0.1-test" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -12,6 +12,8 @@ namespace ln.build.repositories
|
||||||
{
|
{
|
||||||
public string BaseURL { get; }
|
public string BaseURL { get; }
|
||||||
|
|
||||||
|
public string AuthorizationToken { get; set; }
|
||||||
|
|
||||||
public GiteaRepositoryInterface(string baseURL) :base("gitea")
|
public GiteaRepositoryInterface(string baseURL) :base("gitea")
|
||||||
{
|
{
|
||||||
BaseURL = baseURL;
|
BaseURL = baseURL;
|
||||||
|
@ -30,15 +32,18 @@ namespace ln.build.repositories
|
||||||
if (buildStateURL != null)
|
if (buildStateURL != null)
|
||||||
{
|
{
|
||||||
JSONObject stateObject = new JSONObject();
|
JSONObject stateObject = new JSONObject();
|
||||||
stateObject.Add("context", "ln.build");
|
stateObject.Add("context", string.Format("ln.build - {0}", job.CIService.HostName));
|
||||||
stateObject.Add("description", "build job pending");
|
stateObject.Add("description", "build job pending");
|
||||||
stateObject.Add("state", job.BuildState.ToString().ToLower());
|
stateObject.Add("state", job.BuildState.ToString().ToLower());
|
||||||
stateObject.Add("target_url", JSONNull.Instance);
|
stateObject.Add("target_url", job.CIService.GetJobURL(job));
|
||||||
|
|
||||||
using (HttpClient httpClient = new HttpClient())
|
using (HttpClient httpClient = new HttpClient())
|
||||||
{
|
{
|
||||||
|
httpClient.DefaultRequestHeaders.Add("Authorization",String.Format("token {0}", AuthorizationToken));
|
||||||
|
|
||||||
HttpResponseMessage response = httpClient.PostAsync(buildStateURL, new StringContent(stateObject.ToString(),Encoding.UTF8,"application/json")).Result;
|
HttpResponseMessage response = httpClient.PostAsync(buildStateURL, new StringContent(stateObject.ToString(),Encoding.UTF8,"application/json")).Result;
|
||||||
job.Logger.Log(LogLevel.DEBUG, "UpdateBuildState({0}): {1}", job.BuildState, buildStateURL);
|
job.Logger.Log(LogLevel.DEBUG, "UpdateBuildState({0}): {1}", job.BuildState, buildStateURL);
|
||||||
|
job.Logger.Log(LogLevel.DEBUG, "Request: {0}", stateObject.ToString());
|
||||||
job.Logger.Log(LogLevel.DEBUG, "Response: {0}", response );
|
job.Logger.Log(LogLevel.DEBUG, "Response: {0}", response );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +73,7 @@ namespace ln.build.repositories
|
||||||
string commitID = commit["id"].ToNative().ToString();
|
string commitID = commit["id"].ToNative().ToString();
|
||||||
Logging.Log("Received CI request of repository {0} for commit {1}", cloneUrl, commitID);
|
Logging.Log("Received CI request of repository {0} for commit {1}", cloneUrl, commitID);
|
||||||
|
|
||||||
CIJob job = new CIJob(cloneUrl)
|
CIJob job = new CIJob(ciService, this, cloneUrl)
|
||||||
.SetVariable("REPO_OWNER", message["repository"]["owner"]["username"].ToNative().ToString())
|
.SetVariable("REPO_OWNER", message["repository"]["owner"]["username"].ToNative().ToString())
|
||||||
.SetVariable("REPO_NAME", message["repository"]["name"].ToNative().ToString())
|
.SetVariable("REPO_NAME", message["repository"]["name"].ToNative().ToString())
|
||||||
.SetVariable("COMMIT_ID", commitID)
|
.SetVariable("COMMIT_ID", commitID)
|
||||||
|
|
Loading…
Reference in New Issue