mirror of
https://github.com/lxsang/antd-web-apps
synced 2025-07-20 15:49:46 +02:00
Compare commits
103 Commits
Author | SHA1 | Date | |
---|---|---|---|
5db04af313 | |||
8e02d3f6ba | |||
8fb8460e50 | |||
7ede87e962 | |||
b65bee0323 | |||
a05507123c | |||
6da933da67 | |||
a76942f2f3 | |||
93b6ca18ad | |||
b10ecfc259 | |||
452e3c5343 | |||
04e7192ef8 | |||
1c19cc985c | |||
1f9c93efb4 | |||
0dba37d86e | |||
a874234925 | |||
3c8aa63e9a | |||
1d9107cc04 | |||
f737109330 | |||
59dd9276bf | |||
7081d2c337 | |||
85fa2a7c24 | |||
395249972e | |||
5b8acf2d87 | |||
4016a0abef | |||
f10df26f82 | |||
4d091604d1 | |||
aec0ae5bb7 | |||
b3ca999e88 | |||
e775e9d7e7 | |||
fd31477739 | |||
6d3ee13d87 | |||
ce05d67166 | |||
ce99218348 | |||
e66336fe56 | |||
5abcc47679 | |||
72102481d8 | |||
0d1312e1ba | |||
820899a420 | |||
9b519ffb46 | |||
bac46c691a | |||
b2383f8113 | |||
62435e387e | |||
1e5f988f73 | |||
1ed7bf91ae | |||
fd4b1064f5 | |||
beb24048d8 | |||
cd914ad8c0 | |||
64a84f045d | |||
6511f15d5b | |||
e7ee8cd889 | |||
b5bee06263 | |||
64f02d4614 | |||
87e8ba94b4 | |||
e35e8852b9 | |||
bb88057ec1 | |||
175f952f23 | |||
51dfab7ca3 | |||
701983e0fb | |||
1f3f18b83e | |||
a44d12fa03 | |||
4479c9a882 | |||
30abb9552f | |||
1bac21078c | |||
e317131f0a | |||
7ceeee20e2 | |||
9b05222488 | |||
0cef9754eb | |||
32c120640d | |||
081c924e18 | |||
45d0d7af1e | |||
35bade9d61 | |||
22f700e54b | |||
1bc95188e1 | |||
effcedb94e | |||
dcb3803120 | |||
4f80957ece | |||
e85d550b12 | |||
e8f3fc2c83 | |||
f457924d38 | |||
0765d22913 | |||
7eb9746dc7 | |||
af802822e6 | |||
d416ae36c8 | |||
bf52f93e27 | |||
74b02e66bd | |||
7f9e8bfbaa | |||
d135587c3b | |||
83288a00b0 | |||
143094d301 | |||
7f29ab310a | |||
ecd409778a | |||
96624d2cbd | |||
9fce4a3fd5 | |||
8d0f9d3480 | |||
8427f00aa0 | |||
e2f8bd8116 | |||
c09799ba08 | |||
3f1530cfe8 | |||
0c6d81ef93 | |||
6addbc88ca | |||
4d62cee7af | |||
87169dd41f |
22
.drone.yml
Normal file
22
.drone.yml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: exec
|
||||||
|
name: default
|
||||||
|
platform:
|
||||||
|
os: linux
|
||||||
|
arch: amd64
|
||||||
|
clone:
|
||||||
|
disable: true
|
||||||
|
steps:
|
||||||
|
- name: clone
|
||||||
|
commands:
|
||||||
|
- pwd
|
||||||
|
- git clone ssh://git@iohub.dev/lxsang/antd-web-apps.git
|
||||||
|
- cd ./antd-web-apps && git checkout master
|
||||||
|
- name: build
|
||||||
|
commands:
|
||||||
|
- cd ./antd-web-apps
|
||||||
|
- BUILDDIR=/opt/www/htdocs make
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- master
|
39
Jenkinsfile
vendored
Normal file
39
Jenkinsfile
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
pipeline{
|
||||||
|
agent { node{ label'workstation' }}
|
||||||
|
options {
|
||||||
|
// Limit build history with buildDiscarder option:
|
||||||
|
// daysToKeepStr: history is only kept up to this many days.
|
||||||
|
// numToKeepStr: only this many build logs are kept.
|
||||||
|
// artifactDaysToKeepStr: artifacts are only kept up to this many days.
|
||||||
|
// artifactNumToKeepStr: only this many builds have their artifacts kept.
|
||||||
|
buildDiscarder(logRotator(numToKeepStr: "1"))
|
||||||
|
// Enable timestamps in build log console
|
||||||
|
timestamps()
|
||||||
|
// Maximum time to run the whole pipeline before canceling it
|
||||||
|
timeout(time: 1, unit: 'HOURS')
|
||||||
|
// Use Jenkins ANSI Color Plugin for log console
|
||||||
|
ansiColor('xterm')
|
||||||
|
// Limit build concurrency to 1 per branch
|
||||||
|
disableConcurrentBuilds()
|
||||||
|
}
|
||||||
|
stages
|
||||||
|
{
|
||||||
|
stage('Build') {
|
||||||
|
steps {
|
||||||
|
sh'''
|
||||||
|
cd $WORKSPACE
|
||||||
|
[ -d build ] && rm -rf build
|
||||||
|
mkdir -p build/opt/www/htdocs
|
||||||
|
export BUILDDIR="$WORKSPACE/build/opt/www/htdocs"
|
||||||
|
make
|
||||||
|
'''
|
||||||
|
script {
|
||||||
|
// only useful for any master branch
|
||||||
|
//if (env.BRANCH_NAME =~ /^master/) {
|
||||||
|
archiveArtifacts artifacts: 'build/', fingerprint: true
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
Makefile
10
Makefile
@ -1,12 +1,13 @@
|
|||||||
BUILDDIR?=./build
|
BUILDDIR?=./build
|
||||||
PROJS?=grs info blog os doc ci talk get
|
PROJS?=grs blog talk get info doc
|
||||||
copyfiles = index.ls mimes.json
|
# info blog doc talk get
|
||||||
|
copyfiles = layout.ls index.lua mimes.json
|
||||||
main: copy
|
main: copy
|
||||||
for f in $(PROJS); do BUILDDIR=$(BUILDDIR)/"$${f}" make -C "$${f}" ; done
|
for f in $(PROJS); do BUILDDIR=$(BUILDDIR)/"$${f}" make -C "$${f}" ; done
|
||||||
|
|
||||||
copy:
|
copy:
|
||||||
cp -rf $(copyfiles) $(BUILDDIR)
|
cp -rfv $(copyfiles) $(BUILDDIR)
|
||||||
cp -r silk $(BUILDDIR)
|
# cp -rv silk $(BUILDDIR)
|
||||||
|
|
||||||
ar:
|
ar:
|
||||||
-[ -d /tmp/antd_web_apps ] && rm -r /tmp/antd_web_apps
|
-[ -d /tmp/antd_web_apps ] && rm -r /tmp/antd_web_apps
|
||||||
@ -19,4 +20,3 @@ ar:
|
|||||||
clean:
|
clean:
|
||||||
-for f in $(PROJS); do rm -r $(BUILDDIR)/"$${f}"; done
|
-for f in $(PROJS); do rm -r $(BUILDDIR)/"$${f}"; done
|
||||||
-for f in $(copyfiles); do rm -r $(BUILDDIR)/"$${f}"; done
|
-for f in $(copyfiles); do rm -r $(BUILDDIR)/"$${f}"; done
|
||||||
-rm -r $(BUILDDIR)/silk
|
|
@ -1,2 +1,2 @@
|
|||||||
# antd-web-apps
|
# antd-web-apps
|
||||||
Some web apps for antd server
|
Various web apps for antd server
|
||||||
|
1135
blog/assets/afx.css
Normal file
1135
blog/assets/afx.css
Normal file
File diff suppressed because it is too large
Load Diff
1
blog/assets/afx.js
Normal file
1
blog/assets/afx.js
Normal file
File diff suppressed because one or more lines are too long
220
blog/assets/graph.js
Normal file
220
blog/assets/graph.js
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
const colors = [
|
||||||
|
"#3957ff", "#d3fe14", "#c9080a", "#fec7f8", "#0b7b3e", "#0bf0e9", "#c203c8", "#fd9b39",
|
||||||
|
"#888593", "#906407", "#98ba7f", "#fe6794", "#10b0ff", "#ac7bff", "#fee7c0", "#964c63",
|
||||||
|
"#1da49c", "#0ad811", "#bbd9fd", "#fe6cfe", "#297192", "#d1a09c", "#78579e", "#81ffad",
|
||||||
|
"#739400", "#ca6949", "#d9bf01", "#646a58", "#d5097e", "#bb73a9", "#ccf6e9", "#9cb4b6",
|
||||||
|
"#b6a7d4", "#9e8c62", "#6e83c8", "#01af64", "#a71afd", "#cfe589", "#d4ccd1", "#fd4109",
|
||||||
|
"#bf8f0e", "#2f786e", "#4ed1a5", "#d8bb7d", "#a54509", "#6a9276", "#a4777a", "#fc12c9",
|
||||||
|
"#606f15", "#3cc4d9", "#f31c4e", "#73616f", "#f097c6", "#fc8772", "#92a6fe", "#875b44",
|
||||||
|
"#699ab3", "#94bc19", "#7d5bf0", "#d24dfe", "#c85b74", "#68ff57", "#b62347", "#994b91",
|
||||||
|
"#646b8c", "#977ab4", "#d694fd", "#c4d5b5", "#fdc4bd", "#1cae05", "#7bd972", "#e9700a",
|
||||||
|
"#d08f5d", "#8bb9e1", "#fde945", "#a29d98", "#1682fb", "#9ad9e0", "#d6cafe", "#8d8328",
|
||||||
|
"#b091a7", "#647579", "#1f8d11", "#e7eafd", "#b9660b", "#a4a644", "#fec24c", "#b1168c",
|
||||||
|
"#188cc1", "#7ab297", "#4468ae", "#c949a6", "#d48295", "#eb6dc2", "#d5b0cb", "#ff9ffb",
|
||||||
|
"#fdb082", "#af4d44", "#a759c4", "#a9e03a", "#0d906b", "#9ee3bd", "#5b8846", "#0d8995",
|
||||||
|
"#f25c58", "#70ae4f", "#847f74", "#9094bb", "#ffe2f1", "#a67149", "#936c8e", "#d04907",
|
||||||
|
"#c3b8a6", "#cef8c4", "#7a9293", "#fda2ab", "#2ef6c5", "#807242", "#cb94cc", "#b6bdd0",
|
||||||
|
"#b5c75d", "#fde189", "#b7ff80", "#fa2d8e", "#839a5f", "#28c2b5", "#e5e9e1", "#bc79d8",
|
||||||
|
"#7ed8fe", "#9f20c3", "#4f7a5b", "#f511fd", "#09c959", "#bcd0ce", "#8685fd", "#98fcff",
|
||||||
|
"#afbff9", "#6d69b4", "#5f99fd", "#aaa87e", "#b59dfb", "#5d809d", "#d9a742", "#ac5c86",
|
||||||
|
"#9468d5", "#a4a2b2", "#b1376e", "#d43f3d", "#05a9d1", "#c38375", "#24b58e", "#6eabaf"];
|
||||||
|
|
||||||
|
d3.json("/post/graph_json")
|
||||||
|
.then(
|
||||||
|
function (json) {
|
||||||
|
if (json.result) {
|
||||||
|
const tooltip_div = d3.select("#desktop")
|
||||||
|
.append("div")
|
||||||
|
.attr("class", "d3tooltip")
|
||||||
|
.style("display", "none");
|
||||||
|
const links = json.result.links;
|
||||||
|
const nodes = json.result.nodes;
|
||||||
|
|
||||||
|
|
||||||
|
drag = simulation => {
|
||||||
|
|
||||||
|
function dragstarted(event) {
|
||||||
|
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||||
|
event.subject.fx = event.subject.x;
|
||||||
|
event.subject.fy = event.subject.y;
|
||||||
|
tooltip_div.style("display", "none");
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragged(event) {
|
||||||
|
event.subject.fx = event.x;
|
||||||
|
event.subject.fy = event.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dragended(event) {
|
||||||
|
if (!event.active) simulation.alphaTarget(0);
|
||||||
|
event.subject.fx = null;
|
||||||
|
event.subject.fy = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d3.drag()
|
||||||
|
.on("start", dragstarted)
|
||||||
|
.on("drag", dragged)
|
||||||
|
.on("end", dragended);
|
||||||
|
};
|
||||||
|
|
||||||
|
const simulation = d3.forceSimulation(nodes)
|
||||||
|
.force("link",
|
||||||
|
d3.forceLink(links)
|
||||||
|
.id(d => d.id)
|
||||||
|
.distance(d => 1.0 / d.score)
|
||||||
|
.strength(d => d.score)
|
||||||
|
)
|
||||||
|
.force("charge", d3.forceManyBody())
|
||||||
|
.force("center", d3.forceCenter());
|
||||||
|
const svg = d3.create("svg")
|
||||||
|
.attr("preserveAspectRatio", "xMidYMid meet");
|
||||||
|
const link = svg.append("g")
|
||||||
|
.attr("stroke", "#999")
|
||||||
|
.attr("stroke-opacity", 0.8)
|
||||||
|
.selectAll("line")
|
||||||
|
.data(links)
|
||||||
|
.join("line")
|
||||||
|
.attr("stroke-width", d => d.score * 7.0); //d.score
|
||||||
|
|
||||||
|
const node = svg.append("g")
|
||||||
|
.attr("stroke", "#fff")
|
||||||
|
.attr("stroke-width", 0.5)
|
||||||
|
.selectAll("circle")
|
||||||
|
.data(nodes)
|
||||||
|
.join("circle")
|
||||||
|
.attr("r", (d) => {
|
||||||
|
conn = links.filter((l) => {
|
||||||
|
//console.log(d.id, l.target.id, l.source.id)
|
||||||
|
return l.target.id == d.id || l.source.id == d.id;
|
||||||
|
}).map(c=>c.score);
|
||||||
|
//return conn.reduce((a, b) => a + b, 0) * 10;
|
||||||
|
return conn.length;
|
||||||
|
})
|
||||||
|
.attr("fill", (d) => {
|
||||||
|
conn = links.filter((l) => {
|
||||||
|
//console.log(d.id, l.target.id, l.source.id)
|
||||||
|
return l.target.id == d.id || l.source.id == d.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return colors[conn.length % colors.length - 1];
|
||||||
|
})
|
||||||
|
.on("click", (d) => {
|
||||||
|
const index = $(d.target).index();
|
||||||
|
const data = nodes[index];
|
||||||
|
d3.json("/post/json/" + data.id)
|
||||||
|
.then( (json) => {
|
||||||
|
if(json.result)
|
||||||
|
{
|
||||||
|
$("#floating_content").html(json.result.description);
|
||||||
|
$("#floating_container").show();
|
||||||
|
$("#floating_btn_read_more").attr("href", "/post/id/" + json.result.id);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch ((e)=>{
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.call(drag(simulation))
|
||||||
|
.on('mouseover', function (d) {
|
||||||
|
const index = $(d.target).index();
|
||||||
|
const data = nodes[index];
|
||||||
|
link.style('stroke', function (l) {
|
||||||
|
if (data.id == l.source.id || data.id == l.target.id)
|
||||||
|
return "#9a031e";
|
||||||
|
else
|
||||||
|
return "#999";
|
||||||
|
});
|
||||||
|
const off = $("#desktop").offset();
|
||||||
|
tooltip_div.transition()
|
||||||
|
.duration(200)
|
||||||
|
tooltip_div.style("display", "block")
|
||||||
|
.style("opacity", .8);
|
||||||
|
tooltip_div.html(data.title)
|
||||||
|
.style("left", (d.clientX - off.left + 10) + "px")
|
||||||
|
.style("top", (d.clientY - off.top + 10) + "px");
|
||||||
|
})
|
||||||
|
.on('mouseout', function () {
|
||||||
|
link.style('stroke', "#999");
|
||||||
|
tooltip_div.style("display", "none");
|
||||||
|
});
|
||||||
|
|
||||||
|
//node.append("title")
|
||||||
|
// .text(d => d.title);
|
||||||
|
|
||||||
|
/*const label = svg.append("g")
|
||||||
|
.attr("stroke", "#fff")
|
||||||
|
.attr("stroke-width", 0.2)
|
||||||
|
.selectAll("text")
|
||||||
|
.data(nodes)
|
||||||
|
.join("text")
|
||||||
|
.text(d=>d.id)
|
||||||
|
.style("user-select", "none")
|
||||||
|
.style("font-size", (d) =>{
|
||||||
|
conn = links.filter((l) => {
|
||||||
|
//console.log(d.id, l.target.id, l.source.id)
|
||||||
|
return l.target.id == d.id || l.source.id == d.id;
|
||||||
|
});
|
||||||
|
return conn.length + "px";
|
||||||
|
})
|
||||||
|
.style('fill', '#000');*/
|
||||||
|
|
||||||
|
|
||||||
|
simulation.on("tick", () => {
|
||||||
|
link
|
||||||
|
.attr("x1", d => d.source.x)
|
||||||
|
.attr("y1", d => d.source.y)
|
||||||
|
.attr("x2", d => d.target.x)
|
||||||
|
.attr("y2", d => d.target.y);
|
||||||
|
|
||||||
|
node
|
||||||
|
.attr("cx", d => d.x)
|
||||||
|
.attr("cy", d => d.y);
|
||||||
|
|
||||||
|
const nodes_x = nodes.map(d => d.x);
|
||||||
|
const nodes_y = nodes.map(d => d.y);
|
||||||
|
const min_x = Math.min(...nodes_x) - 10;
|
||||||
|
const min_y = Math.min(...nodes_y) - 10;
|
||||||
|
const w = Math.max(...nodes_x) - min_x + 10;
|
||||||
|
const h = Math.max(...nodes_y) - min_y + 10;
|
||||||
|
svg.attr("viewBox",
|
||||||
|
[min_x, min_y, w, h]);
|
||||||
|
/*label
|
||||||
|
.attr("x", d => {
|
||||||
|
conn = links.filter((l) => {
|
||||||
|
//console.log(d.id, l.target.id, l.source.id)
|
||||||
|
return l.target.id == d.id || l.source.id == d.id;
|
||||||
|
});
|
||||||
|
return d.x - conn.length / 2;
|
||||||
|
})
|
||||||
|
.attr("y", d => {
|
||||||
|
conn = links.filter((l) => {
|
||||||
|
//console.log(d.id, l.target.id, l.source.id)
|
||||||
|
return l.target.id == d.id || l.source.id == d.id;
|
||||||
|
});
|
||||||
|
return d.y + conn.length / 2;
|
||||||
|
});*/
|
||||||
|
});
|
||||||
|
|
||||||
|
// invalidation.then(() => simulation.stop());
|
||||||
|
$("#floating_btn_close").click((e)=>{
|
||||||
|
$("#floating_container").hide();
|
||||||
|
});
|
||||||
|
$("#desktop")
|
||||||
|
.css("position", "relative");
|
||||||
|
$("#container")
|
||||||
|
.css("height", "100%")
|
||||||
|
.css("position", "relative")
|
||||||
|
.append($(svg.node())
|
||||||
|
.css("height", "calc(100% - 10px)")
|
||||||
|
.css("margin", "0 auto")
|
||||||
|
.css("display", "block"));
|
||||||
|
$("#floating_container").show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
});
|
@ -18,7 +18,6 @@ function subscribe(prefix) {
|
|||||||
scheme = undefined;
|
scheme = undefined;
|
||||||
});
|
});
|
||||||
obs.on("rendered", function (d) {
|
obs.on("rendered", function (d) {
|
||||||
console.log("rednered");
|
|
||||||
$(".afx-window-title", scheme).html("Subscribe");
|
$(".afx-window-title", scheme).html("Subscribe");
|
||||||
$("[data-id='send']", scheme).click(function () {
|
$("[data-id='send']", scheme).click(function () {
|
||||||
var status = $("[data-id='status']", scheme);
|
var status = $("[data-id='status']", scheme);
|
||||||
|
@ -73,6 +73,52 @@ body {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
div.d3tooltip {
|
||||||
|
position: absolute;
|
||||||
|
padding: 5px;
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
color: white;
|
||||||
|
border: 0px;
|
||||||
|
border-radius: 5px;
|
||||||
|
max-width: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
div.d3tooltip a{
|
||||||
|
color: white;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
#floating_container {
|
||||||
|
position: absolute;
|
||||||
|
max-width: 50%;
|
||||||
|
height: calc(100% - 1px);
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
opacity: 0.9;
|
||||||
|
right: 0;
|
||||||
|
overflow:hidden;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#floating_content
|
||||||
|
{
|
||||||
|
overflow-y:auto;
|
||||||
|
height: 100%;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
#floating_btn_container
|
||||||
|
{
|
||||||
|
display: block;
|
||||||
|
height: 24px;
|
||||||
|
padding: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid #cccccc;
|
||||||
|
}
|
||||||
|
#floating_btn_container a {
|
||||||
|
text-decoration: none;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
#floating_container, #floating_container a{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
#navbar.navmobile {
|
#navbar.navmobile {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 960px;
|
max-width: 960px;
|
||||||
@ -214,10 +260,43 @@ button {
|
|||||||
white-space: -o-pre-wrap; /* Opera 7 */
|
white-space: -o-pre-wrap; /* Opera 7 */
|
||||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||||
}
|
}
|
||||||
#container .blogentry a {
|
.search-result {
|
||||||
|
color: #24292e;
|
||||||
|
}
|
||||||
|
.search-result ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.search-result ul li{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.search-result ul li b {
|
||||||
|
color: #878887;
|
||||||
|
}
|
||||||
|
.search-result ul li p.title
|
||||||
|
{
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.search-result ul li p.preview {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
#container .blogentry a,
|
||||||
|
.search-result a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #3170b2;
|
color: #3170b2;
|
||||||
}
|
}
|
||||||
|
.search-result h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
text-align: left;
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
border-bottom: 1px solid #eaecef;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
#container .blogentry hr {
|
#container .blogentry hr {
|
||||||
display: block;
|
display: block;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
@ -284,6 +363,9 @@ button {
|
|||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
form.search-form {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
input.search-box {
|
input.search-box {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -6,6 +6,34 @@ BaseController:subclass(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
local tools = {}
|
||||||
|
tools.sum = function(v)
|
||||||
|
local sum = 0.0
|
||||||
|
for i=1,#v do sum = sum + v[i] end
|
||||||
|
return sum
|
||||||
|
end
|
||||||
|
|
||||||
|
tools.mean = function(v)
|
||||||
|
return tools.sum(v)/#v
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
tools.argmax = function(v)
|
||||||
|
local maxv = 0.0
|
||||||
|
local maxi = 0.0
|
||||||
|
for i = 1,#v do
|
||||||
|
if v[i] >= maxv then
|
||||||
|
maxi = i
|
||||||
|
maxv = v[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return maxi,maxv
|
||||||
|
end
|
||||||
|
|
||||||
|
tools.cmp = function(a,b)
|
||||||
|
return a[2] > b[2]
|
||||||
|
end
|
||||||
|
|
||||||
function PostController:index(...)
|
function PostController:index(...)
|
||||||
return self:top(table.unpack({...}))
|
return self:top(table.unpack({...}))
|
||||||
end
|
end
|
||||||
@ -22,7 +50,7 @@ end
|
|||||||
|
|
||||||
function PostController:afterof(id, limit)
|
function PostController:afterof(id, limit)
|
||||||
limit = limit or POST_LIMIT
|
limit = limit or POST_LIMIT
|
||||||
local data, order = self.blog:fetch({[">"] = {id = id}}, limit, {ctime = "ASC"})
|
local data, order = self.blog:fetch({["id$gt"] = tonumber(id)}, limit, { "ctime$asc"})
|
||||||
if not data or #order == 0 then
|
if not data or #order == 0 then
|
||||||
return self:notfound("No entry found")
|
return self:notfound("No entry found")
|
||||||
end
|
end
|
||||||
@ -36,9 +64,68 @@ function PostController:afterof(id, limit)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function PostController:search(...)
|
||||||
|
local index_file = DB_FILE..".index.json"
|
||||||
|
local st = require("stmr")
|
||||||
|
local indexes, err_code = JSON.decodeFile(index_file)
|
||||||
|
local terms = REQUEST.q
|
||||||
|
if not err_code then
|
||||||
|
-- prepare the vectors
|
||||||
|
local docs = {}
|
||||||
|
local tid = 1
|
||||||
|
local tokens = {}
|
||||||
|
local search_vector = {}
|
||||||
|
for word in string.gmatch(terms,'%w+') do
|
||||||
|
local token = st.stmr(word:lower())
|
||||||
|
local index = indexes[token]
|
||||||
|
if index then
|
||||||
|
for id,v in pairs(index) do
|
||||||
|
if not docs[id] then
|
||||||
|
docs[id] = {}
|
||||||
|
end
|
||||||
|
docs[id][token] = v
|
||||||
|
end
|
||||||
|
tokens[tid] = token
|
||||||
|
tid = tid + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--echo(JSON.encode(docs))
|
||||||
|
--echo(JSON.encode(tokens))
|
||||||
|
|
||||||
|
-- now create one vector for each documents
|
||||||
|
local mean_tfidf = {}
|
||||||
|
for id,doc in pairs(docs) do
|
||||||
|
local vector = {}
|
||||||
|
for i,token in ipairs(tokens) do
|
||||||
|
if doc[token] then
|
||||||
|
vector[i] = doc[token]
|
||||||
|
else
|
||||||
|
vector[i] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local data, order = self.blog:find({
|
||||||
|
where = {id = tonumber(id)},
|
||||||
|
fields = {"id", "title", "utime", "ctime", "content"}
|
||||||
|
})
|
||||||
|
if data and data[1] then
|
||||||
|
data[1].content = data[1].content:sub(1,255)
|
||||||
|
table.insert(mean_tfidf, {id, tools.mean(vector), data[1]})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(mean_tfidf, tools.cmp)
|
||||||
|
self.template:setView("search")
|
||||||
|
self.template:set("result", mean_tfidf)
|
||||||
|
self.template:set("title", "Search result")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
LOG_ERROR("Unable to parse file %s", index_file)
|
||||||
|
return self:notfound("Internal search error")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function PostController:beforeof(id, limit)
|
function PostController:beforeof(id, limit)
|
||||||
limit = limit or POST_LIMIT
|
limit = limit or POST_LIMIT
|
||||||
local data, order = self.blog:fetch({["<"] = {id = id}}, limit)
|
local data, order = self.blog:fetch({["id$lt"] = tonumber(id)}, limit)
|
||||||
if not data or #order == 0 then
|
if not data or #order == 0 then
|
||||||
return self:notfound("No entry found")
|
return self:notfound("No entry found")
|
||||||
end
|
end
|
||||||
@ -58,15 +145,15 @@ function PostController:list(data, order)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function PostController:bytag(b64tag, limit, action, id)
|
function PostController:bytag(b64tag, limit, action, id)
|
||||||
local tag = bytes.__tostring(std.b64decode(b64tag .. "=="))
|
local tag = tostring(enc.b64decode(b64tag .. "=="))
|
||||||
local cond = {["LIKE"] = {tags = "%%" .. tag .. "%%"}}
|
local cond = {["tags$like"] = "%%"..tag.."%%"}
|
||||||
local order = nil
|
local order = nil
|
||||||
limit = limit or POST_LIMIT
|
limit = limit or POST_LIMIT
|
||||||
if action == "before" then
|
if action == "before" then
|
||||||
cond = {["and"] = {cond, {["<"] = {id = id}}}}
|
cond["id$lt"] = tonumber(id)
|
||||||
elseif action == "after" then
|
elseif action == "after" then
|
||||||
cond = {["and"] = {cond, {[">"] = {id = id}}}}
|
cond["id$gt"] = tonumber(id)
|
||||||
order = {ctime = "ASC"}
|
order = {"ctime$asc"}
|
||||||
end
|
end
|
||||||
local data, sort = self.blog:fetch(cond, limit, order)
|
local data, sort = self.blog:fetch(cond, limit, order)
|
||||||
if not data or #sort == 0 then
|
if not data or #sort == 0 then
|
||||||
@ -88,8 +175,45 @@ function PostController:bytag(b64tag, limit, action, id)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function PostController:json(id)
|
||||||
|
local obj = {
|
||||||
|
error = false,
|
||||||
|
result = false
|
||||||
|
}
|
||||||
|
local data, order = self.blog:fetch({id = tonumber(id)})
|
||||||
|
if not data or #order == 0 then
|
||||||
|
obj.error = "No data found"
|
||||||
|
else
|
||||||
|
data = data[1]
|
||||||
|
obj.result = {
|
||||||
|
id = data.id,
|
||||||
|
title = data.title,
|
||||||
|
description = nil,
|
||||||
|
tags = data.tags,
|
||||||
|
ctime = data.ctimestr,
|
||||||
|
utime = data.utimestr
|
||||||
|
}
|
||||||
|
|
||||||
|
local c, d = data.content:find("%-%-%-%-%-")
|
||||||
|
if c then
|
||||||
|
obj.description = data.content:sub(0, c - 1)
|
||||||
|
else
|
||||||
|
obj.description = data.content
|
||||||
|
end
|
||||||
|
-- convert description to html
|
||||||
|
local content = ""
|
||||||
|
local md = require("md")
|
||||||
|
local callback = function(s) content = content .. s end
|
||||||
|
md.to_html(obj.description, callback)
|
||||||
|
obj.result.description = content
|
||||||
|
end
|
||||||
|
std.json()
|
||||||
|
std.t(JSON.encode(obj));
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
|
||||||
function PostController:id(pid)
|
function PostController:id(pid)
|
||||||
local data, order = self.blog:fetch({["="] = {id = pid}})
|
local data, order = self.blog:fetch({id = tonumber(pid)})
|
||||||
if not data or #order == 0 then
|
if not data or #order == 0 then
|
||||||
return self:notfound("No post found")
|
return self:notfound("No post found")
|
||||||
end
|
end
|
||||||
@ -112,7 +236,8 @@ function PostController:id(pid)
|
|||||||
self.template:set("similar_posts", similar_posts)
|
self.template:set("similar_posts", similar_posts)
|
||||||
self.template:set("render", true)
|
self.template:set("render", true)
|
||||||
self.template:set("tags", data.tags)
|
self.template:set("tags", data.tags)
|
||||||
self.template:set("url", HTTP_ROOT .. "/post/id/" .. pid)
|
self.template:set("url", string.format(HTTP_ROOT .. "/post/id/%d",pid))
|
||||||
|
-- self.template:set("url", string.format("https://blog.lxsang.me/post/id/%d",pid))
|
||||||
self.template:setView("detail")
|
self.template:setView("detail")
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@ -130,39 +255,48 @@ function PostController:actionnotfound(...)
|
|||||||
return self:notfound("Action [" .. args[1] .. "] not found")
|
return self:notfound("Action [" .. args[1] .. "] not found")
|
||||||
end
|
end
|
||||||
|
|
||||||
function PostController:analyse(n)
|
function PostController:graph_json(...)
|
||||||
if not n then
|
local nodes = self.blog:find({ where = {publish = 1}, fields = {"id", "title"}})
|
||||||
n = 5
|
local output = { error = false, result = false }
|
||||||
end
|
local lut = {}
|
||||||
local path = "/home/mrsang/aiws/blog-clustering"
|
std.json()
|
||||||
local gettext = loadfile(path .. "/gettext.lua")()
|
if not nodes then
|
||||||
local cluster = loadfile(path .. "/cluster.lua")()
|
output.error = "No nodes found"
|
||||||
local data = gettext.get({publish = 1})
|
else
|
||||||
local documents = {}
|
output.result = {
|
||||||
if data then
|
nodes = {},
|
||||||
local sw = gettext.stopwords(path .. "/stopwords.txt")
|
links = {}
|
||||||
for k, v in pairs(data) do
|
}
|
||||||
local bag = cluster.bow(data[k].content, sw)
|
for k,v in ipairs(nodes) do
|
||||||
documents[data[k].id] = bag
|
local title = v.title
|
||||||
|
output.result.nodes[k] = { id = tonumber(v.id), title = title }
|
||||||
end
|
end
|
||||||
cluster.tfidf(documents)
|
-- get statistic links
|
||||||
--local v = cluster.search("arm", documents)
|
local links = self.analytical:find({fields = {"pid", "sid", "score"}})
|
||||||
--echo(JSON.encode(v))
|
if links then
|
||||||
local vectors, maxv, size = cluster.get_vectors(documents)
|
local i = 1
|
||||||
-- purge the table
|
for k,v in ipairs(links) do
|
||||||
self.analytical:delete({["="] = {["1"] = 1}})
|
local link = { source = tonumber(v.pid), target = tonumber(v.sid), score = tonumber(v.score)}
|
||||||
-- get similarity and put to the table
|
local key = ""
|
||||||
for id, v in pairs(vectors) do
|
if link.source < link.target then
|
||||||
local top = cluster.top_similarity(id, vectors, n)
|
key = v.pid..v.sid
|
||||||
for a, b in pairs(top) do
|
else
|
||||||
local record = {pid = id, sid = a, score = b}
|
key = v.sid..v.pid
|
||||||
self.analytical:create(record)
|
end
|
||||||
|
key = enc.sha1(key)
|
||||||
|
if not lut[key] then
|
||||||
|
output.result.links[i] = link
|
||||||
|
i = i + 1
|
||||||
|
lut[key] = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.template:set("message", "Analyse complete")
|
|
||||||
else
|
|
||||||
self.template:set("message", "Cannot analyse")
|
|
||||||
end
|
end
|
||||||
self.template:set("title", "TFIDF-analyse")
|
std.t(JSON.encode(output))
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
function PostController:graph(...)
|
||||||
|
self.template:set("title", "Posts connection graph")
|
||||||
|
self.template:set("d3", true)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
@ -19,24 +19,66 @@ end
|
|||||||
function ServiceController:sendmail()
|
function ServiceController:sendmail()
|
||||||
if not REQUEST.json then
|
if not REQUEST.json then
|
||||||
fail("unknown request")
|
fail("unknown request")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local setting = JSON.decodeFile(SMTP_SETTING)
|
||||||
|
|
||||||
|
local socket = require 'socket'
|
||||||
|
local smtp = require 'socket.smtp'
|
||||||
|
local ssl = require 'ssl'
|
||||||
|
local https = require 'ssl.https'
|
||||||
|
local ltn12 = require 'ltn12'
|
||||||
|
|
||||||
|
local sslCreate = function()
|
||||||
|
local sock = socket.tcp()
|
||||||
|
return setmetatable({
|
||||||
|
connect = function(_, host, port)
|
||||||
|
local r, e = sock:connect(host, port)
|
||||||
|
if not r then return r, e end
|
||||||
|
sock = ssl.wrap(sock, {mode='client', protocol='tlsv1_2'})
|
||||||
|
return sock:dohandshake()
|
||||||
|
end
|
||||||
|
}, {
|
||||||
|
__index = function(t,n)
|
||||||
|
return function(_, ...)
|
||||||
|
return sock[n](sock, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if not setting then
|
||||||
|
fail("Dont know how to connect to SMTP server")
|
||||||
|
return
|
||||||
end
|
end
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
local rq = (JSON.decodeString(REQUEST.json))
|
||||||
local to = "mrsang@lxsang.me"
|
local to = "contact@iohub.dev"
|
||||||
local from = "From: " .. rq.email .. "\n"
|
|
||||||
local suject = "Subject: " .. rq.subject .. "\n"
|
local msg = {
|
||||||
local content = "Contact request from:" .. rq.name .. "\n Email: " .. rq.email .. "\n" .. rq.content .. "\n"
|
headers = {
|
||||||
|
from = string.format("%s <%s>", rq.name, rq.email),
|
||||||
local cmd = 'echo "' .. utils.escape(from .. suject .. content) .. '"| sendmail ' .. to
|
to = string.format("Contact <%s>",to),
|
||||||
|
subject = rq.subject
|
||||||
--print(cmd)
|
},
|
||||||
local r = os.execute(cmd)
|
body = rq.content
|
||||||
|
}
|
||||||
if r then
|
LOG_INFO("Send mail on server %s user %s port %d: %s", setting.server, setting.user, setting.port, JSON.encode(msg))
|
||||||
result(r)
|
local ok, err = smtp.send {
|
||||||
|
from = string.format("<%s>",rq.email),
|
||||||
|
rcpt = string.format('<%s>', to),
|
||||||
|
source = smtp.message(msg),
|
||||||
|
user = setting.user,
|
||||||
|
password = setting.password,
|
||||||
|
server = setting.server,
|
||||||
|
port = math.floor(setting.port),
|
||||||
|
create = sslCreate
|
||||||
|
}
|
||||||
|
if not ok then
|
||||||
|
fail(err)
|
||||||
else
|
else
|
||||||
fail("Cannot send email at the moment, the service may be down")
|
result("Email sent")
|
||||||
end
|
end
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function ServiceController:subscribe()
|
function ServiceController:subscribe()
|
||||||
@ -45,7 +87,7 @@ function ServiceController:subscribe()
|
|||||||
end
|
end
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
local rq = (JSON.decodeString(REQUEST.json))
|
||||||
-- check if email is exist
|
-- check if email is exist
|
||||||
local data = self.subscribers:find({exp = {["="] = {email = rq.email}}})
|
local data = self.subscribers:find({where = {email = rq.email}})
|
||||||
if data and #data > 0 then
|
if data and #data > 0 then
|
||||||
fail("You are already/previously subscribed")
|
fail("You are already/previously subscribed")
|
||||||
else
|
else
|
||||||
|
@ -9,5 +9,8 @@ BaseModel:subclass("AnalyticalModel",{
|
|||||||
})
|
})
|
||||||
|
|
||||||
function AnalyticalModel:similarof(id)
|
function AnalyticalModel:similarof(id)
|
||||||
return self:find({ exp = {["="] = {pid = id}}, order = {score = "DESC"}})
|
return self:find({
|
||||||
|
where = {pid = id},
|
||||||
|
order = { "score$desc"}
|
||||||
|
})
|
||||||
end
|
end
|
@ -15,28 +15,27 @@ BaseModel:subclass("BlogModel",{
|
|||||||
})
|
})
|
||||||
|
|
||||||
function BlogModel:fetch(cnd, limit, order)
|
function BlogModel:fetch(cnd, limit, order)
|
||||||
local exp = {}
|
local filter = {
|
||||||
exp[1] = {["="] = { publish = 1 }}
|
order = { "ctime$desc" },
|
||||||
if cnd then
|
|
||||||
exp[2] = cnd
|
|
||||||
else
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
local cond = {
|
|
||||||
exp = {["and"] = exp },
|
|
||||||
order = { ctime = "DESC" },
|
|
||||||
fields = {
|
fields = {
|
||||||
"id", "title", "utime", "ctime", "utimestr", "ctimestr", "rendered", "tags"
|
"id", "title", "utime", "ctime", "utimestr", "content", "ctimestr", "rendered", "tags"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if limit then
|
if limit then
|
||||||
cond.limit = limit
|
filter.limit = limit
|
||||||
end
|
end
|
||||||
if order then
|
if order then
|
||||||
cond.order = order
|
filter.order = order
|
||||||
end
|
end
|
||||||
return self:find(cond)
|
|
||||||
|
filter.where = {}
|
||||||
|
if cnd then
|
||||||
|
filter.where = cnd
|
||||||
|
end
|
||||||
|
filter.where.publish = 1
|
||||||
|
|
||||||
|
return self:find(filter)
|
||||||
end
|
end
|
||||||
|
|
||||||
function BlogModel:minid()
|
function BlogModel:minid()
|
||||||
|
@ -1,26 +1,30 @@
|
|||||||
|
|
||||||
-- the rewrite rule for the framework
|
-- the rewrite rule for the framework
|
||||||
-- should be something like this
|
-- should be something like this
|
||||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
||||||
-- some global variables
|
-- some global variables
|
||||||
DIR_SEP = "/"
|
package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
|
||||||
|
require("silk.api")
|
||||||
|
-- crypto lib
|
||||||
|
enc = require("enc")
|
||||||
WWW_ROOT = __ROOT__.."/blog"
|
WWW_ROOT = __ROOT__.."/blog"
|
||||||
|
DB_LOC="/opt/www/databases"
|
||||||
|
DB_FILE = DB_LOC.."/mrsang.db"
|
||||||
|
SMTP_SETTING=DB_LOC.."/smtp.json"
|
||||||
|
-- add aditional paths
|
||||||
|
package.path = package.path..";"..WWW_ROOT .. '/?.lua'
|
||||||
|
|
||||||
|
DIR_SEP = "/"
|
||||||
if HEADER.Host then
|
if HEADER.Host then
|
||||||
HTTP_ROOT= "https://"..HEADER.Host
|
HTTP_ROOT= "https://"..HEADER.Host
|
||||||
else
|
else
|
||||||
HTTP_ROOT = "https://blog.lxsang.me"
|
HTTP_ROOT = "https://blog.iohub.dev"
|
||||||
end
|
end
|
||||||
-- class path: path.to.class
|
|
||||||
BASE_FRW = ""
|
CONTROLLER_ROOT = "blog.controllers"
|
||||||
-- class path: path.to.class
|
MODEL_ROOT = "blog.models"
|
||||||
CONTROLLER_ROOT = BASE_FRW.."blog.controllers"
|
|
||||||
MODEL_ROOT = BASE_FRW.."blog.models"
|
|
||||||
-- file path: path/to/file
|
-- file path: path/to/file
|
||||||
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
||||||
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
|
|
||||||
POST_LIMIT = 10
|
POST_LIMIT = 10
|
||||||
-- require needed library
|
|
||||||
require(BASE_FRW.."silk.api")
|
|
||||||
|
|
||||||
if REQUEST.r then
|
if REQUEST.r then
|
||||||
REQUEST.r = REQUEST.r:gsub("%:", "/")
|
REQUEST.r = REQUEST.r:gsub("%:", "/")
|
||||||
@ -29,8 +33,8 @@ end
|
|||||||
-- registry object store global variables
|
-- registry object store global variables
|
||||||
local REGISTRY = {}
|
local REGISTRY = {}
|
||||||
-- set logging level
|
-- set logging level
|
||||||
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = true, DEBUG = false}}
|
REGISTRY.logger = Logger:new{ level = Logger.INFO}
|
||||||
REGISTRY.db = DBHelper:new{db="mrsang"}
|
REGISTRY.db = DBModel:new{db=DB_FILE}
|
||||||
REGISTRY.layout = 'default'
|
REGISTRY.layout = 'default'
|
||||||
REGISTRY.fileaccess = true
|
REGISTRY.fileaccess = true
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
|
|
||||||
<?lua
|
<?lua
|
||||||
|
local chat_uri="https://chat.iohub.dev/comment"
|
||||||
local title = __main__:get("title")
|
local title = __main__:get("title")
|
||||||
local render = __main__:get("render")
|
local render = __main__:get("render")
|
||||||
local url = __main__:get("url")
|
local url = __main__:get("url")
|
||||||
local tags = __main__:get("tags")
|
local tags = __main__:get("tags")
|
||||||
|
local d3 = __main__:get("d3")
|
||||||
local cls = ""
|
local cls = ""
|
||||||
if HEADER.mobile then
|
if HEADER.mobile then
|
||||||
cls = "navmobile"
|
cls = "navmobile"
|
||||||
@ -17,23 +19,35 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/rst/ubuntu-regular.css" />
|
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin" />
|
||||||
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/rst/font-awesome.css" />
|
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/rst/afx.css" />
|
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/assets/afx.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/assets/style.css" />
|
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/assets/style.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/assets/github-markdown.css" />
|
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/assets/github-markdown.css" />
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="https://chat.iohub.dev/assets/quicktalk.css" />
|
<link rel="stylesheet" type="text/css" href="https://chat.iohub.dev/assets/quicktalk.css" />
|
||||||
<script src="https://chat.iohub.dev/assets/quicktalk.js"> </script>
|
<script src="https://chat.iohub.dev/assets/quicktalk.js"> </script>
|
||||||
|
|
||||||
|
<!--link rel="stylesheet" type="text/css" href="https://app.iohub.dev/next/talk/assets/quicktalk.css" />
|
||||||
|
<script src="https://app.iohub.dev/next/talk/assets/quicktalk.js"> </script-->
|
||||||
|
|
||||||
<script src="<?=HTTP_ROOT?>/rst/afx.js"> </script>
|
<script src="<?=HTTP_ROOT?>/assets/afx.js"> </script>
|
||||||
<script src="<?=HTTP_ROOT?>/rst/gscripts/jquery-3.2.1.min.js"> </script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"> </script>
|
||||||
<script src="<?=HTTP_ROOT?>/assets/main.js"></script>
|
<script src="<?=HTTP_ROOT?>/assets/main.js"></script>
|
||||||
|
<?lua if d3 then ?>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.5.0/d3.min.js" ></script>
|
||||||
|
<script src="https://d3js.org/d3-dispatch.v2.min.js"></script>
|
||||||
|
<script src="https://d3js.org/d3-quadtree.v2.min.js"></script>
|
||||||
|
<script src="https://d3js.org/d3-timer.v2.min.js"></script>
|
||||||
|
<script src="https://d3js.org/d3-force.v2.min.js"></script>
|
||||||
|
<script src="<?=HTTP_ROOT?>/assets/graph.js"></script>
|
||||||
|
<?lua end ?>
|
||||||
<meta property="og:image" content="" />
|
<meta property="og:image" content="" />
|
||||||
<?lua if render then ?>
|
<?lua if render then ?>
|
||||||
|
<?lua url = url:gsub("lxsang.me", "iohub.dev") ?>
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
<meta name="twitter:site" content="@blog.lxsang.me" />
|
<meta name="twitter:site" content="@blog.iohub.dev" />
|
||||||
<meta name="twitter:creator" content="@lexsang" />
|
<meta name="twitter:creator" content="@DanyLE" />
|
||||||
<meta property="og:url" content="<?=url?>" />
|
<meta property="og:url" content="<?=url?>" />
|
||||||
<meta property="og:type" content="article" />
|
<meta property="og:type" content="article" />
|
||||||
<meta property="og:title" content="<?=title?>" />
|
<meta property="og:title" content="<?=title?>" />
|
||||||
@ -45,9 +59,9 @@
|
|||||||
<script src="<?=HTTP_ROOT?>/rst/katex/katex.min.js"> </script>
|
<script src="<?=HTTP_ROOT?>/rst/katex/katex.min.js"> </script>
|
||||||
<script src="<?=HTTP_ROOT?>/rst/katex/auto-render.min.js"> </script>
|
<script src="<?=HTTP_ROOT?>/rst/katex/auto-render.min.js"> </script>
|
||||||
<?lua else ?>
|
<?lua else ?>
|
||||||
<meta property="og:url" content="https://blog.lxsang.me" />
|
<meta property="og:url" content="https://blog.iohub.dev" />
|
||||||
<meta property="og:type" content="article" />
|
<meta property="og:type" content="article" />
|
||||||
<meta property="og:title" content="Xuan Sang LE's blog" />
|
<meta property="og:title" content="Dany LE's blog" />
|
||||||
<meta property="og:description" content="Blog Home" />
|
<meta property="og:description" content="Blog Home" />
|
||||||
<?lua end ?>
|
<?lua end ?>
|
||||||
<script>
|
<script>
|
||||||
@ -57,7 +71,7 @@
|
|||||||
var options = {
|
var options = {
|
||||||
target: "quick_talk_comment_thread",
|
target: "quick_talk_comment_thread",
|
||||||
page: "desktop",
|
page: "desktop",
|
||||||
api_uri: "https://chat.iohub.dev/comment",
|
api_uri: "<?=chat_uri?>",
|
||||||
uri: "<?=url?>",
|
uri: "<?=url?>",
|
||||||
author: {
|
author: {
|
||||||
first: "mrsang",
|
first: "mrsang",
|
||||||
@ -113,22 +127,25 @@
|
|||||||
|
|
||||||
<div id = "top">
|
<div id = "top">
|
||||||
<div id = "navbar" class = "<?=cls?>">
|
<div id = "navbar" class = "<?=cls?>">
|
||||||
<div class = "logo"><a href = "https://lxsang.me"></a></div>
|
<div class = "logo"><a href = "https://iohub.dev"></a></div>
|
||||||
<ul>
|
<ul>
|
||||||
<li><i class = "fa fa-home"></i><a href="<?=HTTP_ROOT?>">Home</a></li>
|
<li><i class = "fa fa-home"></i><a href="<?=HTTP_ROOT?>">Home</a></li>
|
||||||
<li ><i class = "fa fa-address-card"></i><a href="https://info.lxsang.me" >Portfolio</a></li>
|
|
||||||
<li><i class = "fa fa-envelope"></i><a href="#" onclick="mailtoMe('<?=HTTP_ROOT?>')" >Contact</a></li>
|
|
||||||
<?lua
|
<?lua
|
||||||
if not HEADER.mobile then
|
if not HEADER.mobile then
|
||||||
?>
|
?>
|
||||||
|
<li > <i class = "fa fa-globe"></i><a href = "<?=HTTP_ROOT?>/post/graph">Explore</a></li>
|
||||||
<li> <i class = "fa fa-paper-plane"></i><a href="#" onclick="subscribe('<?=HTTP_ROOT?>')">Subscribe</a></li>
|
<li> <i class = "fa fa-paper-plane"></i><a href="#" onclick="subscribe('<?=HTTP_ROOT?>')">Subscribe</a></li>
|
||||||
<li > <i class = "fa fa-globe"></i><a href = "https://os.lxsang.me" target="_blank">AntOS</a></li>
|
|
||||||
<?lua end ?>
|
<?lua end ?>
|
||||||
|
<li ><i class = "fa fa-address-card"></i><a href="https://info.iohub.dev" >Portfolio</a></li>
|
||||||
|
<li><i class = "fa fa-envelope"></i><a href="#" onclick="mailtoMe('<?=HTTP_ROOT?>')" >Contact</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<?lua
|
<?lua
|
||||||
if not HEADER.mobile then
|
if not HEADER.mobile then
|
||||||
?>
|
?>
|
||||||
<input type = "text" class = "search-box"></input>
|
<form class="search-form" method="get" action="<?=HTTP_ROOT?>/post/search">
|
||||||
|
<input type = "text" class = "search-box" name="q"></input>
|
||||||
|
<input type="submit" hidden ></input>
|
||||||
|
</form>
|
||||||
<div class= "search-icon"></div>
|
<div class= "search-icon"></div>
|
||||||
<?lua
|
<?lua
|
||||||
end
|
end
|
||||||
@ -143,7 +160,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id = "bottom">
|
<div id = "bottom">
|
||||||
Powered by antd server, (c) 2017 - <?=os.date("*t").year?> Xuan Sang LE
|
Powered by antd server, (c) 2017 - <?=os.date("*t").year?> Dany LE.
|
||||||
|
<?lua
|
||||||
|
if not HEADER.mobile then
|
||||||
|
?>
|
||||||
|
This site does not use cookie, but some third-party contents (e.g. Youtube, Twitter) may do.
|
||||||
|
<?lua
|
||||||
|
end
|
||||||
|
?>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
local atags = {}
|
local atags = {}
|
||||||
local i = 1
|
local i = 1
|
||||||
for tag in data.tags:gmatch(",*([^,]+)") do
|
for tag in data.tags:gmatch(",*([^,]+)") do
|
||||||
tag = std.trim(tag, " ")
|
tag = ulib.trim(tag, " ")
|
||||||
if tag ~= "" then
|
if tag ~= "" then
|
||||||
local b64tag = std.b64encode(tag)
|
local b64tag = enc.b64encode(tag)
|
||||||
atags[i] = '<a href = "'..HTTP_ROOT..'/post/bytag/'..b64tag:gsub("=","")..'/'..POST_LIMIT..'">'..tag.."</a>"
|
atags[i] = '<a href = "'..HTTP_ROOT..'/post/bytag/'..b64tag:gsub("=","")..'/'..POST_LIMIT..'">'..tag.."</a>"
|
||||||
i = i+ 1
|
i = i+ 1
|
||||||
end
|
end
|
||||||
|
26
blog/views/default/post/graph.ls
Normal file
26
blog/views/default/post/graph.ls
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<div id="floating_container">
|
||||||
|
<div id="floating_btn_container">
|
||||||
|
<a id="floating_btn_close" href="#" ><i class="fa fa-close"></i> Close</a>
|
||||||
|
<a id="floating_btn_read_more" href="#"><i class="fa fa-chain"></i> Read more</a>
|
||||||
|
</div>
|
||||||
|
<div id="floating_content">
|
||||||
|
<p>
|
||||||
|
The graph shows this blog posts relationship in term of similarity.
|
||||||
|
Each node in the graph is a post, two nodes are connected by an edge if
|
||||||
|
they share some degree of similarity (weighted by edge thickness and edge distance).
|
||||||
|
A large edge thickness and/or short edge distance shows a strong similarity between
|
||||||
|
the two connected nodes.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Nodes are arranged by force which is modelled by content similarity.
|
||||||
|
The more similar the nodes content, the stronger the force between them.
|
||||||
|
Therefore, nodes that share similar topic will tend to group themself together in a cluster.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Navigate the blog by hovering the mouse on a node and following the node relationship
|
||||||
|
(edges) to find your interesting topic.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -1,3 +1,6 @@
|
|||||||
|
<?lua if not HEADER.mobile then ?>
|
||||||
|
<!--iframe width="980" height="410" src="https://mars.nasa.gov/layout/embed/send-your-name/future/certificate/?cn=792789419260" frameborder="0"></iframe-->
|
||||||
|
<?lua end ?>
|
||||||
<?lua
|
<?lua
|
||||||
local datas = posts
|
local datas = posts
|
||||||
local class = "card"
|
local class = "card"
|
||||||
@ -20,16 +23,16 @@
|
|||||||
local atags = {}
|
local atags = {}
|
||||||
local i = 1
|
local i = 1
|
||||||
for tag in data.tags:gmatch(",*([^,]+)") do
|
for tag in data.tags:gmatch(",*([^,]+)") do
|
||||||
tag = std.trim(tag, " ")
|
tag = ulib.trim(tag, " ")
|
||||||
if tag ~= "" then
|
if tag ~= "" then
|
||||||
local b64tag = std.b64encode(tag)
|
local b64tag = enc.b64encode(tag)
|
||||||
atags[i] = '<a href = "'..HTTP_ROOT..'/post/bytag/'..b64tag:gsub("=","")..'/'..POST_LIMIT..'">'..tag.."</a>"
|
atags[i] = '<a href = "'..HTTP_ROOT..'/post/bytag/'..b64tag:gsub("=","")..'/'..POST_LIMIT..'">'..tag.."</a>"
|
||||||
i = i+ 1
|
i = i+ 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
echo(table.concat(atags, ", "))
|
echo(table.concat(atags, ", "))
|
||||||
local url = HTTP_ROOT.."/post/id/"..data.id
|
local url = HTTP_ROOT.."/post/id/"..string.format("%d",data.id)
|
||||||
local old_url = HTTP_ROOT.."/r:id:"..data.id
|
local old_url = HTTP_ROOT.."/r:id:"..string.format("%d",data.id)
|
||||||
?>
|
?>
|
||||||
</span>
|
</span>
|
||||||
<!--div class="fb-like" data-href="<?=old_url?>" data-layout="button_count" data-action="like" data-size="small" data-show-faces="true" data-share="true"></div-->
|
<!--div class="fb-like" data-href="<?=old_url?>" data-layout="button_count" data-action="like" data-size="small" data-show-faces="true" data-share="true"></div-->
|
||||||
@ -55,7 +58,7 @@
|
|||||||
end
|
end
|
||||||
if title then
|
if title then
|
||||||
echo(content:sub(0, b))
|
echo(content:sub(0, b))
|
||||||
echo("<a class = 'title_link' href='"..HTTP_ROOT.."/post/id/"..data.id.."'>"..title.."</a>")
|
echo("<a class = 'title_link' href='"..url.."'>"..title.."</a>")
|
||||||
echo(content:sub(c))
|
echo(content:sub(c))
|
||||||
else
|
else
|
||||||
echo(content)
|
echo(content)
|
||||||
@ -64,18 +67,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class = "detail">
|
<div class = "detail">
|
||||||
<span></span>
|
<span></span>
|
||||||
<?='<a href="'..HTTP_ROOT..'/post/id/'..data.id..'" ></a>'?>
|
<?='<a href="'..url..'" ></a>'?>
|
||||||
<span></span>
|
<span></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?lua
|
<?lua
|
||||||
end
|
end
|
||||||
local beforelk = HTTP_ROOT.."/post/beforeof/"..first_id.."/"..POST_LIMIT
|
local beforelk = HTTP_ROOT.."/post/beforeof/"..string.format("%d",first_id).."/"..POST_LIMIT
|
||||||
local afterlk = HTTP_ROOT.."/post/afterof/"..last_id.."/"..POST_LIMIT
|
local afterlk = HTTP_ROOT.."/post/afterof/"..string.format("%d",last_id).."/"..POST_LIMIT
|
||||||
if action == "bytag" or action == "search" then
|
if action == "bytag" or action == "search" then
|
||||||
beforelk = HTTP_ROOT.."/post/"..action.."/"..query.."/"..POST_LIMIT.."/before/"..first_id
|
beforelk = HTTP_ROOT.."/post/"..action.."/"..query.."/"..POST_LIMIT.."/before/"..string.format("%d",first_id)
|
||||||
afterlk = HTTP_ROOT.."/post/"..action.."/"..query.."/"..POST_LIMIT.."/after/"..last_id
|
afterlk = HTTP_ROOT.."/post/"..action.."/"..query.."/"..POST_LIMIT.."/after/"..string.format("%d",last_id)
|
||||||
end
|
end
|
||||||
?>
|
?>
|
||||||
<div class = "time-travel">
|
<div class = "time-travel">
|
||||||
|
17
blog/views/default/post/search.ls
Normal file
17
blog/views/default/post/search.ls
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<div class="search-result">
|
||||||
|
<h2>Posts matched for query: <?=REQUEST.q?></h2>
|
||||||
|
<ul>
|
||||||
|
<?lua
|
||||||
|
for i,v in ipairs(result) do
|
||||||
|
?>
|
||||||
|
<li>
|
||||||
|
<p class="title">
|
||||||
|
<b>Score <?=string.format("%.3f",v[2])?></b> <a href="<?=HTTP_ROOT?>/post/id/<?=v[3].id?>"><?=v[3].title?></a>
|
||||||
|
</p>
|
||||||
|
<p class="preview">
|
||||||
|
<?=v[3].content?>...
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<?lua end ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
@ -1,6 +0,0 @@
|
|||||||
copyfiles = router.lua scripts
|
|
||||||
|
|
||||||
main:
|
|
||||||
- mkdir -p $(BUILDDIR)
|
|
||||||
cp -rvf $(copyfiles) $(BUILDDIR)
|
|
||||||
- mkdir -p $(BUILDDIR)/log
|
|
@ -1,82 +0,0 @@
|
|||||||
|
|
||||||
-- the rewrite rule for the framework
|
|
||||||
-- should be something like this
|
|
||||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
|
||||||
-- some global variables
|
|
||||||
function fail(msg)
|
|
||||||
std.json()
|
|
||||||
std.t(JSON.encode({error=msg}))
|
|
||||||
end
|
|
||||||
|
|
||||||
function result(obj)
|
|
||||||
std.json()
|
|
||||||
std.t(JSON.encode({result=obj, error=false}))
|
|
||||||
end
|
|
||||||
DIR_SEP = "/"
|
|
||||||
WWW_ROOT = __ROOT__.."/ci"
|
|
||||||
if HEADER.Host then
|
|
||||||
HTTP_ROOT= "https://"..HEADER.Host
|
|
||||||
else
|
|
||||||
HTTP_ROOT = "https://ci.iohub.dev"
|
|
||||||
end
|
|
||||||
-- class path: path.to.class
|
|
||||||
BASE_FRW = ""
|
|
||||||
-- class path: path.to.class
|
|
||||||
CONTROLLER_ROOT = BASE_FRW.."ci.controllers"
|
|
||||||
MODEL_ROOT = BASE_FRW.."ci.models"
|
|
||||||
-- file path: path/to/file
|
|
||||||
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
|
||||||
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
|
|
||||||
|
|
||||||
-- require needed library
|
|
||||||
require(BASE_FRW.."silk.api")
|
|
||||||
|
|
||||||
function NotfoundController:index(...)
|
|
||||||
local args = {...}
|
|
||||||
if #args == 0 then
|
|
||||||
fail("Unknown action")
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
local action = args[1]
|
|
||||||
if action == "BuildController" then
|
|
||||||
if REQUEST.json then
|
|
||||||
local request = JSON.decodeString(REQUEST.json)
|
|
||||||
if request.ref and request.ref == "refs/heads/ci" then
|
|
||||||
local branch = "ci"
|
|
||||||
local repository = request.repository.name
|
|
||||||
local path = WWW_ROOT..DIR_SEP.."scripts"..DIR_SEP..repository..".sh"
|
|
||||||
|
|
||||||
if ulib.exists(path) then
|
|
||||||
result("Build action triggered, log file will soon be available at: https://ci.iohub.dev/log/"..repository.."_"..branch..".txt")
|
|
||||||
os.execute("at now -f "..path)
|
|
||||||
else
|
|
||||||
fail("No build script found")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
result("This action is ignored by the CI")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Unknow action parameters")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Action not supported: "..action)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
-- registry object store global variables
|
|
||||||
local REGISTRY = {}
|
|
||||||
-- set logging level
|
|
||||||
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = false, DEBUG = false}}
|
|
||||||
|
|
||||||
REGISTRY.layout = 'default'
|
|
||||||
REGISTRY.fileaccess = true
|
|
||||||
|
|
||||||
local router = Router:new{registry = REGISTRY}
|
|
||||||
REGISTRY.router = router
|
|
||||||
router:setPath(CONTROLLER_ROOT)
|
|
||||||
--router:route('edit', 'post/edit', "ALL" )
|
|
||||||
|
|
||||||
router:route('default', default_routes_dependencies )
|
|
||||||
router:delegate()
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
BRANCH="ci"
|
|
||||||
PRJ="antd-web-apps"
|
|
||||||
DEST="/opt/www/htdocs"
|
|
||||||
|
|
||||||
REPO="https://github.com/lxsang/$PRJ.git"
|
|
||||||
|
|
||||||
|
|
||||||
if [ ! -z $1 ]; then
|
|
||||||
BRANCH="$1"
|
|
||||||
fi
|
|
||||||
{
|
|
||||||
echo "Build date: $(date)"
|
|
||||||
echo "Building $PRJ using branch $BRANCH..."
|
|
||||||
if [ -d "/tmp/ci/$PRJ" ]; then
|
|
||||||
echo "Clean up /tmp/ci/$PRJ"
|
|
||||||
rm -rf /tmp/ci/$PRJ
|
|
||||||
else
|
|
||||||
echo "Creating /tmp/ci/"
|
|
||||||
mkdir -p "/tmp/ci"
|
|
||||||
fi
|
|
||||||
cd /tmp/ci || (echo "Unable to change directory to /tmp/ci" && exit 1)
|
|
||||||
echo "Cloning $PRJ (branch $BRANCH) to /tmp/ci..."
|
|
||||||
git clone -b "$BRANCH" --single-branch --depth=1 "$REPO"
|
|
||||||
cd "$PRJ" || (echo "Unable to change directory to source code folder" && exit 1)
|
|
||||||
mkdir -p "$DEST"
|
|
||||||
BUILDDIR="$DEST" make
|
|
||||||
echo "Done!"
|
|
||||||
} 2>&1 | tee "/opt/www/htdocs/ci/log/${PRJ}_${BRANCH}.txt"
|
|
||||||
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
BRANCH="ci"
|
|
||||||
PRJ="antos"
|
|
||||||
DEST="/opt/www/htdocs/"
|
|
||||||
# /opt/www/htdocs
|
|
||||||
REPO="https://github.com/lxsang/$PRJ.git"
|
|
||||||
|
|
||||||
if [ ! -z $1 ]; then
|
|
||||||
BRANCH="$1"
|
|
||||||
fi
|
|
||||||
{
|
|
||||||
echo "Build date: $(date)"
|
|
||||||
echo "Building $PRJ using branch $BRANCH..."
|
|
||||||
if [ -d "/tmp/ci/$PRJ" ]; then
|
|
||||||
echo "Clean up /tmp/ci/$PRJ"
|
|
||||||
rm -rf /tmp/ci/$PRJ
|
|
||||||
else
|
|
||||||
echo "Creating /tmp/ci/"
|
|
||||||
mkdir -p "/tmp/ci"
|
|
||||||
fi
|
|
||||||
cd /tmp/ci || (echo "Unable to change directory to /tmp/ci" && exit 1)
|
|
||||||
echo "Cloning $PRJ (branch $BRANCH) to /tmp/ci..."
|
|
||||||
git clone -b "$BRANCH" --single-branch --depth=1 "$REPO"
|
|
||||||
cd "$PRJ" || (echo "Unable to change directory to source code folder" && exit 1)
|
|
||||||
npm i @types/jquery
|
|
||||||
mkdir -p "$DEST/os"
|
|
||||||
BUILDDIR="$DEST/os" make release
|
|
||||||
mkdir -p "$DEST/grs"
|
|
||||||
BUILDDIR="$DEST/grs" make standalone_tags
|
|
||||||
echo "Done!"
|
|
||||||
} 2>&1 | tee "/opt/www/htdocs/ci/log/${PRJ}_${BRANCH}.txt"
|
|
BIN
dist/antd_web_apps.tar.gz
vendored
BIN
dist/antd_web_apps.tar.gz
vendored
Binary file not shown.
4
doc/assets/office.css
Normal file
4
doc/assets/office.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
@ -29,6 +29,7 @@ body {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-top: 1px solid #878887;
|
border-top: 1px solid #878887;
|
||||||
|
z-index: 22;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cover {
|
#cover {
|
||||||
@ -38,14 +39,14 @@ body {
|
|||||||
}
|
}
|
||||||
#navbar {
|
#navbar {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 80%;
|
/* max-width: 80%; */
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
#book {
|
#book {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 80%;
|
/* max-width: 80%; */
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@ -53,10 +54,36 @@ body {
|
|||||||
text-align: justify;
|
text-align: justify;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
div.doc-name {
|
div.doc-toc-menu {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
}
|
}
|
||||||
|
div.doc-toc-menu a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #c9c9c9;
|
||||||
|
}
|
||||||
|
div.doc-toc-legend::before
|
||||||
|
{
|
||||||
|
content: "\f0c9";
|
||||||
|
color: #2c2c2c;
|
||||||
|
width: 20px;
|
||||||
|
height: 25px;
|
||||||
|
margin-right: 3px;
|
||||||
|
font-family: "FontAwesome";
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
div.doc-toc-menu a::before {
|
||||||
|
/* padding-top:13px; */
|
||||||
|
content: "\f02d";
|
||||||
|
color: #c9c9c9;
|
||||||
|
width: 20px;
|
||||||
|
height: 25px;
|
||||||
|
font-family: "FontAwesome";
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
div.doc-name{
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
div.doc-name a {
|
div.doc-name a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #c9c9c9;
|
color: #c9c9c9;
|
||||||
@ -65,13 +92,14 @@ div.doc-name a {
|
|||||||
a.x-link,
|
a.x-link,
|
||||||
a.x-link:hover {
|
a.x-link:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #c9c9c9;
|
color: #2c2c2c;
|
||||||
padding-left: 15px;
|
font-weight: bold;
|
||||||
|
/* padding-left: 15px; */
|
||||||
padding-top: 7px;
|
padding-top: 7px;
|
||||||
}
|
}
|
||||||
a.x-link::before {
|
a.x-link::before {
|
||||||
content: "\f08e";
|
content: "\f08e";
|
||||||
color: #c9c9c9;
|
color: #2c2c2c;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
font-family: "FontAwesome";
|
font-family: "FontAwesome";
|
||||||
@ -80,7 +108,7 @@ a.x-link::before {
|
|||||||
div.doc-name a::before {
|
div.doc-name a::before {
|
||||||
/* padding-top:13px; */
|
/* padding-top:13px; */
|
||||||
content: "\f015";
|
content: "\f015";
|
||||||
color: #c9c9c9;
|
color: #2c2c2c;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
font-family: "FontAwesome";
|
font-family: "FontAwesome";
|
||||||
@ -117,8 +145,8 @@ div.search-icon {
|
|||||||
width: 35px;
|
width: 35px;
|
||||||
}
|
}
|
||||||
div.doc-toc {
|
div.doc-toc {
|
||||||
width: 300px;
|
max-width: 70%;
|
||||||
/* font-size: 11px; */
|
padding-right: 0;
|
||||||
background-color: #e3e3e3;
|
background-color: #e3e3e3;
|
||||||
color: #2c2c2c;
|
color: #2c2c2c;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
@ -126,8 +154,38 @@ div.doc-toc {
|
|||||||
top: 30px;
|
top: 30px;
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
border-right: 1px solid #c9c9c9;
|
border-right: 1px solid #c9c9c9;
|
||||||
padding-top: 10px;
|
border-left: 1px solid #c9c9c9;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
box-shadow: 0px 6px 3px -1px rgba(0,0,0,0.65);
|
||||||
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.toc-active {
|
||||||
|
border: 1px solid #c9c9c9;
|
||||||
|
border-bottom: 0;
|
||||||
|
background-color: #e3e3e3;
|
||||||
|
outline: none;
|
||||||
|
display: block;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
color: #2c2c2c !important;
|
||||||
|
}
|
||||||
|
a.toc-active::before{
|
||||||
|
color: #2c2c2c !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.doc-toc-header{
|
||||||
|
display: block;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding: 3px;
|
||||||
|
/* height: 24px; */
|
||||||
|
border-bottom: 1px solid #c9c9c9;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
div.doc-toc a {
|
div.doc-toc a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #2c2c2c;
|
color: #2c2c2c;
|
||||||
@ -137,6 +195,7 @@ div.doc-toc a {
|
|||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
div.doc-toc ul.nested {
|
div.doc-toc ul.nested {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
@ -178,7 +237,7 @@ div.doc-toc a.highlight {
|
|||||||
|
|
||||||
div.doc-content {
|
div.doc-content {
|
||||||
display: block;
|
display: block;
|
||||||
width: calc(100% - 300px);
|
width: 100%;
|
||||||
float: right;
|
float: right;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,11 @@ require(CONTROLLER_ROOT..".doccontroller")
|
|||||||
DocController:subclass("AntosController", {
|
DocController:subclass("AntosController", {
|
||||||
path_map = {
|
path_map = {
|
||||||
vfs_path = "book://",
|
vfs_path = "book://",
|
||||||
local_path = "/home/mrsang/doc/antos",
|
local_path = DOC_DIR.."/antos",
|
||||||
api_path = "/opt/www/htdocs/doc/antos"
|
|
||||||
},
|
},
|
||||||
name = "antos",
|
name = "antos",
|
||||||
elinks = {
|
elinks = {
|
||||||
{ name = "API", url = "https://doc.iohub.dev/antos/api/index.html" },
|
{ name = "API", url = "https://ci.iohub.dev/public/antos-release/doc/1.2.x/" },
|
||||||
{ name = "Code", url = "https://github.com/lxsang/antos" }
|
{ name = "Code", url = "https://github.com/lxsang/antos" }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
BaseController:subclass("IndexController")
|
BaseController:subclass("IndexController")
|
||||||
|
|
||||||
function IndexController:index(...)
|
function IndexController:index(...)
|
||||||
local file = io.open("/home/mrsang/doc/library.md", "r")
|
local file = io.open(DOC_COVER, "r")
|
||||||
if file then
|
if file then
|
||||||
local content = ""
|
local content = ""
|
||||||
local md = require("md")
|
local md = require("md")
|
||||||
local callback = function(s) content = content .. s end
|
local callback = function(s) content = content .. s end
|
||||||
md.to_html(file:read("*a"), callback)
|
md.to_html(file:read("*a"), callback)
|
||||||
file.close()
|
file:close()
|
||||||
self.template:set("data", content)
|
self.template:set("data", content)
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
|
@ -2,7 +2,7 @@ require(CONTROLLER_ROOT..".doccontroller")
|
|||||||
DocController:subclass("JarvisController", {
|
DocController:subclass("JarvisController", {
|
||||||
path_map = {
|
path_map = {
|
||||||
vfs_path = "book://",
|
vfs_path = "book://",
|
||||||
local_path = "/home/mrsang/doc/jarvis",
|
local_path = DOC_DIR.."/jarvis",
|
||||||
},
|
},
|
||||||
name = "jarvis"
|
name = "jarvis"
|
||||||
})
|
})
|
||||||
|
@ -13,7 +13,7 @@ local pre_process_md = function(str, obj)
|
|||||||
"%%-")
|
"%%-")
|
||||||
if apath then
|
if apath then
|
||||||
apath = apath:gsub(" ", "%%%%20")
|
apath = apath:gsub(" ", "%%%%20")
|
||||||
print(apath)
|
--print(apath)
|
||||||
content = content:gsub(pattern, "")
|
obj.name .. "/asset/" .. apath .. ")")
|
||||||
end
|
end
|
||||||
@ -107,8 +107,8 @@ function DocController:loadTOC()
|
|||||||
if line then
|
if line then
|
||||||
local file =
|
local file =
|
||||||
{
|
{
|
||||||
name = std.trim(
|
name = ulib.trim(
|
||||||
std.trim(line, "#"), " "),
|
ulib.trim(line, "#"), " "),
|
||||||
path = vf.path,
|
path = vf.path,
|
||||||
tpath = vf.path,
|
tpath = vf.path,
|
||||||
parent = section,
|
parent = section,
|
||||||
@ -145,14 +145,14 @@ function DocController:index(...)
|
|||||||
if args[1] then
|
if args[1] then
|
||||||
local b64text = args[1]
|
local b64text = args[1]
|
||||||
if b64text then
|
if b64text then
|
||||||
local p = bytes.__tostring(std.b64decode(b64text .. "=="))
|
local p = tostring(enc.b64decode(b64text .. "=="))
|
||||||
if p then
|
if p then
|
||||||
toc.cpath = p
|
toc.cpath = p
|
||||||
path = getpath(p, self)
|
path = getpath(p, self)
|
||||||
if path and ulib.exists(path) then
|
if path and ulib.exists(path) then
|
||||||
self.template:set("url", HTTP_ROOT .. '/' .. self.name ..
|
self.template:set("url", HTTP_ROOT .. '/' .. self.name ..
|
||||||
'/' ..
|
'/' ..
|
||||||
std.b64encode(toc.cpath):gsub("=", ""))
|
enc.b64encode(toc.cpath):gsub("=", ""))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -167,7 +167,7 @@ function DocController:index(...)
|
|||||||
local md = require("md")
|
local md = require("md")
|
||||||
local callback = function(s) content = content .. s end
|
local callback = function(s) content = content .. s end
|
||||||
md.to_html(pre_process_md(file:read("*a"), self), callback)
|
md.to_html(pre_process_md(file:read("*a"), self), callback)
|
||||||
file.close()
|
file:close()
|
||||||
content, has_3d = post_process_md(content, self)
|
content, has_3d = post_process_md(content, self)
|
||||||
-- replace some display plugins
|
-- replace some display plugins
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ function DocController:search(...)
|
|||||||
local arr = explode(query, " ")
|
local arr = explode(query, " ")
|
||||||
local patterns = {}
|
local patterns = {}
|
||||||
for k, v in ipairs(arr) do
|
for k, v in ipairs(arr) do
|
||||||
local world = std.trim(v, " ")
|
local world = ulib.trim(v, " ")
|
||||||
if v and v ~= "" then
|
if v and v ~= "" then
|
||||||
cmd = cmd .. " -e '" .. v .. "' "
|
cmd = cmd .. " -e '" .. v .. "' "
|
||||||
table.insert(patterns, v:lower())
|
table.insert(patterns, v:lower())
|
||||||
|
@ -3,24 +3,29 @@
|
|||||||
-- should be something like this
|
-- should be something like this
|
||||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
||||||
-- some global variables
|
-- some global variables
|
||||||
DIR_SEP = "/"
|
package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
|
||||||
|
require("silk.api")
|
||||||
|
-- crypto lib
|
||||||
|
enc = require("enc")
|
||||||
WWW_ROOT = __ROOT__.."/doc"
|
WWW_ROOT = __ROOT__.."/doc"
|
||||||
|
DIR_SEP = "/"
|
||||||
if HEADER.Host then
|
if HEADER.Host then
|
||||||
HTTP_ROOT= "https://"..HEADER.Host
|
HTTP_ROOT= "https://"..HEADER.Host
|
||||||
else
|
else
|
||||||
HTTP_ROOT = "https://doc.iohub.dev"
|
HTTP_ROOT = "https://doc.iohub.dev"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
-- class path: path.to.class
|
-- class path: path.to.class
|
||||||
BASE_FRW = ""
|
CONTROLLER_ROOT = "doc.controllers"
|
||||||
-- class path: path.to.class
|
MODEL_ROOT = "doc.models"
|
||||||
CONTROLLER_ROOT = BASE_FRW.."doc.controllers"
|
|
||||||
MODEL_ROOT = BASE_FRW.."doc.models"
|
|
||||||
-- file path: path/to/file
|
-- file path: path/to/file
|
||||||
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
||||||
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
|
|
||||||
POST_LIMIT = 10
|
DOC_DIR = "/opt/www/doc"
|
||||||
-- require needed library
|
DOC_COVER = DOC_DIR.."/library.md"
|
||||||
require(BASE_FRW.."silk.api")
|
|
||||||
|
package.path = package.path..";"..__ROOT__.."/os/libs/?.lua"
|
||||||
|
|
||||||
POLICY.mimes["model/gltf-binary"] = true
|
POLICY.mimes["model/gltf-binary"] = true
|
||||||
|
|
||||||
@ -31,7 +36,7 @@ end
|
|||||||
-- registry object store global variables
|
-- registry object store global variables
|
||||||
local REGISTRY = {}
|
local REGISTRY = {}
|
||||||
-- set logging level
|
-- set logging level
|
||||||
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = true, DEBUG = false}}
|
REGISTRY.logger = Logger:new{ level = Logger.INFO}
|
||||||
REGISTRY.layout = 'default'
|
REGISTRY.layout = 'default'
|
||||||
REGISTRY.fileaccess = true
|
REGISTRY.fileaccess = true
|
||||||
|
|
||||||
|
@ -76,12 +76,12 @@
|
|||||||
<?lua
|
<?lua
|
||||||
|
|
||||||
if prev_entry then
|
if prev_entry then
|
||||||
echo("<a class = 'go_prev' href="..HTTP_ROOT..'/'..toc.controller..'/'..std.b64encode(prev_entry.path):gsub("=","")..'/'..prev_entry.name:gsub(" ", "_")..".md".." >")
|
echo("<a class = 'go_prev' href="..HTTP_ROOT..'/'..toc.controller..'/'..enc.b64encode(prev_entry.path):gsub("=","")..'/'..prev_entry.name:gsub(" ", "_")..".md?show_toc=false".." >")
|
||||||
echo(prev_entry.name)
|
echo(prev_entry.name)
|
||||||
echo("</a>")
|
echo("</a>")
|
||||||
end
|
end
|
||||||
if next_entry then
|
if next_entry then
|
||||||
echo("<a class = 'go_next' href="..HTTP_ROOT..'/'..toc.controller..'/'..std.b64encode(next_entry.path):gsub("=","")..'/'..next_entry.name:gsub(" ", "_")..".md".." >")
|
echo("<a class = 'go_next' href="..HTTP_ROOT..'/'..toc.controller..'/'..enc.b64encode(next_entry.path):gsub("=","")..'/'..next_entry.name:gsub(" ", "_")..".md?show_toc=false".." >")
|
||||||
echo(next_entry.name)
|
echo(next_entry.name)
|
||||||
echo("</a>")
|
echo("</a>")
|
||||||
end
|
end
|
||||||
@ -108,12 +108,12 @@ The comment editor supports <b>Markdown</b> syntax. Your email is necessary to n
|
|||||||
<div class = "pagenav">
|
<div class = "pagenav">
|
||||||
<?lua
|
<?lua
|
||||||
if prev_entry then
|
if prev_entry then
|
||||||
echo("<a class = 'go_prev' href="..HTTP_ROOT..'/'..toc.controller..'/'..std.b64encode(prev_entry.path):gsub("=","")..'/'..prev_entry.name:gsub(" ", "_")..".md".." >")
|
echo("<a class = 'go_prev' href="..HTTP_ROOT..'/'..toc.controller..'/'..enc.b64encode(prev_entry.path):gsub("=","")..'/'..prev_entry.name:gsub(" ", "_")..".md?show_toc=false".." >")
|
||||||
echo(prev_entry.name)
|
echo(prev_entry.name)
|
||||||
echo("</a>")
|
echo("</a>")
|
||||||
end
|
end
|
||||||
if next_entry then
|
if next_entry then
|
||||||
echo("<a class = 'go_next' href="..HTTP_ROOT..'/'..toc.controller..'/'..std.b64encode(next_entry.path):gsub("=","")..'/'..next_entry.name:gsub(" ", "_")..".md".." >")
|
echo("<a class = 'go_next' href="..HTTP_ROOT..'/'..toc.controller..'/'..enc.b64encode(next_entry.path):gsub("=","")..'/'..next_entry.name:gsub(" ", "_")..".md?show_toc=false".." >")
|
||||||
echo(next_entry.name)
|
echo(next_entry.name)
|
||||||
echo("</a>")
|
echo("</a>")
|
||||||
end
|
end
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
local title = io.read()
|
local title = io.read()
|
||||||
io.close()
|
io.close()
|
||||||
file = file:gsub(map.local_path, map.vfs_path)
|
file = file:gsub(map.local_path, map.vfs_path)
|
||||||
title = std.trim(std.trim(title, "#"), " ")
|
title = ulib.trim(ulib.trim(title, "#"), " ")
|
||||||
echo("<div>")
|
echo("<div>")
|
||||||
echo("<p class= 'result-header'><a href='"..HTTP_ROOT..'/'..controller..'/'..std.b64encode(file):gsub("=","")..'/'..title:gsub(" ", "_")..".md'>")
|
echo("<p class= 'result-header'><a href='"..HTTP_ROOT..'/'..controller..'/'..enc.b64encode(file):gsub("=","")..'/'..title:gsub(" ", "_")..".md'>")
|
||||||
echo(title)
|
echo(title)
|
||||||
echo("</a></p>")
|
echo("</a></p>")
|
||||||
for i,content in ipairs(arr) do
|
for i,content in ipairs(arr) do
|
||||||
|
@ -3,10 +3,20 @@ local tocdata = __main__:get("toc")
|
|||||||
local elinks = __main__:get("elinks")
|
local elinks = __main__:get("elinks")
|
||||||
local has_3d = __main__:get("has_3d")
|
local has_3d = __main__:get("has_3d")
|
||||||
local url = __main__:get("url")
|
local url = __main__:get("url")
|
||||||
|
local show_toc_css = ''
|
||||||
|
if REQUEST.show_toc and REQUEST.show_toc == "false" then
|
||||||
|
show_toc_css = 'style="display:none;"'
|
||||||
|
end
|
||||||
|
local book_width_css='style="max-width:80%;"'
|
||||||
|
if HEADER.mobile then
|
||||||
|
book_width_css='style="max-width:95%;"'
|
||||||
|
end
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
@ -15,7 +25,7 @@ local url = __main__:get("url")
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
href="<?=HTTP_ROOT?>/rst/katex/katex.min.css" />
|
href="<?=HTTP_ROOT?>/rst/katex/katex.min.css" />
|
||||||
<script src="<?=HTTP_ROOT?>/rst/gscripts/jquery-3.2.1.min.js"> </script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"> </script>
|
||||||
<?lua
|
<?lua
|
||||||
if has_3d then
|
if has_3d then
|
||||||
?>
|
?>
|
||||||
@ -57,14 +67,8 @@ local url = __main__:get("url")
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
type="text/css"
|
type="text/css"
|
||||||
href="<?=HTTP_ROOT?>/assets/github-markdown.css" />
|
href="<?=HTTP_ROOT?>/assets/github-markdown.css" />
|
||||||
<link
|
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
|
||||||
rel="stylesheet"
|
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin" />
|
||||||
type="text/css"
|
|
||||||
href="<?=HTTP_ROOT?>/rst/font-awesome.css" />
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
type="text/css"
|
|
||||||
href="<?=HTTP_ROOT?>/rst/ubuntu-regular.css" />
|
|
||||||
<title>
|
<title>
|
||||||
<?lua
|
<?lua
|
||||||
if tocdata then
|
if tocdata then
|
||||||
@ -77,28 +81,20 @@ local url = __main__:get("url")
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id = "top">
|
<div id = "top">
|
||||||
<div id = "navbar">
|
<div id = "navbar" <?=book_width_css?>>
|
||||||
<div class = "doc-name">
|
<?lua
|
||||||
|
if tocdata then
|
||||||
|
?>
|
||||||
|
<div class = "doc-toc-menu">
|
||||||
<?lua if tocdata then ?>
|
<?lua if tocdata then ?>
|
||||||
<a href ="<?=HTTP_ROOT..'/'..tocdata.controller..'/'?>">
|
<a href ="#" id="btn_toc">
|
||||||
<?=tocdata.data.name?>
|
<?=tocdata.data.name?>
|
||||||
</a>
|
</a>
|
||||||
<?lua end ?>
|
<?lua end ?>
|
||||||
</div>
|
</div>
|
||||||
<?lua
|
|
||||||
if elinks then
|
|
||||||
for k,v in ipairs(elinks) do
|
|
||||||
?>
|
|
||||||
<a class = "x-link" target="_blank" href ="<?=v.url?>">
|
|
||||||
<?=v.name?>
|
|
||||||
</a>
|
|
||||||
<?lua
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if tocdata then
|
|
||||||
?>
|
|
||||||
<form id = "search_form" action="<?=HTTP_ROOT..'/'..tocdata.controller..'/search/'?>" method="get" class="search-form">
|
<form id = "search_form" action="<?=HTTP_ROOT..'/'..tocdata.controller..'/search/'?>" method="get" class="search-form">
|
||||||
<input id = "search_box" name="q" type = "text" class = "search-box"></input>
|
<input id = "search_box" name="q" type = "text" class = "search-box"></input>
|
||||||
|
<input name="show_toc" type = "hidden" value="false"></input>
|
||||||
</form>
|
</form>
|
||||||
<div class= "search-icon"></div>
|
<div class= "search-icon"></div>
|
||||||
<?lua
|
<?lua
|
||||||
@ -107,11 +103,28 @@ local url = __main__:get("url")
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id = "cover">
|
<div id = "cover">
|
||||||
<div id = "book">
|
<div id = "book" <?=book_width_css?>>
|
||||||
<?lua
|
<?lua if tocdata then ?>
|
||||||
if tocdata then
|
<div id="doc_toc" class = "doc-toc" <?=show_toc_css?> >
|
||||||
?>
|
<div class = "doc-name doc-toc-header">
|
||||||
<div class = "doc-toc">
|
<a href ="<?=HTTP_ROOT..'/'..tocdata.controller..'/'?>">
|
||||||
|
<?=tocdata.data.name?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?lua
|
||||||
|
if elinks then
|
||||||
|
for k,v in ipairs(elinks) do
|
||||||
|
?>
|
||||||
|
<div class = "doc-toc-header">
|
||||||
|
<a class = "x-link" target="_blank" href ="<?=v.url?>">
|
||||||
|
<?=v.name?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?lua
|
||||||
|
end
|
||||||
|
end
|
||||||
|
?>
|
||||||
|
<div class = "doc-toc-header doc-toc-legend">Table of content</div>
|
||||||
<?lua
|
<?lua
|
||||||
if toc then
|
if toc then
|
||||||
toc:set("data", tocdata)
|
toc:set("data", tocdata)
|
||||||
@ -119,7 +132,7 @@ local url = __main__:get("url")
|
|||||||
end
|
end
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
<div class="doc-content markdown-body">
|
<div class="doc-content markdown-body" id="doc_content">
|
||||||
<?lua
|
<?lua
|
||||||
if __main__ then
|
if __main__ then
|
||||||
__main__:render()
|
__main__:render()
|
||||||
@ -141,10 +154,28 @@ local url = __main__:get("url")
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id = "bottom">
|
<div id = "bottom">
|
||||||
Powered by antd server, (c) 2019 - <?=os.date("*t").year?> Xuan Sang LE
|
Powered by antd server, (c) 2019 - <?=os.date("*t").year?> Dany LE
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
const toc_class_toggle = () => {
|
||||||
|
if(!$("#doc_toc").is(":hidden"))
|
||||||
|
{
|
||||||
|
$("#btn_toc").attr("class", "toc-active");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$("#btn_toc").removeClass("toc-active");
|
||||||
|
}
|
||||||
|
};
|
||||||
window.addEventListener('load', (event) => {
|
window.addEventListener('load', (event) => {
|
||||||
|
$("#btn_toc").click(function(){
|
||||||
|
$("#doc_toc").toggle();
|
||||||
|
toc_class_toggle();
|
||||||
|
});
|
||||||
|
$("#doc_content").click(function(){
|
||||||
|
$("#doc_toc").hide();
|
||||||
|
toc_class_toggle();
|
||||||
|
});
|
||||||
// tree view events
|
// tree view events
|
||||||
var toggler = document.getElementsByClassName("caret");
|
var toggler = document.getElementsByClassName("caret");
|
||||||
var i;
|
var i;
|
||||||
@ -191,7 +222,7 @@ local url = __main__:get("url")
|
|||||||
end
|
end
|
||||||
?>
|
?>
|
||||||
});
|
});
|
||||||
|
toc_class_toggle();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -20,7 +20,7 @@ gentree = function(data, controller, cpath)
|
|||||||
selected = "class = 'selected'"
|
selected = "class = 'selected'"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local markup = '<li '..selected..'>'..caret..'<a '..highlight..' href="'..HTTP_ROOT..'/'..controller..'/'..std.b64encode(data.path):gsub("=","")..'/'..data.name:gsub(" ", "_")..'.md">'..data.name.."</a>"
|
local markup = '<li '..selected..'>'..caret..'<a '..highlight..' href="'..HTTP_ROOT..'/'..controller..'/'..enc.b64encode(data.path):gsub("=","")..'/'..data.name:gsub(" ", "_")..'.md">'..data.name.."</a>"
|
||||||
if data.entries then
|
if data.entries then
|
||||||
markup = markup.."<ul class='nested "..active.."'>"
|
markup = markup.."<ul class='nested "..active.."'>"
|
||||||
for k,v in pairs(data.entries) do
|
for k,v in pairs(data.entries) do
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
-- should be something like this
|
-- should be something like this
|
||||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
||||||
-- some global variables
|
-- some global variables
|
||||||
DIR_SEP = "/"
|
package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
|
||||||
|
require("silk.api")
|
||||||
WWW_ROOT = __ROOT__.."/get"
|
WWW_ROOT = __ROOT__.."/get"
|
||||||
if HEADER.Host then
|
if HEADER.Host then
|
||||||
HTTP_ROOT= "https://"..HEADER.Host
|
HTTP_ROOT= "https://"..HEADER.Host
|
||||||
@ -11,16 +12,11 @@ else
|
|||||||
HTTP_ROOT = "https://get.iohub.dev"
|
HTTP_ROOT = "https://get.iohub.dev"
|
||||||
end
|
end
|
||||||
-- class path: path.to.class
|
-- class path: path.to.class
|
||||||
BASE_FRW = ""
|
CONTROLLER_ROOT = "get.controllers"
|
||||||
-- class path: path.to.class
|
MODEL_ROOT = "get.models"
|
||||||
CONTROLLER_ROOT = BASE_FRW.."get.controllers"
|
|
||||||
MODEL_ROOT = BASE_FRW.."get.models"
|
|
||||||
-- file path: path/to/file
|
-- file path: path/to/file
|
||||||
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
||||||
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
|
|
||||||
|
|
||||||
-- require needed library
|
|
||||||
require(BASE_FRW.."silk.api")
|
|
||||||
|
|
||||||
function NotfoundController:index(...)
|
function NotfoundController:index(...)
|
||||||
local args = {...}
|
local args = {...}
|
||||||
@ -32,8 +28,7 @@ function NotfoundController:index(...)
|
|||||||
local path = WWW_ROOT..DIR_SEP.."shs"..DIR_SEP..name..".sh"
|
local path = WWW_ROOT..DIR_SEP.."shs"..DIR_SEP..name..".sh"
|
||||||
|
|
||||||
if ulib.exists(path) then
|
if ulib.exists(path) then
|
||||||
std.header("text/plain")
|
std.sendFile(path)
|
||||||
std.f(path)
|
|
||||||
else
|
else
|
||||||
self:error("No script found: "..path)
|
self:error("No script found: "..path)
|
||||||
end
|
end
|
||||||
@ -44,8 +39,7 @@ end
|
|||||||
-- registry object store global variables
|
-- registry object store global variables
|
||||||
local REGISTRY = {}
|
local REGISTRY = {}
|
||||||
-- set logging level
|
-- set logging level
|
||||||
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = false, DEBUG = false}}
|
REGISTRY.logger = Logger:new{ level = Logger.INFO}
|
||||||
|
|
||||||
REGISTRY.layout = 'default'
|
REGISTRY.layout = 'default'
|
||||||
REGISTRY.fileaccess = false
|
REGISTRY.fileaccess = false
|
||||||
|
|
||||||
|
@ -2,23 +2,32 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
V_ANTD="1.0.6b"
|
||||||
|
V_LUA="0.5.2b"
|
||||||
|
#V_WTERM="1.0.0b"
|
||||||
|
V_TUNNEL="0.1.3b"
|
||||||
|
#V_CGI="1.0.0b"
|
||||||
|
V_PUBS="0.1.2a"
|
||||||
|
V_ANTOS=$(wget -qO- https://github.com/lxsang/antos/raw/1.2.1/release/latest)
|
||||||
|
|
||||||
mkdir -p /opt/www/htdocs
|
mkdir -p /opt/www/htdocs
|
||||||
|
|
||||||
if [ "$1" = "full" ]; then
|
if [ "$1" = "full" ]; then
|
||||||
# base server
|
# base server
|
||||||
wget -O- https://get.bitdojo.dev/antd | bash -s "1.0.6b"
|
wget -O- https://get.iohub.dev/antd | bash -s "$V_ANTD"
|
||||||
# base plugin
|
# base plugin
|
||||||
wget -O- https://get.bitdojo.dev/antd_plugin | bash -s "lua-0.5.2b"
|
wget -O- https://get.iohub.dev/antd_plugin | bash -s "lua-$V_LUA"
|
||||||
wget -O- https://get.bitdojo.dev/antd_plugin | bash -s "wterm-1.0.0b"
|
#wget -O- https://get.iohub.dev/antd_plugin | bash -s "wterm-$V_WTERM"
|
||||||
wget -O- https://get.bitdojo.dev/antd_plugin | bash -s "tunnel-0.1.0b"
|
wget -O- https://get.iohub.dev/antd_plugin | bash -s "tunnel-$V_TUNNEL"
|
||||||
|
#wget -O- https://get.iohub.dev/antd_plugin | bash -s "cgi-$V_CGI"
|
||||||
# install antos
|
# install antos
|
||||||
|
|
||||||
[ -d /tmp/apub ] && rm -r /tmp/apub
|
[ -d /tmp/apub ] && rm -r /tmp/apub
|
||||||
mkdir -p /tmp/apub
|
mkdir -p /tmp/apub
|
||||||
cd /tmp/apub
|
cd /tmp/apub
|
||||||
wget --no-check-certificate "https://github.com/lxsang/antd-tunnel-publishers/raw/master/dist/antd-publishers-0.1.0a.tar.gz"
|
wget --no-check-certificate "https://github.com/lxsang/antd-tunnel-publishers/raw/master/dist/antd-publishers-$V_PUBS.tar.gz"
|
||||||
tar xvzf antd-publishers-0.1.0a.tar.gz
|
tar xvzf antd-publishers-$V_PUBS.tar.gz
|
||||||
cd antd-publishers-0.1.0a
|
cd antd-publishers-$V_PUBS
|
||||||
./configure --prefix=/opt/www && make && make install
|
./configure --prefix=/opt/www && make && make install
|
||||||
|
|
||||||
cd /opt/www
|
cd /opt/www
|
||||||
@ -95,11 +104,11 @@ cd /opt/www/htdocs
|
|||||||
wget --no-check-certificate "https://github.com/lxsang/antd-web-apps/raw/master/dist/antd_web_apps.tar.gz"
|
wget --no-check-certificate "https://github.com/lxsang/antd-web-apps/raw/master/dist/antd_web_apps.tar.gz"
|
||||||
tar xvzf antd_web_apps.tar.gz
|
tar xvzf antd_web_apps.tar.gz
|
||||||
rm antd_web_apps.tar.gz
|
rm antd_web_apps.tar.gz
|
||||||
rm -r ci blog doc index.ls info talk get
|
rm -r blog doc index.ls info talk get
|
||||||
|
|
||||||
cd /opt/www/htdocs/os
|
cd /opt/www/htdocs/os
|
||||||
wget --no-check-certificate "https://github.com/lxsang/antos/raw/master/release/antos-1.1.2.tar.gz"
|
wget --no-check-certificate "https://github.com/lxsang/antos/raw/1.2.1/release/antos-$V_ANTOS.tar.gz"
|
||||||
tar xvzf antos-1.1.2.tar.gz
|
tar xvzf antos-$V_ANTOS.tar.gz
|
||||||
rm antos-1.1.2.tar.gz
|
rm antos-$V_ANTOS.tar.gz
|
||||||
|
|
||||||
echo "Install done..."
|
echo "Install done..."
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
copyfiles = katex font-awesome.css fonts images sendmail.lua subscribe.lua subscribe.html main.js ubuntu-regular.css mainsite.css sendto.html hermit-light.css hljs
|
copyfiles = katex font-awesome.css fonts images subscribe.html mainsite.css sendto.html hermit-light.css hljs
|
||||||
|
|
||||||
main:
|
main:
|
||||||
- mkdir $(BUILDDIR)
|
- mkdir $(BUILDDIR)
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
<div data-height="5"></div>
|
<div data-height="5"></div>
|
||||||
<afx-hbox data-height="20" class="inputbox">
|
<afx-hbox data-height="20" class="inputbox">
|
||||||
<div data-width="45" class="label">From:</div>
|
<div data-width="45" class="label">Email:</div>
|
||||||
<input data-class="data" type="text" name="email" />
|
<input data-class="data" type="text" name="email" />
|
||||||
</afx-hbox>
|
</afx-hbox>
|
||||||
<div data-height="5"></div>
|
<div data-height="5"></div>
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
/*! Generated by Font Squirrel (https://www.fontsquirrel.com) on August 6, 2017 */
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Ubuntu';
|
|
||||||
src: url('resources/themes/system/fonts//ubuntu-regular-webfont.woff2') format('woff2'),
|
|
||||||
url('resources/themes/system/fonts//ubuntu-regular-webfont.woff') format('woff');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Ubuntu';
|
|
||||||
src: url('resources/themes/system/fonts//ubuntu-bold-webfont.woff2') format('woff2'),
|
|
||||||
url('resources/themes/system/fonts//ubuntu-bold-webfont.woff') format('woff');
|
|
||||||
font-weight: bold;
|
|
||||||
font-style: normal;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Ubuntu';
|
|
||||||
src: url('resources/themes/system/fonts//ubuntu-bolditalic-webfont.woff2') format('woff2'),
|
|
||||||
url('resources/themes/system/fonts//ubuntu-bolditalic-webfont.woff') format('woff');
|
|
||||||
font-weight: bold;
|
|
||||||
font-style: italic;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Ubuntu';
|
|
||||||
src: url('resources/themes/system/fonts//ubuntu-italic-webfont.woff2') format('woff2'),
|
|
||||||
url('resources/themes/system/fonts//ubuntu-italic-webfont.woff') format('woff');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: italic;
|
|
||||||
|
|
||||||
}
|
|
14
index.lua
Normal file
14
index.lua
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
|
||||||
|
require("silk.api")
|
||||||
|
|
||||||
|
local args = {}
|
||||||
|
local argv = {}
|
||||||
|
local fn, e = loadscript(__ROOT__.."/layout.ls", args)
|
||||||
|
if fn then
|
||||||
|
local r,o = pcall(fn, table.unpack(argv))
|
||||||
|
if not r then
|
||||||
|
std.error(500, o)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
std.error(500, e)
|
||||||
|
end
|
@ -7,8 +7,13 @@ BaseController:subclass(
|
|||||||
)
|
)
|
||||||
|
|
||||||
local sectionsByCid = function(db, id)
|
local sectionsByCid = function(db, id)
|
||||||
local cond = {exp = { ["and"] = {{["="] = {cid = id}}, {["= "] = {publish = 1}} }}, order = {start = "DESC"}}
|
local data, a = db:find({
|
||||||
local data, a = db:find(cond)
|
where = {
|
||||||
|
cid = id,
|
||||||
|
publish = 1
|
||||||
|
},
|
||||||
|
order = {"start$desc"}
|
||||||
|
})
|
||||||
return data, a
|
return data, a
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -16,8 +21,12 @@ function IndexController:index(...)
|
|||||||
local args = {...}
|
local args = {...}
|
||||||
-- now read all the data
|
-- now read all the data
|
||||||
-- get all root sections as the toc
|
-- get all root sections as the toc
|
||||||
local cond = {exp = {["="] = {pid = 0}}, order = {name = "ASC"}}
|
local data, a = self.category:find({
|
||||||
local data, a = self.category:find(cond)
|
where = {
|
||||||
|
pid = 0
|
||||||
|
},
|
||||||
|
order = {"name$asc"}
|
||||||
|
})
|
||||||
local toc = {}
|
local toc = {}
|
||||||
if not data then
|
if not data then
|
||||||
return self:error("Cannot query the ToC")
|
return self:error("Cannot query the ToC")
|
||||||
@ -26,8 +35,12 @@ function IndexController:index(...)
|
|||||||
for key, cat in pairs(data) do
|
for key, cat in pairs(data) do
|
||||||
cat.name = cat.name:gsub("^%d+%.", "")
|
cat.name = cat.name:gsub("^%d+%.", "")
|
||||||
table.insert(toc, {cat.name, cat.id})
|
table.insert(toc, {cat.name, cat.id})
|
||||||
cond = {exp = {["="] = {pid = cat.id}}, order = {name = "ASC"}}
|
local children, b = self.category:find({
|
||||||
local children, b = self.category:find(cond)
|
where = {
|
||||||
|
pid = cat.id
|
||||||
|
},
|
||||||
|
order = {"name$asc"}
|
||||||
|
})
|
||||||
if children and #children > 0 then
|
if children and #children > 0 then
|
||||||
for k, v in pairs(children) do
|
for k, v in pairs(children) do
|
||||||
v.sections = sectionsByCid(self.sections, v.id)
|
v.sections = sectionsByCid(self.sections, v.id)
|
||||||
@ -53,12 +66,11 @@ end
|
|||||||
|
|
||||||
function IndexController:pdf(...)
|
function IndexController:pdf(...)
|
||||||
local tmp_file = WWW_ROOT.."/cv_exported.pdf"
|
local tmp_file = WWW_ROOT.."/cv_exported.pdf"
|
||||||
local cmd = "wkhtmltopdf "..HTTP_ROOT.."/"..self.registry.user.."/notoc "..tmp_file
|
local cmd = "wkhtmltopdf "..HTTP_ROOT.."/"..self.registry.user.."/index/notoc "..tmp_file
|
||||||
|
print(cmd)
|
||||||
local r = os.execute(cmd)
|
local r = os.execute(cmd)
|
||||||
if r then
|
if r then
|
||||||
local mime = std.mimeOf(tmp_file)
|
std.sendFile(tmp_file)
|
||||||
std.header(mime)
|
|
||||||
std.f(tmp_file)
|
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
return self:error("Sorry.Problem generate PDF file")
|
return self:error("Sorry.Problem generate PDF file")
|
||||||
|
@ -15,3 +15,25 @@ function UserController:index(...)
|
|||||||
self.template:set("data", data[1])
|
self.template:set("data", data[1])
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function UserController:photo(...)
|
||||||
|
local data = self.user:findAll()
|
||||||
|
if not data or not data[1] then
|
||||||
|
self:error("Cannot fetch user info")
|
||||||
|
end
|
||||||
|
if(not data[1] or data[1].photo == "") then
|
||||||
|
self:error("User photo is not available")
|
||||||
|
end
|
||||||
|
local prefix = data[1].photo:match("%a+://")
|
||||||
|
local suffix = data[1].photo:gsub(prefix,"")
|
||||||
|
local path = string.format("/home/%s/", self.registry.user)..suffix
|
||||||
|
LOG_DEBUG("Photo path: %s", path)
|
||||||
|
if ulib.exists(path) then
|
||||||
|
local mime = std.mimeOf(path)
|
||||||
|
std.sendFile(path)
|
||||||
|
else
|
||||||
|
self:error("Asset file not found or access forbidden: "..path)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
@ -3,32 +3,34 @@
|
|||||||
-- should be something like this
|
-- should be something like this
|
||||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
||||||
-- some global variables
|
-- some global variables
|
||||||
DIR_SEP = "/"
|
package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
|
||||||
|
require("silk.api")
|
||||||
WWW_ROOT = __ROOT__.."/info"
|
WWW_ROOT = __ROOT__.."/info"
|
||||||
if HEADER.Host then
|
if HEADER.Host then
|
||||||
HTTP_ROOT= "https://"..HEADER.Host
|
HTTP_ROOT= "https://"..HEADER.Host
|
||||||
else
|
else
|
||||||
HTTP_ROOT = "https://info.lxsang.me"
|
HTTP_ROOT = "https://info.iohub.dev"
|
||||||
end
|
end
|
||||||
-- class path: path.to.class
|
|
||||||
BASE_FRW = ""
|
DB_LOC="/opt/www/databases"
|
||||||
-- class path: path.to.class
|
|
||||||
CONTROLLER_ROOT = BASE_FRW.."info.controllers"
|
CONTROLLER_ROOT = "info.controllers"
|
||||||
MODEL_ROOT = BASE_FRW.."info.models"
|
MODEL_ROOT = "info.models"
|
||||||
-- file path: path/to/file
|
-- file path: path/to/file
|
||||||
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
||||||
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
|
|
||||||
|
|
||||||
-- require needed library
|
|
||||||
require(BASE_FRW.."silk.api")
|
|
||||||
|
|
||||||
-- registry object store global variables
|
-- registry object store global variables
|
||||||
local REGISTRY = {}
|
local REGISTRY = {}
|
||||||
-- set logging level
|
-- set logging level
|
||||||
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = true, DEBUG = false}}
|
REGISTRY.logger = Logger:new{ level = Logger.INFO}
|
||||||
REGISTRY.users_allowed = { phuong = true, mrsang = true }
|
REGISTRY.users_allowed = { phuong = true, mrsang = true, dany = true }
|
||||||
|
|
||||||
REGISTRY.user = "mrsang"
|
REGISTRY.user = "mrsang"
|
||||||
REGISTRY.db = DBHelper:new{db=REGISTRY.user}
|
|
||||||
|
REGISTRY.dbfile = DB_LOC.."/"..REGISTRY.user..".db"
|
||||||
|
|
||||||
|
REGISTRY.db = DBModel:new{db=REGISTRY.dbfile}
|
||||||
REGISTRY.layout = 'default'
|
REGISTRY.layout = 'default'
|
||||||
REGISTRY.fileaccess = true
|
REGISTRY.fileaccess = true
|
||||||
|
|
||||||
@ -67,10 +69,12 @@ function NotfoundController:index(...)
|
|||||||
self:error("404: Controller "..args[1].." not found : "..args[2])
|
self:error("404: Controller "..args[1].." not found : "..args[2])
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
REQUEST.r = "index/"..std.trim(REQUEST.r:gsub(user, ""), "/")
|
LOG_DEBUG("Request: %s", REQUEST.r)
|
||||||
|
REQUEST.r = ulib.trim(REQUEST.r:gsub(user, ""), "/")
|
||||||
if REGISTRY.db then REGISTRY.db:close() end
|
if REGISTRY.db then REGISTRY.db:close() end
|
||||||
REGISTRY.user = user
|
REGISTRY.user = user
|
||||||
REGISTRY.db = DBHelper:new{db=REGISTRY.user}
|
REGISTRY.dbfile = DB_LOC.."/"..REGISTRY.user..".db"
|
||||||
|
REGISTRY.db = DBModel:new{db=REGISTRY.dbfile}
|
||||||
REGISTRY.db:open()
|
REGISTRY.db:open()
|
||||||
router:delegate()
|
router:delegate()
|
||||||
end
|
end
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.layout div.container{
|
.layout div.container{
|
||||||
display: none;
|
display: block;
|
||||||
}
|
}
|
||||||
.layout div.container_active{
|
.layout div.container_active{
|
||||||
display: block;
|
display: block;
|
||||||
@ -169,4 +169,16 @@ hr{
|
|||||||
border-top: 1px solid #878887;
|
border-top: 1px solid #878887;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
img {max-width:100%}
|
img {max-width:100%}
|
||||||
|
|
||||||
|
.header_container {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.header_container img {
|
||||||
|
float: left;
|
||||||
|
max-width: 150px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
@ -29,8 +29,8 @@
|
|||||||
<span><?=entry.subtitle?></span>
|
<span><?=entry.subtitle?></span>
|
||||||
<span class="date">
|
<span class="date">
|
||||||
<?lua
|
<?lua
|
||||||
if entry["start"]:match("^20%d.*") and entry['end']:match("^20%d.*") then
|
if tostring(entry["start"]):match("^20%d.*") and tostring(entry['end']):match("^20%d.*") then
|
||||||
echo(entry.start.."-"..entry['end'])
|
echo("%d-%d",entry["start"],entry['end'])
|
||||||
end
|
end
|
||||||
?>
|
?>
|
||||||
</span>
|
</span>
|
||||||
@ -71,8 +71,8 @@
|
|||||||
<span><?=entry.subtitle?></span>
|
<span><?=entry.subtitle?></span>
|
||||||
<span class="date">
|
<span class="date">
|
||||||
<?lua
|
<?lua
|
||||||
if entry["start"]:match("^20%d.*") and entry['end']:match("^20%d.*") then
|
if tostring(entry["start"]):match("^20%d.*") and tostring(entry['end']):match("^20%d.*") then
|
||||||
echo(entry.start.."-"..entry['end'])
|
echo("%d-%d",entry["start"],entry['end'])
|
||||||
end
|
end
|
||||||
?>
|
?>
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script type="text/javascript" src="<?=HTTP_ROOT?>/rst/gscripts/showdown.min.js"></script>
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/style.css" />
|
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/style.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/rst/font-awesome.css" />
|
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
|
||||||
|
|
||||||
<?lua
|
<?lua
|
||||||
if not toc then
|
if not toc then
|
||||||
?>
|
?>
|
||||||
<link rel="stylesheet" type="text/css" href="<?=HTTP_ROOT?>/rst/ubuntu-regular.css" />
|
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin" />
|
||||||
<?lua
|
<?lua
|
||||||
end
|
end
|
||||||
?>
|
?>
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
local active = "toc_active"
|
local active = "toc_active"
|
||||||
for k, v in pairs(data) do
|
for k, v in pairs(data) do
|
||||||
?>
|
?>
|
||||||
<li class="<?=active?>"><a href=<?='"#toc'..v[2]..'"'?> onclick='switchTab("toc<?=v[2]?>", this)' ><?=v[1]?></a></li>
|
<!--onclick='switchTab("toc<?=v[2]?>", this)'-->
|
||||||
|
<li class="<?=active?>"><a href=<?='"#toc'..v[2]..'"'?> ><?=v[1]?></a></li>
|
||||||
<?lua
|
<?lua
|
||||||
active = ''
|
active = ''
|
||||||
end
|
end
|
||||||
|
@ -1,27 +1,36 @@
|
|||||||
<h1>
|
<div class="header_container">
|
||||||
<span class="name"><?=data.fullname?></span>
|
<?lua if data.photo and data.photo ~= "" and data.user ~= "mrsang" then ?>
|
||||||
<span class="cv">Curriculum Vitae</span>
|
<img src="<?=HTTP_ROOT?>/<?=data.user?>/user/photo"></img>
|
||||||
</h1>
|
<?lua end ?>
|
||||||
<p class="coordination">
|
<h1>
|
||||||
<span class="fa fa-home"></span><?=data.address?></p>
|
<span class="name"><?=data.fullname?></span>
|
||||||
<p class="coordination">
|
<span class="cv">Curriculum Vitae</span>
|
||||||
<span class="fa fa-phone"></span>
|
</h1>
|
||||||
<span class="text"><?=data.Phone?></span>
|
<p class="coordination">
|
||||||
<span class="fa fa-envelope-o"></span>
|
<span class="fa fa-home"></span><?=data.address?></p>
|
||||||
<span class="text"><?=data.email?></span>
|
<p class="coordination">
|
||||||
<span class="fa fa-globe"></span>
|
<span class="fa fa-phone"></span>
|
||||||
<span class="text"><a href ="<?=data.url?>"><?=data.url?></a></span>
|
<span class="text"><?=data.Phone?></span>
|
||||||
<?lua
|
<span class="fa fa-envelope-o"></span>
|
||||||
if not preview then
|
<span class="text"><?=data.email?></span>
|
||||||
?>
|
<br/>
|
||||||
<span class="fa fa-file-pdf-o"></span>
|
<span class="fa fa-globe"></span>
|
||||||
<span class="text"><a href ="<?=HTTP_ROOT?>/<?=data.user?>/pdf" target="_blank">Download</a></span>
|
<span class="text"><a href ="<?=data.url?>"><?=data.url?></a></span>
|
||||||
<?lua
|
<?lua
|
||||||
end
|
if not preview then
|
||||||
?>
|
?>
|
||||||
</p>
|
<span class="fa fa-file-pdf-o"></span>
|
||||||
<p class="shortbio">
|
<span class="text"><a href ="<?=HTTP_ROOT?>/<?=data.user?>/index/pdf" target="_blank">Download</a></span>
|
||||||
<span class="fa fa-quote-left"></span>
|
<?lua
|
||||||
<span><?=data.shortbiblio?></span>
|
end
|
||||||
<span class="fa fa-quote-right"></span>
|
?>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="shortbio">
|
||||||
|
<span class="fa fa-quote-left"></span>
|
||||||
|
<span><?=data.shortbiblio?></span>
|
||||||
|
<span class="fa fa-quote-right"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?lua if not HEADER.mobile and data.user == "mrsang" then ?>
|
||||||
|
<!--iframe width="770" height="330" src="https://mars.nasa.gov/layout/embed/send-your-name/future/certificate/?cn=792789419260" frameborder="0"></iframe-->
|
||||||
|
<?lua end ?>
|
@ -1,7 +1,5 @@
|
|||||||
<?lua
|
<?lua
|
||||||
std.html()
|
std.html()
|
||||||
require("sqlite")
|
|
||||||
local user = "mrsang"
|
|
||||||
local die = function(m)
|
local die = function(m)
|
||||||
echo(m)
|
echo(m)
|
||||||
debug.traceback=nil
|
debug.traceback=nil
|
||||||
@ -9,9 +7,10 @@
|
|||||||
end
|
end
|
||||||
local mobilecls = ""
|
local mobilecls = ""
|
||||||
if HEADER.mobile then mobilecls = "mobile" end
|
if HEADER.mobile then mobilecls = "mobile" end
|
||||||
local db = require("os.libs.dbmodel").get(user,"user",nil)
|
local db = DBModel:new{db="/home/dany/databases/mrsang.db"}
|
||||||
if db == nil then die("cannot get db data") end
|
if db == nil then die("cannot get db data") end
|
||||||
local data, a = db:getAll()
|
db:open()
|
||||||
|
local data, a = db:getAll("user")
|
||||||
db:close()
|
db:close()
|
||||||
if data == nil or data[1] == nil then die("Cannot fetch user info") end
|
if data == nil or data[1] == nil then die("Cannot fetch user info") end
|
||||||
data = data[1]
|
data = data[1]
|
||||||
@ -19,11 +18,11 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Hi, I'm Xuan Sang LE</title>
|
<title>Hi, I'm <?=data.fullname?></title>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" type="text/css" href="grs/ubuntu-regular.css" />
|
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin">
|
||||||
<link rel="stylesheet" type="text/css" href="grs/font-awesome.css" />
|
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="grs/mainsite.css" />
|
<link rel="stylesheet" type="text/css" href="grs/mainsite.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -39,7 +38,7 @@
|
|||||||
<div id = "container" class="<?=mobilecls?>" >
|
<div id = "container" class="<?=mobilecls?>" >
|
||||||
<img src = "grs/images/mrsang.png" ></img>
|
<img src = "grs/images/mrsang.png" ></img>
|
||||||
<div id = "vcard">
|
<div id = "vcard">
|
||||||
<p class = "greeting">Hi, I'm <b>Xuan Sang LE</b></p>
|
<p class = "greeting">Hi, I'm <b><?=data.fullname?></b></p>
|
||||||
<p class = "dedicate">
|
<p class = "dedicate">
|
||||||
<span class="fa fa-quote-left"></span>
|
<span class="fa fa-quote-left"></span>
|
||||||
<span>
|
<span>
|
||||||
@ -47,12 +46,13 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="fa fa-quote-right"></span>
|
<span class="fa fa-quote-right"></span>
|
||||||
</p>
|
</p>
|
||||||
<a href="https://info.lxsang.me" class ="about">Find out more about me</a>
|
<a href="https://blog.iohub.dev" class ="about">Read my blog</a>
|
||||||
|
<a href="https://info.iohub.dev" class ="about">More about me</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id = "bottom">
|
<div id = "bottom">
|
||||||
Powered by antd server, (c) 2017 - 2018 Xuan Sang LE
|
Powered by antd server, (c) 2017 - 2022 Dany LE
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
59
mimes.json
59
mimes.json
@ -1,5 +1,48 @@
|
|||||||
{
|
{
|
||||||
"odt": { "mime": "application/vnd.oasis.opendocument.text", "binary": true },
|
"odt": {
|
||||||
|
"mime": "application/vnd.oasis.opendocument.text",
|
||||||
|
"binary": true
|
||||||
|
},
|
||||||
|
"docx": {
|
||||||
|
"mime": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
"binary": true
|
||||||
|
},
|
||||||
|
"doc": {
|
||||||
|
"mime": "application/msword",
|
||||||
|
"binary": true
|
||||||
|
},
|
||||||
|
"xls": {
|
||||||
|
"mime": "application/vnd.ms-excel",
|
||||||
|
"binary": true
|
||||||
|
},
|
||||||
|
"xlsx": {
|
||||||
|
"mime": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
"binary": true
|
||||||
|
},
|
||||||
|
"ppt": {
|
||||||
|
"mime": "application/vnd.ms-powerpoint",
|
||||||
|
"binary": true
|
||||||
|
},
|
||||||
|
"pptx": {
|
||||||
|
"mime": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
|
"binary": true
|
||||||
|
},
|
||||||
|
"epub": {
|
||||||
|
"mime": "application/epub+zip",
|
||||||
|
"binary": true
|
||||||
|
},
|
||||||
|
"csv": {
|
||||||
|
"mime": "text/csv",
|
||||||
|
"binary": false
|
||||||
|
},
|
||||||
|
"ods": {
|
||||||
|
"mime": "application/vnd.oasis.opendocument.spreadsheet",
|
||||||
|
"binary": true
|
||||||
|
},
|
||||||
|
"odp": {
|
||||||
|
"mime": "application/vnd.oasis.opendocument.presentation",
|
||||||
|
"binary": true
|
||||||
|
},
|
||||||
"viz": {
|
"viz": {
|
||||||
"mime": "text/vnd.graphviz",
|
"mime": "text/vnd.graphviz",
|
||||||
"binary": false
|
"binary": false
|
||||||
@ -8,7 +51,7 @@
|
|||||||
"mime": "text/x-python",
|
"mime": "text/x-python",
|
||||||
"binary": false
|
"binary": false
|
||||||
},
|
},
|
||||||
"coffee":{
|
"coffee": {
|
||||||
"mime": "text/vnd.coffeescript",
|
"mime": "text/vnd.coffeescript",
|
||||||
"binary": false
|
"binary": false
|
||||||
},
|
},
|
||||||
@ -23,5 +66,17 @@
|
|||||||
"glb": {
|
"glb": {
|
||||||
"mime": "model/gltf-binary",
|
"mime": "model/gltf-binary",
|
||||||
"binary": true
|
"binary": true
|
||||||
|
},
|
||||||
|
"ts":{
|
||||||
|
"mime": "text/x.typescript",
|
||||||
|
"binary": false
|
||||||
|
},
|
||||||
|
"map":{
|
||||||
|
"mime": "application/json",
|
||||||
|
"binary": false
|
||||||
|
},
|
||||||
|
"sh":{
|
||||||
|
"mime": "text/plain",
|
||||||
|
"binary": false
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +0,0 @@
|
|||||||
copyfiles = controllers libs router.lua
|
|
||||||
|
|
||||||
main:
|
|
||||||
- mkdir -p $(BUILDDIR)
|
|
||||||
cp -rfv $(copyfiles) $(BUILDDIR)
|
|
||||||
- cd $(BUILDDIR) && ln -s ../grs ./rst
|
|
@ -1,33 +0,0 @@
|
|||||||
BaseController:subclass(
|
|
||||||
"IndexController",
|
|
||||||
{
|
|
||||||
registry = {},
|
|
||||||
models = {}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function IndexController:actionnotfound(...)
|
|
||||||
return self:index(table.unpack({...}))
|
|
||||||
end
|
|
||||||
|
|
||||||
function IndexController:index(...)
|
|
||||||
html()
|
|
||||||
std.f(WWW_ROOT..DIR_SEP.."index.html")
|
|
||||||
end
|
|
||||||
|
|
||||||
function IndexController:doc(...)
|
|
||||||
local api = {
|
|
||||||
author = "Xuan Sang LE",
|
|
||||||
email = "xsang.le@gmail.com",
|
|
||||||
api_name = "AntOS API",
|
|
||||||
version = "0.2.4 a",
|
|
||||||
documents = {
|
|
||||||
vfs = HTTP_ROOT.."/VFS",
|
|
||||||
vdb = HTTP_ROOT.."/VDB",
|
|
||||||
user = HTTP_ROOT.."/user",
|
|
||||||
system = HTTP_ROOT.."/system"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result(api)
|
|
||||||
return false
|
|
||||||
end
|
|
@ -1,200 +0,0 @@
|
|||||||
BaseController:subclass(
|
|
||||||
"SystemController",
|
|
||||||
{
|
|
||||||
registry = {},
|
|
||||||
models = {}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function SystemController:actionnotfound(...)
|
|
||||||
return self:index(table.unpack({...}))
|
|
||||||
end
|
|
||||||
|
|
||||||
function SystemController:index(...)
|
|
||||||
local api = {
|
|
||||||
description = "This api handle system operations",
|
|
||||||
actions = {
|
|
||||||
["/packages"] = "Handle all operation relate to package: list, install, cache, uninstall",
|
|
||||||
["/settings"] = "Save user setting",
|
|
||||||
["/application"] = "Call a specific server side application api",
|
|
||||||
["/apigateway"] = "Gateway for executing custom server side code"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result(api)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function SystemController:packages(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
local packages = require("packages")
|
|
||||||
packages.init(rq.args.paths)
|
|
||||||
if rq ~= nil then
|
|
||||||
-- check user command here
|
|
||||||
if (rq.command == "install") then
|
|
||||||
packages.install(rq.args)
|
|
||||||
elseif rq.command == "cache" then
|
|
||||||
packages.cache(rq.args)
|
|
||||||
elseif rq.command == "list" then
|
|
||||||
packages.list(rq.args.paths)
|
|
||||||
elseif rq.command == "uninstall" then
|
|
||||||
packages.uninstall(rq.args.path)
|
|
||||||
else
|
|
||||||
fail("Uknown packages command")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Uknown request")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function SystemController:settings(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local user = SESSION.user
|
|
||||||
if user then
|
|
||||||
local ospath = require("vfs").ospath("home:///", user)
|
|
||||||
if REQUEST and REQUEST.json then
|
|
||||||
local file_path = ospath .. "/" .. ".settings.json"
|
|
||||||
local f = io.open(file_path, "w")
|
|
||||||
if f then
|
|
||||||
f:write(REQUEST.json)
|
|
||||||
f:close()
|
|
||||||
os.execute("chmod o-r "..file_path)
|
|
||||||
result(true)
|
|
||||||
else
|
|
||||||
fail("Cannot save setting")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("No setting founds")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("User not found")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function SystemController:application(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local rq = nil
|
|
||||||
if REQUEST.json ~= nil then
|
|
||||||
rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
else
|
|
||||||
rq = REQUEST
|
|
||||||
end
|
|
||||||
|
|
||||||
if rq.path ~= nil then
|
|
||||||
local pkg = require("vfs").ospath(rq.path)
|
|
||||||
if pkg == nil then
|
|
||||||
pkg = WWW_ROOT .. "/packages/" .. rq.path
|
|
||||||
--die("unkown request path:"..rq.path)
|
|
||||||
end
|
|
||||||
pkg = pkg .. "/api.lua"
|
|
||||||
if ulib.exists(pkg) then
|
|
||||||
dofile(pkg).exec(rq.method, rq.arguments)
|
|
||||||
else
|
|
||||||
fail("Uknown application handler: " .. pkg)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Uknown request")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function SystemController:apigateway(...)
|
|
||||||
local use_ws = false
|
|
||||||
if REQUEST and REQUEST.ws == "1" then
|
|
||||||
-- override the global echo command
|
|
||||||
echo = std.ws.swrite
|
|
||||||
use_ws = true
|
|
||||||
--else
|
|
||||||
-- std.json()
|
|
||||||
end
|
|
||||||
local exec_with_user_priv = function(data)
|
|
||||||
local uid = ulib.uid(SESSION.user)
|
|
||||||
if not ulib.setgid(uid.gid) or not ulib.setuid(uid.id) then
|
|
||||||
echo("Cannot set permission to execute the code")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local r, e
|
|
||||||
e = "{'error': 'Unknow function'}"
|
|
||||||
if data.code then
|
|
||||||
r, e = load(data.code)
|
|
||||||
if r then
|
|
||||||
local status, result = pcall(r)
|
|
||||||
if (status) then
|
|
||||||
echo(JSON.encode(result))
|
|
||||||
else
|
|
||||||
echo(result)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
echo(e)
|
|
||||||
end
|
|
||||||
elseif data.path then
|
|
||||||
local ospath = require("vfs").ospath(data.path)
|
|
||||||
r, e = loadfile(ospath)
|
|
||||||
if r then
|
|
||||||
local status, result = pcall(r, data.parameters)
|
|
||||||
if (status) then
|
|
||||||
echo(JSON.encode(result))
|
|
||||||
else
|
|
||||||
echo(result)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
echo(e)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
echo(e)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if (is_auth()) then
|
|
||||||
local pid = ulib.fork()
|
|
||||||
if (pid == -1) then
|
|
||||||
echo("{'error':'Cannot create process'}")
|
|
||||||
elseif pid > 0 then -- parent
|
|
||||||
-- wait for the child exit or websocket exit
|
|
||||||
ulib.waitpid(pid, 0)
|
|
||||||
print("Parent exit")
|
|
||||||
else -- child
|
|
||||||
if use_ws then
|
|
||||||
if std.ws.enable() then
|
|
||||||
-- read header
|
|
||||||
local header = std.ws.header()
|
|
||||||
if header then
|
|
||||||
if header.mask == 0 then
|
|
||||||
print("Data is not masked")
|
|
||||||
std.ws.close(1012)
|
|
||||||
elseif header.opcode == std.ws.CLOSE then
|
|
||||||
print("Connection closed")
|
|
||||||
std.ws.close(1000)
|
|
||||||
elseif header.opcode == std.ws.TEXT then
|
|
||||||
-- read the file
|
|
||||||
local data = std.ws.read(header)
|
|
||||||
if data then
|
|
||||||
data = (JSON.decodeString(data))
|
|
||||||
exec_with_user_priv(data)
|
|
||||||
std.ws.close(1011)
|
|
||||||
else
|
|
||||||
echo("Error: Invalid request")
|
|
||||||
std.ws.close(1011)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
std.ws.close(1011)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
print("Web socket is not available.")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if REQUEST.json then
|
|
||||||
data = JSON.decodeString(REQUEST.json)
|
|
||||||
--std.json()
|
|
||||||
exec_with_user_priv(data)
|
|
||||||
else
|
|
||||||
fail("Unkown request")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
print("Child exit")
|
|
||||||
ulib.kill(-1)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
echo('{"error":"User unauthorized. Please login"}')
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,96 +0,0 @@
|
|||||||
BaseController:subclass(
|
|
||||||
"UserController",
|
|
||||||
{
|
|
||||||
registry = {},
|
|
||||||
models = {}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function UserController:actionnotfound(...)
|
|
||||||
return self:index(table.unpack({...}))
|
|
||||||
end
|
|
||||||
|
|
||||||
function UserController:index(...)
|
|
||||||
local api = {
|
|
||||||
description = "This api handle the user authentification",
|
|
||||||
actions = {
|
|
||||||
["/auth"] = "Return user information if a user is alreay logged in",
|
|
||||||
["/login"] = "Perform a login operation",
|
|
||||||
["/logout"] = "Perform a logout operation"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result(api)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
--[[
|
|
||||||
request query: none
|
|
||||||
return:
|
|
||||||
|
|
||||||
]]
|
|
||||||
function UserController:auth(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local user = require("uman").userinfo(SESSION.user)
|
|
||||||
result(user)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[ request:
|
|
||||||
{"username":"mrsang", "password":"pass"}
|
|
||||||
return:
|
|
||||||
{} ]]
|
|
||||||
function UserController:login(...)
|
|
||||||
if REQUEST.json ~= nil then
|
|
||||||
local request = JSON.decodeString(REQUEST.json)
|
|
||||||
local r = ulib.auth(request.username,request.password)
|
|
||||||
if r == true then
|
|
||||||
local cookie = {sessionid=std.sha1(request.username..request.password)} -- iotos_user = request.username
|
|
||||||
local db = sysdb();
|
|
||||||
if db == nil then return fail("Cannot setup session") end
|
|
||||||
local cond = {exp= {["="] = { sessionid = cookie.sessionid }}}
|
|
||||||
local data = db:find(cond)
|
|
||||||
--print(data)
|
|
||||||
if data == nil or data[1] == nil then
|
|
||||||
--print("insert new data")
|
|
||||||
data = {sessionid = cookie.sessionid, username=request.username, stamp=os.time(os.date("!*t"))}
|
|
||||||
else
|
|
||||||
data = data[1]
|
|
||||||
--print("Update old data")
|
|
||||||
data.stamp = os.time(os.date("!*t"))
|
|
||||||
end
|
|
||||||
if data.id == nil then
|
|
||||||
db:insert(data)
|
|
||||||
else
|
|
||||||
db:update(data)
|
|
||||||
end
|
|
||||||
db:close()
|
|
||||||
std.cjson(cookie)
|
|
||||||
SESSION.user = request.username
|
|
||||||
local user = {
|
|
||||||
result = require("uman").userinfo(request.username),
|
|
||||||
error = false
|
|
||||||
}
|
|
||||||
std.t(JSON.encode(user))
|
|
||||||
else
|
|
||||||
fail("Invalid login")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Invalid request")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function UserController:logout(...)
|
|
||||||
if SESSION.sessionid ~= nil and SESSION.sessionid ~= '0' then
|
|
||||||
local cookie = {sessionid='0'}
|
|
||||||
local db = sysdb()
|
|
||||||
if db ~= nil then
|
|
||||||
local cond = {["="] = { sessionid = SESSION.sessionid }}
|
|
||||||
db:delete(cond)
|
|
||||||
db:close()
|
|
||||||
end
|
|
||||||
std.cjson(cookie)
|
|
||||||
else
|
|
||||||
std.json()
|
|
||||||
end
|
|
||||||
std.t(JSON.encode({error=false,result=true}))
|
|
||||||
end
|
|
@ -1,130 +0,0 @@
|
|||||||
BaseController:subclass(
|
|
||||||
"VDBController",
|
|
||||||
{
|
|
||||||
registry = {},
|
|
||||||
models = {}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function VDBController:actionnotfound(...)
|
|
||||||
return self:index(table.unpack({...}))
|
|
||||||
end
|
|
||||||
|
|
||||||
function VDBController:index(...)
|
|
||||||
local api = {
|
|
||||||
description = "This api handle database operation",
|
|
||||||
actions = {
|
|
||||||
["/save"] = "Save a record to a table",
|
|
||||||
["/get"] = "Get all records or Get a record by id",
|
|
||||||
["/select"] = "Select records by a condition",
|
|
||||||
["/delete"] = "Delete record(s) by condition or by id"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result(api)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VDBController:save(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
if (rq ~= nil and rq.table ~= nil) then
|
|
||||||
local model = require("dbmodel").get(SESSION.user, rq.table, rq.data)
|
|
||||||
local ret
|
|
||||||
if model == nil then
|
|
||||||
fail("Cannot get table metadata:" .. rq.table)
|
|
||||||
else
|
|
||||||
if (rq.data.id ~= nil) then
|
|
||||||
rq.data.id = tonumber(rq.data.id)
|
|
||||||
ret = model:update(rq.data)
|
|
||||||
else
|
|
||||||
ret = model:insert(rq.data)
|
|
||||||
end
|
|
||||||
model:close()
|
|
||||||
if ret == true then
|
|
||||||
result(ret)
|
|
||||||
else
|
|
||||||
fail("Cannot modify/update table " .. rq.table)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Unknown database request")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VDBController:get(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
if (rq ~= nil and rq.table ~= nil) then
|
|
||||||
local model = require("dbmodel").get(SESSION.user, rq.table, nil)
|
|
||||||
local ret
|
|
||||||
if model == nil then
|
|
||||||
fail("Cannot get table metadata:" .. rq.table)
|
|
||||||
else
|
|
||||||
if (rq.id == nil) then
|
|
||||||
ret = model:getAll()
|
|
||||||
else
|
|
||||||
ret = model:get(rq.id)
|
|
||||||
end
|
|
||||||
model:close()
|
|
||||||
result(ret)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Unknown database request")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function VDBController:select(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
if (rq ~= nil and rq.table ~= nil) then
|
|
||||||
local model = require("dbmodel").get(SESSION.user, rq.table, nil)
|
|
||||||
local ret
|
|
||||||
if model == nil then
|
|
||||||
fail("Cannot get table metadata:" .. rq.table)
|
|
||||||
else
|
|
||||||
if (rq.cond == nil) then
|
|
||||||
model:close()
|
|
||||||
return fail("Unknow condition")
|
|
||||||
else
|
|
||||||
ret = model:find(rq.cond)
|
|
||||||
end
|
|
||||||
model:close()
|
|
||||||
result(ret)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Unknown database request")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function VDBController:delete(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
if (rq ~= nil and rq.table ~= nil) then
|
|
||||||
local model = require("dbmodel").get(SESSION.user, rq.table, nil)
|
|
||||||
local ret
|
|
||||||
if model == nil then
|
|
||||||
fail("Cannot get table metadata:" .. rq.table)
|
|
||||||
else
|
|
||||||
if (rq.id == nil) then
|
|
||||||
if (rq.cond) then
|
|
||||||
ret = model:delete(rq.cond)
|
|
||||||
model:close()
|
|
||||||
else
|
|
||||||
model:close()
|
|
||||||
return fail("Unknow element to delete")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
ret = model:deleteByID(rq.id)
|
|
||||||
model:close()
|
|
||||||
end
|
|
||||||
if ret then
|
|
||||||
result(ret)
|
|
||||||
else
|
|
||||||
fail("Querry error or database is locked")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Unknown database request")
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,221 +0,0 @@
|
|||||||
BaseController:subclass(
|
|
||||||
"VFSController",
|
|
||||||
{
|
|
||||||
registry = {},
|
|
||||||
models = {}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function VFSController:actionnotfound(...)
|
|
||||||
return self:index(table.unpack({...}))
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:index(...)
|
|
||||||
local api = {
|
|
||||||
description = "This api handle file operations",
|
|
||||||
actions = {
|
|
||||||
["/fileinfo"] = "Get file information",
|
|
||||||
["/exists"] = "Check if file exists",
|
|
||||||
["/delete"] = "Delete a file",
|
|
||||||
["/get"] = "Get a file content",
|
|
||||||
["/mkdir"] = "Create directory",
|
|
||||||
["/move"] = "Move file to a new destination",
|
|
||||||
["/publish"] = "Share a file to all users",
|
|
||||||
["/scandir"] = "List all files and folders",
|
|
||||||
["/write"] = "Write data to file",
|
|
||||||
["/shared"] = "Get shared file content"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result(api)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:fileinfo(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local vfspath = (JSON.decodeString(REQUEST.json)).path
|
|
||||||
local r, m = require("vfs").fileinfo(vfspath)
|
|
||||||
if r then
|
|
||||||
result(m)
|
|
||||||
else
|
|
||||||
fail(m)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:exists(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
|
|
||||||
local vfs = require("vfs")
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
|
|
||||||
if rq ~= nil then
|
|
||||||
result(vfs.exists(rq.path))
|
|
||||||
else
|
|
||||||
fail("Uknown request")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:delete(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
|
|
||||||
local vfs = require("vfs")
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
|
|
||||||
if rq ~= nil then
|
|
||||||
local r, e = vfs.delete(rq.path)
|
|
||||||
if r then
|
|
||||||
result(r)
|
|
||||||
else
|
|
||||||
fail(e)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Uknown request")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:get(...)
|
|
||||||
local args = {...}
|
|
||||||
local uri = implode(args, "/")
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local vfsfile = utils.decodeURI(uri)
|
|
||||||
local r, m = require("vfs").checkperm(vfsfile, "read")
|
|
||||||
if r then
|
|
||||||
std.sendFile(m)
|
|
||||||
else
|
|
||||||
fail(m)
|
|
||||||
end
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:mkdir(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
|
|
||||||
if rq ~= nil then
|
|
||||||
local r, m = require("vfs").mkdir(rq.path)
|
|
||||||
if r then
|
|
||||||
result(r)
|
|
||||||
else
|
|
||||||
fail(m)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Uknown request")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:move(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
|
|
||||||
if rq ~= nil then
|
|
||||||
local r, m = require("vfs").move(rq.src, rq.dest)
|
|
||||||
if r then
|
|
||||||
result(r)
|
|
||||||
else
|
|
||||||
fail(m)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Uknown request")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:publish(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
|
|
||||||
if rq ~= nil then
|
|
||||||
local p = nil
|
|
||||||
if rq.publish then
|
|
||||||
p = require("vfs").ospath(rq.path)
|
|
||||||
else
|
|
||||||
p = require("shared").ospath(rq.path)
|
|
||||||
end
|
|
||||||
local user = SESSION.user
|
|
||||||
local uid = ulib.uid(user)
|
|
||||||
local st = ulib.file_stat(p)
|
|
||||||
if uid.id ~= st.uid then
|
|
||||||
die("Only the owner can share or unshare this file")
|
|
||||||
end
|
|
||||||
local entry = {sid = std.sha1(p), user = SESSION.user, path = p, uid = uid.id}
|
|
||||||
local db = require("dbmodel").get("sysdb", "shared", entry)
|
|
||||||
if db == nil then
|
|
||||||
die("Cannot get system database")
|
|
||||||
end
|
|
||||||
local cond = nil
|
|
||||||
if rq.publish then
|
|
||||||
cond = {exp = {["="] = {path = p}}}
|
|
||||||
local data = db:find(cond)
|
|
||||||
if data == nil or data[0] == nil then
|
|
||||||
-- insert entry
|
|
||||||
db:insert(entry)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
cond = {["="] = {sid = rq.path}}
|
|
||||||
db:delete(cond)
|
|
||||||
end
|
|
||||||
db:close()
|
|
||||||
result(entry.sid)
|
|
||||||
else
|
|
||||||
fail("Uknown request")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:scandir(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local rq = JSON.decodeString(REQUEST.json)
|
|
||||||
local vfspath = rq.path
|
|
||||||
local r = require("vfs").readDir(vfspath)
|
|
||||||
if r == nil then
|
|
||||||
fail("Resource not found: " .. rq.path)
|
|
||||||
else
|
|
||||||
--print(JSON.encode(readDir(ospath, vfspath)))
|
|
||||||
result(r)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:upload(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local vfs = require("vfs")
|
|
||||||
if REQUEST then
|
|
||||||
local r, m = require("vfs").upload(REQUEST.path)
|
|
||||||
if r then
|
|
||||||
result(r)
|
|
||||||
else
|
|
||||||
fail(m)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Query not found")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:write(...)
|
|
||||||
auth_or_die("User unauthorized. Please login")
|
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
|
||||||
|
|
||||||
if rq ~= nil then
|
|
||||||
local r, m = require("vfs").write(rq.path, rq.data)
|
|
||||||
if r then
|
|
||||||
result(r)
|
|
||||||
else
|
|
||||||
fail(m)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
fail("Uknown request")
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function VFSController:shared(sid)
|
|
||||||
require("shared").get(sid)
|
|
||||||
end
|
|
@ -1,47 +0,0 @@
|
|||||||
require("sqlite")
|
|
||||||
function fail(msg)
|
|
||||||
std.json()
|
|
||||||
std.t(JSON.encode({error=msg}))
|
|
||||||
end
|
|
||||||
|
|
||||||
function result(obj)
|
|
||||||
std.json()
|
|
||||||
std.t(JSON.encode({result=obj, error=false}))
|
|
||||||
end
|
|
||||||
|
|
||||||
function die (msg)
|
|
||||||
fail(msg)
|
|
||||||
debug.traceback=nil
|
|
||||||
error("Permission denied")
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- check if the sysdb is create, otherwise create the table
|
|
||||||
function sysdb()
|
|
||||||
local meta = {}
|
|
||||||
meta.sessionid = ""
|
|
||||||
meta.username = ""
|
|
||||||
meta.stamp = 0
|
|
||||||
return require("dbmodel").get("sysdb", "sessions", meta)
|
|
||||||
end
|
|
||||||
|
|
||||||
function is_auth()
|
|
||||||
if SESSION.sessionid == nil or SESSION.sessionid == '0' then return false end
|
|
||||||
-- query session id from database
|
|
||||||
local db = sysdb()
|
|
||||||
if db == nil then return false end
|
|
||||||
local cond = {exp= {["="] = { sessionid = SESSION.sessionid }}}
|
|
||||||
local data = db:find(cond)
|
|
||||||
--print(JSON.encode(data))
|
|
||||||
db:close()
|
|
||||||
if data == nil or data[1] == nil then return die("No user data found") end
|
|
||||||
-- next time check the stamp
|
|
||||||
SESSION.user = data[1].username
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function auth_or_die(msg)
|
|
||||||
if(is_auth() == false) then
|
|
||||||
die(msg)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,20 +0,0 @@
|
|||||||
local model = {}
|
|
||||||
|
|
||||||
model.get = function(name, tbl, data)
|
|
||||||
local db = DBModel:new{db = name, name=tbl}
|
|
||||||
db:open()
|
|
||||||
if db:available() then return db end
|
|
||||||
if data == nil then return nil end
|
|
||||||
local meta = {}
|
|
||||||
--print(JSON.encode(data))
|
|
||||||
for k,v in pairs(data) do
|
|
||||||
if type(v) == "number" or type(v) == "boolean" then
|
|
||||||
meta[k] = "NUMERIC"
|
|
||||||
else
|
|
||||||
meta[k] = "TEXT"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
db:createTable(meta)
|
|
||||||
return db
|
|
||||||
end
|
|
||||||
return model
|
|
@ -1,119 +0,0 @@
|
|||||||
local packages={}
|
|
||||||
local vfs = require("vfs")
|
|
||||||
local uid = ulib.uid(SESSION.user)
|
|
||||||
|
|
||||||
packages._cache = function(y)
|
|
||||||
local p = vfs.ospath(y)
|
|
||||||
local f = io.open(p.."/packages.json", "w")
|
|
||||||
local has_cache = false
|
|
||||||
local i = 1
|
|
||||||
local meta = {}
|
|
||||||
if f then
|
|
||||||
local files = vfs.readDir(y)
|
|
||||||
for k,v in pairs(files) do
|
|
||||||
if v.type == "dir" then
|
|
||||||
local f1 = io.open(vfs.ospath(v.path.."/package.json"))
|
|
||||||
if f1 then
|
|
||||||
|
|
||||||
local name = std.basename(v.path)
|
|
||||||
local mt = JSON.decodeString(f1:read("*all"))
|
|
||||||
mt.path = v.path
|
|
||||||
meta[i] ='"'..name..'":'..JSON.encode(mt)
|
|
||||||
i = i+1
|
|
||||||
f1:close()
|
|
||||||
has_cache = true;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
f:write(table.concat(meta, ","))
|
|
||||||
f:close()
|
|
||||||
if has_cache == false then
|
|
||||||
ulib.delete(p.."/packages.json");
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- we will change this later
|
|
||||||
packages.list = function(paths)
|
|
||||||
std.json()
|
|
||||||
std.t("{\"result\" : { ")
|
|
||||||
local first = true
|
|
||||||
--std.f(__ROOT__.."/system/packages.json")
|
|
||||||
for k,v in pairs(paths) do
|
|
||||||
local osp = vfs.ospath(v.."/packages.json")
|
|
||||||
if ulib.exists(osp) == false then
|
|
||||||
packages._cache(v)
|
|
||||||
end
|
|
||||||
if ulib.exists(osp) then
|
|
||||||
if first == false then
|
|
||||||
std.t(",")
|
|
||||||
else
|
|
||||||
first = false
|
|
||||||
end
|
|
||||||
std.f(osp)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
std.t("}, \"error\":false}")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- generate the packages caches
|
|
||||||
packages.cache = function(args)
|
|
||||||
-- perform a packages caches
|
|
||||||
for x,y in pairs(args.paths) do
|
|
||||||
packages._cache(y)
|
|
||||||
end
|
|
||||||
result(true)
|
|
||||||
end
|
|
||||||
-- install a function from zip file
|
|
||||||
packages.install = function(args)
|
|
||||||
local path = vfs.ospath(args.dest)
|
|
||||||
local zip = vfs.ospath(args.zip)
|
|
||||||
if(ulib.exists(path) == false) then
|
|
||||||
-- create directory if not exist
|
|
||||||
ulib.mkdir(path)
|
|
||||||
-- change permission
|
|
||||||
ulib.chown(path, uid.id, uid.gid)
|
|
||||||
end
|
|
||||||
-- extract the zip file to it
|
|
||||||
if(ulib.unzip(zip, path)) then
|
|
||||||
-- read metadata
|
|
||||||
local meta = JSON.decodeFile(path.."/metadata.json")
|
|
||||||
meta.path = args.dest
|
|
||||||
meta.scope = "user"
|
|
||||||
local f=io.open(path.."/package.json","w")
|
|
||||||
if f then
|
|
||||||
f:write(JSON.encode(meta))
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
result(true)
|
|
||||||
else
|
|
||||||
fail("Problem extracting zip file")
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
-- uninstall the package
|
|
||||||
packages.uninstall = function(path)
|
|
||||||
local osf = vfs.ospath(path)
|
|
||||||
if(osf and ulib.exists(osf) ) then
|
|
||||||
--remove it
|
|
||||||
ulib.delete(osf)
|
|
||||||
result(true)
|
|
||||||
else
|
|
||||||
fail("Cannot find package")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- set user packages environment
|
|
||||||
packages.init = function(paths)
|
|
||||||
if(paths) then
|
|
||||||
for k,v in pairs(paths) do
|
|
||||||
local p = vfs.ospath(v)
|
|
||||||
if p and (ulib.exists(p) == false) then
|
|
||||||
ulib.mkdir(p)
|
|
||||||
-- change permission
|
|
||||||
ulib.chown(p, uid.id, uid.gid)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return packages
|
|
@ -1,47 +0,0 @@
|
|||||||
local shared = {}
|
|
||||||
shared.get = function(sharedid)
|
|
||||||
if sharedid == "all" then
|
|
||||||
-- get all shared files
|
|
||||||
local db = require("dbmodel").get("sysdb", "shared", nil)
|
|
||||||
if db == nil then die("Cannot get shared database") end
|
|
||||||
local data = db:getAll()
|
|
||||||
if data == nil then die("No file found") end
|
|
||||||
local i = 1
|
|
||||||
local ret = {}
|
|
||||||
for k,v in pairs(data) do
|
|
||||||
if(ulib.exists(v.path)) then
|
|
||||||
local r = ulib.file_stat(v.path)
|
|
||||||
if(r.error == nil) then
|
|
||||||
r.path = "shared://"..v.sid
|
|
||||||
r.filename = std.basename(v.path)
|
|
||||||
if r.mime == "application/octet-stream" then
|
|
||||||
r.mime = std.extra_mime(r.filename)
|
|
||||||
end
|
|
||||||
ret[i] = r
|
|
||||||
i = i+1
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local cond = { ["="] = { sid = v.sid } }
|
|
||||||
db:delete(cond)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
db:close()
|
|
||||||
--std.json()
|
|
||||||
result(ret)
|
|
||||||
else
|
|
||||||
|
|
||||||
local p = shared.ospath(sharedid)
|
|
||||||
std.sendFile(p)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared.ospath = function(sharedid)
|
|
||||||
local db = require("dbmodel").get("sysdb", "shared", nil)
|
|
||||||
if db == nil then die("Cannot get shared database") end
|
|
||||||
local cond = { exp = { ["="] = { sid = sharedid } } }
|
|
||||||
local data = db:find(cond)
|
|
||||||
db:close()
|
|
||||||
if data == nil or data[1] == nil then die("Cannot get shared file with: "..sharedid) end
|
|
||||||
return data[1].path
|
|
||||||
end
|
|
||||||
return shared;
|
|
@ -1,27 +0,0 @@
|
|||||||
local uman={}
|
|
||||||
|
|
||||||
uman.userinfo = function(user)
|
|
||||||
local info = {}
|
|
||||||
local uid = ulib.uid(user)
|
|
||||||
if uid then
|
|
||||||
-- read the setting
|
|
||||||
-- use the decodeFile function of JSON instead
|
|
||||||
local file = require('vfs').ospath("home:///").."/.settings.json"
|
|
||||||
local st = JSON.decodeFile(file)
|
|
||||||
if(st) then
|
|
||||||
info = st
|
|
||||||
end
|
|
||||||
info.user = {
|
|
||||||
username = user,
|
|
||||||
id = uid.id,
|
|
||||||
name = user,
|
|
||||||
groups = uid.groups
|
|
||||||
}
|
|
||||||
--print(JSON.encode(info))
|
|
||||||
return info
|
|
||||||
else
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return uman
|
|
217
os/libs/vfs.lua
217
os/libs/vfs.lua
@ -1,217 +0,0 @@
|
|||||||
local vfs = {}
|
|
||||||
|
|
||||||
|
|
||||||
vfs.ospath = function(path)
|
|
||||||
local user = SESSION.user
|
|
||||||
local prefix = string.match(path, "%a+://")
|
|
||||||
if(prefix ~= nil) then
|
|
||||||
local suffix = string.gsub(path,prefix,"")
|
|
||||||
if prefix == "home://" then
|
|
||||||
return string.format(VFS_HOME,user)..'/'..suffix
|
|
||||||
elseif prefix == "desktop://" then
|
|
||||||
return string.format(VFS_HOME,user).."/.desktop/"..suffix
|
|
||||||
elseif prefix == "shared://" then
|
|
||||||
return require("shared").ospath(std.trim(suffix,"/"))
|
|
||||||
elseif prefix == "os://" then
|
|
||||||
return WWW_ROOT.."/"..suffix
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return nil;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
vfs.delete = function(path)
|
|
||||||
local r,m = vfs.checkperm(path,"write")
|
|
||||||
if r then
|
|
||||||
if ulib.delete(m) then
|
|
||||||
-- change permission
|
|
||||||
return true,nil
|
|
||||||
else
|
|
||||||
return false,"Cant not delete the file"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return r,m
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vfs.exists = function(path)
|
|
||||||
local osfile = vfs.ospath(path)
|
|
||||||
return ulib.exists(osfile)
|
|
||||||
end
|
|
||||||
|
|
||||||
vfs.fileinfo = function(vfspath)
|
|
||||||
local ospath = vfs.ospath(vfspath)
|
|
||||||
if ospath then
|
|
||||||
if(ulib.exists(ospath) == false) then return false,"File not found" end
|
|
||||||
local r = ulib.file_stat(ospath)
|
|
||||||
if(r.error ~= nil) then return false,r.error end
|
|
||||||
r.path = vfspath
|
|
||||||
r.name = std.basename(vfspath)
|
|
||||||
if r.mime == "application/octet-stream" then
|
|
||||||
r.mime = std.extra_mime(r.name)
|
|
||||||
end
|
|
||||||
return true,r
|
|
||||||
else
|
|
||||||
return false,"Resource not found"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vfs.mkdir = function(path)
|
|
||||||
local file = std.basename(path)
|
|
||||||
local folder = string.gsub(path, utils.escape_pattern(file).."$","")
|
|
||||||
local r,m = vfs.checkperm(folder,"write")
|
|
||||||
|
|
||||||
if r then
|
|
||||||
local osfile = m.."/"..file
|
|
||||||
local uid = ulib.uid(SESSION.user)
|
|
||||||
ulib.mkdir(osfile)
|
|
||||||
-- change permission
|
|
||||||
ulib.chown(osfile, uid.id, uid.gid)
|
|
||||||
return true,nil
|
|
||||||
else
|
|
||||||
return r,m
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vfs.move = function(src,dest)
|
|
||||||
local file = std.basename(dest)
|
|
||||||
local folder = string.gsub(dest, utils.escape_pattern(file),"")
|
|
||||||
|
|
||||||
local sp,sm = vfs.checkperm(src,"write")
|
|
||||||
if sp then
|
|
||||||
local dp,dm = vfs.checkperm(folder,"write")
|
|
||||||
if dp then
|
|
||||||
ulib.move(sm,dm.."/"..file)
|
|
||||||
-- change permission
|
|
||||||
return true,nil
|
|
||||||
else
|
|
||||||
return dp,dm
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return sp,sm
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vfs.write = function(path,data)
|
|
||||||
local file = std.basename(path)
|
|
||||||
local folder = string.gsub(path, utils.escape_pattern(file),"")
|
|
||||||
|
|
||||||
local r,m = vfs.checkperm(folder,"write")
|
|
||||||
if r then
|
|
||||||
local osfile = m.."/"..file
|
|
||||||
|
|
||||||
if ulib.exists(osfile) then
|
|
||||||
local r1,m1 = vfs.checkperm(path,"write")
|
|
||||||
if not r1 then
|
|
||||||
return r1, m1..": "..path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local uid = ulib.uid(SESSION.user)
|
|
||||||
--
|
|
||||||
if data ~= "" then
|
|
||||||
local header = string.match(data, "^data%:[%w%.-]+%/[%w%.-]+;base64,")
|
|
||||||
if header ~= nil then
|
|
||||||
local b64data = string.gsub(data, utils.escape_pattern(header),"")
|
|
||||||
local barr = std.b64decode(b64data)
|
|
||||||
bytes.write(barr,osfile)
|
|
||||||
--[[ if std.isBinary(osfile) then
|
|
||||||
|
|
||||||
else
|
|
||||||
local f = io.open(osfile, "w")
|
|
||||||
f:write(bytes.__tostring(barr))
|
|
||||||
f:close()
|
|
||||||
end ]]
|
|
||||||
else
|
|
||||||
return false, "Wrong data format"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
bytes.write(bytes.new(0),osfile)
|
|
||||||
end
|
|
||||||
--f:close()
|
|
||||||
-- change permission
|
|
||||||
ulib.chown(osfile, uid.id, uid.gid)
|
|
||||||
return true,nil
|
|
||||||
else
|
|
||||||
return r,m..": "..folder
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vfs.upload = function(path)
|
|
||||||
local r,m = vfs.checkperm(path,"write")
|
|
||||||
if(r) then
|
|
||||||
local uid = ulib.uid(SESSION.user)
|
|
||||||
local file = m.."/"..REQUEST["upload.file"]
|
|
||||||
ulib.move(REQUEST["upload.tmp"], file)
|
|
||||||
ulib.chown(file, uid.id, uid.gid)
|
|
||||||
return true, nil
|
|
||||||
else
|
|
||||||
return r,m
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vfs.checkperm = function(path, right)
|
|
||||||
local osfile = vfs.ospath(path)
|
|
||||||
local perm = vfs.perm(osfile)
|
|
||||||
print(osfile)
|
|
||||||
if not ulib.exists(osfile) then
|
|
||||||
return false,"Resource does not exist"
|
|
||||||
end
|
|
||||||
-- check if user own the file
|
|
||||||
if perm ~= nil then
|
|
||||||
if perm[right] == true then
|
|
||||||
print("Permission granted")
|
|
||||||
return true,osfile
|
|
||||||
else
|
|
||||||
print("Permission denie")
|
|
||||||
return false,"You dont have "..right.." permission on this file"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return false,"User is unrecognized"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vfs.perm = function(file)
|
|
||||||
local user = SESSION.user
|
|
||||||
local uid = ulib.uid(user)
|
|
||||||
local st = ulib.file_stat(file)
|
|
||||||
-- check if user own the file
|
|
||||||
if uid ~= nil and st ~= nil and st.perm ~= nil then
|
|
||||||
--print(JSON.encode({uid, st}))
|
|
||||||
if(uid.id == st.uid) then -- the user owned the file
|
|
||||||
print("file belong to user")
|
|
||||||
return st.perm.owner
|
|
||||||
elseif uid.groups and uid.groups[st.gid] then
|
|
||||||
print("User belong to this group")
|
|
||||||
return st.perm.group
|
|
||||||
else
|
|
||||||
print("User belong to other")
|
|
||||||
return st.perm.other
|
|
||||||
end
|
|
||||||
else
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
vfs.readDir = function(vfspath)
|
|
||||||
if(string.sub(vfspath,-1) == "/") then
|
|
||||||
prefix = string.sub(vfspath,1,-2)
|
|
||||||
else
|
|
||||||
prefix = vfspath
|
|
||||||
end
|
|
||||||
local ospath = vfs.ospath(vfspath,SESSION.user)
|
|
||||||
local r = ulib.read_dir(ospath, prefix)
|
|
||||||
if(r.error ~= nil) then return nil end
|
|
||||||
-- add extra mime type
|
|
||||||
for k,v in pairs(r) do
|
|
||||||
if v.mime == "application/octet-stream" then
|
|
||||||
v.mime = std.extra_mime(v.filename)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return r
|
|
||||||
end
|
|
||||||
|
|
||||||
return vfs
|
|
@ -1,45 +0,0 @@
|
|||||||
|
|
||||||
-- the rewrite rule for the framework
|
|
||||||
-- should be something like this
|
|
||||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
|
||||||
-- some global variables
|
|
||||||
DIR_SEP = "/"
|
|
||||||
WWW_ROOT = __ROOT__.."/os"
|
|
||||||
if HEADER.Host then
|
|
||||||
HTTP_ROOT= "https://"..HEADER.Host
|
|
||||||
else
|
|
||||||
HTTP_ROOT = "https://os.lxsang.me"
|
|
||||||
end
|
|
||||||
-- class path: path.to.class
|
|
||||||
BASE_FRW = ""
|
|
||||||
-- class path: path.to.class
|
|
||||||
CONTROLLER_ROOT = BASE_FRW.."os.controllers"
|
|
||||||
MODEL_ROOT = BASE_FRW.."os.models"
|
|
||||||
-- file path: path/to/file
|
|
||||||
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
|
||||||
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
|
|
||||||
|
|
||||||
VFS_HOME = "/home/%s"
|
|
||||||
|
|
||||||
package.path = package.path..";"..WWW_ROOT .. '/libs/?.lua'
|
|
||||||
-- require needed library
|
|
||||||
require(BASE_FRW.."silk.api")
|
|
||||||
require("common")
|
|
||||||
|
|
||||||
|
|
||||||
-- registry object store global variables
|
|
||||||
local REGISTRY = {}
|
|
||||||
-- set logging level
|
|
||||||
REGISTRY.logger = Logger:new{ levels = {INFO = false, ERROR = true, DEBUG = false}}
|
|
||||||
--REGISTRY.db = DBHelper:new{db="sysdb"}
|
|
||||||
REGISTRY.layout = 'default'
|
|
||||||
REGISTRY.fileaccess = true
|
|
||||||
|
|
||||||
--REGISTRY.db:open()
|
|
||||||
local router = Router:new{registry = REGISTRY}
|
|
||||||
REGISTRY.router = router
|
|
||||||
router:setPath(CONTROLLER_ROOT)
|
|
||||||
|
|
||||||
router:delegate()
|
|
||||||
--if REGISTRY.db then REGISTRY.db:close() end
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
|||||||
-- create class
|
|
||||||
BaseObject:subclass("BaseController",
|
|
||||||
{
|
|
||||||
registry = {},
|
|
||||||
models = {},
|
|
||||||
main = false
|
|
||||||
})
|
|
||||||
|
|
||||||
-- set the name here in each subclasses
|
|
||||||
function BaseController:initialize()
|
|
||||||
for k, v in pairs(self.models) do
|
|
||||||
--- infer the class here
|
|
||||||
local modelname = firstToUpper(v).."Model"
|
|
||||||
local path = MODEL_ROOT.."."..modelname
|
|
||||||
-- require it
|
|
||||||
pcall(require, path)
|
|
||||||
--require(controller_path)
|
|
||||||
if not _G[modelname] then
|
|
||||||
self:modelnotfound(v)
|
|
||||||
else
|
|
||||||
-- create new model object
|
|
||||||
self[v] = _G[modelname]:new{registry = self.registry}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- create template
|
|
||||||
self.template = Template:new{registry = self.registry}
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseController:print(...)
|
|
||||||
return self:actionnotfound("print")
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseController:redirect(url)
|
|
||||||
std.status(301, "Moved Permanently")
|
|
||||||
std.custom_header("Content-Type","text/html")
|
|
||||||
std.custom_header("Location", url)
|
|
||||||
std.header_flush()
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseController:switchLayout(name)
|
|
||||||
if self.main then
|
|
||||||
self.registry.layout = name
|
|
||||||
else
|
|
||||||
self:log("Cannot switch layout since the controller "..self.class.." is not the main controller")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseController:setSession(key, value)
|
|
||||||
SESSION[key] = value
|
|
||||||
end
|
|
||||||
function BaseController:getSession(key)
|
|
||||||
return SESSION[key]
|
|
||||||
end
|
|
||||||
function BaseController:removeSession(key)
|
|
||||||
self:setSession(key, nil)
|
|
||||||
end
|
|
||||||
function BaseController:index(...)
|
|
||||||
self:error("#index: subclasses responsibility")
|
|
||||||
end
|
|
||||||
|
|
||||||
-- not found action
|
|
||||||
function BaseController:actionnotfound(...)
|
|
||||||
local args = {...}
|
|
||||||
self:error("#action "..args[1].." is not found in controller "..self.class)
|
|
||||||
end
|
|
||||||
-- not found model
|
|
||||||
function BaseController:modelnotfound(...)
|
|
||||||
local args = {...}
|
|
||||||
self:error("Model "..firstToUpper(args[1]).."Model is not found in controller "..self.class)
|
|
||||||
end
|
|
||||||
-- The not found controller
|
|
||||||
|
|
||||||
BaseController:subclass("NotfoundController",{ registry = {}, models = {} })
|
|
||||||
|
|
||||||
function NotfoundController:index(...)
|
|
||||||
local args = {...}
|
|
||||||
local error = args[2] or ""
|
|
||||||
if self.template:path() then
|
|
||||||
self.template:set("error", error)
|
|
||||||
self.template:set("title", "404 not found")
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
self:error("404: Controller "..args[1].." not found : "..error)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
-- The asset controller for the static file
|
|
||||||
BaseController:subclass("AssetController", {registry={}, models={}})
|
|
||||||
function AssetController:index(...)
|
|
||||||
local args = {...}
|
|
||||||
return self:get(table.unpack(args))
|
|
||||||
end
|
|
||||||
|
|
||||||
function AssetController:get(...)
|
|
||||||
local path = WWW_ROOT..DIR_SEP..implode({...}, DIR_SEP)
|
|
||||||
|
|
||||||
if self.registry.fileaccess and ulib.exists(path) then
|
|
||||||
local mime = std.mimeOf(path)
|
|
||||||
if POLICY.mimes[mime] then
|
|
||||||
std.sendFile(path)
|
|
||||||
else
|
|
||||||
self:error("Access forbidden: "..path)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self:error("Asset file not found or access forbidden: "..path)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
@ -1,51 +0,0 @@
|
|||||||
-- create class
|
|
||||||
BaseObject:subclass("BaseModel", {registry = {}})
|
|
||||||
|
|
||||||
function BaseModel:initialize()
|
|
||||||
self.db = self.registry.db
|
|
||||||
if self.db and self.name and self.name ~= "" and self.fields and
|
|
||||||
not self.db:available(self.name) then
|
|
||||||
self.db:createTable(self.name, self.fields)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseModel:create(m)
|
|
||||||
if self.db and m then return self.db:insert(self.name, m) end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseModel:update(m)
|
|
||||||
if self.db and m then return self.db:update(self.name, m) end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseModel:delete(cond)
|
|
||||||
if self.db and cond then return self.db:delete(self.name, cond) end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseModel:find(cond)
|
|
||||||
if self.db and cond then return self.db:find(self.name, cond) end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseModel:get(id)
|
|
||||||
local data, order = self:find({exp = {["="] = {id = id}}})
|
|
||||||
if not data or #order == 0 then return false end
|
|
||||||
return data[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseModel:findAll()
|
|
||||||
if self.db then return self.db:getAll(self.name) end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseModel:query(sql)
|
|
||||||
if self.db then return self.db:query(sql) end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseModel:select(sel, sql_cnd)
|
|
||||||
if self.db then return self.db:select(self.name, sel, sql_cnd) end
|
|
||||||
return nil
|
|
||||||
end
|
|
@ -1,34 +0,0 @@
|
|||||||
BaseObject = Object:extends{registry = {}, class="BaseObject"}
|
|
||||||
function BaseObject:subclass(name, args)
|
|
||||||
_G[name] = self:extends(args)
|
|
||||||
_G[name].class = name
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseObject:log(msg, level)
|
|
||||||
level = level or "INFO"
|
|
||||||
if self.registry.logger then
|
|
||||||
self.registry.logger:log(msg,level)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseObject:debug(msg)
|
|
||||||
self:log(msg, "DEBUG")
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseObject:print()
|
|
||||||
print(self.class)
|
|
||||||
end
|
|
||||||
|
|
||||||
function BaseObject:error(msg, trace)
|
|
||||||
html()
|
|
||||||
--local line = debug.getinfo(1).currentline
|
|
||||||
echo(msg)
|
|
||||||
self:log(msg,"ERROR")
|
|
||||||
if trace then
|
|
||||||
debug.traceback=nil
|
|
||||||
error(msg)
|
|
||||||
else
|
|
||||||
error(msg)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
@ -1,144 +0,0 @@
|
|||||||
sqlite = modules.sqlite()
|
|
||||||
|
|
||||||
if sqlite == nil then return 0 end
|
|
||||||
-- create class
|
|
||||||
BaseObject:subclass("DBHelper", {db = {}})
|
|
||||||
|
|
||||||
function DBHelper:createTable(tbl, m)
|
|
||||||
if self:available(tbl) then return true end
|
|
||||||
local sql = "CREATE TABLE " .. tbl .. "(id INTEGER PRIMARY KEY"
|
|
||||||
for k, v in pairs(m) do
|
|
||||||
if k ~= "id" then sql = sql .. "," .. k .. " " .. v end
|
|
||||||
end
|
|
||||||
sql = sql .. ");"
|
|
||||||
return sqlite.query(self.db, sql) == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:insert(tbl, m)
|
|
||||||
local keys = {}
|
|
||||||
local values = {}
|
|
||||||
for k, v in pairs(m) do
|
|
||||||
if k ~= "id" then
|
|
||||||
table.insert(keys, k)
|
|
||||||
if type(v) == "number" then
|
|
||||||
table.insert(values, v)
|
|
||||||
else
|
|
||||||
local t = "\"" .. v:gsub('"', '""') .. "\""
|
|
||||||
table.insert(values, t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local sql = "INSERT INTO " .. tbl .. " (" .. table.concat(keys, ',') ..
|
|
||||||
') VALUES ('
|
|
||||||
sql = sql .. table.concat(values, ',') .. ');'
|
|
||||||
return sqlite.query(self.db, sql) == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:get(tbl, id)
|
|
||||||
return sqlite.select(self.db, tbl, "*", "id=" .. id)[1]
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:getAll(tbl)
|
|
||||||
local data = sqlite.select(self.db, tbl, "*", "1=1")
|
|
||||||
if data == nil then return nil end
|
|
||||||
local a = {}
|
|
||||||
for n in pairs(data) do table.insert(a, n) end
|
|
||||||
table.sort(a)
|
|
||||||
return data, a
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:find(tbl, cond)
|
|
||||||
local cnd = "1=1"
|
|
||||||
local sel = "*"
|
|
||||||
if cond.exp then cnd = self:gencond(cond.exp) end
|
|
||||||
if cond.order then
|
|
||||||
cnd = cnd .. " ORDER BY "
|
|
||||||
local l = {}
|
|
||||||
local i = 1
|
|
||||||
for k, v in pairs(cond.order) do
|
|
||||||
l[i] = k .. " " .. v
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
cnd = cnd .. table.concat(l, ",")
|
|
||||||
end
|
|
||||||
if cond.limit then cnd = cnd .. " LIMIT " .. cond.limit end
|
|
||||||
if cond.fields then
|
|
||||||
sel = table.concat(cond.fields, ",")
|
|
||||||
-- print(sel)
|
|
||||||
end
|
|
||||||
local data = sqlite.select(self.db, tbl, sel, cnd)
|
|
||||||
if data == nil then return nil end
|
|
||||||
local a = {}
|
|
||||||
for n in pairs(data) do table.insert(a, n) end
|
|
||||||
table.sort(a)
|
|
||||||
return data, a
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:select(tbl, sel, cnd)
|
|
||||||
local data = sqlite.select(self.db, tbl, sel, cnd)
|
|
||||||
if data == nil then return nil end
|
|
||||||
local a = {}
|
|
||||||
for n in pairs(data) do table.insert(a, n) end
|
|
||||||
table.sort(a)
|
|
||||||
return data, a
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:query(sql) return sqlite.query(self.db, sql) == 1 end
|
|
||||||
|
|
||||||
function DBHelper:update(tbl, m)
|
|
||||||
local id = m['id']
|
|
||||||
if id ~= nil then
|
|
||||||
local lst = {}
|
|
||||||
for k, v in pairs(m) do
|
|
||||||
if (type(v) == "number") then
|
|
||||||
table.insert(lst, k .. "=" .. v)
|
|
||||||
else
|
|
||||||
table.insert(lst, k .. "=\"" .. v:gsub('"', '""') .. "\"")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local sql = "UPDATE " .. tbl .. " SET " .. table.concat(lst, ",") ..
|
|
||||||
" WHERE id=" .. id .. ";"
|
|
||||||
return sqlite.query(self.db, sql) == 1
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:available(tbl) return sqlite.hasTable(self.db, tbl) == 1 end
|
|
||||||
function DBHelper:deleteByID(tbl, id)
|
|
||||||
local sql = "DELETE FROM " .. tbl .. " WHERE id=" .. id .. ";"
|
|
||||||
return sqlite.query(self.db, sql) == 1
|
|
||||||
end
|
|
||||||
function DBHelper:gencond(o)
|
|
||||||
for k, v in pairs(o) do
|
|
||||||
if k == "and" or k == "or" then
|
|
||||||
local cnd = {}
|
|
||||||
local i = 1
|
|
||||||
for k1, v1 in pairs(v) do
|
|
||||||
cnd[i] = self:gencond(v1)
|
|
||||||
i = i + 1
|
|
||||||
end
|
|
||||||
return " (" .. table.concat(cnd, " " .. k .. " ") .. ") "
|
|
||||||
else
|
|
||||||
for k1, v1 in pairs(v) do
|
|
||||||
local t = type(v1)
|
|
||||||
if (t == "string") then
|
|
||||||
return
|
|
||||||
" (" .. k1 .. " " .. k .. ' "' .. v1:gsub('"', '""') ..
|
|
||||||
'") '
|
|
||||||
end
|
|
||||||
return " (" .. k1 .. " " .. k .. " " .. v1 .. ") "
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
function DBHelper:delete(tbl, cond)
|
|
||||||
local sql = "DELETE FROM " .. tbl .. " WHERE " .. self:gencond(cond) .. ";"
|
|
||||||
return sqlite.query(self.db, sql) == 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function DBHelper:lastInsertID() return sqlite.lastInsertID(self.db) end
|
|
||||||
|
|
||||||
function DBHelper:close() if self.db then sqlite.dbclose(self.db) end end
|
|
||||||
function DBHelper:open()
|
|
||||||
if self.db ~= nil then self.db = sqlite.getdb(self.db) end
|
|
||||||
end
|
|
@ -1,30 +0,0 @@
|
|||||||
Logger = Object:extends{levels = {}}
|
|
||||||
|
|
||||||
function Logger:initialize()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Logger:log(msg,level)
|
|
||||||
if self.levels[level] and ulib.exists(LOG_ROOT) then
|
|
||||||
local path = LOG_ROOT..DIR_SEP..level..'.txt'
|
|
||||||
local f = io.open(path, 'a')
|
|
||||||
local text = '['..level.."]: "..msg
|
|
||||||
if f then
|
|
||||||
f:write(text..'\n')
|
|
||||||
f:close()
|
|
||||||
end
|
|
||||||
print(text)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Logger:info(msg)
|
|
||||||
self:log(msg, "INFO")
|
|
||||||
end
|
|
||||||
|
|
||||||
function Logger:debug(msg)
|
|
||||||
self:log(msg, "DEBUG")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Logger:error(msg)
|
|
||||||
self:log(msg, "ERROR")
|
|
||||||
end
|
|
164
silk/Router.lua
164
silk/Router.lua
@ -1,164 +0,0 @@
|
|||||||
--define the class
|
|
||||||
BaseObject:subclass("Router", {registry = {}})
|
|
||||||
function Router:setPath(path)
|
|
||||||
self.path = path
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:initialize()
|
|
||||||
self.routes = {}
|
|
||||||
self.remaps = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
--function Router:setArgs(args)
|
|
||||||
-- self.args = args
|
|
||||||
--end
|
|
||||||
|
|
||||||
--function Router:arg(name)
|
|
||||||
-- return self.args[name]
|
|
||||||
--end
|
|
||||||
|
|
||||||
function Router:infer(url)
|
|
||||||
-- a controller is like this /a/b/c/d/e
|
|
||||||
-- a is controller name
|
|
||||||
-- b is action
|
|
||||||
-- c,d,e is parameters
|
|
||||||
-- if user dont provide the url, try to infer it
|
|
||||||
-- from the REQUEST
|
|
||||||
url = url or REQUEST.r or ""
|
|
||||||
url = std.trim(url, "/")
|
|
||||||
local args = explode(url, "/")
|
|
||||||
local data = {
|
|
||||||
name = "index",
|
|
||||||
action = "index",
|
|
||||||
args = {}
|
|
||||||
}
|
|
||||||
if args and #args > 0 and args[1] ~= "" then
|
|
||||||
data.name = args[1]:gsub("%.", "")
|
|
||||||
if args[2] then
|
|
||||||
data.action = args[2]:gsub("%.", "")
|
|
||||||
end
|
|
||||||
for i = 3, #args do
|
|
||||||
table.insert(data.args, args[i])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- remap if needed
|
|
||||||
if self.remaps[data.name] ~= nil then
|
|
||||||
data.name = self.remaps[data.name]
|
|
||||||
end
|
|
||||||
-- find the controller class and init it
|
|
||||||
local controller_name = firstToUpper(data.name) .. "Controller"
|
|
||||||
local controller_path = self.path .. "." .. controller_name
|
|
||||||
-- require the controller module
|
|
||||||
-- ignore the error
|
|
||||||
local r, e = pcall(require, controller_path)
|
|
||||||
--require(controller_path)
|
|
||||||
if not _G[controller_name] then
|
|
||||||
-- verify if it is an asset
|
|
||||||
url = url:gsub("/", DIR_SEP)
|
|
||||||
local filepath = WWW_ROOT..DIR_SEP..url
|
|
||||||
if ulib.exists(filepath) then -- and not std.is_dir(filepath)
|
|
||||||
data.controller = AssetController:new {registry = self.registry}
|
|
||||||
data.action = "get"
|
|
||||||
data.name = "asset"
|
|
||||||
data.args ={url}
|
|
||||||
else
|
|
||||||
-- let the notfound controller handle the error
|
|
||||||
data.controller = NotfoundController:new {registry = self.registry}
|
|
||||||
data.args = {controller_name, e}
|
|
||||||
data.action = "index"
|
|
||||||
data.name = "notfound"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
-- create the coresponding controller
|
|
||||||
data.controller = _G[controller_name]:new {registry = self.registry}
|
|
||||||
if not data.controller[data.action] then
|
|
||||||
--data.args = {data.action}
|
|
||||||
table.insert(data.args, 1, data.action)
|
|
||||||
data.action = "actionnotfound"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
self:log("Controller: " .. data.controller.class .. ", action: "..data.action..", args: ".. JSON.encode(data.args))
|
|
||||||
return data
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:delegate()
|
|
||||||
local views = {}
|
|
||||||
local data = self:infer()
|
|
||||||
-- set the controller to the main controller
|
|
||||||
data.controller.main = true
|
|
||||||
views.__main__ = self:call(data)
|
|
||||||
if not views.__main__ then
|
|
||||||
--self:error("No view available for this action")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
-- get all visible routes
|
|
||||||
local routes = self:dependencies(data.name .. "/" .. data.action)
|
|
||||||
for k, v in pairs(routes) do
|
|
||||||
data = self:infer(v)
|
|
||||||
views[k] = self:call(data)
|
|
||||||
end
|
|
||||||
-- now require the main page to put the view
|
|
||||||
local view_args = {}
|
|
||||||
local view_argv = {}
|
|
||||||
for k,v in pairs(views) do
|
|
||||||
table.insert( view_args, k )
|
|
||||||
table.insert( view_argv, v )
|
|
||||||
end
|
|
||||||
|
|
||||||
local fn, e = loadscript(VIEW_ROOT .. DIR_SEP .. self.registry.layout .. DIR_SEP .. "layout.ls", view_args)
|
|
||||||
html()
|
|
||||||
if fn then
|
|
||||||
local r, o = pcall(fn, table.unpack(view_argv))
|
|
||||||
if not r then
|
|
||||||
self:error(o)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
e = e or ""
|
|
||||||
self:error("The index page is not found for layout: " .. self.registry.layout..": "..e)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:dependencies(url)
|
|
||||||
if not self.routes[self.registry.layout] then
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
local list = {}
|
|
||||||
--self:log("comparing "..url)
|
|
||||||
for k, v in pairs(self.routes[self.registry.layout]) do
|
|
||||||
v.url = std.trim(v.url, "/")
|
|
||||||
if v.visibility == "ALL" then
|
|
||||||
list[k] = v.url
|
|
||||||
elseif v.visibility.routes then
|
|
||||||
if v.visibility.shown == true or v.visibility.shown == nil then
|
|
||||||
if v.visibility.routes[url] then
|
|
||||||
list[k] = v.url
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if not v.visibility.routes[url] then
|
|
||||||
list[k] = v.url
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return list
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:call(data)
|
|
||||||
data.controller.template:setView(data.action, data.name)
|
|
||||||
local obj = data.controller[data.action](data.controller, table.unpack(data.args))
|
|
||||||
if obj then
|
|
||||||
return data.controller.template
|
|
||||||
else
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:remap(from, to)
|
|
||||||
self.remaps[from] = to
|
|
||||||
end
|
|
||||||
|
|
||||||
function Router:route(layout, dependencies)
|
|
||||||
self.routes[layout] = dependencies
|
|
||||||
end
|
|
@ -1,58 +0,0 @@
|
|||||||
-- create class
|
|
||||||
BaseObject:subclass("Template",{registry = {}})
|
|
||||||
|
|
||||||
function Template:initialize()
|
|
||||||
self.vars = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function Template:set(k, v, ow)
|
|
||||||
if not self.vars[k] or (self.vars[k] and ow) then
|
|
||||||
self.vars[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Template:get(k)
|
|
||||||
return self.vars[k]
|
|
||||||
end
|
|
||||||
|
|
||||||
function Template:remove(k)
|
|
||||||
self.vars[k] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- infer view path
|
|
||||||
function Template:setView(name, controller)
|
|
||||||
self.name = name
|
|
||||||
if controller then
|
|
||||||
self.controller = controller
|
|
||||||
end
|
|
||||||
end
|
|
||||||
function Template:path()
|
|
||||||
local path = VIEW_ROOT..DIR_SEP..self.registry.layout..DIR_SEP..self.controller..DIR_SEP..self.name..".ls"
|
|
||||||
if ulib.exists(path) then
|
|
||||||
return path
|
|
||||||
else
|
|
||||||
return false, path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
-- render the page
|
|
||||||
function Template:render()
|
|
||||||
local path, err = self:path()
|
|
||||||
if not path then
|
|
||||||
return self:error("View not found: "..err)
|
|
||||||
end
|
|
||||||
local args = {}
|
|
||||||
local argv = {}
|
|
||||||
for k, v in pairs(self.vars) do
|
|
||||||
table.insert( args, k )
|
|
||||||
table.insert( argv,v )
|
|
||||||
end
|
|
||||||
local fn, e = loadscript(self:path(), args)
|
|
||||||
if fn then
|
|
||||||
local r,o = pcall(fn, table.unpack(argv))
|
|
||||||
if not r then
|
|
||||||
self:error(o)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self:error(e)
|
|
||||||
end
|
|
||||||
end
|
|
57
silk/api.lua
57
silk/api.lua
@ -1,57 +0,0 @@
|
|||||||
require("OOP")
|
|
||||||
ulib = require("ulib")
|
|
||||||
require(BASE_FRW.."silk.BaseObject")
|
|
||||||
require(BASE_FRW.."silk.DBHelper")
|
|
||||||
require(BASE_FRW.."silk.Router")
|
|
||||||
require(BASE_FRW.."silk.BaseController")
|
|
||||||
require(BASE_FRW.."silk.BaseModel")
|
|
||||||
require(BASE_FRW.."silk.Logger")
|
|
||||||
require(BASE_FRW.."silk.Template")
|
|
||||||
|
|
||||||
-- mime type allows
|
|
||||||
-- this will bypass the default server security
|
|
||||||
-- the default list is from the server setting
|
|
||||||
POLICY = {}
|
|
||||||
POLICY.mimes = {
|
|
||||||
["application/javascript"] = true,
|
|
||||||
["image/bmp"] = true,
|
|
||||||
["image/jpeg"] = true,
|
|
||||||
["image/png"] = true,
|
|
||||||
["text/css"] = true,
|
|
||||||
["text/markdown"] = true,
|
|
||||||
["text/csv"] = true,
|
|
||||||
["application/pdf"] = true,
|
|
||||||
["image/gif"] = true,
|
|
||||||
["text/html"] = true,
|
|
||||||
["application/json"] = true,
|
|
||||||
["application/javascript"] = true,
|
|
||||||
["image/x-portable-pixmap"] = true,
|
|
||||||
["application/x-rar-compressed"] = true,
|
|
||||||
["image/tiff"] = true,
|
|
||||||
["application/x-tar"] = true,
|
|
||||||
["text/plain"] = true,
|
|
||||||
["application/x-font-ttf"] = true,
|
|
||||||
["application/xhtml+xml"] = true,
|
|
||||||
["application/xml"] = true,
|
|
||||||
["application/zip"] = true,
|
|
||||||
["image/svg+xml"] = true,
|
|
||||||
["application/vnd.ms-fontobject"] = true,
|
|
||||||
["application/x-font-woff"] = true,
|
|
||||||
["application/x-font-otf"] = true,
|
|
||||||
["audio/mpeg"] = true,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
HEADER_FLAG = false
|
|
||||||
|
|
||||||
function html()
|
|
||||||
if not HEADER_FLAG then
|
|
||||||
std.chtml(SESSION)
|
|
||||||
HEADER_FLAG = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function import(module)
|
|
||||||
return require(BASE_FRW.."silk.api."..module)
|
|
||||||
end
|
|
@ -1,54 +0,0 @@
|
|||||||
|
|
||||||
-- the rewrite rule for the framework
|
|
||||||
-- should be something like this
|
|
||||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
|
||||||
-- some global variables
|
|
||||||
DIR_SEP = "/"
|
|
||||||
WWW_ROOT = "/opt/www/htdocs/apps"
|
|
||||||
HTTP_ROOT = "https://apps.localhost:9195/"
|
|
||||||
-- class path: path.to.class
|
|
||||||
BASE_FRW = ""
|
|
||||||
-- class path: path.to.class
|
|
||||||
CONTROLLER_ROOT = BASE_FRW.."apps.controllers"
|
|
||||||
MODEL_ROOT = BASE_FRW.."apps.models"
|
|
||||||
-- file path: path/to/file
|
|
||||||
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
|
||||||
LOG_ROOT = WWW_ROOT..DIR_SEP.."logs"
|
|
||||||
|
|
||||||
-- require needed library
|
|
||||||
require(BASE_FRW.."silk.api")
|
|
||||||
|
|
||||||
-- registry object store global variables
|
|
||||||
local REGISTRY = {}
|
|
||||||
-- set logging level
|
|
||||||
REGISTRY.logger = Logger:new{ levels = {INFO = true, ERROR = true, DEBUG = true}}
|
|
||||||
REGISTRY.db = DBHelper:new{db="iosapps"}
|
|
||||||
REGISTRY.layout = 'default'
|
|
||||||
|
|
||||||
REGISTRY.db:open()
|
|
||||||
local router = Router:new{registry = REGISTRY}
|
|
||||||
REGISTRY.router = router
|
|
||||||
router:setPath(CONTROLLER_ROOT)
|
|
||||||
--router:route('edit', 'post/edit', "ALL" )
|
|
||||||
|
|
||||||
-- example of depedencies to the current main route
|
|
||||||
-- each layout may have different dependencies
|
|
||||||
local default_routes_dependencies = {
|
|
||||||
edit = {
|
|
||||||
url = "post/edit",
|
|
||||||
visibility = {
|
|
||||||
shown = true,
|
|
||||||
routes = {
|
|
||||||
["post/index"] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
--category = {
|
|
||||||
-- url = "cat/index",
|
|
||||||
-- visibility = "ALL"
|
|
||||||
--}
|
|
||||||
}
|
|
||||||
router:route('default', default_routes_dependencies )
|
|
||||||
router:delegate()
|
|
||||||
if REGISTRY.db then REGISTRY.db:close() end
|
|
||||||
|
|
@ -21,7 +21,15 @@ class QuickTalk {
|
|||||||
this.instant_compose.parentNode.removeChild(this.instant_compose);
|
this.instant_compose.parentNode.removeChild(this.instant_compose);
|
||||||
}
|
}
|
||||||
this.instant_compose = this.compose(editor, 0, true, (data) => {
|
this.instant_compose = this.compose(editor, 0, true, (data) => {
|
||||||
this.show_comment(container, data, true).scrollIntoView();
|
this.show_comment(container, data, true);
|
||||||
|
if(this.options.page)
|
||||||
|
{
|
||||||
|
this.options.page.scrollTop = this.options.page.scrollHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.scrollTo(0, document.body.scrollHeight);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -199,9 +207,9 @@ class QuickTalk {
|
|||||||
container.appendChild(preview);
|
container.appendChild(preview);
|
||||||
container.appendChild(footer);
|
container.appendChild(footer);
|
||||||
at.appendChild(container);
|
at.appendChild(container);
|
||||||
if (this.options.page) {
|
//if (this.options.page) {
|
||||||
this.options.page.scrollTop = container.offsetTop - this.options.page.offsetTop;
|
// this.options.page.scrollTop = container.offsetTop - this.options.page.offsetTop;
|
||||||
}
|
//}
|
||||||
|
|
||||||
//container.scrollIntoView();
|
//container.scrollIntoView();
|
||||||
return container;
|
return container;
|
||||||
@ -228,6 +236,7 @@ class QuickTalk {
|
|||||||
show_comment(at, comment, show_footer) {
|
show_comment(at, comment, show_footer) {
|
||||||
let container = document.createElement("div");
|
let container = document.createElement("div");
|
||||||
container.setAttribute("class", "quick-talk-comment");
|
container.setAttribute("class", "quick-talk-comment");
|
||||||
|
container.setAttribute("id", "comment-" + comment.id);
|
||||||
let header = document.createElement("div");
|
let header = document.createElement("div");
|
||||||
header.setAttribute("class", "quick-talk-comment-header");
|
header.setAttribute("class", "quick-talk-comment-header");
|
||||||
let username = document.createElement("span");
|
let username = document.createElement("span");
|
||||||
@ -265,7 +274,8 @@ class QuickTalk {
|
|||||||
this.instant_compose.parentNode.removeChild(this.instant_compose);
|
this.instant_compose.parentNode.removeChild(this.instant_compose);
|
||||||
}
|
}
|
||||||
this.instant_compose = this.compose(editor, parseInt(comment.id), true, (data) => {
|
this.instant_compose = this.compose(editor, parseInt(comment.id), true, (data) => {
|
||||||
this.show_comment(sub_comments, data, false).scrollIntoView();
|
this.show_comment(sub_comments, data, false);
|
||||||
|
//.scrollIntoView();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
container.appendChild(footer);
|
container.appendChild(footer);
|
||||||
|
@ -10,15 +10,65 @@ local function process_md(input)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function sendmail(to, subject, content)
|
local function sendmail(to, subject, content)
|
||||||
local from = "From: contact@iohub.dev\nTo: " .. to .. "\n"
|
LOG_DEBUG("Sending email to %s", to)
|
||||||
local suject = "Subject: " .. subject .. "\n"
|
|
||||||
|
|
||||||
local cmd = 'echo "' .. utils.escape(from .. suject .. content) ..
|
local setting = JSON.decodeFile(SMTP_SETTING)
|
||||||
'"| sendmail ' .. to
|
|
||||||
local r = os.execute(cmd)
|
local socket = require 'socket'
|
||||||
|
local smtp = require 'socket.smtp'
|
||||||
|
local ssl = require 'ssl'
|
||||||
|
local https = require 'ssl.https'
|
||||||
|
local ltn12 = require 'ltn12'
|
||||||
|
|
||||||
|
local sslCreate = function()
|
||||||
|
local sock = socket.tcp()
|
||||||
|
return setmetatable({
|
||||||
|
connect = function(_, host, port)
|
||||||
|
local r, e = sock:connect(host, port)
|
||||||
|
if not r then return r, e end
|
||||||
|
sock = ssl.wrap(sock, {mode='client', protocol='tlsv1_2'})
|
||||||
|
return sock:dohandshake()
|
||||||
|
end
|
||||||
|
}, {
|
||||||
|
__index = function(t,n)
|
||||||
|
return function(_, ...)
|
||||||
|
return sock[n](sock, ...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local setting = JSON.decodeFile(SMTP_SETTING)
|
||||||
|
|
||||||
|
if not setting then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local from = "contact@iohub.dev"
|
||||||
|
|
||||||
|
local msg = {
|
||||||
|
headers = {
|
||||||
|
from = string.format("QuickTalk <%s>", from),
|
||||||
|
to = string.format("%s <%s>",to,to),
|
||||||
|
subject = subject
|
||||||
|
},
|
||||||
|
body = content
|
||||||
|
}
|
||||||
|
LOG_INFO("Send mail on server %s user %s port %d: %s", setting.server, setting.user, setting.port, JSON.encode(msg))
|
||||||
|
local ok, err = smtp.send {
|
||||||
|
from = string.format("<%s>",from),
|
||||||
|
rcpt = string.format('<%s>', to),
|
||||||
|
source = smtp.message(msg),
|
||||||
|
user = setting.user,
|
||||||
|
password = setting.password,
|
||||||
|
server = setting.server,
|
||||||
|
port = math.floor(setting.port),
|
||||||
|
create = sslCreate
|
||||||
|
}
|
||||||
|
|
||||||
|
if not ok then return false end
|
||||||
|
return true
|
||||||
|
|
||||||
if r then return true end
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
function CommentController:index(...)
|
function CommentController:index(...)
|
||||||
if (REQUEST.method == "OPTIONS") then
|
if (REQUEST.method == "OPTIONS") then
|
||||||
@ -31,17 +81,17 @@ function CommentController:index(...)
|
|||||||
end
|
end
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
local rq = (JSON.decodeString(REQUEST.json))
|
||||||
if (rq) then
|
if (rq) then
|
||||||
local pages, order = self.pages:find({exp = {["="] = {uri = rq.page}}})
|
local pages, order = self.pages:find({where = {uri = rq.page}})
|
||||||
if not pages or #order == 0 then
|
if not pages or #order == 0 then
|
||||||
fail("Be the first to comment")
|
fail("Be the first to comment")
|
||||||
else
|
else
|
||||||
local pid = pages[1].id
|
local pid = pages[1].id
|
||||||
local comments, order = self.comment:find(
|
local comments, order = self.comment:find({
|
||||||
{
|
where = {
|
||||||
exp = {
|
pid = pid,
|
||||||
["and"] = {{["="] = {pid = pid}}, {[" = "] = {rid = 0}}}
|
rid = 0
|
||||||
},
|
},
|
||||||
order = {time = "ASC"},
|
order = {"time$asc"},
|
||||||
fields = {"id", "time", "name", "rid", "pid", "content"}
|
fields = {"id", "time", "name", "rid", "pid", "content"}
|
||||||
})
|
})
|
||||||
if not comments or #order == 0 then
|
if not comments or #order == 0 then
|
||||||
@ -53,17 +103,14 @@ function CommentController:index(...)
|
|||||||
data.children = {}
|
data.children = {}
|
||||||
-- find all the replies to this thread
|
-- find all the replies to this thread
|
||||||
local sub_comments, suborder =
|
local sub_comments, suborder =
|
||||||
self.comment:find(
|
self.comment:find({
|
||||||
{
|
where = {
|
||||||
exp = {
|
pid = pid,
|
||||||
["and"] = {
|
rid = data.id
|
||||||
{["="] = {pid = pid}},
|
},
|
||||||
{[" = "] = {rid = data.id}}
|
order = {"time$asc"}
|
||||||
}
|
|
||||||
},
|
|
||||||
order = {time = "ASC"}
|
|
||||||
|
|
||||||
})
|
})
|
||||||
if sub_comments and #suborder ~= 0 then
|
if sub_comments and #suborder ~= 0 then
|
||||||
for i, subc in pairs(suborder) do
|
for i, subc in pairs(suborder) do
|
||||||
sub_comments[subc].content =
|
sub_comments[subc].content =
|
||||||
@ -92,7 +139,7 @@ function CommentController:post(...)
|
|||||||
end
|
end
|
||||||
local rq = (JSON.decodeString(REQUEST.json))
|
local rq = (JSON.decodeString(REQUEST.json))
|
||||||
if rq then
|
if rq then
|
||||||
local pages, order = self.pages:find({exp = {["="] = rq.page}})
|
local pages, order = self.pages:find({where = rq.page})
|
||||||
if not pages or #order == 0 then
|
if not pages or #order == 0 then
|
||||||
-- insert data
|
-- insert data
|
||||||
if self.pages:create(rq.page) then
|
if self.pages:create(rq.page) then
|
||||||
@ -120,19 +167,27 @@ function CommentController:post(...)
|
|||||||
".\nBest regards,\nEmail automatically sent by QuickTalk API")
|
".\nBest regards,\nEmail automatically sent by QuickTalk API")
|
||||||
end
|
end
|
||||||
-- send mail to all users of current page
|
-- send mail to all users of current page
|
||||||
local cmts, cmti = self.comment:select("MIN(id) as id,email",
|
local cmts, cmti = self.comment:find(
|
||||||
"pid=" .. rq.comment.pid ..
|
{
|
||||||
" AND email != '" ..
|
where = {
|
||||||
rq.comment.email ..
|
pid = rq.comment.pid,
|
||||||
"' GROUP BY email")
|
["email$ne"] = rq.comment.email
|
||||||
|
},
|
||||||
|
fields = {"id", "email"}
|
||||||
|
})
|
||||||
|
-- check duplicate email
|
||||||
if cmts and #cmti > 0 then
|
if cmts and #cmti > 0 then
|
||||||
|
local sent = {}
|
||||||
for idx, v in pairs(cmti) do
|
for idx, v in pairs(cmti) do
|
||||||
sendmail(cmts[v].email, rq.comment.name ..
|
if not sent[cmts[v].email] then
|
||||||
|
sendmail(cmts[v].email, rq.comment.name ..
|
||||||
" has written something on a page that you've commented on",
|
" has written something on a page that you've commented on",
|
||||||
rq.comment.name ..
|
rq.comment.name ..
|
||||||
" has written something on a page that you've commented. \nPlease visit this page: " ..
|
" has written something on a page that you've commented. \nPlease visit this page: " ..
|
||||||
rq.page.uri ..
|
rq.page.uri..
|
||||||
" for updates on the discussion.\nBest regards,\nEmail automatically sent by QuickTalk API")
|
" for updates on the discussion.\nBest regards,\nEmail automatically sent by QuickTalk API")
|
||||||
|
sent[cmts[v].email] = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rq.comment.email = ""
|
rq.comment.email = ""
|
||||||
|
@ -2,6 +2,14 @@
|
|||||||
-- should be something like this
|
-- should be something like this
|
||||||
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
-- ^\/apps\/+(.*)$ = /apps/router.lua?r=<1>&<query>
|
||||||
-- some global variables
|
-- some global variables
|
||||||
|
package.path = _SERVER["LIB_DIR"].."/lua/?.lua"
|
||||||
|
require("silk.api")
|
||||||
|
-- crypto lib
|
||||||
|
enc = require("enc")
|
||||||
|
WWW_ROOT = __ROOT__.."/talk"
|
||||||
|
DB_LOC="/opt/www/databases"
|
||||||
|
DB_FILE = DB_LOC.."/quicktalk.db"
|
||||||
|
SMTP_SETTING=DB_LOC.."/smtp.json"
|
||||||
function fail(msg)
|
function fail(msg)
|
||||||
std.json()
|
std.json()
|
||||||
std.t(JSON.encode({error = msg}))
|
std.t(JSON.encode({error = msg}))
|
||||||
@ -12,34 +20,27 @@ function result(obj)
|
|||||||
std.t(JSON.encode({result = obj, error = false}))
|
std.t(JSON.encode({result = obj, error = false}))
|
||||||
end
|
end
|
||||||
DIR_SEP = "/"
|
DIR_SEP = "/"
|
||||||
WWW_ROOT = __ROOT__ .. "/talk"
|
|
||||||
if HEADER.Host then
|
if HEADER.Host then
|
||||||
HTTP_ROOT = "https://" .. HEADER.Host
|
HTTP_ROOT = "https://" .. HEADER.Host
|
||||||
else
|
else
|
||||||
HTTP_ROOT = "https://talk.iohub.dev"
|
HTTP_ROOT = "https://talk.iohub.dev"
|
||||||
end
|
end
|
||||||
-- class path: path.to.class
|
|
||||||
BASE_FRW = ""
|
|
||||||
-- class path: path.to.class
|
|
||||||
CONTROLLER_ROOT = BASE_FRW .. "talk.controllers"
|
|
||||||
MODEL_ROOT = BASE_FRW .. "talk.models"
|
|
||||||
-- file path: path/to/file
|
|
||||||
VIEW_ROOT = WWW_ROOT .. DIR_SEP .. "views"
|
|
||||||
LOG_ROOT = WWW_ROOT .. DIR_SEP .. "logs"
|
|
||||||
|
|
||||||
-- require needed library
|
-- class path: path.to.class
|
||||||
require(BASE_FRW .. "silk.api")
|
CONTROLLER_ROOT = "talk.controllers"
|
||||||
|
MODEL_ROOT = "talk.models"
|
||||||
|
-- file path: path/to/file
|
||||||
|
VIEW_ROOT = WWW_ROOT..DIR_SEP.."views"
|
||||||
|
|
||||||
|
|
||||||
-- registry object store global variables
|
-- registry object store global variables
|
||||||
local REGISTRY = {}
|
local REGISTRY = {}
|
||||||
-- set logging level
|
-- set logging level
|
||||||
REGISTRY.logger = Logger:new{
|
REGISTRY.logger = Logger:new{ level = Logger.INFO}
|
||||||
levels = {INFO = false, ERROR = false, DEBUG = false}
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTRY.layout = 'default'
|
REGISTRY.layout = 'default'
|
||||||
REGISTRY.fileaccess = true
|
REGISTRY.fileaccess = true
|
||||||
REGISTRY.db = DBHelper:new{db = "quicktalk"}
|
REGISTRY.db = DBModel:new{db = DB_FILE}
|
||||||
REGISTRY.db:open()
|
REGISTRY.db:open()
|
||||||
|
|
||||||
local router = Router:new{registry = REGISTRY}
|
local router = Router:new{registry = REGISTRY}
|
||||||
|
Reference in New Issue
Block a user