1
0
mirror of https://github.com/lxsang/antd-web-apps synced 2025-07-16 13:59:52 +02:00

Compare commits

..

71 Commits

Author SHA1 Message Date
ce05d67166 Merge branch 'master' of github.com:lxsang/antd-web-apps 2022-08-19 15:11:16 +02:00
ce99218348 update OS backend 2022-08-19 15:10:55 +02:00
e66336fe56 Update Jenkinsfile 2022-08-02 12:04:31 +02:00
5abcc47679 Update README.md 2022-08-01 18:38:02 +02:00
72102481d8 Create Jenkinsfile 2022-08-01 18:34:27 +02:00
0d1312e1ba Update README.md 2022-08-01 18:29:43 +02:00
820899a420 change footer author 2022-07-22 18:39:57 +02:00
9b519ffb46 add dany to supported user 2022-07-20 12:46:02 +02:00
bac46c691a regen ar 2022-07-19 20:54:21 +02:00
b2383f8113 resolve conflict 2022-07-19 20:54:01 +02:00
62435e387e add mime entry 2022-07-19 20:53:01 +02:00
1e5f988f73 regen ar 2022-07-06 10:50:58 +02:00
1ed7bf91ae update zero conf script 2022-07-06 10:50:44 +02:00
fd4b1064f5 Merge branch 'master' of github.com:lxsang/antd-web-apps 2022-03-29 13:56:04 +02:00
beb24048d8 add blog link 2022-03-29 13:55:31 +02:00
cd914ad8c0 update component version 2022-02-23 10:00:03 +01:00
64a84f045d update component version 2022-02-23 09:59:38 +01:00
6511f15d5b fix send file bug 2022-02-23 09:21:06 +01:00
e7ee8cd889 minor site update 2022-02-15 11:04:17 +01:00
b5bee06263 fix incorrect path bug 2022-02-10 10:18:03 +01:00
64f02d4614 add missing files to makefile 2022-02-10 10:04:43 +01:00
87e8ba94b4 move ai scripts to blog source 2022-02-10 09:57:21 +01:00
e35e8852b9 improve antos system API 2021-12-06 11:18:05 +01:00
bb88057ec1 allow single multiple upload file 2021-11-21 19:13:06 +01:00
175f952f23 allow user identification in publisher 2021-10-27 15:42:30 +02:00
51dfab7ca3 regen ar 2021-10-24 19:51:28 +02:00
701983e0fb Update common.lua 2021-10-04 22:33:16 +02:00
1f3f18b83e Update .drone.yml 2021-10-04 22:16:55 +02:00
a44d12fa03 update keychain if exists 2021-10-04 22:10:14 +02:00
4479c9a882 force connection close 2021-05-06 20:39:10 +02:00
30abb9552f update jquery 2021-04-26 12:26:36 +02:00
1bac21078c Update layout.ls 2021-04-22 09:23:42 +02:00
e317131f0a fix info pages 2021-04-21 18:16:05 +02:00
7ceeee20e2 add mime support to backend 2021-04-19 21:04:46 +02:00
9b05222488 update user view 2021-04-04 20:13:50 +02:00
0cef9754eb update dist file 2021-04-01 10:25:42 +02:00
32c120640d Merge branch 'master' of github.com:lxsang/antd-web-apps 2021-03-24 11:47:40 +01:00
081c924e18 update script 2021-03-24 11:47:29 +01:00
45d0d7af1e Update .drone.yml 2021-03-19 11:03:21 +01:00
35bade9d61 Update antos.sh 2021-03-17 18:46:12 +01:00
22f700e54b improve antos zero-conf script 2021-03-15 20:52:18 +01:00
1bc95188e1 regen dist 2021-03-13 20:56:32 +01:00
effcedb94e clean up 2021-03-06 13:09:48 +01:00
dcb3803120 add only office to doc 2021-03-05 17:40:20 +01:00
4f80957ece add support mars 2026 mission banner 2021-02-26 19:10:06 +01:00
e85d550b12 minor ui fix 2021-02-24 18:06:16 +01:00
e8f3fc2c83 enhance book display 2021-02-19 19:37:26 +01:00
f457924d38 fix typo + layout 2021-02-19 18:24:32 +01:00
0765d22913 allow to set threadhold to top similarity post 2021-02-18 18:57:16 +01:00
7eb9746dc7 allow to set threadhold to top similarity post 2021-02-18 18:54:16 +01:00
af802822e6 add streng force to link 2021-02-18 18:07:25 +01:00
d416ae36c8 add explore mode to blog 2021-02-17 12:59:42 +01:00
bf52f93e27 fix mime pattern bug 2021-02-11 13:46:33 +01:00
74b02e66bd fix mime pattern bug 2021-02-11 13:45:40 +01:00
7f9e8bfbaa add more mimes 2021-02-09 18:34:16 +01:00
d135587c3b improve OS API 2021-02-09 01:30:08 +01:00
83288a00b0 fix home page render error 2021-02-07 14:23:02 +01:00
143094d301 enable mobile compatibility 2021-02-07 13:52:06 +01:00
7f29ab310a enable mobile compatibility 2021-02-07 13:44:15 +01:00
ecd409778a enable mobile compatibility 2021-02-07 13:25:18 +01:00
96624d2cbd enable mobile compatibility 2021-02-07 13:23:46 +01:00
9fce4a3fd5 enable mobile compatibility 2021-02-07 12:36:52 +01:00
8d0f9d3480 enable mobile compatibility 2021-02-07 11:56:53 +01:00
8427f00aa0 enable mobile compatibility 2021-02-07 11:54:13 +01:00
e2f8bd8116 enable mobile compatibility 2021-02-07 11:52:46 +01:00
c09799ba08 mobile page width change to 95% 2021-02-07 11:51:27 +01:00
3f1530cfe8 enable mobile compatibility 2021-02-07 11:35:26 +01:00
0c6d81ef93 Update .drone.yml 2021-01-31 19:43:04 +01:00
6addbc88ca Create .drone.yml 2021-01-29 21:55:49 +01:00
4d62cee7af fix file path 2021-01-22 14:49:13 +01:00
87169dd41f display photo 2021-01-05 19:12:35 +00:00
44 changed files with 1563 additions and 273 deletions

