diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index 03e988dcecc..96dc348da62 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -1,4 +1,17 @@ { + "publish": "Publish", + "publishService": "Publish service", + "publishServiceNotStarted": "Publish Service Not Started", + "publishServiceTip": "When enabled, the publish service will be started. This service publishes the content of the current workspace in read-only mode on the local network.", + "publishServicePort": "Service port", + "publishServicePortTip": "Enable the publish service using the specified port number. If set to 0, a random port will be used.", + "publishServiceAddresses": "Service access addresses", + "publishServiceAddressesTip": "Possible network addresses to access the publish service.", + "publishServiceAuth": "Service basic authentication", + "publishServiceAuthTip": "When enabled, authentication is required to access the publish service.", + "publishServiceAuthAccounts": "Authenticated accounts", + "publishServiceAuthAccountsTip": "List of Basic authentication accounts. Visitors need to enter the username and password from this list to view the published content.", + "publishServiceAuthAccountAdd": "Add account", "copyMirror": "Copy mirror", "duplicateMirror": "Duplicate mirror", "duplicateCompletely": "Duplicate completely", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index 7f98438e6cb..4d378d71e37 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1,4 +1,17 @@ { + "publish": "Publicar", + "publishService": "Publicar servicio", + "publishServiceNotStarted": "Servicio de publicación no iniciado", + "publishServiceTip": "Al activar esto, se iniciará el servicio de publicación. Este servicio publicará el contenido del espacio de trabajo actual en modo de solo lectura en la LAN", + "publishServicePort": "Número de puerto del servicio", + "publishServicePortTip": "Activar el servicio de publicación con el número de puerto especificado. Si se establece en 0, se utilizará un puerto aleatorio", + "publishServiceAddresses": "Direcciones de acceso al servicio", + "publishServiceAddressesTip": "Direcciones de red desde las que se puede acceder al servicio de publicación", + "publishServiceAuth": "Autenticación básica del servicio", + "publishServiceAuthTip": "Al activar esto, se requerirá autenticación al acceder al servicio de publicación", + "publishServiceAuthAccounts": "Cuentas de autenticación", + "publishServiceAuthAccountsTip": "Lista de cuentas de autenticación básica. Después de activar la autenticación básica, los visitantes deberán ingresar el nombre de usuario y la contraseña de la lista para ver el contenido publicado", + "publishServiceAuthAccountAdd": "Agregar cuenta", "copyMirror": "Copiar espejo", "duplicateMirror": "Espejo duplicado", "duplicateCompletely": "Duplicar completamente", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index d49cca4e6d8..f01d7d21b4d 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -1,4 +1,17 @@ { + "publish": "Publier", + "publishService": "Publier le service", + "publishServiceNotStarted": "Service de publication non démarré", + "publishServiceTip": "Lorsqu'activé, le service de publication démarre. Ce service publie en mode lecture seule le contenu de l'espace de travail actuel dans le réseau local.", + "publishServicePort": "Numéro de port du service", + "publishServicePortTip": "Active le service de publication avec le numéro de port spécifié. Si défini sur 0, un port aléatoire sera utilisé.", + "publishServiceAddresses": "Adresses d'accès au service", + "publishServiceAddressesTip": "Adresses réseau qui peuvent accéder au service de publication", + "publishServiceAuth": "Authentification Basic du service", + "publishServiceAuthTip": "Lorsqu'activé, une authentification est requise pour accéder au service de publication", + "publishServiceAuthAccounts": "Comptes d'authentification", + "publishServiceAuthAccountsTip": "Liste des comptes d'authentification Basic. Lorsque l'authentification Basic est activée, les visiteurs doivent entrer un nom d'utilisateur et un mot de passe figurant dans cette liste pour consulter le contenu publié.", + "publishServiceAuthAccountAdd": "Ajouter un compte", "copyMirror": "Copier le miroir", "duplicateMirror": "Miroir en double", "duplicateCompletely": "Dupliquer complètement", diff --git a/app/appearance/langs/ja_JP.json b/app/appearance/langs/ja_JP.json index 2fe7c88b193..039e1e04e30 100644 --- a/app/appearance/langs/ja_JP.json +++ b/app/appearance/langs/ja_JP.json @@ -1,4 +1,17 @@ { + "publish": "公開する", + "publishService": "サービスを公開する", + "publishServiceNotStarted": "サービスが開始されていません", + "publishServiceTip": "有効にすると、サービスを開始します。このサービスは、現在のワークスペースの内容を読み取り専用モードでローカルネットワークに公開します", + "publishServicePort": "サービスポート", + "publishServicePortTip": "指定したポート番号を使用してサービスを有効にします。0に設定するとランダムなポートが使用されます", + "publishServiceAddresses": "サービスアドレス", + "publishServiceAddressesTip": "サービスを公開することが可能なネットワークアドレス", + "publishServiceAuth": "サービスの基本認証", + "publishServiceAuthTip": "有効にすると、公開サービスへのアクセス時に認証が必要になります", + "publishServiceAuthAccounts": "認証アカウント", + "publishServiceAuthAccountsTip": "基本認証アカウントのリスト。基本認証を有効にした場合、訪問者はリスト内のユーザー名とパスワードを入力して公開内容を表示することができます", + "publishServiceAuthAccountAdd": "アカウントを追加する", "copyMirror": "ミラーをコピー", "duplicateMirror": "ミラーを複製", "duplicateCompletely": "完全に複製", @@ -1523,4 +1536,4 @@ "247": "ファイル [%s] は制限サイズ [%s] を超えているためアップロードされませんでした", "248": "目標の見出しがコンテナブロック内にあるためドロップできません" } -} \ No newline at end of file +} diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index b739f8a7653..8147efa9180 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -1,4 +1,17 @@ { + "publish": "發布", + "publishService": "發布服務", + "publishServiceNotStarted": "發布服務未啟動", + "publishServiceTip": "啟用後將啟動發布服務。該服務以只讀模式在區域網中發布當前工作空間的內容", + "publishServicePort": "服務端口號", + "publishServicePortTip": "使用指定的端口號啟用發布服務。若設置為 0 則使用隨機端口", + "publishServiceAddresses": "服務訪問地址", + "publishServiceAddressesTip": "可能訪問到發布服務的網路地址", + "publishServiceAuth": "服務 Basic 認證", + "publishServiceAuthTip": "啟用後在訪問發布服務時需要進行認證", + "publishServiceAuthAccounts": "認證帳戶", + "publishServiceAuthAccountsTip": "Basic 認證帳戶列表。啟用 Basic 認證後訪問者輸入列表中的用戶名與密碼後才能查看發布的內容", + "publishServiceAuthAccountAdd": "添加帳戶", "copyMirror": "複製鏡像", "duplicateMirror": "複製為鏡像副本", "duplicateCompletely": "複製為完整副本", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index 41c82d39b73..a049a8095b9 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -1,4 +1,17 @@ { + "publish": "发布", + "publishService": "发布服务", + "publishServiceNotStarted": "发布服务未启动", + "publishServiceTip": "启用后将启动发布服务。该服务以只读模式在局域网中发布当前工作空间的内容", + "publishServicePort": "服务端口号", + "publishServicePortTip": "使用指定的端口号启用发布服务。若设置为 0 则使用随机端口", + "publishServiceAddresses": "服务访问地址", + "publishServiceAddressesTip": "可能访问到发布服务的网络地址", + "publishServiceAuth": "服务 Basic 认证", + "publishServiceAuthTip": "启用后访问者在访问发布服务时需要使用用户名与密码进行认证", + "publishServiceAuthAccounts": "认证账户", + "publishServiceAuthAccountsTip": "Basic 认证账户列表。访问者输入列表中的用户名与密码后才能查看发布的内容", + "publishServiceAuthAccountAdd": "添加账户", "copyMirror": "复制镜像", "duplicateMirror": "复制为镜像副本", "duplicateCompletely": "复制为完整副本", diff --git a/app/guide/20210808180117-6v0mkxr/.gitignore b/app/guide/20210808180117-6v0mkxr/.gitignore deleted file mode 100644 index 5e86e558d03..00000000000 --- a/app/guide/20210808180117-6v0mkxr/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.idea/ -.siyuan/history/ diff --git a/app/guide/20210808180117-6v0mkxr/.siyuan/sort.json b/app/guide/20210808180117-6v0mkxr/.siyuan/sort.json index a61e32b0b0a..cfb82eeda54 100644 --- a/app/guide/20210808180117-6v0mkxr/.siyuan/sort.json +++ b/app/guide/20210808180117-6v0mkxr/.siyuan/sort.json @@ -1 +1 @@ -{"20200923234011-ieuun1p":1,"20200923234602-gy54e67":8,"20200923234731-h3zkwm2":3,"20200924093441-ft2rhps":1,"20200924095938-a9p5450":2,"20200924100110-vcg96wy":1,"20200924100635-ms0p9lb":6,"20200924100717-yzwzn64":21,"20200924100744-br924ar":10,"20200924100808-j9sddk9":2,"20200924100906-0u4zfq3":4,"20200924100950-9op5xi1":18,"20200924101106-19z4kaa":1,"20200924101200-gss5vee":4,"20200924101225-k254i8g":2,"20200924101256-f8b1sbi":3,"20201004194026-s8h2cog":19,"20201117112518-dott91x":6,"20201121224345-rc27qvo":9,"20201204184532-3qm9l8n":11,"20201210233038-3xr19g5":5,"20201222100222-q47d64s":3,"20201222100339-i5hzcph":2,"20201227201128-m1wrouw":20,"20201227201751-gv0fpx2":22,"20210110181011-fbhoesf":5,"20210117215840-jcl17fx":4,"20210127203829-qe2mzof":12,"20210331201142-4g923es":14,"20210505164949-c085p1d":3,"20210613191509-cbkxcbz":7,"20210615213222-vs5tzbd":15,"20210721112159-9p645xm":1,"20210721112206-mhr9wxi":2,"20210721160238-yvhbh0h":4,"20210808180303-6yi0dv5":1,"20210808180303-axh6q1d":4,"20210808180303-h361q1i":2,"20210808180303-l3qg72k":3,"20210808180303-xaduj2o":5,"20210824202056-udkf7wg":8,"20211010212318-3wx2kqb":13,"20220105101227-n5zpr1a":6,"20220415232231-pqcizol":1,"20220628204454-hhxohv5":2,"20220708103401-mgydrfg":3,"20221016204105-qx2aq0g":3,"20221223221636-ms2b4w9":16,"20230104152135-1iei0xa":23,"20230106104821-9nfphwm":1,"20230304000547-ibldj1z":17,"20230405172236-pg3l9eu":6,"20230429115711-ejbts4s":5,"20230506205948-yah52eb":9,"20230802114825-2jkkct7":5,"20230805231614-vqn28eh":7,"20230805231816-h1z9mpc":2,"20230805232018-hgrq0ju":1,"20230805232134-3d6mx2k":2,"20240113110040-7sgw8kl":2,"20240119211017-1vbbt95":4,"20240119212048-0huuevw":5,"20240208172514-9dsv6na":7,"20240317202444-5txwumu":7} \ No newline at end of file +{"20200923234011-ieuun1p":1,"20200923234602-gy54e67":8,"20200923234731-h3zkwm2":3,"20200924093441-ft2rhps":1,"20200924095938-a9p5450":2,"20200924100110-vcg96wy":1,"20200924100635-ms0p9lb":6,"20200924100717-yzwzn64":21,"20200924100744-br924ar":10,"20200924100808-j9sddk9":2,"20200924100906-0u4zfq3":4,"20200924100950-9op5xi1":18,"20200924101106-19z4kaa":1,"20200924101200-gss5vee":4,"20200924101225-k254i8g":2,"20200924101256-f8b1sbi":3,"20201004194026-s8h2cog":19,"20201117112518-dott91x":6,"20201121224345-rc27qvo":9,"20201204184532-3qm9l8n":11,"20201210233038-3xr19g5":5,"20201222100222-q47d64s":3,"20201222100339-i5hzcph":2,"20201227201128-m1wrouw":20,"20201227201751-gv0fpx2":22,"20210110181011-fbhoesf":5,"20210117215840-jcl17fx":4,"20210127203829-qe2mzof":12,"20210331201142-4g923es":14,"20210505164949-c085p1d":3,"20210613191509-cbkxcbz":7,"20210615213222-vs5tzbd":15,"20210721112159-9p645xm":1,"20210721112206-mhr9wxi":2,"20210721160238-yvhbh0h":4,"20210808180303-6yi0dv5":1,"20210808180303-axh6q1d":4,"20210808180303-h361q1i":2,"20210808180303-l3qg72k":3,"20210808180303-xaduj2o":5,"20210824202056-udkf7wg":8,"20211010212318-3wx2kqb":13,"20220105101227-n5zpr1a":6,"20220415232231-pqcizol":1,"20220628204454-hhxohv5":2,"20220708103401-mgydrfg":3,"20221016204105-qx2aq0g":3,"20221223221636-ms2b4w9":16,"20230104152135-1iei0xa":23,"20230106104821-9nfphwm":1,"20230304000547-ibldj1z":17,"20230405172236-pg3l9eu":6,"20230429115711-ejbts4s":5,"20230506205948-yah52eb":9,"20230802114825-2jkkct7":5,"20230805231614-vqn28eh":7,"20230805231816-h1z9mpc":2,"20230805232018-hgrq0ju":1,"20230805232134-3d6mx2k":2,"20240113110040-7sgw8kl":2,"20240119211017-1vbbt95":4,"20240119212048-0huuevw":5,"20240208172514-9dsv6na":7,"20240317202444-5txwumu":7,"20240517031132-jmr5ihm":24} \ No newline at end of file diff --git a/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20240517031132-jmr5ihm.sy b/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20240517031132-jmr5ihm.sy new file mode 100644 index 00000000000..afef5d773c7 --- /dev/null +++ b/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20240517031132-jmr5ihm.sy @@ -0,0 +1,526 @@ +{ + "ID": "20240517031132-jmr5ihm", + "Spec": "1", + "Type": "NodeDocument", + "Properties": { + "id": "20240517031132-jmr5ihm", + "title": "Publish service", + "type": "doc", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-um049u9", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240517031304-um049u9", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "Overview" + } + ] + }, + { + "ID": "20240517031304-kebcgzv", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-kebcgzv", + "updated": "20240517031305" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "Joplin supports publishing the content of the current workspace in read-only mode on the local area network." + } + ] + }, + { + "ID": "20240517031304-7e0kvj3", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240517031304-7e0kvj3", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "Usage" + } + ] + }, + { + "ID": "20240517031304-gt5qj60", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031304-gt5qj60", + "updated": "20240517031305" + }, + "Children": [ + { + "ID": "20240517031304-sjppgk3", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-sjppgk3", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-r9a0anm", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-r9a0anm", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "Open " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "Settings" + }, + { + "Type": "NodeText", + "Data": "​ - " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "Publish" + }, + { + "Type": "NodeText", + "Data": "​ to enter the publishing service settings panel." + } + ] + } + ] + }, + { + "ID": "20240517031304-u6inl9h", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-u6inl9h", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-ye7ittw", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-ye7ittw", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "Set the " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "Server port" + }, + { + "Type": "NodeText", + "Data": "​" + } + ] + }, + { + "ID": "20240517031304-olvdqhj", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031304-olvdqhj", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-1ttdtl4", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-1ttdtl4", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-sl59b81", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-sl59b81", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "The default port number is " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "code", + "TextMarkTextContent": "6808" + }, + { + "Type": "NodeText", + "Data": "​." + } + ] + } + ] + }, + { + "ID": "20240517031304-t37dfgj", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-t37dfgj", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-mxx5df4", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-mxx5df4", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "If the port number is set to " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "code", + "TextMarkTextContent": "0" + }, + { + "Type": "NodeText", + "Data": "​, a random port will be used." + } + ] + } + ] + } + ] + } + ] + }, + { + "ID": "20240517031304-y4kcmaf", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-y4kcmaf", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-5qilsih", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-5qilsih", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "If access control is required for the publishing service:" + } + ] + }, + { + "ID": "20240517031304-659m8fi", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031304-659m8fi", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-xh0kq2y", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-xh0kq2y", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-lay209u", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-lay209u", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "Add " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "Authenticated accounts" + }, + { + "Type": "NodeText", + "Data": "​ and enable the " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "Service basic authentication" + }, + { + "Type": "NodeText", + "Data": "​ switch." + } + ] + } + ] + }, + { + "ID": "20240517031304-ri1nm5j", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-ri1nm5j", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-4y267ml", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-4y267ml", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "When enabled, the publishing service will use the " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "a", + "TextMarkAHref": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme", + "TextMarkTextContent": "Basic authentication scheme" + }, + { + "Type": "NodeText", + "Data": " to authenticate visitors." + } + ] + }, + { + "ID": "20240517031304-h61xxm7", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031304-h61xxm7", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-1k96w0z", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-1k96w0z", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-l5hafpz", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-l5hafpz", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "Visitors must enter the username and password set in the \"Authentication Accounts\" before browsing the published content." + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "ID": "20240517031304-0nps2p8", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-0nps2p8", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-y16fc8r", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-y16fc8r", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "Enable the " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "Publish service" + }, + { + "Type": "NodeText", + "Data": "​ switch." + } + ] + } + ] + } + ] + }, + { + "ID": "20240517031304-6cy4lbq", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240517031304-6cy4lbq", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "Note" + } + ] + }, + { + "ID": "20240517031304-3slh48m", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031304-3slh48m", + "updated": "20240517031305" + }, + "Children": [ + { + "ID": "20240517031304-uquw6i6", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-uquw6i6", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-j38u7hu", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-j38u7hu", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "After enabling the publishing service, visitors can browse the content of the entire workspace." + } + ] + } + ] + }, + { + "ID": "20240517031304-c6iqquc", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031304-c6iqquc", + "updated": "20240517031304" + }, + "Children": [ + { + "ID": "20240517031304-1qsa7qz", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031304-1qsa7qz", + "updated": "20240517031304" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "When " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "Service basic authentication" + }, + { + "Type": "NodeText", + "Data": "​ is disabled, all visitors can browse the content of the entire workspace without authentication." + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/app/guide/20210808180117-czj9bvb/.gitignore b/app/guide/20210808180117-czj9bvb/.gitignore deleted file mode 100644 index 5e86e558d03..00000000000 --- a/app/guide/20210808180117-czj9bvb/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.idea/ -.siyuan/history/ diff --git a/app/guide/20210808180117-czj9bvb/.siyuan/sort.json b/app/guide/20210808180117-czj9bvb/.siyuan/sort.json index 90390d00071..8be2ae31d0e 100644 --- a/app/guide/20210808180117-czj9bvb/.siyuan/sort.json +++ b/app/guide/20210808180117-czj9bvb/.siyuan/sort.json @@ -1 +1 @@ -{"20200812220555-lj3enxa":1,"20200813004551-gm0pbn1":18,"20200813004931-q4cu8na":1,"20200813013559-sgbzl5k":3,"20200813093015-u6bopdt":3,"20200813125307-pxsjela":2,"20200813131152-0wk5akh":4,"20200813163359-v04n73b":10,"20200822191536-rm6hwid":4,"20200825162036-4dx365o":1,"20200828105441-r76vmu5":21,"20200905090211-2vixtlf":2,"20200910201551-h4twhas":6,"20200915214115-42b8zma":10,"20200922102318-oz84yu3":2,"20201004184819-nj8ibyg":19,"20201117101902-2ewjjum":6,"20201121212605-9td1a62":11,"20201204181006-7bkppue":11,"20201210103036-1x3vm8t":5,"20201222093044-rx4zjoy":2,"20201222095049-hghafhe":3,"20201227173504-847cs1q":20,"20201227194925-7ipoiv6":22,"20210110175347-2xrwoiq":5,"20210117211155-56n4odu":4,"20210127202655-2334vvv":12,"20210331200042-94gs1hh":14,"20210505163537-oo97zov":3,"20210612224500-ywcms1m":7,"20210615211733-v6rzowm":15,"20210808180320-abz7w6k":2,"20210808180320-fqgskfj":1,"20210808180320-gyngv2x":3,"20210808180320-qgr0b3q":5,"20210808180321-hbvl5c2":6,"20210824201257-cy7icrc":8,"20211010211311-ffz0wbu":13,"20220415190432-r3xqn3r":1,"20220628204444-9n0y9h2":2,"20221016213308-uz5af79":3,"20221223215557-o6gfsoy":16,"20230104144904-39br4c6":23,"20230106101434-e6g4av3":1,"20230303235619-ex5l63e":17,"20230405155631-leo4vc6":6,"20230428153709-hioyy5l":7,"20230429114837-70asb4j":5,"20230506210010-houyyvy":9,"20230519105228-hm0y74i":8,"20230805222417-2lj3dvk":7,"20230805225107-qm1m2f5":2,"20230805230131-sn7obzb":1,"20230805230218-aea8icj":2,"20230808120347-3cob0nb":2,"20230808120347-mw3qrwy":4,"20230808120347-pzvmkik":1,"20230808120348-hynr7og":5,"20230808120348-lgcp9zm":3,"20230808120348-vaxi6eq":6,"20230808120348-yut741f":7,"20240113102857-c63dmo5":2,"20240119205452-o8xp4ve":4,"20240119205543-hknwwrl":5,"20240208113259-nykkvaq":7,"20240317200013-fim8wm8":9} \ No newline at end of file +{"20200812220555-lj3enxa":1,"20200813004551-gm0pbn1":18,"20200813004931-q4cu8na":1,"20200813013559-sgbzl5k":3,"20200813093015-u6bopdt":3,"20200813125307-pxsjela":2,"20200813131152-0wk5akh":4,"20200813163359-v04n73b":10,"20200822191536-rm6hwid":4,"20200825162036-4dx365o":1,"20200828105441-r76vmu5":21,"20200905090211-2vixtlf":2,"20200910201551-h4twhas":6,"20200915214115-42b8zma":10,"20200922102318-oz84yu3":2,"20201004184819-nj8ibyg":19,"20201117101902-2ewjjum":6,"20201121212605-9td1a62":11,"20201204181006-7bkppue":11,"20201210103036-1x3vm8t":5,"20201222093044-rx4zjoy":2,"20201222095049-hghafhe":3,"20201227173504-847cs1q":20,"20201227194925-7ipoiv6":22,"20210110175347-2xrwoiq":5,"20210117211155-56n4odu":4,"20210127202655-2334vvv":12,"20210331200042-94gs1hh":14,"20210505163537-oo97zov":3,"20210612224500-ywcms1m":7,"20210615211733-v6rzowm":15,"20210808180320-abz7w6k":2,"20210808180320-fqgskfj":1,"20210808180320-gyngv2x":3,"20210808180320-qgr0b3q":5,"20210808180321-hbvl5c2":6,"20210824201257-cy7icrc":8,"20211010211311-ffz0wbu":13,"20220415190432-r3xqn3r":1,"20220628204444-9n0y9h2":2,"20221016213308-uz5af79":3,"20221223215557-o6gfsoy":16,"20230104144904-39br4c6":23,"20230106101434-e6g4av3":1,"20230303235619-ex5l63e":17,"20230405155631-leo4vc6":6,"20230428153709-hioyy5l":7,"20230429114837-70asb4j":5,"20230506210010-houyyvy":9,"20230519105228-hm0y74i":8,"20230805222417-2lj3dvk":7,"20230805225107-qm1m2f5":2,"20230805230131-sn7obzb":1,"20230805230218-aea8icj":2,"20230808120347-3cob0nb":2,"20230808120347-mw3qrwy":4,"20230808120347-pzvmkik":1,"20230808120348-hynr7og":5,"20230808120348-lgcp9zm":3,"20230808120348-vaxi6eq":6,"20230808120348-yut741f":7,"20240113102857-c63dmo5":2,"20240119205452-o8xp4ve":4,"20240119205543-hknwwrl":5,"20240208113259-nykkvaq":7,"20240317200013-fim8wm8":9,"20240517031028-xqinazo":24} \ No newline at end of file diff --git a/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa/20210808180321-hbvl5c2/20240517031028-xqinazo.sy b/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa/20210808180321-hbvl5c2/20240517031028-xqinazo.sy new file mode 100644 index 00000000000..9e2bbbc1862 --- /dev/null +++ b/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa/20210808180321-hbvl5c2/20240517031028-xqinazo.sy @@ -0,0 +1,540 @@ +{ + "ID": "20240517031028-xqinazo", + "Spec": "1", + "Type": "NodeDocument", + "Properties": { + "id": "20240517031028-xqinazo", + "title": "发布服务", + "type": "doc", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-7ox0m9z", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240517031044-7ox0m9z", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "概述" + } + ] + }, + { + "ID": "20240517031044-6mppc0k", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-6mppc0k", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "思源笔记支持以只读模式在局域网中发布当前工作空间的内容。" + } + ] + }, + { + "ID": "20240517031044-anhkwow", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240517031044-anhkwow", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "使用方式" + } + ] + }, + { + "ID": "20240517031044-0vj6or7", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031044-0vj6or7", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-8ugboo7", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-8ugboo7", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-grogeue", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-grogeue", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "打开 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "设置" + }, + { + "Type": "NodeText", + "Data": "​ - " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "发布" + }, + { + "Type": "NodeText", + "Data": "​ 进入发布服务的设置面板" + } + ] + } + ] + }, + { + "ID": "20240517031044-mta08zv", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-mta08zv", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-7dg9xy9", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-7dg9xy9", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "设置 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "服务端口号" + }, + { + "Type": "NodeText", + "Data": "​" + } + ] + }, + { + "ID": "20240517031044-vzj8o1a", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031044-vzj8o1a", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-t2igim6", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-t2igim6", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-8ghv9tj", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-8ghv9tj", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "默认的端口号为 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "code", + "TextMarkTextContent": "6808" + }, + { + "Type": "NodeText", + "Data": "​" + } + ] + } + ] + }, + { + "ID": "20240517031044-3oto1iz", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-3oto1iz", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-0m68jzx", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-0m68jzx", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "若端口号设置为 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "code", + "TextMarkTextContent": "0" + }, + { + "Type": "NodeText", + "Data": "​ 将使用随机端口" + } + ] + } + ] + } + ] + } + ] + }, + { + "ID": "20240517031044-ddii8sb", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-ddii8sb", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-vaiskyz", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-vaiskyz", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "若需要对发布服务进行访问控制" + } + ] + }, + { + "ID": "20240517031044-ze7l6sg", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031044-ze7l6sg", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-fxpcxvc", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-fxpcxvc", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-bno9bca", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-bno9bca", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "添加 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "认证账户" + }, + { + "Type": "NodeText", + "Data": "​ 并开启 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "服务 Basic 认证" + }, + { + "Type": "NodeText", + "Data": "​ 开关" + } + ] + } + ] + }, + { + "ID": "20240517031044-29kw4ks", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-29kw4ks", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-g05n8gc", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-g05n8gc", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "开启后发布服务将使用 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "a", + "TextMarkAHref": "https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Authentication#basic_验证方案", + "TextMarkTextContent": "Basic 验证方案" + }, + { + "Type": "NodeText", + "Data": " 对访问者进行认证" + } + ] + }, + { + "ID": "20240517031044-e54f9vf", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031044-e54f9vf", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-kr1t232", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-kr1t232", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-rf8nyba", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-rf8nyba", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "访问者在浏览发布内容前必须正确输入 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "认证账户" + }, + { + "Type": "NodeText", + "Data": "​ 中设置的用户名与密码" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "ID": "20240517031044-s5d5aak", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-s5d5aak", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-ec9vaac", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-ec9vaac", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "开启 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "发布服务" + }, + { + "Type": "NodeText", + "Data": "​ 开关" + } + ] + } + ] + } + ] + }, + { + "ID": "20240517031044-6le4t1r", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240517031044-6le4t1r", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeTextMark", + "TextMarkType": "tag", + "TextMarkTextContent": "注意" + }, + { + "Type": "NodeText", + "Data": "​" + } + ] + }, + { + "ID": "20240517031044-ahzjzvg", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031044-ahzjzvg", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-8f3g2fd", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-8f3g2fd", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-uiys91u", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-uiys91u", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "开启发布服务后访问者可以浏览整个工作空间的内容" + } + ] + } + ] + }, + { + "ID": "20240517031044-979coq3", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031044-979coq3", + "updated": "20240517031044" + }, + "Children": [ + { + "ID": "20240517031044-7be46qj", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031044-7be46qj", + "updated": "20240517031044" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "关闭 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "服务 Basic 认证" + }, + { + "Type": "NodeText", + "Data": "​ 后所有的访问者无需认证即可浏览整个工作空间的内容" + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/app/guide/20211226090932-5lcq56f/.gitignore b/app/guide/20211226090932-5lcq56f/.gitignore deleted file mode 100644 index 5e86e558d03..00000000000 --- a/app/guide/20211226090932-5lcq56f/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.idea/ -.siyuan/history/ diff --git a/app/guide/20211226090932-5lcq56f/.siyuan/sort.json b/app/guide/20211226090932-5lcq56f/.siyuan/sort.json index b16c52c8f60..30845523009 100644 --- a/app/guide/20211226090932-5lcq56f/.siyuan/sort.json +++ b/app/guide/20211226090932-5lcq56f/.siyuan/sort.json @@ -1 +1 @@ -{"20211226114339-dk0gtpr":8,"20211226114929-08ap1r0":9,"20211226115043-afhev0g":4,"20211226115227-r1rty9v":3,"20211226115423-d5z1joq":1,"20211226115825-mhcslw2":1,"20211226120055-9mityht":1,"20211226120147-ib6yy3i":2,"20211226120247-63nd8y5":3,"20211226120349-rbkmozu":4,"20211226120422-bkzsd2e":5,"20211226120508-yzh70eh":6,"20211226120802-77aj0is":7,"20211226120854-dr1jfx2":2,"20211226120933-vnjgwwh":3,"20211226121109-f060fkg":4,"20211226121203-rjjngpz":5,"20211226121319-emrk2yy":1,"20211226121322-9argcys":3,"20211226121329-c5v3dto":22,"20211226121332-irgblss":4,"20211226121438-xaafdo8":2,"20211226121503-k3jma6m":1,"20211226121808-fnxmngk":2,"20211226122358-hctqcn5":21,"20211226122459-08mi5cq":20,"20211226122523-rl8356a":19,"20211226122549-jktxego":18,"20211226122707-8cr09co":15,"20211226122728-cnqf7rz":14,"20211226122814-r1rdpcx":13,"20211226122943-st7fpcj":12,"20211226123004-dplpw0o":11,"20211226123038-4umgpxy":10,"20211226123101-qjw03ab":8,"20211226123130-jpeg5b2":6,"20211226123154-fd5e001":5,"20211226123216-tlxw66f":4,"20211226123241-51pujtr":3,"20211226123302-akitvb1":2,"20220105101348-corstqc":6,"20220415232129-shpzg6r":1,"20220628204420-ui79vkt":2,"20220708102441-u6wopo9":3,"20221016213639-1nag9jj":3,"20221223221501-mops33i":16,"20230104151953-48hwkwf":23,"20230106104645-o838uew":1,"20230304000829-9jwu3po":17,"20230405172131-yb16aax":6,"20230429115206-ob8nl8t":5,"20230506211210-1roopyo":9,"20230805232636-zh0adz2":6,"20230805232719-04mqbcx":2,"20230805232903-erdoerp":1,"20230805232920-5fdco36":2,"20240113110500-dz2ae4n":2,"20240119210914-a2tm8c4":4,"20240119212000-qkldbjm":5,"20240208171522-y7dxcno":7,"20240317202230-l8duv3r":7,"20240508010647-8nuyk31":5} \ No newline at end of file +{"20211226114339-dk0gtpr":8,"20211226114929-08ap1r0":9,"20211226115043-afhev0g":4,"20211226115227-r1rty9v":3,"20211226115423-d5z1joq":1,"20211226115825-mhcslw2":1,"20211226120055-9mityht":1,"20211226120147-ib6yy3i":2,"20211226120247-63nd8y5":3,"20211226120349-rbkmozu":4,"20211226120422-bkzsd2e":5,"20211226120508-yzh70eh":6,"20211226120802-77aj0is":7,"20211226120854-dr1jfx2":2,"20211226120933-vnjgwwh":3,"20211226121109-f060fkg":4,"20211226121203-rjjngpz":5,"20211226121319-emrk2yy":1,"20211226121322-9argcys":3,"20211226121329-c5v3dto":22,"20211226121332-irgblss":4,"20211226121438-xaafdo8":2,"20211226121503-k3jma6m":1,"20211226121808-fnxmngk":2,"20211226122358-hctqcn5":21,"20211226122459-08mi5cq":20,"20211226122523-rl8356a":19,"20211226122549-jktxego":18,"20211226122707-8cr09co":15,"20211226122728-cnqf7rz":14,"20211226122814-r1rdpcx":13,"20211226122943-st7fpcj":12,"20211226123004-dplpw0o":11,"20211226123038-4umgpxy":10,"20211226123101-qjw03ab":8,"20211226123130-jpeg5b2":6,"20211226123154-fd5e001":5,"20211226123216-tlxw66f":4,"20211226123241-51pujtr":3,"20211226123302-akitvb1":2,"20220105101348-corstqc":6,"20220415232129-shpzg6r":1,"20220628204420-ui79vkt":2,"20220708102441-u6wopo9":3,"20221016213639-1nag9jj":3,"20221223221501-mops33i":16,"20230104151953-48hwkwf":23,"20230106104645-o838uew":1,"20230304000829-9jwu3po":17,"20230405172131-yb16aax":6,"20230429115206-ob8nl8t":5,"20230506211210-1roopyo":9,"20230805232636-zh0adz2":6,"20230805232719-04mqbcx":2,"20230805232903-erdoerp":1,"20230805232920-5fdco36":2,"20240113110500-dz2ae4n":2,"20240119210914-a2tm8c4":4,"20240119212000-qkldbjm":5,"20240208171522-y7dxcno":7,"20240317202230-l8duv3r":7,"20240508010647-8nuyk31":5,"20240517031052-mgoxs16":24} \ No newline at end of file diff --git a/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq/20211226121203-rjjngpz/20240517031052-mgoxs16.sy b/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq/20211226121203-rjjngpz/20240517031052-mgoxs16.sy new file mode 100644 index 00000000000..f5b1cf78119 --- /dev/null +++ b/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq/20211226121203-rjjngpz/20240517031052-mgoxs16.sy @@ -0,0 +1,540 @@ +{ + "ID": "20240517031052-mgoxs16", + "Spec": "1", + "Type": "NodeDocument", + "Properties": { + "id": "20240517031052-mgoxs16", + "title": "發布服務", + "type": "doc", + "updated": "20240517031115" + }, + "Children": [ + { + "ID": "20240517031115-pjkrctx", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240517031115-pjkrctx", + "updated": "20240517031115" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "概述" + } + ] + }, + { + "ID": "20240517031116-a0xf075", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-a0xf075", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "思源筆記支援以只讀模式在區域網路中發佈當前工作空間的內容。" + } + ] + }, + { + "ID": "20240517031116-ml8jd6n", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240517031116-ml8jd6n", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "使用方式" + } + ] + }, + { + "ID": "20240517031116-5u42tlr", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031116-5u42tlr", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-06mevu7", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-06mevu7", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-wqo2aje", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-wqo2aje", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "開啟 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "設定" + }, + { + "Type": "NodeText", + "Data": "​ - " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "發佈" + }, + { + "Type": "NodeText", + "Data": "​ 進入發佈服務的設定面板" + } + ] + } + ] + }, + { + "ID": "20240517031116-dsvhebl", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-dsvhebl", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-13481er", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-13481er", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "設定 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "服務端口號" + }, + { + "Type": "NodeText", + "Data": "​" + } + ] + }, + { + "ID": "20240517031116-sjbg5cn", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031116-sjbg5cn", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-z1q43ui", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-z1q43ui", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-ssdjmbf", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-ssdjmbf", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "默認的端口號為 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "code", + "TextMarkTextContent": "6808" + }, + { + "Type": "NodeText", + "Data": "​" + } + ] + } + ] + }, + { + "ID": "20240517031116-jkcxgaj", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-jkcxgaj", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-ka1wome", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-ka1wome", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "若端口號設定為 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "code", + "TextMarkTextContent": "0" + }, + { + "Type": "NodeText", + "Data": "​ 將使用隨機端口" + } + ] + } + ] + } + ] + } + ] + }, + { + "ID": "20240517031116-ipkirs4", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-ipkirs4", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-g8qr29s", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-g8qr29s", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "若需要對發佈服務進行訪問控制" + } + ] + }, + { + "ID": "20240517031116-maj6mmd", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031116-maj6mmd", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-du7rttx", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-du7rttx", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-ct3chrz", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-ct3chrz", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "添加 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "認證帳戶" + }, + { + "Type": "NodeText", + "Data": "​ 並開啟 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "服務 Basic 認證" + }, + { + "Type": "NodeText", + "Data": "​ 開關" + } + ] + } + ] + }, + { + "ID": "20240517031116-dlaep2b", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-dlaep2b", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-3u2v8g7", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-3u2v8g7", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "開啟後發佈服務將使用 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "a", + "TextMarkAHref": "https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Authentication#basic_验证方案", + "TextMarkTextContent": "Basic 驗證方案" + }, + { + "Type": "NodeText", + "Data": " 對訪問者進行認證" + } + ] + }, + { + "ID": "20240517031116-qmzfje7", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031116-qmzfje7", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-xthto7w", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-xthto7w", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-kbz5qye", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-kbz5qye", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "訪問者在瀏覽發佈內容前必須正確輸入 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "認證帳戶" + }, + { + "Type": "NodeText", + "Data": "​ 中設定的用戶名與密碼" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "ID": "20240517031116-bx01v3n", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-bx01v3n", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-44j60lx", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-44j60lx", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "開啟 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "發佈服務" + }, + { + "Type": "NodeText", + "Data": "​ 開關" + } + ] + } + ] + } + ] + }, + { + "ID": "20240517031116-2v0lh0m", + "Type": "NodeHeading", + "HeadingLevel": 3, + "Properties": { + "id": "20240517031116-2v0lh0m", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeTextMark", + "TextMarkType": "tag", + "TextMarkTextContent": "注意" + }, + { + "Type": "NodeText", + "Data": "​" + } + ] + }, + { + "ID": "20240517031116-lx0pe26", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240517031116-lx0pe26", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-kvje8ru", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-kvje8ru", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-gydcx3b", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-gydcx3b", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "開啟發佈服務後訪問者可以瀏覽整個工作空間的內容" + } + ] + } + ] + }, + { + "ID": "20240517031116-ufsul2c", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240517031116-ufsul2c", + "updated": "20240517031116" + }, + "Children": [ + { + "ID": "20240517031116-xyb4012", + "Type": "NodeParagraph", + "Properties": { + "id": "20240517031116-xyb4012", + "updated": "20240517031116" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "關閉 " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "服務 Basic 認證" + }, + { + "Type": "NodeText", + "Data": "​ 後所有的訪問者無需認證即可瀏覽整個工作空間的內容" + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/app/guide/20240530133126-axarxgx/.siyuan/sort.json b/app/guide/20240530133126-axarxgx/.siyuan/sort.json index f312046cefa..79c3184bfbe 100644 --- a/app/guide/20240530133126-axarxgx/.siyuan/sort.json +++ b/app/guide/20240530133126-axarxgx/.siyuan/sort.json @@ -1 +1 @@ -{"20240530101000-0zd9si2":13,"20240530101000-1m5un7l":10,"20240530101000-1vvnhju":6,"20240530101000-35bbvcx":2,"20240530101000-3eaevtp":7,"20240530101000-3ourzoa":2,"20240530101000-3qhz7br":1,"20240530101000-3xv6jjr":9,"20240530101000-40hohog":19,"20240530101000-4cqkoel":23,"20240530101000-4p096e8":1,"20240530101000-4qitucx":1,"20240530101000-58z6rjh":2,"20240530101000-5gi76ax":8,"20240530101000-5k5d5i3":2,"20240530101000-6x9ivi7":11,"20240530101000-75auqcn":6,"20240530101000-78d5w10":4,"20240530101000-7yv4y5z":4,"20240530101000-875snki":7,"20240530101000-8m2dowc":9,"20240530101000-96n5y9v":1,"20240530101000-a91lmk2":3,"20240530101000-bb0qktp":1,"20240530101000-bgv304g":8,"20240530101000-boiita2":4,"20240530101000-bpj42r0":2,"20240530101000-cb37szr":7,"20240530101000-d31i9v5":2,"20240530101000-dro2zi9":5,"20240530101000-dytmky4":22,"20240530101000-e6z5okf":3,"20240530101000-ei5t6tt":17,"20240530101000-f16hpct":20,"20240530101000-flot1gj":7,"20240530101000-g3ugxml":5,"20240530101000-gc0tuyd":3,"20240530101000-gcdp3gl":3,"20240530101000-gvtksyj":1,"20240530101000-h0cp5vx":5,"20240530101000-hkgs92c":5,"20240530101000-hnu6o79":2,"20240530101000-hq9sn3k":1,"20240530101000-jp793ic":4,"20240530101000-mpln2lp":6,"20240530101000-mrlr6e9":4,"20240530101000-na9sys7":1,"20240530101000-odye3ea":3,"20240530101000-qf0xtkd":2,"20240530101000-scr2p21":5,"20240530101000-to5uvpi":12,"20240530101000-txrc7z1":15,"20240530101000-uxno1yp":3,"20240530101000-vejnh1k":1,"20240530101000-vql5s27":5,"20240530101000-wo49zvq":2,"20240530101000-xc13cm6":6,"20240530101000-xq26o73":21,"20240530101000-xr22qn8":4,"20240530101000-xsbxokr":18,"20240530101000-ynpmvl7":16,"20240530101000-zj4k048":3,"20240530101000-znj103k":10,"20240530101000-zprnpfi":14} \ No newline at end of file +{"20240530101000-0zd9si2":13,"20240530101000-1m5un7l":10,"20240530101000-1vvnhju":6,"20240530101000-35bbvcx":2,"20240530101000-3eaevtp":7,"20240530101000-3ourzoa":2,"20240530101000-3qhz7br":1,"20240530101000-3xv6jjr":9,"20240530101000-40hohog":19,"20240530101000-4cqkoel":23,"20240530101000-4p096e8":1,"20240530101000-4qitucx":1,"20240530101000-58z6rjh":2,"20240530101000-5gi76ax":8,"20240530101000-5k5d5i3":2,"20240530101000-6x9ivi7":11,"20240530101000-75auqcn":6,"20240530101000-78d5w10":4,"20240530101000-7yv4y5z":4,"20240530101000-875snki":7,"20240530101000-8m2dowc":9,"20240530101000-96n5y9v":1,"20240530101000-a91lmk2":3,"20240530101000-bb0qktp":1,"20240530101000-bgv304g":8,"20240530101000-boiita2":4,"20240530101000-bpj42r0":2,"20240530101000-cb37szr":7,"20240530101000-d31i9v5":2,"20240530101000-dro2zi9":5,"20240530101000-dytmky4":22,"20240530101000-e6z5okf":3,"20240530101000-ei5t6tt":17,"20240530101000-f16hpct":20,"20240530101000-flot1gj":7,"20240530101000-g3ugxml":5,"20240530101000-gc0tuyd":3,"20240530101000-gcdp3gl":3,"20240530101000-gvtksyj":1,"20240530101000-h0cp5vx":5,"20240530101000-hkgs92c":5,"20240530101000-hnu6o79":2,"20240530101000-hq9sn3k":1,"20240530101000-jp793ic":4,"20240530101000-mpln2lp":6,"20240530101000-mrlr6e9":4,"20240530101000-na9sys7":1,"20240530101000-odye3ea":3,"20240530101000-qf0xtkd":2,"20240530101000-scr2p21":5,"20240530101000-to5uvpi":12,"20240530101000-txrc7z1":15,"20240530101000-uxno1yp":3,"20240530101000-vejnh1k":1,"20240530101000-vql5s27":5,"20240530101000-wo49zvq":2,"20240530101000-xc13cm6":6,"20240530101000-xq26o73":21,"20240530101000-xr22qn8":4,"20240530101000-xsbxokr":18,"20240530101000-ynpmvl7":16,"20240530101000-zj4k048":3,"20240530101000-znj103k":10,"20240530101000-zprnpfi":14,"20240610233601-ylh1mvk":24} \ No newline at end of file diff --git a/app/guide/20240530133126-axarxgx/20240530101000-4qitucx/20240530101000-g3ugxml/20240610233601-ylh1mvk.sy b/app/guide/20240530133126-axarxgx/20240530101000-4qitucx/20240530101000-g3ugxml/20240610233601-ylh1mvk.sy new file mode 100644 index 00000000000..f05e8f801b1 --- /dev/null +++ b/app/guide/20240530133126-axarxgx/20240530101000-4qitucx/20240530101000-g3ugxml/20240610233601-ylh1mvk.sy @@ -0,0 +1,676 @@ +{ + "ID": "20240610233601-ylh1mvk", + "Spec": "1", + "Type": "NodeDocument", + "Properties": { + "id": "20240610233601-ylh1mvk", + "title": "サービスの公開", + "type": "doc", + "updated": "20240610235250" + }, + "Children": [ + { + "ID": "20240610233629-jjii9tu", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240610233629-jjii9tu", + "updated": "20240610235250" + }, + "Children": [ + { + "Type": "NodeHeadingC8hMarker", + "Data": "## ", + "Properties": { + "id": "" + } + }, + { + "Type": "NodeText", + "Data": "概要", + "Properties": { + "id": "" + } + } + ] + }, + { + "ID": "20240610233629-49em84g", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-49em84g", + "updated": "20240610233739" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "SiYuan Note は、現在のワークスペースの内容を読み取り専用モードでローカルネットワークで公開することができます。", + "Properties": { + "id": "" + } + } + ] + }, + { + "ID": "20240610233629-bxuf2mx", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240610233629-bxuf2mx", + "updated": "20240610235250" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "使い方" + } + ] + }, + { + "ID": "20240610233629-7d7e66r", + "Type": "NodeList", + "ListData": { + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-7d7e66r", + "updated": "20240610235250" + }, + "Children": [ + { + "ID": "20240610233629-jn7c5xq", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-jn7c5xq", + "updated": "20240610234354" + }, + "Children": [ + { + "ID": "20240610233629-ibn4bp6", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-ibn4bp6", + "updated": "20240610234354" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "​" + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "設定" + }, + { + "Type": "NodeText", + "Data": "​ - " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "公開する" + }, + { + "Type": "NodeText", + "Data": "​ に移動して、公開サービスの設定パネルに入ります。" + } + ] + } + ] + }, + { + "ID": "20240610233629-j8ml6bw", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-j8ml6bw", + "updated": "20240610234344" + }, + "Children": [ + { + "ID": "20240610233629-fubq5bz", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-fubq5bz", + "updated": "20240610234344" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "​" + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "サービスポート" + }, + { + "Type": "NodeText", + "Data": "​ を設定します。" + } + ] + }, + { + "ID": "20240610233629-d9q97le", + "Type": "NodeList", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-d9q97le", + "updated": "20240610233629" + }, + "Children": [ + { + "ID": "20240610233629-y8ynj1h", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-y8ynj1h", + "updated": "20240610233629" + }, + "Children": [ + { + "ID": "20240610233629-fwe6fgu", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-fwe6fgu", + "updated": "20240610233629" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "デフォルトのポート番号は ", + "Properties": { + "id": "" + } + }, + { + "Type": "NodeTextMark", + "Properties": { + "id": "" + }, + "TextMarkType": "code", + "TextMarkTextContent": "6808" + }, + { + "Type": "NodeText", + "Data": "​ です。", + "Properties": { + "id": "" + } + } + ] + } + ] + }, + { + "ID": "20240610233629-amkxqp8", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-amkxqp8", + "updated": "20240610233629" + }, + "Children": [ + { + "ID": "20240610233629-726ai0u", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-726ai0u", + "updated": "20240610233629" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "ポート番号を ", + "Properties": { + "id": "" + } + }, + { + "Type": "NodeTextMark", + "Properties": { + "id": "" + }, + "TextMarkType": "code", + "TextMarkTextContent": "0" + }, + { + "Type": "NodeText", + "Data": "​ に設定すると、ランダムなポートが使用されます。", + "Properties": { + "id": "" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "ID": "20240610233629-c2bncke", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-c2bncke", + "updated": "20240610235250" + }, + "Children": [ + { + "ID": "20240610233629-c02853s", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-c02853s", + "updated": "20240610233629" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "公開サービスにアクセス制限を設定する場合", + "Properties": { + "id": "" + } + } + ] + }, + { + "ID": "20240610233629-a5alsxq", + "Type": "NodeList", + "ListData": { + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-a5alsxq", + "updated": "20240610235250" + }, + "Children": [ + { + "ID": "20240610233629-4p8jyop", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-4p8jyop", + "updated": "20240610235142" + }, + "Children": [ + { + "ID": "20240610233629-6pki0nz", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-6pki0nz", + "updated": "20240610235142" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "​" + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "認証アカウント" + }, + { + "Type": "NodeText", + "Data": "​ を追加し、" + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "サービスの基本認証" + }, + { + "Type": "NodeText", + "Data": "​ スイッチをオンにします。" + } + ] + } + ] + }, + { + "ID": "20240610233629-3s2kd31", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-3s2kd31", + "updated": "20240610235250" + }, + "Children": [ + { + "ID": "20240610233629-fozs5am", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-fozs5am", + "updated": "20240610233721" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "この後、公開サービスはアクセスするユーザーを ", + "Properties": { + "id": "" + } + }, + { + "Type": "NodeTextMark", + "Properties": { + "id": "" + }, + "TextMarkType": "a", + "TextMarkAHref": "https://developer.mozilla.org/ja/docs/Web/HTTP/Authentication#basic_認証方式", + "TextMarkTextContent": "Basic 認証方式" + }, + { + "Type": "NodeText", + "Data": " を使用して認証します。", + "Properties": { + "id": "" + } + } + ] + }, + { + "ID": "20240610233629-b18hlwj", + "Type": "NodeList", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-b18hlwj", + "updated": "20240610235250" + }, + "Children": [ + { + "ID": "20240610233629-4mziigo", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-4mziigo", + "updated": "20240610235250" + }, + "Children": [ + { + "ID": "20240610233629-526pequ", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-526pequ", + "updated": "20240610235250" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "ユーザーは、公開コンテンツを閲覧する前に、" + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "認証アカウント" + }, + { + "Type": "NodeText", + "Data": "​ で設定したユーザー名とパスワードを正しく入力する必要があります。" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "ID": "20240610233629-a4d5gct", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-a4d5gct", + "updated": "20240610234926" + }, + "Children": [ + { + "ID": "20240610233629-cqspvm0", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-cqspvm0", + "updated": "20240610234926" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "​" + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "サービスを公開する" + }, + { + "Type": "NodeText", + "Data": "​ スイッチをオンにします" + } + ] + } + ] + } + ] + }, + { + "ID": "20240610233629-ns08k8s", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240610233629-ns08k8s", + "updated": "20240610235147" + }, + "Children": [ + { + "Type": "NodeHeadingC8hMarker", + "Data": "## ", + "Properties": { + "id": "" + } + }, + { + "Type": "NodeText", + "Data": "​", + "Properties": { + "id": "" + } + }, + { + "Type": "NodeTextMark", + "Properties": { + "id": "" + }, + "TextMarkType": "tag", + "TextMarkTextContent": "注意事項" + }, + { + "Type": "NodeText", + "Data": "​", + "Properties": { + "id": "" + } + } + ] + }, + { + "ID": "20240610233629-uqp9rz9", + "Type": "NodeList", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-uqp9rz9", + "updated": "20240610235147" + }, + "Children": [ + { + "ID": "20240610233629-jiv7u6m", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-jiv7u6m", + "updated": "20240610233629" + }, + "Children": [ + { + "ID": "20240610233629-j8ancdr", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-j8ancdr", + "updated": "20240610233629" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "公開サービスを開始すると、訪問者はワークスペースの全コンテンツを閲覧できます。", + "Properties": { + "id": "" + } + } + ] + } + ] + }, + { + "ID": "20240610233629-10tmo1g", + "Type": "NodeListItem", + "Data": "*", + "ListData": { + "Tight": true, + "BulletChar": 42, + "Padding": 2, + "Marker": "Kg==", + "Num": -1 + }, + "Properties": { + "id": "20240610233629-10tmo1g", + "updated": "20240610235147" + }, + "Children": [ + { + "ID": "20240610233629-20lo5k4", + "Type": "NodeParagraph", + "Properties": { + "id": "20240610233629-20lo5k4", + "updated": "20240610235147" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "​" + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "サービスの基本認証" + }, + { + "Type": "NodeText", + "Data": "​ を無効にすると、すべての訪問者はワークスペースの全コンテンツを認証なしで閲覧できます。" + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/app/src/config/editor.ts b/app/src/config/editor.ts index 472e6b9616e..1dcd1b9c342 100644 --- a/app/src/config/editor.ts +++ b/app/src/config/editor.ts @@ -310,10 +310,12 @@ export const editor = { if (fontFamilyElement.tagName === "SELECT") { let fontFamilyHTML = ``; fetchPost("/api/system/getSysFonts", {}, (response) => { - response.data.forEach((item: string) => { - fontFamilyHTML += ``; - }); - fontFamilyElement.innerHTML = fontFamilyHTML; + if (response.code === 0) { + response.data.forEach((item: string) => { + fontFamilyHTML += ``; + }); + fontFamilyElement.innerHTML = fontFamilyHTML; + } }); } editor.element.querySelector("#clearHistory").addEventListener("click", () => { diff --git a/app/src/config/index.ts b/app/src/config/index.ts index e55b4835551..4c246f6ef8b 100644 --- a/app/src/config/index.ts +++ b/app/src/config/index.ts @@ -13,6 +13,7 @@ import {query} from "./query"; import {Dialog} from "../dialog"; import {ai} from "./ai"; import {flashcard} from "./flashcard"; +import {publish} from "./publish"; import {App} from "../index"; import {isHuawei} from "../protyle/util/compatibility"; import {Constants} from "../constants"; @@ -79,6 +80,11 @@ export const genItemPanel = (type: string, containerElement: Element, app: App) query.element = containerElement; query.bindEvent(); break; + case "publish": + containerElement.innerHTML = publish.genHTML(); + publish.element = containerElement; + publish.bindEvent(); + break; default: break; } @@ -115,6 +121,7 @@ export const openSetting = (app: App) => {
  • ${window.siyuan.languages.keymap}
  • ${window.siyuan.languages.account}
  • ${window.siyuan.languages.cloud}
  • +
  • ${window.siyuan.languages.publish}
  • ${window.siyuan.languages.about}
  • @@ -131,6 +138,7 @@ export const openSetting = (app: App) => {
    +
    diff --git a/app/src/config/publish.ts b/app/src/config/publish.ts new file mode 100644 index 00000000000..ecf81758cf0 --- /dev/null +++ b/app/src/config/publish.ts @@ -0,0 +1,213 @@ +import {hasClosestByMatchTag} from "../protyle/util/hasClosest"; +import {fetchPost} from "../util/fetch"; + +export const publish = { + element: undefined as Element, + genHTML: () => { + return ` + + + +
    + +
    +
    + ${window.siyuan.languages.publishServicePort} +
    ${window.siyuan.languages.publishServicePortTip}
    +
    + + +
    + + +
    +
    +
    + ${window.siyuan.languages.publishServiceAddresses} +
    ${window.siyuan.languages.publishServiceAddressesTip}
    +
    +
    +
    +
    +
    +
    +
    + +
    + + + + +
    +
    +
    + ${window.siyuan.languages.publishServiceAuthAccounts} +
    ${window.siyuan.languages.publishServiceAuthAccountsTip}
    +
    +
    + +
    +
    +
    +
    +
    +`; + }, + bindEvent: () => { + const publishAuthAccountAdd = publish.element.querySelector("#publishAuthAccountAdd"); + + /* add account */ + publishAuthAccountAdd.addEventListener("click", () => { + window.siyuan.config.publish.auth.accounts.push({ + username: "", + password: "", + memo: "", + }); + publish._renderPublishAuthAccounts(publish.element); + }); + + /* input change */ + publish.element.querySelectorAll("input").forEach(item => { + item.addEventListener("change", () => { + publish._savePublish(); + }); + }); + + publish._refreshPublish(); + }, + _refreshPublish: () => { + fetchPost("/api/setting/getPublish", {}, publish._updatePublishConfig.bind(null, true)); + }, + _savePublish: (reloadAccounts = true) => { + const publishEnable = publish.element.querySelector("#publishEnable"); + const publishPort = publish.element.querySelector("#publishPort"); + const publishAuthEnable = publish.element.querySelector("#publishAuthEnable"); + + fetchPost("/api/setting/setPublish", { + enable: publishEnable.checked, + port: publishPort.valueAsNumber, + auth: { + enable: publishAuthEnable.checked, + accounts: window.siyuan.config.publish.auth.accounts, + } as Config.IPublishAuth, + } as Config.IPublish, publish._updatePublishConfig.bind(null, reloadAccounts)); + }, + _updatePublishConfig: ( + reloadAccounts: boolean, + response: IWebSocketData, + ) => { + if (response.code === 0) { + window.siyuan.config.publish = response.data.publish; + + reloadAccounts && publish._renderPublishAuthAccounts(publish.element); + publish._renderPublishAddressList(publish.element, response.data.port); + } else { + publish._renderPublishAddressList(publish.element, 0); + } + }, + _renderPublishAuthAccounts: ( + element: Element, + accounts: Config.IPublishAuthAccount[] = window.siyuan.config.publish.auth.accounts, + ) => { + const publishAuthAccounts = element.querySelector("#publishAuthAccounts"); + publishAuthAccounts.innerHTML = `
      ${ + accounts + .map((account, index) => ` +
    • + + +
      + + +
      + + + + + + +
    • ` + ) + .join("") + }
    `; + + /* account field changed */ + publishAuthAccounts + .querySelectorAll("input") + .forEach(input => { + input.addEventListener("change", () => { + const li = hasClosestByMatchTag(input, "LI"); + if (li) { + const index = parseInt(li.dataset.index); + const name = input.dataset.name as keyof Config.IPublishAuthAccount; + if (name in window.siyuan.config.publish.auth.accounts[index]) { + window.siyuan.config.publish.auth.accounts[index][name] = input.value; + publish._savePublish(false); + } + } + }); + }); + + /* delete account */ + publishAuthAccounts + .querySelectorAll('.block__icon[data-action="remove"]') + .forEach(remove => { + remove.addEventListener("click", () => { + const li = hasClosestByMatchTag(remove, "LI"); + if (li) { + const index = parseInt(li.dataset.index); + window.siyuan.config.publish.auth.accounts.splice(index, 1); + publish._savePublish(); + } + }); + }); + + /* Toggle the password display status */ + publishAuthAccounts + .querySelectorAll('.b3-form__icona-icon[data-action="togglePassword"]') + .forEach(togglePassword => { + togglePassword.addEventListener("click", () => { + const isEye = togglePassword.firstElementChild.getAttribute("xlink:href") === "#iconEye"; + togglePassword.firstElementChild.setAttribute("xlink:href", isEye ? "#iconEyeoff" : "#iconEye"); + togglePassword.previousElementSibling.setAttribute("type", isEye ? "text" : "password"); + }); + }); + }, + _renderPublishAddressList: ( + element: Element, + port: number, + ) => { + const publishAddresses = element.querySelector("#publishAddresses"); + if (port === 0) { + publishAddresses.innerText = window.siyuan.languages.publishServiceNotStarted; + } else { + publishAddresses.innerHTML = `
      ${ + window.siyuan.config.localIPs + .filter(ip => !(ip.startsWith("[") && ip.endsWith("]"))) + .map(ip => `
    • ${ip}:${port}
    • `) + .join("") + }${ + window.siyuan.config.localIPs + .filter(ip => (ip.startsWith("[") && ip.endsWith("]"))) + .map(ip => `
    • ${ip}:${port}
    • `) + .join("") + }
    `; + } + }, +} diff --git a/app/src/layout/util.ts b/app/src/layout/util.ts index 80f2d30ca3b..96550b2d45a 100644 --- a/app/src/layout/util.ts +++ b/app/src/layout/util.ts @@ -169,13 +169,17 @@ const dockToJSON = (dock: Dock) => { }; export const resetLayout = () => { - fetchPost("/api/system/setUILayout", {layout: {}}, () => { - window.siyuan.storage[Constants.LOCAL_FILEPOSITION] = {}; - setStorageVal(Constants.LOCAL_FILEPOSITION, window.siyuan.storage[Constants.LOCAL_FILEPOSITION]); - window.siyuan.storage[Constants.LOCAL_DIALOGPOSITION] = {}; - setStorageVal(Constants.LOCAL_DIALOGPOSITION, window.siyuan.storage[Constants.LOCAL_DIALOGPOSITION]); + if (window.siyuan.config.readonly) { window.location.reload(); - }); + } else { + fetchPost("/api/system/setUILayout", {layout: {}}, () => { + window.siyuan.storage[Constants.LOCAL_FILEPOSITION] = {}; + setStorageVal(Constants.LOCAL_FILEPOSITION, window.siyuan.storage[Constants.LOCAL_FILEPOSITION]); + window.siyuan.storage[Constants.LOCAL_DIALOGPOSITION] = {}; + setStorageVal(Constants.LOCAL_DIALOGPOSITION, window.siyuan.storage[Constants.LOCAL_DIALOGPOSITION]); + window.location.reload(); + }); + } }; let saveCount = 0; @@ -211,10 +215,12 @@ export const saveLayout = () => { if (isWindow()) { sessionStorage.setItem("layout", JSON.stringify(layoutJSON)); } else { - fetchPost("/api/system/setUILayout", { - layout: layoutJSON, - errorExit: false // 后台不接受该参数,用于请求发生错误时退出程序 - }); + if (!window.siyuan.config.readonly) { + fetchPost("/api/system/setUILayout", { + layout: layoutJSON, + errorExit: false // 后台不接受该参数,用于请求发生错误时退出程序 + }); + } } } }; @@ -250,12 +256,17 @@ export const exportLayout = (options: { getAllModels().editor.forEach(item => { saveScroll(item.editor.protyle); }); - fetchPost("/api/system/setUILayout", { - layout: layoutJSON, - errorExit: options.errorExit // 后台不接受该参数,用于请求发生错误时退出程序 - }, () => { + + if (window.siyuan.config.readonly) { options.cb(); - }); + } else { + fetchPost("/api/system/setUILayout", { + layout: layoutJSON, + errorExit: options.errorExit // 后台不接受该参数,用于请求发生错误时退出程序 + }, () => { + options.cb(); + }); + } }; export const getAllLayout = () => { diff --git a/app/src/menus/commonMenuItem.ts b/app/src/menus/commonMenuItem.ts index 0c45f02721f..0c1a4ef1eb7 100644 --- a/app/src/menus/commonMenuItem.ts +++ b/app/src/menus/commonMenuItem.ts @@ -507,8 +507,9 @@ export const exportMd = (id: string) => { }); }); return; + } else if (response.code === 0) { + showMessage(window.siyuan.languages.exportTplSucc); } - showMessage(window.siyuan.languages.exportTplSucc); }); dialog.destroy(); }); diff --git a/app/src/menus/workspace.ts b/app/src/menus/workspace.ts index 04ed7fcd5fb..85796b2684d 100644 --- a/app/src/menus/workspace.ts +++ b/app/src/menus/workspace.ts @@ -340,9 +340,13 @@ export const workspaceMenu = (app: App, rect: DOMRect) => { window.siyuan.menus.menu.remove(); return; } - fetchPost("/api/system/setUILayout", {layout: item.layout}, () => { + if (window.siyuan.config.readonly) { window.location.reload(); - }); + } else { + fetchPost("/api/system/setUILayout", {layout: item.layout}, () => { + window.location.reload(); + }); + } }); } }); diff --git a/app/src/protyle/util/compatibility.ts b/app/src/protyle/util/compatibility.ts index 7f07553d177..4f0a1837f1c 100644 --- a/app/src/protyle/util/compatibility.ts +++ b/app/src/protyle/util/compatibility.ts @@ -287,6 +287,10 @@ export const getLocalStorage = (cb: () => void) => { }; export const setStorageVal = (key: string, val: any) => { + if (window.siyuan.config.readonly) { + return; + } + fetchPost("/api/storage/setLocalStorageVal", { app: Constants.SIYUAN_APPID, key, diff --git a/app/src/types/config.d.ts b/app/src/types/config.d.ts index 5d985ea860a..5076311f0c8 100644 --- a/app/src/types/config.d.ts +++ b/app/src/types/config.d.ts @@ -63,6 +63,11 @@ declare namespace Config { * Whether to open the user guide after startup */ openHelp: boolean; + /** + * Publishing service + * 发布服务 + */ + publish: IPublish; /** * Whether it is running in read-only mode * 全局只读 @@ -1030,6 +1035,56 @@ declare namespace Config { */ export type TLogLevel = "off" | "trace" | "debug" | "info" | "warn" | "error" | "fatal"; + /** + * Publishing service + */ + export interface IPublish { + /** + * Whether to open the publishing service + */ + enable: boolean; + /** + * The basic authentication settings of publishing service + */ + auth: IPublishAuth; + /** + * Port on which the publishing service listens + */ + port: number; + } + + /** + * Publishing service authentication settings + */ + export interface IPublishAuth { + /** + * Whether to enable basic authentication for publishing services + */ + enable: boolean; + /** + * List of basic verified accounts + */ + accounts: IPublishAuthAccount[]; + } + + /** + * Basic authentication account + */ + export interface IPublishAuthAccount { + /** + * Account username + */ + username: string; + /** + * Account password + */ + password: string; + /** + * The memo text of the account + */ + memo: string; + } + /** * Snapshot repository related configuration */ diff --git a/app/src/util/assets.ts b/app/src/util/assets.ts index d2ffad69437..314871829de 100644 --- a/app/src/util/assets.ts +++ b/app/src/util/assets.ts @@ -150,8 +150,10 @@ export const initAssets = () => { return; } } - window.siyuan.config.appearance = response.data.appearance; - loadAssets(response.data.appearance); + if (response.code === 0) { + window.siyuan.config.appearance = response.data.appearance; + loadAssets(response.data.appearance); + } }); }); }; diff --git a/app/src/util/fetch.ts b/app/src/util/fetch.ts index 3e258a94fef..36420d9afa7 100644 --- a/app/src/util/fetch.ts +++ b/app/src/util/fetch.ts @@ -32,18 +32,20 @@ export const fetchPost = (url: string, data?: any, cb?: (response: IWebSocketDat init.headers = headers; } fetch(url, init).then((response) => { - if (response.status === 404) { - return { - data: null, - msg: response.statusText, - code: response.status, - }; - } else { - if (response.headers.get("content-type")?.indexOf("application/json") > -1) { - return response.json(); - } else { - return response.text(); - } + switch (response.status) { + case 403: + case 404: + return { + data: null, + msg: response.statusText, + code: response.status, + }; + default: + if (response.headers.get("content-type")?.indexOf("application/json") > -1) { + return response.json(); + } else { + return response.text(); + } } }).then((response: IWebSocketData) => { if (typeof response === "string") { diff --git a/kernel/api/file.go b/kernel/api/file.go index e9453e89980..1a313ea240d 100644 --- a/kernel/api/file.go +++ b/kernel/api/file.go @@ -166,13 +166,31 @@ func getFile(c *gin.Context) { return } if info.IsDir() { - logging.LogErrorf("file [%s] is a directory", fileAbsPath) + logging.LogErrorf("path [%s] is a directory path", fileAbsPath) ret.Code = http.StatusMethodNotAllowed - ret.Msg = "file is a directory" + ret.Msg = "This is a directory path" c.JSON(http.StatusAccepted, ret) return } + // REF: https://github.com/siyuan-note/siyuan/issues/11364 + if role := model.GetGinContextRole(c); !model.IsValidRole(role, []model.Role{ + model.RoleAdministrator, + }) { + if relPath, err := filepath.Rel(util.ConfDir, fileAbsPath); err != nil { + logging.LogErrorf("Get a relative path from [%s] to [%s] failed: %s", util.ConfDir, fileAbsPath, err) + ret.Code = http.StatusInternalServerError + ret.Msg = err.Error() + c.JSON(http.StatusAccepted, ret) + return + } else if relPath == "conf.json" { + ret.Code = http.StatusForbidden + ret.Msg = http.StatusText(http.StatusForbidden) + c.JSON(http.StatusAccepted, ret) + return + } + } + data, err := filelock.ReadFile(fileAbsPath) if nil != err { logging.LogErrorf("read file [%s] failed: %s", fileAbsPath, err) diff --git a/kernel/api/router.go b/kernel/api/router.go index 50ca14bb733..b81d01fc880 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -33,122 +33,121 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/system/loginAuth", model.LoginAuth) ginServer.Handle("POST", "/api/system/logoutAuth", model.LogoutAuth) ginServer.Handle("GET", "/api/system/getCaptcha", model.GetCaptcha) - ginServer.Handle("POST", "/api/system/setUILayout", setUILayout) // 这里不加鉴权 After modifying the access authentication code on the browser side, the other side does not refresh https://github.com/siyuan-note/siyuan/issues/8028 - ginServer.Handle("GET", "/snippets/*filepath", serveSnippets) // 需要鉴权 ginServer.Handle("POST", "/api/system/getEmojiConf", model.CheckAuth, getEmojiConf) - ginServer.Handle("POST", "/api/system/setAPIToken", model.CheckAuth, model.CheckReadonly, setAPIToken) - ginServer.Handle("POST", "/api/system/setAccessAuthCode", model.CheckAuth, model.CheckReadonly, setAccessAuthCode) - ginServer.Handle("POST", "/api/system/setFollowSystemLockScreen", model.CheckAuth, model.CheckReadonly, setFollowSystemLockScreen) - ginServer.Handle("POST", "/api/system/setNetworkServe", model.CheckAuth, model.CheckReadonly, setNetworkServe) - ginServer.Handle("POST", "/api/system/setUploadErrLog", model.CheckAuth, model.CheckReadonly, setUploadErrLog) - ginServer.Handle("POST", "/api/system/setAutoLaunch", model.CheckAuth, model.CheckReadonly, setAutoLaunch) - ginServer.Handle("POST", "/api/system/setGoogleAnalytics", model.CheckAuth, model.CheckReadonly, setGoogleAnalytics) - ginServer.Handle("POST", "/api/system/setDownloadInstallPkg", model.CheckAuth, model.CheckReadonly, setDownloadInstallPkg) - ginServer.Handle("POST", "/api/system/setNetworkProxy", model.CheckAuth, model.CheckReadonly, setNetworkProxy) - ginServer.Handle("POST", "/api/system/setWorkspaceDir", model.CheckAuth, model.CheckReadonly, setWorkspaceDir) - ginServer.Handle("POST", "/api/system/getWorkspaces", model.CheckAuth, getWorkspaces) - ginServer.Handle("POST", "/api/system/getMobileWorkspaces", model.CheckAuth, getMobileWorkspaces) - ginServer.Handle("POST", "/api/system/checkWorkspaceDir", model.CheckAuth, model.CheckReadonly, checkWorkspaceDir) - ginServer.Handle("POST", "/api/system/createWorkspaceDir", model.CheckAuth, model.CheckReadonly, createWorkspaceDir) - ginServer.Handle("POST", "/api/system/removeWorkspaceDir", model.CheckAuth, model.CheckReadonly, removeWorkspaceDir) - ginServer.Handle("POST", "/api/system/removeWorkspaceDirPhysically", model.CheckAuth, model.CheckReadonly, removeWorkspaceDirPhysically) - ginServer.Handle("POST", "/api/system/setAppearanceMode", model.CheckAuth, setAppearanceMode) - ginServer.Handle("POST", "/api/system/getSysFonts", model.CheckAuth, getSysFonts) - ginServer.Handle("POST", "/api/system/exit", model.CheckAuth, exit) + ginServer.Handle("POST", "/api/system/setAPIToken", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAPIToken) + ginServer.Handle("POST", "/api/system/setAccessAuthCode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAccessAuthCode) + ginServer.Handle("POST", "/api/system/setFollowSystemLockScreen", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setFollowSystemLockScreen) + ginServer.Handle("POST", "/api/system/setNetworkServe", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNetworkServe) + ginServer.Handle("POST", "/api/system/setUploadErrLog", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setUploadErrLog) + ginServer.Handle("POST", "/api/system/setAutoLaunch", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAutoLaunch) + ginServer.Handle("POST", "/api/system/setGoogleAnalytics", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setGoogleAnalytics) + ginServer.Handle("POST", "/api/system/setDownloadInstallPkg", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setDownloadInstallPkg) + ginServer.Handle("POST", "/api/system/setNetworkProxy", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNetworkProxy) + ginServer.Handle("POST", "/api/system/setWorkspaceDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setWorkspaceDir) + ginServer.Handle("POST", "/api/system/getWorkspaces", model.CheckAuth, model.CheckAdminRole, getWorkspaces) + ginServer.Handle("POST", "/api/system/getMobileWorkspaces", model.CheckAuth, model.CheckAdminRole, getMobileWorkspaces) + ginServer.Handle("POST", "/api/system/checkWorkspaceDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, checkWorkspaceDir) + ginServer.Handle("POST", "/api/system/createWorkspaceDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createWorkspaceDir) + ginServer.Handle("POST", "/api/system/removeWorkspaceDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeWorkspaceDir) + ginServer.Handle("POST", "/api/system/removeWorkspaceDirPhysically", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeWorkspaceDirPhysically) + ginServer.Handle("POST", "/api/system/setAppearanceMode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAppearanceMode) + ginServer.Handle("POST", "/api/system/setUILayout", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setUILayout) + ginServer.Handle("POST", "/api/system/getSysFonts", model.CheckAuth, model.CheckAdminRole, getSysFonts) + ginServer.Handle("POST", "/api/system/exit", model.CheckAuth, model.CheckAdminRole, exit) ginServer.Handle("POST", "/api/system/getConf", model.CheckAuth, getConf) - ginServer.Handle("POST", "/api/system/checkUpdate", model.CheckAuth, checkUpdate) - ginServer.Handle("POST", "/api/system/exportLog", model.CheckAuth, exportLog) + ginServer.Handle("POST", "/api/system/checkUpdate", model.CheckAuth, model.CheckAdminRole, checkUpdate) + ginServer.Handle("POST", "/api/system/exportLog", model.CheckAuth, model.CheckAdminRole, exportLog) ginServer.Handle("POST", "/api/system/getChangelog", model.CheckAuth, getChangelog) - ginServer.Handle("POST", "/api/system/getNetwork", model.CheckAuth, getNetwork) + ginServer.Handle("POST", "/api/system/getNetwork", model.CheckAuth, model.CheckAdminRole, getNetwork) - ginServer.Handle("POST", "/api/storage/setLocalStorage", model.CheckAuth, model.CheckReadonly, setLocalStorage) + ginServer.Handle("POST", "/api/storage/setLocalStorage", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setLocalStorage) ginServer.Handle("POST", "/api/storage/getLocalStorage", model.CheckAuth, getLocalStorage) - ginServer.Handle("POST", "/api/storage/setLocalStorageVal", model.CheckAuth, model.CheckReadonly, setLocalStorageVal) - ginServer.Handle("POST", "/api/storage/removeLocalStorageVals", model.CheckAuth, model.CheckReadonly, removeLocalStorageVals) - ginServer.Handle("POST", "/api/storage/setCriterion", model.CheckAuth, model.CheckReadonly, setCriterion) + ginServer.Handle("POST", "/api/storage/setLocalStorageVal", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setLocalStorageVal) + ginServer.Handle("POST", "/api/storage/removeLocalStorageVals", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeLocalStorageVals) + ginServer.Handle("POST", "/api/storage/setCriterion", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setCriterion) ginServer.Handle("POST", "/api/storage/getCriteria", model.CheckAuth, getCriteria) - ginServer.Handle("POST", "/api/storage/removeCriterion", model.CheckAuth, model.CheckReadonly, removeCriterion) + ginServer.Handle("POST", "/api/storage/removeCriterion", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeCriterion) ginServer.Handle("POST", "/api/storage/getRecentDocs", model.CheckAuth, getRecentDocs) - ginServer.Handle("POST", "/api/account/login", model.CheckAuth, model.CheckReadonly, login) - ginServer.Handle("POST", "/api/account/checkActivationcode", model.CheckAuth, model.CheckReadonly, checkActivationcode) - ginServer.Handle("POST", "/api/account/useActivationcode", model.CheckAuth, model.CheckReadonly, useActivationcode) - ginServer.Handle("POST", "/api/account/deactivate", model.CheckAuth, model.CheckReadonly, deactivateUser) - ginServer.Handle("POST", "/api/account/startFreeTrial", model.CheckAuth, model.CheckReadonly, startFreeTrial) + ginServer.Handle("POST", "/api/account/login", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, login) + ginServer.Handle("POST", "/api/account/checkActivationcode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, checkActivationcode) + ginServer.Handle("POST", "/api/account/useActivationcode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, useActivationcode) + ginServer.Handle("POST", "/api/account/deactivate", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, deactivateUser) + ginServer.Handle("POST", "/api/account/startFreeTrial", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, startFreeTrial) ginServer.Handle("POST", "/api/notebook/lsNotebooks", model.CheckAuth, lsNotebooks) - ginServer.Handle("POST", "/api/notebook/openNotebook", model.CheckAuth, model.CheckReadonly, openNotebook) - ginServer.Handle("POST", "/api/notebook/closeNotebook", model.CheckAuth, model.CheckReadonly, closeNotebook) + ginServer.Handle("POST", "/api/notebook/openNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, openNotebook) + ginServer.Handle("POST", "/api/notebook/closeNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, closeNotebook) ginServer.Handle("POST", "/api/notebook/getNotebookConf", model.CheckAuth, getNotebookConf) - ginServer.Handle("POST", "/api/notebook/setNotebookConf", model.CheckAuth, model.CheckReadonly, setNotebookConf) - ginServer.Handle("POST", "/api/notebook/createNotebook", model.CheckAuth, model.CheckReadonly, createNotebook) - ginServer.Handle("POST", "/api/notebook/removeNotebook", model.CheckAuth, model.CheckReadonly, removeNotebook) - ginServer.Handle("POST", "/api/notebook/renameNotebook", model.CheckAuth, model.CheckReadonly, renameNotebook) - ginServer.Handle("POST", "/api/notebook/changeSortNotebook", model.CheckAuth, model.CheckReadonly, changeSortNotebook) - ginServer.Handle("POST", "/api/notebook/setNotebookIcon", model.CheckAuth, model.CheckReadonly, setNotebookIcon) + ginServer.Handle("POST", "/api/notebook/setNotebookConf", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNotebookConf) + ginServer.Handle("POST", "/api/notebook/createNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createNotebook) + ginServer.Handle("POST", "/api/notebook/removeNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeNotebook) + ginServer.Handle("POST", "/api/notebook/renameNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameNotebook) + ginServer.Handle("POST", "/api/notebook/changeSortNotebook", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, changeSortNotebook) + ginServer.Handle("POST", "/api/notebook/setNotebookIcon", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setNotebookIcon) ginServer.Handle("POST", "/api/filetree/searchDocs", model.CheckAuth, searchDocs) ginServer.Handle("POST", "/api/filetree/listDocsByPath", model.CheckAuth, listDocsByPath) ginServer.Handle("POST", "/api/filetree/getDoc", model.CheckAuth, getDoc) ginServer.Handle("POST", "/api/filetree/getDocCreateSavePath", model.CheckAuth, getDocCreateSavePath) ginServer.Handle("POST", "/api/filetree/getRefCreateSavePath", model.CheckAuth, getRefCreateSavePath) - ginServer.Handle("POST", "/api/filetree/changeSort", model.CheckAuth, model.CheckReadonly, changeSort) - ginServer.Handle("POST", "/api/filetree/createDocWithMd", model.CheckAuth, model.CheckReadonly, createDocWithMd) - ginServer.Handle("POST", "/api/filetree/createDailyNote", model.CheckAuth, model.CheckReadonly, createDailyNote) - ginServer.Handle("POST", "/api/filetree/createDoc", model.CheckAuth, model.CheckReadonly, createDoc) - ginServer.Handle("POST", "/api/filetree/renameDoc", model.CheckAuth, model.CheckReadonly, renameDoc) - ginServer.Handle("POST", "/api/filetree/removeDoc", model.CheckAuth, model.CheckReadonly, removeDoc) - ginServer.Handle("POST", "/api/filetree/removeDocs", model.CheckAuth, model.CheckReadonly, removeDocs) - ginServer.Handle("POST", "/api/filetree/moveDocs", model.CheckAuth, model.CheckReadonly, moveDocs) - ginServer.Handle("POST", "/api/filetree/duplicateDoc", model.CheckAuth, model.CheckReadonly, duplicateDoc) + ginServer.Handle("POST", "/api/filetree/changeSort", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, changeSort) + ginServer.Handle("POST", "/api/filetree/createDocWithMd", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createDocWithMd) + ginServer.Handle("POST", "/api/filetree/createDailyNote", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createDailyNote) + ginServer.Handle("POST", "/api/filetree/createDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createDoc) + ginServer.Handle("POST", "/api/filetree/renameDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameDoc) + ginServer.Handle("POST", "/api/filetree/removeDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDoc) + ginServer.Handle("POST", "/api/filetree/removeDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDocs) + ginServer.Handle("POST", "/api/filetree/moveDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveDocs) + ginServer.Handle("POST", "/api/filetree/duplicateDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, duplicateDoc) ginServer.Handle("POST", "/api/filetree/getHPathByPath", model.CheckAuth, getHPathByPath) ginServer.Handle("POST", "/api/filetree/getHPathsByPaths", model.CheckAuth, getHPathsByPaths) ginServer.Handle("POST", "/api/filetree/getHPathByID", model.CheckAuth, getHPathByID) ginServer.Handle("POST", "/api/filetree/getFullHPathByID", model.CheckAuth, getFullHPathByID) ginServer.Handle("POST", "/api/filetree/getIDsByHPath", model.CheckAuth, getIDsByHPath) - ginServer.Handle("POST", "/api/filetree/doc2Heading", model.CheckAuth, model.CheckReadonly, doc2Heading) - ginServer.Handle("POST", "/api/filetree/heading2Doc", model.CheckAuth, model.CheckReadonly, heading2Doc) - ginServer.Handle("POST", "/api/filetree/li2Doc", model.CheckAuth, model.CheckReadonly, li2Doc) - ginServer.Handle("POST", "/api/filetree/refreshFiletree", model.CheckAuth, model.CheckReadonly, refreshFiletree) - ginServer.Handle("POST", "/api/filetree/upsertIndexes", model.CheckAuth, model.CheckReadonly, upsertIndexes) - ginServer.Handle("POST", "/api/filetree/removeIndexes", model.CheckAuth, model.CheckReadonly, removeIndexes) - ginServer.Handle("POST", "/api/filetree/listDocTree", model.CheckAuth, model.CheckReadonly, listDocTree) - - ginServer.Handle("POST", "/api/format/autoSpace", model.CheckAuth, model.CheckReadonly, autoSpace) - ginServer.Handle("POST", "/api/format/netImg2LocalAssets", model.CheckAuth, model.CheckReadonly, netImg2LocalAssets) - ginServer.Handle("POST", "/api/format/netAssets2LocalAssets", model.CheckAuth, model.CheckReadonly, netAssets2LocalAssets) - - ginServer.Handle("POST", "/api/history/getNotebookHistory", model.CheckAuth, getNotebookHistory) - ginServer.Handle("POST", "/api/history/rollbackNotebookHistory", model.CheckAuth, model.CheckReadonly, rollbackNotebookHistory) - ginServer.Handle("POST", "/api/history/rollbackAssetsHistory", model.CheckAuth, model.CheckReadonly, rollbackAssetsHistory) - ginServer.Handle("POST", "/api/history/getDocHistoryContent", model.CheckAuth, getDocHistoryContent) - ginServer.Handle("POST", "/api/history/rollbackDocHistory", model.CheckAuth, model.CheckReadonly, rollbackDocHistory) - ginServer.Handle("POST", "/api/history/clearWorkspaceHistory", model.CheckAuth, model.CheckReadonly, clearWorkspaceHistory) - ginServer.Handle("POST", "/api/history/reindexHistory", model.CheckAuth, model.CheckReadonly, reindexHistory) - ginServer.Handle("POST", "/api/history/searchHistory", model.CheckAuth, searchHistory) - ginServer.Handle("POST", "/api/history/getHistoryItems", model.CheckAuth, getHistoryItems) + ginServer.Handle("POST", "/api/filetree/doc2Heading", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, doc2Heading) + ginServer.Handle("POST", "/api/filetree/heading2Doc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, heading2Doc) + ginServer.Handle("POST", "/api/filetree/li2Doc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, li2Doc) + ginServer.Handle("POST", "/api/filetree/refreshFiletree", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, refreshFiletree) + ginServer.Handle("POST", "/api/filetree/upsertIndexes", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, upsertIndexes) + ginServer.Handle("POST", "/api/filetree/removeIndexes", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeIndexes) + ginServer.Handle("POST", "/api/filetree/listDocTree", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, listDocTree) + + ginServer.Handle("POST", "/api/format/autoSpace", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, autoSpace) + ginServer.Handle("POST", "/api/format/netImg2LocalAssets", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, netImg2LocalAssets) + ginServer.Handle("POST", "/api/format/netAssets2LocalAssets", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, netAssets2LocalAssets) + + ginServer.Handle("POST", "/api/history/getNotebookHistory", model.CheckAuth, model.CheckAdminRole, getNotebookHistory) + ginServer.Handle("POST", "/api/history/rollbackNotebookHistory", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, rollbackNotebookHistory) + ginServer.Handle("POST", "/api/history/rollbackAssetsHistory", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, rollbackAssetsHistory) + ginServer.Handle("POST", "/api/history/getDocHistoryContent", model.CheckAuth, model.CheckAdminRole, getDocHistoryContent) + ginServer.Handle("POST", "/api/history/rollbackDocHistory", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, rollbackDocHistory) + ginServer.Handle("POST", "/api/history/clearWorkspaceHistory", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, clearWorkspaceHistory) + ginServer.Handle("POST", "/api/history/reindexHistory", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, reindexHistory) + ginServer.Handle("POST", "/api/history/searchHistory", model.CheckAuth, model.CheckAdminRole, searchHistory) + ginServer.Handle("POST", "/api/history/getHistoryItems", model.CheckAuth, model.CheckAdminRole, getHistoryItems) ginServer.Handle("POST", "/api/outline/getDocOutline", model.CheckAuth, getDocOutline) ginServer.Handle("POST", "/api/bookmark/getBookmark", model.CheckAuth, getBookmark) - ginServer.Handle("POST", "/api/bookmark/renameBookmark", model.CheckAuth, model.CheckReadonly, renameBookmark) - ginServer.Handle("POST", "/api/bookmark/removeBookmark", model.CheckAuth, model.CheckReadonly, removeBookmark) + ginServer.Handle("POST", "/api/bookmark/renameBookmark", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameBookmark) + ginServer.Handle("POST", "/api/bookmark/removeBookmark", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeBookmark) ginServer.Handle("POST", "/api/tag/getTag", model.CheckAuth, getTag) - ginServer.Handle("POST", "/api/tag/renameTag", model.CheckAuth, model.CheckReadonly, renameTag) - ginServer.Handle("POST", "/api/tag/removeTag", model.CheckAuth, model.CheckReadonly, removeTag) + ginServer.Handle("POST", "/api/tag/renameTag", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameTag) + ginServer.Handle("POST", "/api/tag/removeTag", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeTag) ginServer.Handle("POST", "/api/lute/spinBlockDOM", model.CheckAuth, spinBlockDOM) // 未测试 ginServer.Handle("POST", "/api/lute/html2BlockDOM", model.CheckAuth, html2BlockDOM) ginServer.Handle("POST", "/api/lute/copyStdMarkdown", model.CheckAuth, copyStdMarkdown) ginServer.Handle("POST", "/api/query/sql", model.CheckAuth, SQL) - ginServer.Handle("POST", "/api/sqlite/flushTransaction", model.CheckAuth, model.CheckReadonly, flushTransaction) + ginServer.Handle("POST", "/api/sqlite/flushTransaction", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, flushTransaction) ginServer.Handle("POST", "/api/search/searchTag", model.CheckAuth, searchTag) ginServer.Handle("POST", "/api/search/searchTemplate", model.CheckAuth, searchTemplate) - ginServer.Handle("POST", "/api/search/removeTemplate", model.CheckAuth, model.CheckReadonly, removeTemplate) + ginServer.Handle("POST", "/api/search/removeTemplate", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeTemplate) ginServer.Handle("POST", "/api/search/searchWidget", model.CheckAuth, searchWidget) ginServer.Handle("POST", "/api/search/searchRefBlock", model.CheckAuth, searchRefBlock) ginServer.Handle("POST", "/api/search/searchEmbedBlock", model.CheckAuth, searchEmbedBlock) @@ -181,33 +180,33 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/block/getDocInfo", model.CheckAuth, getDocInfo) ginServer.Handle("POST", "/api/block/checkBlockExist", model.CheckAuth, checkBlockExist) ginServer.Handle("POST", "/api/block/checkBlockFold", model.CheckAuth, checkBlockFold) - ginServer.Handle("POST", "/api/block/insertBlock", model.CheckAuth, model.CheckReadonly, insertBlock) - ginServer.Handle("POST", "/api/block/prependBlock", model.CheckAuth, model.CheckReadonly, prependBlock) - ginServer.Handle("POST", "/api/block/appendBlock", model.CheckAuth, model.CheckReadonly, appendBlock) - ginServer.Handle("POST", "/api/block/appendDailyNoteBlock", model.CheckAuth, model.CheckReadonly, appendDailyNoteBlock) - ginServer.Handle("POST", "/api/block/prependDailyNoteBlock", model.CheckAuth, model.CheckReadonly, prependDailyNoteBlock) - ginServer.Handle("POST", "/api/block/updateBlock", model.CheckAuth, model.CheckReadonly, updateBlock) - ginServer.Handle("POST", "/api/block/deleteBlock", model.CheckAuth, model.CheckReadonly, deleteBlock) - ginServer.Handle("POST", "/api/block/moveBlock", model.CheckAuth, model.CheckReadonly, moveBlock) - ginServer.Handle("POST", "/api/block/moveOutlineHeading", model.CheckAuth, model.CheckReadonly, moveOutlineHeading) - ginServer.Handle("POST", "/api/block/foldBlock", model.CheckAuth, model.CheckReadonly, foldBlock) - ginServer.Handle("POST", "/api/block/unfoldBlock", model.CheckAuth, model.CheckReadonly, unfoldBlock) - ginServer.Handle("POST", "/api/block/setBlockReminder", model.CheckAuth, model.CheckReadonly, setBlockReminder) + ginServer.Handle("POST", "/api/block/insertBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, insertBlock) + ginServer.Handle("POST", "/api/block/prependBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, prependBlock) + ginServer.Handle("POST", "/api/block/appendBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, appendBlock) + ginServer.Handle("POST", "/api/block/appendDailyNoteBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, appendDailyNoteBlock) + ginServer.Handle("POST", "/api/block/prependDailyNoteBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, prependDailyNoteBlock) + ginServer.Handle("POST", "/api/block/updateBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, updateBlock) + ginServer.Handle("POST", "/api/block/deleteBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, deleteBlock) + ginServer.Handle("POST", "/api/block/moveBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveBlock) + ginServer.Handle("POST", "/api/block/moveOutlineHeading", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveOutlineHeading) + ginServer.Handle("POST", "/api/block/foldBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, foldBlock) + ginServer.Handle("POST", "/api/block/unfoldBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, unfoldBlock) + ginServer.Handle("POST", "/api/block/setBlockReminder", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setBlockReminder) ginServer.Handle("POST", "/api/block/getHeadingLevelTransaction", model.CheckAuth, getHeadingLevelTransaction) ginServer.Handle("POST", "/api/block/getHeadingDeleteTransaction", model.CheckAuth, getHeadingDeleteTransaction) ginServer.Handle("POST", "/api/block/getHeadingChildrenIDs", model.CheckAuth, getHeadingChildrenIDs) ginServer.Handle("POST", "/api/block/getHeadingChildrenDOM", model.CheckAuth, getHeadingChildrenDOM) - ginServer.Handle("POST", "/api/block/swapBlockRef", model.CheckAuth, model.CheckReadonly, swapBlockRef) - ginServer.Handle("POST", "/api/block/transferBlockRef", model.CheckAuth, model.CheckReadonly, transferBlockRef) + ginServer.Handle("POST", "/api/block/swapBlockRef", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, swapBlockRef) + ginServer.Handle("POST", "/api/block/transferBlockRef", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, transferBlockRef) ginServer.Handle("POST", "/api/block/getBlockSiblingID", model.CheckAuth, getBlockSiblingID) ginServer.Handle("POST", "/api/block/getBlockTreeInfos", model.CheckAuth, getBlockTreeInfos) ginServer.Handle("POST", "/api/file/getFile", model.CheckAuth, getFile) - ginServer.Handle("POST", "/api/file/putFile", model.CheckAuth, model.CheckReadonly, putFile) - ginServer.Handle("POST", "/api/file/copyFile", model.CheckAuth, model.CheckReadonly, copyFile) - ginServer.Handle("POST", "/api/file/globalCopyFiles", model.CheckAuth, model.CheckReadonly, globalCopyFiles) - ginServer.Handle("POST", "/api/file/removeFile", model.CheckAuth, model.CheckReadonly, removeFile) - ginServer.Handle("POST", "/api/file/renameFile", model.CheckAuth, model.CheckReadonly, renameFile) + ginServer.Handle("POST", "/api/file/putFile", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, putFile) + ginServer.Handle("POST", "/api/file/copyFile", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, copyFile) + ginServer.Handle("POST", "/api/file/globalCopyFiles", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, globalCopyFiles) + ginServer.Handle("POST", "/api/file/removeFile", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeFile) + ginServer.Handle("POST", "/api/file/renameFile", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameFile) ginServer.Handle("POST", "/api/file/readDir", model.CheckAuth, readDir) ginServer.Handle("POST", "/api/file/getUniqueFilename", model.CheckAuth, getUniqueFilename) @@ -218,57 +217,57 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/ref/getBackmentionDoc", model.CheckAuth, getBackmentionDoc) ginServer.Handle("POST", "/api/attr/getBookmarkLabels", model.CheckAuth, getBookmarkLabels) - ginServer.Handle("POST", "/api/attr/resetBlockAttrs", model.CheckAuth, model.CheckReadonly, resetBlockAttrs) - ginServer.Handle("POST", "/api/attr/setBlockAttrs", model.CheckAuth, model.CheckReadonly, setBlockAttrs) - ginServer.Handle("POST", "/api/attr/batchSetBlockAttrs", model.CheckAuth, model.CheckReadonly, batchSetBlockAttrs) + ginServer.Handle("POST", "/api/attr/resetBlockAttrs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, resetBlockAttrs) + ginServer.Handle("POST", "/api/attr/setBlockAttrs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setBlockAttrs) + ginServer.Handle("POST", "/api/attr/batchSetBlockAttrs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, batchSetBlockAttrs) ginServer.Handle("POST", "/api/attr/getBlockAttrs", model.CheckAuth, getBlockAttrs) - ginServer.Handle("POST", "/api/cloud/getCloudSpace", model.CheckAuth, getCloudSpace) - - ginServer.Handle("POST", "/api/sync/setSyncEnable", model.CheckAuth, model.CheckReadonly, setSyncEnable) - ginServer.Handle("POST", "/api/sync/setSyncPerception", model.CheckAuth, model.CheckReadonly, setSyncPerception) - ginServer.Handle("POST", "/api/sync/setSyncGenerateConflictDoc", model.CheckAuth, model.CheckReadonly, setSyncGenerateConflictDoc) - ginServer.Handle("POST", "/api/sync/setSyncMode", model.CheckAuth, model.CheckReadonly, setSyncMode) - ginServer.Handle("POST", "/api/sync/setSyncProvider", model.CheckAuth, model.CheckReadonly, setSyncProvider) - ginServer.Handle("POST", "/api/sync/setSyncProviderS3", model.CheckAuth, model.CheckReadonly, setSyncProviderS3) - ginServer.Handle("POST", "/api/sync/setSyncProviderWebDAV", model.CheckAuth, model.CheckReadonly, setSyncProviderWebDAV) - ginServer.Handle("POST", "/api/sync/setCloudSyncDir", model.CheckAuth, model.CheckReadonly, setCloudSyncDir) - ginServer.Handle("POST", "/api/sync/createCloudSyncDir", model.CheckAuth, model.CheckReadonly, createCloudSyncDir) - ginServer.Handle("POST", "/api/sync/removeCloudSyncDir", model.CheckAuth, model.CheckReadonly, removeCloudSyncDir) - ginServer.Handle("POST", "/api/sync/listCloudSyncDir", model.CheckAuth, listCloudSyncDir) - ginServer.Handle("POST", "/api/sync/performSync", model.CheckAuth, model.CheckReadonly, performSync) - ginServer.Handle("POST", "/api/sync/performBootSync", model.CheckAuth, model.CheckReadonly, performBootSync) - ginServer.Handle("POST", "/api/sync/getBootSync", model.CheckAuth, getBootSync) - ginServer.Handle("POST", "/api/sync/getSyncInfo", model.CheckAuth, getSyncInfo) - ginServer.Handle("POST", "/api/sync/exportSyncProviderS3", model.CheckAuth, exportSyncProviderS3) - ginServer.Handle("POST", "/api/sync/importSyncProviderS3", model.CheckAuth, model.CheckReadonly, importSyncProviderS3) - ginServer.Handle("POST", "/api/sync/exportSyncProviderWebDAV", model.CheckAuth, exportSyncProviderWebDAV) - ginServer.Handle("POST", "/api/sync/importSyncProviderWebDAV", model.CheckAuth, model.CheckReadonly, importSyncProviderWebDAV) - - ginServer.Handle("POST", "/api/inbox/getShorthands", model.CheckAuth, getShorthands) - ginServer.Handle("POST", "/api/inbox/getShorthand", model.CheckAuth, getShorthand) - ginServer.Handle("POST", "/api/inbox/removeShorthands", model.CheckAuth, model.CheckReadonly, removeShorthands) - - ginServer.Handle("POST", "/api/extension/copy", model.CheckAuth, model.CheckReadonly, extensionCopy) - - ginServer.Handle("POST", "/api/clipboard/readFilePaths", model.CheckAuth, readFilePaths) - - ginServer.Handle("POST", "/api/asset/uploadCloud", model.CheckAuth, model.CheckReadonly, uploadCloud) - ginServer.Handle("POST", "/api/asset/insertLocalAssets", model.CheckAuth, model.CheckReadonly, insertLocalAssets) + ginServer.Handle("POST", "/api/cloud/getCloudSpace", model.CheckAuth, model.CheckAdminRole, getCloudSpace) + + ginServer.Handle("POST", "/api/sync/setSyncEnable", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncEnable) + ginServer.Handle("POST", "/api/sync/setSyncPerception", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncPerception) + ginServer.Handle("POST", "/api/sync/setSyncGenerateConflictDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncGenerateConflictDoc) + ginServer.Handle("POST", "/api/sync/setSyncMode", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncMode) + ginServer.Handle("POST", "/api/sync/setSyncProvider", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncProvider) + ginServer.Handle("POST", "/api/sync/setSyncProviderS3", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncProviderS3) + ginServer.Handle("POST", "/api/sync/setSyncProviderWebDAV", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSyncProviderWebDAV) + ginServer.Handle("POST", "/api/sync/setCloudSyncDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setCloudSyncDir) + ginServer.Handle("POST", "/api/sync/createCloudSyncDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createCloudSyncDir) + ginServer.Handle("POST", "/api/sync/removeCloudSyncDir", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeCloudSyncDir) + ginServer.Handle("POST", "/api/sync/listCloudSyncDir", model.CheckAuth, model.CheckAdminRole, listCloudSyncDir) + ginServer.Handle("POST", "/api/sync/performSync", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, performSync) + ginServer.Handle("POST", "/api/sync/performBootSync", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, performBootSync) + ginServer.Handle("POST", "/api/sync/getBootSync", model.CheckAuth, model.CheckAdminRole, getBootSync) + ginServer.Handle("POST", "/api/sync/getSyncInfo", model.CheckAuth, model.CheckAdminRole, getSyncInfo) + ginServer.Handle("POST", "/api/sync/exportSyncProviderS3", model.CheckAuth, model.CheckAdminRole, exportSyncProviderS3) + ginServer.Handle("POST", "/api/sync/importSyncProviderS3", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importSyncProviderS3) + ginServer.Handle("POST", "/api/sync/exportSyncProviderWebDAV", model.CheckAuth, model.CheckAdminRole, exportSyncProviderWebDAV) + ginServer.Handle("POST", "/api/sync/importSyncProviderWebDAV", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importSyncProviderWebDAV) + + ginServer.Handle("POST", "/api/inbox/getShorthands", model.CheckAuth, model.CheckAdminRole, getShorthands) + ginServer.Handle("POST", "/api/inbox/getShorthand", model.CheckAuth, model.CheckAdminRole, getShorthand) + ginServer.Handle("POST", "/api/inbox/removeShorthands", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeShorthands) + + ginServer.Handle("POST", "/api/extension/copy", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, extensionCopy) + + ginServer.Handle("POST", "/api/clipboard/readFilePaths", model.CheckAuth, model.CheckAdminRole, readFilePaths) + + ginServer.Handle("POST", "/api/asset/uploadCloud", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uploadCloud) + ginServer.Handle("POST", "/api/asset/insertLocalAssets", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, insertLocalAssets) ginServer.Handle("POST", "/api/asset/resolveAssetPath", model.CheckAuth, resolveAssetPath) - ginServer.Handle("POST", "/api/asset/upload", model.CheckAuth, model.CheckReadonly, model.Upload) - ginServer.Handle("POST", "/api/asset/setFileAnnotation", model.CheckAuth, model.CheckReadonly, setFileAnnotation) + ginServer.Handle("POST", "/api/asset/upload", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, model.Upload) + ginServer.Handle("POST", "/api/asset/setFileAnnotation", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setFileAnnotation) ginServer.Handle("POST", "/api/asset/getFileAnnotation", model.CheckAuth, getFileAnnotation) ginServer.Handle("POST", "/api/asset/getUnusedAssets", model.CheckAuth, getUnusedAssets) ginServer.Handle("POST", "/api/asset/getMissingAssets", model.CheckAuth, getMissingAssets) - ginServer.Handle("POST", "/api/asset/removeUnusedAsset", model.CheckAuth, model.CheckReadonly, removeUnusedAsset) - ginServer.Handle("POST", "/api/asset/removeUnusedAssets", model.CheckAuth, model.CheckReadonly, removeUnusedAssets) + ginServer.Handle("POST", "/api/asset/removeUnusedAsset", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeUnusedAsset) + ginServer.Handle("POST", "/api/asset/removeUnusedAssets", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeUnusedAssets) ginServer.Handle("POST", "/api/asset/getDocImageAssets", model.CheckAuth, getDocImageAssets) - ginServer.Handle("POST", "/api/asset/renameAsset", model.CheckAuth, model.CheckReadonly, renameAsset) - ginServer.Handle("POST", "/api/asset/getImageOCRText", model.CheckAuth, model.CheckReadonly, getImageOCRText) - ginServer.Handle("POST", "/api/asset/setImageOCRText", model.CheckAuth, model.CheckReadonly, setImageOCRText) - ginServer.Handle("POST", "/api/asset/fullReindexAssetContent", model.CheckAuth, model.CheckReadonly, fullReindexAssetContent) - ginServer.Handle("POST", "/api/asset/statAsset", model.CheckAuth, statAsset) + ginServer.Handle("POST", "/api/asset/renameAsset", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameAsset) + ginServer.Handle("POST", "/api/asset/getImageOCRText", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getImageOCRText) + ginServer.Handle("POST", "/api/asset/setImageOCRText", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setImageOCRText) + ginServer.Handle("POST", "/api/asset/fullReindexAssetContent", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, fullReindexAssetContent) + ginServer.Handle("POST", "/api/asset/statAsset", model.CheckAuth, model.CheckAdminRole, statAsset) ginServer.Handle("POST", "/api/export/batchExportMd", model.CheckAuth, batchExportMd) ginServer.Handle("POST", "/api/export/exportMd", model.CheckAuth, exportMd) @@ -286,7 +285,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/export/exportData", model.CheckAuth, exportData) ginServer.Handle("POST", "/api/export/exportDataInFolder", model.CheckAuth, exportDataInFolder) ginServer.Handle("POST", "/api/export/exportTempContent", model.CheckAuth, exportTempContent) - ginServer.Handle("POST", "/api/export/export2Liandi", model.CheckAuth, export2Liandi) + ginServer.Handle("POST", "/api/export/export2Liandi", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, export2Liandi) ginServer.Handle("POST", "/api/export/exportReStructuredText", model.CheckAuth, exportReStructuredText) ginServer.Handle("POST", "/api/export/exportAsciiDoc", model.CheckAuth, exportAsciiDoc) ginServer.Handle("POST", "/api/export/exportTextile", model.CheckAuth, exportTextile) @@ -298,150 +297,152 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/export/exportEPUB", model.CheckAuth, exportEPUB) ginServer.Handle("POST", "/api/export/exportAttributeView", model.CheckAuth, exportAttributeView) - ginServer.Handle("POST", "/api/import/importStdMd", model.CheckAuth, model.CheckReadonly, importStdMd) - ginServer.Handle("POST", "/api/import/importData", model.CheckAuth, model.CheckReadonly, importData) - ginServer.Handle("POST", "/api/import/importSY", model.CheckAuth, model.CheckReadonly, importSY) + ginServer.Handle("POST", "/api/import/importStdMd", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importStdMd) + ginServer.Handle("POST", "/api/import/importData", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importData) + ginServer.Handle("POST", "/api/import/importSY", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importSY) - ginServer.Handle("POST", "/api/convert/pandoc", model.CheckAuth, model.CheckReadonly, pandoc) + ginServer.Handle("POST", "/api/convert/pandoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, pandoc) - ginServer.Handle("POST", "/api/template/render", model.CheckAuth, renderTemplate) - ginServer.Handle("POST", "/api/template/docSaveAsTemplate", model.CheckAuth, model.CheckReadonly, docSaveAsTemplate) + ginServer.Handle("POST", "/api/template/render", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renderTemplate) + ginServer.Handle("POST", "/api/template/docSaveAsTemplate", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, docSaveAsTemplate) ginServer.Handle("POST", "/api/template/renderSprig", model.CheckAuth, renderSprig) - ginServer.Handle("POST", "/api/transactions", model.CheckAuth, model.CheckReadonly, performTransactions) - - ginServer.Handle("POST", "/api/setting/setAccount", model.CheckAuth, model.CheckReadonly, setAccount) - ginServer.Handle("POST", "/api/setting/setEditor", model.CheckAuth, model.CheckReadonly, setEditor) - ginServer.Handle("POST", "/api/setting/setExport", model.CheckAuth, model.CheckReadonly, setExport) - ginServer.Handle("POST", "/api/setting/setFiletree", model.CheckAuth, model.CheckReadonly, setFiletree) - ginServer.Handle("POST", "/api/setting/setSearch", model.CheckAuth, model.CheckReadonly, setSearch) - ginServer.Handle("POST", "/api/setting/setKeymap", model.CheckAuth, model.CheckReadonly, setKeymap) - ginServer.Handle("POST", "/api/setting/setAppearance", model.CheckAuth, model.CheckReadonly, setAppearance) - ginServer.Handle("POST", "/api/setting/getCloudUser", model.CheckAuth, getCloudUser) - ginServer.Handle("POST", "/api/setting/logoutCloudUser", model.CheckAuth, model.CheckReadonly, logoutCloudUser) - ginServer.Handle("POST", "/api/setting/login2faCloudUser", model.CheckAuth, model.CheckReadonly, login2faCloudUser) - ginServer.Handle("POST", "/api/setting/setEmoji", model.CheckAuth, model.CheckReadonly, setEmoji) - ginServer.Handle("POST", "/api/setting/setFlashcard", model.CheckAuth, model.CheckReadonly, setFlashcard) - ginServer.Handle("POST", "/api/setting/setAI", model.CheckAuth, model.CheckReadonly, setAI) - ginServer.Handle("POST", "/api/setting/setBazaar", model.CheckAuth, model.CheckReadonly, setBazaar) - ginServer.Handle("POST", "/api/setting/refreshVirtualBlockRef", model.CheckAuth, model.CheckReadonly, refreshVirtualBlockRef) - ginServer.Handle("POST", "/api/setting/addVirtualBlockRefInclude", model.CheckAuth, model.CheckReadonly, addVirtualBlockRefInclude) - ginServer.Handle("POST", "/api/setting/addVirtualBlockRefExclude", model.CheckAuth, model.CheckReadonly, addVirtualBlockRefExclude) - ginServer.Handle("POST", "/api/setting/setSnippet", model.CheckAuth, model.CheckReadonly, setConfSnippet) - ginServer.Handle("POST", "/api/setting/setEditorReadOnly", model.CheckAuth, model.CheckReadonly, setEditorReadOnly) - - ginServer.Handle("POST", "/api/graph/resetGraph", model.CheckAuth, model.CheckReadonly, resetGraph) - ginServer.Handle("POST", "/api/graph/resetLocalGraph", model.CheckAuth, model.CheckReadonly, resetLocalGraph) + ginServer.Handle("POST", "/api/transactions", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, performTransactions) + + ginServer.Handle("POST", "/api/setting/setAccount", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAccount) + ginServer.Handle("POST", "/api/setting/setEditor", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setEditor) + ginServer.Handle("POST", "/api/setting/setExport", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setExport) + ginServer.Handle("POST", "/api/setting/setFiletree", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setFiletree) + ginServer.Handle("POST", "/api/setting/setSearch", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSearch) + ginServer.Handle("POST", "/api/setting/setKeymap", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setKeymap) + ginServer.Handle("POST", "/api/setting/setAppearance", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAppearance) + ginServer.Handle("POST", "/api/setting/getCloudUser", model.CheckAuth, model.CheckAdminRole, getCloudUser) + ginServer.Handle("POST", "/api/setting/logoutCloudUser", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, logoutCloudUser) + ginServer.Handle("POST", "/api/setting/login2faCloudUser", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, login2faCloudUser) + ginServer.Handle("POST", "/api/setting/setEmoji", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setEmoji) + ginServer.Handle("POST", "/api/setting/setFlashcard", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setFlashcard) + ginServer.Handle("POST", "/api/setting/setAI", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAI) + ginServer.Handle("POST", "/api/setting/setBazaar", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setBazaar) + ginServer.Handle("POST", "/api/setting/setPublish", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setPublish) + ginServer.Handle("POST", "/api/setting/getPublish", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getPublish) + ginServer.Handle("POST", "/api/setting/refreshVirtualBlockRef", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, refreshVirtualBlockRef) + ginServer.Handle("POST", "/api/setting/addVirtualBlockRefInclude", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addVirtualBlockRefInclude) + ginServer.Handle("POST", "/api/setting/addVirtualBlockRefExclude", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addVirtualBlockRefExclude) + ginServer.Handle("POST", "/api/setting/setSnippet", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setConfSnippet) + ginServer.Handle("POST", "/api/setting/setEditorReadOnly", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setEditorReadOnly) + + ginServer.Handle("POST", "/api/graph/resetGraph", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, resetGraph) + ginServer.Handle("POST", "/api/graph/resetLocalGraph", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, resetLocalGraph) ginServer.Handle("POST", "/api/graph/getGraph", model.CheckAuth, getGraph) ginServer.Handle("POST", "/api/graph/getLocalGraph", model.CheckAuth, getLocalGraph) ginServer.Handle("POST", "/api/bazaar/getBazaarPlugin", model.CheckAuth, getBazaarPlugin) ginServer.Handle("POST", "/api/bazaar/getInstalledPlugin", model.CheckAuth, getInstalledPlugin) - ginServer.Handle("POST", "/api/bazaar/installBazaarPlugin", model.CheckAuth, model.CheckReadonly, installBazaarPlugin) - ginServer.Handle("POST", "/api/bazaar/uninstallBazaarPlugin", model.CheckAuth, model.CheckReadonly, uninstallBazaarPlugin) + ginServer.Handle("POST", "/api/bazaar/installBazaarPlugin", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, installBazaarPlugin) + ginServer.Handle("POST", "/api/bazaar/uninstallBazaarPlugin", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uninstallBazaarPlugin) ginServer.Handle("POST", "/api/bazaar/getBazaarWidget", model.CheckAuth, getBazaarWidget) ginServer.Handle("POST", "/api/bazaar/getInstalledWidget", model.CheckAuth, getInstalledWidget) - ginServer.Handle("POST", "/api/bazaar/installBazaarWidget", model.CheckAuth, model.CheckReadonly, installBazaarWidget) - ginServer.Handle("POST", "/api/bazaar/uninstallBazaarWidget", model.CheckAuth, model.CheckReadonly, uninstallBazaarWidget) + ginServer.Handle("POST", "/api/bazaar/installBazaarWidget", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, installBazaarWidget) + ginServer.Handle("POST", "/api/bazaar/uninstallBazaarWidget", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uninstallBazaarWidget) ginServer.Handle("POST", "/api/bazaar/getBazaarIcon", model.CheckAuth, getBazaarIcon) ginServer.Handle("POST", "/api/bazaar/getInstalledIcon", model.CheckAuth, getInstalledIcon) - ginServer.Handle("POST", "/api/bazaar/installBazaarIcon", model.CheckAuth, model.CheckReadonly, installBazaarIcon) - ginServer.Handle("POST", "/api/bazaar/uninstallBazaarIcon", model.CheckAuth, model.CheckReadonly, uninstallBazaarIcon) + ginServer.Handle("POST", "/api/bazaar/installBazaarIcon", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, installBazaarIcon) + ginServer.Handle("POST", "/api/bazaar/uninstallBazaarIcon", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uninstallBazaarIcon) ginServer.Handle("POST", "/api/bazaar/getBazaarTemplate", model.CheckAuth, getBazaarTemplate) ginServer.Handle("POST", "/api/bazaar/getInstalledTemplate", model.CheckAuth, getInstalledTemplate) - ginServer.Handle("POST", "/api/bazaar/installBazaarTemplate", model.CheckAuth, model.CheckReadonly, installBazaarTemplate) - ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTemplate", model.CheckAuth, model.CheckReadonly, uninstallBazaarTemplate) + ginServer.Handle("POST", "/api/bazaar/installBazaarTemplate", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, installBazaarTemplate) + ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTemplate", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uninstallBazaarTemplate) ginServer.Handle("POST", "/api/bazaar/getBazaarTheme", model.CheckAuth, getBazaarTheme) ginServer.Handle("POST", "/api/bazaar/getInstalledTheme", model.CheckAuth, getInstalledTheme) - ginServer.Handle("POST", "/api/bazaar/installBazaarTheme", model.CheckAuth, model.CheckReadonly, installBazaarTheme) - ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTheme", model.CheckAuth, model.CheckReadonly, uninstallBazaarTheme) + ginServer.Handle("POST", "/api/bazaar/installBazaarTheme", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, installBazaarTheme) + ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTheme", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uninstallBazaarTheme) ginServer.Handle("POST", "/api/bazaar/getBazaarPackageREAME", model.CheckAuth, getBazaarPackageREAME) ginServer.Handle("POST", "/api/bazaar/getUpdatedPackage", model.CheckAuth, getUpdatedPackage) - ginServer.Handle("POST", "/api/bazaar/batchUpdatePackage", model.CheckAuth, batchUpdatePackage) - - ginServer.Handle("POST", "/api/repo/initRepoKey", model.CheckAuth, model.CheckReadonly, initRepoKey) - ginServer.Handle("POST", "/api/repo/initRepoKeyFromPassphrase", model.CheckAuth, model.CheckReadonly, initRepoKeyFromPassphrase) - ginServer.Handle("POST", "/api/repo/resetRepo", model.CheckAuth, model.CheckReadonly, resetRepo) - ginServer.Handle("POST", "/api/repo/purgeRepo", model.CheckAuth, model.CheckReadonly, purgeRepo) - ginServer.Handle("POST", "/api/repo/purgeCloudRepo", model.CheckAuth, model.CheckReadonly, purgeCloudRepo) - ginServer.Handle("POST", "/api/repo/importRepoKey", model.CheckAuth, model.CheckReadonly, importRepoKey) - ginServer.Handle("POST", "/api/repo/createSnapshot", model.CheckAuth, model.CheckReadonly, createSnapshot) - ginServer.Handle("POST", "/api/repo/tagSnapshot", model.CheckAuth, model.CheckReadonly, tagSnapshot) - ginServer.Handle("POST", "/api/repo/checkoutRepo", model.CheckAuth, model.CheckReadonly, checkoutRepo) - ginServer.Handle("POST", "/api/repo/getRepoSnapshots", model.CheckAuth, getRepoSnapshots) - ginServer.Handle("POST", "/api/repo/getRepoTagSnapshots", model.CheckAuth, getRepoTagSnapshots) - ginServer.Handle("POST", "/api/repo/removeRepoTagSnapshot", model.CheckAuth, model.CheckReadonly, removeRepoTagSnapshot) - ginServer.Handle("POST", "/api/repo/getCloudRepoTagSnapshots", model.CheckAuth, getCloudRepoTagSnapshots) - ginServer.Handle("POST", "/api/repo/getCloudRepoSnapshots", model.CheckAuth, getCloudRepoSnapshots) - ginServer.Handle("POST", "/api/repo/removeCloudRepoTagSnapshot", model.CheckAuth, model.CheckReadonly, removeCloudRepoTagSnapshot) - ginServer.Handle("POST", "/api/repo/uploadCloudSnapshot", model.CheckAuth, model.CheckReadonly, uploadCloudSnapshot) - ginServer.Handle("POST", "/api/repo/downloadCloudSnapshot", model.CheckAuth, model.CheckReadonly, downloadCloudSnapshot) - ginServer.Handle("POST", "/api/repo/diffRepoSnapshots", model.CheckAuth, diffRepoSnapshots) - ginServer.Handle("POST", "/api/repo/openRepoSnapshotDoc", model.CheckAuth, openRepoSnapshotDoc) - ginServer.Handle("POST", "/api/repo/getRepoFile", model.CheckAuth, getRepoFile) - - ginServer.Handle("POST", "/api/riff/createRiffDeck", model.CheckAuth, model.CheckReadonly, createRiffDeck) - ginServer.Handle("POST", "/api/riff/renameRiffDeck", model.CheckAuth, model.CheckReadonly, renameRiffDeck) - ginServer.Handle("POST", "/api/riff/removeRiffDeck", model.CheckAuth, model.CheckReadonly, removeRiffDeck) - ginServer.Handle("POST", "/api/riff/getRiffDecks", model.CheckAuth, getRiffDecks) - ginServer.Handle("POST", "/api/riff/addRiffCards", model.CheckAuth, model.CheckReadonly, addRiffCards) - ginServer.Handle("POST", "/api/riff/removeRiffCards", model.CheckAuth, model.CheckReadonly, removeRiffCards) - ginServer.Handle("POST", "/api/riff/getRiffDueCards", model.CheckAuth, getRiffDueCards) - ginServer.Handle("POST", "/api/riff/getTreeRiffDueCards", model.CheckAuth, getTreeRiffDueCards) - ginServer.Handle("POST", "/api/riff/getNotebookRiffDueCards", model.CheckAuth, getNotebookRiffDueCards) - ginServer.Handle("POST", "/api/riff/reviewRiffCard", model.CheckAuth, model.CheckReadonly, reviewRiffCard) - ginServer.Handle("POST", "/api/riff/skipReviewRiffCard", model.CheckAuth, model.CheckReadonly, skipReviewRiffCard) - ginServer.Handle("POST", "/api/riff/getRiffCards", model.CheckAuth, getRiffCards) - ginServer.Handle("POST", "/api/riff/getTreeRiffCards", model.CheckAuth, getTreeRiffCards) - ginServer.Handle("POST", "/api/riff/getNotebookRiffCards", model.CheckAuth, getNotebookRiffCards) - ginServer.Handle("POST", "/api/riff/resetRiffCards", model.CheckAuth, model.CheckReadonly, resetRiffCards) - ginServer.Handle("POST", "/api/riff/batchSetRiffCardsDueTime", model.CheckAuth, model.CheckReadonly, batchSetRiffCardsDueTime) - ginServer.Handle("POST", "/api/riff/getRiffCardsByBlockIDs", model.CheckAuth, model.CheckReadonly, getRiffCardsByBlockIDs) - - ginServer.Handle("POST", "/api/notification/pushMsg", model.CheckAuth, pushMsg) - ginServer.Handle("POST", "/api/notification/pushErrMsg", model.CheckAuth, pushErrMsg) + ginServer.Handle("POST", "/api/bazaar/batchUpdatePackage", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, batchUpdatePackage) + + ginServer.Handle("POST", "/api/repo/initRepoKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, initRepoKey) + ginServer.Handle("POST", "/api/repo/initRepoKeyFromPassphrase", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, initRepoKeyFromPassphrase) + ginServer.Handle("POST", "/api/repo/resetRepo", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, resetRepo) + ginServer.Handle("POST", "/api/repo/purgeRepo", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, purgeRepo) + ginServer.Handle("POST", "/api/repo/purgeCloudRepo", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, purgeCloudRepo) + ginServer.Handle("POST", "/api/repo/importRepoKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importRepoKey) + ginServer.Handle("POST", "/api/repo/createSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createSnapshot) + ginServer.Handle("POST", "/api/repo/tagSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, tagSnapshot) + ginServer.Handle("POST", "/api/repo/checkoutRepo", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, checkoutRepo) + ginServer.Handle("POST", "/api/repo/getRepoSnapshots", model.CheckAuth, model.CheckAdminRole, getRepoSnapshots) + ginServer.Handle("POST", "/api/repo/getRepoTagSnapshots", model.CheckAuth, model.CheckAdminRole, getRepoTagSnapshots) + ginServer.Handle("POST", "/api/repo/removeRepoTagSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeRepoTagSnapshot) + ginServer.Handle("POST", "/api/repo/getCloudRepoTagSnapshots", model.CheckAuth, model.CheckAdminRole, getCloudRepoTagSnapshots) + ginServer.Handle("POST", "/api/repo/getCloudRepoSnapshots", model.CheckAuth, model.CheckAdminRole, getCloudRepoSnapshots) + ginServer.Handle("POST", "/api/repo/removeCloudRepoTagSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeCloudRepoTagSnapshot) + ginServer.Handle("POST", "/api/repo/uploadCloudSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, uploadCloudSnapshot) + ginServer.Handle("POST", "/api/repo/downloadCloudSnapshot", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, downloadCloudSnapshot) + ginServer.Handle("POST", "/api/repo/diffRepoSnapshots", model.CheckAuth, model.CheckAdminRole, diffRepoSnapshots) + ginServer.Handle("POST", "/api/repo/openRepoSnapshotDoc", model.CheckAuth, model.CheckAdminRole, openRepoSnapshotDoc) + ginServer.Handle("POST", "/api/repo/getRepoFile", model.CheckAuth, model.CheckAdminRole, getRepoFile) + + ginServer.Handle("POST", "/api/riff/createRiffDeck", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, createRiffDeck) + ginServer.Handle("POST", "/api/riff/renameRiffDeck", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameRiffDeck) + ginServer.Handle("POST", "/api/riff/removeRiffDeck", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeRiffDeck) + ginServer.Handle("POST", "/api/riff/getRiffDecks", model.CheckAuth, model.CheckAdminRole, getRiffDecks) + ginServer.Handle("POST", "/api/riff/addRiffCards", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addRiffCards) + ginServer.Handle("POST", "/api/riff/removeRiffCards", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeRiffCards) + ginServer.Handle("POST", "/api/riff/getRiffDueCards", model.CheckAuth, model.CheckAdminRole, getRiffDueCards) + ginServer.Handle("POST", "/api/riff/getTreeRiffDueCards", model.CheckAuth, model.CheckAdminRole, getTreeRiffDueCards) + ginServer.Handle("POST", "/api/riff/getNotebookRiffDueCards", model.CheckAuth, model.CheckAdminRole, getNotebookRiffDueCards) + ginServer.Handle("POST", "/api/riff/reviewRiffCard", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, reviewRiffCard) + ginServer.Handle("POST", "/api/riff/skipReviewRiffCard", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, skipReviewRiffCard) + ginServer.Handle("POST", "/api/riff/getRiffCards", model.CheckAuth, model.CheckAdminRole, getRiffCards) + ginServer.Handle("POST", "/api/riff/getTreeRiffCards", model.CheckAuth, model.CheckAdminRole, getTreeRiffCards) + ginServer.Handle("POST", "/api/riff/getNotebookRiffCards", model.CheckAuth, model.CheckAdminRole, getNotebookRiffCards) + ginServer.Handle("POST", "/api/riff/resetRiffCards", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, resetRiffCards) + ginServer.Handle("POST", "/api/riff/batchSetRiffCardsDueTime", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, batchSetRiffCardsDueTime) + ginServer.Handle("POST", "/api/riff/getRiffCardsByBlockIDs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getRiffCardsByBlockIDs) + + ginServer.Handle("POST", "/api/notification/pushMsg", model.CheckAuth, model.CheckAdminRole, pushMsg) + ginServer.Handle("POST", "/api/notification/pushErrMsg", model.CheckAuth, model.CheckAdminRole, pushErrMsg) ginServer.Handle("POST", "/api/snippet/getSnippet", model.CheckAuth, getSnippet) - ginServer.Handle("POST", "/api/snippet/setSnippet", model.CheckAuth, model.CheckReadonly, setSnippet) - ginServer.Handle("POST", "/api/snippet/removeSnippet", model.CheckAuth, model.CheckReadonly, removeSnippet) + ginServer.Handle("POST", "/api/snippet/setSnippet", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setSnippet) + ginServer.Handle("POST", "/api/snippet/removeSnippet", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeSnippet) ginServer.Handle("POST", "/api/av/renderAttributeView", model.CheckAuth, renderAttributeView) - ginServer.Handle("POST", "/api/av/renderHistoryAttributeView", model.CheckAuth, renderHistoryAttributeView) - ginServer.Handle("POST", "/api/av/renderSnapshotAttributeView", model.CheckAuth, renderSnapshotAttributeView) + ginServer.Handle("POST", "/api/av/renderHistoryAttributeView", model.CheckAuth, model.CheckAdminRole, renderHistoryAttributeView) + ginServer.Handle("POST", "/api/av/renderSnapshotAttributeView", model.CheckAuth, model.CheckAdminRole, renderSnapshotAttributeView) ginServer.Handle("POST", "/api/av/getAttributeViewKeys", model.CheckAuth, getAttributeViewKeys) - ginServer.Handle("POST", "/api/av/setAttributeViewBlockAttr", model.CheckAuth, model.CheckReadonly, setAttributeViewBlockAttr) + ginServer.Handle("POST", "/api/av/setAttributeViewBlockAttr", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setAttributeViewBlockAttr) ginServer.Handle("POST", "/api/av/searchAttributeView", model.CheckAuth, model.CheckReadonly, searchAttributeView) ginServer.Handle("POST", "/api/av/getAttributeView", model.CheckAuth, model.CheckReadonly, getAttributeView) - ginServer.Handle("POST", "/api/av/searchAttributeViewRelationKey", model.CheckAuth, model.CheckReadonly, searchAttributeViewRelationKey) - ginServer.Handle("POST", "/api/av/searchAttributeViewNonRelationKey", model.CheckAuth, model.CheckReadonly, searchAttributeViewNonRelationKey) - ginServer.Handle("POST", "/api/av/getAttributeViewFilterSort", model.CheckAuth, model.CheckReadonly, getAttributeViewFilterSort) - ginServer.Handle("POST", "/api/av/addAttributeViewKey", model.CheckAuth, model.CheckReadonly, addAttributeViewKey) - ginServer.Handle("POST", "/api/av/removeAttributeViewKey", model.CheckAuth, model.CheckReadonly, removeAttributeViewKey) - ginServer.Handle("POST", "/api/av/sortAttributeViewViewKey", model.CheckAuth, model.CheckReadonly, sortAttributeViewViewKey) - ginServer.Handle("POST", "/api/av/sortAttributeViewKey", model.CheckAuth, model.CheckReadonly, sortAttributeViewKey) - ginServer.Handle("POST", "/api/av/addAttributeViewBlocks", model.CheckAuth, model.CheckReadonly, addAttributeViewBlocks) - ginServer.Handle("POST", "/api/av/removeAttributeViewBlocks", model.CheckAuth, model.CheckReadonly, removeAttributeViewBlocks) - ginServer.Handle("POST", "/api/av/getAttributeViewPrimaryKeyValues", model.CheckAuth, model.CheckReadonly, getAttributeViewPrimaryKeyValues) - ginServer.Handle("POST", "/api/av/setDatabaseBlockView", model.CheckAuth, model.CheckReadonly, setDatabaseBlockView) - ginServer.Handle("POST", "/api/av/getMirrorDatabaseBlocks", model.CheckAuth, model.CheckReadonly, getMirrorDatabaseBlocks) - ginServer.Handle("POST", "/api/av/getAttributeViewKeysByAvID", model.CheckAuth, model.CheckReadonly, getAttributeViewKeysByAvID) - ginServer.Handle("POST", "/api/av/duplicateAttributeViewBlock", model.CheckAuth, model.CheckReadonly, duplicateAttributeViewBlock) - ginServer.Handle("POST", "/api/av/appendAttributeViewDetachedBlocksWithValues", model.CheckAuth, model.CheckReadonly, appendAttributeViewDetachedBlocksWithValues) - - ginServer.Handle("POST", "/api/ai/chatGPT", model.CheckAuth, chatGPT) - ginServer.Handle("POST", "/api/ai/chatGPTWithAction", model.CheckAuth, chatGPTWithAction) + ginServer.Handle("POST", "/api/av/searchAttributeViewRelationKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, searchAttributeViewRelationKey) + ginServer.Handle("POST", "/api/av/searchAttributeViewNonRelationKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, searchAttributeViewNonRelationKey) + ginServer.Handle("POST", "/api/av/getAttributeViewFilterSort", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getAttributeViewFilterSort) + ginServer.Handle("POST", "/api/av/addAttributeViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addAttributeViewKey) + ginServer.Handle("POST", "/api/av/removeAttributeViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeAttributeViewKey) + ginServer.Handle("POST", "/api/av/sortAttributeViewViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, sortAttributeViewViewKey) + ginServer.Handle("POST", "/api/av/sortAttributeViewKey", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, sortAttributeViewKey) + ginServer.Handle("POST", "/api/av/addAttributeViewBlocks", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, addAttributeViewBlocks) + ginServer.Handle("POST", "/api/av/removeAttributeViewBlocks", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeAttributeViewBlocks) + ginServer.Handle("POST", "/api/av/getAttributeViewPrimaryKeyValues", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getAttributeViewPrimaryKeyValues) + ginServer.Handle("POST", "/api/av/setDatabaseBlockView", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setDatabaseBlockView) + ginServer.Handle("POST", "/api/av/getMirrorDatabaseBlocks", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getMirrorDatabaseBlocks) + ginServer.Handle("POST", "/api/av/getAttributeViewKeysByAvID", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, getAttributeViewKeysByAvID) + ginServer.Handle("POST", "/api/av/duplicateAttributeViewBlock", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, duplicateAttributeViewBlock) + ginServer.Handle("POST", "/api/av/appendAttributeViewDetachedBlocksWithValues", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, appendAttributeViewDetachedBlocksWithValues) + + ginServer.Handle("POST", "/api/ai/chatGPT", model.CheckAuth, model.CheckAdminRole, chatGPT) + ginServer.Handle("POST", "/api/ai/chatGPTWithAction", model.CheckAuth, model.CheckAdminRole, chatGPTWithAction) ginServer.Handle("POST", "/api/petal/loadPetals", model.CheckAuth, loadPetals) - ginServer.Handle("POST", "/api/petal/setPetalEnabled", model.CheckAuth, model.CheckReadonly, setPetalEnabled) + ginServer.Handle("POST", "/api/petal/setPetalEnabled", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setPetalEnabled) - ginServer.Any("/api/network/echo", model.CheckAuth, echo) - ginServer.Handle("POST", "/api/network/forwardProxy", model.CheckAuth, forwardProxy) + ginServer.Any("/api/network/echo", model.CheckAuth, model.CheckAdminRole, echo) + ginServer.Handle("POST", "/api/network/forwardProxy", model.CheckAuth, model.CheckAdminRole, forwardProxy) - ginServer.Handle("GET", "/ws/broadcast", model.CheckAuth, broadcast) - ginServer.Handle("POST", "/api/broadcast/postMessage", model.CheckAuth, postMessage) - ginServer.Handle("POST", "/api/broadcast/getChannels", model.CheckAuth, getChannels) - ginServer.Handle("POST", "/api/broadcast/getChannelInfo", model.CheckAuth, getChannelInfo) + ginServer.Handle("GET", "/ws/broadcast", model.CheckAuth, model.CheckAdminRole, broadcast) + ginServer.Handle("POST", "/api/broadcast/postMessage", model.CheckAuth, model.CheckAdminRole, postMessage) + ginServer.Handle("POST", "/api/broadcast/getChannels", model.CheckAuth, model.CheckAdminRole, getChannels) + ginServer.Handle("POST", "/api/broadcast/getChannelInfo", model.CheckAuth, model.CheckAdminRole, getChannelInfo) - ginServer.Handle("POST", "/api/archive/zip", model.CheckAuth, model.CheckReadonly, zip) - ginServer.Handle("POST", "/api/archive/unzip", model.CheckAuth, model.CheckReadonly, unzip) + ginServer.Handle("POST", "/api/archive/zip", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, zip) + ginServer.Handle("POST", "/api/archive/unzip", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, unzip) } diff --git a/kernel/api/setting.go b/kernel/api/setting.go index 733c4eefd43..4a6322455ad 100644 --- a/kernel/api/setting.go +++ b/kernel/api/setting.go @@ -25,6 +25,7 @@ import ( "github.com/gin-gonic/gin" "github.com/siyuan-note/siyuan/kernel/conf" "github.com/siyuan-note/siyuan/kernel/model" + "github.com/siyuan-note/siyuan/kernel/server/proxy" "github.com/siyuan-note/siyuan/kernel/sql" "github.com/siyuan-note/siyuan/kernel/util" ) @@ -533,6 +534,58 @@ func setAppearance(c *gin.Context) { ret.Data = model.Conf.Appearance } +func setPublish(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + arg, ok := util.JsonArg(c, ret) + if !ok { + return + } + + param, err := gulu.JSON.MarshalJSON(arg) + if nil != err { + ret.Code = -1 + ret.Msg = err.Error() + return + } + + publish := &conf.Publish{} + if err = gulu.JSON.UnmarshalJSON(param, publish); nil != err { + ret.Code = -1 + ret.Msg = err.Error() + return + } + + model.Conf.Publish = publish + model.Conf.Save() + + if port, err := proxy.InitPublishService(); err != nil { + ret.Code = -1 + ret.Msg = err.Error() + } else { + ret.Data = map[string]any{ + "port": port, + "publish": model.Conf.Publish, + } + } +} + +func getPublish(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + if port, err := proxy.InitPublishService(); err != nil { + ret.Code = -1 + ret.Msg = err.Error() + } else { + ret.Data = map[string]any{ + "port": port, + "publish": model.Conf.Publish, + } + } +} + func getCloudUser(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) diff --git a/kernel/api/snippet.go b/kernel/api/snippet.go index 6d26dd3667f..b975d061b4a 100644 --- a/kernel/api/snippet.go +++ b/kernel/api/snippet.go @@ -17,44 +17,17 @@ package api import ( - "mime" "net/http" - "path/filepath" "strings" "github.com/88250/gulu" "github.com/88250/lute/ast" "github.com/gin-gonic/gin" - "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/conf" "github.com/siyuan-note/siyuan/kernel/model" "github.com/siyuan-note/siyuan/kernel/util" ) -func serveSnippets(c *gin.Context) { - filePath := strings.TrimPrefix(c.Request.URL.Path, "/snippets/") - ext := filepath.Ext(filePath) - name := strings.TrimSuffix(filePath, ext) - confSnippets, err := model.LoadSnippets() - if nil != err { - logging.LogErrorf("load snippets failed: %s", err) - c.Status(404) - return - } - - for _, s := range confSnippets { - if s.Name == name && ("" != ext && s.Type == ext[1:]) { - c.Header("Content-Type", mime.TypeByExtension(ext)) - c.String(http.StatusOK, s.Content) - return - } - } - - // 没有在配置文件中命中时在文件系统上查找 - filePath = filepath.Join(util.SnippetsPath, filePath) - c.File(filePath) -} - func getSnippet(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) diff --git a/kernel/api/system.go b/kernel/api/system.go index 18c235cf7a2..300d0353b56 100644 --- a/kernel/api/system.go +++ b/kernel/api/system.go @@ -17,7 +17,6 @@ package api import ( - "github.com/88250/lute" "net/http" "os" "path/filepath" @@ -25,6 +24,8 @@ import ( "sync" "time" + "github.com/88250/lute" + "github.com/88250/gulu" "github.com/gin-gonic/gin" "github.com/siyuan-note/logging" @@ -218,6 +219,17 @@ func getConf(c *gin.Context) { maskedConf.Sync.Stat = model.Conf.Language(53) } + // REF: https://github.com/siyuan-note/siyuan/issues/11364 + role := model.GetGinContextRole(c) + if model.IsReadOnlyRole(role) { + maskedConf.ReadOnly = true + } + if !model.IsValidRole(role, []model.Role{ + model.RoleAdministrator, + }) { + model.HideConfSecret(maskedConf) + } + ret.Data = map[string]interface{}{ "conf": maskedConf, "start": !util.IsUILoaded, diff --git a/kernel/conf/publish.go b/kernel/conf/publish.go new file mode 100644 index 00000000000..45552c5ccc5 --- /dev/null +++ b/kernel/conf/publish.go @@ -0,0 +1,45 @@ +// SiYuan - Refactor your thinking +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package conf + +type Publish struct { + Enable bool `json:"enable"` // 是否启用发布服务 + Port uint16 `json:"port"` // 发布服务端口 + Auth *BasicAuth `json:"auth"` // Basic 认证 +} + +type BasicAuth struct { + Enable bool `json:"enable"` // 是否启用基础认证 + Accounts []*BasicAuthAccount `json:"accounts"` // 账户列表 +} + +type BasicAuthAccount struct { + Username string `json:"username"` // 用户名 + Password string `json:"password"` // 密码 + Memo string `json:"memo"` // 备注 +} + +func NewPublish() *Publish { + return &Publish{ + Enable: false, + Port: 6808, + Auth: &BasicAuth{ + Enable: true, + Accounts: []*BasicAuthAccount{}, + }, + } +} diff --git a/kernel/go.mod b/kernel/go.mod index e847bdb50f6..8c5beb6e13b 100644 --- a/kernel/go.mod +++ b/kernel/go.mod @@ -33,6 +33,7 @@ require ( github.com/go-ole/go-ole v1.3.0 github.com/goccy/go-json v0.10.3 github.com/gofrs/flock v0.8.1 + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/gorilla/css v1.0.1 github.com/gorilla/websocket v1.5.1 github.com/imroc/req/v3 v3.43.7 diff --git a/kernel/go.sum b/kernel/go.sum index d756bc01484..5e996521d32 100644 --- a/kernel/go.sum +++ b/kernel/go.sum @@ -154,6 +154,8 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= diff --git a/kernel/model/auth.go b/kernel/model/auth.go new file mode 100644 index 00000000000..79022d226df --- /dev/null +++ b/kernel/model/auth.go @@ -0,0 +1,133 @@ +// SiYuan - Refactor your thinking +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package model + +import ( + "crypto/rand" + "net/http" + + "github.com/golang-jwt/jwt/v5" + "github.com/siyuan-note/logging" +) + +type Account struct { + Username string + Password string + Token string +} +type AccountsMap map[string]*Account +type ClaimsKeyType string + +const ( + XAuthTokenKey = "X-Auth-Token" + + ClaimsContextKey = "claims" + + iss = "siyuan-publish-reverse-proxy-server" + sub = "publish" + aud = "siyuan-kernel" + + ClaimsKeyRole string = "role" +) + +var ( + accountsMap = AccountsMap{} + + key = make([]byte, 32) +) + +func GetBasicAuthAccount(username string) *Account { + return accountsMap[username] +} + +func InitAccounts() { + accountsMap = AccountsMap{ + "": &Account{}, // 匿名用户 + } + for _, account := range Conf.Publish.Auth.Accounts { + accountsMap[account.Username] = &Account{ + Username: account.Username, + Password: account.Password, + } + } + + InitJWT() +} + +func InitJWT() { + if _, err := rand.Read(key); err != nil { + logging.LogErrorf("generate JWT signing key failed: %s", err) + return + } + + for username, account := range accountsMap { + // REF: https://golang-jwt.github.io/jwt/usage/create/ + t := jwt.NewWithClaims( + jwt.SigningMethodHS256, + jwt.MapClaims{ + "iss": iss, + "sub": sub, + "aud": aud, + "jti": username, + + ClaimsKeyRole: RoleReader, + }, + ) + if token, err := t.SignedString(key); err != nil { + logging.LogErrorf("JWT signature failed: %s", err) + return + } else { + account.Token = token + } + } +} + +func ParseJWT(tokenString string) (*jwt.Token, error) { + // REF: https://golang-jwt.github.io/jwt/usage/parse/ + return jwt.Parse( + tokenString, + func(token *jwt.Token) (interface{}, error) { + return key, nil + }, + jwt.WithIssuer(iss), + jwt.WithSubject(sub), + jwt.WithAudience(aud), + ) +} + +func ParseXAuthToken(r *http.Request) *jwt.Token { + tokenString := r.Header.Get(XAuthTokenKey) + if tokenString != "" { + if token, err := ParseJWT(tokenString); err != nil { + logging.LogErrorf("JWT parse failed: %s", err) + } else { + return token + } + } + return nil +} + +func GetTokenClaims(token *jwt.Token) jwt.MapClaims { + return token.Claims.(jwt.MapClaims) +} + +func GetClaimRole(claims jwt.MapClaims) Role { + if role := claims[ClaimsKeyRole]; role != nil { + return Role(role.(float64)) + } + return RoleVisitor +} diff --git a/kernel/model/conf.go b/kernel/model/conf.go index cbc3f9e2d1c..9a518967050 100644 --- a/kernel/model/conf.go +++ b/kernel/model/conf.go @@ -76,6 +76,7 @@ type AppConf struct { Stat *conf.Stat `json:"stat"` // 统计 Api *conf.API `json:"api"` // API Repo *conf.Repo `json:"repo"` // 数据仓库 + Publish *conf.Publish `json:"publish"` // 发布服务 OpenHelp bool `json:"openHelp"` // 启动后是否需要打开用户指南 ShowChangelog bool `json:"showChangelog"` // 是否显示版本更新日志 CloudRegion int `json:"cloudRegion"` // 云端区域,0:中国大陆,1:北美 @@ -357,6 +358,10 @@ func InitConf() { Conf.Bazaar = conf.NewBazaar() } + if nil == Conf.Publish { + Conf.Publish = conf.NewPublish() + } + if nil == Conf.Repo { Conf.Repo = conf.NewRepo() } @@ -894,6 +899,24 @@ func GetMaskedConf() (ret *AppConf, err error) { return } +// REF: https://github.com/siyuan-note/siyuan/issues/11364 +// HideConfSecret 隐藏设置中的秘密信息 +func HideConfSecret(c *AppConf) { + c.AI = &conf.AI{} + c.Api = &conf.API{} + c.Flashcard = &conf.Flashcard{} + c.LocalIPs = []string{} + c.Publish = &conf.Publish{} + c.Repo = &conf.Repo{} + c.Sync = &conf.Sync{} + c.System.AppDir = "" + c.System.ConfDir = "" + c.System.DataDir = "" + c.System.HomeDir = "" + c.System.Name = "" + c.System.NetworkProxy = &conf.NetworkProxy{} +} + func clearPortJSON() { pid := fmt.Sprintf("%d", os.Getpid()) portJSON := filepath.Join(util.HomeDir, ".config", "siyuan", "port.json") diff --git a/kernel/model/role.go b/kernel/model/role.go new file mode 100644 index 00000000000..5f0fca3aa55 --- /dev/null +++ b/kernel/model/role.go @@ -0,0 +1,56 @@ +// SiYuan - Refactor your thinking +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package model + +import "github.com/gin-gonic/gin" + +type Role uint + +const ( + RoleContextKey = "role" +) + +const ( + RoleAdministrator Role = iota // 管理员 + RoleEditor // 编辑者 + RoleReader // 读者 + RoleVisitor // 匿名访问者 +) + +func IsValidRole(role Role, roles []Role) bool { + for _, role_ := range roles { + if role == role_ { + return true + } + } + return false +} + +func IsReadOnlyRole(role Role) bool { + return IsValidRole(role, []Role{ + RoleReader, + RoleVisitor, + }) +} + +func GetGinContextRole(c *gin.Context) Role { + if role, exists := c.Get(RoleContextKey); exists { + return role.(Role) + } else { + return RoleVisitor + } +} diff --git a/kernel/model/session.go b/kernel/model/session.go index 1f1ea227344..9eff880344e 100644 --- a/kernel/model/session.go +++ b/kernel/model/session.go @@ -164,6 +164,16 @@ func CheckReadonly(c *gin.Context) { } func CheckAuth(c *gin.Context) { + // 已通过 JWT 认证 + if role := GetGinContextRole(c); IsValidRole(role, []Role{ + RoleAdministrator, + RoleEditor, + RoleReader, + }) { + c.Next() + return + } + //logging.LogInfof("check auth for [%s]", c.Request.RequestURI) localhost := util.IsLocalHost(c.Request.RemoteAddr) @@ -171,6 +181,7 @@ func CheckAuth(c *gin.Context) { if "" == Conf.AccessAuthCode { // Skip the empty access authorization code check https://github.com/siyuan-note/siyuan/issues/9709 if util.SiyuanAccessAuthCodeBypass { + c.Set(RoleContextKey, RoleAdministrator) c.Next() return } @@ -190,6 +201,7 @@ func CheckAuth(c *gin.Context) { return } + c.Set(RoleContextKey, RoleAdministrator) c.Next() return } @@ -206,19 +218,23 @@ func CheckAuth(c *gin.Context) { // 放过来自本机的某些请求 if localhost { if strings.HasPrefix(c.Request.RequestURI, "/assets/") { + c.Set(RoleContextKey, RoleAdministrator) c.Next() return } if strings.HasPrefix(c.Request.RequestURI, "/api/system/exit") { + c.Set(RoleContextKey, RoleAdministrator) c.Next() return } if strings.HasPrefix(c.Request.RequestURI, "/api/system/getNetwork") { + c.Set(RoleContextKey, RoleAdministrator) c.Next() return } if strings.HasPrefix(c.Request.RequestURI, "/api/sync/performSync") { if util.ContainerIOS == util.Container || util.ContainerAndroid == util.Container { + c.Set(RoleContextKey, RoleAdministrator) c.Next() return } @@ -229,6 +245,7 @@ func CheckAuth(c *gin.Context) { session := util.GetSession(c) workspaceSession := util.GetWorkspaceSession(session) if workspaceSession.AccessAuthCode == Conf.AccessAuthCode { + c.Set(RoleContextKey, RoleAdministrator) c.Next() return } @@ -248,6 +265,7 @@ func CheckAuth(c *gin.Context) { if "" != token { if Conf.Api.Token == token { + c.Set(RoleContextKey, RoleAdministrator) c.Next() return } @@ -261,6 +279,7 @@ func CheckAuth(c *gin.Context) { // 通过 API token (query-params: token) if token := c.Query("token"); "" != token { if Conf.Api.Token == token { + c.Set(RoleContextKey, RoleAdministrator) c.Next() return } @@ -300,9 +319,43 @@ func CheckAuth(c *gin.Context) { return } + c.Set(RoleContextKey, RoleAdministrator) c.Next() } +func CheckAdminRole(c *gin.Context) { + if IsValidRole(GetGinContextRole(c), []Role{ + RoleAdministrator, + }) { + c.Next() + } else { + c.AbortWithStatus(http.StatusForbidden) + } +} + +func CheckEditRole(c *gin.Context) { + if IsValidRole(GetGinContextRole(c), []Role{ + RoleAdministrator, + RoleEditor, + }) { + c.Next() + } else { + c.AbortWithStatus(http.StatusForbidden) + } +} + +func CheckReadRole(c *gin.Context) { + if IsValidRole(GetGinContextRole(c), []Role{ + RoleAdministrator, + RoleEditor, + RoleReader, + }) { + c.Next() + } else { + c.AbortWithStatus(http.StatusForbidden) + } +} + var timingAPIs = map[string]int{ "/api/search/fullTextSearchBlock": 200, // Monitor the search performance and suggest solutions https://github.com/siyuan-note/siyuan/issues/7873 } diff --git a/kernel/server/port.go b/kernel/server/port.go index ec26f4e1fdc..b982bb9221f 100644 --- a/kernel/server/port.go +++ b/kernel/server/port.go @@ -18,7 +18,6 @@ package server import ( "fmt" - "net" "os" "os/exec" "path/filepath" @@ -65,7 +64,7 @@ func killRunningKernel() { } func killByPort(port string) { - if !isPortOpen(port) { + if !util.IsPortOpen(port) { return } @@ -87,19 +86,6 @@ func killByPort(port string) { logging.LogInfof("killed process [name=%s, pid=%s]", name, pid) } -func isPortOpen(port string) bool { - timeout := time.Second - conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", port), timeout) - if nil != err { - return false - } - if nil != conn { - conn.Close() - return true - } - return false -} - func kill(pid string) { var killCmd *exec.Cmd if gulu.OS.IsWindows() { diff --git a/kernel/server/proxy/fixedport.go b/kernel/server/proxy/fixedport.go new file mode 100644 index 00000000000..dface6bc1e6 --- /dev/null +++ b/kernel/server/proxy/fixedport.go @@ -0,0 +1,41 @@ +// SiYuan - Refactor your thinking +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package proxy + +import ( + "net/http" + "net/http/httputil" + + "github.com/siyuan-note/logging" + "github.com/siyuan-note/siyuan/kernel/util" +) + +func InitFixedPortService(host string) { + if util.FixedPort != util.ServerPort { + if util.IsPortOpen(util.FixedPort) { + return + } + + // 启动一个固定 6806 端口的反向代理服务器,这样浏览器扩展才能直接使用 127.0.0.1:6806,不用配置端口 + proxy := httputil.NewSingleHostReverseProxy(util.ServerURL) + logging.LogInfof("fixed port service [%s:%s] is running", host, util.FixedPort) + if proxyErr := http.ListenAndServe(host+":"+util.FixedPort, proxy); nil != proxyErr { + logging.LogWarnf("boot fixed port service [%s] failed: %s", util.ServerURL, proxyErr) + } + logging.LogInfof("fixed port service [%s:%s] is stopped", host, util.FixedPort) + } +} diff --git a/kernel/server/proxy/publish.go b/kernel/server/proxy/publish.go new file mode 100644 index 00000000000..6490d50521f --- /dev/null +++ b/kernel/server/proxy/publish.go @@ -0,0 +1,161 @@ +// SiYuan - Refactor your thinking +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package proxy + +import ( + "fmt" + "net" + "net/http" + "net/http/httputil" + "strconv" + + "github.com/siyuan-note/logging" + "github.com/siyuan-note/siyuan/kernel/model" + "github.com/siyuan-note/siyuan/kernel/util" +) + +type PublishServiceTransport struct{} + +var ( + Host = "0.0.0.0" + Port = "0" + + listener net.Listener + transport = PublishServiceTransport{} + proxy = &httputil.ReverseProxy{ + Rewrite: rewrite, + Transport: transport, + } +) + +func InitPublishService() (uint16, error) { + model.InitAccounts() + + if listener != nil { + if !model.Conf.Publish.Enable { + // 关闭发布服务 + closePublishListener() + return 0, nil + } + + if port, err := util.ParsePort(Port); err != nil { + return 0, err + } else if port != model.Conf.Publish.Port { + // 关闭原端口的发布服务 + if err = closePublishListener(); err != nil { + return 0, err + } + + // 重新启动新端口的发布服务 + initPublishService() + } + } else { + if !model.Conf.Publish.Enable { + return 0, nil + } + + // 启动新端口的发布服务 + initPublishService() + } + return util.ParsePort(Port) +} + +func initPublishService() (err error) { + if err = initPublishListener(); err == nil { + go startPublishReverseProxyService() + } + return +} + +func initPublishListener() (err error) { + // Start new listener + listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", Host, model.Conf.Publish.Port)) + if err != nil { + logging.LogErrorf("start listener failed: %s", err) + return + } + + _, Port, err = net.SplitHostPort(listener.Addr().String()) + if nil != err { + logging.LogErrorf("split host and port failed: %s", err) + return + } + return +} + +func closePublishListener() (err error) { + listener_ := listener + listener = nil + if err = listener_.Close(); err != nil { + logging.LogErrorf("close listener %s failed: %s", listener_.Addr().String(), err) + listener = listener_ + } + return +} + +func startPublishReverseProxyService() { + logging.LogInfof("publish service [%s:%s] is running", Host, Port) + // 服务进行时一直阻塞 + if err := http.Serve(listener, proxy); nil != err { + if listener != nil { + logging.LogErrorf("boot publish service failed: %s", err) + } + } + logging.LogInfof("publish service [%s:%s] is stopped", Host, Port) +} + +func rewrite(r *httputil.ProxyRequest) { + r.SetURL(util.ServerURL) + r.SetXForwarded() + // r.Out.Host = r.In.Host // if desired +} + +func (PublishServiceTransport) RoundTrip(request *http.Request) (response *http.Response, err error) { + if model.Conf.Publish.Auth.Enable { + // Basic Auth + username, password, ok := request.BasicAuth() + account := model.GetBasicAuthAccount(username) + + if !ok || + account == nil || + account.Username == "" || // 匿名用户 + account.Password != password { + + return &http.Response{ + StatusCode: http.StatusUnauthorized, + Status: http.StatusText(http.StatusUnauthorized), + Proto: request.Proto, + ProtoMajor: request.ProtoMajor, + ProtoMinor: request.ProtoMinor, + Request: request, + Header: http.Header{ + "WWW-Authenticate": {"Basic realm=" + strconv.Quote("Authorization Required")}, + }, + Close: false, + ContentLength: -1, + }, nil + } else { + // set JWT + request.Header.Set(model.XAuthTokenKey, account.Token) + } + } else { + request.Header.Set(model.XAuthTokenKey, model.GetBasicAuthAccount("").Token) + } + + response, err = http.DefaultTransport.RoundTrip(request) + return +} diff --git a/kernel/server/serve.go b/kernel/server/serve.go index 77d7e98b38b..b5b69692296 100644 --- a/kernel/server/serve.go +++ b/kernel/server/serve.go @@ -20,9 +20,9 @@ import ( "bytes" "fmt" "html/template" + "mime" "net" "net/http" - "net/http/httputil" "net/http/pprof" "net/url" "os" @@ -42,10 +42,13 @@ import ( "github.com/siyuan-note/siyuan/kernel/api" "github.com/siyuan-note/siyuan/kernel/cmd" "github.com/siyuan-note/siyuan/kernel/model" + "github.com/siyuan-note/siyuan/kernel/server/proxy" "github.com/siyuan-note/siyuan/kernel/util" ) -var cookieStore = cookie.NewStore([]byte("ATN51UlxVq1Gcvdf")) +var ( + cookieStore = cookie.NewStore([]byte("ATN51UlxVq1Gcvdf")) +) func Serve(fastMode bool) { gin.SetMode(gin.ReleaseMode) @@ -57,6 +60,7 @@ func Serve(fastMode bool) { model.Timing, model.Recover, corsMiddleware(), // 后端服务支持 CORS 预检请求验证 https://github.com/siyuan-note/siyuan/pull/5593 + jwtMiddleware, // 解析 JWT https://github.com/siyuan-note/siyuan/issues/11364 gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedExtensions([]string{".pdf", ".mp3", ".wav", ".ogg", ".mov", ".weba", ".mkv", ".mp4", ".webm"})), ) @@ -78,7 +82,10 @@ func Serve(fastMode bool) { serveEmojis(ginServer) serveTemplates(ginServer) servePublic(ginServer) + serveSnippets(ginServer) serveRepoDiff(ginServer) + serveCheckAuth(ginServer) + serveFixedStaticFiles(ginServer) api.ServeAPI(ginServer) var host string @@ -108,34 +115,27 @@ func Serve(fastMode bool) { } util.ServerPort = port + util.ServerURL, err = url.Parse("http://127.0.0.1:" + port) + if err != nil { + logging.LogErrorf("parse server url failed: %s", err) + } + pid := fmt.Sprintf("%d", os.Getpid()) if !fastMode { rewritePortJSON(pid, port) } - logging.LogInfof("kernel [pid=%s] http server [%s] is booting", pid, host+":"+port) util.HttpServing = true + go util.HookUILoaded() + go func() { time.Sleep(1 * time.Second) - if util.FixedPort != port { - if isPortOpen(util.FixedPort) { - return - } - - // 启动一个 6806 端口的反向代理服务器,这样浏览器扩展才能直接使用 127.0.0.1:6806,不用配置端口 - serverURL, _ := url.Parse("http://127.0.0.1:" + port) - proxy := httputil.NewSingleHostReverseProxy(serverURL) - logging.LogInfof("reverse proxy server [%s] is booting", host+":"+util.FixedPort) - if proxyErr := http.ListenAndServe(host+":"+util.FixedPort, proxy); nil != proxyErr { - logging.LogWarnf("boot reverse proxy server [%s] failed: %s", serverURL, proxyErr) - } - // 反代服务器启动失败不影响核心服务器启动 - } + go proxy.InitFixedPortService(host) + go proxy.InitPublishService() + // 反代服务器启动失败不影响核心服务器启动 }() - go util.HookUILoaded() - if err = http.Serve(ln, ginServer.Handler()); nil != err { if !fastMode { logging.LogErrorf("boot kernel failed: %s", err) @@ -196,11 +196,33 @@ func servePublic(ginServer *gin.Engine) { ginServer.Static("/public/", filepath.Join(util.DataDir, "public")) } -func serveAppearance(ginServer *gin.Engine) { - ginServer.StaticFile("favicon.ico", filepath.Join(util.WorkingDir, "stage", "icon.png")) - ginServer.StaticFile("manifest.json", filepath.Join(util.WorkingDir, "stage", "manifest.webmanifest")) - ginServer.StaticFile("manifest.webmanifest", filepath.Join(util.WorkingDir, "stage", "manifest.webmanifest")) +func serveSnippets(ginServer *gin.Engine) { + ginServer.Handle("GET", "/snippets/*filepath", func(c *gin.Context) { + filePath := strings.TrimPrefix(c.Request.URL.Path, "/snippets/") + ext := filepath.Ext(filePath) + name := strings.TrimSuffix(filePath, ext) + confSnippets, err := model.LoadSnippets() + if nil != err { + logging.LogErrorf("load snippets failed: %s", err) + c.Status(http.StatusNotFound) + return + } + + for _, s := range confSnippets { + if s.Name == name && ("" != ext && s.Type == ext[1:]) { + c.Header("Content-Type", mime.TypeByExtension(ext)) + c.String(http.StatusOK, s.Content) + return + } + } + // 没有在配置文件中命中时在文件系统上查找 + filePath = filepath.Join(util.SnippetsPath, filePath) + c.File(filePath) + }) +} + +func serveAppearance(ginServer *gin.Engine) { siyuan := ginServer.Group("", model.CheckAuth) siyuan.Handle("GET", "/", func(c *gin.Context) { @@ -295,12 +317,13 @@ func serveAppearance(ginServer *gin.Engine) { }) siyuan.Static("/stage/", filepath.Join(util.WorkingDir, "stage")) - ginServer.StaticFile("service-worker.js", filepath.Join(util.WorkingDir, "stage", "service-worker.js")) +} - siyuan.GET("/check-auth", serveCheckAuth) +func serveCheckAuth(ginServer *gin.Engine) { + ginServer.GET("/check-auth", serveAuthPage) } -func serveCheckAuth(c *gin.Context) { +func serveAuthPage(c *gin.Context) { data, err := os.ReadFile(filepath.Join(util.WorkingDir, "stage/auth.html")) if nil != err { logging.LogErrorf("load auth page failed: %s", err) @@ -363,20 +386,20 @@ func serveCheckAuth(c *gin.Context) { } func serveAssets(ginServer *gin.Engine) { - ginServer.POST("/upload", model.CheckAuth, model.Upload) + ginServer.POST("/upload", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, model.Upload) ginServer.GET("/assets/*path", model.CheckAuth, func(context *gin.Context) { requestPath := context.Param("path") relativePath := path.Join("assets", requestPath) p, err := model.GetAssetAbsPath(relativePath) if nil != err { - context.Status(404) + context.Status(http.StatusNotFound) return } http.ServeFile(context.Writer, context.Request, p) return }) - ginServer.GET("/history/*path", model.CheckAuth, func(context *gin.Context) { + ginServer.GET("/history/*path", model.CheckAuth, model.CheckAdminRole, func(context *gin.Context) { p := filepath.Join(util.HistoryDir, context.Param("path")) http.ServeFile(context.Writer, context.Request, p) return @@ -384,7 +407,7 @@ func serveAssets(ginServer *gin.Engine) { } func serveRepoDiff(ginServer *gin.Engine) { - ginServer.GET("/repo/diff/*path", model.CheckAuth, func(context *gin.Context) { + ginServer.GET("/repo/diff/*path", model.CheckAuth, model.CheckAdminRole, func(context *gin.Context) { requestPath := context.Param("path") p := filepath.Join(util.TempDir, "repo", "diff", requestPath) http.ServeFile(context.Writer, context.Request, p) @@ -451,6 +474,17 @@ func serveWebSocket(ginServer *gin.Engine) { } } + // REF: https://github.com/siyuan-note/siyuan/issues/11364 + if !authOk { + if token := model.ParseXAuthToken(s.Request); token != nil { + authOk = token.Valid && model.IsValidRole(model.GetClaimRole(model.GetTokenClaims(token)), []model.Role{ + model.RoleAdministrator, + model.RoleEditor, + model.RoleReader, + }) + } + } + if !authOk { // 用于授权页保持连接,避免非常驻内存内核自动退出 https://github.com/siyuan-note/insider/issues/1099 authOk = strings.Contains(s.Request.RequestURI, "/ws?app=siyuan&id=auth") @@ -516,12 +550,24 @@ func serveWebSocket(ginServer *gin.Engine) { s.Write(result.Bytes()) return } - if util.ReadOnly && !command.IsRead() { - result := util.NewResult() - result.Code = -1 - result.Msg = model.Conf.Language(34) - s.Write(result.Bytes()) - return + if !command.IsRead() { + readonly := util.ReadOnly + if !readonly { + if token := model.ParseXAuthToken(s.Request); token != nil { + readonly = token.Valid && model.IsValidRole(model.GetClaimRole(model.GetTokenClaims(token)), []model.Role{ + model.RoleReader, + model.RoleVisitor, + }) + } + } + + if readonly { + result := util.NewResult() + result.Code = -1 + result.Msg = model.Conf.Language(34) + s.Write(result.Bytes()) + return + } } end := time.Now() @@ -564,3 +610,30 @@ func corsMiddleware() gin.HandlerFunc { c.Next() } } + +// jwtMiddleware is a middleware to check jwt token +// REF: https://github.com/siyuan-note/siyuan/issues/11364 +func jwtMiddleware(c *gin.Context) { + if token := model.ParseXAuthToken(c.Request); token != nil { + // c.Request.Header.Del(model.XAuthTokenKey) + if token.Valid { + claims := model.GetTokenClaims(token) + c.Set(model.ClaimsContextKey, claims) + c.Set(model.RoleContextKey, model.GetClaimRole(claims)) + c.Next() + return + } + } + c.Set(model.RoleContextKey, model.RoleVisitor) + c.Next() + return +} + +func serveFixedStaticFiles(ginServer *gin.Engine) { + ginServer.StaticFile("favicon.ico", filepath.Join(util.WorkingDir, "stage", "icon.png")) + + ginServer.StaticFile("manifest.json", filepath.Join(util.WorkingDir, "stage", "manifest.webmanifest")) + ginServer.StaticFile("manifest.webmanifest", filepath.Join(util.WorkingDir, "stage", "manifest.webmanifest")) + + ginServer.StaticFile("service-worker.js", filepath.Join(util.WorkingDir, "stage", "service-worker.js")) +} diff --git a/kernel/sql/asset.go b/kernel/sql/asset.go index 66d619d55f7..049be5d2937 100644 --- a/kernel/sql/asset.go +++ b/kernel/sql/asset.go @@ -18,10 +18,11 @@ package sql import ( "database/sql" - "github.com/siyuan-note/filelock" "path/filepath" "strings" + "github.com/siyuan-note/filelock" + "github.com/88250/lute/ast" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/treenode" diff --git a/kernel/util/net.go b/kernel/util/net.go index 450b227dc50..1b51b936c99 100644 --- a/kernel/util/net.go +++ b/kernel/util/net.go @@ -20,6 +20,7 @@ import ( "net" "net/http" "net/url" + "strconv" "strings" "time" @@ -90,6 +91,19 @@ func IsOnline(checkURL string, skipTlsVerify bool) bool { return false } +func IsPortOpen(port string) bool { + timeout := time.Second + conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", port), timeout) + if nil != err { + return false + } + if nil != conn { + conn.Close() + return true + } + return false +} + func isOnline(checkURL string, skipTlsVerify bool) (ret bool) { c := req.C().SetTimeout(3 * time.Second) if skipTlsVerify { @@ -168,3 +182,12 @@ func initHttpClient() { http.DefaultClient = httpclient.GetCloudFileClient2Min() http.DefaultTransport = httpclient.NewTransport(false) } + +func ParsePort(portString string) (uint16, error) { + if port, err := strconv.ParseUint(portString, 10, 16); err != nil { + logging.LogErrorf("parse port [%s] failed: %s", portString, err) + return 0, err + } else { + return uint16(port), nil + } +} diff --git a/kernel/util/path.go b/kernel/util/path.go index 3561c8f1af0..6d0848f84d0 100644 --- a/kernel/util/path.go +++ b/kernel/util/path.go @@ -269,6 +269,7 @@ func IsDisplayableAsset(p string) bool { func GetAbsPathInWorkspace(relPath string) (string, error) { absPath := filepath.Join(WorkspaceDir, relPath) + absPath = filepath.Clean(absPath) if WorkspaceDir == absPath { return absPath, nil } diff --git a/kernel/util/working.go b/kernel/util/working.go index 14f0e5114e6..54f15a0b47b 100644 --- a/kernel/util/working.go +++ b/kernel/util/working.go @@ -22,6 +22,7 @@ import ( "fmt" "math/rand" "mime" + "net/url" "os" "path/filepath" "runtime" @@ -341,7 +342,9 @@ func WriteWorkspacePaths(workspacePaths []string) (err error) { } var ( - ServerPort = "0" // HTTP/WebSocket 端口,0 为使用随机端口 + ServerURL *url.URL // 内核服务 URL + ServerPort = "0" // HTTP/WebSocket 端口,0 为使用随机端口 + ReadOnly bool AccessAuthCode string Lang = ""