mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 21:19:13 +00:00
332 lines
24 KiB
HTML
332 lines
24 KiB
HTML
![]() |
<!DOCTYPE HTML>
|
|||
|
<html lang="en" class="sidebar-visible no-js light">
|
|||
|
<head>
|
|||
|
<!-- Book generated using mdBook -->
|
|||
|
<meta charset="UTF-8">
|
|||
|
<title>Webhooks - Matrix Hookshot</title>
|
|||
|
|
|||
|
|
|||
|
<!-- Custom HTML head -->
|
|||
|
|
|||
|
|
|||
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|||
|
<meta name="description" content="">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|||
|
<meta name="theme-color" content="#ffffff" />
|
|||
|
|
|||
|
<link rel="icon" href="../favicon.svg">
|
|||
|
<link rel="shortcut icon" href="../favicon.png">
|
|||
|
<link rel="stylesheet" href="../css/variables.css">
|
|||
|
<link rel="stylesheet" href="../css/general.css">
|
|||
|
<link rel="stylesheet" href="../css/chrome.css">
|
|||
|
<link rel="stylesheet" href="../css/print.css" media="print">
|
|||
|
|
|||
|
<!-- Fonts -->
|
|||
|
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
|||
|
<link rel="stylesheet" href="../fonts/fonts.css">
|
|||
|
|
|||
|
<!-- Highlight.js Stylesheets -->
|
|||
|
<link rel="stylesheet" href="../highlight.css">
|
|||
|
<link rel="stylesheet" href="../tomorrow-night.css">
|
|||
|
<link rel="stylesheet" href="../ayu-highlight.css">
|
|||
|
|
|||
|
<!-- Custom theme stylesheets -->
|
|||
|
<link rel="stylesheet" href="../docs/_site/style.css">
|
|||
|
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<!-- Provide site root to javascript -->
|
|||
|
<script type="text/javascript">
|
|||
|
var path_to_root = "../";
|
|||
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|||
|
<script type="text/javascript">
|
|||
|
try {
|
|||
|
var theme = localStorage.getItem('mdbook-theme');
|
|||
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|||
|
|
|||
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|||
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|||
|
}
|
|||
|
|
|||
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|||
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|||
|
}
|
|||
|
} catch (e) { }
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|||
|
<script type="text/javascript">
|
|||
|
var theme;
|
|||
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|||
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|||
|
var html = document.querySelector('html');
|
|||
|
html.classList.remove('no-js')
|
|||
|
html.classList.remove('light')
|
|||
|
html.classList.add(theme);
|
|||
|
html.classList.add('js');
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|||
|
<script type="text/javascript">
|
|||
|
var html = document.querySelector('html');
|
|||
|
var sidebar = 'hidden';
|
|||
|
if (document.body.clientWidth >= 1080) {
|
|||
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|||
|
sidebar = sidebar || 'visible';
|
|||
|
}
|
|||
|
html.classList.remove('sidebar-visible');
|
|||
|
html.classList.add("sidebar-" + sidebar);
|
|||
|
</script>
|
|||
|
|
|||
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|||
|
<div class="sidebar-scrollbox">
|
|||
|
<ol class="chapter"><li class="chapter-item expanded "><a href="../hookshot.html"><strong aria-hidden="true">1.</strong> ℹ️ Hookshot</a></li><li class="chapter-item expanded "><a href="../setup.html"><strong aria-hidden="true">2.</strong> ⚙️ Setup</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../setup/sample-configuration.html"><strong aria-hidden="true">2.1.</strong> 📃 Sample Configuration</a></li><li class="chapter-item expanded "><a href="../setup/feeds.html"><strong aria-hidden="true">2.2.</strong> Feeds</a></li><li class="chapter-item expanded "><a href="../setup/figma.html"><strong aria-hidden="true">2.3.</strong> Figma</a></li><li class="chapter-item expanded "><a href="../setup/github.html"><strong aria-hidden="true">2.4.</strong> GitHub</a></li><li class="chapter-item expanded "><a href="../setup/gitlab.html"><strong aria-hidden="true">2.5.</strong> GitLab</a></li><li class="chapter-item expanded "><a href="../setup/jira.html"><strong aria-hidden="true">2.6.</strong> JIRA</a></li><li class="chapter-item expanded "><a href="../setup/webhooks.html" class="active"><strong aria-hidden="true">2.7.</strong> Webhooks</a></li></ol></li><li class="chapter-item expanded "><a href="../usage.html"><strong aria-hidden="true">3.</strong> 👤 Usage</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../usage/dynamic_rooms.html"><strong aria-hidden="true">3.1.</strong> Dynamic Rooms</a></li><li class="chapter-item expanded "><a href="../usage/auth.html"><strong aria-hidden="true">3.2.</strong> Authenticating</a></li><li class="chapter-item expanded "><a href="../usage/room_configuration.html"><strong aria-hidden="true">3.3.</strong> Room Configuration</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="../usage/room_configuration/github_repo.html"><strong aria-hidden="true">3.3.1.</strong> GitHub Repo</a></li><li class="chapter-item expanded "><a href="../usage/room_configuration/gitlab_project.html"><strong aria-hidden="true">3.3.2.</strong> GitLab Project</a></li><li class="chapter-item expanded "><a href="../usage/room_configuration/jira_project.html"><strong aria-hidden="true">3.3.3.</strong> JIRA Project</a></li></ol></li></ol></li><li class="chapter-item expanded "><a href="../metrics.html"><strong aria-hidden="true">4.</strong> 📊 Metrics</a></li><li class="chapter-item expanded "><a href="../sentry.html"><strong aria-hidden="true">5.</strong> Sentry</a></li><li class="chapter-item expanded affix "><li class="part-title">🧑💻 Development</li><li class="chapter-item expanded "><a href="../contributing.html"><strong aria-hidden="true">6.</strong> Contributing</a></li><li class="chapter-item expanded affix "><li class="part-title">🥼 Advanced</li><li class="chapter-item expanded "><a href="../advanced/provisioning.html"><strong aria-hidden="true">7.</strong> Provisioning</a></li><li class="chapter-item expanded "><a href="../advanced/workers.html"><strong aria-hidden="true">8.</strong> Workers</a></li><li class="chapter-item expanded "><a href="../advanced/encryption.html"><strong aria-hidden="true">9.</strong> 🔒 Encryption</a></li><li class="chapter-item expanded "><a href="../advanced/widgets.html"><strong aria-hidden="true">10.</strong> 🪀 Widgets</a></li><li class="chapter-item expanded "><a href="../advanced/service_bots.html"><strong aria-hidden="true">11.</strong> Service Bots</a></li></ol> </div>
|
|||
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
|||
|
</nav>
|
|||
|
|
|||
|
<div id="page-wrapper" class="page-wrapper">
|
|||
|
|
|||
|
<div class="page">
|
|||
|
|
|||
|
<div id="menu-bar-hover-placeholder"></div>
|
|||
|
<div id="menu-bar" class="menu-bar sticky bordered">
|
|||
|
<div class="left-buttons">
|
|||
|
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|||
|
<i class="fa fa-bars"></i>
|
|||
|
</button>
|
|||
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|||
|
<i class="fa fa-paint-brush"></i>
|
|||
|
</button>
|
|||
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|||
|
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
|||
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|||
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|||
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|||
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|||
|
</ul>
|
|||
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|||
|
<i class="fa fa-search"></i>
|
|||
|
</button>
|
|||
|
</div>
|
|||
|
|
|||
|
<h1 class="menu-title">Matrix Hookshot</h1>
|
|||
|
|
|||
|
<div class="right-buttons">
|
|||
|
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
|||
|
<i id="print-button" class="fa fa-print"></i>
|
|||
|
</a>
|
|||
|
<a href="https://github.com/matrix-org/matrix-hookshot" title="Git repository" aria-label="Git repository">
|
|||
|
<i id="git-repository-button" class="fa fa-github"></i>
|
|||
|
</a>
|
|||
|
<a href="https://github.com/matrix-org/matrix-hookshot/edit/main/docs/setup/webhooks.md" title="Suggest an edit" aria-label="Suggest an edit">
|
|||
|
<i id="git-edit-button" class="fa fa-edit"></i>
|
|||
|
</a>
|
|||
|
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<div id="search-wrapper" class="hidden">
|
|||
|
<form id="searchbar-outer" class="searchbar-outer">
|
|||
|
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|||
|
</form>
|
|||
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|||
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|||
|
<ul id="searchresults">
|
|||
|
</ul>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|||
|
<script type="text/javascript">
|
|||
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|||
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|||
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|||
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|||
|
});
|
|||
|
</script>
|
|||
|
|
|||
|
<div id="content" class="content">
|
|||
|
<main>
|
|||
|
<h1 id="webhooks"><a class="header" href="#webhooks">Webhooks</a></h1>
|
|||
|
<p>Hookshot supports generic webhook support so that services can send messages into Matrix rooms without being aware of the Matrix protocol. This works
|
|||
|
by having services hit a unique URL that then transforms a HTTP payload into a Matrix message.</p>
|
|||
|
<h2 id="configuration"><a class="header" href="#configuration">Configuration</a></h2>
|
|||
|
<p>You will need to add the following configuration to the config file.</p>
|
|||
|
<pre><code class="language-yaml">generic:
|
|||
|
enabled: true
|
|||
|
urlPrefix: https://example.com/mywebhookspath/
|
|||
|
allowJsTransformationFunctions: false
|
|||
|
waitForComplete: false
|
|||
|
enableHttpGet: false
|
|||
|
# userIdPrefix: webhook_
|
|||
|
</code></pre>
|
|||
|
<section class="notice">
|
|||
|
Previous versions of the bridge listened for requests on `/` rather than `/webhook`. While this behaviour will continue to work,
|
|||
|
administators are advised to use `/webhook`.
|
|||
|
</section>
|
|||
|
<p>The webhooks listener listens on the path <code>/webhook</code>.</p>
|
|||
|
<p>The bridge listens for incoming webhooks requests on the host and port provided in the <a href="../setup.html#listeners-configuration"><code>listeners</code> config</a>.</p>
|
|||
|
<p><code>urlPrefix</code> describes the public facing URL of your webhook handler. For instance, if your load balancer redirected
|
|||
|
webhook requests from <code>https://example.com/mywebhookspath</code> to the bridge (on <code>/webhook</code>), an example webhook URL would look like:
|
|||
|
<code>https://example.com/mywebhookspath/abcdef</code>.</p>
|
|||
|
<p><code>waitForComplete</code> causes the bridge to wait until the webhook is processed before sending a response. Some services prefer you always
|
|||
|
respond with a 200 as soon as the webhook has entered processing (<code>false</code>) while others prefer to know if the resulting Matrix message
|
|||
|
has been sent (<code>true</code>). By default this is <code>false</code>.</p>
|
|||
|
<p><code>enableHttpGet</code> means that webhooks can be triggered by <code>GET</code> requests, in addition to <code>POST</code> and <code>PUT</code>. This was previously on by default,
|
|||
|
but is now disabled due to concerns mentioned below.</p>
|
|||
|
<p>You may set a <code>userIdPrefix</code> to create a specific user for each new webhook connection in a room. For example, a connection with a name
|
|||
|
like <code>example</code> for a prefix of <code>webhook_</code> will create a user called <code>@webhook_example:example.com</code>. If you enable this option,
|
|||
|
you need to configure the user to be part of your registration file e.g.:</p>
|
|||
|
<pre><code class="language-yaml"># registration.yaml
|
|||
|
...
|
|||
|
namespaces:
|
|||
|
users:
|
|||
|
- regex: "@webhook_.+:example.com" # Where example.com is your domain name.
|
|||
|
exclusive: true
|
|||
|
</code></pre>
|
|||
|
<h2 id="adding-a-webhook"><a class="header" href="#adding-a-webhook">Adding a webhook</a></h2>
|
|||
|
<p>To add a webhook to your room:</p>
|
|||
|
<ul>
|
|||
|
<li>Invite the bot user to the room.</li>
|
|||
|
<li>Make sure the bot able to send state events (usually the Moderator power level in clients)</li>
|
|||
|
<li>Say <code>!hookshot webhook example</code> where <code>example</code> is a name for your hook.</li>
|
|||
|
<li>The bot will respond with the webhook URL to be sent to services.</li>
|
|||
|
</ul>
|
|||
|
<h2 id="webhook-handling"><a class="header" href="#webhook-handling">Webhook Handling</a></h2>
|
|||
|
<p>Hookshot handles <code>POST</code> and <code>PUT</code> HTTP requests by default.</p>
|
|||
|
<p>Hookshot handles HTTP requests with a method of <code>GET</code>, <code>POST</code> or <code>PUT</code>.</p>
|
|||
|
<p>If the request is a <code>GET</code> request, the query parameters are assumed to be the body. Otherwise, the body of the request should be a supported payload.</p>
|
|||
|
<p>If the body contains a <code>text</code> key, then that key will be used as a message body in Matrix (aka <code>body</code>). This text will be automatically converted from Markdown to HTML (unless
|
|||
|
a <code>html</code> key is provided.).</p>
|
|||
|
<p>If the body contains a <code>html</code> key, then that key will be used as the HTML message body in Matrix (aka <code>formatted_body</code>). A <code>text</code> key fallback MUST still be provided.</p>
|
|||
|
<p>If the body <em>also</em> contains a <code>username</code> key, then the message will be prepended by the given username. This will be prepended to both <code>text</code> and <code>html</code>.</p>
|
|||
|
<p>If the body does NOT contain a <code>text</code> field, the full payload will be sent to the room. This can be adapted into a message by creating a <strong>JavaScript transformation function</strong>.</p>
|
|||
|
<h3 id="payload-formats"><a class="header" href="#payload-formats">Payload formats</a></h3>
|
|||
|
<p>If the request is a <code>POST</code>/<code>PUT</code>, the body of the request will be decoded and stored inside the event. Currently, Hookshot supports:</p>
|
|||
|
<ul>
|
|||
|
<li>XML, when the <code>Content-Type</code> header ends in <code>/xml</code> or <code>+xml</code>.</li>
|
|||
|
<li>Web form data, when the <code>Content-Type</code> header is <code>application/x-www-form-urlencoded</code>.</li>
|
|||
|
<li>JSON, when the <code>Content-Type</code> header is <code>application/json</code>.</li>
|
|||
|
<li>Text, when the <code>Content-Type</code> header begins with <code>text/</code>.</li>
|
|||
|
</ul>
|
|||
|
<p>Decoding is done in the order given above. E.g. <code>text/xml</code> would be parsed as XML. Any formats not described above are not
|
|||
|
decoded.</p>
|
|||
|
<h3 id="get-requests"><a class="header" href="#get-requests">GET requests</a></h3>
|
|||
|
<p>In previous versions of hookshot, it would also handle the <code>GET</code> HTTP method. This was disabled due to concerns that it was too easy for the webhook to be
|
|||
|
inadvertently triggered by URL preview features in clients and servers. If you still need this functionality, you can enable it in the config.</p>
|
|||
|
<p>Hookshot will insert the full content of the body into a key under the Matrix event called <code>uk.half-shot.hookshot.webhook_data</code>, which may be useful if you have
|
|||
|
other integrations that would like to make use of the raw request body.</p>
|
|||
|
<section class="notice">
|
|||
|
Matrix does NOT support floating point values in JSON, so the <code>uk.half-shot.hookshot.webhook_data</code> field will automatically convert any float values
|
|||
|
to a string representation of that value. This change is <strong>not applied</strong> to the JavaScript transformation <code>data</code>
|
|||
|
variable, so it will contain proper float values.
|
|||
|
</section>
|
|||
|
<h2 id="javascript-transformations"><a class="header" href="#javascript-transformations">JavaScript Transformations</a></h2>
|
|||
|
<section class="notice">
|
|||
|
Although every effort has been made to securely sandbox scripts, running untrusted code from users is always risky. Ensure safe permissions
|
|||
|
in your room to prevent users from tampering with the script.
|
|||
|
</section>
|
|||
|
<p>This bridge supports creating small JavaScript snippets to translate an incoming webhook payload into a message for the room, giving
|
|||
|
you a very powerful ability to generate messages based on whatever input is coming in.</p>
|
|||
|
<p>The input is parsed and executed within a separate JavaScript Virtual Machine context, and is limited to an execution time of 2 seconds.
|
|||
|
With that said, the feature is disabled by default and <code>allowJsTransformationFunctions</code> must be enabled in the config.</p>
|
|||
|
<p>The code snippets can be edited by editing the Matrix state event corresponding to this connection (with a state type of <code>uk.half-shot.matrix-hookshot.generic.hook</code>).
|
|||
|
Because this is a fairly advanced feature, this documentation won't go into how to edit state events from your client.
|
|||
|
Please seek out documentation from your client on how to achieve this.</p>
|
|||
|
<p>The script string should be set within the state event under the <code>transformationFunction</code> key.</p>
|
|||
|
<h3 id="script-api"><a class="header" href="#script-api">Script API</a></h3>
|
|||
|
<p>Transformation scripts have a versioned API. You can check the version of the API that the hookshot instance supports
|
|||
|
at runtime by checking the <code>HookshotApiVersion</code> variable. If the variable is undefined, it should be considered <code>v1</code>.</p>
|
|||
|
<p>The execution environment will contain a <code>data</code> variable, which will be the body of the incoming request (see <a href="#payload-formats">Payload formats</a>).
|
|||
|
Scripts are executed synchronously and expect the <code>result</code> variable to be set.</p>
|
|||
|
<p>If the script contains errors or is otherwise unable to work, the bridge will send an error to the room. You can check the logs of the bridge
|
|||
|
for a more precise error.</p>
|
|||
|
<h3 id="v2-api"><a class="header" href="#v2-api">V2 API</a></h3>
|
|||
|
<p>The <code>v2</code> api expects an object to be returned from the <code>result</code> variable.</p>
|
|||
|
<pre><code class="language-json5">{
|
|||
|
"version": "v2" // The version of the schema being returned from the function. This is always "v2".
|
|||
|
"empty": true|false, // Should the webhook be ignored and no output returned. The default is false (plain must be provided).
|
|||
|
"plain": "Some text", // The plaintext value to be used for the Matrix message.
|
|||
|
"html": "<b>Some</b> text", // The HTML value to be used for the Matrix message. If not provided, plain will be interpreted as markdown.
|
|||
|
"msgtype": "some.type", // The message type, such as m.notice or m.text, to be used for the Matrix message. If not provided, m.notice will be used.
|
|||
|
}
|
|||
|
</code></pre>
|
|||
|
<h4 id="example-script"><a class="header" href="#example-script">Example script</a></h4>
|
|||
|
<p>Where <code>data</code> = <code>{"counter": 5, "maxValue": 4}</code></p>
|
|||
|
<pre><code class="language-js">if (data.counter === undefined) {
|
|||
|
// The API didn't give us a counter, send no message.
|
|||
|
result = {empty: true, version: "v2"};
|
|||
|
} else if (data.counter > data.maxValue) {
|
|||
|
result = {plain: `**Oh no!** The counter has gone over by ${data.counter - data.maxValue}`, version: "v2"};
|
|||
|
} else {
|
|||
|
result = {plain: `*Everything is fine*, the counter is under by ${data.maxValue - data.counter}`, version: "v2"};
|
|||
|
}
|
|||
|
</code></pre>
|
|||
|
<h3 id="v1-api"><a class="header" href="#v1-api">V1 API</a></h3>
|
|||
|
<p>The v1 API expects <code>result</code> to be a string. The string will be automatically interpreted as Markdown and transformed into HTML. All webhook messages
|
|||
|
will be prefixed with <code>Received webhook:</code>. If <code>result</code> is falsey (undefined, false or null) then the message will be <code>No content</code>.</p>
|
|||
|
<h4 id="example-script-1"><a class="header" href="#example-script-1">Example script</a></h4>
|
|||
|
<p>Where <code>data</code> = <code>{"counter": 5, "maxValue": 4}</code></p>
|
|||
|
<pre><code class="language-js">if (data.counter > data.maxValue) {
|
|||
|
result = `**Oh no!** The counter has gone over by ${data.counter - data.maxValue}`
|
|||
|
} else {
|
|||
|
result = `*Everything is fine*, the counter is under by ${data.maxValue - data.counter}`
|
|||
|
}
|
|||
|
</code></pre>
|
|||
|
|
|||
|
</main>
|
|||
|
|
|||
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|||
|
<!-- Mobile navigation buttons -->
|
|||
|
<a rel="prev" href="../setup/jira.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|||
|
<i class="fa fa-angle-left"></i>
|
|||
|
</a>
|
|||
|
|
|||
|
<a rel="next" href="../usage.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|||
|
<i class="fa fa-angle-right"></i>
|
|||
|
</a>
|
|||
|
|
|||
|
<div style="clear: both"></div>
|
|||
|
</nav>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|||
|
<a rel="prev" href="../setup/jira.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|||
|
<i class="fa fa-angle-left"></i>
|
|||
|
</a>
|
|||
|
|
|||
|
<a rel="next" href="../usage.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|||
|
<i class="fa fa-angle-right"></i>
|
|||
|
</a>
|
|||
|
</nav>
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<script type="text/javascript">
|
|||
|
window.playground_copyable = true;
|
|||
|
</script>
|
|||
|
|
|||
|
|
|||
|
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
|
|||
|
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
|
|||
|
|
|||
|
<!-- Custom JS scripts -->
|
|||
|
<script type="text/javascript" src="../docs/_site/main.js"></script>
|
|||
|
<script type="text/javascript" src="../docs/_site/version.js"></script>
|
|||
|
|
|||
|
|
|||
|
</body>
|
|||
|
</html>
|