22
.drone.yml Normal file
View 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

49
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,49 @@
def remote = [:]
remote.name = 'workstation'
remote.host = 'workstation'
remote.user = 'dany'
remote.identityFile = '/var/jenkins_home/.ssh/id_rsa'
remote.allowAnyHosts = true
remote.agent = false
remote.logLevel = 'INFO'
pipeline{
agent { node{ label'master' }}
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 {
sshCommand remote: remote, command: '''
set -e
export WORKSPACE=$(realpath "./jenkins/workspace/antd-web-apps")
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
//}
}
}
}
}
}

View File

@ -1,12 +1,12 @@
BUILDDIR?=./build BUILDDIR?=./build
PROJS?=grs info blog os doc ci talk get PROJS?=grs info blog os doc talk get
copyfiles = index.ls mimes.json copyfiles = index.ls 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

View File

@ -1,2 +1,2 @@
# antd-web-apps # antd-web-apps
Some web apps for antd server Various web apps for antd server

View File

@ -1,4 +1,4 @@
copyfiles = assets views models controllers router.lua copyfiles = assets views models ai controllers router.lua
main: main:
- mkdir -p $(BUILDDIR) - mkdir -p $(BUILDDIR)

345
blog/ai/cluster.lua Normal file
View File

@ -0,0 +1,345 @@
local doclassify = {}
local st = require("stmr")
doclassify.bow = function(data, stopwords)
-- first step get a table of worlds that contain
-- world: occurences
local bag = {}
for w in data:gmatch('%w+') do
local word = w:lower()
if not stopwords[word] then
word = st.stmr(word)
if bag[word] then
bag[word].count = bag[word].count + 1
else
bag[word] = {count=0, tf=0, tfidf=0.0}
bag[word].count = 1
end
end
end
-- now calculate the tf of the bag
for k,v in pairs(bag) do
bag[k].tf = math.log(1 + bag[k].count)
end
return bag
end
doclassify.len = function(table)
local cnt = 0
for k,v in pairs(table) do cnt = cnt+1 end
return cnt
end
doclassify.tfidf = function(documents)
-- now for each term in a bag, calculate
-- the inverse document frequency, which
-- is a measure of how much information
-- the word provides, that is, whether the
-- term is common or rare across all documents
local ndoc = doclassify.len(documents)
for k,bag in pairs(documents) do
-- for eacht term in bag
-- calculate its idf across all documents
for term,b in pairs(bag) do
local n = 0
for id,doc in pairs(documents) do
if doc[term] then n = n+1 end
end
--echo("term:"..term.." appears in"..n.." documents")
b.tfidf = b.tf*math.log(ndoc/n)
end
end
end
doclassify.search = function(term, documents)
local r = {}
for id, doc in pairs(documents) do
if doc[term:lower()] then
r[id] = doc[term].tfidf
end
end
return r
end
doclassify.get_vectors = function(documents)
-- get a list of vector from documents
local index = 0
local vectors = {}
local maps = {}
local terms = {}
local maxv = 0
for id in pairs(documents) do
maps[id] = {}
vectors[id] = {}
end
-- first loop, get the term
for id, doc in pairs(documents) do
for k,v in pairs(doc) do
-- get max value
if v.tfidf > maxv then
maxv = v.tfidf
end
-- get the term
if not terms[k] then
index = index + 1
terms[k] = index
end
for pid in pairs(documents) do
if not maps[pid][k] then
if id == pid then
maps[pid][k] = v.tfidf
else
maps[pid][k] = 0
end
else
if maps[pid][k] == 0 and id == pid then
maps[pid][k] = v.tfidf
end
end
end
end
end
-- reindexing the vectors
for id in pairs(documents) do
for k,v in pairs(maps[id]) do
vectors[id][terms[k]] = v
end
end
--echo("Max tfidf "..maxv.." in document #"..maxid.." of term "..term)
return vectors, maxv, index, terms
end
doclassify.similarity = function(va, vb)
-- using cosin similarity
local dotp = 0
local maga = 0
local magb = 0
for k = 1,#va do
dotp = dotp + va[k]*vb[k]
maga = maga + va[k]*va[k]
magb = magb + vb[k]*vb[k]
end
maga = math.sqrt(maga)
magb = math.sqrt(magb)
local d = 0
if maga ~= 0 and magb ~= 0 then
d = dotp/ (magb*maga)
end
return d
end
doclassify.similarities = function(v1, collection)
local similarities = {}
assert(#v1 == #(collection[1]), "Incorrect vectors size")
for i=1,#collection do
similarities[i] = doclassify.similarity(v1, collection[i])
end
return similarities
end
doclassify.mean_similarity = function(v1, v2)
assert(#v1 == #v2, "Incorrect vectors size")
local similarities = {}
for i = 1,#v1 do similarities[i] = doclassify.similarity(v1[i], v2[i]) end
return doclassify.mean(similarities)
end
doclassify.similarity_chart = function(id, vectors)
local vs = {}
local cnt = 0
local lut = {}
for k,v in pairs(vectors) do
if k ~= id then
cnt = cnt + 1
vs[cnt] = v
lut[cnt] = k
end
end
if not vs[1] then return {} end
return doclassify.similarities(vectors[id], vs), lut
end
doclassify.top_similarity = function(id, vectors, n, th)
local chart,lut = doclassify.similarity_chart(id,vectors)
--echo(JSON.encode(chart))
--echo(JSON.encode(lut))
if not lut or #lut <= 0 then return nil end
local top = {}
local j=0
local goon = true
if not th then
goon = false
end
while j < n or goon
do
local i,maxv = doclassify.argmax(chart)
top[lut[i]] = maxv
chart[i] = 0.0
j=j+1
if maxv < th and goon then
goon = false
end
end
--for j=1,n do
-- local i,maxv = doclassify.argmax(chart)
-- top[lut[i]] = maxv
-- chart[i] = 0.0
--end
return top
end
doclassify.save_vectors = function(vectors, name)
local f = io.open(name,"w")
if f == nil then return false end
for id, v in pairs(vectors) do
f:write(id)
for i=1,#v do f:write(","..v[i]) end
f:write("\n")
end
f:close()
return true
end
doclassify.save_topchart = function(vectors, name,n)
local f = io.open(name,"w")
if f == nil then return false end
for k,v in pairs(vectors) do
local top = doclassify.top_similarity(k,vectors,n, 0.1)
for a,b in pairs(top) do
f:write(k.." "..a.." "..b.."\n")
end
end
f:close()
return true
end
doclassify.kmean = function(nclass, documents, maxstep, ids)
-- now
local vectors, maxv, size = doclassify.get_vectors(documents)
-- random centroids
local centroids = {}
local old_centroids = {}
local clusters = {}
--for pid in pairs(documents) do clusters[pid] = 0 end
-- add noise to mean_vector
for i = 1,nclass do
if ids == nil then
centroids[i] = doclassify.random(size,math.floor(maxv))
else
centroids[i] = vectors[ids[i]]
end
old_centroids[i] = doclassify.zeros(size)
end
-- loop until convergence or maxstep reached
local similarity = doclassify.mean_similarity(centroids, old_centroids)
local step = maxstep
while 1.0-similarity > 1e-9 and step > 0 do
clusters = {}
--echo(JSON.encode(centroids))
for id,v in pairs(vectors) do
local similarities = doclassify.similarities(v, centroids)
--echo(JSON.encode(similarities))
local cluster, maxvalue = doclassify.argmax(similarities)
--echo("doc #"..id.." is in clusters #"..cluster.." max value is "..maxvalue)
clusters[id] = cluster
end
-- storing the old centroids
old_centroids = centroids
-- calculate new centroids
local new_centroids = {}
for class in pairs(centroids) do
local cnt = 0
local cvectors = {}
for id,v in pairs(vectors) do
if clusters[id] == class then
cnt = cnt + 1
cvectors[cnt] = v
end
end
new_centroids[class] = doclassify.mean_vector(cvectors, size)
end
centroids = new_centroids
--echo(JSON.encode(centroids))
--echo(JSON.encode(old_centroids))
similarity = doclassify.mean_similarity(centroids, old_centroids)
echo("step #"..step..", similarity "..similarity)
step = step - 1
end
local results = {}
for i = 1,nclass do
local list = {}
local cnt = 0
for id,c in pairs(clusters) do
if c == i then
cnt = cnt + 1
list[cnt] = id
end
end
results[i] = list
end
return results, clusters, centroids
end
doclassify.zeros = function(n)
local vector = {}
for i = 1,n do vector[i] = 0.0 end
return vector
end
doclassify.random = function(n,maxv)
local vector = {}
for i=1,n do
vector[i] = math.random() + math.random(0, maxv)
end
return vector
end
doclassify.sum = function(v)
local sum = 0.0
for i=1,#v do sum = sum + v[i] end
return sum
end
doclassify.mean = function(v)
return doclassify.sum(v)/#v
end
doclassify.mean_vector = function(vectors, size)
local means = doclassify.zeros(size)
if not vectors or #vectors == 0 then return means end
--local size = #(vectors[1])
local times = 0
for k,v in pairs(vectors) do
for i=1,#v do means[i] = means[i] + v[i] end
times = times + 1
end
for i = 1,size do means[i] = means[i]/times end
return means
end
doclassify.argmin = function(v)
local minv = 0.0
local mini = 0.0
for i = 1,#v do
if v[i] <= minv then
mini = i
minv = v[i]
end
end
--echo("min index"..mini.." val "..minv)
return mini, minv
end
doclassify.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
return doclassify

29
blog/ai/gettext.lua Normal file
View File

@ -0,0 +1,29 @@
local gettext = {}
require("sqlite")
gettext.get = function(q)
local db = require("os.libs.dbmodel").get("mrsang","blogs",nil)
if not db then return nil end
local exp = {["="] =q}
local cond = {
exp = exp,
fields = {"id", "content"}
}
local data, sort = db:find(cond)
db:close()
if not data or #data == 0 then return nil end
--for k,v in pairs(data) do
-- data[k].content = bytes.__tostring(std.b64decode(data[k].content)):gsub("%%","%%%%")
--end
return data
end
gettext.stopwords = function(ospath)
--local ospath = require("fs/vfs").ospath(path)
local words = {}
for line in io.lines(ospath) do
words[line] = true
end
return words
end
return gettext

151
blog/ai/stopwords.txt Normal file
View File

@ -0,0 +1,151 @@
i
me
my
myself
we
our
ours
ourselves
you
your
yours
yourself
yourselves
he
him
his
himself
she
her
hers
herself
it
its
itself
they
them
their
theirs
themselves
what
which
who
whom
this
that
these
those
am
is
are
was
were
be
been
being
have
has
had
having
do
does
did
doing
a
an
the
and
but
if
or
because
as
until
while
of
at
by
for
with
about
against
between
into
through
during
before
after
above
below
to
from
up
down
in
out
on
off
over
under
again
further
then
once
here
there
when
where
why
how
all
any
both
each
few
more
most
other
some
such
no
nor
not
only
own
same
so
than
too
very
s
t
can
will
just
don
should
now
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
w
r
s
t
x
y
z

50
blog/ai/test.lua Normal file
View File

@ -0,0 +1,50 @@
local path = require("fs/vfs").ospath("home://aiws/blog-clustering")
local gettext = loadfile(path.."/gettext.lua")()
local cluster = loadfile(path.."/cluster.lua")()
local refresh = false
local file = "/home/mrsang/test.csv"
if refresh then
local data = gettext.get({publish=1})
local documents = {}
if data then
local sw = gettext.stopwords("home://aiws/blog-clustering/stopwords.txt")
for k,v in pairs(data) do
local bag = cluster.bow(data[k].content, sw)
documents[data[k].id] = bag
end
cluster.tfidf(documents)
--local v = cluster.search("arm", documents)
--echo(JSON.encode(v))
local vectors, maxv, size = cluster.get_vectors(documents)
local s = cluster.save_topchart(vectors,file, 3)
if s then echo("file saved") else echo("error save file") end
--echo(JSON.encode(r))
--r = cluster.similarity(vectors["14"],vectors["16"])
--echo("Similarity "..r)
--local c,l = cluster.kmean(3, documents, 10)
--echo(JSON.encode(c))
--echo(JSON.encode(l))
else
echo("Data missing")
end
else
local f = io.open(file,"r")
local result = {}
for line in f:lines() do
local arr = {}
local cnt = 0
for i in line:gmatch( "%S+") do
cnt = cnt + 1
arr[cnt] = i
end
if not result[arr[1]] then result[arr[1]] = {} end
result[arr[1]][arr[2]] = tonumber(arr[3])
end
f:close()
echo(JSON.encode(result))
--local r = cluster.top_similarity("2",vectors, 3)
--echo(JSON.encode(r))
end

220
blog/assets/graph.js Normal file
View 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);
});
});

View File

@ -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);

View File

@ -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;

View File

@ -88,6 +88,43 @@ 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 = 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 = pid}})
if not data or #order == 0 then if not data or #order == 0 then
@ -130,11 +167,57 @@ function PostController:actionnotfound(...)
return self:notfound("Action [" .. args[1] .. "] not found") return self:notfound("Action [" .. args[1] .. "] not found")
end end
function PostController:graph_json(...)
local nodes = self.blog:find({exp= { ["="] = { publish = 1}}, fields = {"id", "title"}})
local output = { error = false, result = false }
local lut = {}
std.json()
if not nodes then
output.error = "No nodes found"
else
output.result = {
nodes = {},
links = {}
}
for k,v in ipairs(nodes) do
local title = v.title
output.result.nodes[k] = { id = tonumber(v.id), title = title }
end
-- get statistic links
local links = self.analytical:find({fields = {"pid", "sid", "score"}})
if links then
local i = 1
for k,v in ipairs(links) do
local link = { source = tonumber(v.pid), target = tonumber(v.sid), score = tonumber(v.score)}
local key = ""
if link.source < link.target then
key = v.pid..v.sid
else
key = v.sid..v.pid
end
key = std.sha1(key)
if not lut[key] then
output.result.links[i] = link
i = i + 1
lut[key] = true
end
end
end
end
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
end
function PostController:analyse(n) function PostController:analyse(n)
if not n then if not n then
n = 5 n = 5
end end
local path = "/home/mrsang/aiws/blog-clustering" local path = WWW_ROOT..DIR_SEP.."ai"
local gettext = loadfile(path .. "/gettext.lua")() local gettext = loadfile(path .. "/gettext.lua")()
local cluster = loadfile(path .. "/cluster.lua")() local cluster = loadfile(path .. "/cluster.lua")()
local data = gettext.get({publish = 1}) local data = gettext.get({publish = 1})
@ -153,7 +236,7 @@ function PostController:analyse(n)
self.analytical:delete({["="] = {["1"] = 1}}) self.analytical:delete({["="] = {["1"] = 1}})
-- get similarity and put to the table -- get similarity and put to the table
for id, v in pairs(vectors) do for id, v in pairs(vectors) do
local top = cluster.top_similarity(id, vectors, n) local top = cluster.top_similarity(id, vectors, tonumber(n), 0.1)
for a, b in pairs(top) do for a, b in pairs(top) do
local record = {pid = id, sid = a, score = b} local record = {pid = id, sid = a, score = b}
self.analytical:create(record) self.analytical:create(record)

View File

@ -27,7 +27,7 @@ function BlogModel:fetch(cnd, limit, order)
exp = {["and"] = exp }, exp = {["and"] = exp },
order = { ctime = "DESC" }, 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

View File

@ -5,6 +5,7 @@
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"
@ -27,8 +28,16 @@
<script src="https://chat.iohub.dev/assets/quicktalk.js"> </script> <script src="https://chat.iohub.dev/assets/quicktalk.js"> </script>
<script src="<?=HTTP_ROOT?>/rst/afx.js"> </script> <script src="<?=HTTP_ROOT?>/rst/afx.js"> </script>
<script src="<?=HTTP_ROOT?>/rst/gscripts/jquery-3.2.1.min.js"> </script> <script src="<?=HTTP_ROOT?>/rst/gscripts/jquery-3.4.1.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 ?>
<meta name="twitter:card" content="summary" /> <meta name="twitter:card" content="summary" />
@ -116,14 +125,14 @@
<div class = "logo"><a href = "https://lxsang.me"></a></div> <div class = "logo"><a href = "https://lxsang.me"></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 = "/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.lxsang.me" >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
@ -143,7 +152,7 @@
</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
</div> </div>
</body> </body>
</html> </html>

View 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>&nbsp;Close</a>
<a id="floating_btn_read_more" href="#"><i class="fa fa-chain"></i>&nbsp;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>

View File

@ -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"

View File

@ -1,6 +0,0 @@
copyfiles = router.lua scripts
main:
- mkdir -p $(BUILDDIR)
cp -rvf $(copyfiles) $(BUILDDIR)
- mkdir -p $(BUILDDIR)/log

View File

@ -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()

View File

@ -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"

View File

@ -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"

Binary file not shown.

4
doc/assets/office.css Normal file
View File

@ -0,0 +1,4 @@
html, body {
margin: 0;
height: 100%;
}

View File

@ -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;
} }

View File

@ -0,0 +1,91 @@
BaseController:subclass(
"OfficeController",
{
registry = {},
models = {}
}
)
local docType = function(ext)
if ext == "doc" or ext=="docx" or ext =="odt" then
return "word"
elseif ext == "csv" or ext =="ods" or ext== "xls" or ext == "xlsx" then
return "cell"
elseif ext == "odp" or ext == "ppt" or ext == "pptx" then
return "slide"
else
return "none"
end
end
function OfficeController:index(sid)
-- doing nothing here
require("sqlite")
local ospath = require("shared").ospath(sid)
local ext = ospath:match("^.+%.(.+)$")
local name = ospath:match("^.+/(.+)$")
if(ulib.exists(ospath)) then
local stat = ulib.file_stat(ospath)
if stat.error == nil then
local key = std.sha1(ospath..":"..stat.mtime)
self.template:set("shareid", sid)
self.template:set("ext", ext )
self.template:set("doctype", docType(ext) )
self.template:set("name", name)
self.template:set("key", key)
end
end
self:switchLayout("office")
return true
end
function OfficeController:shared(id)
require("sqlite")
require(BASE_FRW.."shared").get(id)
return false
end
function OfficeController:save(sid)
require("sqlite")
local ospath = require("shared").ospath(sid)
std.json()
local obj = {
error = false,
result = false
}
if not REQUEST.json then
obj.error = "Invalid request"
echo(JSON.encode(obj))
return false
end
local data = JSON.decodeString(REQUEST.json)
if not data then
obj.error = "Invalid request"
echo(JSON.encode(obj))
return false
end
if data.status == 2 then
local tmpfile = "/tmp/"..std.sha1(ospath)
local cmd = "curl -o "..tmpfile..' "'..data.url..'"'
os.execute(cmd)
-- move file to correct position
if ulib.exists(tmpfile) then
cmd = "mv "..tmpfile.." "..ospath
os.execute(cmd)
print("File "..ospath.." sync with remote")
else
obj.error = "Unable to sync file"
echo(JSON.encode(obj))
return false
end
end
obj.result = "OK"
echo(JSON.encode(obj))
return false
end
function OfficeController:actionnotfound(...)
self.template:setView("index")
return self:index(table.unpack({...}))
end

View File

@ -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, "![](" .. HTTP_ROOT .. "/" .. content = content:gsub(pattern, "![](" .. HTTP_ROOT .. "/" ..
obj.name .. "/asset/" .. apath .. ")") obj.name .. "/asset/" .. apath .. ")")
end end

View File

@ -22,6 +22,8 @@ POST_LIMIT = 10
-- require needed library -- require needed library
require(BASE_FRW.."silk.api") 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
if REQUEST.r then if REQUEST.r then

View File

@ -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..'/'..std.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..'/'..std.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..'/'..std.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..'/'..std.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

View File

@ -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="<?=HTTP_ROOT?>/rst/gscripts/jquery-3.4.1.min.js"> </script>
<?lua <?lua
if has_3d then if has_3d then
?> ?>
@ -77,28 +87,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 +109,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 if tocdata then ?>
<div id="doc_toc" class = "doc-toc" <?=show_toc_css?> >
<div class = "doc-name doc-toc-header">
<a href ="<?=HTTP_ROOT..'/'..tocdata.controller..'/'?>">
<?=tocdata.data.name?>
</a>
</div>
<?lua <?lua
if tocdata then if elinks then
for k,v in ipairs(elinks) do
?> ?>
<div class = "doc-toc"> <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 +138,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()
@ -144,7 +163,25 @@ local url = __main__:get("url")
Powered by antd server, (c) 2019 - <?=os.date("*t").year?> Xuan Sang LE Powered by antd server, (c) 2019 - <?=os.date("*t").year?> Xuan Sang 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 +228,7 @@ local url = __main__:get("url")
end end
?> ?>
}); });
toc_class_toggle();
</script> </script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,67 @@
<?lua
local shareid = __main__:get("shareid")
local ext = __main__:get("ext")
local doctype = __main__:get("doctype")
local name = __main__:get("name")
local key = __main__:get("key")
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link
rel="stylesheet"
type="text/css"
href="<?=HTTP_ROOT?>/assets/office.css" />
<!--meta name="viewport" content="width=device-width, initial-scale=1"-->
<script src="https://office.iohub.dev/web-apps/apps/api/documents/api.js"> </script>
<title>
<?lua
echo(name)
?>
</title>
<script type="text/javascript">
<?lua
if shareid and ext and doctype ~= "none" then
?>
window.onload = () => {
const editor = new DocsAPI.DocEditor("container", {
events: {
onAppReady: (e) => () => {},
//onRequestCreateNew: () => @newDocument(),
//onRequestSaveAs: (e) => @saveAs(e)
},
document: {
fileType: '<?=ext?>',
key: '<?=key?>',
title: '<?=name?>',
url: "https://doc.iohub.dev/office/shared/<?=shareid?>"
},
documentType: '<?=doctype?>',
editorConfig: {
//user: {
// id: @systemsetting.user.id.toString(),
// name: @systemsetting.user.username
//},
customization: {
compactHeader: false,
},
callbackUrl:"https://doc.iohub.dev/office/save/<?=shareid?>"
}
});
};
<?lua
end
?>
</script>
</head>
<body>
<div id = "container">
<?lua
if not shareid or not ext or doctype == "none" then
echo("<h1> Invalid document </h1>")
end
?>
</div>
</body>
</html>

View File

@ -33,7 +33,7 @@ function NotfoundController:index(...)
if ulib.exists(path) then if ulib.exists(path) then
std.header("text/plain") std.header("text/plain")
std.f(path) std.sendFile(path)
else else
self:error("No script found: "..path) self:error("No script found: "..path)
end end

View File

@ -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..."

View File

@ -19,7 +19,7 @@
<!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="grs/ubuntu-regular.css" />
@ -39,7 +39,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 +47,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>

View File

@ -53,7 +53,8 @@ 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) local mime = std.mimeOf(tmp_file)

View 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
print(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

View File

@ -26,7 +26,7 @@ require(BASE_FRW.."silk.api")
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{ levels = {INFO = false, ERROR = true, DEBUG = false}}
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.db = DBHelper:new{db=REGISTRY.user}
REGISTRY.layout = 'default' REGISTRY.layout = 'default'
@ -67,7 +67,7 @@ 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, ""), "/") REQUEST.r = std.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.db = DBHelper:new{db=REGISTRY.user}

View File

@ -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;
@ -170,3 +170,15 @@ hr{
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;
}

View File

@ -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

View File

@ -1,3 +1,7 @@
<div class="header_container">
<?lua if data.photo and data.photo ~= "" and data.user ~= "mrsang" then ?>
<img src="/<?=data.user?>/user/photo"></img>
<?lua end ?>
<h1> <h1>
<span class="name"><?=data.fullname?></span> <span class="name"><?=data.fullname?></span>
<span class="cv">Curriculum Vitae</span> <span class="cv">Curriculum Vitae</span>
@ -9,13 +13,14 @@
<span class="text"><?=data.Phone?></span> <span class="text"><?=data.Phone?></span>
<span class="fa fa-envelope-o"></span> <span class="fa fa-envelope-o"></span>
<span class="text"><?=data.email?></span> <span class="text"><?=data.email?></span>
<br/>
<span class="fa fa-globe"></span> <span class="fa fa-globe"></span>
<span class="text"><a href ="<?=data.url?>"><?=data.url?></a></span> <span class="text"><a href ="<?=data.url?>"><?=data.url?></a></span>
<?lua <?lua
if not preview then if not preview then
?> ?>
<span class="fa fa-file-pdf-o"></span> <span class="fa fa-file-pdf-o"></span>
<span class="text"><a href ="<?=HTTP_ROOT?>/<?=data.user?>/pdf" target="_blank">Download</a></span> <span class="text"><a href ="<?=HTTP_ROOT?>/<?=data.user?>/index/pdf" target="_blank">Download</a></span>
<?lua <?lua
end end
?> ?>
@ -25,3 +30,7 @@
<span><?=data.shortbiblio?></span> <span><?=data.shortbiblio?></span>
<span class="fa fa-quote-right"></span> <span class="fa fa-quote-right"></span>
</p> </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 ?>

View File

@ -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
@ -23,5 +66,13 @@
"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
} }
} }

View File

@ -98,6 +98,7 @@ function SystemController:application(...)
end end
function SystemController:apigateway(...) function SystemController:apigateway(...)
local args={...}
local use_ws = false local use_ws = false
if REQUEST and REQUEST.ws == "1" then if REQUEST and REQUEST.ws == "1" then
-- override the global echo command -- override the global echo command
@ -156,7 +157,9 @@ function SystemController:apigateway(...)
if use_ws then if use_ws then
if std.ws.enable() then if std.ws.enable() then
-- read header -- read header
local header = std.ws.header() local header = nil
-- wait until we receive request
while(not header) do header = std.ws.header() end
if header then if header then
if header.mask == 0 then if header.mask == 0 then
print("Data is not masked") print("Data is not masked")
@ -183,10 +186,19 @@ function SystemController:apigateway(...)
print("Web socket is not available.") print("Web socket is not available.")
end end
else else
if REQUEST.json then if REQUEST.path then
exec_with_user_priv(REQUEST)
elseif REQUEST.json then
data = JSON.decodeString(REQUEST.json) data = JSON.decodeString(REQUEST.json)
--std.json()
exec_with_user_priv(data) exec_with_user_priv(data)
elseif args and #args > 0 then
local decoded = std.b64decode(args[1])
data = JSON.decodeString(bytes.__tostring(decoded))
if data and data.path then
exec_with_user_priv(data)
else
fail("Unknown request")
end
else else
fail("Unkown request") fail("Unkown request")
end end

View File

@ -191,6 +191,7 @@ function VFSController:upload(...)
if r then if r then
result(r) result(r)
else else
self:error(m)
fail(m) fail(m)
end end
else else

View File

@ -1,10 +1,13 @@
require("sqlite") require("sqlite")
local TUNNEL_KEYCHAIN = "/opt/www/tmp/channels/antunnel_keychain"
function fail(msg) function fail(msg)
std.custom_header("Connection","close")
std.json() std.json()
std.t(JSON.encode({error=msg})) std.t(JSON.encode({error=msg}))
end end
function result(obj) function result(obj)
std.custom_header("Connection","close")
std.json() std.json()
std.t(JSON.encode({result=obj, error=false})) std.t(JSON.encode({result=obj, error=false}))
end end
@ -26,17 +29,33 @@ function sysdb()
end end
function is_auth() function is_auth()
if SESSION.sessionid == nil or SESSION.sessionid == '0' then return false end local sessionid = nil
if SESSION.sessionid and SESSION.sessionid ~= '0' then
sessionid = SESSION.sessionid
-- should be used only by API call
elseif REQUEST.sessionid and REQUEST.sessionid ~= '0' then
sessionid = REQUEST.sessionid
elseif REQUEST.access_token and REQUEST.access_token ~= '0' then
sessionid = REQUEST.access_token
end
if sessionid == nil then
return false
end
-- query session id from database -- query session id from database
local db = sysdb() local db = sysdb()
if db == nil then return false end if db == nil then return false end
local cond = {exp= {["="] = { sessionid = SESSION.sessionid }}} local cond = {exp= {["="] = { sessionid = sessionid }}}
local data = db:find(cond) local data = db:find(cond)
--print(JSON.encode(data)) --print(JSON.encode(data))
db:close() db:close()
if data == nil or data[1] == nil then return die("No user data found") end if data == nil or data[1] == nil then return die("No user data found") end
-- next time check the stamp -- next time check the stamp
SESSION.user = data[1].username SESSION.user = data[1].username
local f = io.open(TUNNEL_KEYCHAIN, "w")
if f then
f:write(sessionid..SESSION.user)
f:close()
end
return true return true
end end

View File

@ -113,7 +113,7 @@ vfs.write = function(path,data)
local uid = ulib.uid(SESSION.user) local uid = ulib.uid(SESSION.user)
-- --
if data ~= "" then if data ~= "" then
local header = string.match(data, "^data%:[%w%.-]+%/[%w%.-]+;base64,") local header = string.match(data, "^data%:[%w%.-%+]+%/[%w%.-%+]+;base64,")
if header ~= nil then if header ~= nil then
local b64data = string.gsub(data, utils.escape_pattern(header),"") local b64data = string.gsub(data, utils.escape_pattern(header),"")
local barr = std.b64decode(b64data) local barr = std.b64decode(b64data)
@ -141,13 +141,23 @@ vfs.write = function(path,data)
end end
vfs.upload = function(path) vfs.upload = function(path)
if(not path) then
return false, "Unknown upload destination, abort!"
end
local r,m = vfs.checkperm(path,"write") local r,m = vfs.checkperm(path,"write")
if(r) then if(r) then
local uid = ulib.uid(SESSION.user) local uid = ulib.uid(SESSION.user)
local file = m.."/"..REQUEST["upload.file"] local index = 0
ulib.move(REQUEST["upload.tmp"], file) while(REQUEST["upload-"..index..".tmp"] ~= nil) do
local file = m.."/"..REQUEST["upload-"..index..".file"]
ulib.move(REQUEST["upload-"..index..".tmp"], file)
ulib.chown(file, uid.id, uid.gid) ulib.chown(file, uid.id, uid.gid)
return true, nil index = index + 1
end
if(index == 0) then
return false, "No file is uploaded"
end
return true, index
else else
return r,m return r,m
end end
@ -156,14 +166,14 @@ end
vfs.checkperm = function(path, right) vfs.checkperm = function(path, right)
local osfile = vfs.ospath(path) local osfile = vfs.ospath(path)
local perm = vfs.perm(osfile) local perm = vfs.perm(osfile)
print(osfile) --print(osfile)
if not ulib.exists(osfile) then if not ulib.exists(osfile) then
return false,"Resource does not exist" return false,"Resource does not exist"
end end
-- check if user own the file -- check if user own the file
if perm ~= nil then if perm ~= nil then
if perm[right] == true then if perm[right] == true then
print("Permission granted") --print("Permission granted")
return true,osfile return true,osfile
else else
print("Permission denie") print("Permission denie")
@ -182,13 +192,13 @@ vfs.perm = function(file)
if uid ~= nil and st ~= nil and st.perm ~= nil then if uid ~= nil and st ~= nil and st.perm ~= nil then
--print(JSON.encode({uid, st})) --print(JSON.encode({uid, st}))
if(uid.id == st.uid) then -- the user owned the file if(uid.id == st.uid) then -- the user owned the file
print("file belong to user") --print("file belong to user")
return st.perm.owner return st.perm.owner
elseif uid.groups and uid.groups[st.gid] then elseif uid.groups and uid.groups[st.gid] then
print("User belong to this group") --print("User belong to this group")
return st.perm.group return st.perm.group
else else
print("User belong to other") --print("User belong to other")
return st.perm.other return st.perm.other
end end
else else