Everton Pedrolo
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Integração de metadados CRM - SGDP via Totvs Link ## Passos necessários para a integração ### 1. Registrar a aplicação no Totvs RAC Neste passo o Roger solicitou a criação do produto "TOTVS CRM" no RAC para os ambientes Em breve teremos a possibiliadde de registrar novos tenants dinamicamente para que possamos automitizar completamnete o processo, ou seja, ao cadastrarmos um novo cliente e criar um tenant para o mesmo no cloud manager, receberemos por mensageira a lista dos tenants disponíveis, iremos comparar com os que existem previamente no serviço data protection e atualizar os mesmos, é importante registrar cada novo tenant no RAC para que possamos receber as mensagens para este tenant. ### 2. Criar o tenant do CRM no SGDP Foi criado o tenant para o desenvolvimento chamado "totvscrm" e cadastrado os usuários com o perfil de acesso DPO. Para o acesso às telas usamos o endereço https://totvscrm.sgdp.dev.totvs.app/ Para acessar o RAC desse tenant, pode ser utilizado o endereço: https://totvscrm.rac.dev.totvs.app/ ### 3. Associar o tenant ao produto no SGDP Foi associado o tenant "totvscrm" ao produto "TOTVS CRM" no SGDP na instância admin; ### 4. Importar a lib do Totvs Client no data-protection Utilizar as informações do Totvs Link para configuração do Totvs Client no serviço: https://arquitetura.totvs.io/solution-books/TOTVSApps/services/support-elements/LINK/ ### 5. Realizar a configuração do arquivo yaml de propriedades Segue um exemplo do arquivo de configuração usado pelo TOTVS CRM: ``` # TOTVS LINK Config totvs: link: auth: url: https://admin.rac.dev.totvs.io/totvs.rac/connect/token client: totvscrm secret: b517f1b7-2203-441f-a69a-8f613e691738 tenants: - tenant: client: 59f1e4a6ed4c475394e2fd6438254c2d secret: 5f0e57e2bbb446c890de3e2dc9cf61f5 client: subscriptions: - SGDPDataCommand - SGDPMaskCommand server: host: link.dev.totvs.app port: 443 secure: true ``` Nota: Existe a possibilidade de adicionar todos os tenants no arquivo de configuração, mas não vamos optar por essa abordagem, pois seria necessário reiniciar os serviços todas as vezes que tivesse um novo tenant. Está é toda a configuração necessária do lado do client, que será a aplicação Java que consumirá as mensagens e enviará para o Link Service. Deve ser informado no arquivo de configuração yaml os atributos abaixo #### Autenticação do `Link client` do CRM no `Link service` Para a autenticação, deve ser enviado os dados dentro da tag *auth*: - url: endereço de autenticação com o Totvs RAC - Exemplo: https://admin.rac.dev.totvs.io/totvs.rac/connect/token (*RAC de dev*) - client: descrição da chave de acesso - Exemplo: totvscrm - secret: senha da chave de acesso - Exemplo: b517f1b7-2203-441f-a69a-8f613e691738; Sendo essa chave (client e secret) gerada no Totvs RAC, pelos seguintes passos: - acessar o menu "Clientes OAuth" e encontrar o serviço/produto do `LINK service`, conforme imagem abaixo: ![](https://i.imgur.com/9SYopP2.png) - editar o `LINK service` criando uma nova chave de acesso para ser utilizada no `Link client do CRM`, conforme imagem abaixo: ![](https://i.imgur.com/a4ZgXNU.png) > Acredito que não vamos ter acesso a fazer essa criação da chave de acesso no Totvs RAC, ficando como responsabilidade do time do SGDP, já que esse processo deve ser executado uma única vez. Sendo nossa responsabilidade somente cadastrar corretamente no arquivo .yaml #### Relação de tenants > Informar o client e secret de todos os tenants?? > - client: tenant_id ?? > - secret: senha para o tenant ?? **Manualmente** essa chave (client e secret) do tenant é gerada no RAC do tenant, somente para usuários *admin*, que é diferente do Totvs RAC, ou seja, cada tenant tem o seu RAC próprio, onde também deve ser gerada a chave de acesso para ser utilizada pelo `Link client` do CRM, seguindo os passos: - acessar o menu "Cadastro de Clientes OAuth", conforme imagem abaixo: ![](https://i.imgur.com/pMixR8e.png) - incluir um novo registro para o produto "TOTVS CRM", conforme imagem abaixo: ![](https://i.imgur.com/KtFa1Hh.png) - clicar em `Gerar` para gerar a senha secreta (**secret**), que deve ser copiada, pois depois não ficará mais visivél em tela; - já o **client** (chamado de ClientId) e o tenant_id (identificado como Id do Tenant), só ficam visíveis com a edição do registro, conforme imagem abaixo: ![](https://i.imgur.com/KsQCQkM.png) > Lembrando que o tenant_id será necessário para o envio e consumo das mensagens, já que ele faz parte do *header* da mensagem >*Ponto ANA*: Não tenho certeza aonde o tenant é criado, mas acredito que seja no Totvs RAC, conforme percepção no exemplo do Roger abaixo, onde tem um menu: Cadastro de Tenants ![](https://i.imgur.com/XjUpjNP.png) Se realmente for, para fazermos uma inseção automática do tenant, estou achando que vamos ter dois processos: um para criar o tenant no Totvs RAC e outro para criar a chave de acesso no RAC do tenant... só se eles tiverem um endpoint lá que já faça tudo isso kkk #### Subscrição mensageria Informar qual fila da mensageria deverá estar subscrito, dentro da tag *subscriptions*: - SGDPDataCommand: Fila para consulta de dados anonimizados? - SGDPMaskCommand: Fila para mensagens de anonimização? > **Existem filas de retorno?** > - SGDPDataResponse? > - SGDPMaskResponse? #### Acesso Totvs Link Deve ser informado os dados de acesso ao servidor do Totvs Link, especificado dentro da tag *server*: - host: URL do servidor do Totvs Link - Exemplo: link.dev.totvs.app - port: Porta do servidor do Totvs Link - Exemplo: 443 - secure: Indica se a comunicação será feita de forma segura - Exemplo: true ### 6. Criação do modelo para o tenant? Existem duas possibilidades de informar para o servidor os tenants que queremos consumir. Atualmente para cadastrar um novo tenant é necessário solicitar para a equipe do Link Service, após isso feito, podemos definir os tenants que iremos filtrar de duas formas: 1. A primeira opção é utilizando no arquivo de configuração, citado mais acima: ``` auth: url: https://admin.rac.dev.totvs.io/totvs.rac/connect/token client: totvscrm secret: b517f1b7-2203-441f-a69a-8f613e691738 tenants: - tenant: client: 59f1e4a6ed4c475394e2fd6438254c2d secret: 5f0e57e2bbb446c890de3e2dc9cf61f5 ``` 2. A segunda opção está em desenvolvimento, Foi solicitado para que possamos cadastrar tenants dinamicamente. Onde precisamos cadastrar nossos tenants por meio de REST ou mensageria, recebemos um client e uma secret para o mesmo. Sempre que temos um novo cliente, recebemos essa informação no data protection por mensageria e cadastramos esse novo tenant. Para tenants que não estão sendo mais utilizados, por exclusão por exemplo, fazemos uma verificação no serviço para que possamos excluir estes tenants do Link Service. Como não teremos nada preenchido no tenants, iremos consumir todas as mensagens, além de cadastrar e excluir tenants dinamicamente. ### 7. Qual a diferença entre SGDP...Tenant... e SGDP...Model...? Quando é utilizado o Model no meio do tópico significa que é algo padrão que servirá para todos os tenants, entretanto, quanto o Tenant é utilizado é algo personalizado para cada tenant. Aparentemente utilizaremos apenas os tópicos que contém o Tenant no meio, já que não mesmo as entidades padrões podem ser personalizadas e adicionadas novos campos pelos clientes. É importante lembrar que um modelo padrão, ou seja, os models application, não são alterados por metadados específicos. Ou seja, um modelo application, não será deletado por um DeleteTenant por exemplo. ### 8. Como cadastrar um novo produto e aplicação? Basta enviar uma mensagem para o seguinte tópico: SGDPUpdateApplicationMetadata. Importante lembrar que o applicationId e também o productId são apenas agrupadores. Quando estamos utilizamos o application e o product default, não conseguimos realizar todas as operações. ``` { "header": { "type": "SGDPUpdateApplicationMetadata" }, "content": { "applicationId": "sgdp-sample-aggregate-dev", "productId": "sgdp-sample", } } ``` Nota: o dado só será mostrado na tela do Totvs Link se o mesmo estiver configurado para ser mostrado, no seguinte link: https://totvscrm.sgdp.dev.totvs.app/config ![](https://i.imgur.com/iyf78Gv.png) O produto récem cadastrado deve ser selecionado no campo de multi select produtos. ### 9. Envio do metadado? ``` **Metadados** são os objetos e as informações do mesmo, contendo se o campo pode ser anonimizado e seus tipos. ``` Nota: É importante frisar que esse tópico vai atualizar todos os metadados já adicionados, isso significa que é necessário enviar uma lista com todos os metadados pertinentes aquele tenant, caso contrário, os metadados que estavam previamente gravados, serão excluidos e restará ou serão adicionados apenas os que estão sendo enviados nessa mensagem. Entidade para salvar ou atualizar todos os metadados de uma vez: ``` { "header": { "type": "SGDPUpdateApplicationMetadata" }, "content": { "applicationId": "sgdp-sample-aggregate-dev", "productId": "sgdp-sample", "models": { "br.com.sgdp.sample.model.Customer": { "sgdpSupport": true, "description": "Cadastro de Clientes", "attributes": { "gender": { "type": "String", "description": "Gender", "sgdpData": { "sensitive": true, "type": "EMPTY", "allowsAnonymization": false, "identification": false } } } } } } } ``` ### 10. Como atualizar um metadado em especifico? SGDPUpdateApplicationModel. O tópico SGDPUpdateApplicationMetadata se alterado, irá sobreescrever, os registros anteriores, ou seja, caso foi enviado apenas dois registros e haviam quatro, apenas dois restarão, os outros são serão excluidos automaticamente. Para atualizar um modelo em especifico, basta enviar uma mensagem para o seguitne tópico: Com o seguinte payload: ``` { "header": { "type": "SGDPUpdateApplicationModel" }, "content": { "applicationId": "sgdp-sample-aggregate-dev", "modelId": "br.com.sgdp.sample.model.Customer", "model": { "sgdpSupport": true, "description": "Cadastro de Clientes", "attributes": { "gender": { "type": "Boolean", "description": "Gender", "sgdpData": { "sensitive": true, "type": "EMPTY", "allowsAnonymization": false, "identification": false } }, "name": { "type": "String", "description": "Name", "sgdpData": { "sensitive": true, "type": "EMPTY", "allowsAnonymization": false, "identification": false } } } } } } ``` ### 11. Como excluir uma aplicação? Ao excluir uma aplicação, é importante lembrar que todas as politicas de privacidade e também os metadados previamentes associados a esse aplicação serão perdidos, serão todos excluidos automaticamente. Para excluir uma aplicação basta enviar para o seguinte tópico: SGDPDeleteApplication. Com o seguinte payload: ``` { "header": { "type": "SGDPDeleteApplication" }, "content": { "applicationId": "sgdp-sample-app" } } ``` ### 12. Como excluir um modelo especifico? Para excluir um modelo especifico basta enviar para o seguinte tópico: SGDPDeleteApplicationModel. Com o seguinte payload: ``` { "header": { "type": "SGDPDeleteApplicationModel" }, "content": { "applicationId": "sgdp-sample-app", "modelId": "Jedi" } } ``` ### 13. Como consumir mensagens utilizando o TOTVS LINK? Tudo que se faz necessário é a implementação de apenas uma interface, após de configurado o arquivo yaml propriamente. Segue o exemplo de uma implementação abaixo: ``` @Component public class TotvsLinkMessageHandler implements MessageHandler { private static final Logger LOGGER = LoggerFactory.getLogger(TotvsLinkMessageHandler.class); @Autowired private LinkService service; @Override public void handle(final LinkMessage message) throws Exception { LOGGER.info("Message: " + message.getDeliveryTag() + message.getMessageContent()); service.ackMessage(message.getDeliveryTag(), true); service.sendMessage("SGDPDataResponse", message.getMessageContent(), "SGDP"); } } ``` ### 14. Como atualizar (Inserir ou modificar) um Metadata de um Tenant em especifico? Este funciona de maneira muito parecida com o tópico SGDPUpdateApplicationMetadata. Entretanto, a diferença é que esse é para um tenant em especifico, porém as regras são as mesmas. Para atualizar basta enviar uma mensagem para o seguinte tópico: SGDPUpdateTenantMetadata. Com o seguinte tópico: ``` { "header": { "type": "SGDPUpdateTenantMetadata", "tenantId": "5d097ae0-1720-40f8-9e44-546245d34e6c" }, "content": { "applicationId": "totvs-crm", "productId": "totvscrm", "models": { "br.com.sgdp.sample.model.Test": { "sgdpSupport": true, "description": "Cadastro de Clientes", "attributes": { "test": { "type": "Boolean", "description": "Gender", "sgdpData": { "sensitive": false, "type": "EMPTY", "allowsAnonymization": false, "identification": false } } } } } } } ``` ### 15. Como atualizar (Inserir ou modificar) um modelo de um Tenant em especifico? Este tópico é bem parecido com o tópico: SGDPUpdateApplicationModel A diferença é que ao usar o mesmo, atualizará apenas o modelo no tenant, ou seja, não é algo padronizado. Para fazer isso, basta enviar para o seguinte tópico: SGDPUpdateTenantModel Nota: Pelo que eu entendi, o modelo e a aplicação que serão enviados no payload já devem previamente existir. Com o seguinte payload: ``` { "header": { "type": "SGDPUpdateTenantModel", "tenantId": "5d097ae0-1720-40f8-9e44-546245d34e6c" }, "content": { "applicationId": "totvs-crm", "modelId": "br.com.sgdp.sample.model.Test2", "model": { "sgdpSupport": true, "description": "Cadastro de Clientes", "attributes": { "working": { "type": "Boolean", "description": "Gender", "sgdpData": { "sensitive": false, "type": "EMPTY", "allowsAnonymization": false, "identification": false } } } } } } ``` ### 16. Como atualizar a politica de dados pessoais padrão da aplicação? É importante lembrar que só existe uma politica de dados por aplicação. A política pode ser vista abaixo: ![](https://i.imgur.com/1ZXCvPx.png) Para atualizar a política da aplicação basta enviar uma mensagem com o seguinte tópico: SGDPUpdateApplicationPrivacy E com o seguinte payload: ``` { "header": { "type": "SGDPUpdateApplicationPrivacy" }, "content": { "applicationId": "totvs-crm", "privacyText": "Política de privacidade da aplicação..." } } ``` ### 17. Como excluir um modelo criado a partir de um tenant? Para excluir um modelo específico criado a partir de um tenant e não de um modelo da aplicação, basta apenas enviar uma mensagem para o seguinte tópico: SGDPDeleteTenantModel Com o seguinte payload: ``` { "header": { "type": "SGDPDeleteTenantModel", "tenantId": "5d097ae0-1720-40f8-9e44-546245d34e6c" }, "content": { "applicationId": "totvs-crm", "modelId": "br.com.sgdp.sample.model.Test2" } } ``` ### 18. Como excluir um metadado de um tenant em específico? Nota: É importante frisar que apesar do nome do tópico, não será excluido os modelos, além disso, os modelos que forem padrão da aplicação, também não serão excluídos. Mas para excluir os modelos por tenant de uma vez, por tenant, basta enviar uma mensagme para o seguinte tópico: SGDPDeleteTenantMetadata. Com o seguinte payload: ``` { "header": { "type": "SGDPDeleteTenantMetadata", "tenantId": "5d097ae0-1720-40f8-9e44-546245d34e6c" }, "content": { "applicationId": "teste" } } ``` ### 19. Fluxo de cadastro de metadados no warmup ```mermaid sequenceDiagram WARMUP->>+Cadastro dos metadados no SGDP: Cadastra todos os tenants no RAC e salva os ids gerados no banco WARMUP->>+Cadastro dos metadados no SGDP: Traz do banco todas os tenants, junto com os ids gerados pelo RAC e todos os seus respectivos metadados WARMUP->>+Cadastro dos metadados no SGDP: Envia os metadados formatados para o seguinte tópico: SGDPUpdateApplicationMetadata Note right of WARMUP: É importante enviar no header o tenantId, para a aplicação saber para onde enviar o metadado Note right of WARMUP: A aplicação e o produto são criados ao envio do SGDPUpdateApplicationMetadata ``` ### 20. Informações importantes Por enquanto, ao ser enviado um payload inválido, não temos nenhum tipo de validação, ou seja, ainda que o serviço não consiga processar, não receberemos erro algum. ## Documentação de referência - Portal oficial da documentação SGDP - https://arquitetura.totvs.io/solution-books/TOTVSApps/services/support-elements/SGDP/ - Documentação swagger do ambiente DEV: - https://link.dev.totvs.app/swagger/swagger-ui/index.html?configUrl=/swagger/swagger-config#/ - Exemplo de metadado: - https://sgdp.dev.totvs.app/sgdp-jedi-order/sgdp/metadata

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully