起因
想把自己的工作计划在网页里展示出来,方便监督自己。
想法
plantuml、mermaid、excalidraw都是不错的工具。
plantuml和mermaid最主要的是展示甘特图,最终选择mermaid,原因是界面稍微漂亮一些,而plantuml渲染需要外部服务器。
excalidraw是个非常自由的白板,本打算使用多人协作方式集成它,这样就能多端实时协作,但因为跨域的安全性问题无法集成到网页中。
有了想法就开工。
集成mermaid
mermaid提供js脚本,可以直接集成到网页中。
<div id="mermaid-gantt" class="mermaid">
gantt
dateFormat YYYY-MM-DD
axisFormat %y/%d/%m
title Schedule
excludes weekdays
section 个人安排网页<br>开发
集成甘特图 : done, 2022-11-02,3h
</div>
<script src="js/mermaid.min.js"></script>
<script>
window.onload = function () {
mermaid.initialize({
theme: 'forest',
// themeCSS: '.node rect { fill: red; }',
logLevel: 3,
securityLevel: 'loose',
flowchart: {curve: 'basis'},
gantt: {axisFormat: '%m/%d/%Y'},
sequence: {actorMargin: 50},
// sequenceDiagram: { actorMargin: 300 } // deprecated
});
}
</script>
集成excalidraw
方案1--分享链接集成
这个方案很简单,效果也很好,将excalidraw的分享链接塞进iframe里。 每回修改白板后就把新的链接更新到代码中即可。
最后并没有采用这个方案,原因在于数据不在自己手上。
这个方案并没有真正测试过,可能也会出现方案2的问题。
方案2--实时写作集成
集成失败,由于部署环境没有部署https,所以静态页的安全上下文(secure context)为不安全。 因此,嵌在静态页中的iframe的安全上下文也为不安全。
这个安全上下文不安全导致excalidraw的实时写作无法使用加密信道。
方案3--使用静态数据由kroki渲染
kroki提供一堆各种图的在线渲染,将数据给他,他给你一个svg,就是这么简单,但是数据存在泄露的风险。
有严重缺陷,kroki仅支持4096字节长度的数据渲染,一旦图大了也就无法渲染。
function textEncode(str) {
if (window.TextEncoder) {
return new TextEncoder('utf-8').encode(str);
}
var utf8 = unescape(encodeURIComponent(str));
var result = new Uint8Array(utf8.length);
for (var i = 0; i < utf8.length; i++) {
result[i] = utf8.charCodeAt(i);
}
return result;
}
// 数据编码
var data = textEncode("your data here");
// 数据压缩
var compressed = pako.deflate(data, {level: 9, to: 'string'})
var result = btoa(compressed)
.replace(/\+/g, '-').replace(/\//g, '_');
var kroki_url = 'https://kroki.io/excalidraw/svg/' + result;
// 请求svg
request.open('GET', kroki_url, false);
request.send(null);
if (request.status === 200) {
// 向div中插入svg
document.getElementById("excalidraw").innerHTML = request.responseText;
}
方案4
将excalidraw转为SVG。
使用前端库excalidraw/utils将excalidraw转换为svg,插入到网页中。
官方的示例代码有点问题,正确的如下。需要注意的地方是,不能直接传入string作为参数,需要将string转换成结构体传入(excalidraw数据本身就是json格式)。
function loadExcalidraw() {
var request = new XMLHttpRequest();
request.open('GET', '/data/schedule.excalidraw', false);
request.send(null);
if (request.status === 200) {
var data = eval("(" + request.responseText + ")");
var svg = exportToSvg(data);
svg.then((value) => {
console.log(value);
document.getElementById("excalidraw").innerHTML = value.outerHTML;
})
}
}
在线编辑
在github上打开项目,将com改为dev或者按一下“.”就可以进入GitHub.dev在线编辑环境,在其中安装excalidraw和mardown preview插件(网页端渲染mermaid还暂时不支持),就可以在线编辑excalidraw,还有预览mermaid图了。
持续部署
因为是静态网页,所谓的部署也就是把最新的网页拉取回来。 没有将静态页打包成docker镜像,若采用docker打包,则需要拉取 镜像--停止旧容器--运行新容器--删除旧镜像 这一系列操作;远不如本地部署nginx,更新对应的web资源目录来的方便。
触发使用github webhook触发,这里没有选择写个简单web服务通过内网穿透暴露到互联网上,主要担心内网穿透服务不稳定。
ntfy是一个消息发布、订阅的工具,原本我是想用telegram来打通树莓派和github的,但是telegram不支持两个bot间通信。 在ntfy官方服务器中发布消息,你得先有一个主题,这个主题任何人都可以订阅、发布(没错,官方服务器就是这么设定的,想使用权限,你需要自己部署),所以起一个又长又难猜的主题吧。消息发布、和接受文档里都写得非常详细,不再赘述。
我这里使用python去轮询ntfy接口收取消息,并加上了访问频率限制,未对消息内容做检查,因为用不上,反正做的操作也是从github拉取项目。
注:github webhook支持内容验证,ntfy不能与之对接,得自己写web服务对接,所以这一点来说,ntfy有很大缺点。
import json
import os
import time
import requests as requests
class RateLimiter:
def __init__(self):
self.capacity = 4.0
self.rate = 0.25
self.tokens = 0.0
self.timeStamp = time.time()
def control(self):
now = time.time()
self.tokens = min(self.capacity, self.tokens + int(now - self.timeStamp) * self.rate)
self.timeStamp = now
if self.tokens < 1:
return False
else:
self.tokens -= 1
return True
rateLimiter = RateLimiter()
last_time = time.time()
while True:
try:
tmp_time = time.time()
resp = requests.get(
"https://ntfy.sh/yourtopic/json?poll=1&since="
+ str(int(last_time)), stream=False)
last_time = tmp_time
for line in resp.iter_lines():
if line:
print(line)
data = json.loads(line)
if data['event'] == 'message':
if data['message'] == 'Update finish':
continue
if rateLimiter.control():
print("get message")
os.system("git pull")
requests.post(
"https://ntfy.sh/yourtopic",
data="Update finish".encode(encoding='utf-8'))
time.sleep(10)
except Exception as e:
print(e)