前言
最近项目想集成drawio,由于官方并没有提供NPM包引入方案,所以最终选择了官网提供的嵌入式方案来集成drawio,并且集成过程中自定义了菜单,下面具体说说如何操作。
这里首先说明一下,因为我使用的是next.js所以我在网上找到一个已经集成了drawio的嵌入模式的npm包react-drawio,在使用的过程中我发现这个包并不满足我的需要,所以我参考了它的代码一步步实现我想要的效果。
还有在使用drawio的二次开发过程中,我参考了这篇系列文章draw.io 网页版二次开发
需求
每个人在准备开发drawio前总有自己的需求和目的,所以我的需求就是:
1、使用drawio的iframe嵌入模式
2、添加和修改下拉菜单并且调用自己网站的弹出框逻辑
操作
带着需求我们来具体的操作:
1、下载drawio代码
官网地址在这
2、运行代码及访问
我们首先安装一下serve包:
sudo npm install -g serve
安装好之后,我们到webapp目录执行启动命令:
serve
执行之后,我们在url中输入如下地址来模拟嵌入模式:
http://localhost:61286/?embed=1&proto=json&ui=min&dark=0&spin=0&noExitBtn=1&noSaveBtn=1&saveAndExit=0&dev=1
上面url参数说明:
embed:是否是嵌入模式
ui:主题 'min' | 'atlas' | 'kennedy' | 'dark' | 'sketch' | 'simple'
dark:是否是暗色主题
dev:开发模式
noExitBtn:不显示退出按钮
noSaveBtn:不显示保存按钮
saveAndExit:是否显示保存和退出按钮
3、修改菜单及添加自定义事件
上面就是我想要实现的效果,打开我的文件
,保存到我的文件
,打开
这三个是我新添加的,导出是使用原有的,还有就是隐藏不需要的菜单,比如系统自带的导入功能,我就觉得不好用,就隐藏了。
修改webapp/js/diagramly/Menus.js
this.put('diagram', new Menu(mxUtils.bind(this, function (menu, parent) {
var file = editorUi.getCurrentFile();
if (Editor.currentTheme != 'simple') {
editorUi.menus.addMenuItems(menu, ['-', 'importFromSeaurl','saveFromSeaurl', 'importFile'], parent);
this.addSubmenu('exportFile', menu, parent);
menu.addSeparator(parent);
editorUi.menus.addSubmenu('extras', menu, parent, mxResources.get('settings'));
menu.addSeparator(parent);
}
...
// 导出文件我使用了原有的代码,下拉复制到这就完成了
this.put('exportFile', new Menu(mxUtils.bind(this, function (menu, parent) {
if (editorUi.isExportToCanvas()) {
this.addMenuItems(menu, ['exportPng'], parent);
if (editorUi.jpgSupported) {
this.addMenuItems(menu, ['exportJpg'], parent);
}
if (editorUi.webpSupported) {
this.addMenuItems(menu, ['exportWebp'], parent);
}
}
// Disabled for standalone mode in iOS because new tab cannot be closed
else if (!editorUi.isOffline() && (!mxClient.IS_IOS || !navigator.standalone)) {
this.addMenuItems(menu, ['exportPng', 'exportJpg'], parent);
}
this.addMenuItems(menu, ['exportSvg', '-'], parent);
// Redirects export to PDF to print in Chrome App
if (editorUi.isOffline() || editorUi.printPdfExport) {
this.addMenuItems(menu, ['exportPdf'], parent);
}
// Disabled for standalone mode in iOS because new tab cannot be closed
else if (!editorUi.isOffline() && (!mxClient.IS_IOS || !navigator.standalone)) {
this.addMenuItems(menu, ['exportPdf'], parent);
}
if (!mxClient.IS_IE && (typeof (VsdxExport) !== 'undefined' || !editorUi.isOffline())) {
this.addMenuItems(menu, ['exportVsdx'], parent);
}
this.addMenuItems(menu, ['-', 'exportHtml', 'exportXml', 'exportUrl'], parent);
if (!editorUi.isOffline()) {
menu.addSeparator(parent);
this.addMenuItem(menu, 'export', parent).firstChild.nextSibling.innerHTML = mxResources.get('advanced') + '...';
}
if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && Editor.currentTheme == 'min') {
this.addMenuItems(menu, ['publishLink'], parent);
}
if (editorUi.mode != App.MODE_ATLAS && urlParams['extAuth'] != '1' &&
(Editor.currentTheme == 'simple' || Editor.currentTheme == 'sketch' ||
Editor.currentTheme == 'min')) {
menu.addSeparator(parent);
editorUi.menus.addSubmenu('embed', menu, parent);
}
})));
上面的importFromSeaurl
、saveFromSeaurl
、importFile
、exportFile
就是我添加的四个菜单项。其中exportFile
导出按钮功能,已经在上面代码实现了。
修改webapp/js/grapheditor/Actions.js
添加如下代码
this.addAction('importFromSeaurl', function() {
ui.importFromSeaurl()
}, null, null, '')
this.addAction('saveFromSeaurl', function() {
ui.saveFromSeaurl()
}, null, null, '')
this.addAction('importFile', function() {
ui.importLocalFile(true);
}, null, null, '');
注意:importLocalFile
是系统自带的功能,这样就完成了打开文件的菜单项功能。而上面的importFromSeaurl
和saveFromSeaurl
是自己自定义的,所以我们还需要改下面的代码:
修改webapp/js/diagramly/EditorUi.js
EditorUi.prototype.importFromSeaurl = function(){
var parent = window.opener || window.parent;
parent.postMessage(JSON.stringify({
event: 'importFromSeaurl'
}), '*');
};
EditorUi.prototype.saveFromSeaurl = function(){
var parent = window.opener || window.parent;
var xml = this.getFileData(true);
var msg = this.createLoadMessage('saveFromSeaurl');
msg.xml = xml;
parent.postMessage(JSON.stringify(msg), '*');
};
这里你会看到,我使用了iframe的postMessage功能,将消息发送给父窗体,这样就达到了触发事件效果,所以到时候我们只要到父窗体组件中接收这个消息处理自己的逻辑即可。
4、打包docker镜像并发布到k8s
这里我参考了docker-drawio的代码实现打包和发布功能,还有一点就是我使用了github actions来实现打包和部署,你们当然也可以使用了jenkins或者gitlab ci/cd来实现,这里我就不赘述了。
在/etc/docker目录下添加文件
1、添加DockerFile
FROM openjdk:11-jdk-slim AS build
RUN apt-get update -y && \
# this solves some weird issue with openjdk-11-jdk-headless
# https://github.com/nextcloud/docker/issues/380
mkdir -p /usr/share/man/man1 && \
apt-get install -y \
ant \
git
RUN cd /tmp && \
git clone --depth 1 https://github.com/zhangwei900808/seaurl-drawio.git && \
cd /tmp/seaurl-drawio/etc/build/ && \
ant war
FROM tomcat:9-jre11
LABEL maintainer="seaurl" \
org.opencontainers.image.authors="Seaurl Ltd" \
org.opencontainers.image.url="https://www.seaurl.com" \
org.opencontainers.image.source="https://github.com/zhangwei900808/seaurl-drawio"
ENV RUN_USER tomcat
ENV RUN_GROUP tomcat
RUN apt-get update -y && \
apt-get install -y --no-install-recommends \
certbot \
curl \
xmlstarlet \
unzip && \
apt-get autoremove -y --purge && \
apt-get clean && \
rm -r /var/lib/apt/lists/*
COPY --from=build /tmp/seaurl-drawio/build/draw.war /tmp
# Extract draw.io war & Update server.xml to set Draw.io webapp to root
RUN mkdir -p $CATALINA_HOME/webapps/draw && \
unzip /tmp/draw.war -d $CATALINA_HOME/webapps/draw && \
rm -rf /tmp/draw.war /tmp/seaurl-drawio && \
cd $CATALINA_HOME && \
xmlstarlet ed \
-P -S -L \
-i '/Server/Service/Engine/Host/Valve' -t 'elem' -n 'Context' \
-i '/Server/Service/Engine/Host/Context' -t 'attr' -n 'path' -v '/' \
-i '/Server/Service/Engine/Host/Context[@path="/"]' -t 'attr' -n 'docBase' -v 'draw' \
-s '/Server/Service/Engine/Host/Context[@path="/"]' -t 'elem' -n 'WatchedResource' -v 'WEB-INF/web.xml' \
conf/server.xml
# Copy docker-entrypoint
# 注意:当你使用 COPY --from=build 时,Docker 知道它需要从构建阶段(build)中提取文件
COPY --from=build /tmp/seaurl-drawio/etc/docker/docker-entrypoint.sh /
RUN chmod 755 /docker-entrypoint.sh
# Add a tomcat user
RUN groupadd -r ${RUN_GROUP} && useradd -g ${RUN_GROUP} -d ${CATALINA_HOME} -s /bin/bash ${RUN_USER} && \
chown -R ${RUN_USER}:${RUN_GROUP} ${CATALINA_HOME}
USER ${RUN_USER}
WORKDIR $CATALINA_HOME
EXPOSE 8080 8443
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["catalina.sh", "run"]
2、添加k8s-deploy.yaml文件
apiVersion: v1
kind: Namespace
metadata:
name: drawio
---
apiVersion: v1
kind: Service
metadata:
name: drawio
namespace: drawio
labels:
app: draw.io
spec:
# 使用ip访问,所以设置成NodePort
type: NodePort
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600
ports:
- name: http
port: 8080
selector:
app: draw.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: drawio
namespace: drawio
spec:
replicas: 2
selector:
matchLabels:
app: draw.io
template:
metadata:
labels:
app: draw.io
spec:
containers:
- image: xxx:<tag>
imagePullPolicy: IfNotPresent
name: drawio
ports:
- containerPort: 8080
---
3、添加docker-entrypoint文件
#!/bin/bash
#set -e
LETS_ENCRYPT_ENABLED=${LETS_ENCRYPT_ENABLED:-false}
PUBLIC_DNS=${PUBLIC_DNS:-'draw.example.com'}
ORGANISATION_UNIT=${ORGANISATION_UNIT:-'Cloud Native Application'}
ORGANISATION=${ORGANISATION:-'example inc'}
CITY=${CITY:-'Paris'}
STATE=${STATE:-'Paris'}
COUNTRY_CODE=${COUNTRY_CODE:-'FR'}
KEYSTORE_PASS=${KEYSTORE_PASS:-'V3ry1nS3cur3P4ssw0rd'}
KEY_PASS=${KEY_PASS:-$KEYSTORE_PASS}
echo "Init PreConfig.js"
#Add CSP to prevent calls to draw.io
echo "(function() {" > $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo " try {" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo " var s = document.createElement('meta');" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
if [[ -z "${DRAWIO_GITLAB_ID}" ]]; then
echo " s.setAttribute('content', '${DRAWIO_CSP_HEADER:-default-src \'self\'; script-src \'self\' https://storage.googleapis.com https://apis.google.com https://docs.google.com https://code.jquery.com \'unsafe-inline\'; connect-src \'self\' https://*.dropboxapi.com https://api.trello.com https://api.github.com https://raw.githubusercontent.com https://*.googleapis.com https://*.googleusercontent.com https://graph.microsoft.com https://*.1drv.com https://*.sharepoint.com https://gitlab.com https://*.google.com https://fonts.gstatic.com https://fonts.googleapis.com; img-src * data:; media-src * data:; font-src * about:; style-src \'self\' \'unsafe-inline\' https://fonts.googleapis.com; frame-src \'self\' https://*.google.com;}');" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
else
echo " s.setAttribute('content', '${DRAWIO_CSP_HEADER:-default-src \'self\'; script-src \'self\' https://storage.googleapis.com https://apis.google.com https://docs.google.com https://code.jquery.com \'unsafe-inline\'; connect-src \'self\' $DRAWIO_GITLAB_URL https://*.dropboxapi.com https://api.trello.com https://api.github.com https://raw.githubusercontent.com https://*.googleapis.com https://*.googleusercontent.com https://graph.microsoft.com https://*.1drv.com https://*.sharepoint.com https://gitlab.com https://*.google.com https://fonts.gstatic.com https://fonts.googleapis.com; img-src * data:; media-src * data:; font-src * about:; style-src \'self\' \'unsafe-inline\' https://fonts.googleapis.com; frame-src \'self\' https://*.google.com;}');" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
fi
echo " s.setAttribute('http-equiv', 'Content-Security-Policy');" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo " var t = document.getElementsByTagName('meta')[0];" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo " t.parentNode.insertBefore(s, t);" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo " } catch (e) {} // ignore" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "})();" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
#Overrides of global vars need to be pre-loaded
if [[ "${DRAWIO_SELF_CONTAINED}" ]]; then
echo "window.EXPORT_URL = '/service/0'; //This points to ExportProxyServlet which uses the local export server at port 8000. This proxy configuration allows https requests to the export server via Tomcat." >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "window.PLANT_URL = '/service/1';" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
elif [[ "${EXPORT_URL}" ]]; then
echo "window.EXPORT_URL = '/service/0';" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
fi
#DRAWIO_SERVER_URL is the new URL of the deployment, e.g. https://www.example.com/drawio/
#DRAWIO_BASE_URL is still used by viewer, lightbox and embed. For backwards compatibility, DRAWIO_SERVER_URL is set to DRAWIO_BASE_URL if not specified.
if [[ "${DRAWIO_SERVER_URL}" ]]; then
echo "window.DRAWIO_SERVER_URL = '${DRAWIO_SERVER_URL}';" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "window.DRAWIO_BASE_URL = '${DRAWIO_BASE_URL:-${DRAWIO_SERVER_URL:0:$((${#DRAWIO_SERVER_URL}-1))}}';" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
else
echo "window.DRAWIO_BASE_URL = '${DRAWIO_BASE_URL:-http://localhost:8080}';" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "window.DRAWIO_SERVER_URL = window.DRAWIO_BASE_URL + '/';" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
fi
#DRAWIO_VIEWER_URL is path to the viewer js, e.g. https://www.example.com/js/viewer.min.js
echo "window.DRAWIO_VIEWER_URL = '${DRAWIO_VIEWER_URL}';" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
#DRAWIO_LIGHTBOX_URL Replace with your lightbox URL, eg. https://www.example.com
echo "window.DRAWIO_LIGHTBOX_URL = '${DRAWIO_LIGHTBOX_URL}';" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "window.DRAW_MATH_URL = 'math/es5';" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
#Custom draw.io configurations. For more details, https://www.drawio.com/doc/faq/configure-diagram-editor
echo "window.DRAWIO_CONFIG = ${DRAWIO_CONFIG:-null};" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
#Real-time configuration
echo "urlParams['sync'] = 'manual'; //Disable Real-Time" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
#Disable unsupported services
echo "urlParams['db'] = '0'; //dropbox" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "urlParams['gh'] = '0'; //github" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "urlParams['tr'] = '0'; //trello" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
#Google Drive
if [[ -z "${DRAWIO_GOOGLE_CLIENT_ID}" ]]; then
echo "urlParams['gapi'] = '0'; //Google Drive" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
else
#Google drive application id and client id for the editor
echo "window.DRAWIO_GOOGLE_APP_ID = '${DRAWIO_GOOGLE_APP_ID}'; " >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "window.DRAWIO_GOOGLE_CLIENT_ID = '${DRAWIO_GOOGLE_CLIENT_ID}'; " >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo -n "${DRAWIO_GOOGLE_CLIENT_ID}" > $CATALINA_HOME/webapps/draw/WEB-INF/google_client_id
echo -n "${DRAWIO_GOOGLE_CLIENT_SECRET}" > $CATALINA_HOME/webapps/draw/WEB-INF/google_client_secret
#If you want to use the editor as a viewer also, you can create another app with read-only access. You can use the same info as above if write-access is not an issue.
if [[ "${DRAWIO_GOOGLE_VIEWER_CLIENT_ID}" ]]; then
echo "window.DRAWIO_GOOGLE_VIEWER_APP_ID = '${DRAWIO_GOOGLE_VIEWER_APP_ID}'; " >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "window.DRAWIO_GOOGLE_VIEWER_CLIENT_ID = '${DRAWIO_GOOGLE_VIEWER_CLIENT_ID}'; " >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo -n "/:::/${DRAWIO_GOOGLE_VIEWER_CLIENT_ID}" >> $CATALINA_HOME/webapps/draw/WEB-INF/google_client_id
echo -n "/:::/${DRAWIO_GOOGLE_VIEWER_CLIENT_SECRET}" >> $CATALINA_HOME/webapps/draw/WEB-INF/google_client_secret
fi
fi
#Microsoft OneDrive
if [[ -z "${DRAWIO_MSGRAPH_CLIENT_ID}" ]]; then
echo "urlParams['od'] = '0'; //OneDrive" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
else
#Google drive application id and client id for the editor
echo "window.DRAWIO_MSGRAPH_CLIENT_ID = '${DRAWIO_MSGRAPH_CLIENT_ID}'; " >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo -n "${DRAWIO_MSGRAPH_CLIENT_ID}" > $CATALINA_HOME/webapps/draw/WEB-INF/msgraph_client_id
echo -n "${DRAWIO_MSGRAPH_CLIENT_SECRET}" > $CATALINA_HOME/webapps/draw/WEB-INF/msgraph_client_secret
if [[ "${DRAWIO_MSGRAPH_TENANT_ID}" ]]; then
echo "window.DRAWIO_MSGRAPH_TENANT_ID = '${DRAWIO_MSGRAPH_TENANT_ID}'; " >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
fi
fi
#Gitlab
if [[ -z "${DRAWIO_GITLAB_ID}" ]]; then
echo "urlParams['gl'] = '0'; //Gitlab" >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
else
#Gitlab url and id for the editor
echo "window.DRAWIO_GITLAB_URL = '${DRAWIO_GITLAB_URL}'; " >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "window.DRAWIO_GITLAB_ID = '${DRAWIO_GITLAB_ID}'; " >> $CATALINA_HOME/webapps/draw/js/PreConfig.js
#Gitlab server flow auth (since 14.6.7)
echo -n "${DRAWIO_GITLAB_URL}/oauth/token" > $CATALINA_HOME/webapps/draw/WEB-INF/gitlab_auth_url
echo -n "${DRAWIO_GITLAB_ID}" > $CATALINA_HOME/webapps/draw/WEB-INF/gitlab_client_id
echo -n "${DRAWIO_GITLAB_SECRET}" > $CATALINA_HOME/webapps/draw/WEB-INF/gitlab_client_secret
fi
cat $CATALINA_HOME/webapps/draw/js/PreConfig.js
echo "Init PostConfig.js"
#null'ing of global vars need to be after init.js
echo "window.VSD_CONVERT_URL = null;" > $CATALINA_HOME/webapps/draw/js/PostConfig.js
echo "window.ICONSEARCH_PATH = null;" >> $CATALINA_HOME/webapps/draw/js/PostConfig.js
echo "EditorUi.enableLogging = false; //Disable logging" >> $CATALINA_HOME/webapps/draw/js/PostConfig.js
#This requires subscription with cloudconvert.com
if [[ -z "${DRAWIO_CLOUD_CONVERT_APIKEY}" ]]; then
echo "window.EMF_CONVERT_URL = null;" >> $CATALINA_HOME/webapps/draw/js/PostConfig.js
else
echo "window.EMF_CONVERT_URL = '/convert';" >> $CATALINA_HOME/webapps/draw/js/PostConfig.js
echo -n "${DRAWIO_CLOUD_CONVERT_APIKEY}" > $CATALINA_HOME/webapps/draw/WEB-INF/cloud_convert_api_key
fi
if [[ "${DRAWIO_SELF_CONTAINED}" ]]; then
echo "EditorUi.enablePlantUml = true; //Enables PlantUML" >> $CATALINA_HOME/webapps/draw/js/PostConfig.js
fi
#Treat this domain as a draw.io domain
echo "App.prototype.isDriveDomain = function() { return true; }" >> $CATALINA_HOME/webapps/draw/js/PostConfig.js
cat $CATALINA_HOME/webapps/draw/js/PostConfig.js
if ! [ -f $CATALINA_HOME/.keystore ] && [ "$LETS_ENCRYPT_ENABLED" == "true" ]; then
echo "Generating Let's Encrypt certificate"
keytool -genkey -noprompt -alias tomcat -dname "CN=${PUBLIC_DNS}, OU=${ORGANISATION_UNIT}, O=${ORGANISATION}, L=${CITY}, S=${STATE}, C=${COUNTRY_CODE}" -keystore $CATALINA_HOME/.keystore -storepass "${KEYSTORE_PASS}" -KeySize 2048 -keypass "${KEY_PASS}" -keyalg RSA -storetype pkcs12
keytool -list -keystore $CATALINA_HOME/.keystore -v -storepass "${KEYSTORE_PASS}"
keytool -certreq -alias tomcat -file request.csr -keystore $CATALINA_HOME/.keystore -storepass "${KEYSTORE_PASS}"
certbot certonly --csr $CATALINA_HOME/request.csr --standalone --register-unsafely-without-email --agree-tos
keytool -import -trustcacerts -alias tomcat -file 0001_chain.pem -keystore $CATALINA_HOME/.keystore -storepass "${KEYSTORE_PASS}"
fi
if ! [ -f $CATALINA_HOME/.keystore ] && [ "$LETS_ENCRYPT_ENABLED" == "false" ]; then
echo "Generating Self-Signed certificate"
keytool -genkey -noprompt -alias selfsigned -dname "CN=${PUBLIC_DNS}, OU=${ORGANISATION_UNIT}, O=${ORGANISATION}, L=${CITY}, S=${STATE}, C=${COUNTRY_CODE}" -keystore $CATALINA_HOME/.keystore -storepass "${KEYSTORE_PASS}" -KeySize 2048 -keypass "${KEY_PASS}" -keyalg RSA -validity 3600 -storetype pkcs12
keytool -list -keystore $CATALINA_HOME/.keystore -v -storepass "${KEYSTORE_PASS}"
fi
# Update SSL port configuration if it does'nt exists
#
UUID="$(cat /dev/urandom | tr -dc 'a-zA-Z' | fold -w 1 | head -n 1)$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 7 | head -n 1)"
VAR=$(cat conf/server.xml | grep "$CATALINA_HOME/.keystore")
if [ -f $CATALINA_HOME/.keystore ] && [ -z $VAR ]; then
echo "Append https connector to server.xml"
xmlstarlet ed \
-P -S -L \
-s '/Server/Service' -t 'elem' -n "${UUID}" \
-i "/Server/Service/${UUID}" -t 'attr' -n 'port' -v '8443' \
-i "/Server/Service/${UUID}" -t 'attr' -n 'protocol' -v 'org.apache.coyote.http11.Http11NioProtocol' \
-i "/Server/Service/${UUID}" -t 'attr' -n 'SSLEnabled' -v 'true' \
-i "/Server/Service/${UUID}" -t 'attr' -n 'maxThreads' -v '150' \
-i "/Server/Service/${UUID}" -t 'attr' -n 'scheme' -v 'https' \
-i "/Server/Service/${UUID}" -t 'attr' -n 'secure' -v 'true' \
-i "/Server/Service/${UUID}" -t 'attr' -n 'clientAuth' -v 'false' \
-i "/Server/Service/${UUID}" -t 'attr' -n 'sslProtocol' -v 'TLS' \
-i "/Server/Service/${UUID}" -t 'attr' -n 'KeystoreFile' -v "$CATALINA_HOME/.keystore" \
-i "/Server/Service/${UUID}" -t 'attr' -n 'KeystorePass' -v "${KEY_PASS}" \
-i "/Server/Service/${UUID}" -t 'attr' -n 'defaultSSLHostConfigName' -v "${PUBLIC_DNS:-'draw.example.com'}" \
-s "/Server/Service/${UUID}" -t 'elem' -n 'SSLHostConfig' \
-i "/Server/Service/${UUID}/SSLHostConfig" -t 'attr' -n 'hostName' -v "${PUBLIC_DNS:-'draw.example.com'}" \
-i "/Server/Service/${UUID}/SSLHostConfig" -t 'attr' -n 'protocols' -v 'TLSv1.2' \
-s "/Server/Service/${UUID}/SSLHostConfig" -t 'elem' -n 'Certificate' \
-i "/Server/Service/${UUID}/SSLHostConfig/Certificate" -t 'attr' -n 'certificateKeystoreFile' -v "$CATALINA_HOME/.keystore" \
-i "/Server/Service/${UUID}/SSLHostConfig/Certificate" -t 'attr' -n 'certificateKeystorePassword' -v "${KEY_PASS}" \
-r "/Server/Service/${UUID}" -v 'Connector' \
conf/server.xml
fi
exec "$@"
添加github actions文件
添加.github/workflow/docker-image.yml文件
name: Docker Image CI
on:
workflow_dispatch:
# push:
# branches:
# - main
# tags:
# - 'v*'
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Log in to Alibaba Cloud Registry
uses: docker/login-action@v2
with:
registry: ${{ secrets.ALIYUN_REGISTRY_SERVER }}
username: ${{ secrets.ALIYUN_REGISTRY_USERNAME }}
password: ${{ secrets.ALIYUN_REGISTRY_PASSWORD }}
- name: Build And Publish Docker Image
run: |
# Get draw.io current latest version
wget https://raw.githubusercontent.com/zhangwei900808/seaurl-drawio/main/VERSION
export VERSION=`cat VERSION`
export TAG=`if [ "" == ${VERSION} ]; then echo "latest"; else echo ${VERSION} ; fi`
docker build -f etc/docker/Dockerfile -t ${{ secrets.ALIYUN_REGISTRY_SERVER }}/com-seaurl/seaurl-drawio:${TAG} .
docker push ${{ secrets.ALIYUN_REGISTRY_SERVER }}/com-seaurl/seaurl-drawio:${TAG}
- name: Set up Kubernetes CLI
uses: azure/setup-kubectl@v3
# with:
# version: '1.24.0' # Specify the kubectl version you need
- name: Configure kubectl
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG }}" > ~/.kube/config
- name: Deploy to Kubernetes
run: |
# Get draw.io current latest version
wget https://raw.githubusercontent.com/zhangwei900808/seaurl-drawio/main/VERSION
export VERSION=`cat VERSION`
export TAG=`if [ "" == ${VERSION} ]; then echo "latest"; else echo ${VERSION} ; fi`
sed -i "s/<tag>/${TAG}/g" etc/docker/k8s-deploy.yaml
kubectl get namespace drawio || kubectl create namespace drawio
kubectl apply -f etc/docker/k8s-deploy.yaml -n drawio
# kubectl rollout status deployment/seaurl-drawio-deployment
运行github actions
运行成功之后,我们看看有没有成功
☁ seaurl-drawio-docker [main] ⚡ k get all -ndrawio
NAME READY STATUS RESTARTS AGE
pod/drawio-86555c55f4-48t7x 1/1 Running 0 88s
pod/drawio-86555c55f4-pr525 1/1 Running 0 67s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/drawio NodePort 192.168.49.196 <none> 8080:30128/TCP 3d6h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/drawio 2/2 2 2 3d6h
NAME DESIRED CURRENT READY AGE
replicaset.apps/drawio-54c59986f4 0 0 0 30h
replicaset.apps/drawio-59fbbd8dfd 0 0 0 5h17m
replicaset.apps/drawio-69cfff758d 0 0 0 28h
replicaset.apps/drawio-777c9485f5 0 0 0 32h
replicaset.apps/drawio-786d85d487 0 0 0 3d6h
replicaset.apps/drawio-86555c55f4 2 2 2 88s
说明已经成功了,这里我们获取到NODE_IP和PORT之后,我们复制到自己的项目中。
5、集成到Next.js项目中
下面是我的集成后的代码:
'use client'
import {useEffect, useRef, useState} from "react";
import {useSession} from "next-auth/react";
import {useRouter, useSearchParams} from "next/navigation";
import {withRouter} from "@/hoc/withRouter";
import {useDispatch, useSelector} from "react-redux";
import {addFileForText, queryUserAiTools, setDrawio, updateDt} from "@/lib/slices/aiSpaceSlice";
import {message, Spin} from "antd";
import Loading from "@/components/loading";
import SaveFileDlg from "@/components/dialog/saveFileDlg";
import SelectFileDlg from "@/components/dialog/selectFileDlg";
const Drawio = (props) => {
const {data: session} = useSession()
const query = useSearchParams()
const router = useRouter()
const isUpdate = useRef(false)
const drawioRef = useRef(null);
const [currentData, setCurrentData] = useState()
const {drawio, aiToolsWillChangeFunc} = useSelector(state => state.aiSpace)
const {initData} = drawio
const dispatch = useDispatch()
const [isLoading, setIsLoading] = useState(true);
const gotoRef = useRef(null)
const {platFormTheme} = useSelector(state => state.system)
const [dlg, setDlg] = useState({
title: '',
show: false
})
const [selectFileDlg, setSelectFileDlg] = useState({
title: '',
show: false
})
useEffect(() => {
// 默认加载空的画板
if(drawioRef.current){
drawioRef.current?.contentWindow?.postMessage(JSON.stringify({
action: 'load',
xml: ''
}), "*")
}
// 第一次进来的时候选择模板
if (drawioRef.current && !isLoading && !initData) {
// 打开template窗口
drawioRef.current?.contentWindow?.postMessage(JSON.stringify({
action: 'template'
}), "*")
}
// 如果有initData说明有数据缓存,则加载这个缓存数据到drawio中
if (drawioRef.current && initData){
drawioRef.current?.contentWindow?.postMessage(JSON.stringify({
action: 'load',
xml: initData
}), "*")
}
}, [isLoading])
// 打开我的文件
function openMyFile() {
...
}
// 保存到我的文件
function saveMyFile(xml) {
...
}
// 监听drawio的消息
function getMessageFromDrawio(event) {
try {
const data = JSON.parse(event?.data);
console.log('event data=', data)
if (data.event === "clickDrawio"){
globalUpdate()
}
if (data.event === 'importFromSeaurl'){
openMyFile()
}
if (data.event === "saveFromSeaurl"){
saveMyFile(data.xml)
}
// 页面初始化完成
if (data?.event === 'init') {
setIsLoading(false)
}
// 用户发起了导出命令
if (data?.event === "export"){
setCurrentData(data.xml)
}
if (event.origin === "*") {
console.log("Message received from iframe:", event.data);
}
} catch (e) {
}
}
useEffect(() => {
// 父窗口中监听从 iframe 返回的消息
window.addEventListener("message", getMessageFromDrawio);
return () => {
window.removeEventListener("message", getMessageFromDrawio);
}
}, [])
return <div className={'w-full h-[calc(100vh-56px)] relative'} onClick={globalUpdate}>
{isLoading ?
<div className={'flex items-center justify-center h-full w-full absolute top-0 left-0 right-0 bottom-0 z-20 bg-white dark:bg-[#141414]'}>
<Loading/>
</div> : null}
<iframe ref={drawioRef}
className={'w-full h-full min-h-[400px] min-w-[400px] border-0'}
src={`http://xxx.xxx.xxx.xxx:xxxx/?embed=1&proto=json&ui=min&${platFormTheme.background === 'light' ? "dark=0":"dark=1"}&spin=0&noExitBtn=1&noSaveBtn=1&saveAndExit=0`}
title="seaurl.com"></iframe>
<SaveFileDlg dlg={dlg}/>
<SelectFileDlg dlg={selectFileDlg}/>
</div>
};
export default withRouter(Drawio);
上面代码中我们使用window.addEventListener
来监听drawio菜单返回来的消息,这样你就可以打开自己项目中的窗体或者执行相关逻辑了。
总结
1、这里最难的点应该算修改drawio的菜单项的新增与修改了,所以需要多阅读drawio的代码才能实现自己想要的效果。
2、因为使用了iframe的嵌入模式,所以需要来回的发送和接收消息,我觉得drawio应该出个自己的NPM包,这样开发者集成更方便一点
3、打开页面的时候应该默认xml:''
,否则打开就是空白,拖拽图标到空白处不起作用
4、react中,useEffect中的return方法是在组件销毁后执行的,所以当你在return方法中使用ref来获取组件相关数据时是获取不到的,报null的错
5、如果你们觉得有啥问题可以多看看react-drawio这个npm包源码,我也是参考它的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。