@flowfuse/device-agent
Version:
An Edge Agent for running Node-RED instances deployed from the FlowFuse Platform
640 lines (574 loc) • 35.3 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Device Agent - FlowFuse</title>
<link rel="icon" href="assets/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicon-16x16.png">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<style>
*,
body,
html {
box-sizing: border-box;
font-size: 14px;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'
}
body,
html {
height: 100%;
text-align: center
}
.drop-shadow {
filter: drop-shadow(0px 0px 6px rgba(0,0,0,.5));
}
body {
padding: 0;
background: #f8f8f8;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
border-width: 0;
border-style: solid;
border-color: rgba(229, 231, 235, 1);
box-sizing: border-box;
font-family: inherit;
margin: 0;
font-size: 14px;
display: flex;
min-height: 100vh;
flex-direction: column;
line-height: 1.5;
letter-spacing: 0;
color: rgba(107, 114, 128, 1);
background-color: #1f2937;
}
.table {
display: table;
margin: auto;
width: 100%;
text-align: left;
border-radius: .2rem;
/* border-collapse: collapse; */
max-width: 680px;
min-width: 420px;
background: #f8f8f8;
padding: 0px 0px 0px 0px;
}
.table-row {
display: table-row;
}
.table-header {
font-weight: bold;
border: 2px solid #1f2937;
color: #f8f8f8;
background-color: rgb(188, 56, 56);
}
.table-cell {
display: table-cell;
border: 1px solid #1f2937;
padding: 5px;
}
.table-cell.col-1 {
width: 180px;
}
.ff-bg-light {
background-color: #f9fafb
}
.text-gray-500 {
color: rgba(107, 114, 128, 1)
}
header div a {
color: #ed4e4e;
font-size: 1.125rem;
line-height: 1.75rem;
text-decoration: none;
}
h1,
h2,
h3,
h4 {
position: relative
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
color: #8ce2e7;
margin: 0
}
h1 {
font-size: 2.25rem;
line-height: 3rem
}
#web-server-status {
font-size: 3rem;
color: #ed4e4e;
}
.section-header h1 {
font-size: min(6vmin, 26px);
min-width: 420px;
color: #f8f8f8
}
.hero-header {
padding: 16px 20px 12px 20px;
display: flex;
align-items: center;
background-color: #1f2937;
min-width: 420px;
}
.hero-header-item {
font-size: 24px;
font-weight: 700
}
.hero-header-item-1 {
margin-right: 20px;
width: 220px;
padding-bottom: 7px
}
.hero-header-item-2 {
text-align: center;
padding-left: 18px;
padding-bottom: 4px;
font-weight: lighter;
font-size: 28px
}
.hero-header-item-3 {
text-align: right;
flex: auto;
}
.uploader {
display: block;
clear: both;
margin: 0 auto;
width: 100%;
max-width: 680px;
min-width: 420px;
}
.hidden {
display: none
}
.uploader textarea {
/* white-space: nowrap; */
font-family: "DejaVu Sans Mono", monospace;
float: left;
clear: both;
width: 100%;
height: 11.5em;
padding: .5rem .5rem;
text-align: left;
background: #fff;
border-radius: .2rem;
border: 3px solid #eee;
transition: all .2s ease;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none
}
.uploader input[type=file] {
display: none
}
.btn {
display: inline-block;
margin: 1.5rem .5rem 1rem .5rem;
clear: both;
font-family: inherit;
font-weight: 700;
font-size: 14px;
text-decoration: none;
text-transform: initial;
border: none;
border-radius: .2rem;
outline: 0;
padding: 0 1rem;
height: 36px;
line-height: 36px;
color: #fff;
text-transform: uppercase;
transition: all .2s ease-in-out;
box-sizing: border-box;
background: #ed4e4e;
border-color: #ed4e4e;
cursor: pointer
}
.disabled .btn,
.disabled #file-data {
opacity: .5;
cursor: not-allowed;
}
.px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem
}
.mt-2 {
margin-top: 0.5rem
}
.mt-4 {
margin-top: 1rem
}
.w-full {
width: 100%
}
.m-auto {
margin: auto
}
.max-w-lg {
max-width: 32rem
}
.max-w-2xl {
max-width: 42rem
}
.container {
position: relative;
z-index: 2;
width: 100%
}
.text-center {
text-align: center
}
svg {
display: block;
vertical-align: middle
}
@media (max-width:600px) {
.sm\:px-0 {
padding-left: 0px;
padding-right: 0px
}
.sm\:px-2 {
padding-left: 2px;
padding-right: 2px
}
.sm\:pb-2 {
padding-bottom: 2px
}
.sm\:mt-4{
margin-top: 4px
}
.hero-header {
/* flex-direction: column; */
padding: 5px 5px 5px 5px ;
}
.hero-header-item-1 {
margin-right: 0;
margin-bottom: 0px;
padding-bottom: 0px;
}
.hero-header-item-2 {
display: none;
}
}
</style>
</head>
<body>
<div class="text-gray-500 min-h-screen flex flex-col">
<header class="hero-header sm:py-2 drop-shadow">
<div class="hero-header-item hero-header-item-1">
<a id="link-to-forge" class="ff-logo ff-wordmark no-underline hover:no-underline" href="/">
<!--?xml version="1.0" encoding="UTF-8"?-->
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="Layer_1" x="0" y="0" style="enable-background:new 0 0 402 89.7" version="1.1" viewBox="0 0 402 89.7"><style>.st0{fill:#ed4e4e}.st1{fill:#fff}</style><path d="M45.8 50.2c8.5 3.6 16.6 7.9 25.5 9.8 3.9.7 7.9 1 11.8 1V39.4h-3.3c-12.1 0-23.2 6.2-34 10.8zM20.7 57.1c-3.9-.3-7.8-.3-11.7-.3H0v19.9c0 3.6 2.9 6.5 6.5 6.5h70.1c3.6 0 6.5-2.9 6.5-6.5v-2.5c-8.5 0-17.3-1.1-25.2-4.4-12.4-4.3-23.8-12.4-37.2-12.7zM76.6 0H6.5C2.9 0 0 2.9 0 6.5v36.8c7.8 0 16.1.3 24-.3 10.8-1 20.2-6.9 30.4-10.8 9.1-3.9 19-6.2 28.8-5.9V6.5c-.1-3.6-3-6.5-6.6-6.5z" class="st0"/><path d="M83.1 39.4V26.3c-9.8-.3-19.6 2-28.8 5.9C44.2 36.1 34.7 42 24 43c-7.8.7-16.1.3-24 .3v13.4h8.9c3.9 0 7.8 0 11.7.3 13.4.3 24.8 8.4 37.2 12.7 7.8 3.3 16.7 4.4 25.2 4.4V61c-3.9 0-7.9-.3-11.8-1-8.8-2-17-6.2-25.5-9.8 10.8-4.6 21.9-10.8 34-10.8h3.4zM134.2.2h-22.3c-1.5 0-2.8.5-3.7 1.4-.9.9-1.4 2.1-1.4 3.7v37.8c0 1.6.4 2.7 1.2 3.4.8.7 2.1 1 3.9 1 .9 0 1.7-.1 2.5-.2s1.4-.2 1.8-.3V28.4H129c1.1 0 1.9-.3 2.5-1 .6-.7.9-1.7.9-2.9 0-.8-.1-1.5-.3-2.1-.2-.6-.5-1.2-.7-1.6h-15.2V7.9h15.4c1.1 0 1.9-.3 2.5-1 .6-.7.9-1.7.9-3 0-.8-.1-1.5-.3-2.1 0-.8-.2-1.3-.5-1.6zM145.6.2c-.9 0-1.7.1-2.4.2-.8.2-1.4.3-1.8.4v42.4c0 1.6.4 2.7 1.3 3.3.8.6 2.1.9 3.8.9.9 0 1.7-.1 2.5-.2.7-.1 1.3-.3 1.7-.4V4.5c0-1.6-.4-2.7-1.2-3.3-.9-.7-2.2-1-3.9-1zM187.7 12.1c-2.8-1.6-6.1-2.4-9.9-2.4-3.7 0-7 .8-9.9 2.4-2.9 1.6-5.1 3.8-6.6 6.7-1.6 2.9-2.4 6.3-2.4 10.2 0 3.9.8 7.3 2.3 10.2 1.6 2.9 3.8 5.1 6.6 6.6 2.9 1.6 6.2 2.3 10 2.3s7.2-.8 10-2.3c2.8-1.6 5-3.8 6.6-6.7 1.6-2.9 2.3-6.3 2.3-10.1 0-3.9-.8-7.3-2.4-10.2-1.5-2.9-3.7-5.1-6.6-6.7zm-3 25.5c-1.7 2.1-3.9 3.1-6.8 3.1s-5.2-1-6.8-3.1c-1.7-2-2.5-5-2.5-8.7 0-3.7.8-6.6 2.5-8.7 1.7-2.1 4-3.1 6.8-3.1 2.9 0 5.2 1 6.8 3.1 1.7 2.1 2.5 5 2.5 8.7 0 3.8-.9 6.7-2.5 8.7zM288.4.2h-22.3c-1.5 0-2.8.5-3.7 1.4S261 3.7 261 5.3v37.8c0 1.6.4 2.7 1.2 3.4.8.7 2.1 1 3.9 1 .9 0 1.7-.1 2.5-.2s1.4-.2 1.8-.3V28.4h12.8c1.1 0 1.9-.3 2.5-1 .6-.7.9-1.7.9-2.9 0-.8-.1-1.5-.3-2.1-.2-.6-.5-1.2-.7-1.6h-15.2V7.9H286c1.1 0 1.9-.3 2.5-1 .6-.7.9-1.7.9-3 0-.8-.1-1.5-.3-2.1-.2-.8-.4-1.3-.7-1.6zM252.5 11c-.8-.6-1.9-.8-3.1-.8-1.5 0-2.9.4-4.1 1.1-.7 4.7-1.5 9.4-2.5 14.2-1 4.8-2 9.3-2.9 13.5h-.4c-.3-1.2-.7-3-1.4-5.4-.7-2.4-1.5-5.1-2.4-8.2-.9-3.1-1.9-6.3-2.9-9.5-.3-.9-.9-1.6-1.8-2-.9-.4-2-.7-3.3-.7-1 0-1.9.1-2.7.3-.8.2-1.5.5-2.1.9-1 3.4-2 6.8-3 10.1s-1.9 6.3-2.6 8.8c-.7 2.6-1.3 4.5-1.6 5.6h-.3c-.2-.8-.5-2-.8-3.6-.4-1.6-.8-3.4-1.2-5.5-.5-2.1-.9-4.5-1.4-7.2-.5-2.7-1-5.4-1.4-8.3-.3-1.5-.8-2.6-1.5-3.3-.8-.6-1.8-1-3.1-1-1.1 0-2 .2-2.9.6-.8.4-1.5.9-2 1.4.4 1.9.8 4.2 1.3 6.7s1.1 5.1 1.8 7.7c.6 2.7 1.3 5.2 1.9 7.7.6 2.5 1.3 4.7 1.9 6.5.6 1.9 1.2 3.3 1.6 4.4.4.8 1.1 1.4 2.2 1.9 1.1.4 2.3.7 3.7.7 1.2 0 2.3-.1 3.1-.4.8-.3 1.5-.6 2-1 .6-1.7 1.3-3.8 2.2-6.3.8-2.5 1.7-5.3 2.6-8.2.9-2.9 1.7-5.8 2.4-8.6l2.4 8.4c.8 2.8 1.6 5.5 2.4 7.9.8 2.4 1.5 4.4 2.1 5.9.5 1.5 2.3 2.3 5.4 2.3 1.2 0 2.3-.1 3.3-.4 1-.3 1.7-.6 2.2-1 .4-1 .9-2.4 1.5-4.1.6-1.7 1.2-3.7 1.8-5.9.6-2.2 1.3-4.5 1.9-6.8.6-2.3 1.2-4.6 1.6-6.7.5-2.1.9-3.9 1.2-5.6.3-1.6.5-2.7.5-3.4-.4-1.2-.8-2.2-1.6-2.7zM321.5 10.3c-.9 0-1.7.1-2.4.2-.8.2-1.4.3-1.8.4V39.1c-.6.4-1.5.7-2.6 1-1.1.3-2.5.5-4.1.5-2.6 0-4.5-.6-6-1.8-1.4-1.2-2.1-3.2-2.1-5.9V14.6c0-1.7-.4-2.8-1.2-3.4-.8-.6-2.1-.9-3.8-.9-.9 0-1.8.1-2.5.2-.8.2-1.3.3-1.7.4v22.2c0 3.5.7 6.3 2.1 8.5 1.4 2.2 3.4 3.8 6 4.8s5.7 1.5 9.2 1.5c2.9 0 5.5-.3 7.7-.9 2.2-.6 4.1-1.3 5.5-2.2 1.1-.7 1.8-1.4 2.2-2.1.4-.8.6-1.7.6-2.9V14.6c0-1.7-.4-2.8-1.2-3.4-.9-.6-2.2-.9-3.9-.9zM350.6 25.4l-4-1c-1.6-.4-2.7-.9-3.4-1.5-.7-.6-1-1.4-1-2.3 0-1.2.6-2.2 1.8-2.8 1.2-.6 2.8-1 4.7-1 1.3 0 2.6.1 3.9.4 1.3.3 2.5.6 3.6 1.1 1.1.4 1.9.9 2.5 1.3.6-.4 1-.9 1.4-1.6.4-.7.6-1.4.6-2.3 0-1.3-.5-2.4-1.6-3.3-1.1-.9-2.6-1.6-4.5-2.1s-4.1-.7-6.5-.7c-4.7 0-8.3 1-10.9 3.1-2.6 2.1-3.9 4.7-3.9 7.8 0 2.9.9 5.2 2.6 6.9 1.8 1.7 4.5 3 8.2 3.8l4.5 1.1c1.6.4 2.8.9 3.6 1.6.8.7 1.1 1.6 1.1 2.8 0 2.7-2.2 4.1-6.6 4.1-2.4 0-4.5-.4-6.4-1.1-1.9-.7-3.5-1.6-5-2.5-.7.5-1.2 1-1.6 1.8-.4.7-.6 1.5-.6 2.4 0 2 1.3 3.6 3.9 4.8 2.6 1.2 5.9 1.8 10 1.8 5 0 8.8-1 11.4-3 2.6-2 3.9-4.8 3.9-8.3 0-3-.9-5.4-2.8-7.2-1.8-1.8-4.8-3.2-8.9-4.1zM400.9 28.8c.7-.7 1.1-1.7 1.1-3.2 0-3.1-.7-5.8-2.1-8.2-1.4-2.4-3.4-4.3-5.9-5.7-2.5-1.4-5.4-2.1-8.8-2.1-2.4 0-4.8.4-6.9 1.2-2.2.8-4.1 2-5.7 3.6-1.7 1.6-3 3.5-3.9 5.9-.9 2.3-1.4 5.1-1.4 8.2 0 4.3.8 7.9 2.5 10.8 1.7 2.9 4 5.1 7.1 6.5 3 1.5 6.5 2.2 10.5 2.2 2.6 0 4.8-.3 6.8-.8s3.5-1.2 4.7-2.2c1.2-.9 1.8-2.1 1.8-3.3 0-.8-.2-1.6-.7-2.3-.4-.7-1-1.2-1.6-1.6-1.1.8-2.6 1.5-4.4 2.1-1.8.6-3.8 1-5.9 1-3.2 0-5.9-.8-8-2.4-1.7-1.3-2.8-3.1-3.4-5.4l21.5-3.1c1-.1 2-.5 2.7-1.2zm-25-2.1c.2-3.1 1-5.5 2.7-7.2 1.8-1.8 4-2.8 6.6-2.8 2.6 0 4.5.7 5.9 2.2 1.4 1.5 2.1 3.2 2.3 5.2l-17.5 2.6zM115.2 65.3c.7 0 1.4.1 2 .2.6.2 1.2.3 1.7.6v-6.8c.2 0 .4-.1.7-.2.3-.1.6-.1 1-.1.7 0 1.2.1 1.6.4.3.3.5.7.5 1.3v18.8c0 .5-.1.9-.3 1.2-.2.3-.5.6-1 .9-.5.3-1.3.7-2.3 1-1 .3-2 .4-3.2.4-1.4 0-2.6-.2-3.7-.5s-2.1-.9-2.9-1.6-1.4-1.7-1.8-2.8c-.4-1.1-.6-2.4-.6-3.9s.2-2.9.7-4c.4-1.1 1-2.1 1.8-2.8.8-.7 1.7-1.3 2.7-1.6.9-.3 2-.5 3.1-.5zm3.7 4c-.4-.3-.9-.5-1.4-.7-.5-.2-1.1-.3-1.8-.3s-1.4.1-2 .3c-.6.2-1.1.6-1.6 1-.4.5-.8 1.1-1.1 1.9s-.4 1.7-.4 2.7c0 2 .5 3.5 1.4 4.4 1 .9 2.2 1.4 3.8 1.4.7 0 1.3-.1 1.8-.2s.9-.3 1.3-.5v-10zM130.6 76.1c.3 1.4 1 2.4 1.9 3 1 .6 2.2 1 3.7 1 1 0 1.9-.2 2.8-.5.8-.3 1.5-.6 2-1 .7.4 1 .9 1 1.6 0 .4-.2.8-.5 1.1-.3.3-.7.6-1.3.9s-1.2.4-1.9.6c-.7.2-1.5.2-2.3.2-1.4 0-2.6-.2-3.8-.6s-2.1-1-2.9-1.7c-.8-.8-1.4-1.7-1.9-2.8-.4-1.1-.7-2.4-.7-3.9s.2-2.7.6-3.8c.4-1.1 1-2 1.7-2.7.7-.7 1.6-1.3 2.6-1.7 1-.4 2.1-.6 3.2-.6 1.2 0 2.2.2 3.1.5 1 .4 1.7.9 2.4 1.5.7.7 1.2 1.4 1.6 2.3s.6 1.9.6 3c0 .6-.2 1-.4 1.3-.3.3-.7.4-1.2.5l-10.3 1.8zm4.4-7.9c-1.3 0-2.4.4-3.3 1.3-.9.9-1.3 2.2-1.4 3.8l8.8-1.2c-.1-1.1-.5-2-1.2-2.7-.5-.8-1.5-1.2-2.9-1.2zM159.2 65.5c.5 0 1 .1 1.3.4.3.3.5.7.5 1.2 0 .3-.1.8-.3 1.5-.2.7-.4 1.5-.7 2.4s-.7 1.9-1 3c-.4 1.1-.8 2.1-1.2 3.1-.4 1-.9 2-1.3 3s-.8 1.7-1.2 2.4c-.2.1-.5.2-.9.3-.4.1-.8.2-1.3.2-1.2 0-1.9-.3-2.3-1-.3-.6-.8-1.5-1.3-2.8-.5-1.3-1.1-2.7-1.7-4.2-.6-1.5-1.1-3-1.7-4.5-.5-1.5-1-2.8-1.3-3.9.2-.3.5-.5.9-.7.3-.2.7-.3 1.1-.3.5 0 1 .1 1.3.4s.6.7.8 1.3l2 6.2c.1.4.3 1 .5 1.5s.4 1.1.6 1.7c.2.6.4 1.1.6 1.6.2.5.3 1 .5 1.3h.2c.8-2.3 1.6-4.5 2.3-6.8s1.4-4.5 1.9-6.8c.5-.4 1-.5 1.7-.5zM164.4 60.7c0-.6.2-1.1.6-1.6.4-.4 1-.6 1.7-.6s1.2.2 1.6.6c.4.4.6 1 .6 1.6 0 .6-.2 1.1-.6 1.6-.4.4-1 .6-1.6.6-.7 0-1.2-.2-1.7-.6-.4-.5-.6-1-.6-1.6zm4.1 21.8c-.2.1-.4.1-.7.2-.3.1-.6.1-1 .1-.7 0-1.2-.1-1.6-.4s-.5-.7-.5-1.3V65.9c.2 0 .4-.1.7-.2.3-.1.6-.1 1-.1.7 0 1.2.1 1.5.4s.5.7.5 1.4l.1 15.1zM181.9 68.3c-.8 0-1.5.1-2.1.4-.7.3-1.2.6-1.7 1.1-.4.5-.8 1.1-1.1 1.8s-.4 1.6-.4 2.5c0 1.9.5 3.3 1.5 4.3s2.3 1.5 3.8 1.5c.9 0 1.7-.1 2.3-.3.6-.2 1.2-.5 1.7-.9.3.2.6.4.8.7.2.3.3.6.3 1 0 .7-.5 1.4-1.5 1.9s-2.3.8-3.8.8c-1.3 0-2.5-.2-3.6-.5s-2-.9-2.8-1.7c-.8-.7-1.4-1.7-1.8-2.8-.4-1.1-.7-2.4-.7-3.9s.2-2.8.7-3.9c.5-1.1 1.1-2.1 1.9-2.8s1.7-1.3 2.8-1.7c1.1-.4 2.2-.5 3.4-.5.8 0 1.5.1 2.1.2s1.2.3 1.6.6c.4.3.8.5 1 .9.3.3.4.7.4 1s-.1.6-.3.9c-.2.3-.4.5-.7.6-.5-.3-1-.6-1.7-.8-.4-.2-1.2-.3-2.1-.4zM193.3 76.1c.3 1.4 1 2.4 1.9 3 1 .6 2.2 1 3.7 1 1 0 1.9-.2 2.8-.5.8-.3 1.5-.6 2-1 .7.4 1 .9 1 1.6 0 .4-.2.8-.5 1.1-.3.3-.7.6-1.3.9-.5.3-1.2.4-1.9.6-.7.2-1.5.2-2.3.2-1.4 0-2.6-.2-3.8-.6s-2.1-1-2.9-1.7c-.8-.8-1.4-1.7-1.9-2.8s-.7-2.4-.7-3.9.2-2.7.6-3.8c.4-1.1 1-2 1.7-2.7.7-.7 1.6-1.3 2.6-1.7 1-.4 2.1-.6 3.2-.6s2.2.2 3.1.5c1 .4 1.7.9 2.4 1.5.7.7 1.2 1.4 1.6 2.3.4.9.6 1.9.6 3 0 .6-.2 1-.4 1.3-.3.3-.7.4-1.2.5l-10.3 1.8zm4.5-7.9c-1.3 0-2.4.4-3.3 1.3-.9.9-1.3 2.2-1.4 3.8l8.8-1.2c-.1-1.1-.5-2-1.2-2.7-.6-.8-1.6-1.2-2.9-1.2zM222.7 65.3c2.2 0 4 .5 5.3 1.5 1.3 1 2 2.5 2 4.6v8.3c0 .5-.1 1-.4 1.3-.3.3-.6.6-1 .9-.6.3-1.4.7-2.4.9-1 .3-2.1.4-3.4.4-2.3 0-4.1-.4-5.4-1.4-1.3-.9-1.9-2.3-1.9-4 0-1.6.5-2.8 1.5-3.6 1-.8 2.5-1.3 4.4-1.6l5-.5v-.8c0-1-.3-1.8-1-2.3-.7-.5-1.6-.7-2.9-.7-1 0-1.9.1-2.8.4-.9.3-1.7.6-2.3.9-.3-.2-.4-.4-.6-.7-.2-.3-.3-.6-.3-.9 0-.4.1-.7.3-1 .2-.3.5-.5 1-.7.7-.3 1.4-.6 2.3-.7.7-.3 1.7-.3 2.6-.3zm0 14.8c.8 0 1.6-.1 2.2-.3.6-.2 1-.3 1.3-.5v-4.5l-3.9.4c-1.1.1-1.9.3-2.5.7-.5.4-.8.9-.8 1.7 0 .8.3 1.4.9 1.8.7.5 1.6.7 2.8.7zM245.7 81c-.4.3-1 .5-1.7.7-.7.2-1.4.3-2.3.3-1 0-2-.2-3-.4-1-.3-1.8-.8-2.5-1.4-.7-.7-1.3-1.5-1.7-2.6-.4-1-.6-2.3-.6-3.9 0-1.4.2-2.6.6-3.6.4-1 1-1.9 1.7-2.6.8-.7 1.7-1.2 2.7-1.6 1-.3 2.2-.5 3.5-.5 1.2 0 2.3.2 3.2.4 1 .3 1.8.7 2.4 1 .4.3.7.6 1 .9.2.3.3.7.3 1.2V83c0 1.2-.2 2.2-.6 3.1-.4.9-1 1.6-1.7 2.1s-1.6.9-2.6 1.2-2.1.4-3.2.4c-1.2 0-2.3-.1-3.1-.3-.9-.3-1.5-.5-1.9-.7-.7-.4-1.1-1-1.1-1.8 0-.4.1-.8.3-1 .2-.3.4-.5.6-.7.5.4 1.3.7 2.2 1 .9.3 1.9.5 2.9.5 1.5 0 2.7-.3 3.5-.9.8-.6 1.2-1.6 1.2-3l-.1-1.9zm-3.5-2c.8 0 1.5-.1 2-.3.5-.2 1-.5 1.4-.9v-8.7c-.3-.2-.8-.4-1.3-.6-.5-.2-1.1-.3-1.9-.3-1.5 0-2.6.4-3.5 1.3-.9.9-1.3 2.2-1.3 4 0 1 .1 1.8.3 2.5.3.7.6 1.2 1 1.7s.9.7 1.5.9c.5.3 1.1.4 1.8.4zM257.1 76.1c.3 1.4 1 2.4 1.9 3 1 .6 2.2 1 3.7 1 1 0 1.9-.2 2.8-.5.8-.3 1.5-.6 2-1 .7.4 1 .9 1 1.6 0 .4-.2.8-.5 1.1-.3.3-.7.6-1.3.9-.5.3-1.2.4-1.9.6-.7.2-1.5.2-2.3.2-1.4 0-2.6-.2-3.8-.6-1.1-.4-2.1-1-2.9-1.7-.8-.8-1.4-1.7-1.9-2.8-.4-1.1-.7-2.4-.7-3.9s.2-2.7.6-3.8c.4-1.1 1-2 1.7-2.7.7-.7 1.6-1.3 2.6-1.7 1-.4 2.1-.6 3.2-.6 1.2 0 2.2.2 3.1.5 1 .4 1.7.9 2.4 1.5.7.7 1.2 1.4 1.6 2.3s.5 1.9.5 3c0 .6-.1 1-.4 1.3-.3.3-.7.4-1.2.5l-10.2 1.8zm4.5-7.9c-1.3 0-2.4.4-3.3 1.3-.9.9-1.3 2.2-1.4 3.8l8.8-1.2c-.1-1.1-.5-2-1.2-2.7-.6-.8-1.6-1.2-2.9-1.2zM283.4 69.1c-.7-.5-1.6-.8-2.7-.8-.8 0-1.6.1-2.3.3s-1.2.4-1.7.7v13.1c-.2.1-.4.1-.7.2s-.6.1-1 .1c-.7 0-1.2-.1-1.5-.4-.3-.3-.5-.7-.5-1.3V69.3c0-.5.1-1 .3-1.3.2-.3.6-.7 1.1-1 .7-.4 1.6-.8 2.6-1.1 1.1-.3 2.3-.5 3.6-.5 2.3 0 4.1.5 5.4 1.6 1.3 1 2 2.6 2 4.7v10.9c-.2.1-.4.1-.7.2s-.6.1-1 .1c-.7 0-1.2-.1-1.6-.4-.3-.3-.5-.7-.5-1.3v-9.3c.1-1.4-.2-2.2-.8-2.8zM297.6 79.5c.5.3 1.2.5 2.1.5.4 0 .8-.1 1.3-.2.4-.1.9-.3 1.2-.4.2.2.3.4.4.6.1.3.2.5.2.9 0 .6-.3 1.1-1 1.5s-1.5.6-2.8.6c-1.8 0-3.2-.4-4.3-1.2-1-.8-1.6-2.1-1.6-4V61.1c.2 0 .4-.1.7-.2.3-.1.6-.1 1-.1.7 0 1.2.1 1.5.4.3.3.5.7.5 1.3v3.6h5.6s.2.3.3.6c.1.3.1.5.1.8 0 1-.4 1.6-1.3 1.6h-4.6v8.5c0 .8.2 1.4.7 1.9zM22.6 13.1h-9.5v9.5h9.5v-9.5z" class="st1"/><path d="M29.7 20.8h-3v-5.9h3c.3 0 .6-.3.6-.6s-.3-.6-.6-.6h-3v-1c0-2.1-1.7-3.8-3.7-3.8h-1V6c0-.3-.3-.6-.6-.6s-.6.2-.6.6v3h-5.9V6c0-.3-.3-.6-.6-.6s-.6.2-.6.6v3h-1.1c-2 0-3.7 1.7-3.7 3.8v1H6c-.3 0-.6.3-.6.6s.3.6.6.6h3v5.9H6c-.3 0-.6.3-.6.6s.2.5.6.5h3v1c0 2.1 1.7 3.8 3.7 3.8h1.1v3c0 .3.3.6.6.6s.6-.3.6-.6v-3h5.9v3c0 .3.3.6.6.6s.5-.4.5-.7v-3h1c2 0 3.7-1.7 3.7-3.8v-1h3c.3 0 .6-.3.6-.6s-.3-.5-.6-.5zM25.5 23c0 1.4-1.1 2.6-2.5 2.6H12.6c-1.4 0-2.5-1.2-2.5-2.6V12.7c0-1.4 1.1-2.6 2.5-2.6H23c1.4 0 2.5 1.2 2.5 2.6V23z" class="st1"/></svg>
</a>
</div>
<div class="hero-header-item hero-header-item-3"><a class="flex items-center gap-2" href="https://flowfuse.com/docs/device-agent/introduction/" target="_blank">docs</a></div>
</header>
<main>
<!-- Status title -->
<div class="section-header w-full px-6 sm:px-0 pb-2 mt-2 sm:py-2 sm:my-2">
<div class="container m-auto text-center max-w-2xl">
<h1 class="max-w-lg m-auto">
Agent Information
</h1>
</div>
</div>
<!-- Status data -->
<div class="w-full px-6 pb-2 sm:px-2">
<div class="container m-auto text-center max-w-4xl">
<div class="m-auto w-full text-center">
<div id="status-table" class="table"></div>
<div id="config-table" class="table mt-4"></div>
</div>
</div>
</div>
<!-- Config Upload title -->
<div class="section-header w-full px-6 sm:px-0 pb-2 mt-2 sm:py-2 sm:my-2">
<div class="container m-auto text-center max-w-2xl">
<h1 class="max-w-lg m-auto">
Agent Configuration
</h1>
</div>
</div>
<!-- Config Upload text input and buttons -->
<div class="w-full px-6 sm:px-2">
<div class="container m-auto text-center max-w-4xl">
<div class="m-auto w-full text-center">
<form id="file-upload-form" class="uploader" enctype="multipart/form-data">
<input id="file-upload" type="file" name="fileUpload" accept=".yml,.txt" />
<label for="file-upload" id="file-drop-target">
<textarea id="file-data" spellcheck="false" name="deviceText"
rows="7"
cols="80"
></textarea>
<span id="file-upload-btn" class="btn" style="width: 125px;">Load file</span>
</label>
<button type="button" class="btn" id="submit-button" style="width: 125px;"> Apply</button>
</form>
</div>
</div>
</div>
<h2 id="web-server-status" class="text-center hidden">Web server offline</h2>
</main>
</div>
<script>
function main () {
const fileSelect = document.getElementById('file-upload')
const fileData = document.getElementById('file-data')
const fileDropTarget = document.getElementById('file-drop-target')
const submitButton = document.getElementById('submit-button')
const uploadButton = document.getElementById('file-upload-btn')
function InitDropLoader () {
// hand the file upload input to the file drop target handler
fileSelect.addEventListener('change', fileSelectHandler, false)
// Is XHR2 available?
const xhr = new XMLHttpRequest()
if (xhr.upload) {
// File Drop
fileDropTarget.addEventListener('drop', fileSelectHandler, false)
fileData.addEventListener('drop', fileSelectHandler, false)
document.addEventListener('dragover', (e) => {
const validDropPoints = ['file-drop-target', 'file-data']
const items = e.dataTransfer.items
if (items.length > 1 || items.length === 0 || !validDropPoints.includes(e.target.id)) {
e.dataTransfer.dropEffect = 'none'
e.preventDefault()
return true
}
e.dataTransfer.dropEffect = 'copy'
return false
}, false)
document.addEventListener('drop', (e) => {
e.preventDefault()
return true
}, false)
}
}
/** @param {DragEvent} e */
function fileSelectHandler (e) {
/** @type {FileList} */
const files = e.target.files || e.dataTransfer.files
// only accept one file
if (files.length !== 1) {
alert('Please select a single file.')
// cancel the drop
e.stopPropagation()
e.preventDefault()
return false
}
// the file to process
const file = files[0]
// get the file type
let fType = file.type
// if the file type is empty, try to determine it from the file name
if (fType === '') {
const ext = file.name.split('.').pop()
if (ext === 'yml' || ext === 'yaml') {
fType = 'text/yaml'
} else if (ext === 'txt') {
fType = 'text/plain'
}
}
// ensure file is of correct type
if (fType !== 'text/plain' && fType !== 'text/yaml') {
alert('Invalid file type. Please select a .txt or .yml file.')
e.stopPropagation()
e.preventDefault()
return false
}
e.stopPropagation()
e.preventDefault()
// clear the text area and load the file
fileData.innerText = ''
const reader = new FileReader()
reader.onload = function (event) {
fileData.value = event.target.result.replace(/\\n/g, '\n').replace(/\t/g, ' ')
fileSelect.value = '' // clear the file selector
updateTextareaWrapping()
}
reader.readAsText(file)
updateTextareaWrapping()
return true
}
/** submit button handler */
function submitHandler () {
const xhrSubmitConfig = new XMLHttpRequest()
xhrSubmitConfig.timeout = 5000 // Set timeout to 5 seconds (5000 milliseconds)
xhrSubmitConfig.ontimeout = function () { alert('Request timed out!') }
xhrSubmitConfig.onreadystatechange = function () {
if (xhrSubmitConfig.readyState === XMLHttpRequest.DONE) {
if (xhrSubmitConfig.status === 200) {
fileData.value = ''
alert('Configuration saved. The device agent will now reload with this new configuration.')
} else {
const data = typeof xhrSubmitConfig.response === 'object' ? xhrSubmitConfig.response : JSON.parse(xhrSubmitConfig.responseText || '{}')
const message = data.error || 'There was a problem with the request.'
alert(message)
}
}
}
xhrSubmitConfig.open('POST', 'submit')
xhrSubmitConfig.setRequestHeader('Content-type', 'application/json')
xhrSubmitConfig.setRequestHeader('Accept', 'application/json')
xhrSubmitConfig.timeout = 5000 // Set timeout to 5 seconds (5000 milliseconds)
xhrSubmitConfig.responseType = 'json'
const data = { config: fileData.value }
xhrSubmitConfig.send(encodeURIComponent(JSON.stringify(data)))
statusPoll()
return false // prevent page refresh
}
/** update the link to the forge */
function updateLinkToForge (forgeURL, target) {
const linkToForge = document.getElementById('link-to-forge')
if (linkToForge) {
linkToForge.href = forgeURL
linkToForge.target = target
}
}
/** add/remove wrapping to the text area to show/hide line breaks in the placeholder text */
function updateTextareaWrapping () {
fileData.style.whiteSpace = fileData.value.length > 0 ? 'nowrap' : ''
}
submitButton.addEventListener('click', submitHandler, false)
if (window.File && window.FileList && window.FileReader) {
fileData.placeholder = 'Enter the device or a provisioning configuration yaml. \nYou can load a file, drop a file or paste text in directly. \nClick Apply to update and reload the device.'
InitDropLoader()
} else {
fileData.placeholder = 'Enter the device configuration.'
uploadButton.style.display = 'none'
}
// listen for changes to the text area and wrap/nowrap the text as
// needed to show line breaks in the placeholder text
'change input cut paste focus blur'.split(' ').forEach(evName => {
fileData.addEventListener(evName, updateTextareaWrapping, false)
})
// status updates
const statusPollInterval = 5000
let statusPollTimeoutCount = 0
let statusPollTimer = null
function statusPoll () {
const showOfflineMessage = () => {
const webServerStatus = document.getElementById('web-server-status')
if (webServerStatus) {
webServerStatus.classList.remove('hidden')
}
}
const xhrStatus = new XMLHttpRequest()
xhrStatus.ontimeout = function () {
if (statusPollTimeoutCount < 3) {
statusPollTimeoutCount++
return
}
clearInterval(statusPollTimer)
showOfflineMessage()
}
xhrStatus.onerror = function () {
if (statusPollTimeoutCount < 3) {
statusPollTimeoutCount++
return
}
clearInterval(statusPollTimer)
showOfflineMessage()
}
xhrStatus.onreadystatechange = function () {
if (xhrStatus.readyState === XMLHttpRequest.DONE) {
if (xhrStatus.status === 200) {
const data = xhrStatus.response || {}
const status = data.status || {}
const deviceClock = status.deviceClock ? new Date(status.deviceClock) : 'unknown'
const config = status.config || {}
const statusTable = document.getElementById('status-table')
const configTable = document.getElementById('config-table')
const tempTable = document.createElement('div') // for building the tables before adding to the DOM
const addRow = (table, key, value, classes, skipIfNull, defaultForNull = 'unknown') => {
const { headerClass, rowClass, cellClass } = Object.assign({}, classes, { rowClass: 'table-row', cellClass: 'table-cell' })
if (skipIfNull && (value === null || value === undefined)) {
return
}
if (value === null || value === undefined) {
value = defaultForNull
}
const row = document.createElement('div')
row.classList.add(rowClass)
if (headerClass) {
row.classList.add(headerClass)
}
const keyCell = document.createElement('div')
keyCell.classList.add(cellClass, 'col-1')
keyCell.textContent = key
const valueCell = document.createElement('div')
valueCell.classList.add(cellClass, 'col-2')
valueCell.textContent = value
row.appendChild(keyCell)
row.appendChild(valueCell)
table.appendChild(row)
}
// ** status table ** //
tempTable.innerHTML = ''
// Create the header row
addRow(tempTable, 'Status', 'Value', { headerClass: 'table-header' })
// add the status & version rows
addRow(tempTable, 'Agent Status', status.state)
addRow(tempTable, 'Developer Mode', status.mode === 'developer' ? 'true' : undefined, null, true)
addRow(tempTable, 'Snapshot Name', status.snapshotName, null, false, 'none')
addRow(tempTable, 'Snapshot Desc', status.snapshotDesc, null, true)
addRow(tempTable, 'Agent Version', status.version)
addRow(tempTable, 'Device Clock', deviceClock)
statusTable.innerHTML = tempTable.innerHTML
// ** config table ** //
tempTable.innerHTML = ''
// add the config header row
addRow(tempTable, 'Configuration', 'Value', { headerClass: 'table-header' })
// add the config rows
addRow(tempTable, 'Device ID', config.deviceId || 'not set')
addRow(tempTable, 'Device Name', status.name || 'not set')
addRow(tempTable, 'Forge URL', config.forgeURL || 'not set')
addRow(tempTable, 'Working Directory', config.dir || 'not set')
addRow(tempTable, 'Device File', config.deviceFile || 'unknown')
addRow(tempTable, 'Port', config.port || 'unknown')
if (config.provisioningMode) {
addRow(tempTable, 'Provisioning Mode', config.provisioningMode)
addRow(tempTable, 'Provisioning Name', config.provisioningName)
addRow(tempTable, 'Provisioning Team', config.provisioningTeam)
}
configTable.innerHTML = tempTable.innerHTML
tempTable.innerHTML = ''
// update the hyperlink to the forge
if (config.forgeURL) {
updateLinkToForge(config.forgeURL, '_forge')
} else {
updateLinkToForge('/', '_self')
}
}
}
}
xhrStatus.open('GET', 'status')
xhrStatus.setRequestHeader('Content-type', 'application/json')
xhrStatus.setRequestHeader('Accept', 'application/json')
xhrStatus.responseType = 'json'
xhrStatus.timeout = statusPollInterval - 200 // Set timeout to statusPollInterval less 200ms for grace
xhrStatus.send()
}
// start the status poll
statusPollTimer = setInterval(() => {
statusPoll()
}, statusPollInterval)
// initial poll for status
statusPoll()
}
main()
</script>
</body>
</html>