mirror of
https://github.com/esphome/esphome.git
synced 2025-03-21 01:58:16 +00:00
617 lines
21 KiB
HTML
617 lines
21 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>esphomeyaml Dashboard</title>
|
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
|
|
|
|
<link rel="stylesheet" href="/static/materialize-stepper.min.css">
|
|
|
|
<!-- jQuery :( -->
|
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
|
|
<script src="https://code.jquery.com/ui/1.8.5/jquery-ui.min.js" integrity="sha256-fOse6WapxTrUSJOJICXXYwHRJOPa6C1OUQXi7C9Ddy8=" crossorigin="anonymous"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/js/materialize.min.js"></script>
|
|
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.15.0/jquery.validate.min.js"></script>
|
|
|
|
|
|
<script src="/static/materialize-stepper.min.js"></script>
|
|
|
|
<style>
|
|
nav .brand-logo {
|
|
margin-left: 48px;
|
|
font-size: 20px;
|
|
}
|
|
|
|
main .container {
|
|
margin-top: -12vh;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.ribbon {
|
|
width: 100%;
|
|
height: 17vh;
|
|
background-color: #3F51B5;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.ribbon-fab:not(.tap-target-origin) {
|
|
position: absolute;
|
|
right: 24px;
|
|
top: calc(17vh + 34px);
|
|
}
|
|
|
|
i.very-large {
|
|
font-size: 8rem;
|
|
padding-top: 2px;
|
|
color: #424242;
|
|
}
|
|
|
|
.card .card-content {
|
|
padding-left: 18px;
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
.chip {
|
|
height: 26px;
|
|
font-size: 12px;
|
|
line-height: 26px;
|
|
}
|
|
|
|
.log {
|
|
background-color: #1c1c1c;
|
|
margin-top: 0;
|
|
margin-bottom: 0;
|
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
font-size: 12px;
|
|
padding: 16px;
|
|
overflow: auto;
|
|
line-height: 1.45;
|
|
border-radius: 3px;
|
|
word-wrap: normal;
|
|
color: #DDD;
|
|
}
|
|
|
|
.log.bold {
|
|
font-weight: bold;
|
|
}
|
|
|
|
.log .v {
|
|
color: #888888;
|
|
}
|
|
|
|
.log .d {
|
|
color: #00DDDD;
|
|
}
|
|
|
|
.log .c {
|
|
color: magenta;
|
|
}
|
|
|
|
.log .i {
|
|
color: limegreen;
|
|
}
|
|
|
|
.log .w {
|
|
color: yellow;
|
|
}
|
|
|
|
.log .e {
|
|
color: red;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.log .e {
|
|
color: red;
|
|
}
|
|
|
|
.log .ww {
|
|
color: white;
|
|
}
|
|
|
|
.modal {
|
|
width: 90%;
|
|
max-height: 85%;
|
|
}
|
|
|
|
.log-container {
|
|
position: absolute;
|
|
bottom: 0;
|
|
width: calc(100% - 48px);
|
|
}
|
|
|
|
.log {
|
|
position: absolute;
|
|
bottom: 0;
|
|
right: 0;
|
|
left: 0;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.page-footer {
|
|
padding-top: 0;
|
|
}
|
|
|
|
body {
|
|
display: flex;
|
|
min-height: 100vh;
|
|
flex-direction: column;
|
|
}
|
|
|
|
main {
|
|
flex: 1 0 auto;
|
|
}
|
|
|
|
ul.browser-default {
|
|
padding-left: 30px;
|
|
margin-top: 10px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
ul.browser-default li {
|
|
list-style-type: initial;
|
|
}
|
|
|
|
ul.stepper:not(.horizontal) .step.active::before, ul.stepper:not(.horizontal) .step.done::before, ul.stepper.horizontal .step.active .step-title::before, ul.stepper.horizontal .step.done .step-title::before {
|
|
background-color: #3f51b5 !important;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<nav>
|
|
<div class="nav-wrapper indigo">
|
|
<a href="#" class="brand-logo left">esphomeyaml Dashboard</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="ribbon"></div>
|
|
</header>
|
|
|
|
<main>
|
|
<div class="container">
|
|
{% for file in files %}
|
|
<div class="row">
|
|
<div class="col s8 offset-s2 m10 offset-m1 l12">
|
|
<div class="card horizontal">
|
|
<div class="card-image center-align">
|
|
<i class="material-icons very-large icon-grey">memory</i>
|
|
</div>
|
|
<div class="card-stacked">
|
|
<div class="card-content">
|
|
<span class="card-title">{{ file }}</span>
|
|
</div>
|
|
<div class="card-action">
|
|
<a href="#" class="action-upload" data-node="{{ file }}">Upload</a>
|
|
<a href="#" class="action-show-logs" data-node="{{ file }}">Show Logs</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% end %}
|
|
</div>
|
|
|
|
<div id="modal-logs" class="modal modal-fixed-footer">
|
|
<div class="modal-content">
|
|
<h4>Show Logs</h4>
|
|
<div class="log-container">
|
|
<pre class="log"></pre>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<a class="modal-close waves-effect waves-green btn-flat">Close</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="modal-run" class="modal modal-fixed-footer">
|
|
<div class="modal-content">
|
|
<h4>Compile And Upload</h4>
|
|
<div class="upload-port row">
|
|
<div class="input-field col s8">
|
|
<select></select>
|
|
</div>
|
|
<div class="col s4">
|
|
<button class="btn waves-effect waves-light upload-port-submit" type="submit" name="action">Select
|
|
<i class="material-icons right">send</i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="log-container">
|
|
<pre class="log"></pre>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<a class="modal-close waves-effect waves-green btn-flat">Stop</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="modal-wizard" class="modal">
|
|
<div class="modal-content">
|
|
<form action="/wizard.html" method="POST">
|
|
<ul class="stepper linear">
|
|
<li class="step active">
|
|
<div class="step-title waves-effect">Introduction And Name</div>
|
|
<div class="step-content">
|
|
<div class="row">
|
|
<p>
|
|
Hi there! I'm the esphomeyaml setup wizard and will guide you through setting up
|
|
your first ESP8266 or ESP32-powered device using esphomeyaml.
|
|
</p>
|
|
<a href="https://www.espressif.com/en/products/hardware/esp8266ex/overview" target="_blank">ESP8266s</a> and
|
|
their successors (the <a href="https://www.espressif.com/en/products/hardware/esp32/overview" target="_blank">ESP32s</a>)
|
|
are great low-cost microcontrollers that can communicate with the outside world using WiFi.
|
|
They're found in many devices such as the popular Sonoff/iTead, but also exist as development boards
|
|
such as the <a href="http://nodemcu.com/index_en.html" target="_blank">NodeMCU</a>.
|
|
<p>
|
|
</p>
|
|
<a href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml</a>,
|
|
the tool you're using here, creates custom firmwares for these devices using YAML configuration
|
|
files (similar to the ones you might be used to with Home Assistant).
|
|
<p>
|
|
</p>
|
|
This wizard will create a basic YAML configuration file for your "node" (the microcontroller).
|
|
Later, you will be able to customize this file and add some of
|
|
<a href="https://github.com/OttoWinter/esphomelib" target="_blank">esphomelib's</a>
|
|
many integrations.
|
|
<p>
|
|
<p>
|
|
First, I need to know what this node should be called. Choose this name wisely, changing this
|
|
later makes Over-The-Air Update attempts difficult.
|
|
It may only contain the characters <code class="inlinecode">a-z</code>, <code class="inlinecode">A-Z</code>,
|
|
<code class="inlinecode">0-9</code> and <code class="inlinecode">_</code>
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<input id="node_name" class="validate" type="text" name="name" required>
|
|
<label for="node_name">Name of node</label>
|
|
</div>
|
|
</div>
|
|
<div class="step-actions">
|
|
<button class="waves-effect waves-dark btn indigo next-step" id="step-1-continue">CONTINUE</button>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
<li class="step">
|
|
<div class="step-title waves-effect">Device Type</div>
|
|
<div class="step-content">
|
|
<div class="row">
|
|
<p>
|
|
Great! Now I need to know what type of microcontroller you're using so that I can compile firmware for them.
|
|
Please choose either ESP32 or ESP8266 (use ESP8266 for Sonoff devices).
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<select id="esp_type" name="platform" required>
|
|
<option value="ESP8266">ESP8266</option>
|
|
<option value="ESP32">ESP32</option>
|
|
</select>
|
|
<label>Microcontroller Type</label>
|
|
</div>
|
|
<p>
|
|
I'm also going to need to know which type of board you're using. Please go to
|
|
<a href="http://docs.platformio.org/en/latest/platforms/espressif32.html#boards" target="_blank">ESP32 boards</a> or
|
|
<a href="http://docs.platformio.org/en/latest/platforms/espressif8266.html#boards" target="_blank">ESP8266 boards</a>,
|
|
find your board and enter it here. For example, enter <code class="inlinecode">nodemcuv2</code>
|
|
for ESP8266 NodeMCU boards. Note: Use <code class="inlinecode">esp01_1m</code> for Sonoff devices.
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<input id="board_type" class="validate" type="text" name="board" required>
|
|
<label for="board_type">Board Type</label>
|
|
</div>
|
|
</div>
|
|
<div class="step-actions">
|
|
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
<li class="step">
|
|
<div class="step-title waves-effect">WiFi And Over-The-Air Updates</div>
|
|
<div class="step-content">
|
|
<div class="row">
|
|
<p>
|
|
Thanks! Now I need to know what WiFi Access Point I should instruct the node to connect to.
|
|
Please enter an SSID (name of the WiFi network) and password (leave empty for no password).
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<input id="wifi_ssid" class="validate" type="text" name="ssid" required>
|
|
<label for="wifi_ssid">WiFi SSID</label>
|
|
</div>
|
|
<div class="input-field col s12">
|
|
<input id="wifi_password" name="psk" type="password">
|
|
<label for="wifi_password">WiFi Password</label>
|
|
</div>
|
|
<p>
|
|
Esphomelib automatically sets up an Over-The-Air update server on the node
|
|
so that you only need to flash a firmware once. Optionally, you can set a password for this
|
|
upload process here.
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<input id="ota_password" class="validate" name="ota_password" type="password">
|
|
<label for="ota_password">OTA Password</label>
|
|
</div>
|
|
</div>
|
|
<div class="step-actions">
|
|
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
<li class="step">
|
|
<div class="step-title waves-effect">MQTT</div>
|
|
<div class="step-content">
|
|
<div class="row">
|
|
<p>
|
|
esphomelib connects to your Home Assistant instance via
|
|
<a href="https://www.home-assistant.io/docs/mqtt/">MQTT</a>. If you haven't already, please set up
|
|
MQTT on your Home Assistant server, for example with the awesome
|
|
<a href="https://www.home-assistant.io/addons/mosquitto/">Mosquitto Hass.io Add-on</a>.
|
|
</p>
|
|
<p>
|
|
When you're done with that, please enter your MQTT broker here. For example
|
|
<code class="inlinecode">hassio.local</code>. Please also specify the MQTT username and password
|
|
you wish esphomelib to use (leave them empty if you're not using any authentication).
|
|
</p>
|
|
<div class="input-field col s12">
|
|
<input id="mqtt_broker" class="validate" type="text" name="broker" required>
|
|
<label for="mqtt_broker">MQTT Broker</label>
|
|
</div>
|
|
<div class="input-field col s6">
|
|
<input id="mqtt_username" class="validate" type="text" name="mqtt_username">
|
|
<label for="mqtt_username">MQTT Username</label>
|
|
</div>
|
|
<div class="input-field col s6">
|
|
<input id="mqtt_password" class="validate" name="mqtt_password" type="password">
|
|
<label for="mqtt_password">MQTT Password</label>
|
|
</div>
|
|
</div>
|
|
<div class="step-actions">
|
|
<button class="waves-effect waves-dark btn indigo next-step">CONTINUE</button>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
<li class="step">
|
|
<div class="step-title waves-effect">Done!</div>
|
|
<div class="step-content">
|
|
<p>
|
|
Hooray! 🎉🎉🎉 You've successfully created your first esphomeyaml configuration file.
|
|
When you click Submit, I will save this configuration file under
|
|
<code class="inlinecode"><HASS_CONFIG_FOLDER>/esphomeyaml/<NAME_OF_NODE>.yaml</code> and
|
|
you will be able to edit this file with the
|
|
<a href="https://www.home-assistant.io/addons/configurator/" target="_blank">HASS Configuratior add-on</a>.
|
|
</p>
|
|
<h5>Next steps</h5>
|
|
<ul class="browser-default">
|
|
<li>
|
|
Flash the firmware. This can be done using the “UPLOAD” option in the dashboard. See
|
|
<a href="https://esphomelib.com/esphomeyaml/index.html#using-with" target="_blank">this</a>
|
|
for guides on how to flash different types of devices.
|
|
</li>
|
|
<li>
|
|
See the <a href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml index</a>
|
|
for a list of supported sensors/devices.
|
|
</li>
|
|
<li>
|
|
Join the <a href="https://discord.gg/KhAMKrd" target="_blank">Discord server</a> and say hi. When I
|
|
have time, I would be happy to help with issues and discuss new features.
|
|
</li>
|
|
<li>
|
|
Star <a href="https://github.com/OttoWinter/esphomelib" target="_blank">esphomelib</a> and
|
|
<a href="https://github.com/OttoWinter/esphomeyaml" target="_blank">esphomeyaml</a> on GitHub and
|
|
report issues using the bug trackers there.
|
|
</li>
|
|
</ul>
|
|
<div class="step-actions">
|
|
<button class="waves-effect waves-dark btn indigo" type="submit">SUBMIT</button>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<a href="#!" class="modal-close waves-effect waves-green btn-flat">Abort</a>
|
|
</div>
|
|
</div>
|
|
|
|
<a class="btn-floating btn-large ribbon-fab waves-effect waves-light pink accent-2" id="setup-wizard-start">
|
|
<i class="material-icons">add</i>
|
|
</a>
|
|
|
|
<div class="tap-target pink lighten-1" data-target="setup-wizard-start">
|
|
<div class="tap-target-content">
|
|
<h5>Set up your first Node</h5>
|
|
<p>
|
|
Huh... It seems like you you don't have any esphomeyaml configuration files yet...
|
|
Fortunately, there's a setup wizard that will step you through setting up your first node 🎉
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<footer class="page-footer indigo darken-1">
|
|
<div class="container">
|
|
|
|
</div>
|
|
<div class="footer-copyright">
|
|
<div class="container">
|
|
© 2018 Copyright Otto Winter, Made with <a class="grey-text text-lighten-4" href="https://materializecss.com/" target="_blank">Materialize</a>
|
|
<a class="grey-text text-lighten-4 right" href="https://esphomelib.com/esphomeyaml/index.html" target="_blank">esphomeyaml {{ version }} Documentation</a>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
M.AutoInit(document.body);
|
|
});
|
|
|
|
const colorReplace = (input) => {
|
|
input = input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
input = input.replace(/\\033\[(?:0;)?31m/g, '<span class="e">');
|
|
input = input.replace(/\\033\[(?:1;)?31m/g, '<span class="e bold">');
|
|
input = input.replace(/\\033\[(?:0;)?32m/g, '<span class="i">');
|
|
input = input.replace(/\\033\[(?:1;)?32m/g, '<span class="i bold">');
|
|
input = input.replace(/\\033\[(?:0;)?33m/g, '<span class="w">');
|
|
input = input.replace(/\\033\[(?:1;)?33m/g, '<span class="w bold">');
|
|
input = input.replace(/\\033\[(?:0;)?35m/g, '<span class="c">');
|
|
input = input.replace(/\\033\[(?:1;)?35m/g, '<span class="c bold">');
|
|
input = input.replace(/\\033\[(?:0;)?36m/g, '<span class="d">');
|
|
input = input.replace(/\\033\[(?:1;)?36m/g, '<span class="d bold">');
|
|
input = input.replace(/\\033\[(?:0;)?37m/g, '<span class="v">');
|
|
input = input.replace(/\\033\[(?:1;)?37m/g, '<span class="v bold">');
|
|
input = input.replace(/\\033\[(?:0;)?38m/g, '<span class="vv">');
|
|
input = input.replace(/\\033\[(?:1;)?38m/g, '<span class="vv bold">');
|
|
input = input.replace(/\\033\[0m/g, '</span>');
|
|
|
|
return input;
|
|
};
|
|
|
|
let configuration = "";
|
|
const ws_url = 'ws://' + window.location.hostname + ':' + window.location.port;
|
|
|
|
document.querySelectorAll(".action-show-logs").forEach((showLogs) => {
|
|
console.log(showLogs);
|
|
showLogs.addEventListener('click', (e) => {
|
|
configuration = e.target.getAttribute('data-node');
|
|
const elem = document.getElementById("modal-logs");
|
|
const instance = M.Modal.getInstance(elem);
|
|
const log = elem.querySelector(".log");
|
|
log.innerHTML = "";
|
|
|
|
instance.open();
|
|
|
|
const logSocket = new WebSocket(ws_url + "/logs");
|
|
logSocket.addEventListener('message', (event) => {
|
|
const data = JSON.parse(event.data);
|
|
if (data.event === "line") {
|
|
const msg = data.data;
|
|
log.innerHTML += colorReplace(msg);
|
|
}
|
|
});
|
|
logSocket.addEventListener('open', (event) => {
|
|
logSocket.send(configuration);
|
|
});
|
|
logSocket.addEventListener('close', () => {
|
|
M.toast({html: 'Terminated process.'});
|
|
});
|
|
|
|
instance.options.onCloseStart = () => {
|
|
logSocket.close();
|
|
};
|
|
});
|
|
});
|
|
|
|
const modalRunElem = document.getElementById("modal-run");
|
|
const submitButton = modalRunElem.querySelector('.upload-port-submit');
|
|
let startRun = undefined;
|
|
const select = modalRunElem.querySelector('select');
|
|
const uploadPort = modalRunElem.querySelector(".upload-port");
|
|
|
|
document.querySelectorAll(".action-upload").forEach((actionUpload) => {
|
|
actionUpload.addEventListener('click', (e) => {
|
|
configuration = e.target.getAttribute('data-node');
|
|
|
|
const modal_instance = M.Modal.getInstance(modalRunElem);
|
|
if (M.FormSelect.getInstance(select) === undefined) {
|
|
M.FormSelect.getInstance(select).destroy();
|
|
}
|
|
const log = modalRunElem.querySelector(".log");
|
|
log.innerHTML = "";
|
|
|
|
if (uploadPort.classList.contains('hide')) {
|
|
uploadPort.classList.remove('hide');
|
|
}
|
|
|
|
modal_instance.open();
|
|
|
|
startRun = (port) => {
|
|
const logSocket = new WebSocket(ws_url + "/run");
|
|
logSocket.addEventListener('message', (event) => {
|
|
const data = JSON.parse(event.data);
|
|
if (data.event === "line") {
|
|
const msg = data.data;
|
|
log.innerHTML += colorReplace(msg);
|
|
}
|
|
});
|
|
logSocket.addEventListener('open', () => {
|
|
const msg = JSON.stringify({configuration: configuration, port: port});
|
|
logSocket.send(msg);
|
|
});
|
|
logSocket.addEventListener('close', () => {
|
|
M.toast({html: 'Terminated process.'});
|
|
});
|
|
modal_instance.options.onCloseStart = () => {
|
|
logSocket.close();
|
|
};
|
|
};
|
|
|
|
fetch('/serial-ports').then(res => res.json())
|
|
.then(response => {
|
|
if (response.length > 1) {
|
|
select.innerHTML = "";
|
|
for (let i = 0; i < response.length; i++) {
|
|
const val = response[i];
|
|
select.innerHTML += `<option value="${val.port}">${val.port} (${val.desc})</option>`;
|
|
}
|
|
M.FormSelect.init(select, {});
|
|
} else {
|
|
uploadPort.classList.add('hide');
|
|
startRun("OTA");
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
submitButton.addEventListener('click', () => {
|
|
const inst = M.FormSelect.getInstance(select);
|
|
console.log(inst.getSelectedValues());
|
|
startRun(inst.getSelectedValues()[0]);
|
|
inst.destroy();
|
|
uploadPort.classList.add('hide');
|
|
});
|
|
|
|
const modalSetupElem = document.getElementById("modal-wizard");
|
|
const setupWizardStart = document.getElementById('setup-wizard-start');
|
|
const startWizard = () => {
|
|
const modalInstance = M.Modal.getInstance(modalSetupElem);
|
|
modalInstance.open();
|
|
|
|
modalInstance.options.onCloseStart = () => {
|
|
|
|
};
|
|
|
|
$('.stepper').activateStepper({
|
|
linearStepsNavigation: false,
|
|
autoFocusInput: true,
|
|
autoFormCreation: true,
|
|
showFeedbackLoader: true,
|
|
parallel: false
|
|
});
|
|
|
|
document.getElementById('#step-1-continue').addEventListener('click', () => {
|
|
console.log("NEXT STEP");
|
|
$('.stepper').nextStep();
|
|
});
|
|
$('#step-1-continue').on('click', () => {
|
|
|
|
});
|
|
};
|
|
setupWizardStart.addEventListener('click', startWizard);
|
|
</script>
|
|
|
|
{% if len(files) == 0 %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const tapTargetElem = document.querySelector('.tap-target');
|
|
const tapTargetInstance = M.TapTarget.getInstance(tapTargetElem);
|
|
tapTargetInstance.options.onOpen = () => {
|
|
$('.tap-target-origin').on('click', () => {
|
|
startWizard();
|
|
});
|
|
};
|
|
tapTargetInstance.open();
|
|
});
|
|
</script>
|
|
{% end %}
|
|
|
|
</body>
|
|
</html> |