From e001b06b6252704fd2e8a5368cd0936fc4028b0a Mon Sep 17 00:00:00 2001 From: marmei <43099631+mar-mei@users.noreply.github.com> Date: Fri, 2 Aug 2019 20:12:09 +0200 Subject: [PATCH] v2.0.0.0000 --- .gitignore | 7 + LICENSE | 2 +- README-DEV.md | 1 + README.md | 128 +- html/configuration.html | 58 + html/create-first-user.html | 47 + html/css/base.css | 448 ++++ html/css/screen.css | 540 ++++ html/css/screen2.css | 3 + html/img/BC-QR.jpg | Bin 0 -> 29877 bytes html/img/filter.png | Bin 0 -> 2067 bytes html/img/log.png | Bin 0 -> 1995 bytes html/img/logo_w_600x200.png | Bin 0 -> 11183 bytes html/img/logout.png | Bin 0 -> 2011 bytes html/img/m3u.png | Bin 0 -> 1569 bytes html/img/mapping.png | Bin 0 -> 1750 bytes html/img/settings.png | Bin 0 -> 2374 bytes html/img/stream-limit.jpg | Bin 0 -> 113826 bytes html/img/users.png | Bin 0 -> 2004 bytes html/img/x_ transparent.png | Bin 0 -> 7733 bytes html/img/x_black.png | Bin 0 -> 7132 bytes html/img/x_white.png | Bin 0 -> 6732 bytes html/img/xmltv.png | Bin 0 -> 1579 bytes html/index.html | 108 + html/js/authentication.js | 42 + html/js/authentication_ts.js | 32 + html/js/base.js | 331 +++ html/js/base_ts.js | 473 ++++ html/js/classes_ts.js | 40 + html/js/configuaration.js | 294 +++ html/js/configuration_ts.js | 140 ++ html/js/data.js | 329 +++ html/js/files.js | 379 +++ html/js/log.js | 114 + html/js/logs_ts.js | 42 + html/js/mapping-editor.js | 1466 +++++++++++ html/js/menu.js | 754 ++++++ html/js/menu_ts.js | 1747 +++++++++++++ html/js/network_ts.js | 105 + html/js/settings_ts.js | 442 ++++ html/js/users.js | 341 +++ html/lang/en.json | 419 ++++ html/login.html | 46 + html/maintenance.html | 30 + html/video/stream-limit.ts | Bin 0 -> 23876 bytes src/authentication.go | 169 ++ src/backup.go | 191 ++ src/buffer.go | 1405 +++++++++++ src/compression.go | 148 ++ src/config.go | 242 ++ src/data.go | 953 +++++++ src/hdhr.go | 236 ++ src/html-build.go | 147 ++ src/images.go | 153 ++ src/internal/authentication/authentication.go | 592 +++++ src/internal/m3u-parser/m3u-parser_test.go | 84 + src/internal/m3u-parser/test_list_1.m3u | 7 + src/internal/m3u-parser/xteve_m3uParser.go | 267 ++ src/internal/up2date/client/client.go | 129 + src/internal/up2date/client/update.go | 271 ++ src/m3u.go | 238 ++ src/maintenance.go | 84 + src/provider.go | 323 +++ src/screen.go | 404 +++ src/ssdp.go | 69 + src/struct-buffer.go | 109 + src/struct-hdhr.go | 61 + src/struct-system.go | 280 +++ src/struct-webserver.go | 145 ++ src/struct-xml.go | 123 + src/system.go | 323 +++ src/toolchain.go | 356 +++ src/update.go | 273 ++ src/webUI.go | 54 + src/webserver.go | 1050 ++++++++ src/xepg.go | 967 ++++++++ ts/authentication_ts.ts | 47 + ts/base_ts.ts | 663 +++++ ts/compileJS.sh | 3 + ts/configuration_ts.ts | 169 ++ ts/logs_ts.ts | 65 + ts/menu_ts.ts | 2198 +++++++++++++++++ ts/network_ts.ts | 147 ++ ts/settings_ts.ts | 564 +++++ xteve.go | 171 ++ 85 files changed, 22786 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 README-DEV.md create mode 100644 html/configuration.html create mode 100644 html/create-first-user.html create mode 100644 html/css/base.css create mode 100644 html/css/screen.css create mode 100644 html/css/screen2.css create mode 100644 html/img/BC-QR.jpg create mode 100644 html/img/filter.png create mode 100644 html/img/log.png create mode 100644 html/img/logo_w_600x200.png create mode 100644 html/img/logout.png create mode 100644 html/img/m3u.png create mode 100644 html/img/mapping.png create mode 100644 html/img/settings.png create mode 100644 html/img/stream-limit.jpg create mode 100644 html/img/users.png create mode 100644 html/img/x_ transparent.png create mode 100644 html/img/x_black.png create mode 100644 html/img/x_white.png create mode 100644 html/img/xmltv.png create mode 100644 html/index.html create mode 100644 html/js/authentication.js create mode 100644 html/js/authentication_ts.js create mode 100644 html/js/base.js create mode 100644 html/js/base_ts.js create mode 100644 html/js/classes_ts.js create mode 100644 html/js/configuaration.js create mode 100644 html/js/configuration_ts.js create mode 100644 html/js/data.js create mode 100644 html/js/files.js create mode 100644 html/js/log.js create mode 100644 html/js/logs_ts.js create mode 100644 html/js/mapping-editor.js create mode 100644 html/js/menu.js create mode 100644 html/js/menu_ts.js create mode 100644 html/js/network_ts.js create mode 100644 html/js/settings_ts.js create mode 100644 html/js/users.js create mode 100644 html/lang/en.json create mode 100644 html/login.html create mode 100644 html/maintenance.html create mode 100644 html/video/stream-limit.ts create mode 100644 src/authentication.go create mode 100644 src/backup.go create mode 100644 src/buffer.go create mode 100644 src/compression.go create mode 100644 src/config.go create mode 100644 src/data.go create mode 100644 src/hdhr.go create mode 100644 src/html-build.go create mode 100644 src/images.go create mode 100755 src/internal/authentication/authentication.go create mode 100644 src/internal/m3u-parser/m3u-parser_test.go create mode 100644 src/internal/m3u-parser/test_list_1.m3u create mode 100755 src/internal/m3u-parser/xteve_m3uParser.go create mode 100755 src/internal/up2date/client/client.go create mode 100755 src/internal/up2date/client/update.go create mode 100644 src/m3u.go create mode 100644 src/maintenance.go create mode 100644 src/provider.go create mode 100644 src/screen.go create mode 100644 src/ssdp.go create mode 100644 src/struct-buffer.go create mode 100644 src/struct-hdhr.go create mode 100644 src/struct-system.go create mode 100644 src/struct-webserver.go create mode 100644 src/struct-xml.go create mode 100644 src/system.go create mode 100644 src/toolchain.go create mode 100644 src/update.go create mode 100644 src/webUI.go create mode 100644 src/webserver.go create mode 100644 src/xepg.go create mode 100644 ts/authentication_ts.ts create mode 100644 ts/base_ts.ts create mode 100755 ts/compileJS.sh create mode 100644 ts/configuration_ts.ts create mode 100644 ts/logs_ts.ts create mode 100644 ts/menu_ts.ts create mode 100644 ts/network_ts.ts create mode 100644 ts/settings_ts.ts create mode 100644 xteve.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..979f7ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +compiler +files +update_xteve*.sh +xteve +xteve.exe +de.json \ No newline at end of file diff --git a/LICENSE b/LICENSE index 2c72196..622e181 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 xteve-project +Copyright (c) 2019 marmei@xteve-project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README-DEV.md b/README-DEV.md new file mode 100644 index 0000000..4882217 --- /dev/null +++ b/README-DEV.md @@ -0,0 +1 @@ +# Information for the developers will come soon \ No newline at end of file diff --git a/README.md b/README.md index 6ee1cfa..dde85af 100644 --- a/README.md +++ b/README.md @@ -1 +1,127 @@ -# xTeVe \ No newline at end of file +
+ xTeVe +
+
+ +# xTeVe +## M3U Proxy for Plex DVR and Emby Live TV. + +Documentation for setup and configuration is [here](https://github.com/xteve-project/xTeVe-Documentation/blob/master/en/configuration.md). + +#### Donation +* **Bitcoin:** 1c1iCe4CJPfNUXtqxKBbW2Qd2EtqRPWme +![Bitcoin](html/img/BC-QR.jpg "Bitcoin - xTeVe") + +## Requirements +### Plex +* Plex Media Server (1.11.1.4730 or newer) +* Plex Client with DVR support +* Plex Pass + +### Emby +* Emby Server (3.5.3.0 or newer) +* Emby Client with Live-TV support +* Emby Premiere + +--- + +## Features + +#### Files +* Merge external M3U files +* Merge external XMLTV files +* Automatic M3U and XMLTV update +* M3U und XMLTV export + +#### Channel management +* Filtering streams +* Channel mapping +* Channel order +* Channel logos +* Channel categories + +#### Streaming +* Buffer with HLS / M3U8 support +* Re-streaming +* Number of tuners adjustable +* Compatible with Plex / Emby EPG + +--- + +## Downloads v2 | 64 Bit only +#### 64 Bit Intel / AMD + +* [Windows](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_windows_amd64.zip?raw=true) +* [OS X](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_darwin_amd64.zip?raw=true) +* [Linux](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_linux_amd64.zip?raw=true) +* [FreeBSD](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_freebsd_amd64.zip?raw=true) + +#### 64 Bit ARM +* [Linux](https://github.com/xteve-project/xTeVe-Downloads/blob/master/xteve_linux_arm64.zip?raw=true) + +#### Recommended Docker Image (Linux 64 Bit) +Thanks to @alturismo and @LeeD for creating the Docker Images. + +**Created by alturismo:** +[xTeVe](https://hub.docker.com/r/alturismo/xteve) +[xTeVe / Guide2go](https://hub.docker.com/r/alturismo/xteve_guide2go) + +Including: +- Guide2go: XMLTV grabber for Schedules Direct + + +**Created by LeeD:** +[xTeVe / Guide2go / Zap2XML](https://hub.docker.com/r/dnsforge/xteve) + +Including: +- Guide2go: XMLTV grabber for Schedules Direct +- Zap2XML: Perl based zap2it XMLTV grabber +- Bash: A Unix / Linux shell +- Crond: Daemon to execute scheduled commands +- Perl: Programming language + +--- + +## Build from source code [Go / Golang] + +#### Requirements +* Go (go1.12.4 or newer) + +#### Dependancys +* [go-ssdp](https://github.com/koron/go-ssdp) +* [websocket](https://github.com/gorilla/websocket) +* [osext](https://github.com/kardianos/osext) + +#### Build +1. Download source code +2. Install dependancys +``` +go get github.com/koron/go-ssdp +go get github.com/gorilla/websocket +go get github.com/kardianos/osext +``` +3. Build xTeVe +``` +go build xteve.go +``` + +--- + +## Fork without pull request :mega: +When creating a fork, the xTeVe GitHub account must be changed from the sorce code or the update function disabled. +Future updates of the xteve-project would update your fork. :wink: + +xteve.go - Line: 29 +```Go +var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-Downloads", Update: true} + +/* + Branch: GitHub Branch + User: GitHub Username + Repo: GitHub Repository + Update: Automatic updates from the GitHub repository [true|false] +*/ + +``` + + diff --git a/html/configuration.html b/html/configuration.html new file mode 100644 index 0000000..c8bea21 --- /dev/null +++ b/html/configuration.html @@ -0,0 +1,58 @@ + + + + + + xTeVe + + + + + + + + + + + +
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + +
Version: OS: 
UUID: Arch: 
Streams: DVR: 
+ +
+

Configuration

+
+

+
+ +
+ +
+ + \ No newline at end of file diff --git a/html/create-first-user.html b/html/create-first-user.html new file mode 100644 index 0000000..ee10a2b --- /dev/null +++ b/html/create-first-user.html @@ -0,0 +1,47 @@ + + + + + + xTeVe + + + + + + + + + + +
+ +
+

{{.account.headline}}

+
+ +

+ +
+ +
+ +
{{.account.username.title}}:
+ +
{{.account.password.title}}:
+ +
{{.account.confirm.title}}:
+ + +
+ +
+ + + + +
+ + \ No newline at end of file diff --git a/html/css/base.css b/html/css/base.css new file mode 100644 index 0000000..8fb9390 --- /dev/null +++ b/html/css/base.css @@ -0,0 +1,448 @@ +* { + -webkit-appearance: none; + -moz-appearance: none; + -ms-appearance: none; + font-family: "Arial", sans-serif; + letter-spacing: 2px; +} + +/* +::-webkit-scrollbar { + display: none; +} +*/ + +::-webkit-scrollbar { + width: 12px; + height: 12px; +} + + +::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); + border-radius: 5px; + +} + +::-webkit-scrollbar-thumb { + border-radius: 5px; + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0,0.6); + background-color: #444; +} + +::-webkit-scrollbar-thumb:hover { + background: #333; +} + +::-webkit-scrollbar-corner { + background: transparent; +} + +a { + color: #00E6FF; +} + +html, body { + color: #fff; + margin: 0px auto; + height: 100%; + font-size: 14px; +} + +h2 { + font-size: 24px; + letter-spacing: 2px; +} + +h3 { + font-size: 22px; + letter-spacing: 1px; +} + +h4 { + font-size: 20px; + letter-spacing: 1px; + line-height: 1.5em; + +} + +h5 { + font-size: 16px; + letter-spacing: 1px; + line-height: 1.2em; + margin: 25px 0px 10px 0px; +} + +hr { + border: 0; + height: 1px; + background: #333; + margin: 10px 0px; +} + +p { + margin: 2px; + padding: 2px 5px; +} + +pre { + margin: 0px 0px 5px 0px; + font-size: 12px; + color: #ddd; + letter-spacing: 1px; + white-space: pre-wrap; + font-family: monospace; + font-size: 12px; + font-style: normal; + font-variant: normal; + line-height: 1.6em; +} + +label { + margin-bottom: 20px; + display: block; +} + +li { + list-style-type: none; + background-color: #111; + padding: 10px 20px; + cursor: pointer; + border-left: solid 2px #111; + transition: all 0.3; +} + +li:hover { + border-color: #00E6FF +} + +select { + cursor: pointer; + width: calc(100% + 2px); + border: solid 0px #00E6FF; + border-radius: 0px; + outline: none; + color: #fff; + padding: 9px 10px; + display:block; + background-color: #333; + font-size: 14px; + margin: 5px 0px 5px 0px; +} + +select:focus { + outline: none; +} + +input { + -webkit-appearance: none; + margin: 5px 0px; + padding: 2.5px 10px; + outline: none; + font-size: 14px; +} + +input[type=button], input[type=submit] { + cursor: pointer; + background-color: #000; + margin: 10px 10px; + padding: 10px 25px; + border: solid 0px; + border-color: #000; + border-radius: 3px; + outline: none; + color: #fff; +} + +input[type=button]:focus { + outline: none; +} + +input[type=button]:hover { + background-color: #00E6FF; + color: #000; +} + +input[type=button]:hover.delete { + background-color: red; + color: #fff; +} + +input[type=text], input[type=search], input[type=password] { + color: #fff; + width: -webkit-calc(100% - 0px); + width: -moz-calc(100% - 0px); + width: calc(100% - 0px); + outline: none; + border: solid 1px transparent; + background-color: transparent; + border-bottom-color: #555; + border-radius: 0px; + padding: 8px 10px; +} + +input[type="checkbox"] { + border: solid 1px #00E6FF; + background-color: #333; + height: 25px; + width: 25px; + cursor: pointer; + /* + -webkit-appearance: checkbox; + */ +} + +input[type="checkbox"]:checked { + color: #fff; + background-color: #00E6FF; + /*display: inline-block;*/ +} + +input[type="checkbox"]:before { + position: initial; + left: 0px; + margin-left: -4px; + content: " "; +} + +input[type="checkbox"]:checked:before { + position: initial; + left: 0px; + margin-left: -3px; + content: "✓"; + color: #000; +} + + +input[type=button].cancel { + + background-color: transparent; + border-color: red; +} + +input[type=button].save{ + background-color: #111; + float: right; +} + + +input[type=button].black, input[type=submit].black{ + background-color: #000; + border-color: #000; +} + +input[type=button].center{ + margin-right: auto; + margin-left: auto; + background-color: #000; + border-color: #000; +} + +.pointer { + cursor: pointer; +} + +.pointer:hover { + color: #00E6FF; + cursor: pointer; +} + +.sortThis { + color: #00E6FF; +} + +.w40px { + max-width: 40px; +} + +.w50px { + max-width: 50px; +} + +.w80px { + max-width: 80px; +} + +.w150px { + max-width: 150px; +} + +.w200px { + max-width: 200px; + min-width: 100px; + width: 200px; + overflow-x: hidden; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.w300px { + max-width: 300px; +} + +.w220px { + max-width: 220px; + cursor: alias; +} + +.footer { + font-size: 10px; +} + +.center { + text-align: center; +} + +.screenLogHidden { + transform: translate(0px, -110px); +} + +.borderSpace { + margin-bottom: 30px; +} + +.block { + +} + +.none { + display: none; +} + + +.notVisible { + height: 0px; + display: none; + opacity: 0; + border-bottom: #000 solid 0px; + +} + +.visible { + opacity: 1; + display: block; + border-bottom: #444 solid 1px; + padding: 10px; +} + +.floatRight { + float: right; +} + +.floatLeft { + float: left; +} + +.menu-active { + background-color: #00E6FF; +} + +.menu-notActive { + +} + +#branch { + display: table; + margin: auto; + color: red; +} + +#interaction { + margin-bottom: 100px; + text-align: center; + border-bottom: solid 0px #777; +} + + +.half { + display: block; + width: 45%; +} + +.menu { + border: solid 1px #00E6FF; +} + +.infoMsg { + color: #aaa; +} + +.errorMsg { + color: red; +} + +.warningMsg { + color: yellow; +} + +.debugMsg { + color: magenta; +} + +.News, .Movie, .Series, .Sports, .Kids { + border-left: solid 2px +} + +.News { + border-color: tomato +} + +.Movie { + border-color: royalblue; +} + +.Series { + border-color: gold; +} + +.Sports { + border-color: yellowgreen; +} + +.Kids { + border-color: mediumpurple; +} + +/* Loading */ +#loading { + left: 0px; + top: 0px; + z-index: 10000; + position: absolute; + background-color: rgba(0,0,0, 0.8); + margin: auto; + width: 100%; + height: 100%; +} + + +.loader { + border: 5px solid transparent; + border-radius: 50%; + border-top: 5px solid #00E6FF; + border-bottom: 5px solid #00E6FF; + width: 50px; + height: 50px; + -webkit-animation: spin 1.2s linear infinite; + animation: spin 1.2s linear infinite; + + position: fixed; + margin: auto; + + top: 0; + right: 0; + bottom: 0; + left: 0; + +} + +@-webkit-keyframes spin { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); } +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/html/css/screen.css b/html/css/screen.css new file mode 100644 index 0000000..dde71f3 --- /dev/null +++ b/html/css/screen.css @@ -0,0 +1,540 @@ +nav img { + display: block; + max-height: 20px; + max-width: 20px; + float: left; +} + +nav p { + text-align: left; + padding: 0px 30px; +} + +#layout { + display: block; + height: 100%; +} + + +.layout-left { + display: block; + min-width: 150px; + max-width: 20%; + background-color: #111; + height: inherit; + float: left; +} + +.layout-right { + display: block; + background-color: #444; +} + +#menu-wrapper { + height: 100%; +} + + +#logo { + display: block; + min-width: 180px; + width: 100%; + height: 100px; + background: url("../img/logo_w_600x200.png"); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; +} + + +#page { + max-width: 950px; + margin: auto; + background-color: #444; + + /* + height: -webkit-calc(100% - 130px); + height: -moz-calc(100% - 130px); + height: calc(100% - 130px); + */ + + min-height: -webkit-calc(100% - 120px); + min-height: -moz-calc(100% - 120px); + min-height: calc(100% - 120px); + + + box-shadow: 0px 5px 5px #222; + +} + +#uiSetting { + float: right; + margin-right: 25px; +} + +#box input[type=text], #box input[type=password] { + width: -webkit-calc(100% - 20px); + width: -moz-calc(100% - 20px); + width: calc(100% - 20px); +} + +#box input[type=submit]{ + margin: 50px auto; +} + +#settings { + display: block; + padding: 10px 10px; +} + +#settings h5 { + margin: 50px 0px 10px 0px; +} + +#content-interaction .search { + width: 200px; + border: 1px solid #000; + padding: 9px; + background-color: #333; + margin: 10px; + float: right; + border-radius: 3px; + +} + +#myStreams { + position: fixed; + bottom: 0px; + background-color: #111; + width: 100%; + max-width: 950px; + + /* + max-height: 100px; + */ + margin-bottom: 0px; +} + +#myStreams img { + width: 4%; + padding: 2px 5px; + cursor: pointer; + float: right; +} + +#settings-footer { + +} + + +/* Wizard*/ +#box { + background-color: #444; + min-height: 400px; + + display: flex; + flex-direction: column; + justify-content: space-between; +} + +#box p{ + padding: 10px 0px; +} + +#box-footer { + margin-top: auto; +} + +#box-footer { + margin: auto; + padding: 10px; +} + +#headline { + background-color: #222; + border-bottom: solid 2px #222; + transition: all 0.5s; + padding: 10px 0px; + display: block; +} + +#content { + display: block; + overflow: auto; + padding: 10px; +} + +/* --- */ + + +#clientInfo, #activeStreams, #inactiveStreams { + font-family: monospace; + display: block; + font-size: 9px; + background-color: #111; + color: #00E6FF; + border-bottom: solid 0px;; + padding: 0px; + letter-spacing: 1px; + overflow-x: hidden; + border-spacing: 4px 4px; + border-bottom: solid 1px #444; +} + +#myStreamsBox { + position: relative; + padding: 0px; + /*height: 100px;*/ + max-height: 150px; + background-color: #111; + color: white; + display:flex; + justify-content:center; + align-items:center; +} + +#openStreams { + width: 20px; + height: 20px; + cursor: pointer; + float: right; + position: absolute; + right: 0px; + bottom: 0px; + background: url("../img/touch.png"); + background-color: #111; + + background-position: bottom right; +} + +#allStreams { + width: 100%; + height: 100%; + padding: 2px; +} + +#activeStreams, #inactiveStreams { + overflow-y: scroll; + width: 50%; + max-height: 100px; + float: left; +} + +#activeStreams .tdKey, #inactiveStreams .tdKey { + width: 75px; +} + + + + +#inactiveStreams .tdKey { + color: red; +} + +#clientInfo .tdVal, #logInfo .tdVal, #activeStreams .tdVal, #inactiveStreams .tdVal, #mappingInfo .tdVal{ + color: #aaa; + white-space: inherit; +} + +#box-wrapper { + display: inline-block; + width: 100%; + + overflow-y: scroll; +} + +#content_table, #mapping-detail-table, #content_table { + display: table; + + border-collapse: collapse; + overflow-y: scroll; + width: 100%; +} + + +#content_table .content_table_header { + background-color: #333; + height: 50px; + border-bottom: solid 1px #111; + border-left: solid 3px #333; + cursor: auto; + +} + + +tbody { + width: 100%; +} + + +.tableEllipsis { + width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#content_table img { + display: block; + max-height: 28px; + margin-left: auto; + margin-right: auto; + max-width: 30px; +} + +#content_table tr{ + border-left: solid 3px 444; + border-bottom: solid 1px #333; + cursor: pointer; +} + +#content_table tr:hover { + background-color: #333; +} + +#content_table td { + + padding: 0px 2px; +} + +#content_table input[type=text]{ + width: 80%; + border: 0px; + background-color: #333; + margin-left: 5px; + text-align: left; +} + +#content_table input[type=checkbox]{ + max-width: 25px; + margin: auto; +} + + +.showBulk { + display: block; +} + +.hideBulk { + display: none; +} + +.noBulk { + +} + +#content_table tr.activeEPG{ + border-left: solid 3px lawngreen; +} + +#content_table tr.notActiveEPG{ + border-left: solid 3px red; +} + + +#logScreen p{ + white-space: pre; + font-size: 10px; + /* + line-height: 1.6em; + font-family: "Arial", sans-serif; + */ + letter-spacing: 1px; + font-family: monospace; + font-size: 12px; + font-style: normal; + font-variant: normal; + line-height: 1.6em; +} + +#popup { + background-color: rgba(0, 0, 0, 0.4); + position: fixed; + left: 0px; + width: 100%; + z-index: 100; + height: 100%; +} + +#mapping-detail, #user-detail, #file-detail, #popup-custom { + box-shadow: 0px 5px 40px #000; + margin-top: 20px; + margin-left: auto; + margin-right: auto; + + max-width: 600px; + background-color: #222; + padding: 10px; + overflow:auto; +} + +#popup-custom h3 { + text-align: center; +} + +#file-detail input[type=text] { + width: -webkit-calc(100% - 20px); + width: -moz-calc(100% - 20px); + width: calc(100% - 20px); +} + +#mapping-detail img { + display: block; + max-height: 30px; + margin-bottom: 20px; + margin-left: auto; + margin-right: auto; +} + +#popup-custom input[type=text], #popup-custom input[type=password], #mapping-detail input[type=text], #content_settings input[type=text], #content_settings input[type=password]{ + border: solid 1px; + border-color: transparent; + background-color: #333; + text-align: left; + width: -webkit-calc(100% - 20px); + width: -moz-calc(100% - 20px); + width: calc(100% - 20px); +} + +#popup-custom input[type=text].notAvailable { + border-color: red; + color: #666; + cursor: not-allowed; +} + +#mapping-detail-table, #user-detail-table { + display: inline-table; + width: 100%; +} + +#popup-custom table, #content_settings table { + display: inline-table; + table-layout: fixed; + width: 100%; +} + + +#mapping-detail-table td, #user-detail-table td { + padding: 10px 0px; + +} + +#mapping-detail-table td.left, #user-detail-table td.left, #popup-custom td.left { + width: 38%; +} + +.interaction, #interaction { + margin-top: 20px; + display: inline-flex; + float: right; +} + +.interaction input[type=button], .interaction input[type=submit] { + background-color: #000; + min-width: 100px; + margin: 0px 10px; + text-align: center; +} + +#notification { + display: block; + position: fixed; + right: 0px; + height: 100%; + width: 250px; + + background-color: #222; + box-shadow: 0px 0px 20px #000; +} + +#notification h5 { + background-color: #121212; + padding: 5px 10px 5px 10px; +} + +#notification pre { + padding: 0px 10px 0px 10px; +} + +#notification p { + font-size: 10 px; + margin: 0px; + padding: 0px 10px 5px 10px; +} + +#notification .element { + /*padding: 0px 5px;*/ + margin: 5px 5px; + border-radius: 5px; + background-color: #181818; + border-left: 10px solid green; +} + + +@media only screen and (min-width: 620px){ + body { + width: 100%; + background-color: #444; + } + + h1 { + font-size: 26px; + letter-spacing: 3px; + } + + nav p { + display: block; + } + + + + #header_config { + display: block; + height: 100px; + background: url("../img/logo_w_600x200.png"); + background-repeat: no-repeat; + + background-size: 300px 100px; + } + + #screenLog { + margin-left: 300px; + + transition: none; + background-color: transparent; + border-bottom: solid 1px transparent; + box-shadow: 0px 0px 0px #222; + } + + #settings { + /* + height: -webkit-calc(100% - 100px); + height: -moz-calc(100% - 100px); + height: calc(100% - 100px); + */ + position: relative; + overflow: auto; + } + + + .screenLogHidden { + transform: translate(0px, 0px); + } + + + #box { + display: block; + min-height: 500px; + max-width: 500px; + margin: 10px auto; + background-color: #444; + box-shadow: 0px 5px 5px #222; + + display: flex; + flex-direction: column; + } + + #settings, #settings-footer { + + } +} diff --git a/html/css/screen2.css b/html/css/screen2.css new file mode 100644 index 0000000..2e774d9 --- /dev/null +++ b/html/css/screen2.css @@ -0,0 +1,3 @@ +h1 { + color: green; +} \ No newline at end of file diff --git a/html/img/BC-QR.jpg b/html/img/BC-QR.jpg new file mode 100644 index 0000000000000000000000000000000000000000..25d79aa20fd01545aaf3e7326a60351cd329cdd5 GIT binary patch literal 29877 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 a>wVwc@FV|4s{Z%ir|6`5{rkV&^S=PHnA6q( literal 0 HcmV?d00001 diff --git a/html/img/filter.png b/html/img/filter.png new file mode 100644 index 0000000000000000000000000000000000000000..ee4b93634a2c5e7738573a0609403d1686a18d0b GIT binary patch literal 2067 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 Jmvv4FO#pK*`B(q| literal 0 HcmV?d00001 diff --git a/html/img/logo_w_600x200.png b/html/img/logo_w_600x200.png new file mode 100644 index 0000000000000000000000000000000000000000..288577e3e096da7a4228c81d545d96b0a1f67e85 GIT binary patch literal 11183 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 literal 0 HcmV?d00001 diff --git a/html/img/logout.png b/html/img/logout.png new file mode 100644 index 0000000000000000000000000000000000000000..7649a30ee9d2356e08e35a6ed3f0e919610b93bd GIT binary patch literal 2011 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}oCO|{#S9EO z-XP4l)OOlR1_llpi<;HsXMd|v6mX?+mni1bQ_Qv zeo=5iVsa|TWne8xLg=c&) zKgSO2i(C*_!C222q7zvjs?|mxl#h_|6(rMwMS;22jti(0Rsz^@@$Pnf$iToH;OXKR zlEM0Rrfr6ZqeNS=r%3O|_6)9xDop}SE7oQ_;`i7f&}5dR-l3or!I7nWaLcU~9g7w; zMR5oT9`q6jzOghwYrm#nj?@;)J3~q;M^I2T-8<#JN>;5C*_@(H@^&_u$ zKa_UNW&Uz-#kxB;_-}J0l^>d*Xthpqnc$RljqHV8!oLm~HJwni;NCCHqF&mb$WmIm zLEcVXbL*Gwzy5xkswmwk{cxh|inj${dlsx=&ATCDCAWT$V9VLY*DS z+5J%HPh-GiA9}9vyP$e~!)D3IX>yH%9ENeqS&f&~l|5CqA9^b=cfreD zn+2zySRhv+zF?u>(uAKg8YM77jKWOxV zA+!8&&4hI-afh)Psh!>bKcDjWys@>o@JgxKIsy{y4L}s(PA1wWtYN~W- zM!sy1b5PCsBMp|ES8qM=3kqF(|D;EX+cmAFO0!LSGS#&9%{}F;y6U$?X3D(ps?V}| zE>DP*Z+&`CxqHcu>i*3WUmTg^ZD2HMX2NY3jtE|UUd4TGPwqO#?AdVj{1*$ZC-;=K zFX&H{idXKI5dUuWxo(+Lth#g0o$7TBwG&f(PR*E}a4b_<_m3K%+-$v!?;1}xT{-LB zBjt2FlYhlN8_B>~SIZ`_yH$SpBVK8=$*Hq%+xDKaAHM5Yf6BY@SNdLAqJ93f`lc}P zv;R!@g&lT_|KWRXpN@FQ3E_{IK7>kh3EpM+_4rSovZ}nVj|`ir_~(VU0y?WInO9A_ zVt(`dX;a&)oQGWZwAi>lzjj3N$m!o(a)aL<-M(i1>&NX=w~K$Ay~2P^_RpDF{c2Bj dp=fS?83{1OQFb@Bsh- literal 0 HcmV?d00001 diff --git a/html/img/m3u.png b/html/img/m3u.png new file mode 100644 index 0000000000000000000000000000000000000000..1d63bb73ede987bfebc6464c8a1d1f1cd5900e37 GIT binary patch literal 1569 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}oCO|{#S9EO z-XP4l)OOlR1_llpi<;HsXMd|v6mX?+mni1bQ_Qv zeo=5iVsa|TWne8xLg=c&)#Z4lGcndAc}; zWU#)CGW25#6iGRnk<=(wcBFtIIp6&e>kExHwhgSE%RjJqxhDELuP}Vb5+Ir6xv+O? zYvJ7K8=kapcyqk+&3)DP>XZMz**Eul+Ou{xt*J-59#?QZ**kB}zfzO`tnVgQ-;Uv* z^GG;dK1+JxN97|NDew2jDBjb58lbUqkHeXb`Rf;PP5gQLfI!l@njveFAu()b-goD)-js6Q>Xr6Iy z)r`Yer5Ww(7nPshZhz#;B8^{Oueg<2oNJR;)EV8_c72oV+K}4Q?+abN_N{WudYUI! z(@?f;LErz&OrCM4s~7fPvrH4Xke%N*jdS7oCZk5xML8P2>75tEw1gv+PPJVsnZ}X3 zkYeFHrQ%t-^-WLDtef!s&+`S=W!H@!uwS%JVEV)08vE(_7V`&;E7Y9+F?gLyzpj;2 RcK}pwd%F6$taD0e0svAD7)t;E literal 0 HcmV?d00001 diff --git a/html/img/mapping.png b/html/img/mapping.png new file mode 100644 index 0000000000000000000000000000000000000000..c5d5deefb457b0e73ec2ce02e5b1860f10b3ef92 GIT binary patch literal 1750 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 S@GSlpi<;HsXMd|v6mX?+mni1bQ_Qv zeo=5iVsa|TWne8xLg=c&)}eUV}lSwb1MT2D`Q|Rps7J}0Z7KVC^fMpzbGU> zKgSO2i(C*_!C222q7zvjs?|mxl#h_|6(rMwMS;22jti(0Rsz^@nVMWV%)r3x>FMGa zlEHd)=ILzVK#^m{w{~TSZr$ght<<_Gz~#uVH9s6;xONpkJk+Y8=MrAJP&)Pp|BEOQ z^Fu zpQ!8#TDfKRwdDunCa}+aApL{OuDLzIf}<(YZnm*Oq3DexCZ5wBFPQiVTxRcUGIU`f%$IN z&Ccj-RJ|Z2xm~s3#P4(Zk1cOkt(}<2QFm*S#^-sSuR`1`g7+&{Dkug$xi{g;ik=V- zE+M7Fch!E^vORmdCq5TA>v4i*XL3`__mFv7%@4#qjNLM~U5lN&fq98?=B@?T>lU^Y z?K-_y^iyw(v}Vn}g~yjH^OtgybGBKpxIWPM-1I#W$$K77*euwwJfZU9gp+y?x5ds{ zuJbP2xcZTle8z=mo{MUuUG@}~6y2G3Hd#%LkLm4{3%322o~$)2iY*Ma3%snF{yCy? zf!PO_glFtK|H`Spj8=Q9#rfT~1c`J8a`uQ>XuHN+3d)U<`JvRN;{bb)N!*x<# zcRgHN&)VislMLdHciW{avaL9~Q~lAauU}Jv<>jFx66dqwdj>UYRUI zVYj?5?>FVN+|H70-m9CjME#0P+{7A-3rX{}o%b%B_(uJhlk2AQ8NDCvw;5M*u^&xW P0adl0u6{1-oD!M;a> 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 literal 0 HcmV?d00001 diff --git a/html/img/x_black.png b/html/img/x_black.png new file mode 100644 index 0000000000000000000000000000000000000000..bee463642b6bccf393db202fa9b2fa7e3afac003 GIT binary patch literal 7132 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 literal 0 HcmV?d00001 diff --git a/html/img/x_white.png b/html/img/x_white.png new file mode 100644 index 0000000000000000000000000000000000000000..729bc38d8bad3cf786bcf48e6cccf15ae9dd52ee GIT binary patch literal 6732 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 WVd)FN*}Xr0#iOffpi!Y_7y4h@m-#UO literal 0 HcmV?d00001 diff --git a/html/img/xmltv.png b/html/img/xmltv.png new file mode 100644 index 0000000000000000000000000000000000000000..9dd3019a7d3e3d9357941ec355d178d36fee892e GIT binary patch literal 1579 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}oCO|{#S9EO z-XP4l)OOlR1_llpi<;HsXMd|v6mX?+mni1bQ_Qv zeo=5iVsa|TWne8xLg=c&)l~JeT%>D1{Np_JY5_^ zGFab6ZS*_rz_V8&AyH%gfpdqB9bl+uOiD_Bd5mNJfkXFX?H1{Hc!VS+HgxJc7#JLI zs90BUYgg#27fX(m-mIz;RHY@wwDK|miO`Zvh ztajUPzMlQ&{F|>|Jym9%jXf>A@mpuH>l)2Tyz=kg)CXnli%q}!cjjE}K=WU*tJhh@ z9$BI!A3LGLV)G+y;mSkH<_k)1|9h?4u0T!j&xRkp2PD~h`^_I%|4SG6ILEU^OkmHU q0v7p$nugwCO=r0OFxBy_e + + + + + xTeVe + + + + + + + + + + + + + +
+
+
+ + + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
xTeVe: OS: DVR IP: 
UUID: Arch: M3U URL: 
Available Streams: EPG Source: XEPG URL: 
XEPG Channels: Errors: Warnings: 
+ +
+ +
+
+
+
+ +
+ +
+ +
+ +
+ + + + \ No newline at end of file diff --git a/html/js/authentication.js b/html/js/authentication.js new file mode 100644 index 0000000..581700b --- /dev/null +++ b/html/js/authentication.js @@ -0,0 +1,42 @@ +function createFirstAccount(elm) { + var err = false; + var div = document.getElementById(elm); + console.log(div); + + var form = document.getElementById('authentication'); + + const username = document.getElementById('username'); + const password = document.getElementById('password'); + const confirm = document.getElementById('confirm'); + + var inputs = div.getElementsByTagName('INPUT') + console.log(confirm); + + switch(confirm) { + case null: break; + + default: + for (var i = 0; i < inputs.length; i++) { + if (inputs[i].value.length == 0) { + inputs[i].style.borderColor = 'red'; + err = true + } + } + + switch(err) { + case true: return; break; + case false: + if (password.value != confirm.value) { + confirm.style.borderColor = 'red'; + return; + } + break; + } + } + + + + + form.submit(); + return; +} \ No newline at end of file diff --git a/html/js/authentication_ts.js b/html/js/authentication_ts.js new file mode 100644 index 0000000..f708119 --- /dev/null +++ b/html/js/authentication_ts.js @@ -0,0 +1,32 @@ +function login() { + var err = false; + var data = new Object(); + var div = document.getElementById("content"); + var form = document.getElementById("authentication"); + var inputs = div.getElementsByTagName("INPUT"); + console.log(inputs); + for (var i = inputs.length - 1; i >= 0; i--) { + var key = inputs[i].name; + var value = inputs[i].value; + if (value.length == 0) { + inputs[i].style.borderColor = "red"; + err = true; + } + data[key] = value; + } + if (err == true) { + data = new Object(); + return; + } + if (data.hasOwnProperty("confirm")) { + if (data["confirm"] != data["password"]) { + alert("sdafsd"); + document.getElementById('password').style.borderColor = "red"; + document.getElementById('confirm').style.borderColor = "red"; + document.getElementById("err").innerHTML = "{{.account.failed}}"; + return; + } + } + console.log(data); + form.submit(); +} diff --git a/html/js/base.js b/html/js/base.js new file mode 100644 index 0000000..ebc2f66 --- /dev/null +++ b/html/js/base.js @@ -0,0 +1,331 @@ +var config = new Object(); +var menu = new Object(); +var subMenu = new Object(); +var activeStreams = new Object(); +var xEPG = new Object(); +var users = new Object(); +var log = new Object(); +var undo = new Object(); +var webSockets = true; +var closeLog, version, activeMenu; +var columnToSort = 0 + + +if (window.WebSocket === undefined) { + alert("Your browser does not support WebSockets"); + webSockets = false; +} + +function pageReady() { + var data = new Object(); + data["cmd"] = "getServerConfig"; + xTeVe(data); + //showLoadingScreen(false); + + var resizeHandle = document.getElementById("openStreams"); + var box = document.getElementById("myStreamsBox"); + resizeHandle.addEventListener("mousedown", initialiseResize, false); + + function initialiseResize(e) { + window.addEventListener("mousemove", startResizing, false); + window.addEventListener("mouseup", stopResizing, false); + } + + function startResizing(e) { + box.style.height = (e.clientY - box.offsetTop) + "px"; + + var elm = document.getElementById("allStreams"); + if (e.clientY > 120) { + elm.className = "visible"; + } else { + elm.className = "notVisible"; + } + + calculateWrapperHeight(); + + } + function stopResizing(e) { + window.removeEventListener('mousemove', startResizing, false); + window.removeEventListener('mouseup', stopResizing, false); + calculateWrapperHeight(); + } + + window.addEventListener("resize", function(){ + calculateWrapperHeight(); + }, true); +} + + +function getObjKeys(obj) { + var keys = new Array(); + + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + keys.push(i); + } + } + + return keys; +} + + +function createElement(item) { + //console.log(item); + var element = document.createElement(item["_element"]); + if (item.hasOwnProperty("_text")) { + //element.innerHTML = "

" + item["_text"] + "

"; + element.innerHTML = item["_text"]; + } + + var keys = getObjKeys(item); + for (var i = 0; i < keys.length; i++) { + if (keys[i].charAt(0) != "_") { + //console.log(keys[i], item[keys[i]]); + element.setAttribute(keys[i], item[keys[i]]); + } + } + + //console.log(element); + return element; +} + +function modifyOption(id, options, values) { + var select = document.getElementById(id); + select.innerHTML = ""; + + for (var i = 0; i < options.length; i++) { + + var element = document.createElement("OPTION") + + element.value = values[i]; + element.innerHTML = options[i]; + + document.getElementById(id).appendChild(element); + + } + +} + + +function startWebSocket() { + if (webSockets == false) { + return; + } + + //ws.send('{"cmd": "getServerConfig1"}'); + +} + +function checkErr(obj) { + //alert(obj["err"]) + //screenLog(obj["err"], "error") + console.log(obj); + var newObj = new Object(); + var newErr = new Object(); + newErr["key"] = "Error"; + newErr["value"] = obj["err"]; + newErr["type"] = "error"; + + newObj[0] = newErr + showLog(newObj); + return +} + +function screenLog(msg, msgType, show) { + return + clearTimeout(closeLog) + var div = document.getElementById("screenLog"); + var newMsg = new Object(); + + newMsg["_element"] = "P"; + + switch(msgType) { + case "error": newMsg["class"] = "errorMsg"; break; + case "warning": newMsg["class"] = "warningMsg"; break; + //default: newMsg["class"] = "infoMsg" + } + + newMsg["_text"] = msg; + + div.appendChild(createElement(newMsg)); + + div.scrollTop = div.scrollHeight; + + if (show == false) { + return; + } + + div.className = "" + closeLog = setTimeout(closeScreenLog, 10000); +} + + +function closeScreenLog() { + var div = document.getElementById("screenLog"); + div.className = "screenLogHidden" +} + +function showScreenLog() { + clearTimeout(closeLog) + var div = document.getElementById("screenLog"); + var currentClass = div.className; + div.className = "screenLogHidden" + + switch(currentClass) { + case "screenLogHidden": div.className = ""; break; + case "": div.className = "screenLogHidden"; break; + } +} + +function showLoadingScreen(elm) { + var div = document.getElementById("loading"); + switch(elm) { + case true: div.className = "block"; break; + case false: div.className = "none"; break; + + /* + case true: div.style.display = "block"; break; + case false: div.style.display = "none"; break; + */ + } +} + +function createClintInfo(obj) { + //console.log(obj); + var keys = getObjKeys(obj); + for (var i = 0; i < keys.length; i++) { + if(document.getElementById(keys[i])){ + document.getElementById(keys[i]).innerHTML = obj[keys[i]]; + } + } + //document.getElementById("clientInfo").className = "visible"; +} + +function showElement(elmID, type) { + switch(type) { + case true: cssClass = "block"; break; + case false: cssClass = "none"; break; + } + + document.getElementById(elmID).className = cssClass; +} + +function showPopUpElement(elm) { + var allElements = new Array("deleteUserDetail", "mapping-detail", "user-detail", "file-detail"); + + for (var i = 0; i < allElements.length; i++) { + showElement(allElements[i], false) + } + + showElement(elm, true) + + setTimeout(function(){ + showElement("popup", true); + }, 10); +} + + // body... + +function showStreams(force) { + + var elmBox = document.getElementById("myStreamsBox"); + var elm = document.getElementById("allStreams"); + //console.log(elm); + show = elm.className; + + switch(force) { + case true: show = "notVisible"; break; + case false: show = "visible"; break; + } + + switch(show) { + case "notVisible": + elm.className = "visible"; + elmBox.style.height = "100px"; + break; + + default: + elm.className = "notVisible"; + elmBox.style.height = "20px"; + break; + } + + var show = elm.style.display; { + //console.log(elm.style.display); + } + + calculateWrapperHeight(); +} + +function xteveBackup() { + console.log("xteveBackup"); + var data = new Object(); + data["cmd"] = "xteveBackup"; + + xTeVe(data); +} + +function xteveRestore(elm) { + var restore = document.createElement("INPUT"); + restore.setAttribute("type", "file"); + restore.setAttribute("class", "notVisible"); + restore.setAttribute("name", ""); + restore.id = "upload"; + + document.body.appendChild(restore); + restore.click(); + + restore.onchange = function() { + var filename = restore.files[0].name + //console.log(restore.srcElement.files[0]); + var check = confirm("File: " + filename + "\nAll data will be replaced with those from the backup.\nShould the files be restored?"); + if (check == true) { + var reader = new FileReader(); + var file = document.querySelector('input[type=file]').files[0]; + if (file) { + reader.readAsDataURL(file); + reader.onload = function() { + console.log(reader.result); + var data = new Object(); + data["cmd"] = "xteveRestore" + data["base64"] = reader.result + + xTeVe(data); + return + }; + } else { + alert("File could not be loaded") + } + } + }; +} + +function getBase64(file) { + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = function() { + console.log(reader.result); + }; + reader.onerror = function(error) { + console.log('Error: ', error); + }; +} + +function logout() { + document.cookie.split(';').forEach(function(c) { + document.cookie = c.trim().split('=')[0] + '=;' + 'expires=Thu, 01 Jan 1970 00:00:00 UTC;'; + }); + location.reload(); +} + +function getCookie(name) { + var value = "; " + document.cookie; + var parts = value.split("; " + name + "="); + if (parts.length == 2) return parts.pop().split(";").shift(); +} + +function setCookie(token) { + //console.log(token); + document.cookie = "Token=" + token +} + diff --git a/html/js/base_ts.js b/html/js/base_ts.js new file mode 100644 index 0000000..88143a3 --- /dev/null +++ b/html/js/base_ts.js @@ -0,0 +1,473 @@ +var SERVER = new Object(); +var BULK_EDIT = false; +var COLUMN_TO_SORT; +var SEARCH_MAPPING = new Object(); +var UNDO = new Object(); +var SERVER_CONNECTION = false; +var WS_AVAILABLE = false; +// Menü +var menuItems = new Array(); +menuItems.push(new MainMenuItem("playlist", "{{.mainMenu.item.playlist}}", "m3u.png", "{{.mainMenu.headline.playlist}}")); +//menuItems.push(new MainMenuItem("pmsID", "{{.mainMenu.item.pmsID}}", "number.png", "{{.mainMenu.headline.pmsID}}")) +menuItems.push(new MainMenuItem("filter", "{{.mainMenu.item.filter}}", "filter.png", "{{.mainMenu.headline.filter}}")); +menuItems.push(new MainMenuItem("xmltv", "{{.mainMenu.item.xmltv}}", "xmltv.png", "{{.mainMenu.headline.xmltv}}")); +menuItems.push(new MainMenuItem("mapping", "{{.mainMenu.item.mapping}}", "mapping.png", "{{.mainMenu.headline.mapping}}")); +menuItems.push(new MainMenuItem("users", "{{.mainMenu.item.users}}", "users.png", "{{.mainMenu.headline.users}}")); +menuItems.push(new MainMenuItem("settings", "{{.mainMenu.item.settings}}", "settings.png", "{{.mainMenu.headline.settings}}")); +menuItems.push(new MainMenuItem("log", "{{.mainMenu.item.log}}", "log.png", "{{.mainMenu.headline.log}}")); +menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.png", "{{.mainMenu.headline.logout}}")); +// Kategorien für die Einstellungen +var settingsCategory = new Array(); +settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api")); +settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images")); +settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent")); +settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep")); +settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api")); +function showPopUpElement(elm) { + var allElements = new Array("popup-custom"); + for (var i = 0; i < allElements.length; i++) { + showElement(allElements[i], false); + } + showElement(elm, true); + setTimeout(function () { + showElement("popup", true); + }, 10); + return; +} +function showElement(elmID, type) { + var cssClass; + switch (type) { + case true: + cssClass = "block"; + break; + case false: + cssClass = "none"; + break; + } + document.getElementById(elmID).className = cssClass; +} +function changeButtonAction(element, buttonID, attribute) { + var value = element.options[element.selectedIndex].value; + document.getElementById(buttonID).setAttribute(attribute, value); +} +function getLocalData(dataType, id) { + var data = new Object(); + switch (dataType) { + case "m3u": + data = SERVER["settings"]["files"][dataType][id]; + break; + case "hdhr": + data = SERVER["settings"]["files"][dataType][id]; + break; + case "filter": + case "custom-filter": + case "group-title": + if (id == -1) { + data["active"] = true; + data["caseSensitive"] = false; + data["description"] = ""; + data["exclude"] = ""; + data["filter"] = ""; + data["include"] = ""; + data["name"] = ""; + data["type"] = "group-title"; + SERVER["settings"]["filter"][id] = data; + } + data = SERVER["settings"]["filter"][id]; + break; + case "xmltv": + data = SERVER["settings"]["files"][dataType][id]; + break; + case "users": + data = SERVER["users"][id]["data"]; + break; + case "mapping": + data = SERVER["xepg"]["epgMapping"][id]; + break; + case "m3uGroups": + data = SERVER["data"]["playlist"]["m3u"]["groups"]; + break; + } + return data; +} +function getObjKeys(obj) { + var keys = new Array(); + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + keys.push(i); + } + } + return keys; +} +function getAllSelectedChannels() { + var channels = new Array(); + if (BULK_EDIT == false) { + return channels; + } + var trs = document.getElementById("content_table").getElementsByTagName("TR"); + for (var i = 1; i < trs.length; i++) { + if (trs[i].style.display != "none") { + if (trs[i].firstChild.firstChild.checked == true) { + channels.push(trs[i].id); + } + } + } + return channels; +} +function selectAllChannels() { + var bulk = false; + var trs = document.getElementById("content_table").getElementsByTagName("TR"); + if (trs[0].firstChild.firstChild.checked == true) { + bulk = true; + } + for (var i = 1; i < trs.length; i++) { + if (trs[i].style.display != "none") { + switch (bulk) { + case true: + trs[i].firstChild.firstChild.checked = true; + break; + case false: + trs[i].firstChild.firstChild.checked = false; + break; + } + } + } + return; +} +function bulkEdit() { + BULK_EDIT = !BULK_EDIT; + var className; + var rows = document.getElementsByClassName("bulk"); + switch (BULK_EDIT) { + case true: + className = "bulk showBulk"; + break; + case false: + className = "bulk hideBulk"; + break; + } + for (var i = 0; i < rows.length; i++) { + rows[i].className = className; + rows[i].checked = false; + } + return; +} +function sortTable(column) { + //console.log(columm); + if (column == COLUMN_TO_SORT) { + return; + } + var table = document.getElementById("content_table"); + var tableHead = table.getElementsByTagName("TR")[0]; + var tableItems = tableHead.getElementsByTagName("TD"); + var sortObj = new Object(); + var x, xValue; + var tableHeader; + var sortByString = false; + if (column > 0 && COLUMN_TO_SORT > 0) { + tableItems[COLUMN_TO_SORT].className = "pointer"; + tableItems[column].className = "sortThis"; + } + COLUMN_TO_SORT = column; + var rows = table.rows; + if (rows[1] != undefined) { + tableHeader = rows[0]; + x = rows[1].getElementsByTagName("TD")[column]; + for (i = 1; i < rows.length; i++) { + x = rows[i].getElementsByTagName("TD")[column]; + switch (x.childNodes[0].tagName.toLowerCase()) { + case "input": + xValue = x.getElementsByTagName("INPUT")[0].value.toLowerCase(); + break; + case "p": + xValue = x.getElementsByTagName("P")[0].innerText.toLowerCase(); + break; + default: console.log(x.childNodes[0].tagName); + } + if (xValue == "" || xValue == NaN) { + xValue = i; + sortObj[i] = rows[i]; + } + else { + switch (isNaN(xValue)) { + case false: + xValue = parseFloat(xValue); + sortObj[xValue] = rows[i]; + break; + case true: + sortByString = true; + sortObj[xValue.toLowerCase() + i] = rows[i]; + break; + } + } + } + while (table.firstChild) { + table.removeChild(table.firstChild); + } + var sortValues = getObjKeys(sortObj); + if (sortByString == true) { + sortValues.sort(); + console.log(sortValues); + } + else { + function sortFloat(a, b) { + return a - b; + } + sortValues.sort(sortFloat); + } + table.appendChild(tableHeader); + for (var i = 0; i < sortValues.length; i++) { + table.appendChild(sortObj[sortValues[i]]); + } + } + return; +} +function createSearchObj() { + SEARCH_MAPPING = new Object(); + var data = SERVER["xepg"]["epgMapping"]; + var channels = getObjKeys(data); + var channelKeys = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title"]; + channels.forEach(function (id) { + channelKeys.forEach(function (key) { + if (key == "x-active") { + switch (data[id][key]) { + case true: + SEARCH_MAPPING[id] = "online "; + break; + case false: + SEARCH_MAPPING[id] = "offline "; + break; + } + } + else { + SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " "; + } + }); + }); + return; +} +function searchInMapping() { + var searchValue = document.getElementById("searchMapping").value; + var trs = document.getElementById("content_table").getElementsByTagName("TR"); + for (var i = 1; i < trs.length; ++i) { + var id = trs[i].getAttribute("id"); + var element = SEARCH_MAPPING[id]; + switch (element.toLowerCase().includes(searchValue.toLowerCase())) { + case true: + document.getElementById(id).style.display = ""; + break; + case false: + document.getElementById(id).style.display = "none"; + break; + } + } + return; +} +function calculateWrapperHeight() { + if (document.getElementById("box-wrapper")) { + var elm = document.getElementById("box-wrapper"); + var divs = new Array("myStreamsBox", "clientInfo", "content"); + var elementsHeight = 0 - elm.offsetHeight; + for (var i = 0; i < divs.length; i++) { + elementsHeight = elementsHeight + document.getElementById(divs[i]).offsetHeight; + } + elm.style.height = window.innerHeight - elementsHeight + "px"; + } + return; +} +function changeChannelNumber(element) { + var dbID = element.parentNode.parentNode.id; + var newNumber = parseFloat(element.value); + var channelNumbers = []; + var data = SERVER["xepg"]["epgMapping"]; + var channels = getObjKeys(data); + if (isNaN(newNumber)) { + alert("{{.alert.invalidChannelNumber}}"); + return; + } + channels.forEach(function (id) { + var channelNumber = parseFloat(data[id]["x-channelID"]); + channelNumbers.push(channelNumber); + }); + for (var i = 0; i < channelNumbers.length; i++) { + if (channelNumbers.indexOf(newNumber) == -1) { + break; + } + if (Math.floor(newNumber) == newNumber) { + newNumber = newNumber + 1; + } + else { + newNumber = newNumber + 0.1; + newNumber.toFixed(1); + newNumber = Math.round(newNumber * 10) / 10; + } + } + data[dbID]["x-channelID"] = newNumber.toString(); + element.value = newNumber; + console.log(data[dbID]["x-channelID"]); + if (COLUMN_TO_SORT == 1) { + COLUMN_TO_SORT = -1; + sortTable(1); + } + return; +} +function backup() { + var data = new Object(); + console.log("Backup data"); + var cmd = "xteveBackup"; + console.log("SEND TO SERVER"); + console.log(data); + var server = new Server(cmd); + server.request(data); + return; +} +function toggleChannelStatus(id) { + var element; + var status; + if (document.getElementById("active")) { + var checkbox = document.getElementById("active"); + status = (checkbox).checked; + } + var ids = getAllSelectedChannels(); + if (ids.length == 0) { + ids.push(id); + } + ids.forEach(function (id) { + var channel = SERVER["xepg"]["epgMapping"][id]; + channel["x-active"] = status; + switch (channel["x-active"]) { + case true: + if (channel["x-xmltv-file"] == "-" || channel["x-mapping"] == "-") { + if (BULK_EDIT == false) { + alert(channel["x-name"] + ": Missing XMLTV file / channel"); + checkbox.checked = false; + } + channel["x-active"] = false; + } + break; + case false: + // code... + break; + } + if (channel["x-active"] == false) { + document.getElementById(id).className = "notActiveEPG"; + } + else { + document.getElementById(id).className = "activeEPG"; + } + }); +} +function restore() { + if (document.getElementById('upload')) { + document.getElementById('upload').remove(); + } + var restore = document.createElement("INPUT"); + restore.setAttribute("type", "file"); + restore.setAttribute("class", "notVisible"); + restore.setAttribute("name", ""); + restore.id = "upload"; + document.body.appendChild(restore); + restore.click(); + restore.onchange = function () { + var filename = restore.files[0].name; + var check = confirm("File: " + filename + "\n{{.confirm.restore}}"); + if (check == true) { + var reader = new FileReader(); + var file = document.querySelector('input[type=file]').files[0]; + if (file) { + reader.readAsDataURL(file); + reader.onload = function () { + console.log(reader.result); + var data = new Object(); + var cmd = "xteveRestore"; + data["base64"] = reader.result; + var server = new Server(cmd); + server.request(data); + }; + } + else { + alert("File could not be loaded"); + } + restore.remove(); + return; + } + }; + return; +} +function uploadLogo() { + if (document.getElementById('upload')) { + document.getElementById('upload').remove(); + } + var upload = document.createElement("INPUT"); + upload.setAttribute("type", "file"); + upload.setAttribute("class", "notVisible"); + upload.setAttribute("name", ""); + upload.id = "upload"; + document.body.appendChild(upload); + upload.click(); + upload.onblur = function () { + alert(); + }; + upload.onchange = function () { + var filename = upload.files[0].name; + var reader = new FileReader(); + var file = document.querySelector('input[type=file]').files[0]; + if (file) { + reader.readAsDataURL(file); + reader.onload = function () { + console.log(reader.result); + var data = new Object(); + var cmd = "uploadLogo"; + data["base64"] = reader.result; + data["filename"] = file.name; + var server = new Server(cmd); + server.request(data); + var updateLogo = document.getElementById('update-icon'); + updateLogo.checked = false; + updateLogo.className = "changed"; + }; + } + else { + alert("File could not be loaded"); + } + upload.remove(); + return; + }; +} +function checkUndo(key) { + switch (key) { + case "epgMapping": + if (UNDO.hasOwnProperty(key)) { + SERVER["xepg"][key] = JSON.parse(JSON.stringify(UNDO[key])); + } + else { + UNDO[key] = JSON.parse(JSON.stringify(SERVER["xepg"][key])); + } + break; + default: + break; + } + return; +} +function sortSelect(elem) { + var tmpAry = []; + var selectedValue = elem[elem.selectedIndex].value; + for (var i = 0; i < elem.options.length; i++) + tmpAry.push(elem.options[i]); + tmpAry.sort(function (a, b) { return (a.text < b.text) ? -1 : 1; }); + while (elem.options.length > 0) + elem.options[0] = null; + var newSelectedIndex = 0; + for (var i = 0; i < tmpAry.length; i++) { + elem.options[i] = tmpAry[i]; + if (elem.options[i].value == selectedValue) + newSelectedIndex = i; + } + elem.selectedIndex = newSelectedIndex; // Set new selected index after sorting + return; +} +function updateLog() { + console.log("TOKEN"); + var server = new Server("updateLog"); + server.request(new Object()); +} diff --git a/html/js/classes_ts.js b/html/js/classes_ts.js new file mode 100644 index 0000000..80c168b --- /dev/null +++ b/html/js/classes_ts.js @@ -0,0 +1,40 @@ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var MainMenu = /** @class */ (function () { + function MainMenu() { + this.DocumentID = "main-menu"; + this.HTMLTag = "LI"; + } + MainMenu.prototype.create = function () { + console.log(this.DocumentID); + }; + return MainMenu; +}()); +var MainMenuItem = /** @class */ (function (_super) { + __extends(MainMenuItem, _super); + function MainMenuItem() { + return _super !== null && _super.apply(this, arguments) || this; + } + MainMenuItem.prototype.create2 = function () { + var element = document.createElement(this.HTMLTag); + element.innerText = this.Value; + console.log(element); + }; + return MainMenuItem; +}(MainMenu)); +function pageReady() { + var item = new MainMenuItem(); + item.Value = "Test"; + item.create2(); +} diff --git a/html/js/configuaration.js b/html/js/configuaration.js new file mode 100644 index 0000000..0b1105b --- /dev/null +++ b/html/js/configuaration.js @@ -0,0 +1,294 @@ +var configMenu = new Object(); +var wizard = new Array("key", "tuner", "epgSource", "m3u", "complete"); +var activeWizard; +var dvrIP + +var configMenu_tuner = new Object(); +configMenu_tuner["_element"] = "SELECT"; +configMenu_tuner["_menuType"] = "singleInput"; +configMenu_tuner["_configKey"] = "tuner"; +configMenu_tuner["_label"] = "Available tuners"; +configMenu_tuner["name"] = "tuner"; +configMenu_tuner["id"] = "Tuner"; +configMenu_tuner["placeholder"] = "Tuner"; +configMenu_tuner["_usage"] = "This setting is only used by Plex and Emby.
The number of concurrent streams allowed by the IPTV provider." + + +var optionValues = new Array(); +for (var i = 1; i <= 100; i++) { + optionValues.push(i) +} +configMenu_tuner["_optionValues"] = optionValues; + +var configMenu_epg = new Object(); +configMenu_epg["_element"] = "SELECT"; +configMenu_epg["_menuType"] = "singleInput"; +configMenu_epg["_configKey"] = "epgSource"; +configMenu_epg["_label"] = "Selection of the EPG source"; +configMenu_epg["name"] = "epgSource"; +configMenu_epg["id"] = "EPG source"; +configMenu_epg["placeholder"] = "EPG source"; +configMenu_epg["_optionValues"] = new Array("PMS", "XEPG"); +configMenu_epg["_usage"] = "PMS: Use EPG data from Plex or Emby
XEPG: Use of external EPG data (XMLTV)
Several XMLTV sources possible
Allows editing and order channels
M3U / XMLTV export (HTTP link for IPTV apps)" + +var configMenu_m3u = new Object(); +configMenu_m3u["_element"] = "INPUT"; +configMenu_m3u["_menuType"] = "inputArray"; +configMenu_m3u["_configKey"] = "file"; +configMenu_m3u["_label"] = "M3U File: local or remote"; +configMenu_m3u["name"] = "file"; +configMenu_m3u["id"] = "m3u"; +configMenu_m3u["type"] = "text"; +configMenu_m3u["placeholder"] = "M3U File"; +configMenu_m3u["_usage"] = "Remote playlist: http://your.provider.com/file.m3u
Local playlist: /path/to/file.m3u" + + +configMenu_m3u["value"] = "http://websrv.local:8080/kabel.m3u"; + +var configMenu_complete = new Object(); +configMenu_complete["_element"] = "H2"; +configMenu_complete["_menuType"] = "inputArray"; +configMenu_complete["_configKey"] = "file"; +configMenu_complete["_text"] = "xTeVe was successfully set up"; +configMenu_complete["name"] = "complete"; +configMenu_complete["id"] = "complete"; +configMenu_complete["type"] = "text"; +configMenu_complete["class"] = "center"; + +configMenu["tuner"] = configMenu_tuner; +configMenu["epgSource"] = configMenu_epg; +configMenu["m3u"] = configMenu_m3u; +configMenu["complete"] = configMenu_complete; + +function readyForConfiguration() { + var data = new Object(); + data["cmd"] = "getServerConfig"; + xTeVe(data); + showLoadingScreen(false); +} + +function createConfiguration(elm) { + + activeWizard = elm; + var item = configMenu[elm]; + + var div = document.getElementById("content"); + div.innerHTML = ""; + div.setAttribute("data-configKey", item["_configKey"]); + div.setAttribute("data-menuType", item["_menuType"]); + + switch(item.hasOwnProperty("_label")) { + case true: + var newItem = new Object(); + newItem["_element"] = "LABEL"; + newItem["_text"] = item["_label"]; + newItem["for"] = item["id"]; + div.appendChild(createElement(newItem)); + break + } + + switch(item["_element"]) { + case "SELECT": + div.appendChild(createElement(item)); + var selectElement = div.getElementsByTagName("SELECT")[0]; + var values = item["_optionValues"]; + for (var i = 0; i < values.length; i++) { + var newEntry = new Object; + newEntry["_element"] = "OPTION"; + newEntry["_text"] = item["id"] + ": " + values[i]; + newEntry["value"] = values[i]; + selectElement.appendChild(createElement(newEntry)); + } + //return + break; + + default: + div.appendChild(createElement(item)); + break; + + + } + //alert() + + switch(item.hasOwnProperty("_usage")) { + case true: + var usageItem = new Object(); + usageItem["_element"] = "PRE" + usageItem["_text"] = item["_usage"]; + div.appendChild(createElement(usageItem)); + } + + if (activeWizard == "complete") { + document.getElementById("next").value = "Finished" + //document.getElementById("next").setAttribute("onclick", "javascript: location.reload();") + } + + //div.appendChild(createElement(item)); +} + +function saveData() { + + var div = document.getElementById("content"); + var inputs = div.getElementsByTagName("INPUT"); + var selects = div.getElementsByTagName("SELECT"); + var value; + var data = new Object(); + var valueArr = new Array(); + var newData = false; + + if (activeWizard == "complete") { + data["cmd"] = "wizardCompleted"; + showLoadingScreen(true) + xTeVe(data); + return + } + + for (var i = 0; i < inputs.length; i++) { + var menuType = inputs[i].parentElement.getAttribute("data-menutype"); + if (inputs[i].value != undefined && inputs[i].value != "" ) { + newData = true; + + console.log(inputs[i].id) + switch(inputs[i].id) { + case "m3u": + var newPlaylist = new Object(); + newPlaylist["file.source"] = inputs[i].value; + //newPlaylist["name"] = inputs[i].value; + newPlaylist["type"] = "m3u"; + newPlaylist["new"] = true; + + data["files"] = new Object(); + data["files"]["m3u"] = new Object(); + data["files"]["m3u"]["-"] = newPlaylist; + + data["cmd"] = "saveFilesM3U"; + xTeVe(data) + return + } + /* + switch(menuType) { + case "singleInput": + data[inputs[i].name] = inputs[i].value; break; + case "inputArray": + valueArr.push(inputs[i].value); + data[inputs[i].name] = valueArr; break + + } + */ + } else { + inputs[i].style.borderBottomColor = "red"; + return; + } + } + + + for (var i = 0; i < selects.length; i++) { + var value = selects[i].options[selects[i].selectedIndex].value; + if (isNaN(value) == false) { + value = parseInt(value); + data[selects[i].name] = value; + newData = true; + break; + } + data[selects[i].name] = value; + newData = true; + } + + + //console.log(data, newData); + if (newData == true) { + config = data + data["cmd"] = "saveConfig"; + xTeVe(data); + } +} + +function xTeVe(data) { + + if (webSockets == false) { + alert("Your browser does not support WebSockets"); + return; + } + + if (activeWizard == "m3u" || activeWizard == "epgSource") { + showLoadingScreen(true); + } + + var protocolWS + switch(window.location.protocol) { + case "http:": protocolWS = "ws://"; break; + case "https:": protocolWS = "wss://"; break; + } + + var ws = new WebSocket(protocolWS + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token")); + + ws.onopen = function() { + ws.send(JSON.stringify(data)); + } + + ws.onmessage = function (e) { + + var response = JSON.parse(e.data); + + if (response.hasOwnProperty("clientInfo")) { + createClintInfo(response["clientInfo"]); + } + + if (response.hasOwnProperty("status")) { + if (response["status"] == false) { + document.getElementById("headline").style.borderColor = "red"; + showErr(response["err"]); + showLoadingScreen(false) + return + } else { + document.getElementById("err").innerHTML = ""; + document.getElementById("headline").style.borderColor = "lawngreen"; + } + + dvrIP = response["DVR"] + switch(response["configurationWizard"]) { + case true: + if (activeWizard == undefined) { + activeWizard = wizard[0] + } + var n = wizard.indexOf(activeWizard); + n++; + activeWizard = wizard[n] + + if (activeWizard == undefined) { + data["cmd"] = "wizardCompleted"; + xTeVe(data) + } else { + //console.log(activeWizard); + createConfiguration(activeWizard); + } + + break; + } + + switch(response["reload"]) { + + + case true: + + setTimeout(function(){ + location.reload(); + }, 100); + + //location.reload(); + + break; + + } + + + } + + setTimeout(function(){ showLoadingScreen(false); }, 300); + } + +} + +function showErr(elm) { + document.getElementById("err").innerHTML = elm; +} \ No newline at end of file diff --git a/html/js/configuration_ts.js b/html/js/configuration_ts.js new file mode 100644 index 0000000..773a4de --- /dev/null +++ b/html/js/configuration_ts.js @@ -0,0 +1,140 @@ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var WizardCategory = /** @class */ (function () { + function WizardCategory() { + this.DocumentID = "content"; + } + WizardCategory.prototype.createCategoryHeadline = function (value) { + var element = document.createElement("H4"); + element.innerHTML = value; + return element; + }; + return WizardCategory; +}()); +var WizardItem = /** @class */ (function (_super) { + __extends(WizardItem, _super); + function WizardItem(key, headline) { + var _this = _super.call(this) || this; + _this.headline = headline; + _this.key = key; + return _this; + } + WizardItem.prototype.createWizard = function () { + var headline = this.createCategoryHeadline(this.headline); + var key = this.key; + var content = new PopupContent(); + var description; + var doc = document.getElementById(this.DocumentID); + doc.innerHTML = ""; + doc.appendChild(headline); + switch (key) { + case "tuner": + var text = new Array(); + var values = new Array(); + for (var i = 1; i <= 100; i++) { + text.push(i); + values.push(i); + } + var select = content.createSelect(text, values, "1", key); + select.setAttribute("class", "wizard"); + select.id = key; + doc.appendChild(select); + description = "{{.wizard.tuner.description}}"; + break; + case "epgSource": + var text = ["PMS", "XEPG"]; + var values = ["PMS", "XEPG"]; + var select = content.createSelect(text, values, "XEPG", key); + select.setAttribute("class", "wizard"); + select.id = key; + doc.appendChild(select); + description = "{{.wizard.epgSource.description}}"; + break; + case "m3u": + var input = content.createInput("text", key, ""); + input.setAttribute("class", "wizard"); + input.id = key; + doc.appendChild(input); + description = "{{.wizard.m3u.description}}"; + break; + case "xmltv": + var input = content.createInput("text", key, ""); + input.setAttribute("class", "wizard"); + input.id = key; + doc.appendChild(input); + description = "{{.wizard.xmltv.description}}"; + break; + default: + console.log(key); + break; + } + var pre = document.createElement("PRE"); + pre.innerHTML = description; + doc.appendChild(pre); + console.log(headline, key); + }; + return WizardItem; +}(WizardCategory)); +function readyForConfiguration(wizard) { + var server = new Server("getServerConfig"); + server.request(new Object()); + showElement("loading", false); + configurationWizard[wizard].createWizard(); +} +function saveWizard() { + var cmd = "saveWizard"; + var div = document.getElementById("content"); + var config = div.getElementsByClassName("wizard"); + var wizard = new Object(); + for (var i = 0; i < config.length; i++) { + var name; + var value; + switch (config[i].tagName) { + case "SELECT": + name = config[i].name; + value = config[i].value; + // Wenn der Wert eine Zahl ist, wird dieser als Zahl gespeichert + if (isNaN(value)) { + wizard[name] = value; + } + else { + wizard[name] = parseInt(value); + } + break; + case "INPUT": + switch (config[i].type) { + case "text": + name = config[i].name; + value = config[i].value; + wizard[name] = value; + break; + } + break; + default: + // code... + break; + } + } + var data = new Object(); + data["wizard"] = wizard; + var server = new Server(cmd); + server.request(data); + console.log(data); +} +// Wizard +var configurationWizard = new Array(); +configurationWizard.push(new WizardItem("tuner", "{{.wizard.tuner.title}}")); +configurationWizard.push(new WizardItem("epgSource", "{{.wizard.epgSource.title}}")); +configurationWizard.push(new WizardItem("m3u", "{{.wizard.m3u.title}}")); +configurationWizard.push(new WizardItem("xmltv", "{{.wizard.xmltv.title}}")); diff --git a/html/js/data.js b/html/js/data.js new file mode 100644 index 0000000..688733a --- /dev/null +++ b/html/js/data.js @@ -0,0 +1,329 @@ +function showConfig(obj) { + config = obj; + //setMenuItem(); + createMenu(); + //document.getElementById("page").className = ""; +} + +showMyStreams + +function showMyStreams(allStreamsObj) { + + var streamTypeKeys = getObjKeys(allStreamsObj) + + for (var s = 0; s < streamTypeKeys.length; s++) { + var streamType = streamTypeKeys[s]; + var obj = new Object(); + obj = allStreamsObj[streamType]; + switch(streamType) { + case "activeStreams": activeStreams = obj; break; + } + + document.getElementById(streamType).innerHTML = ""; + + var streamsObj = new Object(); + var streamsNames = new Array(); + + var keys = getObjKeys(obj) + + // Create Object (streamsObj) for the streams and sort by name (streamsNames) + for (var i = 0; i < keys.length; i++) { + var name = obj[keys[i]]["name"]; + var tmp = new Object(); + var streamKey = getObjKeys(obj[keys[i]]); + + for (var j = 0; j < streamKey.length; j++) { + tmp[streamKey[j]] = obj[keys[i]][streamKey[j]]; + } + + streamsObj[name] = tmp; + streamsNames.push(name) + } + + streamsNames.sort(); + + // Create Table for activeStreams + var table = document.getElementById(streamType); + + for (var i = 0; i < streamsNames.length; i++) { + var newEntry = new Object(); + newEntry["_element"] = "TR"; + table.appendChild(createElement(newEntry)); + var line = table.lastChild; + + var tmp = streamsObj[streamsNames[i]] + var keys = getObjKeys(tmp) + + var newKey = new Object() + newKey["_element"] = "TD"; + //newKey["_text"] = streamsNames[i]; + switch(streamType) { + case "activeStreams": newKey["_text"] = "Channel (+):"; break; + case "inactiveStreams": newKey["_text"] = "Channel (-):"; break; + } + + newKey["class"] = "tdKey"; + console.log(); + + + var newVal = new Object() + newVal["_element"] = "TD"; + newVal["_text"] = streamsNames[i]; + newVal["class"] = "tdVal"; + //newVal["_text"] = value; + + line.appendChild(createElement(newKey)); + line.appendChild(createElement(newVal)); + + } + + } + + return +} + +function showActiveStreams(obj) { + document.getElementById("activeStreams").innerHTML = ""; + activeStreams = obj; + var streamsObj = new Object(); + var streamsNames = new Array(); + + var keys = getObjKeys(obj) + + // Create Object (streamsObj) for the streams and sort by name (streamsNames) + for (var i = 0; i < keys.length; i++) { + var name = obj[keys[i]]["name"]; + var tmp = new Object(); + var streamKey = getObjKeys(obj[keys[i]]); + + for (var j = 0; j < streamKey.length; j++) { + tmp[streamKey[j]] = obj[keys[i]][streamKey[j]]; + } + + streamsObj[name] = tmp; + streamsNames.push(name) + } + + streamsNames.sort(); + + // Create Table for activeStreams + var table = document.getElementById("activeStreams"); + + for (var i = 0; i < streamsNames.length; i++) { + var newEntry = new Object(); + newEntry["_element"] = "TR"; + table.appendChild(createElement(newEntry)); + var line = table.lastChild; + + var tmp = streamsObj[streamsNames[i]] + var keys = getObjKeys(tmp) + + var newKey = new Object() + newKey["_element"] = "TD"; + //newKey["_text"] = streamsNames[i]; + newKey["_text"] = "Channel:"; + newKey["class"] = "tdKey"; + console.log(); + + + var newVal = new Object() + newVal["_element"] = "TD"; + newVal["_text"] = streamsNames[i]; + newVal["class"] = "tdVal"; + //newVal["_text"] = value; + + line.appendChild(createElement(newKey)); + line.appendChild(createElement(newVal)); + + } + +} + +function parseLogs(obj) { + log = obj + var keys = getObjKeys(obj) + + var msgType; + for (var i = 0; i < keys.length; i++) { + switch(keys[i]) { + case "warnings": msgType = "warningMsg"; break; + case "errors": msgType = "errorMsg"; break; + } + + switch(obj[keys[i]]) { + case 0: msgType = "tdVal"; break; + default: break; + } + + if(document.getElementById(keys[i])){ + document.getElementById(keys[i]).className = msgType; + } + + + } + return +} + + +function cancelData(element) { + createMenu(); +} + +function saveData(element) { + var data = new Object(); + var div = element.parentNode.parentNode; + var inputs = div.getElementsByTagName("INPUT"); + + var configKey = div.getAttribute("data-configkey"); + var menuType = div.getAttribute("data-menutype"); + var value; + var valueArr = new Array(); + + for (var i = 0; i < inputs.length; i++) { + if (inputs[i].type == "text" && inputs[i].value != undefined && inputs[i].value != "" ) { + console.log(inputs[i].value, menuType) + switch(menuType) { + case "inputArray": valueArr.push(inputs[i].value); break; + case "singleInput": value = inputs[i].value; break; + } + } + } + + switch(menuType) { + case "inputArray": data[configKey] = valueArr; break; + case "singleInput": + if (isNaN(value) == false) { + value = parseInt(value); + data[configKey] = value; + break; + } + + if (value == undefined) { + data["delete"] = configKey; + } else { + data[configKey] = value + } + + break; + } + + data["cmd"] = "saveConfig"; + console.log(data); + xTeVe(data) +} + +function xTeVe(data) { + if (webSockets == false) { + alert("Your browser does not support WebSockets"); + return; + } else { + if (data["cmd"] != "getLog") { + showLoadingScreen(true) + } + } + delete undo["epgMapping"]; + + var protocolWS + switch(window.location.protocol) { + case "http:": protocolWS = "ws://"; break; + case "https:": protocolWS = "wss://"; break; + } + + + var ws = new WebSocket(protocolWS + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token")); + ws.onopen = function() { + console.log(data) + ws.send(JSON.stringify(data)); + } + + ws.onmessage = function (e) { + var response = JSON.parse(e.data); + console.log(response); + + if (response.hasOwnProperty("clientInfo")) { + createClintInfo(response["clientInfo"]); + } + + if (response.hasOwnProperty("log")) { + createClintInfo(response["log"]); + } + + if (response.hasOwnProperty("status")) { + if (response["status"] == false) { + alert(response["err"]) + if(response.hasOwnProperty("reload")) { + location.reload(); + } + //checkErr(response) + console.log(response); + updateXteveStatus(response); + setTimeout(function(){ showLoadingScreen(false); }, 300); + + return + } + + updateXteveStatus(response) + + //console.log(data["cmd"]); + switch(data["cmd"]) { + case "saveUserData": createMenu(); break; + case "saveNewUser": createMenu(); break; + case "saveFilesXMLTV": //createMenu(); break; + case "saveFilesM3U": //createMenu(); return; break; + case "saveConfig": + data = new Object(); + data["cmd"] = "checkToken"; + xTeVe(data); + break; + + case "emptyLog": writeLogInDiv(); break; + case "getLog": return; break; + } + + + } + + if (config["files"] == undefined || config["files"].length == 0) { + createMenu(); + document.getElementById(10).click() + } + + setTimeout(function(){ showLoadingScreen(false); }, 0); + } + +} + +function updateXteveStatus(response) { + var keys = getObjKeys(response); + //console.log(keys); + + for (var i = 0; i < keys.length; i++) { + switch(keys[i]) { + case "alert": alert(response[keys[i]]); break; + case "config": showConfig(response[keys[i]]); break; + case "log": parseLogs(response[keys[i]]); break; + case "myStreams": showMyStreams(response[keys[i]]); break; + case "xEPG": xEPG = response[keys[i]]; break; + case "users": users = response[keys[i]]; break; + case "token": document.cookie = "Token=" + response[keys[i]]; break; + case "reload": location.reload(); break; + case "openLink": window.location = response["openLink"]; break; + //case "version": version = response[keys[i]]; break; + } + } +} + +function getValueFromProviderFile(xXmltvFile, fileType, key) { + + var fileID = xXmltvFile.substring(0, xXmltvFile.lastIndexOf('.')) + + if (config["files"][fileType].hasOwnProperty(fileID) == true) { + var data = config["files"][fileType][fileID]; + return data[key] + } + +} + + + + diff --git a/html/js/files.js b/html/js/files.js new file mode 100644 index 0000000..a8b63f2 --- /dev/null +++ b/html/js/files.js @@ -0,0 +1,379 @@ +function openFiles(elm, fileType) { + //document.getElementById("settings").innerHTML = "Test"; + + columnToSort = 0; + var newDiv = document.getElementById("settings"); + + var newEntry = new Object(); + newEntry["_element"] = "HR"; + newDiv.appendChild(createElement(newEntry)); + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + newEntry["class"] = "button"; + newEntry["value"] = "New"; + newEntry["onclick"] = 'fileDetail("-", "' + fileType + '")'; + newDiv.appendChild(createElement(newEntry)); + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + newEntry["class"] = "button"; + newEntry["value"] = "Update"; + newEntry["onclick"] = "fileDetail(0)"; + //newDiv.appendChild(createElement(newEntry)); + + var div = document.getElementById("settings"); + + // Build table + var newTable = new Object(); + newTable["_element"] = "TABLE"; + newTable["id"] = "id_mapping"; + newTable["class"] = "table-mapping"; + div.appendChild(createElement(newTable)); + + setTimeout(function(){ + createFilesTable(fileType); + }, 10); + +} + +function createFilesTable(fileType) { + var table = document.getElementById("id_mapping"); + var availableFileTypes = new Array(); + + table.innerHTML = ""; + var newTR = new Object(); + newTR["_element"] = "TR"; + newTR["class"] = "table-mapping-header"; + table.appendChild(createElement(newTR)); + + var tr = table.lastChild; + + switch(fileType) { + case "xmltv": + availableFileTypes = new Array("xmltv"); + var trHeadlines = new Array("Guide", "Last Update", "Availability %", "Channels", "Programs") + var compatibilityKeys = new Array("xmltv.channels", "xmltv.programs") + break; + + case "m3u": + availableFileTypes = new Array("m3u", "hdhr"); + var trHeadlines = new Array("Playlist", "Last Update", "Availability %", "Type", "Streams", "group-title %", "tvg-id %", "Unique ID %"); + var compatibilityKeys = new Array("streams", "group.title", "tvg.id", "stream.id"); + break; + } + + for (var i = 0; i < trHeadlines.length; i++) { + var newTD = new Object(); + newTD["_element"] = "TD"; + newTD["_text"] = trHeadlines[i]; + tr.appendChild(createElement(newTD)); + } + + for (var i = 0; i < availableFileTypes.length; i++) { + + var fileType = availableFileTypes[i] + + var data = config["files"][fileType]; + + var allFiles = getObjKeys(data) + + for (var f = 0; f < allFiles.length; f++) { + var elm = data[allFiles[f]]; + var table = document.getElementById("id_mapping"); + var fileID = elm["id.provider"]; + var name = elm["name"]; + var lastUpdate = elm["last.update"]; + var availability = elm["provider.availability"]; + var type = elm["type"].toUpperCase(); + var compatibility = elm["compatibility"]; + + // Create TR + var newTR = new Object(); + newTR["_element"] = "TR"; + newTR["class"] = ""; + newTR["id"] = fileID; + newTR["onclick"] = 'javascript: fileDetail("' + fileID + '","' + fileType + '");'; + table.appendChild(createElement(newTR)); + + var tr = table.lastChild; + + // Create file name TD + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["_text"] = name; + createNewTD(newTD, tr); + + // Create last update TD + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["_text"] = lastUpdate; + createNewTD(newTD, tr); + + // Create availability TD + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["_text"] = availability; + createNewTD(newTD, tr); + + if (fileType == "m3u" || fileType == "hdhr") { + + // Create Type TD + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["_text"] = type; + createNewTD(newTD, tr); + + } + + // Create all compatibility TDs + + for (var j = 0; j < compatibilityKeys.length; j++) { + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["_text"] = compatibility[compatibilityKeys[j]]; + createNewTD(newTD, tr); + } + + } + + } + + + sortTable(0) + + // usage Info + var div = document.getElementById("settings"); + switch(menu[activeMenu.id].hasOwnProperty("_usage")) { + case true: + var usageItem = new Object(); + usageItem["_element"] = "PRE" + usageItem["_text"] = menu[activeMenu.id]["_usage"]; + + var newHR = new Object(); + newHR["_element"] = "HR" + div.appendChild(createElement(newHR)); + div.appendChild(createElement(usageItem)); + break; + } + + calculateWrapperHeight(); + return; +} + + +function fileDetail(fileID, fileType) { + + optionsText = new Array("M3U", "HDHomeRun - [Experimental]") + optionsValue = new Array("m3u", "hdhr") + + switch (fileType) { + + case "m3u": + document.getElementById("name").setAttribute("placeholder", "Playlist name"); + document.getElementById("description").setAttribute("placeholder", "Description of this playlist"); + document.getElementById("file-detail-headline").innerHTML = "M3U Playlist"; + document.getElementById("file-path").innerHTML = "M3U File:"; + document.getElementById("file.source").setAttribute("placeholder", "Local or remote"); + break; + + case "hdhr": + document.getElementById("name").setAttribute("placeholder", "HDHomeRun name"); + document.getElementById("description").setAttribute("placeholder", "Description of this HDHomeRun tuner"); + document.getElementById("file-detail-headline").innerHTML = "HDHomeRun"; + document.getElementById("file-path").innerHTML = "HDHomeRun IP:"; + document.getElementById("file.source").setAttribute("placeholder", "IP address and port of the tuner (192.168.1.10:5004)"); + break; + + case "xmltv": + document.getElementById("name").setAttribute("placeholder", "XMLTV name"); + document.getElementById("description").setAttribute("placeholder", "Description of this XMLTV file"); + document.getElementById("file-detail-headline").innerHTML = "XMLTV File"; + document.getElementById("file-path").innerHTML = "XMLTV File:"; + document.getElementById("file.source").setAttribute("placeholder", "Local or remote"); + + optionsText = new Array("XMLTV") + optionsValue = new Array("xmltv") + break; + } + + modifyOption("type", optionsText, optionsValue) + + showPopUpElement('file-detail'); + + document.getElementById("saveFileDetail").setAttribute("onclick", 'javascript: saveFileDetail("' + fileID + '","' + fileType + '", false)'); + document.getElementById("updateFileDetail").setAttribute("onclick", 'javascript: updateFile("' + fileID + '","' + fileType + '", false)'); + document.getElementById("deleteFileDetail").setAttribute("onclick", 'javascript: saveFileDetail("' + fileID + '","' + fileType + '", true)'); + + var data = new Object(); + + switch(fileID) { + + case "-": // New file + data["name"] = ""; + data["description"] = ""; + data["file.source"] = ""; + data["type"] = fileType; + + document.getElementById("deleteFileDetail").className = "delete"; + document.getElementById("type").setAttribute("onchange", "changeFileType(this);") + document.getElementById("type").setAttribute("data-id", fileID) + + showElement("deleteFileDetail", false); + showElement("updateFileDetail", false); + + if (fileType == "xmltv") { + showElement("type", false); + showElement("file-type", false); + } else { + showElement("type", true); + showElement("file-type", true); + } + + break; + + default: + data = config["files"][fileType][fileID]; + document.getElementById("deleteFileDetail").className = "delete"; + + showElement("updateFileDetail", true); + showElement("type", false); + showElement("file-type", false); + + break; + + } + + var keys = getObjKeys(data); + + for (var i = 0; i < keys.length; i++) { + + if(document.getElementById(keys[i])){ + document.getElementById(keys[i]).value = data[keys[i]]; + } + + + } + +} + +function changeFileType(elm) { + + var fileID = elm.getAttribute("data-id"); + var fileType = elm.options[elm.selectedIndex].value; + + fileDetail(fileID, fileType) + +} + + +function saveFileDetail(fileID, fileType, deleteFile) { + + if (fileID == undefined) { + alert("ID is missing!!!"); + return + } + + var inputs = document.getElementById("file-detail").getElementsByTagName("INPUT"); + var selects = document.getElementById("file-detail").getElementsByTagName("SELECT"); + var newFileData = new Object(); + var data = new Object(); + + for (var i = 0; i < inputs.length; i++) { + switch(inputs[i].type) { + case "text": newFileData[inputs[i].name] = inputs[i].value; break; + } + } + + for (var i = 0; i < selects.length; i++) { + newFileData[selects[i].id] = selects[i].options[selects[i].selectedIndex].value; + } + + if (deleteFile == true) { + switch(fileType) { + case "m3u": var alertText = "Delete this playlist?"; break; + case "hdhr": var alertText = "Delete this HDHomeRun tuner?"; break; + case "xmltv": var alertText = "Delete this XMLTV file?"; break; + } + + if (confirm(alertText)) { + newFileData["delete"] = true + data = buildFilesObj(fileType, fileID, newFileData); + console.log(data); + + } else { + showElement("popup", false); + return + + } + + } else { + + switch(config["files"][fileType].hasOwnProperty(fileID)) { + + case true: + data = config["files"][fileType][fileID]; + if (data["file.source"] != newFileData["file.source"]) { + data["update"] = true + } else { + data["updatePlaylistName"] = true; + } + break; + + case false: + newFileData["new"] = true; + data = buildFilesObj(fileType, fileID, newFileData); + break + + } + + } + + switch(fileType) { + + case "m3u": data["cmd"] = "saveFilesM3U"; break; + case "hdhr": data["cmd"] = "saveFilesHDHR"; break; + case "xmltv": data["cmd"] = "saveFilesXMLTV"; break; + + } + //console.log(data); + xTeVe(data); + return +} + +function updateFile(fileID, fileType, allFiles) { + + switch(config["files"][fileType].hasOwnProperty(fileID)) { + + case true: + + var data = new Object(); + var data = buildFilesObj(fileType, fileID, config["files"][fileType][fileID]) + data["new"] = true + + switch(fileType) { + + case "m3u": data["cmd"] = "updateFileM3U"; break; + case "hdhr": data["cmd"] = "updateFileHDHR"; break; + case "xmltv": data["cmd"] = "updateFileXMLTV"; break; + + } + + xTeVe(data); + + break; + } + +} + +function buildFilesObj(fileType, fileID, obj) { + + var data = new Object(); + data["files"] = new Object(); + data["files"][fileType] = new Object(); + data["files"][fileType][fileID] = obj + return data + +} \ No newline at end of file diff --git a/html/js/log.js b/html/js/log.js new file mode 100644 index 0000000..378d42f --- /dev/null +++ b/html/js/log.js @@ -0,0 +1,114 @@ +var logInterval + + +function updateLog() { + var data = new Object(); + data["cmd"] = "getLog"; + xTeVe(data); + writeLogInDiv(); + return +} + +function writeLogInDiv() { + var logs = log["log"]; + var div = document.getElementById("settings").lastChild.lastChild; + div.innerHTML = ""; + + var max = 50; + + + for (var i = 0; i < logs.length; i++) { + var newEntry = new Object(); + newEntry["_element"] = "P"; + + if (logs[i].includes("ERROR")) { +// case "warnings": msgType = "warningMsg"; break; + newEntry["class"] = "errorMsg"; + } + + if (logs[i].includes("WARNING")) { +// case "warnings": msgType = "warningMsg"; break; + newEntry["class"] = "warningMsg"; + } + + newEntry["_text"] = logs[i]; + + div.appendChild(createElement(newEntry)); + } + + calculateWrapperHeight(); + var scrollDiv = document.getElementById("box-wrapper"); + scrollDiv.scrollTop = scrollDiv.scrollHeight; +} + +function showLog(obj) { + //logInterval = setInterval(updateLog, 5000); + + var logs = log["log"]; + + var div = document.getElementById("settings"); + + var newEntry = new Object(); + newEntry["_element"] = "HR"; + div.appendChild(createElement(newEntry)); + //div = div.lastChild; + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + newEntry["class"] = "button"; + newEntry["value"] = "Empty Log"; + newEntry["onclick"] = "emptyLog()"; + div.appendChild(createElement(newEntry)); + + var newEntry = new Object(); + newEntry["_element"] = "code"; + newEntry["_text"] = "Update Log: "; + div.appendChild(createElement(newEntry)); + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "checkbox"; + //newEntry["checked"] = "checkbox"; + newEntry["onclick"] = "logUpdates(this)"; + div.appendChild(createElement(newEntry)); + + var newEntry = new Object(); + newEntry["_element"] = "HR"; + div.appendChild(createElement(newEntry)); + + + + + + var newWrapper = new Object(); + newWrapper["_element"] = "DIV"; + newWrapper["id"] = "box-wrapper"; + div.appendChild(createElement(newWrapper)); + div = div.lastChild; + + var newPre = new Object(); + newPre["_element"] = "PRE"; + newPre["id"] = "logScreen"; + div.appendChild(createElement(newPre)); + + div = div.lastChild; + + writeLogInDiv() + return +} + +function emptyLog() { + var data = new Object(); + data["cmd"] = "emptyLog"; + xTeVe(data); + return +} + +function logUpdates(elm) { + switch(elm.checked) { + case false: clearInterval(logInterval); break; + case true: logInterval = setInterval(updateLog, 5000); break; + + } +} \ No newline at end of file diff --git a/html/js/logs_ts.js b/html/js/logs_ts.js new file mode 100644 index 0000000..ab2f812 --- /dev/null +++ b/html/js/logs_ts.js @@ -0,0 +1,42 @@ +var Log = /** @class */ (function () { + function Log() { + } + Log.prototype.createLog = function (entry) { + var element = document.createElement("PRE"); + if (entry.indexOf("WARNING") != -1) { + element.className = "warningMsg"; + } + if (entry.indexOf("ERROR") != -1) { + element.className = "errorMsg"; + } + if (entry.indexOf("DEBUG") != -1) { + element.className = "debugMsg"; + } + element.innerHTML = entry; + return element; + }; + return Log; +}()); +function showLogs(bottom) { + var log = new Log(); + var logs = SERVER["log"]["log"]; + var div = document.getElementById("content_log"); + div.innerHTML = ""; + var keys = getObjKeys(logs); + keys.forEach(function (logID) { + var entry = log.createLog(logs[logID]); + div.append(entry); + }); + setTimeout(function () { + if (bottom == true) { + var wrapper = document.getElementById("box-wrapper"); + wrapper.scrollTop = wrapper.scrollHeight; + } + }, 10); +} +function resetLogs() { + var cmd = "resetLogs"; + var data = new Object(); + var server = new Server(cmd); + server.request(data); +} diff --git a/html/js/mapping-editor.js b/html/js/mapping-editor.js new file mode 100644 index 0000000..471dfe3 --- /dev/null +++ b/html/js/mapping-editor.js @@ -0,0 +1,1466 @@ +var mappingError = false; +var bulk = false; +var bulkEditAll = false; +var selectObj = new Object(); +var searchObj = new Object(); + +var bulkIDs = new Array(); +var bulkChangeObj = new Object(); + +function checkUndo(key, elm) { + var tmp = new Object(); + tmp = elm + console.log("--"); + if (undo.hasOwnProperty("epgMapping")) { + xEPG["epgMapping"] = JSON.parse(JSON.stringify(undo["epgMapping"]));; + } else { + undo["epgMapping"] = JSON.parse(JSON.stringify(elm)); + } +} + +//var plexCategories = new Array("-", "Action sports", "Action", "Adults only", "Adventure", "Aerobics", "Animals", "Animated", "Anime", "Anthology", "Archery", "Art", "Arts/crafts", "Auction", "Auto racing", "Auto", "Aviation", "Awards", "Ballet", "Baseball", "Basketball", "Bicycle racing", "Bicycle", "Billiards", "Biography", "Boat racing", "Boat", "Bowling", "Boxing", "Bus./financial", "Children", "Collectibles", "Comedy drama", "Comedy", "Community", "Computers", "Consumer", "Cooking", "Crime drama", "Crime", "Dance", "Dark comedy", "Debate", "Diving", "Docudrama", "Documentary", "Drama", "Educational", "Entertainment", "Environment", "Equestrian", "Erotic", "Event", "Fantasy", "Fashion", "Feature Film", "Fishing", "Football", "Game show", "Gaming", "Gay/lesbian", "Golf", "Handball", "Health", "Historical drama", "History", "Hockey", "Holiday", "Home improvement", "Horror", "Horse", "House/garden", "How-to", "Interview", "Intl soccer", "Law", "Martial arts", "Medical", "Military", "Miniseries", "Mixed martial arts", "Motorcycle racing", "Motorcycle", "Motorsports", "Mountain biking", "Music", "Musical comedy", "Musical", "Mystery", "Nature", "News", "Newsmagazine", "Olympics", "Opera", "Outdoors", "Parade", "Paranormal", "Parenting", "Performing arts", "Playoff sports", "Poker", "Politics", "Pro wrestling", "Public affairs", "Reality", "Religious", "Rodeo", "Roller derby", "Romance", "Romantic comedy", "Rugby", "Running", "Sailing", "Science fiction", "Science", "Self improvement", "Series", "Shooting", "Shopping", "Short Film", "Sitcom", "Skiing", "Snooker", "Soap", "Soccer", "Special", "Sports", "sports", "Sports event", "Sports non-event", "Sports talk", "Standup", "Surfing", "Suspense", "TV Movie", "Talk", "Technology", "Tennis", "Theater", "Thriller", "Track/field", "Travel", "Triathlon", "Variety", "Volleyball", "War", "Watersports", "Weather", "Western", "Wrestling", "Yacht racing", "movie", "series", "sports", "tvshow"); +var plexCategoriesValues = new Array("-", "Kids", "News", "Movie", "Series", "Sports") +var plexCategoriesOption = new Array("-", "Kids (Emby only)", "News", "Movie", "Series", "Sports") + + +function openMappingEditor(elm) { + var columnToSort = 1 + + checkUndo("epgMapping", xEPG["epgMapping"]) + + var newDiv = document.getElementById("settings"); + + var newEntry = new Object(); + newEntry["_element"] = "HR"; + newDiv.appendChild(createElement(newEntry)); + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + newEntry["class"] = "button"; + newEntry["value"] = "Save"; + newEntry["onclick"] = "saveXEPG()"; + newDiv.appendChild(createElement(newEntry)); + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + newEntry["class"] = "button"; + newEntry["value"] = "Bulk Edit"; + newEntry["onclick"] = "bulkEdit()"; + newDiv.appendChild(createElement(newEntry)); + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + newEntry["class"] = "button"; + newEntry["value"] = "Show XEPG"; + newEntry["onclick"] = "showXEPG()"; + newDiv.appendChild(createElement(newEntry)); + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["class"] = "search"; + newEntry["id"] = "searchMapping"; + newEntry["type"] = "search"; + newEntry["placeholder"] = "Search"; + newEntry["onchange"] = "searchInMapping()"; + newDiv.appendChild(createElement(newEntry)); + + var div = document.getElementById("settings"); + //screenLog("Duplicate ID", "error", true) + + + // Build table + + var newWrapper = new Object(); + newWrapper["_element"] = "DIV"; + newWrapper["id"] = "box-wrapper"; + div.appendChild(createElement(newWrapper)); + + + var newTable = new Object(); + newTable["_element"] = "TABLE"; + newTable["id"] = "id_mapping"; + newTable["class"] = "table-mapping"; + div.lastChild.appendChild(createElement(newTable)); + showLoadingScreen(true); + + setTimeout(function(){ + createMappingTable(); + }, 10); + +} + +function createSearchObj() { + searchObj = new Object(); + var IDs = getObjKeys(xEPG["epgMapping"]) + for (var i = IDs.length - 1; i >= 0; i--) { + var item = xEPG["epgMapping"][IDs[i]]; + var searchID = item["x-epg"]; + var searchValue = ""; + searchValue = searchValue + item["x-channelID"] + " "; + searchValue = searchValue + item["x-category"] + " "; + searchValue = searchValue + item["x-name"] + " "; + searchValue = searchValue + item["x-group-title"] + " "; + searchValue = searchValue + item["x-xmltv-file"] + " "; + searchValue = searchValue + item["_file.m3u.name"] + " "; + + switch(item["x-active"]) { + case true: searchValue = searchValue + "online"; break; + case false: searchValue = searchValue + "offline"; break; + } + + searchObj[searchValue] = searchID; + + } +} + + +function calculateWrapperHeight() { + + if (document.getElementById("box-wrapper")){ + + var elm = document.getElementById("box-wrapper"); + + var divs = new Array("myStreamsBox", "clientInfo", "settings"); + var elementsHeight = 0 - elm.offsetHeight; + for (var i = 0; i < divs.length; i++) { + elementsHeight = elementsHeight + document.getElementById(divs[i]).offsetHeight; + } + + elm.style.height = window.innerHeight - elementsHeight + "px"; + + } + + if (document.getElementById("menu-wrapper")){ + + var elm = document.getElementById("menu-wrapper"); + + var offest = document.getElementById("settings").offsetHeight + document.getElementById("myStreamsBox").offsetHeight + document.getElementById("clientInfo").offsetHeight; + + if (window.innerHeight > offest) { + elm.style.height = window.innerHeight + "px" + } else { + elm.style.height = offest + "px" + } + + + } + + +} + +function createMappingTable() { + columnToSort = 1; + createSearchObj(); + + // Create table (Header) + var table = document.getElementById("id_mapping"); + table.innerHTML = ""; + var newTR = new Object(); + newTR["_element"] = "TR"; + newTR["class"] = "table-mapping-header"; + table.appendChild(createElement(newTR)); + + var tr = document.getElementById("id_mapping").lastChild; + var trHeadlines = new Array("Bulk", "Ch. No.", "Logo", "Channel Name", "Playlist", "Group Title", "XMLTV File", "XMLTV ID") + + for (var i = 0; i < trHeadlines.length; i++) { + var newTD = new Object(); + + newTD["_element"] = "TD"; + newTD["_text"] = trHeadlines[i]; + + + + var width = ""; + switch(trHeadlines[i]) { + + case "Bulk": + + maxWidth = "32px"; + minWidth = "32px"; + + // Create bulk TD + var newCheckbox = new Object(); + newCheckbox["_element"] = "INPUT"; + newCheckbox["type"] = "checkbox"; + newCheckbox["class"] = "bulk hideBulk"; + newCheckbox["onmouseout"] = "javascript: this.blur()" + newCheckbox["onclick"] = "javascript: bulkEditAllChannels()" + + + //newTD.appendChild(createElement(newCheckbox)); + + break; + + case "Ch. No.": + maxWidth = "80px"; + minWidth = "70px"; + newTD["onclick"] = "javscript: sortTable(" + i + ");"; + newTD["class"] = "pointer"; + break; + + case "Logo": maxWidth = "120px"; minWidth = "60px"; break; + + case "Channel Name": + maxWidth = "50%"; + minWidth = "200px"; + newTD["onclick"] = "javscript: sortTable(" + i + ");"; + newTD["class"] = "pointer"; + break; + + case "Playlist": + maxWidth = "150px"; + minWidth = "100px"; + newTD["onclick"] = "javscript: sortTable(" + i + ");"; + newTD["class"] = "pointer"; + break; + + case "Group Title": + maxWidth = "150px"; + minWidth = "100px"; + newTD["onclick"] = "javscript: sortTable(" + i + ");"; + newTD["class"] = "pointer"; + break; + + case "XMLTV File": + maxWidth = "150px"; + minWidth = "100px"; + //newTD["onclick"] = "javscript: sortTable(" + i + ");"; + newTD["class"] = ""; + break; + + + case "XMLTV ID": maxWidth = "150px"; minWidth = "100px"; break; + + default: + newTD["class"] = ""; + break; + } + + tr.appendChild(createElement(newTD)); + if (trHeadlines[i] == "Bulk") { + tr.lastChild.innerHTML = ""; + tr.lastChild.appendChild(createElement(newCheckbox)); + + } + + var elm = tr.lastChild; + elm.style.width = maxWidth; + elm.style.maxWidth = maxWidth; + elm.style.minWidth = minWidth; + + } + calculateWrapperHeight(); + var IDs = getObjKeys(xEPG["epgMapping"]) + + var allXmltvFiles = getObjKeys(xEPG["xmltvMap"]); + + if (allXmltvFiles == 0) { + showLoadingScreen(false); + return; + } + + // Sort IDs + var posObj = new Object(); + for (var i = 0; i < IDs.length; i++) { + var item = xEPG["epgMapping"][IDs[i]]; + var pos + switch(isNaN(xEPG["epgMapping"][IDs[i]]["x-channelID"])) { + case false: pos = parseFloat(xEPG["epgMapping"][IDs[i]]["x-channelID"]) ; break; + } + posObj[pos] = item; + } + posFloat = getObjKeys(posObj) + function sortFloat(a,b) { return a - b; } + posFloat.sort(sortFloat) + + //console.log(posFloat); + + // --- + + if (IDs.length > 200) { + setTimeout(function(){ + showLoadingScreen(true); + }, 1); + + } + + + // table for int channel ID's + for (var i = 0; i < posFloat.length; i++) { + + var table = document.getElementById("id_mapping"); + var item = posObj[posFloat[i]]; + //var item = xEPG["epgMapping"][IDs[i]]; + //console.log(item); + var newTR = new Object(); + newTR["_element"] = "TR"; + newTR["class"] = ""; + newTR["id"] = item["x-epg"]; + newTR["oncontextmenu"] = 'javascript: switchChannelStatus("' + item["x-epg"] + '"); return false;'; + table.appendChild(createElement(newTR)); + + var tr = document.getElementById("id_mapping").lastChild; + + // Create bulk TD + var newTD = new Object(); + newTD["_element"] = "INPUT"; + newTD["type"] = "checkbox"; + newTD["class"] = "bulk hideBulk"; + newTD["onmouseout"] = "javascript: this.blur()" + + createNewTD(newTD, tr); + + + // Create ID TD + var newTD = new Object(); + newTD["_element"] = "INPUT"; + newTD["type"] = "text" + newTD["class"] = "w40px"; + newTD["value"] = item["x-channelID"]; + newTD["onfocusout"] = "javascript: arrangeTable(this);" + createNewTD(newTD, tr); + + // Create IMG TD + var newTD = new Object(); + newTD["_element"] = "IMG"; + newTD["onclick"] = 'javascript: mappingDetail("' + item["x-epg"] + '");'; + if (item["tvg-logo"] != undefined) { + newTD["src"] = item["tvg-logo"]; + } else { + item["tvg-logo"] = ""; + newTD["src"] = ""; + } + createNewTD(newTD, tr); + tr.lastChild.setAttribute("onclick", 'javascript: mappingDetail("' + item["x-epg"] + '");') + + // Create P TD (channel name) + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["_text"] = item["x-name"]; + newTD["class"] = item["x-category"]; + + createNewTD(newTD, tr); + tr.lastChild.setAttribute("onclick", 'javascript: mappingDetail("' + item["x-epg"] + '");') + tr.lastChild.lastChild.style.padding = "5px 10px"; + + // Create P TD (Playlist Name) + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["_text"] = item["_file.m3u.name"]; + newTD["class"] = item["tableEllipsis"]; + + createNewTD(newTD, tr); + tr.lastChild.setAttribute("onclick", 'javascript: mappingDetail("' + item["x-epg"] + '");') + + // Create P TD (Group Title) + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["_text"] = item["x-group-title"]; + newTD["class"] = item["tableEllipsis"]; + + createNewTD(newTD, tr); + tr.lastChild.setAttribute("onclick", 'javascript: mappingDetail("' + item["x-epg"] + '");') + + + // Create P TD (XMLTV file) + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["class"] = "tableEllipsis"; + newTD["_text"] = "-" + + if (allXmltvFiles.indexOf(item["x-xmltv-file"]) != -1) { + var xXmltvFile = item["x-xmltv-file"]; + switch(xXmltvFile) { + case "-": newTD["_text"] = xXmltvFile; break; + case "xTeVe Dummy": newTD["_text"] = xXmltvFile; break; + default: newTD["_text"] = getValueFromProviderFile(xXmltvFile, "xmltv", "name"); break; + + } + //console.log(newTD); + + //newTD["_text"] = item["x-xmltv-file"]; + } else { + //newTD["_text"] = "-" + } + createNewTD(newTD, tr); + tr.lastChild.setAttribute("onclick", 'javascript: mappingDetail("' + item["x-epg"] + '");') + + // Creatr P TD (XMLTV channel ID) + newTD["_element"] = "P"; + newTD["class"] = "tableEllipsis"; + + if (item["x-mapping"] != undefined) { + newTD["_text"] = item["x-mapping"]; + } + + createNewTD(newTD, tr); + tr.lastChild.setAttribute("onclick", 'javascript: mappingDetail("' + item["x-epg"] + '");') + + + var xXmltvFile = item["x-xmltv-file"]; + var xMapping = item["x-mapping"]; + var tvgID = item["tvg-id"]; + + //console.log(item["x-epg"]); + //console.log(item); + + if (item["x-active"] == true) { + tr.className = "activeEPG"; + } else { + tr.className = "notActiveEPG"; + } + + } + + sortTable(1); + + setTimeout(function(){ + showLoadingScreen(false); + }, 5); +} + +function searchInMapping(elm) { + + var search = document.getElementById("searchMapping").value; + var values = getObjKeys(searchObj) + + for (var i = values.length - 1; i >= 0; i--) { + var id = searchObj[values[i]]; + var bool = values[i].toLowerCase().includes(search.toLowerCase()); + switch(bool) { + case true: document.getElementById(id).style.display = ""; break; + case false: document.getElementById(id).style.display = "none"; break; + } + } + +} + +function mappingDetail(xepg) { + + bulkIDs = new Array(); + var activeElement = document.activeElement; + // If input id, return + if (activeElement.tagName == "INPUT") { + return + } + + if (bulk == true) { + var elm = document.getElementsByClassName("bulk"); + for (var i = 1; i < elm.length; i++) { + if (elm[i].checked == true) { + var id = elm[i].parentElement.parentElement.id; + bulkIDs.push(id) + } + + } + + if (bulkIDs.length == 0) { + showElement('popup', false) + alert("No channels selected for editing") + return + } + + xepg = bulkIDs[0] + } + + + createSearchObj(); + + showPopUpElement('mapping-detail'); + + var thisChannel = xEPG["epgMapping"][xepg]; + //console.log(thisChannel); + var xXmltvFile = thisChannel["x-xmltv-file"]; + var xMapping = thisChannel["x-mapping"]; + var xCategory = thisChannel["x-category"]; + + if (xXmltvFile == undefined) { + thisChannel["x-xmltv-file"] = "-"; + xXmltvFile = "-"; + } + + if (xMapping == undefined) { + thisChannel["x-mapping"] = "-"; + xMapping = "-"; + } + + /* + console.log("ID:", xepg); + console.log("XMLTV File:", xXmltvFile); + console.log("Mapping:", xMapping); + */ + + var keys = getObjKeys(thisChannel); + for (var i = 0; i < keys.length; i++) { + if(document.getElementById(keys[i])){ + var td = document.getElementById(keys[i]) + } else { + var td = undefined; + } + + var newItem = new Object(); + var values, text = new Array(); + switch(keys[i]) { + case "x-xmltv-file": + var fileIDs = getObjKeys(xEPG["xmltvMap"]); + var value = new Array("-"); + var text = new Array("-"); + + for (var j = fileIDs.length - 1; j >= 0; j--) { + if (fileIDs[j] != "xTeVe Dummy") { + value.push(getValueFromProviderFile(fileIDs[j], "xmltv", "file.xteve")) + text.push(getValueFromProviderFile(fileIDs[j], "xmltv", "name")) + } else { + value.push(fileIDs[j]) + text.push(fileIDs[j]) + } + + } + + newItem["_element"] = "SELECT"; + newItem["_optionValues"] = value; + newItem["_optionText"] = text + newItem["value"] = xXmltvFile; + newItem["onchange"] = 'javascript: changeXmltvFile("' + xepg + '",this);'; + + break; + + case "x-mapping": + + var values = getObjKeys(xEPG["xmltvMap"][xXmltvFile]); + + for (var j = 0; j < values.length; j++) { + + if (xEPG["xmltvMap"][xXmltvFile][values[j]].hasOwnProperty('display-name') == true) { + var displayName = xEPG["xmltvMap"][xXmltvFile][values[j]]["display-name"]; + } else { + var displayName = "-" + } + + //text[j] = values[j] + " (" + displayName + ")"; + text[j] = displayName + " (" + values[j] + ")"; + } + + text.unshift("-"); + values.unshift("-"); + newItem["_element"] = "SELECT"; + newItem["_optionValues"] = values; + newItem["_optionText"] = text + newItem["value"] = xMapping; + newItem["onchange"] = 'javascript: mappingChannel("' + xepg + '",this);'; + break; + + case "x-category": + //var values = plexCategoriesValues + newItem["_element"] = "SELECT"; + newItem["_optionValues"] = plexCategoriesValues; + newItem["_optionText"] = plexCategoriesOption; + newItem["value"] = xCategory; + newItem["onchange"] = 'saveCategory("' + xepg + '")'; + break; + + case "tvg-logo": + document.getElementById("channel-logo").setAttribute("src", thisChannel["tvg-logo"]); + newItem["_element"] = "INPUT"; + newItem["type"] = "text"; + newItem["value"] = thisChannel["tvg-logo"]; + newItem["onfocusout"] = 'saveChannelLogo("' + xepg + '")'; + newItem["placeholder"] = 'Image URL'; + break; + + case "x-update-channel-icon": + newItem["_element"] = "INPUT"; + newItem["type"] = "checkbox"; + switch(JSON.parse(thisChannel["x-update-channel-icon"])) { + case true: newItem["checked"] = thisChannel["x-update-channel-icon"]; + break + } + newItem["onchange"] = 'saveChannelIconUpdate("' + xepg + '")'; + break; + + case "x-name": + newItem["_element"] = "INPUT"; + newItem["type"] = "text"; + newItem["value"] = thisChannel["x-name"]; + newItem["onfocusout"] = 'saveChannelName("' + xepg + '")'; + newItem["placeholder"] = 'Channel Name'; + break; + + case "x-update-channel-name": + if (thisChannel.hasOwnProperty("_uuid.key") == true) { + newItem["_element"] = "INPUT"; + newItem["type"] = "checkbox"; + switch(JSON.parse(thisChannel["x-update-channel-name"])) { + case true: newItem["checked"] = thisChannel["x-update-channel-name"]; + break + } + newItem["onchange"] = 'saveChannelNameUpdate("' + xepg + '")'; + showElement("streamHasCUID", true) + + break; + } else { + //streamHasCUID + showElement("streamHasCUID", false) + break; + } + + case "x-active": + newItem["_element"] = "INPUT"; + newItem["type"] = "checkbox"; + switch(JSON.parse(thisChannel["x-active"])) { + case true: newItem["checked"] = thisChannel["x-active"]; + break + } + newItem["onchange"] = 'saveChannelStatus("' + xepg + '")'; + break; + + case "x-group-title": + newItem["_element"] = "INPUT"; + newItem["type"] = "text"; + newItem["value"] = thisChannel["x-group-title"]; + newItem["onfocusout"] = 'saveGroupTitle("' + xepg + '")'; + newItem["placeholder"] = 'Group Title'; + break; + + default: + newItem["_element"] = "P"; + newItem["_text"] = thisChannel[keys[i]]; + break; + + } + + if (td != undefined) { + td.innerHTML = ""; + var element = createNewElement(newItem) + //console.log(element); + td.appendChild(element); + } + + } + + if (bulk == true) { + + var elm = document.getElementsByClassName("noBulk"); + for (var i = 0; i < elm.length; i++) { + elm[i].lastChild.setAttribute("readonly", true) + elm[i].lastChild.style.borderColor = "red"; + } + + xepg = bulkIDs[0] + } + + sortSelect(document.getElementById("x-xmltv-file").lastChild); + sortSelect(document.getElementById("x-mapping").lastChild); + +} + +function sortSelect(elem) { + + var tmpAry = []; + // Retain selected value before sorting + var selectedValue = elem[elem.selectedIndex].value; + // Grab all existing entries + for (var i=0;i 0) elem.options[0] = null; + // Restore sorted elements + var newSelectedIndex = 0; + for (var i=0;i 0 && xMapping != "-" && xMapping.length > 0) { + if (xEPG["xmltvMap"][xXmltvFile][xMapping].hasOwnProperty("icon")) { + var logoURL = xEPG["xmltvMap"][xXmltvFile][xMapping]["icon"]; + thisChannel["tvg-logo"] = logoURL; + document.getElementById(xepg).childNodes[2].lastChild.setAttribute("src", logoURL); + document.getElementById("channel-logo").setAttribute("src", logoURL); + } else { + alert("No logo URL in the XMLTV file available") + } + + } + + /* + if (xEPG["xmltvMap"][xXmltvFile][xMapping]["icon"] != undefined) { + + + } + */ + + } +} + +function saveChannelLogo(xepg) { + if (bulk == false) { + var thisChannel = xEPG["epgMapping"][xepg]; + thisChannel["tvg-logo"] = document.getElementById("tvg-logo").lastChild.value; + document.getElementById(xepg).childNodes[2].lastChild.setAttribute("src", thisChannel["tvg-logo"]); + mappingDetail(xepg); + return + } + + if (bulk == true) { + var key = "tvg-logo"; + var value = document.getElementById("tvg-logo").lastChild.value; + saveBulk(key, value); + + mappingDetail(xepg); + return + } +} + +function saveChannelIconUpdate(xepg) { + + var key = "x-update-channel-icon"; + var value = JSON.parse(document.getElementById("x-update-channel-icon").lastChild.checked); + if (bulk == false) { + var thisChannel = xEPG["epgMapping"][xepg]; + thisChannel[key] = value + updateChannelLogo(xepg) + + mappingDetail(xepg); + searchInMapping(); + return + } + + if (bulk == true) { + saveBulk(key, value); + mappingDetail(xepg); + return + } + +} + +function saveChannelName(xepg) { + if (bulk == false) { + var thisChannel = xEPG["epgMapping"][xepg]; + thisChannel["x-name"] = document.getElementById("x-name").lastChild.value; + document.getElementById(xepg).childNodes[3].lastChild.innerHTML = thisChannel["x-name"]; + mappingDetail(xepg); + searchInMapping(); + } + +} + +function saveChannelNameUpdate(xepg) { + var key = "x-update-channel-name"; + var value = JSON.parse(document.getElementById("x-update-channel-name").lastChild.checked); + + if (bulk == false) { + var thisChannel = xEPG["epgMapping"][xepg]; + thisChannel[key] = value + mappingDetail(xepg); + searchInMapping(); + return + } + + if (bulk == true) { + saveBulk(key, value); + mappingDetail(xepg); + return + } + +} + +function saveChannelStatus(xepg) { + var thisChannel = xEPG["epgMapping"][xepg]; + var xXmltvFile = thisChannel["x-xmltv-file"]; + + var key = "x-active"; + var value = JSON.parse(document.getElementById("x-active").lastChild.checked); + + if (xEPG["xmltvMap"].hasOwnProperty(xXmltvFile) == true) { + if (thisChannel["x-mapping"] != "-" && thisChannel["x-mapping"] != undefined) { + thisChannel["x-active"] = !thisChannel["x-active"]; + var tr = document.getElementById(xepg); + switch(thisChannel["x-active"]) { + case true: tr.className = "activeEPG"; break; + case false: tr.className = "notActiveEPG"; break; + } + + } else { + var err = "XMLTV Channel is not selected" + alert(err) + value = false + } + + } else { + if (value == true) { + var err = "XMLTV File is not selecte" + alert(err) + value = false + } + } + + + + if (bulk == false) { + var thisChannel = xEPG["epgMapping"][xepg]; + thisChannel[key] = value + mappingDetail(xepg); + searchInMapping(); + + var tr = document.getElementById(xepg); + switch(thisChannel["x-active"]) { + case true: tr.className = "activeEPG"; break; + case false: tr.className = "notActiveEPG"; break; + } + + return + } + + if (bulk == true) { + saveBulk(key, value); + mappingDetail(xepg); + return + } + +} + +function saveGroupTitle(xepg) { + var key = "x-group-title"; + var value = document.getElementById("x-group-title").lastChild.value; + + if (bulk == false) { + var thisChannel = xEPG["epgMapping"][xepg]; + document.getElementById(xepg).childNodes[5].lastChild.innerHTML = value; + thisChannel[key] = value; + mappingDetail(xepg); + searchInMapping(); + } + + if (bulk == true) { + saveBulk(key, value); + mappingDetail(xepg); + return + } + +} + +function saveCategory(xepg) { + var key = "x-category"; + var value = document.getElementById("x-category").lastChild.value; + + if (bulk == false) { + var thisChannel = xEPG["epgMapping"][xepg]; + thisChannel[key] = value + document.getElementById(xepg).childNodes[3].lastChild.className = value + mappingDetail(xepg); + searchInMapping(); + } + + if (bulk == true) { + saveBulk(key, value); + mappingDetail(xepg); + return + } + +} + +function arrangeTable(elm) { + var tr = elm.parentElement.parentElement; + var newPosition = elm.value; + var x_channelID = tr.id; + + switch(isNaN(newPosition)) { + case true: + alert("Ch. No. must be a number"); + mappingError = true; + break; + } + + + //var item = xEPG["epgMapping"][id]; + var keys = getObjKeys(xEPG["epgMapping"]) + for (var i = 0; i < keys.length; i++) { + var item = xEPG["epgMapping"][keys[i]]; + if (item["x-epg"] == x_channelID) { + + // Check if position exist + var oldPosition = item["x-channelID"]; + + if (oldPosition != newPosition) { + + console.log(newPosition, newPosition.length); + if (newPosition.length == 0) { + mappingError = true + newPosition = oldPosition; + + } + + if (mappingError == true) { + elm.value = oldPosition; + return; + } + + for (var j = keys.length - 1; j >= 0; j--) { + var channel = xEPG["epgMapping"][keys[j]]; + if (keys[j] != x_channelID) { + if (newPosition == channel["x-channelID"]) { // If position exist, set next free position. + newPosition++; + elm.value = newPosition; + arrangeTable(elm); + return; + /* + var newError = new Object(); + newError["err"] = "Duplicate ID"; + checkErr(newError); + sortTable(); + mappingError = true; + document.getElementById(x_channelID).getElementsByTagName("INPUT")[0].focus(); + return; + */ + } + } + } + + } + + //console.log(oldPosition, newPosition); + if (keys[i] == x_channelID && oldPosition != newPosition) { + item["x-channelID"] = newPosition; + } + + document.getElementById("logInfo").className = "notVisible"; + if (columnToSort == 1) { + sortTable(columnToSort); + } + mappingError = false; + + } + } +} + +function changeXmltvFile(xepg, elm) { + + var thisChannel = xEPG["epgMapping"][xepg]; + + var xXmltvFile = elm.value; + var channelID = thisChannel["tvg-id"]; + thisChannel["x-xmltv-file"] = xXmltvFile; + + if (bulk == false) { + + setTimeout(function(){ + + var xMapping = "-" + + // Automap + if (xXmltvFile != "-") { + if (xEPG["xmltvMap"][xXmltvFile].hasOwnProperty(channelID) == true) { + thisChannel["x-mapping"] = channelID; + xMapping = channelID + } else { + thisChannel["x-mapping"] = xMapping + } + } else { + thisChannel["x-mapping"] = xMapping + + } + + var tr = document.getElementById(xepg); + + if (xMapping == "-") { + thisChannel["x-active"] = false; + tr.className = "notActiveEPG" + } else { + thisChannel["x-active"] = true; + tr.className = "activeEPG" + } + + // Show data in table + var td = tr.getElementsByTagName("TD"); + var dataFile = td[td.length - 2].lastChild; + switch(xXmltvFile) { + case "-": dataFile.innerHTML = xXmltvFile; break; + case "xTeVe Dummy": dataFile.innerHTML = xXmltvFile; break; + default: dataFile.innerHTML = getValueFromProviderFile(xXmltvFile, "xmltv", "name"); break; + } + + //xXmltvFile.replace(/^.*[\\\/]/, ''); + + var dataXmltvID = td[td.length - 1].lastChild; + dataXmltvID.innerHTML = xMapping; + + mappingDetail(xepg); + + }, 10); + } + + if (bulk == true) { + var key = "x-xmltv-file" + var value = xXmltvFile + saveBulk(key, value); + + var key = "x-mapping" + var value = "-" + saveBulk(key, value); + mappingDetail(xepg); + return + } + + return +} + +function mappingChannel(xepg, elm) { + var thisChannel = xEPG["epgMapping"][xepg]; + //var xMapping = elm.value; + var xMapping = elm.options[elm.selectedIndex].value + + if (bulk == false) { + + thisChannel["x-mapping"] = xMapping; + + var tr = document.getElementById(xepg); + + if (xMapping == "-") { + thisChannel["x-active"] = false; + tr.className = "notActiveEPG" + } else { + thisChannel["x-active"] = true; + tr.className = "activeEPG" + } + + // Show data in table + var td = tr.getElementsByTagName("TD"); + var dataXmltvID = td[td.length - 1].lastChild; + dataXmltvID.innerHTML = xMapping; + //console.log(td[td.length - 1]); + //console.log(xMapping, elm); + + createSearchObj(); + searchInMapping(); + updateChannelLogo(xepg) + mappingDetail(xepg); + return + } + + if (bulk == true) { + + var key = "x-mapping" + var value = xMapping + saveBulk(key, value); + + mappingDetail(xepg); + return + } + + return +} + + +function createNewTD(newItem, elm) { + var newTD = new Object(); + newTD["_element"] = "TD"; + + elm.appendChild(createElement(newTD)); + var td = elm.lastChild; + + switch(newItem["_element"]) { + case "SELECT": + td.appendChild(createElement(newItem)); + var td = elm.lastChild.lastChild; + var values = newItem["_optionValues"]; + for (var i = 0; i < values.length; i++) { + //console.log(item); + var newEntry = new Object; + newEntry["_element"] = "OPTION"; + newEntry["_text"] = values[i]; + newEntry["value"] = values[i]; + td.appendChild(createElement(newEntry)); + } + td.value = newItem["value"]; + + break; + + default: + + td.appendChild(createElement(newItem)); + break; + } + +} + +function saveXEPG() { + if (mappingError == true) { + alert("Data could not be saved, errors in the XEPG data."); + return; + } + showLoadingScreen(true); + + var data = new Object(); + data["epgMapping"] = xEPG["epgMapping"]; + data["cmd"] = "saveEpgMapping"; + //console.log(data); + xTeVe(data); +} + +function bulkEdit() { + bulk = !bulk; + var className; + + var elm = document.getElementsByClassName("bulk"); + + switch(bulk) { + case true: + className = "bulk showBulk"; + break; + + case false: + className = "bulk hideBulk"; + bulkEditAll = false; + break; + } + + for (var i = 0; i < elm.length; i++) { + elm[i].className = className; + elm[i].checked = false; + } + +} + +function bulkEditAllChannels() { + + var allTR = document.getElementById("id_mapping").getElementsByTagName("TR"); + + for (var i = 1; i < allTR.length; i++) { + if (allTR[i].style.display != "none") { + switch(bulkEditAll) { + case false: allTR[i].firstChild.firstChild.checked = true; break; + case true: allTR[i].firstChild.firstChild.checked = false; break; + } + + } + + } + + bulkEditAll = !bulkEditAll; +} + +function sortTable(columm) { + //console.log(columm); + if (columm == columnToSort) { + //return; + } + + var table = document.getElementById("id_mapping"); + var tableHead = table.getElementsByTagName("TR")[0]; + var tableItems = tableHead.getElementsByTagName("TD"); + + var sortObj = new Object(); + var x, xValue; + var tableHeader + var sortByString = false + + if (columm > 0 && columnToSort > 0) { + tableItems[columnToSort].className = "pointer"; + tableItems[columm].className = "sortThis"; + } + + columnToSort = columm; + + var rows = table.rows; + + if (rows[1] != undefined) { + tableHeader = rows[0] + + x = rows[1].getElementsByTagName("TD")[columm]; + + for (i = 1; i < rows.length; i++) { + + x = rows[i].getElementsByTagName("TD")[columm]; + + switch(x.childNodes[0].tagName.toLowerCase()) { + case "input": + xValue = x.getElementsByTagName("INPUT")[0].value.toLowerCase(); + break; + + case "p": + xValue = x.getElementsByTagName("P")[0].innerText.toLowerCase(); + break; + + default: console.log(x.childNodes[0].tagName); + } + + if (xValue == "" || xValue == NaN) { + xValue = i + sortObj[i] = rows[i]; + + } else { + + switch(isNaN(xValue)) { + case false: + + xValue = parseFloat(xValue); + sortObj[xValue] = rows[i] + break; + + case true: + + sortByString = true + sortObj[xValue.toLowerCase() + i] = rows[i] + break; + + } + + } + + } + + while (table.firstChild) { + table.removeChild(table.firstChild); + } + + var sortValues = getObjKeys(sortObj) + if (sortByString == true) { + sortValues.sort() + } else { + function sortFloat(a, b) { + return a - b; + } + sortValues.sort(sortFloat); + } + + table.appendChild(tableHeader) + + for (var i = 0; i < sortValues.length; i++) { + + table.appendChild(sortObj[sortValues[i]]) + + } + + } + +} + + +function sortTable_old(columm) { + showLoadingScreen(true); + + setTimeout(function(){ + + var table, rows, switching, i, x, y, shouldSwitch; + table = document.getElementById("id_mapping"); + + var tableHead = table.getElementsByTagName("TR")[0]; + var tableItems = tableHead.getElementsByTagName("TD"); + + if (columm > 0) { + tableItems[columnToSort].className = "pointer"; + tableItems[columm].className = "sortThis"; + } + + columnToSort = columm; + + /* + for (var i = 0; i < tableItems.length; i++) { + if (tableItems[i].className != undefined) { + tableItems[i].className = "pointer" + } + + } + */ + + + + console.log(tableItems); + + switching = true; + while (switching) { + switching = false; + rows = table.rows; + for (i = 1; i < (rows.length - 1); i++) { + shouldSwitch = false; + + x = rows[i].getElementsByTagName("TD")[columm]; + y = rows[i + 1].getElementsByTagName("TD")[columm]; + + switch(x.childNodes[0].tagName.toLowerCase()) { + case "input": + xValue = x.getElementsByTagName("INPUT")[0].value.toLowerCase(); + yValue = y.getElementsByTagName("INPUT")[0].value.toLowerCase(); + break; + + case "p": + xValue = x.getElementsByTagName("P")[0].innerText.toLowerCase(); + yValue = y.getElementsByTagName("P")[0].innerText.toLowerCase(); + break; + + default: console.log(x.childNodes[0].tagName); + } + + + switch(isNaN(xValue)) { + case false: xValue = parseFloat(xValue) ; break; + } + + switch(isNaN(yValue)) { + case false: yValue = parseFloat(yValue) ; break; + } + + + if (xValue > yValue) { + shouldSwitch = true; + break; + } + + } + if (shouldSwitch) { + rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); + switching = true; + } + } + createSearchObj() + + showLoadingScreen(false); + }, 20); + +} + +function showXEPG() { + var url = location.protocol + "//" + location.hostname + ":" + location.port + "/xmltv/xteve.xml" + var win = window.open(url, '_blank'); + win.focus(); +} \ No newline at end of file diff --git a/html/js/menu.js b/html/js/menu.js new file mode 100644 index 0000000..962e921 --- /dev/null +++ b/html/js/menu.js @@ -0,0 +1,754 @@ + +function setMenuItem() { + + menu = new Object(); + subMenu = new Object(); + + var menu_m3u = new Object(); + menu_m3u["_menuType"] = "inputArray"; + menu_m3u["_element"] = "LI"; + menu_m3u["_configKey"] = "files.m3u"; + menu_m3u["_text"] = "Playlist"; + menu_m3u["_icon"] = "img/m3u.png"; + menu_m3u["_headline"] = "Playlists: Local or remote"; + menu_m3u["_usage"] = "Info
Availability: File availability in percent
Streams: Number of streams in the file.
group-title: Streams that are assigned to a group. Simplifies filtering streams
tvg-id: This ID is used for automatic mapping, must match with the channel ID in the XMLTV file.
Unique ID: Streams with a unique ID to identify them. Allows channel name changes in the M3U without losing the XMLTV mapping (PPV / live events).

Usage M3U:
Remote playlist: http://your.iptv.provider.com/file.m3u
Local playlist: /path/to/file.m3u

Usage HDHomeRun:
IP: 192.168.1.10:5004
" + menu_m3u["name"] = "file"; + menu_m3u["id"] = "file"; + menu_m3u["value"] = menu_m3u["name"]; + menu_m3u["placeholder"] = "Playlist: local or remote"; + menu_m3u["onclick"] = "javascript: toggleMenu(this);"; + menu_m3u["class"] = "menu-notActive"; + + + var menu_filter = new Object(); + menu_filter["_menuType"] = "inputArray"; + menu_filter["_element"] = "LI"; + menu_filter["_configKey"] = "filter"; + menu_filter["_text"] = "Filter"; + menu_filter["_icon"] = "img/filter.png"; + menu_filter["_headline"] = "Filter by M3U parameters, e.g. group-title"; + menu_filter["_usage"] = "Usage:
Sport - All sports channels
Sport {HD} - All HD sports channels
Sport {HD} !{ES,DE} - All HD sports channels, but no Spanish and German

To filter the streams of a HDHomeRun, the playlist name can be entered:
My tuner {HD}" + //menu_filter["_usage"] = "Usage:
All sports channels: Sport
All HD sports channels: Sport {HD}
All HD sports channels, but no Spanish and German: Sport {HD} !{ES,DE}" + menu_filter["name"] = "filter"; + menu_filter["id"] = "M3U"; + menu_filter["value"] = menu_filter["name"]; + menu_filter["placeholder"] = "Filter streams: Sport"; + menu_filter["onclick"] = "javascript: toggleMenu(this);"; + menu_filter["class"] = "menu-notActive"; + + var menu_id = new Object(); + menu_id["_menuType"] = "inputArray"; + menu_id["_element"] = "LI"; + menu_id["_configKey"] = "id"; + menu_id["_text"] = "PMS ID"; + menu_id["_icon"] = "img/number.png"; + menu_id["_headline"] = "Setup PMS guide number"; + menu_id["_usage"] = 'Some playlists have unique channel IDs.
Enter the keyword of the ID. The channel assignment in PMS will change as a result.

e.g. channelID
#EXTINF:0 type="stream" channelId="81", My Streaming Channel HD

Only enter here if you know what you are doing!' + menu_id["name"] = "id"; + menu_id["id"] = "id"; + menu_id["value"] = menu_id["name"]; + menu_id["placeholder"] = "Unique ID from the M3U file"; + menu_id["onclick"] = "javascript: toggleMenu(this);"; + menu_id["class"] = "menu-notActive"; + + + var menu_xmltv = new Object(); + menu_xmltv["_menuType"] = "inputArray"; + menu_xmltv["_element"] = "LI"; + menu_xmltv["_configKey"] = "files.xmltv"; + menu_xmltv["_text"] = "XMLTV"; + menu_xmltv["_icon"] = "img/xmltv.png"; + menu_xmltv["_headline"] = "XMLTV files: Local or remote"; + menu_xmltv["_usage"] = "Info:
Availability: File availability in percent
Channels: Number of channels in the file
Programs: Number of EPG data

Usage:
Remote XMLTV file: http://your.epg.provider.com/guide.xml
Local XMLTV file: /path/to/guide.xml" + menu_xmltv["name"] = "xmltv"; + menu_xmltv["id"] = "xmltv"; + menu_xmltv["value"] = menu_xmltv["name"]; + menu_xmltv["placeholder"] = "XMLTV File: local or remote"; + menu_xmltv["onclick"] = "javascript: toggleMenu(this);"; + menu_xmltv["class"] = "menu-notActive"; + + menu_mapping = new Object(); + menu_mapping["_element"] = "LI"; + menu_mapping["_text"] = "Mapping"; + menu_mapping["_icon"] = "img/mapping.png"; + menu_mapping["_configKey"] = "mapping"; + menu_mapping["_headline"] = "XMLTV assignment and sorting of channels"; + menu_mapping["id"] = "mapping"; + menu_mapping["onclick"] = "javascript: toggleMenu(this);"; + menu_mapping["class"] = "menu-notActive phone"; + + menu_users = new Object(); + menu_users["_element"] = "LI"; + menu_users["_text"] = "Users"; + menu_users["_icon"] = "img/users.png"; + menu_users["_configKey"] = "users"; + menu_users["_headline"] = "Administration of users and permissions"; + menu_users["id"] = "users"; + menu_users["onclick"] = "javascript: toggleMenu(this);"; + menu_users["class"] = "menu-notActive"; + menu_users["_usage"] = "Authorization groups:
WEB: Users can log in to the web interface
PMS: Programs like Plex can access the channel list. Login via DVR IP: username:password@xteve.ip:port
M3U: Allows clients to download the M3U playlist.
XML: Allows clients to download the XMLTV file.
API: Allows clients to use the API interface.

!!! For PMS authentication, only the following special characters are valid: !$()=.,-:;

The individual authentication groups can be activated / deactivated in the settings menu." + + menu_settings = new Object(); + menu_settings["_element"] = "LI"; + menu_settings["_text"] = "Settings"; + menu_settings["_icon"] = "img/settings.png"; + menu_settings["_configKey"] = "settings"; + menu_settings["_headline"] = "Settings"; + menu_settings["_subMenu"] = "701,702,703,704,705,706,707,708,799,710,711,712,713,714"; + menu_settings["id"] = "settings"; + menu_settings["onclick"] = "javascript: toggleMenu(this);"; + menu_settings["class"] = "menu-notActive"; + + menu_log = new Object(); + menu_log["_element"] = "LI"; + menu_log["_text"] = "Log"; + menu_log["_icon"] = "img/log.png"; + menu_log["_headline"] = "Log"; + menu_log["_configKey"] = "log"; + menu_log["id"] = "log"; + menu_log["onclick"] = "javascript: toggleMenu(this);"; + menu_log["class"] = "menu-notActive"; + + menu_logout = new Object(); + menu_logout["_element"] = "LI"; + menu_logout["_text"] = "Logout"; + menu_logout["_icon"] = "img/logout.png"; + menu_logout["id"] = "logout"; + menu_logout["onclick"] = "javascript: logout();"; + menu_logout["class"] = "menu-notActive"; + + var menu_schedule = new Object(); + menu_schedule["_menuType"] = "inputArray"; + menu_schedule["_element"] = "LI"; + menu_schedule["_configKey"] = "update"; + menu_schedule["_text"] = "Schedule"; + menu_schedule["_icon"] = "img/schedule.png"; + menu_schedule["_headline"] = "Schedule for updating M3U, XMLTV files and creating a local backup"; + menu_schedule["_usage"] = "Usage:
0815 = 8:15 am
1930 = 7:30 pm" + menu_schedule["name"] = "update"; + menu_schedule["id"] = "update"; + menu_schedule["value"] = menu_id["name"]; + menu_schedule["placeholder"]= "time of day (24-hour clock)"; + menu_schedule["onclick"] = "javascript: toggleMenu(this);"; + menu_schedule["class"] = "menu-notActive"; + + var menu_filesUpdate = new Object(); + menu_filesUpdate["_element"] = "LI"; + menu_filesUpdate["_menuType"] = "checkbox"; + menu_filesUpdate["_configKey"] = "files.update"; + menu_filesUpdate["_label"] = "Update the provider files at system startup"; + menu_filesUpdate["_headline"] = "Update the provider files at system startup"; + menu_filesUpdate["_usage"] = "Playlists and XMLTV files are updated by xTeVe at system startup." + menu_filesUpdate["name"] = "files.update"; + menu_filesUpdate["id"] = "files.update"; + menu_filesUpdate["value"] = menu_filesUpdate["name"]; + menu_filesUpdate["onclick"] = "javascript: toggleMenu(this);"; + menu_filesUpdate["class"] = "menu-notActive"; + + var menu_tuner = new Object(); + menu_tuner["_element"] = "LI"; + menu_tuner["_menuType"] = "select"; + menu_tuner["_configKey"] = "tuner"; + menu_tuner["_label"] = "Available tuners"; + menu_tuner["_text"] = "Tuner"; + menu_tuner["_icon"] = "img/tuner.png"; + menu_tuner["_headline"] = "Number of tuners"; + menu_tuner["_usage"] = "This setting is only used by Plex and Emby.
The number of concurrent streams allowed by the IPTV provider.
After a change, xTeVe must be delete in the PMS DVR settings and set up again." + menu_tuner["name"] = "tuner"; + menu_tuner["id"] = "tuner"; + menu_tuner["value"] = menu_tuner["name"]; + menu_tuner["placeholder"] = "Number of tuners"; + menu_tuner["onclick"] = "javascript: toggleMenu(this);"; + menu_tuner["class"] = "menu-notActive"; + + var optionValues = new Array(); + for (var i = 1; i <= 100; i++) { + optionValues.push(i) + } + menu_tuner["_optionValues"] = optionValues; + + var menu_epg = new Object(); + menu_epg["_element"] = "LI"; + menu_epg["_menuType"] = "select"; + menu_epg["_configKey"] = "epgSource"; + menu_epg["_label"] = "Selection of the EPG source"; + menu_epg["_text"] = "EPG source"; + menu_epg["_headline"] = "Selection of the EPG source"; + menu_epg["_usage"] = "PMS: Use EPG data from Plex or Emby.
XEPG: Use of external EPG data (XMLTV).
Several XMLTV sources possible.
Allows editing and order channels.
M3U / XMLTV export (HTTP link for IPTV apps)." + menu_epg["name"] = "epgSource"; + menu_epg["id"] = "epgSource"; + menu_epg["value"] = menu_epg["name"]; + menu_epg["placeholder"] = "EPG source"; + menu_epg["onclick"] = "javascript: toggleMenu(this);"; + menu_epg["class"] = "menu-notActive"; + menu_epg["_optionValues"] = new Array("PMS", "XEPG"); + + var menu_xepg = new Object(); + menu_xepg["_element"] = "LI"; + menu_xepg["_menuType"] = "checkbox"; + menu_xepg["_configKey"] = "xteveAutoUpdate"; + menu_xepg["_label"] = "Automatic update of xTeVe"; + menu_xepg["_headline"] = "Automatic update of xTeVe"; + menu_xepg["_usage"] = "If a new version of xTeVe is available, it will be automatically installed." + menu_xepg["name"] = "xteveAutoUpdate"; + menu_xepg["id"] = "xteveAutoUpdate"; + menu_xepg["value"] = menu_xepg["name"]; + menu_xepg["onclick"] = "javascript: toggleMenu(this);"; + menu_xepg["class"] = "menu-notActive"; + + var menu_autoBackupPath = new Object(); + menu_autoBackupPath["_element"] = "LI"; + menu_autoBackupPath["_menuType"] = "singleInput"; + menu_autoBackupPath["_configKey"] = "backup.path"; + menu_autoBackupPath["_label"] = "Location for automatic backups"; + menu_autoBackupPath["_headline"] = "Location for automatic backups"; + menu_autoBackupPath["_usage"] = "Before any update of the provider data by the schedule, xTeVe creates a backup. The path for the automatic backups can be changed. xTeVe requires write permission for this folder." + menu_autoBackupPath["name"] = "backup.path"; + menu_autoBackupPath["id"] = "backup.path"; + menu_autoBackupPath["value"] = menu_autoBackupPath["name"]; + menu_autoBackupPath["onclick"] = "javascript: toggleMenu(this);"; + menu_autoBackupPath["class"] = "menu-notActive"; + + var menu_autoBackupKeep = new Object(); + menu_autoBackupKeep["_element"] = "LI"; + menu_autoBackupKeep["_menuType"] = "select"; + menu_autoBackupKeep["_configKey"] = "backup.keep"; + menu_autoBackupKeep["_text"] = "Keep"; + menu_autoBackupKeep["_label"] = "Number of backups to keep"; + menu_autoBackupKeep["_headline"] = "Number of backups to keep"; + menu_autoBackupKeep["_usage"] = "" + menu_autoBackupKeep["name"] = "backup.keep"; + menu_autoBackupKeep["id"] = "backup.keep"; + menu_autoBackupKeep["value"] = menu_autoBackupKeep["name"]; + menu_autoBackupKeep["onclick"] = "javascript: toggleMenu(this);"; + menu_autoBackupKeep["class"] = "menu-notActive"; + + var optionValues = new Array(5, 10, 20, 30, 40, 50); + menu_autoBackupKeep["_optionValues"] = optionValues; + + + var menu_buffer = new Object(); + menu_buffer["_element"] = "LI"; + menu_buffer["_menuType"] = "checkbox"; + menu_buffer["_configKey"] = "buffer"; + menu_buffer["_label"] = "Stream buffering [Experimental]"; + menu_buffer["_headline"] = "Stream buffering [Experimental]"; + menu_buffer["_usage"] = "With activated buffer, streams can be played and recorded more fluently.
The stream is passed from xTeVe to Plex / Emby" + menu_buffer["name"] = "buffer"; + menu_buffer["id"] = "buffer"; + menu_buffer["value"] = menu_buffer["name"]; + menu_buffer["onclick"] = "javascript: toggleMenu(this);"; + menu_buffer["class"] = "menu-notActive"; + + var menu_api = new Object(); + menu_api["_element"] = "LI"; + menu_api["_menuType"] = "checkbox"; + menu_api["_configKey"] = "api"; + menu_api["_label"] = "API interface"; + menu_api["_headline"] = "API interface"; + menu_api["_usage"] = 'Via API interface it is possible to send commands to xTeVe. API documentation is available here ' + //menu_api["_usage"] = 'Via API interface it is possible to send commands to xTeVe. API documentation is available here ' + menu_api["name"] = "api"; + menu_api["id"] = "api"; + menu_api["value"] = menu_api["name"]; + menu_api["onclick"] = "javascript: toggleMenu(this);"; + menu_api["class"] = "menu-notActive"; + + var menu_authenticationWeb = new Object(); + menu_authenticationWeb["_element"] = "LI"; + menu_authenticationWeb["_menuType"] = "checkbox"; + menu_authenticationWeb["_configKey"] = "authentication.web"; + menu_authenticationWeb["_label"] = "User authentication"; + menu_authenticationWeb["_headline"] = "User authentication"; + menu_authenticationWeb["_usage"] = "Access to xTeVe requires authentication." + menu_authenticationWeb["name"] = "authentication.web"; + menu_authenticationWeb["id"] = "authentication.web"; + menu_authenticationWeb["value"] = menu_authenticationWeb["name"]; + menu_authenticationWeb["onclick"] = "javascript: toggleMenu(this);"; + menu_authenticationWeb["class"] = "menu-notActive"; + + var menu_authenticationPms = new Object(); + menu_authenticationPms["_element"] = "LI"; + menu_authenticationPms["_menuType"] = "checkbox"; + menu_authenticationPms["_configKey"] = "authentication.pms"; + menu_authenticationPms["_label"] = "Plex authentication."; + menu_authenticationPms["_headline"] = "Plex authentication."; + menu_authenticationPms["_usage"] = "Plex requests are only possible with authentication.
Warning!!! After activating this function xTeVe must be delete in the PMS DVR settings and set up again." + menu_authenticationPms["name"] = "authentication.pms"; + menu_authenticationPms["id"] = "authentication.pms"; + menu_authenticationPms["value"] = menu_authenticationPms["name"]; + menu_authenticationPms["onclick"] = "javascript: toggleMenu(this);"; + menu_authenticationPms["class"] = "menu-notActive"; + + var menu_authenticationM3u = new Object(); + menu_authenticationM3u["_element"] = "LI"; + menu_authenticationM3u["_menuType"] = "checkbox"; + menu_authenticationM3u["_configKey"] = "authentication.m3u"; + menu_authenticationM3u["_label"] = "M3U authentication."; + menu_authenticationM3u["_headline"] = "M3U authentication."; + menu_authenticationM3u["_usage"] = "Downloading the M3U file via an HTTP request is only possible with authentication." + menu_authenticationM3u["name"] = "authentication.m3u"; + menu_authenticationM3u["id"] = "authentication.m3u"; + menu_authenticationM3u["value"] = menu_authenticationM3u["name"]; + menu_authenticationM3u["onclick"] = "javascript: toggleMenu(this);"; + menu_authenticationM3u["class"] = "menu-notActive"; + + + var menu_authenticationXml = new Object(); + menu_authenticationXml["_element"] = "LI"; + menu_authenticationXml["_menuType"] = "checkbox"; + menu_authenticationXml["_configKey"] = "authentication.xml"; + menu_authenticationXml["_label"] = "XEPG authentication"; + menu_authenticationXml["_headline"] = "XEPG authentication"; + menu_authenticationXml["_usage"] = "Downloading the XEPG (XMLTV) file via an HTTP request is only possible with authentication." + menu_authenticationXml["name"] = "authentication.xml"; + menu_authenticationXml["id"] = "authentication.xml"; + menu_authenticationXml["value"] = menu_authenticationXml["name"]; + menu_authenticationXml["onclick"] = "javascript: toggleMenu(this);"; + menu_authenticationXml["class"] = "menu-notActive"; + + var menu_authenticationApi = new Object(); + menu_authenticationApi["_element"] = "LI"; + menu_authenticationApi["_menuType"] = "checkbox"; + menu_authenticationApi["_configKey"] = "authentication.api"; + menu_authenticationApi["_label"] = "API authentication"; + menu_authenticationApi["_headline"] = "API authentication"; + menu_authenticationApi["_usage"] = "Access to the API interface is only possible with authentication." + menu_authenticationApi["name"] = "authentication.api"; + menu_authenticationApi["id"] = "authentication.api"; + menu_authenticationApi["value"] = menu_authenticationApi["name"]; + menu_authenticationApi["onclick"] = "javascript: toggleMenu(this);"; + menu_authenticationApi["class"] = "menu-notActive"; + + + // Main menu + menu[10] = menu_m3u; + + switch(config["epgSource"]) { + case "PMS": + menu[20] = menu_id; + break; + + case "XMLTV": + menu[40] = menu_xmltv; + break; + + case "XEPG": + menu[40] = menu_xmltv; + menu[50] = menu_mapping; + break; + } + + menu[30] = menu_filter; + + if (config["authentication.web"] == true) { + menu[60] = menu_users; + } + + menu[70] = menu_settings; + menu[80] = menu_log; + if (config["authentication.web"] == true) { + menu[100] = menu_logout; + } + + + // Sub-Menu + + subMenu[701] = menu_schedule; + subMenu[702] = menu_filesUpdate; + subMenu[703] = menu_tuner; + subMenu[704] = menu_epg; + subMenu[705] = menu_xepg; + subMenu[706] = menu_autoBackupPath; + subMenu[707] = menu_autoBackupKeep; + subMenu[708] = menu_buffer; + + subMenu[710] = menu_authenticationWeb; + + if (config["authentication.web"] == true) { + subMenu[711] = menu_authenticationPms; + subMenu[712] = menu_authenticationM3u; + subMenu[713] = menu_authenticationXml; + subMenu[714] = menu_authenticationApi; + } + + subMenu[799] = menu_api; + + + + return +} + +function createMenu() { + + showElement("popup", false); + + //console.log(config); + setMenuItem(); + var menuItems = getObjKeys(menu) + var nav = document.getElementsByTagName("NAV")[0]; + nav.innerHTML = ""; + var newItem = new Object(); + + for (var i = 0; i < menuItems.length; i++) { + + + var newItem = menu[menuItems[i]]; + newItem["id"] = menuItems[i]; + + + switch(newItem.hasOwnProperty("_icon")) { + case true: + var itemText = newItem["_text"]; + delete newItem["_text"] + nav.appendChild(createElement(newItem)); + newItem["_text"] = itemText; + var newIcon = new Object(); + newIcon["_element"] = "IMG"; + newIcon["src"] = newItem["_icon"]; + + var currentElement = document.getElementById(menuItems[i]); + currentElement.appendChild(createElement(newIcon)); + + + var text = new Object(); + text["_element"] = "P" + text["_text"] = itemText; + text["class"] = "nav-text" + currentElement.appendChild(createElement(text)); + break; + + default: + nav.appendChild(createElement(newIcon)); + break; + } + + } + if (activeMenu != undefined) { + //console.log(activeMenu); + toggleMenu(activeMenu); + } + + return +} + +function toggleMenu(elm) { + //showStreams(false); + clearInterval(logInterval) + activeMenu = elm; + var item = menu[elm.id] + var div = document.getElementById("settings"); + div.innerHTML = ""; + + // Set Headline + var headline = new Object(); + headline["_element"] = "H4"; + headline["_text"] = item["_headline"]; + div.appendChild(createElement(headline)); + + // Sub-Menu + if (item.hasOwnProperty("_subMenu") == true) { + openSubMenu(item); + return + } + + // Mapping, Users, Log, Files + switch(item["_configKey"]) { + case "mapping": openMappingEditor(item); return; break; + case "users": openUsers(item); return; break; + case "log": showLog(item); return; break; + case "files.m3u": openFiles(item, "m3u"); return; break; + case "files.xmltv": openFiles(item, "xmltv"); return; break; + + case "filter": showStreams(true); break; + } + + + + var newHR = new Object(); + newHR["_element"] = "HR" + div.appendChild(createElement(newHR)); + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + //newEntry["class"] = "save"; + newEntry["value"] = "Save"; + newEntry["onclick"] = "saveData2('settings')" + div.appendChild(createElement(newEntry)); + + + var newWrapper = new Object(); + newWrapper["_element"] = "DIV"; + newWrapper["id"] = "box-wrapper"; + div.appendChild(createElement(newWrapper)); + + div = div.lastChild; + + div.appendChild(createMenuItem(item)) + + // usage Info + switch(menu[activeMenu.id].hasOwnProperty("_usage")) { + case true: + var usageItem = new Object(); + usageItem["_element"] = "PRE" + usageItem["_text"] = menu[activeMenu.id]["_usage"]; + div.appendChild(createElement(usageItem)); + } + + calculateWrapperHeight(); + +} + +function createMenuItem(item) { + var element = document.createElement("DIV"); + switch(item["_menuType"]) { + case "inputArray": + if (config.hasOwnProperty(item["_configKey"]) == true) { + var value = config[item["_configKey"]]; + } else { + var value = new Array(); + } + + for (var i = 0; i < value.length; i++) { + var newEntry = new Object(); + newEntry = item + delete newEntry["onclick"]; + newEntry["_element"] = "INPUT"; + newEntry["value"] = value[i]; + newEntry["type"] = "search"; + newEntry["data-menutype"] = item["_menuType"]; + newEntry["data-menukey"] = item["_configKey"]; + element.appendChild(createElement(newEntry)); + + } + // New entry for array + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "search"; + newEntry["name"] = item["name"]; + newEntry["placeholder"] = item["placeholder"]; + newEntry["value"] = ""; + newEntry["data-menutype"] = item["_menuType"]; + newEntry["data-menukey"] = item["_configKey"]; + element.appendChild(createElement(newEntry)); + break; + + case "singleInput": + var value = config[item["_configKey"]]; + if (value == undefined) { + value = ""; + } + var newEntry = new Object(); + newEntry = item; + delete newEntry["onclick"]; + newEntry["_element"] = "INPUT"; + newEntry["value"] = value; + newEntry["type"] = "search"; + newEntry["data-menutype"] = item["_menuType"]; + newEntry["data-menukey"] = item["_configKey"]; + element.appendChild(createElement(newEntry)); + break; + + case "checkbox": + var value = config[item["_configKey"]]; + if (value == undefined) { + value = false; + } + var newEntry = new Object(); + newEntry = item; + delete newEntry["onclick"]; + newEntry["_element"] = "INPUT"; + newEntry["value"] = value; + newEntry["type"] = "checkbox"; + newEntry["data-menutype"] = item["_menuType"]; + newEntry["data-menukey"] = item["_configKey"]; + element.appendChild(createElement(newEntry)); + element.getElementsByTagName("INPUT")[0].checked = value; + break; + + case "select": + var value = config[item["_configKey"]]; + var newEntry = new Object(); + newEntry = item; + delete newEntry["onclick"] + newEntry["_element"] = "SELECT"; + element.appendChild(createElement(newEntry)); + var selectElement = element.getElementsByTagName("SELECT")[0]; + var values = item["_optionValues"]; + for (var i = 0; i < values.length; i++) { + var newEntry = new Object; + newEntry["_element"] = "OPTION"; + newEntry["_text"] = item["_text"] + ": " + values[i]; + newEntry["value"] = values[i]; + selectElement.appendChild(createElement(newEntry)); + } + selectElement.value = value; + break; + + } + return element; +} + +function openSubMenu(item) { + var entrys = item["_subMenu"].split(","); + var div = document.getElementById("settings"); + + var newHR = new Object(); + newHR["_element"] = "HR" + div.appendChild(createElement(newHR)); + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + //newEntry["class"] = "save"; + newEntry["value"] = "Save"; + newEntry["onclick"] = "saveData2('settings')" + div.appendChild(createElement(newEntry)); + + if (item["_configKey"] == "settings") { + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + //newEntry["class"] = "save"; + newEntry["value"] = "Backup"; + newEntry["onclick"] = "xteveBackup()" + div.appendChild(createElement(newEntry)); + } + + if (item["_configKey"] == "settings") { + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + //newEntry["class"] = "save"; + newEntry["value"] = "Restore"; + newEntry["onclick"] = "xteveRestore(this)" + div.appendChild(createElement(newEntry)); + } + + + var newWrapper = new Object(); + newWrapper["_element"] = "DIV"; + newWrapper["id"] = "box-wrapper"; + div.appendChild(createElement(newWrapper)); + + div = div.lastChild; + + + for (var i = 0; i < entrys.length; i++) { + var item = subMenu[entrys[i]]; + if (item == undefined) { + break; + } + + var container = new Object(); + container["_element"] = "DIV"; + div.appendChild(createElement(container)); + + var divContainer = div.lastChild; + + var headline = new Object(); + headline["_element"] = "H5"; + headline["_text"] = item["_headline"]; + divContainer.appendChild(createElement(headline)); + + divContainer.appendChild(createMenuItem(item)) + + switch(item.hasOwnProperty("_usage")) { + case true: + var usageItem = new Object(); + usageItem["_element"] = "PRE" + usageItem["_text"] = item["_usage"]; + divContainer.appendChild(createElement(usageItem)); + } + + var hr = new Object(); + hr["_element"] = "HR"; + divContainer.appendChild(createElement(hr)); + + } + + calculateWrapperHeight(); + return +} + +function saveData2(elm) { + var div = document.getElementById(elm); + var inputs = div.getElementsByTagName("INPUT"); + var selects = div.getElementsByTagName("SELECT"); + var value, configKey; + var data = new Object(); + var valueArr = new Array(); + var newData = false; + + for (var i = 0; i < inputs.length; i++) { + if (inputs[i].type != "button") { + var menuType = inputs[i].getAttribute("data-menutype"); + + //console.log(menuType); + switch(menuType) { + case "singleInput": + value = inputs[i].value; + if (value == "" || value == undefined) { + data = new Object(); + data["delete"] = inputs[i].name + newData = true; + } else { + newData = true; + data[inputs[i].name] = value; + console.log(data); + } + break; + case "inputArray": + value = inputs[i].value; + if (value != "" && value != undefined) { + newData = true; + valueArr.push(value) + data[inputs[i].name] = valueArr; + configKey = inputs[i].name; + } + + break; + + case "checkbox": + value = inputs[i].checked + data[inputs[i].name] = value; + } + + } + + } + + + // Delete config key + if (valueArr.length == 0 && newData == false) { + newData = true; + data = new Object(); + data["delete"] = configKey; + } + + + for (var i = 0; i < selects.length; i++) { + var value = selects[i].options[selects[i].selectedIndex].value; + switch(isNaN(value)) { + case false: value = parseInt(value); break; + } + + data[selects[i].name] = value; + newData = true; + } + + //console.log(data, newData); + + if (newData == true) { + data["cmd"] = "saveConfig"; + if (!data.hasOwnProperty('filter')) { + data["filter"] = config["filter"] + } + var settings = new Object(); + settings["cmd"] = data["cmd"]; + settings["settings"] = data; + console.log(settings); + xTeVe(settings); + } +} diff --git a/html/js/menu_ts.js b/html/js/menu_ts.js new file mode 100644 index 0000000..6b5d2a3 --- /dev/null +++ b/html/js/menu_ts.js @@ -0,0 +1,1747 @@ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var MainMenu = /** @class */ (function () { + function MainMenu() { + this.DocumentID = "main-menu"; + this.HTMLTag = "LI"; + this.ImagePath = "img/"; + } + MainMenu.prototype.createIMG = function (src) { + var element = document.createElement("IMG"); + element.setAttribute("src", this.ImagePath + src); + return element; + }; + MainMenu.prototype.createValue = function (value) { + var element = document.createElement("P"); + element.innerHTML = value; + return element; + }; + return MainMenu; +}()); +var MainMenuItem = /** @class */ (function (_super) { + __extends(MainMenuItem, _super); + function MainMenuItem(menuKey, value, image, headline) { + var _this = _super.call(this) || this; + _this.menuKey = menuKey; + _this.value = value; + _this.imgSrc = image; + _this.headline = headline; + return _this; + } + MainMenuItem.prototype.createItem = function () { + var item = document.createElement("LI"); + item.setAttribute("onclick", "javascript: openThisMenu(this)"); + item.setAttribute("id", this.id); + var img = this.createIMG(this.imgSrc); + var value = this.createValue(this.value); + item.appendChild(img); + item.appendChild(value); + var doc = document.getElementById(this.DocumentID); + doc.appendChild(item); + switch (this.menuKey) { + case "playlist": + this.tableHeader = ["{{.playlist.table.playlist}}", "{{.playlist.table.tuner}}", "{{.playlist.table.lastUpdate}}", "{{.playlist.table.availability}} %", "{{.playlist.table.type}}", "{{.playlist.table.streams}}", "{{.playlist.table.groupTitle}} %", "{{.playlist.table.tvgID}} %", "{{.playlist.table.uniqueID}} %"]; + break; + case "xmltv": + this.tableHeader = ["{{.xmltv.table.guide}}", "{{.xmltv.table.lastUpdate}}", "{{.xmltv.table.availability}} %", "{{.xmltv.table.channels}}", "{{.xmltv.table.programs}}"]; + break; + case "filter": + this.tableHeader = ["{{.filter.table.name}}", "{{.filter.table.type}}", "{{.filter.table.filter}}"]; + break; + case "users": + this.tableHeader = ["{{.users.table.username}}", "{{.users.table.password}}", "{{.users.table.web}}", "{{.users.table.pms}}", "{{.users.table.m3u}}", "{{.users.table.xml}}", "{{.users.table.api}}"]; + break; + case "mapping": + this.tableHeader = ["BULK", "{{.mapping.table.chNo}}", "{{.mapping.table.logo}}", "{{.mapping.table.channelName}}", "{{.mapping.table.playlist}}", "{{.mapping.table.groupTitle}}", "{{.mapping.table.xmltvFile}}", "{{.mapping.table.xmltvID}}"]; + break; + } + //console.log(this.menuKey, this.tableHeader); + }; + return MainMenuItem; +}(MainMenu)); +var Content = /** @class */ (function () { + function Content() { + this.DocumentID = "content"; + this.TableID = "content_table"; + this.headerClass = "content_table_header"; + this.interactionID = "content-interaction"; + } + Content.prototype.createHeadline = function (value) { + var element = document.createElement("H3"); + element.innerHTML = value; + return element; + }; + Content.prototype.createHR = function () { + var element = document.createElement("HR"); + return element; + }; + Content.prototype.createInteraction = function () { + var element = document.createElement("DIV"); + element.setAttribute("id", this.interactionID); + return element; + }; + Content.prototype.createDIV = function () { + var element = document.createElement("DIV"); + element.id = this.DivID; + return element; + }; + Content.prototype.createTABLE = function () { + var element = document.createElement("TABLE"); + element.id = this.TableID; + return element; + }; + Content.prototype.createTableRow = function () { + var element = document.createElement("TR"); + element.className = this.headerClass; + return element; + }; + Content.prototype.createTableContent = function (menuKey) { + var data = new Object(); + var rows = new Array(); + switch (menuKey) { + case "playlist": + var fileTypes = new Array("m3u", "hdhr"); + fileTypes.forEach(function (fileType) { + data = SERVER["settings"]["files"][fileType]; + var keys = getObjKeys(data); + keys.forEach(function (key) { + var tr = document.createElement("TR"); + tr.id = key; + tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)'); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["name"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + if (SERVER["settings"]["buffer"] == true) { + cell.value = data[key]["tuner"]; + } + else { + cell.value = "-"; + } + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["last.update"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["provider.availability"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["type"].toUpperCase(); + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["compatibility"]["streams"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["compatibility"]["group.title"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["compatibility"]["tvg.id"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["compatibility"]["stream.id"]; + tr.appendChild(cell.createCell()); + rows.push(tr); + }); + }); + break; + case "filter": + delete SERVER["settings"]["filter"][-1]; + data = SERVER["settings"]["filter"]; + var keys = getObjKeys(data); + keys.forEach(function (key) { + var tr = document.createElement("TR"); + tr.id = key; + tr.setAttribute('onclick', 'javascript: openPopUp("' + data[key]["type"] + '", this)'); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["name"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + switch (data[key]["type"]) { + case "custom-filter": + cell.value = "{{.filter.custom}}"; + break; + case "group-title": + cell.value = "{{.filter.group}}"; + break; + default: + break; + } + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["filter"]; + tr.appendChild(cell.createCell()); + rows.push(tr); + }); + break; + case "xmltv": + var fileTypes = new Array("xmltv"); + fileTypes.forEach(function (fileType) { + data = SERVER["settings"]["files"][fileType]; + var keys = getObjKeys(data); + keys.forEach(function (key) { + var tr = document.createElement("TR"); + tr.id = key; + tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)'); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["name"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["last.update"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["provider.availability"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["compatibility"]["xmltv.channels"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["compatibility"]["xmltv.programs"]; + tr.appendChild(cell.createCell()); + rows.push(tr); + }); + }); + break; + case "users": + var fileTypes = new Array("users"); + fileTypes.forEach(function (fileType) { + data = SERVER[fileType]; + var keys = getObjKeys(data); + keys.forEach(function (key) { + var tr = document.createElement("TR"); + tr.id = key; + tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)'); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["data"]["username"]; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = "******"; + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + if (data[key]["data"]["authentication.web"] == true) { + cell.value = "✓"; + } + else { + cell.value = "-"; + } + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + if (data[key]["data"]["authentication.pms"] == true) { + cell.value = "✓"; + } + else { + cell.value = "-"; + } + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + if (data[key]["data"]["authentication.m3u"] == true) { + cell.value = "✓"; + } + else { + cell.value = "-"; + } + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + if (data[key]["data"]["authentication.xml"] == true) { + cell.value = "✓"; + } + else { + cell.value = "-"; + } + tr.appendChild(cell.createCell()); + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + if (data[key]["data"]["authentication.api"] == true) { + cell.value = "✓"; + } + else { + cell.value = "-"; + } + tr.appendChild(cell.createCell()); + rows.push(tr); + }); + }); + break; + case "mapping": + BULK_EDIT = false; + createSearchObj(); + checkUndo("epgMapping"); + console.log("MAPPING"); + data = SERVER["xepg"]["epgMapping"]; + var keys = getObjKeys(data); + keys.forEach(function (key) { + var tr = document.createElement("TR"); + tr.id = key; + //tr.setAttribute('oncontextmenu', 'javascript: rightClick(this)') + switch (data[key]["x-active"]) { + case true: + tr.className = "activeEPG"; + break; + case false: + tr.className = "notActiveEPG"; + break; + } + // Bulk + var cell = new Cell(); + cell.child = true; + cell.childType = "BULK"; + cell.value = false; + tr.appendChild(cell.createCell()); + // Kanalnummer + var cell = new Cell(); + cell.child = true; + cell.childType = "INPUTCHANNEL"; + cell.value = data[key]["x-channelID"]; + //td.setAttribute('onclick', 'javascript: changeChannelNumber("' + key + '", this)') + tr.appendChild(cell.createCell()); + // Logo + var cell = new Cell(); + cell.child = true; + cell.childType = "IMG"; + cell.imageURL = data[key]["tvg-logo"]; + var td = cell.createCell(); + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)'); + td.id = key; + tr.appendChild(td); + // Kanalname + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.className = data[key]["x-category"]; + cell.value = data[key]["x-name"]; + var td = cell.createCell(); + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)'); + td.id = key; + tr.appendChild(td); + // Playlist + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + //cell.value = data[key]["_file.m3u.name"] + cell.value = getValueFromProviderFile(data[key]["_file.m3u.id"], "m3u", "name"); + var td = cell.createCell(); + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)'); + td.id = key; + tr.appendChild(td); + // Gruppe (group-title) + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = data[key]["x-group-title"]; + var td = cell.createCell(); + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)'); + td.id = key; + tr.appendChild(td); + // XMLTV Datei + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + if (data[key]["x-xmltv-file"] != "-") { + cell.value = getValueFromProviderFile(data[key]["x-xmltv-file"], "xmltv", "name"); + } + else { + cell.value = data[key]["x-xmltv-file"]; + } + var td = cell.createCell(); + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)'); + td.id = key; + tr.appendChild(td); + // XMLTV Kanal + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + //var value = str.substring(1, 4); + var value = data[key]["x-mapping"]; + if (value.length > 20) { + value = data[key]["x-mapping"].substring(0, 20) + "..."; + } + cell.value = value; + var td = cell.createCell(); + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)'); + td.id = key; + tr.appendChild(td); + rows.push(tr); + }); + break; + case "settings": + alert(); + break; + default: + console.log("Table content (menuKey):", menuKey); + break; + } + return rows; + }; + return Content; +}()); +var Cell = /** @class */ (function () { + function Cell() { + } + Cell.prototype.createCell = function () { + var td = document.createElement("TD"); + if (this.child == true) { + var element; + switch (this.childType) { + case "P": + element = document.createElement(this.childType); + element.innerHTML = this.value; + element.className = this.className; + break; + case "INPUT": + element = document.createElement(this.childType); + element.value = this.value; + element.type = "text"; + break; + case "INPUTCHANNEL": + element = document.createElement("INPUT"); + element.setAttribute("onchange", "javscript: changeChannelNumber(this)"); + element.value = this.value; + element.type = "text"; + break; + case "BULK": + element = document.createElement("INPUT"); + element.checked = this.value; + element.type = "checkbox"; + element.className = "bulk hideBulk"; + break; + case "BULK_HEAD": + element = document.createElement("INPUT"); + element.checked = this.value; + element.type = "checkbox"; + element.className = "bulk hideBulk"; + element.setAttribute("onclick", "javascript: selectAllChannels()"); + break; + case "IMG": + element = document.createElement(this.childType); + element.setAttribute("src", this.imageURL); + if (this.imageURL != "") { + element.setAttribute("onerror", "javascript: this.onerror=null;this.src=''"); + //onerror="this.onerror=null;this.src='missing.gif';" + } + } + td.appendChild(element); + } + else { + td.innerHTML = this.value; + } + if (this.onclick == true) { + td.setAttribute("onclick", this.onclickFunktion); + td.className = "pointer"; + } + if (this.tdClassName != undefined) { + td.className = this.tdClassName; + } + return td; + }; + return Cell; +}()); +var ShowContent = /** @class */ (function (_super) { + __extends(ShowContent, _super); + function ShowContent(menuID) { + var _this = _super.call(this) || this; + _this.menuID = menuID; + return _this; + } + ShowContent.prototype.createInput = function (type, name, value) { + var input = document.createElement("INPUT"); + input.setAttribute("type", type); + input.setAttribute("name", name); + input.setAttribute("value", value); + return input; + }; + ShowContent.prototype.show = function () { + COLUMN_TO_SORT = -1; + // Alten Inhalt löschen + var doc = document.getElementById(this.DocumentID); + doc.innerHTML = ""; + showPreview(false); + // Überschrift + var headline = menuItems[this.menuID].headline; + var menuKey = menuItems[this.menuID].menuKey; + var h = this.createHeadline(headline); + doc.appendChild(h); + var hr = this.createHR(); + doc.appendChild(hr); + // Interaktion + var div = this.createInteraction(); + doc.appendChild(div); + var interaction = document.getElementById(this.interactionID); + switch (menuKey) { + case "playlist": + var input = this.createInput("button", menuKey, "{{.button.new}}"); + input.setAttribute("id", "-"); + input.setAttribute("onclick", 'javascript: openPopUp("playlist")'); + interaction.appendChild(input); + break; + case "filter": + var input = this.createInput("button", menuKey, "{{.button.new}}"); + input.setAttribute("id", -1); + input.setAttribute("onclick", 'javascript: openPopUp("filter", this)'); + interaction.appendChild(input); + break; + case "xmltv": + var input = this.createInput("button", menuKey, "{{.button.new}}"); + input.setAttribute("id", "xmltv"); + input.setAttribute("onclick", 'javascript: openPopUp("xmltv")'); + interaction.appendChild(input); + break; + case "users": + var input = this.createInput("button", menuKey, "{{.button.new}}"); + input.setAttribute("id", "users"); + input.setAttribute("onclick", 'javascript: openPopUp("users")'); + interaction.appendChild(input); + break; + case "mapping": + showElement("loading", true); + var input = this.createInput("button", menuKey, "{{.button.save}}"); + input.setAttribute("onclick", 'javascript: savePopupData("mapping", "", "")'); + interaction.appendChild(input); + var input = this.createInput("button", menuKey, "{{.button.bulkEdit}}"); + input.setAttribute("onclick", 'javascript: bulkEdit()'); + interaction.appendChild(input); + var input = this.createInput("search", "search", ""); + input.setAttribute("id", "searchMapping"); + input.setAttribute("placeholder", "{{.button.search}}"); + input.className = "search"; + input.setAttribute("onchange", 'javascript: searchInMapping()'); + interaction.appendChild(input); + break; + case "settings": + var input = this.createInput("button", menuKey, "{{.button.save}}"); + input.setAttribute("onclick", 'javascript: saveSettings();'); + interaction.appendChild(input); + var input = this.createInput("button", menuKey, "{{.button.backup}}"); + input.setAttribute("onclick", 'javascript: backup();'); + interaction.appendChild(input); + var input = this.createInput("button", menuKey, "{{.button.restore}}"); + input.setAttribute("onclick", 'javascript: restore();'); + interaction.appendChild(input); + var wrapper = document.createElement("DIV"); + wrapper.setAttribute("id", "box-wrapper"); + doc.appendChild(wrapper); + this.DivID = "content_settings"; + var settings = this.createDIV(); + wrapper.appendChild(settings); + showSettings(); + return; + break; + case "log": + var input = this.createInput("button", menuKey, "{{.button.resetlogs}}"); + input.setAttribute("onclick", 'javascript: resetLogs();'); + interaction.appendChild(input); + var wrapper = document.createElement("DIV"); + wrapper.setAttribute("id", "box-wrapper"); + doc.appendChild(wrapper); + this.DivID = "content_log"; + var logs = this.createDIV(); + wrapper.appendChild(logs); + showLogs(true); + return; + break; + case "logout": + location.reload(); + document.cookie = "Token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT"; + break; + default: + console.log("Show content (menuKey):", menuKey); + break; + } + // Tabelle erstellen (falls benötigt) + var tableHeader = menuItems[this.menuID].tableHeader; + if (tableHeader.length > 0) { + var wrapper = document.createElement("DIV"); + doc.appendChild(wrapper); + wrapper.setAttribute("id", "box-wrapper"); + var table = this.createTABLE(); + wrapper.appendChild(table); + var header = this.createTableRow(); + table.appendChild(header); + // Kopfzeile der Tablle + tableHeader.forEach(function (element) { + var cell = new Cell(); + cell.child = true; + cell.childType = "P"; + cell.value = element; + if (element == "BULK") { + cell.childType = "BULK_HEAD"; + cell.value = false; + } + if (menuKey == "mapping") { + if (element == "{{.mapping.table.chNo}}") { + cell.onclick = true; + cell.onclickFunktion = "javascript: sortTable(1);"; + cell.tdClassName = "sortThis"; + } + if (element == "{{.mapping.table.channelName}}") { + cell.onclick = true; + cell.onclickFunktion = "javascript: sortTable(3);"; + } + if (element == "{{.mapping.table.playlist}}") { + cell.onclick = true; + cell.onclickFunktion = "javascript: sortTable(4);"; + } + if (element == "{{.mapping.table.groupTitle}}") { + cell.onclick = true; + cell.onclickFunktion = "javascript: sortTable(5);"; + } + } + header.appendChild(cell.createCell()); + }); + table.appendChild(header); + // Inhalt der Tabelle + var rows = this.createTableContent(menuKey); + rows.forEach(function (tr) { + table.appendChild(tr); + }); + } + switch (menuKey) { + case "mapping": + sortTable(1); + break; + case "filter": + showPreview(true); + sortTable(0); + break; + default: + COLUMN_TO_SORT = -1; + sortTable(0); + break; + } + showElement("loading", false); + }; + return ShowContent; +}(Content)); +function PageReady() { + var server = new Server("getServerConfig"); + server.request(new Object()); + window.addEventListener("resize", function () { + calculateWrapperHeight(); + }, true); + setInterval(function () { + updateLog(); + }, 10000); + return; +} +function createLayout() { + // Client Info + var obj = SERVER["clientInfo"]; + var keys = getObjKeys(obj); + for (var i = 0; i < keys.length; i++) { + if (document.getElementById(keys[i])) { + document.getElementById(keys[i]).innerHTML = obj[keys[i]]; + } + } + if (!document.getElementById("main-menu")) { + return; + } + // Menü erstellen + document.getElementById("main-menu").innerHTML = ""; + for (var i_1 = 0; i_1 < menuItems.length; i_1++) { + menuItems[i_1].id = i_1; + switch (menuItems[i_1]["menuKey"]) { + case "users": + case "logout": + if (SERVER["settings"]["authentication.web"] == true) { + menuItems[i_1].createItem(); + } + break; + case "mapping": + case "xmltv": + if (SERVER["clientInfo"]["epgSource"] == "XEPG") { + menuItems[i_1].createItem(); + } + break; + default: + menuItems[i_1].createItem(); + break; + } + } + return; +} +function openThisMenu(element) { + var id = element.id; + var content = new ShowContent(id); + content.show(); + calculateWrapperHeight(); + return; +} +var PopupWindow = /** @class */ (function () { + function PopupWindow() { + this.DocumentID = "popup-custom"; + this.InteractionID = "interaction"; + this.doc = document.getElementById(this.DocumentID); + } + PopupWindow.prototype.createTitle = function (title) { + var td = document.createElement("TD"); + td.className = "left"; + td.innerHTML = title + ":"; + return td; + }; + PopupWindow.prototype.createContent = function (element) { + var td = document.createElement("TD"); + td.appendChild(element); + return td; + }; + PopupWindow.prototype.createInteraction = function () { + var div = document.createElement("div"); + div.setAttribute("id", "popup-interaction"); + div.className = "interaction"; + this.doc.appendChild(div); + }; + return PopupWindow; +}()); +var PopupContent = /** @class */ (function (_super) { + __extends(PopupContent, _super); + function PopupContent() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.table = document.createElement("TABLE"); + return _this; + } + PopupContent.prototype.createHeadline = function (headline) { + this.doc.innerHTML = ""; + var element = document.createElement("H3"); + element.innerHTML = headline.toUpperCase(); + this.doc.appendChild(element); + // Tabelle erstellen + this.table = document.createElement("TABLE"); + this.doc.appendChild(this.table); + }; + PopupContent.prototype.appendRow = function (title, element) { + var tr = document.createElement("TR"); + // Bezeichnung + if (title.length != 0) { + tr.appendChild(this.createTitle(title)); + } + // Content + tr.appendChild(this.createContent(element)); + this.table.appendChild(tr); + }; + PopupContent.prototype.createInput = function (type, name, value) { + var input = document.createElement("INPUT"); + if (value == undefined) { + value = ""; + } + input.setAttribute("type", type); + input.setAttribute("name", name); + input.setAttribute("value", value); + return input; + }; + PopupContent.prototype.createCheckbox = function (name) { + var input = document.createElement("INPUT"); + input.setAttribute("type", "checkbox"); + input.setAttribute("name", name); + return input; + }; + PopupContent.prototype.createSelect = function (text, values, set, dbKey) { + var select = document.createElement("SELECT"); + select.setAttribute("name", dbKey); + for (var i = 0; i < text.length; i++) { + var option = document.createElement("OPTION"); + option.setAttribute("value", values[i]); + option.innerText = text[i]; + select.appendChild(option); + } + if (set != "") { + select.value = set; + } + if (set == undefined) { + select.value = values[0]; + } + return select; + }; + PopupContent.prototype.selectOption = function (select, value) { + //select.selectedOptions = value + var s = select; + s.options[s.selectedIndex].value = value; + return select; + }; + PopupContent.prototype.description = function (value) { + var tr = document.createElement("TR"); + var td = document.createElement("TD"); + var span = document.createElement("PRE"); + span.innerHTML = value; + tr.appendChild(td); + tr.appendChild(this.createContent(span)); + this.table.appendChild(tr); + }; + // Interaktion + PopupContent.prototype.addInteraction = function (element) { + var interaction = document.getElementById("popup-interaction"); + interaction.appendChild(element); + }; + return PopupContent; +}(PopupWindow)); +function openPopUp(dataType, element) { + var data = new Object(); + var id; + switch (element) { + case undefined: + switch (dataType) { + case "group-title": + if (id == undefined) { + id = -1; + } + data = getLocalData("filter", id); + data["type"] = "group-title"; + break; + case "custom-filter": + if (id == undefined) { + id = -1; + } + data = getLocalData("filter", id); + data["type"] = "custom-filter"; + break; + default: + data["id.provider"] = "-"; + data["type"] = dataType; + id = "-"; + break; + } + break; + default: + id = element.id; + data = getLocalData(dataType, id); + break; + } + var content = new PopupContent(); + switch (dataType) { + case "playlist": + content.createHeadline("{{.playlist.playlistType.title}}"); + // Type + var text = ["M3U", "HDHomeRun"]; + var values = ["javascript: openPopUp('m3u')", "javascript: openPopUp('hdhr')"]; + var select = content.createSelect(text, values, "", "type"); + select.setAttribute("id", "type"); + select.setAttribute("onchange", 'javascript: changeButtonAction(this, "next", "onclick")'); // changeButtonAction + content.appendRow("{{.playlist.type.title}}", select); + // Interaktion + content.createInteraction(); + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}"); + input.setAttribute("onclick", 'javascript: showElement("popup", false);'); + content.addInteraction(input); + // Weiter + var input = content.createInput("button", "next", "{{.button.next}}"); + input.setAttribute("onclick", 'javascript: openPopUp("m3u")'); + input.setAttribute("id", 'next'); + content.addInteraction(input); + break; + case "m3u": + content.createHeadline(dataType); + // Name + var dbKey = "name"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.playlist.name.placeholder}}"); + content.appendRow("{{.playlist.name.title}}", input); + // Beschreibung + var dbKey = "description"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.playlist.description.placeholder}}"); + content.appendRow("{{.playlist.description.title}}", input); + // URL + var dbKey = "file.source"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.playlist.fileM3U.placeholder}}"); + content.appendRow("{{.playlist.fileM3U.title}}", input); + // Tuner + if (SERVER["settings"]["buffer"] == true) { + var text = new Array(); + var values = new Array(); + for (var i = 1; i <= 100; i++) { + text.push(i.toString()); + values.push(i.toString()); + } + var dbKey = "tuner"; + var select = content.createSelect(text, values, data[dbKey], dbKey); + select.setAttribute("onfocus", "javascript: return;"); + content.appendRow("{{.playlist.tuner.title}}", select); + } + else { + var dbKey = "tuner"; + if (data[dbKey] == undefined) { + data[dbKey] = 1; + } + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("readonly", "true"); + input.className = "notAvailable"; + content.appendRow("{{.playlist.tuner.title}}", input); + } + content.description("{{.playlist.tuner.description}}"); + // Interaktion + content.createInteraction(); + // Löschen + if (data["id.provider"] != "-") { + var input = content.createInput("button", "delete", "{{.button.delete}}"); + input.className = "delete"; + input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", true, 0)'); + content.addInteraction(input); + } + else { + var input = content.createInput("button", "back", "{{.button.back}}"); + input.setAttribute("onclick", 'javascript: openPopUp("playlist")'); + content.addInteraction(input); + } + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}"); + input.setAttribute("onclick", 'javascript: showElement("popup", false);'); + content.addInteraction(input); + // Aktualisieren + if (data["id.provider"] != "-") { + var input = content.createInput("button", "update", "{{.button.update}}"); + input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", false, 1)'); + content.addInteraction(input); + } + // Speichern + var input = content.createInput("button", "save", "{{.button.save}}"); + input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", false, 0)'); + content.addInteraction(input); + break; + case "hdhr": + content.createHeadline(dataType); + // Name + var dbKey = "name"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.playlist.name.placeholder}}"); + content.appendRow("{{.playlist.name.title}}", input); + // Beschreibung + var dbKey = "description"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.playlist.description.placeholder}}"); + content.appendRow("{{.playlist.description.placeholder}}", input); + // URL + var dbKey = "file.source"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.playlist.fileHDHR.placeholder}}"); + content.appendRow("{{.playlist.fileHDHR.title}}", input); + // Tuner + if (SERVER["settings"]["buffer"] == true) { + var text = new Array(); + var values = new Array(); + for (var i = 1; i <= 100; i++) { + text.push(i.toString()); + values.push(i.toString()); + } + var dbKey = "tuner"; + var select = content.createSelect(text, values, data[dbKey], dbKey); + select.setAttribute("onfocus", "javascript: return;"); + content.appendRow("{{.playlist.tuner.title}}", select); + } + else { + var dbKey = "tuner"; + if (data[dbKey] == undefined) { + data[dbKey] = 1; + } + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("readonly", "true"); + input.className = "notAvailable"; + content.appendRow("{{.playlist.tuner.title}}", input); + } + content.description("{{.playlist.tuner.description}}"); + // Interaktion + content.createInteraction(); + // Löschen + if (data["id.provider"] != "-") { + var input = content.createInput("button", "delete", "{{.button.delete}}"); + input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", true, 0)'); + input.className = "delete"; + content.addInteraction(input); + } + else { + var input = content.createInput("button", "back", "{{.button.back}}"); + input.setAttribute("onclick", 'javascript: openPopUp("playlist")'); + content.addInteraction(input); + } + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}"); + input.setAttribute("onclick", 'javascript: showElement("popup", false);'); + content.addInteraction(input); + // Aktualisieren + if (data["id.provider"] != "-") { + var input = content.createInput("button", "update", "{{.button.update}}"); + input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", false, 1)'); + content.addInteraction(input); + } + // Speichern + var input = content.createInput("button", "save", "{{.button.save}}"); + input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", false, 0)'); + content.addInteraction(input); + break; + case "filter": + content.createHeadline(dataType); + // Type + var dbKey = "type"; + var text = ["M3U: " + "{{.filter.type.groupTitle}}", "xTeVe: " + "{{.filter.type.customFilter}}"]; + var values = ["javascript: openPopUp('group-title')", "javascript: openPopUp('custom-filter')"]; + var select = content.createSelect(text, values, "javascript: openPopUp('group-title')", dbKey); + select.setAttribute("id", id); + select.setAttribute("onchange", 'javascript: changeButtonAction(this, "next", "onclick");'); // changeButtonAction + content.appendRow("{{.filter.type.title}}", select); + // Interaktion + content.createInteraction(); + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}"); + input.setAttribute("onclick", 'javascript: showElement("popup", false);'); + content.addInteraction(input); + // Weiter + var input = content.createInput("button", "next", "{{.button.next}}"); + input.setAttribute("onclick", 'javascript: openPopUp("group-title")'); + input.setAttribute("id", 'next'); + content.addInteraction(input); + break; + case "custom-filter": + case "group-title": + switch (dataType) { + case "custom-filter": + content.createHeadline("{{.filter.custom}}"); + break; + case "group-title": + content.createHeadline("{{.filter.group}}"); + break; + } + // Name + var dbKey = "name"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.filter.name.placeholder}}"); + content.appendRow("{{.filter.name.title}}", input); + // Beschreibung + var dbKey = "description"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.filter.description.placeholder}}"); + content.appendRow("{{.filter.description.title}}", input); + // Typ + var dbKey = "type"; + var input = content.createInput("hidden", dbKey, data[dbKey]); + content.appendRow("", input); + var filterType = data[dbKey]; + switch (filterType) { + case "custom-filter": + // Groß- Kleinschreibung beachten + var dbKey = "caseSensitive"; + var input = content.createCheckbox(dbKey); + input.checked = data[dbKey]; + content.appendRow("{{.filter.caseSensitive.title}}", input); + // Filterregel (Benutzerdefiniert) + var dbKey = "filter"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.filter.filterRule.placeholder}}"); + content.appendRow("{{.filter.filterRule.title}}", input); + break; + case "group-title": + //alert(dbKey + " " + filterType) + // Filter basierend auf den Gruppen in der M3U + var dbKey = "filter"; + var groupsM3U = getLocalData("m3uGroups", ""); + var text = groupsM3U["text"]; + var values = groupsM3U["value"]; + var select = content.createSelect(text, values, data[dbKey], dbKey); + select.setAttribute("onchange", "javascript: this.className = 'changed'"); + content.appendRow("{{.filter.filterGroup.title}}", select); + content.description("{{.filter.filterGroup.description}}"); + // Groß- Kleinschreibung beachten + var dbKey = "caseSensitive"; + var input = content.createCheckbox(dbKey); + input.checked = data[dbKey]; + content.appendRow("{{.filter.caseSensitive.title}}", input); + var dbKey = "include"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.filter.include.placeholder}}"); + content.appendRow("{{.filter.include.title}}", input); + content.description("{{.filter.include.description}}"); + var dbKey = "exclude"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.filter.exclude.placeholder}}"); + content.appendRow("{{.filter.exclude.title}}", input); + content.description("{{.filter.exclude.description}}"); + break; + default: + break; + } + // Interaktion + content.createInteraction(); + // Löschen + var input = content.createInput("button", "delete", "{{.button.delete}}"); + input.setAttribute('onclick', 'javascript: savePopupData("filter", "' + id + '", true, 0)'); + input.className = "delete"; + content.addInteraction(input); + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}"); + input.setAttribute("onclick", 'javascript: showElement("popup", false);'); + content.addInteraction(input); + // Speichern + var input = content.createInput("button", "save", "{{.button.save}}"); + input.setAttribute('onclick', 'javascript: savePopupData("filter", "' + id + '", false, 0)'); + content.addInteraction(input); + break; + case "xmltv": + content.createHeadline(dataType); + // Name + var dbKey = "name"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.xmltv.name.placeholder}}"); + content.appendRow("{{.xmltv.name.title}}", input); + // Beschreibung + var dbKey = "description"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.xmltv.description.placeholder}}"); + content.appendRow("{{.xmltv.description.title}}", input); + // URL + var dbKey = "file.source"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.xmltv.fileXMLTV.placeholder}}"); + content.appendRow("{{.xmltv.fileXMLTV.title}}", input); + // Interaktion + content.createInteraction(); + // Löschen + if (data["id.provider"] != "-") { + var input = content.createInput("button", "delete", "{{.button.delete}}"); + input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", true, 0)'); + input.className = "delete"; + content.addInteraction(input); + } + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}"); + input.setAttribute("onclick", 'javascript: showElement("popup", false);'); + content.addInteraction(input); + // Aktualisieren + if (data["id.provider"] != "-") { + var input = content.createInput("button", "update", "{{.button.update}}"); + input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", false, 1)'); + content.addInteraction(input); + } + // Speichern + var input = content.createInput("button", "save", "{{.button.save}}"); + input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", false, 0)'); + content.addInteraction(input); + break; + case "users": + content.createHeadline("{{.mainMenu.item.users}}"); + // Benutzername + var dbKey = "username"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("placeholder", "{{.users.username.placeholder}}"); + content.appendRow("{{.users.username.title}}", input); + // Neues Passwort + var dbKey = "password"; + var input = content.createInput("password", dbKey, ""); + input.setAttribute("placeholder", "{{.users.password.placeholder}}"); + content.appendRow("{{.users.password.title}}", input); + // Bestätigung + var dbKey = "confirm"; + var input = content.createInput("password", dbKey, ""); + input.setAttribute("placeholder", "{{.users.confirm.placeholder}}"); + content.appendRow("{{.users.confirm.title}}", input); + // Berechtigung WEB + var dbKey = "authentication.web"; + var input = content.createCheckbox(dbKey); + input.checked = data[dbKey]; + if (data["defaultUser"] == true) { + input.setAttribute("onclick", "javascript: return false"); + } + content.appendRow("{{.users.web.title}}", input); + // Berechtigung PMS + var dbKey = "authentication.pms"; + var input = content.createCheckbox(dbKey); + input.checked = data[dbKey]; + content.appendRow("{{.users.pms.title}}", input); + // Berechtigung M3U + var dbKey = "authentication.m3u"; + var input = content.createCheckbox(dbKey); + input.checked = data[dbKey]; + content.appendRow("{{.users.m3u.title}}", input); + // Berechtigung XML + var dbKey = "authentication.xml"; + var input = content.createCheckbox(dbKey); + input.checked = data[dbKey]; + content.appendRow("{{.users.xml.title}}", input); + // Berechtigung API + var dbKey = "authentication.api"; + var input = content.createCheckbox(dbKey); + input.checked = data[dbKey]; + content.appendRow("{{.users.api.title}}", input); + // Interaktion + content.createInteraction(); + // Löschen + if (data["defaultUser"] != true && id != "-") { + var input = content.createInput("button", "delete", "{{.button.delete}}"); + input.className = "delete"; + input.setAttribute('onclick', 'javascript: savePopupData("' + dataType + '", "' + id + '", true, 0)'); + content.addInteraction(input); + } + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}"); + input.setAttribute("onclick", 'javascript: showElement("popup", false);'); + content.addInteraction(input); + // Speichern + var input = content.createInput("button", "save", "{{.button.save}}"); + input.setAttribute("onclick", 'javascript: savePopupData("' + dataType + '", "' + id + '", "false");'); + content.addInteraction(input); + break; + case "mapping": + content.createHeadline("{{.mainMenu.item.mapping}}"); + // Aktiv + var dbKey = "x-active"; + var input = content.createCheckbox(dbKey); + input.checked = data[dbKey]; + input.id = "active"; + //input.setAttribute("onchange", "javascript: this.className = 'changed'") + input.setAttribute("onchange", "javascript: toggleChannelStatus('" + id + "', this)"); + content.appendRow("{{.mapping.active.title}}", input); + // Kanalname + var dbKey = "x-name"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + if (BULK_EDIT == true) { + input.style.border = "solid 1px red"; + input.setAttribute("readonly", "true"); + } + content.appendRow("{{.mapping.channelName.title}}", input); + // Aktualisierung des Kanalnamens + if (data.hasOwnProperty("_uuid.key")) { + if (data["_uuid.key"] != "") { + var dbKey = "x-update-channel-name"; + var input = content.createCheckbox(dbKey); + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + input.checked = data[dbKey]; + content.appendRow("{{.mapping.updateChannelName.title}}", input); + } + } + // Logo URL (Kanal) + var dbKey = "tvg-logo"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + input.setAttribute("id", "channel-icon"); + content.appendRow("{{.mapping.channelLogo.title}}", input); + // Aktualisierung des Kanallogos + var dbKey = "x-update-channel-icon"; + var input = content.createCheckbox(dbKey); + input.checked = data[dbKey]; + input.setAttribute("id", "update-icon"); + input.setAttribute("onchange", "javascript: this.className = 'changed'; changeChannelLogo('" + id + "');"); + content.appendRow("{{.mapping.updateChannelLogo.title}}", input); + // Erweitern der EPG Kategorie + var dbKey = "x-category"; + var text = ["-", "Kids (Emby only)", "News", "Movie", "Series", "Sports"]; + var values = ["-", "Kids", "News", "Movie", "Series", "Sports"]; + var select = content.createSelect(text, values, data[dbKey], dbKey); + select.setAttribute("onchange", "javascript: this.className = 'changed'"); + content.appendRow("{{.mapping.epgCategory.title}}", select); + // M3U Gruppentitel + var dbKey = "x-group-title"; + var input = content.createInput("text", dbKey, data[dbKey]); + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + content.appendRow("{{.mapping.m3uGroupTitle.title}}", input); + // XMLTV Datei + var dbKey = "x-xmltv-file"; + var xmlFile = data[dbKey]; + var xmltv = new XMLTVFile(); + var select = xmltv.getFiles(data[dbKey]); + select.setAttribute("name", dbKey); + select.setAttribute("id", "popup-xmltv"); + select.setAttribute("onchange", "javascript: this.className = 'changed'; setXmltvChannel('" + id + "',this);"); + content.appendRow("{{.mapping.xmltvFile.title}}", select); + var file = data[dbKey]; + // XMLTV Mapping + var dbKey = "x-mapping"; + var xmltv = new XMLTVFile(); + var select = xmltv.getPrograms(file, data[dbKey]); + select.setAttribute("name", dbKey); + select.setAttribute("id", "popup-mapping"); + select.setAttribute("onchange", "javascript: this.className = 'changed'; checkXmltvChannel('" + id + "',this,'" + xmlFile + "');"); + sortSelect(select); + content.appendRow("{{.mapping.xmltvChannel.title}}", select); + // Interaktion + content.createInteraction(); + // Logo hochladen + var input = content.createInput("button", "cancel", "{{.button.uploadLogo}}"); + input.setAttribute("onclick", 'javascript: uploadLogo();'); + content.addInteraction(input); + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}"); + input.setAttribute("onclick", 'javascript: showElement("popup", false);'); + content.addInteraction(input); + // Fertig + var ids = new Array(); + ids = getAllSelectedChannels(); + if (ids.length == 0) { + ids.push(id); + } + var input = content.createInput("button", "save", "{{.button.done}}"); + input.setAttribute("onclick", 'javascript: donePopupData("' + dataType + '", "' + ids + '", "false");'); + content.addInteraction(input); + break; + default: + break; + } + showPopUpElement('popup-custom'); +} +var XMLTVFile = /** @class */ (function () { + function XMLTVFile() { + } + XMLTVFile.prototype.getFiles = function (set) { + var fileIDs = getObjKeys(SERVER["xepg"]["xmltvMap"]); + var values = new Array("-"); + var text = new Array("-"); + for (var i = 0; i < fileIDs.length; i++) { + if (fileIDs[i] != "xTeVe Dummy") { + values.push(getValueFromProviderFile(fileIDs[i], "xmltv", "file.xteve")); + text.push(getValueFromProviderFile(fileIDs[i], "xmltv", "name")); + } + else { + values.push(fileIDs[i]); + text.push(fileIDs[i]); + } + } + var select = document.createElement("SELECT"); + for (var i = 0; i < text.length; i++) { + var option = document.createElement("OPTION"); + option.setAttribute("value", values[i]); + option.innerText = text[i]; + select.appendChild(option); + } + if (set != "") { + select.value = set; + } + return select; + }; + XMLTVFile.prototype.getPrograms = function (file, set) { + //var fileIDs:string[] = getObjKeys(SERVER["xepg"]["xmltvMap"]) + var values = getObjKeys(SERVER["xepg"]["xmltvMap"][file]); + var text = new Array(); + var displayName; + for (var i = 0; i < values.length; i++) { + if (SERVER["xepg"]["xmltvMap"][file][values[i]].hasOwnProperty('display-name') == true) { + displayName = SERVER["xepg"]["xmltvMap"][file][values[i]]["display-name"]; + } + else { + displayName = "-"; + } + text[i] = displayName + " (" + values[i] + ")"; + } + text.unshift("-"); + values.unshift("-"); + var select = document.createElement("SELECT"); + for (var i = 0; i < text.length; i++) { + var option = document.createElement("OPTION"); + option.setAttribute("value", values[i]); + option.innerText = text[i]; + select.appendChild(option); + } + if (set != "") { + select.value = set; + } + if (select.value != set) { + select.value = "-"; + } + return select; + }; + return XMLTVFile; +}()); +function getValueFromProviderFile(file, fileType, key) { + if (file == "xTeVe Dummy") { + return file; + } + var fileID; + var indicator = file.charAt(0); + switch (indicator) { + case "M": + fileType = "m3u"; + fileID = file; + break; + case "H": + fileType = "hdhr"; + fileID = file; + break; + case "X": + fileType = "xmltv"; + fileID = file.substring(0, file.lastIndexOf('.')); + break; + } + if (SERVER["settings"]["files"][fileType].hasOwnProperty(fileID) == true) { + var data = SERVER["settings"]["files"][fileType][fileID]; + return data[key]; + } + return; +} +function setXmltvChannel(id, element) { + var xmltv = new XMLTVFile(); + var xmlFile = element.value; + var tvgId = SERVER["xepg"]["epgMapping"][id]["tvg-id"]; + var td = document.getElementById("popup-mapping").parentElement; + td.innerHTML = ""; + var select = xmltv.getPrograms(element.value, tvgId); + select.setAttribute("name", "x-mapping"); + select.setAttribute("id", "popup-mapping"); + select.setAttribute("onchange", "javascript: this.className = 'changed'; checkXmltvChannel('" + id + "',this,'" + xmlFile + "');"); + select.className = "changed"; + sortSelect(select); + td.appendChild(select); + checkXmltvChannel(id, select, xmlFile); +} +function checkXmltvChannel(id, element, xmlFile) { + var value = element.value; + var bool; + var checkbox = document.getElementById('active'); + var channel = SERVER["xepg"]["epgMapping"][id]; + var updateLogo; + if (value == "-") { + bool = false; + } + else { + bool = true; + } + checkbox.checked = bool; + checkbox.className = "changed"; + console.log(xmlFile); + // Kanallogo aktualisieren + /* + updateLogo = (document.getElementById("update-icon") as HTMLInputElement).checked + console.log(updateLogo); + */ + if (xmlFile != "xTeVe Dummy" && bool == true) { + //(document.getElementById("update-icon") as HTMLInputElement).checked = true; + //(document.getElementById("update-icon") as HTMLInputElement).className = "changed"; + console.log("ID", id); + changeChannelLogo(id); + return; + } + if (xmlFile == "xTeVe Dummy") { + document.getElementById("update-icon").checked = false; + document.getElementById("update-icon").className = "changed"; + } + return; +} +function changeChannelLogo(id) { + var updateLogo; + var channel = SERVER["xepg"]["epgMapping"][id]; + var f = document.getElementById("popup-xmltv"); + var xmltvFile = f.options[f.selectedIndex].value; + var m = document.getElementById("popup-mapping"); + var xMapping = m.options[m.selectedIndex].value; + var xmltvLogo = SERVER["xepg"]["xmltvMap"][xmltvFile][xMapping]["icon"]; + updateLogo = document.getElementById("update-icon").checked; + if (updateLogo == true && xmltvFile != "xTeVe Dummy") { + if (SERVER["xepg"]["xmltvMap"][xmltvFile].hasOwnProperty(xMapping)) { + var logo = xmltvLogo; + } + else { + logo = channel["tvg-logo"]; + } + var logoInput = document.getElementById("channel-icon"); + logoInput.value = logo; + if (BULK_EDIT == false) { + logoInput.className = "changed"; + } + } +} +function savePopupData(dataType, id, remove, option) { + if (dataType == "mapping") { + var data = new Object(); + console.log("Save mapping data"); + cmd = "saveEpgMapping"; + data["epgMapping"] = SERVER["xepg"]["epgMapping"]; + console.log("SEND TO SERVER"); + var server = new Server(cmd); + server.request(data); + delete UNDO["epgMapping"]; + return; + } + console.log("Save popup data"); + var div = document.getElementById("popup-custom"); + var inputs = div.getElementsByTagName("TABLE")[0].getElementsByTagName("INPUT"); + var selects = div.getElementsByTagName("TABLE")[0].getElementsByTagName("SELECT"); + var input = new Object(); + var confirmMsg; + for (var i = 0; i < selects.length; i++) { + var name; + name = selects[i].name; + var value = selects[i].value; + switch (name) { + case "tuner": + input[name] = parseInt(value); + break; + default: + input[name] = value; + break; + } + } + for (var i = 0; i < inputs.length; i++) { + switch (inputs[i].type) { + case "checkbox": + name = inputs[i].name; + input[name] = inputs[i].checked; + break; + case "text": + case "hidden": + case "password": + name = inputs[i].name; + switch (name) { + case "tuner": + input[name] = parseInt(inputs[i].value); + break; + default: + input[name] = inputs[i].value; + break; + } + break; + } + } + var data = new Object(); + var cmd; + if (remove == true) { + input["delete"] = true; + } + switch (dataType) { + case "users": + confirmMsg = "Delete this user?"; + if (id == "-") { + cmd = "saveNewUser"; + data["userData"] = input; + } + else { + cmd = "saveUserData"; + var d = new Object(); + d[id] = input; + data["userData"] = d; + } + break; + case "m3u": + confirmMsg = "Delete this playlist?"; + switch (option) { + // Popup: Save + case 0: + cmd = "saveFilesM3U"; + break; + // Popup: Update + case 1: + cmd = "updateFileM3U"; + break; + } + data["files"] = new Object; + data["files"][dataType] = new Object; + data["files"][dataType][id] = input; + break; + case "hdhr": + confirmMsg = "Delete this HDHomeRun tuner?"; + switch (option) { + // Popup: Save + case 0: + cmd = "saveFilesHDHR"; + break; + // Popup: Update + case 1: + cmd = "updateFileHDHR"; + break; + } + data["files"] = new Object; + data["files"][dataType] = new Object; + data["files"][dataType][id] = input; + break; + case "xmltv": + confirmMsg = "Delete this XMLTV file?"; + switch (option) { + // Popup: Save + case 0: + cmd = "saveFilesXMLTV"; + break; + // Popup: Update + case 1: + cmd = "updateFileXMLTV"; + break; + } + data["files"] = new Object; + data["files"][dataType] = new Object; + data["files"][dataType][id] = input; + break; + case "filter": + confirmMsg = "Delete this filter?"; + cmd = "saveFilter"; + data["filter"] = new Object; + data["filter"][id] = input; + break; + default: + console.log(dataType, id); + return; + break; + } + if (remove == true) { + if (!confirm(confirmMsg)) { + showElement("popup", false); + return; + } + } + console.log("SEND TO SERVER"); + console.log(data); + var server = new Server(cmd); + server.request(data); +} +function donePopupData(dataType, idsStr) { + var ids = idsStr.split(','); + var div = document.getElementById("popup-custom"); + var inputs = div.getElementsByClassName("changed"); + ids.forEach(function (id) { + var input = new Object(); + input = SERVER["xepg"]["epgMapping"][id]; + console.log(input); + for (var i = 0; i < inputs.length; i++) { + var name; + var value; + switch (inputs[i].tagName) { + case "INPUT": + switch (inputs[i].type) { + case "checkbox": + name = inputs[i].name; + value = inputs[i].checked; + input[name] = value; + break; + case "text": + name = inputs[i].name; + value = inputs[i].value; + input[name] = value; + break; + } + break; + case "SELECT": + name = inputs[i].name; + value = inputs[i].value; + input[name] = value; + break; + } + switch (name) { + case "tvg-logo": + //(document.getElementById(id).childNodes[2].firstChild as HTMLElement).setAttribute("src", value) + break; + case "x-name": + document.getElementById(id).childNodes[3].firstChild.innerHTML = value; + break; + case "x-category": + document.getElementById(id).childNodes[3].firstChild.className = value; + break; + case "x-group-title": + document.getElementById(id).childNodes[5].firstChild.innerHTML = value; + break; + case "x-xmltv-file": + if (value != "xTeVe Dummy" && value != "-") { + value = getValueFromProviderFile(value, "xmltv", "name"); + } + if (value == "-") { + input["x-active"] = false; + } + document.getElementById(id).childNodes[6].firstChild.innerHTML = value; + break; + case "x-mapping": + if (value == "-") { + input["x-active"] = false; + } + document.getElementById(id).childNodes[7].firstChild.innerHTML = value; + break; + default: + } + createSearchObj(); + searchInMapping(); + } + if (input["x-active"] == false) { + document.getElementById(id).className = "notActiveEPG"; + } + else { + document.getElementById(id).className = "activeEPG"; + } + console.log(input["tvg-logo"]); + document.getElementById(id).childNodes[2].firstChild.setAttribute("src", input["tvg-logo"]); + }); + showElement("popup", false); + return; +} +function showPreview(element) { + var div = document.getElementById("myStreamsBox"); + switch (element) { + case false: + div.className = "notVisible"; + return; + break; + } + var streams = ["activeStreams", "inactiveStreams"]; + streams.forEach(function (preview) { + var table = document.getElementById(preview); + table.innerHTML = ""; + var obj = SERVER["data"]["StreamPreviewUI"][preview]; + obj.forEach(function (channel) { + var tr = document.createElement("TR"); + var tdKey = document.createElement("TD"); + var tdVal = document.createElement("TD"); + tdKey.className = "tdKey"; + tdVal.className = "tdVal"; + switch (preview) { + case "activeStreams": + tdKey.innerText = "Channel: (+)"; + break; + case "inactiveStreams": + tdKey.innerText = "Channel: (-)"; + break; + } + tdVal.innerText = channel; + tr.appendChild(tdKey); + tr.appendChild(tdVal); + table.appendChild(tr); + }); + }); + showElement("loading", false); + div.className = "visible"; + return; +} diff --git a/html/js/network_ts.js b/html/js/network_ts.js new file mode 100644 index 0000000..60af0eb --- /dev/null +++ b/html/js/network_ts.js @@ -0,0 +1,105 @@ +var Server = /** @class */ (function () { + function Server(cmd) { + this.cmd = cmd; + } + Server.prototype.request = function (data) { + if (SERVER_CONNECTION == true) { + return; + } + SERVER_CONNECTION = true; + console.log(data); + if (this.cmd != "updateLog") { + showElement("loading", true); + UNDO = new Object(); + } + switch (window.location.protocol) { + case "http:": + this.protocol = "ws://"; + break; + case "https://": + this.protocol = "wss://"; + break; + } + var url = this.protocol + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token"); + data["cmd"] = this.cmd; + var ws = new WebSocket(url); + ws.onopen = function () { + WS_AVAILABLE = true; + console.log("REQUEST (JS):"); + console.log(data); + console.log("REQUEST: (JSON)"); + console.log(JSON.stringify(data)); + this.send(JSON.stringify(data)); + }; + ws.onerror = function (e) { + console.log("No websocket connection to xTeVe could be established. Check your network configuration."); + SERVER_CONNECTION = false; + if (WS_AVAILABLE == false) { + alert("No websocket connection to xTeVe could be established. Check your network configuration."); + } + }; + ws.onmessage = function (e) { + SERVER_CONNECTION = false; + showElement("loading", false); + console.log("RESPONSE:"); + var response = JSON.parse(e.data); + console.log(response); + if (response.hasOwnProperty("token")) { + document.cookie = "Token=" + response["token"]; + } + if (response["status"] == false) { + alert(response["err"]); + if (response.hasOwnProperty("reload")) { + location.reload(); + } + return; + } + if (response.hasOwnProperty("logoURL")) { + var div = document.getElementById("channel-icon"); + div.value = response["logoURL"]; + div.className = "changed"; + return; + } + switch (data["cmd"]) { + case "updateLog": + SERVER["log"] = response["log"]; + if (document.getElementById("content_log")) { + showLogs(false); + } + return; + break; + default: + SERVER = new Object(); + SERVER = response; + break; + } + if (response.hasOwnProperty("openMenu")) { + var menu = document.getElementById(response["openMenu"]); + menu.click(); + showElement("popup", false); + } + if (response.hasOwnProperty("openLink")) { + window.location = response["openLink"]; + } + if (response.hasOwnProperty("alert")) { + alert(response["alert"]); + } + if (response.hasOwnProperty("reload")) { + location.reload(); + } + if (response.hasOwnProperty("wizard")) { + createLayout(); + configurationWizard[response["wizard"]].createWizard(); + return; + } + createLayout(); + }; + }; + return Server; +}()); +function getCookie(name) { + var value = "; " + document.cookie; + var parts = value.split("; " + name + "="); + if (parts.length == 2) + return parts.pop().split(";").shift(); +} diff --git a/html/js/settings_ts.js b/html/js/settings_ts.js new file mode 100644 index 0000000..cc4e461 --- /dev/null +++ b/html/js/settings_ts.js @@ -0,0 +1,442 @@ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var SettingsCategory = /** @class */ (function () { + function SettingsCategory() { + this.DocumentID = "content_settings"; + } + SettingsCategory.prototype.createCategoryHeadline = function (value) { + var element = document.createElement("H4"); + element.innerHTML = value; + return element; + }; + SettingsCategory.prototype.createHR = function () { + var element = document.createElement("HR"); + return element; + }; + SettingsCategory.prototype.createSettings = function (settingsKey) { + var setting = document.createElement("TR"); + var content = new PopupContent(); + var data = SERVER["settings"][settingsKey]; + switch (settingsKey) { + // Texteingaben + case "update": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.update.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createInput("text", "update", data.toString()); + input.setAttribute("placeholder", "{{.settings.update.placeholder}}"); + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "backup.path": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.backupPath.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createInput("text", "backup.path", data); + input.setAttribute("placeholder", "{{.settings.backupPath.placeholder}}"); + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "temp.path": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.tempPath.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createInput("text", "temp.path", data); + input.setAttribute("placeholder", "{{.settings.tmpPath.placeholder}}"); + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "user.agent": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.userAgent.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createInput("text", "user.agent", data); + input.setAttribute("placeholder", "{{.settings.userAgent.placeholder}}"); + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "buffer.timeout": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.bufferTimeout.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createInput("text", "buffer.timeout", data); + input.setAttribute("placeholder", "{{.settings.bufferTimeout.placeholder}}"); + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + // Checkboxen + case "authentication.web": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.authenticationWEB.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "authentication.pms": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.authenticationPMS.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "authentication.m3u": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.authenticationM3U.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "authentication.xml": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.authenticationXML.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "authentication.api": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.authenticationAPI.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "files.update": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.filesUpdate.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "cache.images": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.cacheImages.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "xepg.replace.missing.images": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.replaceEmptyImages.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "xteveAutoUpdate": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.xteveAutoUpdate.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "buffer": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "api": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.api.title}}" + ":"; + var tdRight = document.createElement("TD"); + var input = content.createCheckbox(settingsKey); + input.checked = data; + input.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(input); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + // Select + case "tuner": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.tuner.title}}" + ":"; + var tdRight = document.createElement("TD"); + var text = new Array(); + var values = new Array(); + for (var i = 1; i <= 100; i++) { + text.push(i); + values.push(i); + } + var select = content.createSelect(text, values, data, settingsKey); + select.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(select); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "epgSource": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.epgSource.title}}" + ":"; + var tdRight = document.createElement("TD"); + var text = ["PMS", "XEPG"]; + var values = ["PMS", "XEPG"]; + var select = content.createSelect(text, values, data, settingsKey); + select.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(select); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "backup.keep": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.backupKeep.title}}" + ":"; + var tdRight = document.createElement("TD"); + var text = ["5", "10", "20", "30", "40", "50"]; + var values = ["5", "10", "20", "30", "40", "50"]; + var select = content.createSelect(text, values, data, settingsKey); + select.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(select); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + case "buffer.size.kb": + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = "{{.settings.bufferSize.title}}" + ":"; + var tdRight = document.createElement("TD"); + var text = ["0.5 MB", "1 MB", "2 MB", "3 MB", "4 MB", "5 MB", "6 MB", "7 MB", "8 MB"]; + var values = ["512", "1024", "2048", "3072", "4096", "5120", "6144", "7168", "8192"]; + var select = content.createSelect(text, values, data, settingsKey); + select.setAttribute("onchange", "javascript: this.className = 'changed'"); + tdRight.appendChild(select); + setting.appendChild(tdLeft); + setting.appendChild(tdRight); + break; + } + return setting; + }; + SettingsCategory.prototype.createDescription = function (settingsKey) { + var description = document.createElement("TR"); + var text; + switch (settingsKey) { + case "authentication.web": + text = "{{.settings.authenticationWEB.description}}"; + break; + case "authentication.m3u": + text = "{{.settings.authenticationM3U.description}}"; + break; + case "authentication.pms": + text = "{{.settings.authenticationPMS.description}}"; + break; + case "authentication.xml": + text = "{{.settings.authenticationXML.description}}"; + break; + case "authentication.api": + if (SERVER["settings"]["authentication.web"] == true) { + text = "{{.settings.authenticationAPI.description}}"; + } + break; + case "xteveAutoUpdate": + text = "{{.settings.xteveAutoUpdate.description}}"; + break; + case "backup.keep": + text = "{{.settings.backupKeep.description}}"; + break; + case "backup.path": + text = "{{.settings.backupPath.description}}"; + break; + case "temp.path": + text = "{{.settings.tempPath.description}}"; + break; + case "buffer": + text = "{{.settings.streamBuffering.description}}"; + break; + case "buffer.size.kb": + text = "{{.settings.bufferSize.description}}"; + break; + case "buffer.timeout": + text = "{{.settings.bufferTimeout.description}}"; + break; + case "user.agent": + text = "{{.settings.userAgent.description}}"; + break; + case "epgSource": + text = "{{.settings.epgSource.description}}"; + break; + case "tuner": + text = "{{.settings.tuner.description}}"; + break; + case "update": + text = "{{.settings.update.description}}"; + break; + case "api": + text = "{{.settings.api.description}}"; + break; + case "files.update": + text = "{{.settings.filesUpdate.description}}"; + break; + case "cache.images": + text = "{{.settings.cacheImages.description}}"; + break; + case "xepg.replace.missing.images": + text = "{{.settings.replaceEmptyImages.description}}"; + break; + default: + text = ""; + break; + } + var tdLeft = document.createElement("TD"); + tdLeft.innerHTML = ""; + var tdRight = document.createElement("TD"); + var pre = document.createElement("PRE"); + pre.innerHTML = text; + tdRight.appendChild(pre); + description.appendChild(tdLeft); + description.appendChild(tdRight); + return description; + }; + return SettingsCategory; +}()); +var SettingsCategoryItem = /** @class */ (function (_super) { + __extends(SettingsCategoryItem, _super); + function SettingsCategoryItem(headline, settingsKeys) { + var _this = _super.call(this) || this; + _this.headline = headline; + _this.settingsKeys = settingsKeys; + return _this; + } + SettingsCategoryItem.prototype.createCategory = function () { + var _this = this; + var headline = this.createCategoryHeadline(this.headline); + var settingsKeys = this.settingsKeys; + var doc = document.getElementById(this.DocumentID); + doc.appendChild(headline); + // Tabelle für die Kategorie erstellen + var table = document.createElement("TABLE"); + var keys = settingsKeys.split(","); + keys.forEach(function (settingsKey) { + switch (settingsKey) { + case "authentication.pms": + case "authentication.m3u": + case "authentication.xml": + case "authentication.api": + if (SERVER["settings"]["authentication.web"] == false) { + break; + } + default: + var item = _this.createSettings(settingsKey); + var description = _this.createDescription(settingsKey); + table.appendChild(item); + table.appendChild(description); + break; + } + }); + doc.appendChild(table); + doc.appendChild(this.createHR()); + }; + return SettingsCategoryItem; +}(SettingsCategory)); +function showSettings() { + console.log("SETTINGS"); + for (var i = 0; i < settingsCategory.length; i++) { + settingsCategory[i].createCategory(); + } +} +function saveSettings() { + console.log("Save Settings"); + var cmd = "saveSettings"; + var div = document.getElementById("content_settings"); + var settings = div.getElementsByClassName("changed"); + var newSettings = new Object(); + for (var i = 0; i < settings.length; i++) { + var name; + var value; + switch (settings[i].tagName) { + case "INPUT": + switch (settings[i].type) { + case "checkbox": + name = settings[i].name; + value = settings[i].checked; + newSettings[name] = value; + break; + case "text": + name = settings[i].name; + value = settings[i].value; + switch (name) { + case "update": + value = value.split(","); + value = value.filter(function (e) { return e; }); + break; + case "buffer.timeout": + value = parseFloat(value); + } + newSettings[name] = value; + break; + } + break; + case "SELECT": + name = settings[i].name; + value = settings[i].value; + // Wenn der Wert eine Zahl ist, wird dieser als Zahl gespeichert + if (isNaN(value)) { + newSettings[name] = value; + } + else { + newSettings[name] = parseInt(value); + } + break; + } + } + var data = new Object(); + data["settings"] = newSettings; + var server = new Server(cmd); + server.request(data); +} diff --git a/html/js/users.js b/html/js/users.js new file mode 100644 index 0000000..8cf9414 --- /dev/null +++ b/html/js/users.js @@ -0,0 +1,341 @@ +function openUsers(elm) { + colomnSort = 0; + + var newDiv = document.getElementById("settings"); + + var newEntry = new Object(); + newEntry["_element"] = "HR"; + newDiv.appendChild(createElement(newEntry)); + + var newEntry = new Object(); + newEntry["_element"] = "INPUT"; + newEntry["type"] = "button"; + newEntry["class"] = "button"; + newEntry["value"] = "New"; + newEntry["onclick"] = "userDetail(0)"; + newDiv.appendChild(createElement(newEntry)); + + var div = document.getElementById("settings"); + + // Build table + var newTable = new Object(); + newTable["_element"] = "TABLE"; + newTable["id"] = "id_mapping"; + newTable["class"] = "table-mapping"; + div.appendChild(createElement(newTable)); + + setTimeout(function(){ + createUsersTable(); + }, 10); +} + +function createUsersTable() { + var table = document.getElementById("id_mapping"); + table.innerHTML = ""; + var newTR = new Object(); + newTR["_element"] = "TR"; + newTR["class"] = "table-mapping-header"; + table.appendChild(createElement(newTR)); + + var tr = table.lastChild; + var trHeadlines = new Array("Username", "Password", "WEB", "PMS", "M3U", "XML", "API") + + for (var i = 0; i < trHeadlines.length; i++) { + var newTD = new Object(); + newTD["_element"] = "TD"; + newTD["_text"] = trHeadlines[i]; + tr.appendChild(createElement(newTD)); + } + + + // Sort users + var userIds = getObjKeys(users); + + var userObj = new Object(); + + for (var i = 0; i < userIds.length; i++) { + var username = users[userIds[i]]["data"]["username"]; + userObj[username] = userIds[i]; + } + + var allUsers = getObjKeys(userObj); + allUsers.sort(); + // -- + + for (var i = 0; i < allUsers.length; i++) { + var table = document.getElementById("id_mapping"); + var userID = userObj[allUsers[i]]; + var username = allUsers[i]; + var item = users[userID]["data"]; + + // Create TR + var newTR = new Object(); + newTR["_element"] = "TR"; + newTR["class"] = ""; + newTR["id"] = userID; + newTR["onclick"] = 'javascript: userDetail("' + userID + '");'; + table.appendChild(createElement(newTR)); + + var tr = table.lastChild; + + // Create username TD + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["_text"] = username; + createNewTD(newTD, tr); + + // Create password TD + var newTD = new Object(); + newTD["_element"] = "P"; + newTD["_text"] = "....."; + createNewTD(newTD, tr); + + // Create web access + var newTD = new Object(); + newTD["_element"] = "P"; + switch(item["authentication.web"]){ + case true: newTD["_text"] = "✓"; break; + default: newTD["_text"] = "-"; break; + } + createNewTD(newTD, tr); + + // Create PMS access + var newTD = new Object(); + newTD["_element"] = "P"; + switch(item["authentication.pms"]){ + case true: newTD["_text"] = "✓"; break; + default: newTD["_text"] = "-"; break; + } + createNewTD(newTD, tr); + + // Create M3U access + var newTD = new Object(); + newTD["_element"] = "P"; + switch(item["authentication.m3u"]){ + case true: newTD["_text"] = "✓"; break; + default: newTD["_text"] = "-"; break; + } + createNewTD(newTD, tr); + + // Create XMLTV access + var newTD = new Object(); + newTD["_element"] = "P"; + switch(item["authentication.xml"]){ + case true: newTD["_text"] = "✓"; break; + default: newTD["_text"] = "-"; break; + } + createNewTD(newTD, tr); + + // Create API access + var newTD = new Object(); + newTD["_element"] = "P"; + + switch(item["authentication.api"]){ + case true: newTD["_text"] = "✓"; break; + default: newTD["_text"] = "-"; break; + } + createNewTD(newTD, tr); + + } + + // usage Info + var div = document.getElementById("settings"); + switch(menu[activeMenu.id].hasOwnProperty("_usage")) { + case true: + var usageItem = new Object(); + usageItem["_element"] = "PRE" + usageItem["_text"] = menu[activeMenu.id]["_usage"]; + + var newHR = new Object(); + newHR["_element"] = "HR" + div.appendChild(createElement(newHR)); + div.appendChild(createElement(usageItem)); + } + + sortTable(0); +} + +function userDetail(userID) { + showPopUpElement('user-detail'); + setTimeout(function(){ + showElement("popup", true); + }, 10); + var defaultUser; + + document.getElementById("saveUserDetail").setAttribute("onclick", 'javascript: saveUserDetail("' + userID + '", false)'); + document.getElementById("deleteUserDetail").setAttribute("onclick", 'javascript: saveUserDetail("' + userID + '", true)'); + + var data = new Object(); + + switch(userID) { + case 0: // New User + data["username"] = ""; + data["authentication.web"] = false; + data["authentication.pms"] = true; + data["authentication.xml"] = true; + data["authentication.m3u"] = false; + data["authentication.api"] = false; + data["defaultUser"] = false; + setTimeout(function(){ + showElement("deleteUserDetail", false) + }, 1); + + break; + + default: + data = users[userID]["data"]; + showElement("deleteUserDetail", true) + document.getElementById("deleteUserDetail").className = "delete"; + + break + } + + + var username = data["username"]; + data["password"] = ""; + data["confirm"] = ""; + + var keys = getObjKeys(data); + defaultUser = data["defaultUser"]; + if (data.hasOwnProperty("defaultUser")) { + defaultUser = JSON.parse(data["defaultUser"]); + } + + for (var i = 0; i < keys.length; i++) { + + if(document.getElementById(keys[i])){ + var td = document.getElementById(keys[i]) + } else { + var td = undefined; + } + + var newItem = new Object(); + + newItem["_element"] = "INPUT"; + + newItem["value"] = data[keys[i]]; + newItem["name"] = keys[i]; + + + + + + switch(keys[i].indexOf("authentication")) { + case -1: + if (keys[i] == "password" || keys[i] == "confirm") { + newItem["type"] = "password"; + } else { + newItem["type"] = "text"; + } + break; + + default: + newItem["type"] = "checkbox"; + + if (keys[i] == "authentication.web" && defaultUser == true) { + newItem["onclick"] = "return false"; + } + + if (data[keys[i]] == true) { + newItem["checked"] = data[keys[i]]; + } + + break; + } + + switch(keys[i]) { + case "defaultUser": + //if (data[keys[i]] == true) { + newItem["type"] = "hidden"; + //} + } + + + if (td != undefined) { + td.innerHTML = ""; + var element = createNewElement(newItem) + //console.log(element); + td.appendChild(element); + } + + + } + + + if (defaultUser == true) { + showElement("deleteUserDetail", false) + } else { + showElement("deleteUserDetail", true) + document.getElementById("deleteUserDetail").className = "delete"; + } + +} + +function saveUserDetail(userID, deleteUser) { + + var inputs = document.getElementById("user-detail-table").getElementsByTagName("INPUT"); + + var newUserData = new Object(); + for (var i = 0; i < inputs.length; i++) { + switch(inputs[i].type) { + case "checkbox": newUserData[inputs[i].name] = inputs[i].checked; break; + default: newUserData[inputs[i].name] = inputs[i].value; break; + } + + if (inputs["username"].value.length == 0) { + inputs["username"].style.border = "solid 1px red"; + return; + } + + switch(userID) { + case "0": + if (inputs["password"].value.length == 0) { + console.log(inputs["password"].value.length); + inputs["password"].style.border = "solid 1px red"; + return + } + break; + } + + if (inputs["password"].value.length > 0) { + if (inputs["password"].value != inputs["confirm"].value) { + inputs["password"].style.border = "solid 1px red"; + inputs["confirm"].style.border = "solid 1px red"; + return; + } + } + + } + + var data = new Object(); + + switch(userID) { + case "0": + //data = newUserData + data["userData"] = newUserData + data["cmd"] = "saveNewUser"; break; + + default: + var thisUser = new Object(); + + if (deleteUser == true) { + if (confirm('Delete the selected user?')) { + data["deleteUser"] = true; + } else { + showElement("popup", false); + return + } + } + + thisUser[userID] = newUserData; + + data["userData"] = thisUser; + data["cmd"] = "saveUserData"; break; + } + + xTeVe(data); + //createUsersTable() + showElement("popup", false); +} + + diff --git a/html/lang/en.json b/html/lang/en.json new file mode 100644 index 0000000..a6bc3ad --- /dev/null +++ b/html/lang/en.json @@ -0,0 +1,419 @@ +{ + "mainMenu": { + "item":{ + "playlist": "Playlist", + "pmsID": "PMS ID", + "filter": "Filter", + "xmltv": "XMLTV", + "mapping": "Mapping", + "users": "Users", + "settings": "Settings", + "log": "Log", + "logout": "Logout" + }, + "headline": { + "playlist": "Local or remote playlists", + "filter": "Filter playlist", + "xmltv": "Local or remote XMLTV files", + "mapping": "Map playlist channels to EPG channels", + "users": "User management", + "settings": "Settings", + "log": "Log", + "logout": "Logout" + } + }, + "confirm":{ + "restore": "All data will be replaced with those from the backup.Should the files be restored?" + }, + "alert": { + "fileLoadingError": "File couldn't be loaded", + "invalidChannelNumber": "Invalid channel number" + }, + "button":{ + "back": "Back", + "backup": "Backup", + "bulkEdit": "Bulk Edit", + "cancel": "Cancel", + "delete": "Delete", + "done": "Done", + "login": "Login", + "new": "New", + "next": "Next", + "restore": "Restore", + "save": "Save", + "search": "Search", + "update": "Update", + "craeteAccount": "Create Account", + "resetlogs": "Reset Logs", + "uploadLogo": "Upload Logo" + }, + "filter": { + "table": { + "name": "Filter Name", + "type": "Filter Type", + "filter": "Filter" + }, + "custom": "Custom", + "group": "Group", + "name": { + "title": "Filter Name", + "placeholder": "Filter name", + "description": "" + }, + "description": { + "title": "Description", + "placeholder": "Description", + "description": "" + }, + "type": { + "title": "Type", + "groupTitle": "Group Title", + "customFilter": "Custom Filter" + }, + "caseSensitive": { + "title": "Case Sensitive", + "placeholder": "", + "description": "" + }, + "filterRule": { + "title": "Filter Rule", + "placeholder": "Sport {HD} !{ES,IT}", + "description": "" + }, + "filterGroup": { + "title": "Group Title", + "placeholder": "", + "description": "Select a M3U group. (Counter)
Changing the group title in the M3U invalidates the filter." + }, + "include": { + "title": "Include", + "placeholder": "FHD,UHD", + "description": "Channel name must include.
(Comma separated) Comma means or" + }, + "exclude": { + "title": "Exclude", + "placeholder": "ES,IT", + "description": "Channel name must not contain.
(Comma separated) Comma means or" + } + + }, + "playlist": { + "table": { + "playlist": "Playlist", + "tuner": "Tuner", + "lastUpdate": "Last Update", + "availability": "Availability", + "type": "Type", + "streams": "Streams", + "groupTitle": "group-title", + "tvgID": "tvg-id", + "uniqueID": "Unique ID" + }, + "playlistType": { + "title": "Playlist type", + "placeholder": "", + "description": "" + }, + "type": { + "title": "Type", + "placeholder": "", + "description": "" + }, + "name": { + "title": "Name", + "placeholder": "Playlist name", + "description": "" + }, + "description": { + "title": "Description", + "placeholder": "Description", + "description": "" + }, + "fileM3U": { + "title": "M3U File", + "placeholder": "File path or URL of the M3U", + "description": "" + }, + "fileHDHR": { + "title": "HDHomeRun IP", + "placeholder": "IP address and port (192.168.1.10:5004)", + "description": "" + }, + "tuner": { + "title": "Tuner / Streams", + "placeholder": "", + "description": "Number of parallel connections that can be established to the provider.
Only available with activated buffer.
New settings will only be applied after quitting all streams." + } + }, + "xmltv": { + "table": { + "guide": "Guide", + "lastUpdate": "Last Update", + "availability": "Availability", + "channels": "Channels", + "programs": "Programs" + }, + "name": { + "title": "Name", + "placeholder": "Guide name", + "description": "" + }, + "description": { + "title": "Description", + "placeholder": "Description", + "description": "" + }, + "fileXMLTV": { + "title": "XMLTV File", + "placeholder": "File path or URL of the XMLTV", + "description": "" + } + }, + "mapping": { + "table": { + "chNo": "Ch. No.", + "logo": "Logo", + "channelName": "Channel Name", + "playlist": "Playlist", + "groupTitle": "Group Title", + "xmltvFile": "XMLTV File", + "xmltvID": "XMLTV ID" + }, + "active": { + "title": "Active", + "placeholder": "", + "description": "" + }, + "channelName": { + "title": "Channel Name", + "placeholder": "", + "description": "" + }, + "updateChannelName": { + "title": "Update Channel Name", + "placeholder": "", + "description": "" + }, + "channelLogo": { + "title": "Logo URL", + "placeholder": "", + "description": "" + }, + "updateChannelLogo": { + "title": "Update Channel Logo", + "placeholder": "", + "description": "" + }, + "epgCategory": { + "title": "EPG Category", + "placeholder": "", + "description": "" + }, + "m3uGroupTitle": { + "title": "Group Title (xteve.m3u)", + "placeholder": "", + "description": "" + }, + "xmltvFile": { + "title": "XMLTV File", + "placeholder": "", + "description": "" + }, + "xmltvChannel": { + "title": "XMLTV Channel", + "placeholder": "", + "description": "" + } + }, + "users": { + "table": { + "username": "Username", + "password": "Password", + "web": "WEB", + "pms": "PMS", + "m3u": "M3U", + "xml": "XML", + "api": "API" + }, + "username": { + "title": "Username", + "placeholder": "Username", + "description": "" + }, + "password": { + "title": "Password", + "placeholder": "Passoword", + "description": "" + }, + "confirm": { + "title": "Confirm", + "placeholder": "Password confirm", + "description": "" + }, + "web": { + "title": "Web Access", + "placeholder": "", + "description": "" + }, + "pms": { + "title": "PMS Access", + "placeholder": "", + "description": "" + }, + "m3u": { + "title": "M3U Access", + "placeholder": "", + "description": "" + }, + "xml": { + "title": "XML Access", + "placeholder": "", + "description": "" + }, + "api": { + "title": "API Access", + "placeholder": "", + "description": "" + } + }, + "settings": { + "category": { + "general": "General", + "files": "Files", + "streaming": "Streaming", + "backup": "Backup", + "authentication": "Authentication" + }, + "update": { + "title": "Schedule for updating (Playlist, XMLTV, Backup)", + "placeholder": "0000,1000,2000", + "description": "Time in 24 hour format (0800 = 8:00 am). More times can be entered comma separated." + }, + "api": { + "title": "API Interface", + "description": "Via API interface it is possible to send commands to xTeVe. API documentation is here" + }, + "epgSource": { + "title": "EPG Source", + "description": "PMS:
- Use EPG data from Plex or Emby

XEPG:
- Use of one or more XMLTV files
- Channel management
- M3U / XMLTV export (HTTP link for IPTV apps)" + }, + "tuner":{ + "title": "Number of Tuners", + "description": "Number of parallel connections that can be established to the provider.
Available for: Plex, Emby (HDHR), M3U (with active buffer).
After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again." + }, + "filesUpdate": { + "title": "Updates all files at startup", + "description": "Updates all playlists, tuner and XMLTV files at startup." + }, + "cacheImages": { + "title": "Image caching", + "description": "All images from the XMLTV file are cached, allowing faster rendering of the grid in the client.
Downloading the images may take a while and will be done in the background." + }, + "replaceEmptyImages": { + "title": "Replace missing program images", + "description": "If the poster in the XMLTV program is missing, the channel logo will be used." + }, + "xteveAutoUpdate": { + "title": "Automatic update of xTeVe", + "description": "If a new version of xTeVe is available, it will be automatically installed. The updates are downloaded from GitHub." + }, + "streamBuffering": { + "title": "Stream Buffer", + "description": "- The stream is passed from xTeVe to Plex / Emby / M3U Player
- Small jerking of the streams can be compensated
- HLS / M3U8 support" + }, + "bufferSize": { + "title": "Buffer Size", + "description": "Buffer size in MB.
M3U8: If the TS segment smaller then the buffer size, the file size of the segment is used." + }, + "bufferTimeout": { + "title": "Timeout for new client connections", + "description": "The xTeVe buffer waits until new client connections are established. Helpful for fast channel switching. Value in milliseconds.", + "placeholder": "100" + }, + "userAgent": { + "title": "User agent", + "description": "User Agent for HTTP requests", + "placeholder": "xTeVe" + }, + "backupPath": { + "title": "Location for automatic backups", + "placeholder": "/mnt/data/backup/xteve/", + "description": "Before any update of the provider data by the schedule, xTeVe creates a backup. The path for the automatic backups can be changed. xTeVe requires write permission for this folder." + }, + "tempPath": { + "title": "Location for the temporary files", + "placeholder": "/tmp/xteve/", + "description": "Location for the buffer files." + }, + "backupKeep": { + "title": "Number of backups to keep", + "description": "Number of backups to keep. Older backups are automatically deleted." + }, + "authenticationWEB": { + "title": "WEB Authentication", + "description": "Access to the web interface only possible with credentials." + }, + "authenticationPMS": { + "title": "PMS Authentication", + "description": "Plex requests are only possible with authentication.
Warning!!! After activating this function xTeVe must be delete in the PMS DVR settings and set up again." + }, + "authenticationM3U": { + "title": "M3U Authentication", + "description": "Downloading the xteve.m3u file via an HTTP request is only possible with authentication." + }, + "authenticationXML": { + "title": "XML Authentication", + "description": "Downloading the xteve.xml file via an HTTP request is only possible with authentication" + }, + "authenticationAPI": { + "title": "API Authentication", + "description": "Access to the API interface is only possible with authentication." + } + }, + "wizard": { + "epgSource": { + "title": "EPG Source", + "description": "PMS:
- Use EPG data from Plex or Emby

XEPG:
- Use of one or more XMLTV files
- Channel management
- M3U / XMLTV export (HTTP link for IPTV apps)" + }, + "tuner":{ + "title": "Number of tuners", + "description": "Number of parallel connections that can be established to the provider.
Available for: Plex, Emby (HDHR), M3U (with active buffer).
After a change, xTeVe must be delete in the Plex / Emby DVR settings and set up again." + }, + "m3u": { + "title": "M3U Playlist", + "description": "Local or remote playlists" + }, + "xmltv": { + "title": "XMLTV File", + "description": "Local or remote XMLTV file" + } + }, + "login": { + "failed": "User authentication failed", + "headline": "Login", + "username": { + "title": "Username", + "placeholder": "Username" + }, + "password": { + "title": "Password", + "placeholder": "Password" + } + }, + "account": { + "failed": "Password does not match", + "headline": "Create user account", + "username": { + "title": "Username", + "placeholder": "Username" + }, + "password": { + "title": "Password", + "placeholder": "Password" + }, + "confirm": { + "title": "Confirm", + "placeholder": "Confirm" + } + } +} diff --git a/html/login.html b/html/login.html new file mode 100644 index 0000000..d201713 --- /dev/null +++ b/html/login.html @@ -0,0 +1,46 @@ + + + + + + xTeVe + + + + + + + + + + +
+ +
+

{{.login.headline}}

+
+ +

{{.authenticationErr}}

+ +
+ +
+ +
{{.login.username.title}}:
+ +
{{.login.password.title}}:
+ + +
+ +
+ + + +
+ + + + \ No newline at end of file diff --git a/html/maintenance.html b/html/maintenance.html new file mode 100644 index 0000000..b1ba141 --- /dev/null +++ b/html/maintenance.html @@ -0,0 +1,30 @@ + + + + + + xTeVe + + + + + + + + +
+ +
+

Maintenance

+
+ +
+ xTeVe is updating the database, please try again later. +
+ + + +
+ + + \ No newline at end of file diff --git a/html/video/stream-limit.ts b/html/video/stream-limit.ts new file mode 100644 index 0000000000000000000000000000000000000000..2ed6a89ea82c8629b5e81152d92e14815b0bb494 GIT binary patch literal 23876 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�-H|en+*s${ScV-`v(goq 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≀z^0&_{5 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 tzwg9F45SDwIIa}mKi 0 { + + if v, ok := userData[level].(bool); ok { + + if v == false { + err = errors.New("No authorization") + } + + } else { + userData[level] = false + err = authentication.WriteUserData(userID, userData) + err = errors.New("No authorization") + } + + } else { + err = authentication.WriteUserData(userID, userData) + err = errors.New("No authorization") + } + + return +} diff --git a/src/backup.go b/src/backup.go new file mode 100644 index 0000000..a8d2adf --- /dev/null +++ b/src/backup.go @@ -0,0 +1,191 @@ +package src + +import ( + b64 "encoding/base64" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func xTeVeAutoBackup() (err error) { + + var archiv = "xteve_auto_backup_" + time.Now().Format("20060102_1504") + ".zip" + var target string + var sourceFiles = make([]string, 0) + var oldBackupFiles = make([]string, 0) + var debug string + + if len(Settings.BackupPath) > 0 { + System.Folder.Backup = Settings.BackupPath + } + + showInfo("Backup Path:" + System.Folder.Backup) + + err = checkFolder(System.Folder.Backup) + if err != nil { + ShowError(err, 1070) + return + } + + // Alte Backups löschen + files, err := ioutil.ReadDir(System.Folder.Backup) + + if err == nil { + + for _, file := range files { + + if filepath.Ext(file.Name()) == ".zip" && strings.Contains(file.Name(), "xteve_auto_backup") { + oldBackupFiles = append(oldBackupFiles, file.Name()) + } + + } + + // Alle Backups löschen + var end int + switch Settings.BackupKeep { + case 0: + end = 0 + default: + end = Settings.BackupKeep - 1 + } + + for i := 0; i < len(oldBackupFiles)-end; i++ { + + os.RemoveAll(System.Folder.Backup + oldBackupFiles[i]) + debug = fmt.Sprintf("Delete backup file:%s", oldBackupFiles[i]) + showDebug(debug, 1) + + } + + if Settings.BackupKeep == 0 { + return + } + + } else { + + return + + } + + // Backup erstellen + if err == nil { + + target = System.Folder.Backup + archiv + + for _, i := range SystemFiles { + sourceFiles = append(sourceFiles, System.Folder.Config+i) + } + + sourceFiles = append(sourceFiles, System.Folder.ImagesUpload) + + err = zipFiles(sourceFiles, target) + + if err == nil { + + debug = fmt.Sprintf("Create backup file:%s", target) + showDebug(debug, 1) + + showInfo("Backup file:" + target) + + } + + } + + return +} + +func xteveBackup() (archiv string, err error) { + + err = checkFolder(System.Folder.Temp) + if err != nil { + return + } + + archiv = "xteve_backup_" + time.Now().Format("20060102_1504") + ".zip" + + var target = System.Folder.Temp + archiv + var sourceFiles = make([]string, 0) + + for _, i := range SystemFiles { + sourceFiles = append(sourceFiles, System.Folder.Config+i) + } + + sourceFiles = append(sourceFiles, System.Folder.Data) + + err = zipFiles(sourceFiles, target) + if err != nil { + ShowError(err, 0) + return + } + + return +} + +func xteveRestore(input string) (newWebURL string, err error) { + + var newPort, oldPort string + + // Base64 Json String in base64 umwandeln + b64data := input[strings.IndexByte(input, ',')+1:] + + // Base64 in bytes umwandeln und speichern + sDec, err := b64.StdEncoding.DecodeString(b64data) + + if err != nil { + return + } + + var archive = System.Folder.Temp + "restore.zip" + + err = writeByteToFile(archive, sDec) + if err != nil { + return + } + + // Zip Archiv entpacken + err = extractZIP(archive, System.Folder.Config) + if err != nil { + return + } + + // Neue Config laden um den Port zu überprüfen + newConfig, err := loadJSONFileToMap(System.Folder.Config + "settings.json") + if err != nil { + ShowError(err, 0) + return + } + + newPort = newConfig["port"].(string) + oldPort = Settings.Port + + if newPort == oldPort { + + if err != nil { + ShowError(err, 0) + } + + loadSettings() + + err := Init() + if err != nil { + ShowError(err, 0) + return "", err + } + + err = StartSystem(true) + if err != nil { + ShowError(err, 0) + return "", err + } + + return "", err + } + + var url = System.URLBase + "/web/" + newWebURL = strings.Replace(url, ":"+oldPort, ":"+newPort, 1) + + return +} diff --git a/src/buffer.go b/src/buffer.go new file mode 100644 index 0000000..470e8e9 --- /dev/null +++ b/src/buffer.go @@ -0,0 +1,1405 @@ +package src + +/* + Tuner-Limit Bild als Video rendern [ffmpeg] + -loop 1 -i stream-limit.jpg -c:v libx264 -t 1 -pix_fmt yuv420p -vf scale=1920:1080 stream-limit.ts +*/ + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "path" + "sort" + "strconv" + "strings" + "time" +) + +func createStreamID(stream map[int]ThisStream) (streamID int) { + + var debug string + + streamID = 0 + for i := 0; i <= len(stream); i++ { + + if _, ok := stream[i]; !ok { + streamID = i + break + } + + } + + debug = fmt.Sprintf("Streaming Status:Stream ID = %d", streamID) + showDebug(debug, 1) + + return +} + +func bufferingStream(playlistID, streamingURL, channelName string, w http.ResponseWriter, r *http.Request) { + + time.Sleep(time.Duration(Settings.BufferTimeout) * time.Millisecond) + + var playlist Playlist + var client ThisClient + var stream ThisStream + var streaming = false + var streamID int + var debug string + var timeOut = 0 + var newStream = true + + //w.Header().Set("Connection", "keep-alive") + w.Header().Set("Connection", "close") + + // Überprüfen ob die Playlist schon verwendet wird + if p, ok := BufferInformation.Load(playlistID); !ok { + + var playlistType string + // Playlist wird noch nicht verwendet, Default-Werte für die Playlist erstellen + playlist.Folder = System.Folder.Temp + playlistID + string(os.PathSeparator) + playlist.PlaylistID = playlistID + playlist.Streams = make(map[int]ThisStream) + playlist.Clients = make(map[int]ThisClient) + + err := checkFolder(playlist.Folder) + if err != nil { + ShowError(err, 000) + httpStatusError(w, r, 404) + return + } + + switch playlist.PlaylistID[0:1] { + + case "M": + playlistType = "m3u" + + case "H": + playlistType = "hdhr" + + } + + playlist.Tuner = getTuner(playlistID, playlistType) + + playlist.PlaylistName = getProviderParameter(playlist.PlaylistID, playlistType, "name") + + // Default-Werte für den Stream erstellen + streamID = createStreamID(playlist.Streams) + + client.Connection = 1 + stream.URL = streamingURL + stream.ChannelName = channelName + stream.Status = false + + playlist.Streams[streamID] = stream + playlist.Clients[streamID] = client + + BufferInformation.Store(playlistID, playlist) + + } else { + + // Playlist wird bereits zum streamen verwendet + // Überprüfen ob die URL bereit von einem anderen Client gestreamt wird. + + playlist = p.(Playlist) + + for id := range playlist.Streams { + + stream = playlist.Streams[id] + client = playlist.Clients[id] + + if streamingURL == stream.URL { + + streamID = id + newStream = false + client.Connection++ + + //playlist.Streams[streamID] = stream + playlist.Clients[streamID] = client + + BufferInformation.Store(playlistID, playlist) + + debug = fmt.Sprintf("Restream Status:Playlist: %s - Channel: %s - Connections: %d", playlist.PlaylistName, stream.ChannelName, client.Connection) + + showDebug(debug, 1) + + if c, ok := BufferClients.Load(playlistID + stream.MD5); ok { + + var clients = c.(ClientConnection) + clients.Connection = clients.Connection + 1 + showInfo(fmt.Sprintf("Streaming Status:Channel: %s (Clients: %d)", stream.ChannelName, clients.Connection)) + + BufferClients.Store(playlistID+stream.MD5, clients) + + } + + break + } + + } + + // Neuer Stream bei einer bereits aktiven Playlist + if newStream == true { + + // Prüfen ob die Playlist noch einen weiteren Stream erlaubt (Tuner) + if len(playlist.Streams) >= playlist.Tuner { + + showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - No new connections available. Tuner = %d", playlist.PlaylistName, playlist.Tuner)) + + if value, ok := webUI["html/video/stream-limit.ts"]; ok { + + var content string + content = GetHTMLString(value.(string)) + + w.WriteHeader(200) + w.Header().Set("Content-type", "video/mpeg") + w.Header().Set("Content-Length:", "0") + + for i := 1; i < 60; i++ { + _ = i + w.Write([]byte(content)) + time.Sleep(time.Duration(500) * time.Millisecond) + } + + return + } + + return + } + + // Playlist erlaubt einen weiterern Stream (Das Limit des Tuners ist noch nicht erreicht) + // Default-Werte für den Stream erstellen + stream = ThisStream{} + client = ThisClient{} + + streamID = createStreamID(playlist.Streams) + + client.Connection = 1 + stream.URL = streamingURL + stream.ChannelName = channelName + stream.Status = false + + playlist.Streams[streamID] = stream + playlist.Clients[streamID] = client + + BufferInformation.Store(playlistID, playlist) + + } + + } + + // Überprüfen ob der Stream breits von einem anderen Client abgespielt wird + if playlist.Streams[streamID].Status == false && newStream == true { + + // Neuer Buffer wird benötigt + stream = playlist.Streams[streamID] + stream.MD5 = getMD5(streamingURL) + stream.Folder = playlist.Folder + stream.MD5 + string(os.PathSeparator) + stream.PlaylistID = playlistID + stream.PlaylistName = playlist.PlaylistName + + playlist.Streams[streamID] = stream + BufferInformation.Store(playlistID, playlist) + + go connectToStreamingServer(streamID, playlist) + + showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner)) + + var clients ClientConnection + clients.Connection = 1 + BufferClients.Store(playlistID+stream.MD5, clients) + + } + + w.WriteHeader(200) + + for { // Loop 1: Warten bis das erste Segment durch den Buffer heruntergeladen wurde + + if stream, ok := playlist.Streams[streamID]; ok { + + if stream.Status == false { + + timeOut++ + + time.Sleep(time.Duration(100) * time.Millisecond) + + if c, ok := BufferClients.Load(playlistID + stream.MD5); ok { + + var clients = c.(ClientConnection) + + if clients.Error != nil || timeOut > 200 { + killClientConnection(streamID, stream.PlaylistID, false) + return + } + + } + + continue + } + + var oldSegments []string + + for { // Loop 2: Temporäre Datein sind vorhanden, Daten können zum Client gesendet werden + // HTTP Clientverbindung überwachen + cn, ok := w.(http.CloseNotifier) + if ok { + + select { + + case <-cn.CloseNotify(): + killClientConnection(streamID, playlistID, false) + return + + default: + if c, ok := BufferClients.Load(playlistID + stream.MD5); ok { + + var clients = c.(ClientConnection) + if clients.Error != nil { + ShowError(clients.Error, 0) + killClientConnection(streamID, playlistID, false) + return + } + + } else { + + return + + } + + } + + } + + if _, err := os.Stat(stream.Folder); os.IsNotExist(err) { + killClientConnection(streamID, playlistID, false) + return + } + + var tmpFiles = getTmpFiles(&stream) + //fmt.Println("Buffer Loop:", stream.Connection) + + for _, f := range tmpFiles { + + if _, err := os.Stat(stream.Folder); os.IsNotExist(err) { + killClientConnection(streamID, playlistID, false) + return + } + + oldSegments = append(oldSegments, f) + + var fileName = stream.Folder + f + + file, err := os.Open(fileName) + defer file.Close() + + if err == nil { + + l, err := file.Stat() + if err == nil { + + debug = fmt.Sprintf("Buffer Status:Send to client (%s)", fileName) + showDebug(debug, 2) + + var buffer = make([]byte, int(l.Size())) + _, err = file.Read(buffer) + + if err == nil { + + file.Seek(0, 0) + + if streaming == false { + + contentType := http.DetectContentType(buffer) + _ = contentType + //w.Header().Set("Content-type", "video/mpeg") + w.Header().Set("Content-type", contentType) + w.Header().Set("Content-Length", "0") + w.Header().Set("Connection", "close") + + } + + /* + // HDHR Header + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Pragma", "no-cache") + w.Header().Set("transferMode.dlna.org", "Streaming") + */ + + _, err := w.Write(buffer) + + if err != nil { + file.Close() + killClientConnection(streamID, playlistID, false) + return + } + + file.Close() + streaming = true + + } + + file.Close() + + } + + var n = indexOfString(f, oldSegments) + + if n > 20 { + + var fileToRemove = stream.Folder + oldSegments[0] + os.RemoveAll(getPlatformFile(fileToRemove)) + oldSegments = append(oldSegments[:0], oldSegments[0+1:]...) + + } + + } + + file.Close() + + } + + if len(tmpFiles) == 0 { + time.Sleep(time.Duration(100) * time.Millisecond) + } + + } // Ende Loop 2 + + } else { + + // Stream nicht vorhanden + killClientConnection(streamID, stream.PlaylistID, false) + showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner)) + return + + } + + } // Ende Loop 1 + +} + +func getTmpFiles(stream *ThisStream) (tmpFiles []string) { + + var tmpFolder = stream.Folder + var fileIDs []float64 + + if _, err := os.Stat(tmpFolder); !os.IsNotExist(err) { + + files, err := ioutil.ReadDir(getPlatformPath(tmpFolder)) + if err != nil { + ShowError(err, 000) + return + } + + if len(files) > 1 { + + for _, file := range files { + + var fileID = strings.Replace(file.Name(), ".ts", "", -1) + var f, err = strconv.ParseFloat(fileID, 64) + + if err == nil { + fileIDs = append(fileIDs, f) + } + + } + + sort.Float64s(fileIDs) + fileIDs = fileIDs[:len(fileIDs)-1] + + for _, file := range fileIDs { + + var fileName = fmt.Sprintf("%d.ts", int64(file)) + + if indexOfString(fileName, stream.OldSegments) == -1 { + tmpFiles = append(tmpFiles, fileName) + stream.OldSegments = append(stream.OldSegments, fileName) + } + + } + + } + + } + + return +} + +func killClientConnection(streamID int, playlistID string, force bool) { + + if p, ok := BufferInformation.Load(playlistID); ok { + + var playlist = p.(Playlist) + + if force == true { + delete(playlist.Streams, streamID) + showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner)) + return + } + + if stream, ok := playlist.Streams[streamID]; ok { + + if c, ok := BufferClients.Load(playlistID + stream.MD5); ok { + + var clients = c.(ClientConnection) + clients.Connection = clients.Connection - 1 + BufferClients.Store(playlistID+stream.MD5, clients) + + showInfo("Streaming Status:Client has terminated the connection") + showInfo(fmt.Sprintf("Streaming Status:Channel: %s (Clients: %d)", stream.ChannelName, clients.Connection)) + + if clients.Connection <= 0 { + BufferClients.Delete(playlistID + stream.MD5) + delete(playlist.Streams, streamID) + } + + } + + BufferInformation.Store(playlistID, playlist) + + if len(playlist.Streams) > 0 { + showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner)) + } + + } + + } + +} + +func clientConnection(stream ThisStream) (status bool) { + + status = true + + if _, ok := BufferClients.Load(stream.PlaylistID + stream.MD5); !ok { + + var debug = fmt.Sprintf("Streaming Status:Remove temporary files (%s)", stream.Folder) + showDebug(debug, 1) + + status = false + + debug = fmt.Sprintf("Remove tmp folder:%s", stream.Folder) + showDebug(debug, 1) + + os.RemoveAll(stream.Folder) + + if p, ok := BufferInformation.Load(stream.PlaylistID); ok { + + showInfo(fmt.Sprintf("Streaming Status:Channel: %s - No client is using this channel anymore. Streaming Server connection has ended", stream.ChannelName)) + + var playlist = p.(Playlist) + + showInfo(fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner)) + + if len(playlist.Streams) <= 0 { + BufferInformation.Delete(stream.PlaylistID) + } + + } + + status = false + + } + + return +} + +func connectToStreamingServer(streamID int, playlist Playlist) { + + var timeOut = 0 + var debug string + var tmpSegment = 1 + var tmpFolder = playlist.Streams[streamID].Folder + var m3u8Segments []string + var bandwidth BandwidthCalculation + var networkBandwidth = Settings.M3U8AdaptiveBandwidthMBPS * 1e+6 + + var defaultSegment = func() { + + var segment Segment + + if len(playlist.Streams[streamID].Location) > 0 { + segment.URL = playlist.Streams[streamID].Location + } else { + segment.URL = playlist.Streams[streamID].URL + } + + segment.Duration = 0 + + var stream = playlist.Streams[streamID] + stream.Segment = []Segment{} + stream.Segment = append(stream.Segment, segment) + + stream.HLS = false + stream.Sequence = 0 + stream.Wait = 0 + stream.NetworkBandwidth = networkBandwidth + + playlist.Streams[streamID] = stream + + timeOut++ + + } + + var addErrorToStream = func(err error) { + + var stream = playlist.Streams[streamID] + + if c, ok := BufferClients.Load(stream.PlaylistID + stream.MD5); ok { + + var clients = c.(ClientConnection) + clients.Error = err + BufferClients.Store(stream.PlaylistID+stream.MD5, clients) + + } + + } + + os.RemoveAll(getPlatformPath(tmpFolder)) + + err := checkFolder(tmpFolder) + if err != nil { + ShowError(err, 0) + addErrorToStream(err) + return + } + + // M3U8 Segmente +InitBuffer: + defaultSegment() + + if len(m3u8Segments) > 30 { + m3u8Segments = m3u8Segments[15:] + } + if timeOut >= 10 { + return + } + + var stream ThisStream = playlist.Streams[streamID] + + if stream.Status == false { + + if strings.Index(stream.URL, ".m3u8") != -1 { + showInfo("Streaming Type:" + "[HLS / M3U8]") + } else { + showInfo("Streaming Type:" + "[TS]") + } + + showInfo("Streaming URL:" + stream.URL) + + } + + var s = 0 + + stream.TimeStart = time.Now() + bandwidth.Start = stream.TimeStart + bandwidth.Size = 0 + + for { + + if clientConnection(stream) == false { + return + } + + if len(stream.Segment) == 0 || len(stream.URL) == 0 { + goto InitBuffer + } + + var segment = stream.Segment[0] + + var currentURL = strings.Trim(segment.URL, "\r\n") + + if len(currentURL) == 0 { + goto InitBuffer + } + + debug = fmt.Sprintf("Connection to:%s", currentURL) + showDebug(debug, 2) + + // Sprung für Redirect (301 <---> 308) + Redirect: + + req, err := http.NewRequest("GET", currentURL, nil) + req.Header.Set("User-Agent", Settings.UserAgent) + req.Header.Set("Connection", "close") + //req.Header.Set("Range", "bytes=0-") + req.Header.Set("Accept", "*/*") + debugRequest(req) + + client := &http.Client{} + client.CheckRedirect = func(req *http.Request, via []*http.Request) error { + return errors.New("Redirect") + } + + resp, err := client.Do(req) + + if resp != nil && err != nil { + debugResponse(resp) + } + + if err != nil { + + if resp == nil { + + err = errors.New("No response from streaming server") + fmt.Println("Current URL:", currentURL) + ShowError(err, 0) + + addErrorToStream(err) + + killClientConnection(streamID, stream.PlaylistID, true) + clientConnection(stream) + + return + } + + // Redirect + if resp.StatusCode >= 301 && resp.StatusCode <= 308 { + + debug = fmt.Sprintf("Streaming Status:HTTP response status [%d] %s", resp.StatusCode, http.StatusText(resp.StatusCode)) + showDebug(debug, 2) + + currentURL = strings.Trim(resp.Header.Get("Location"), "\r\n") + + stream.Location = currentURL + + if len(currentURL) > 0 { + + debug = fmt.Sprintf("HTTP Redirect:%s", stream.Location) + showDebug(debug, 2) + defer resp.Body.Close() + goto Redirect + + } else { + + err = errors.New("Streaming server") + ShowError(err, 4002) + addErrorToStream(err) + + defer resp.Body.Close() + + return + + } + + } else { + + ShowError(err, 0) + addErrorToStream(err) + + defer resp.Body.Close() + + return + + } + + defer resp.Body.Close() + + } + + defer resp.Body.Close() + + // HTTP Status überprüfen, bei Fehlern wird der Stream beendet + var contentType = resp.Header.Get("Content-Type") + var httpStatusCode = resp.StatusCode + var httpStatusInfo = fmt.Sprintf("HTTP Response Status [%d] %s", httpStatusCode, http.StatusText(resp.StatusCode)) + + if resp.StatusCode != http.StatusOK { + + showInfo("Content type:" + contentType) + showInfo("Streaming Status:" + httpStatusInfo) + showInfo("Error with this URL:" + currentURL) + + var err = errors.New(http.StatusText(resp.StatusCode)) + ShowError(err, 4004) + + debug = fmt.Sprintf("Streaming Status:Playlist: %s - Tuner: %d / %d", playlist.PlaylistName, len(playlist.Streams), playlist.Tuner) + showDebug(debug, 1) + + BufferInformation.Store(playlist.PlaylistID, playlist) + addErrorToStream(err) + + killClientConnection(streamID, stream.PlaylistID, true) + clientConnection(stream) + resp.Body.Close() + + return + } + + // Informationen über den Streamingserver auslesen + if stream.Status == false { + + if len(stream.URLStreamingServer) == 0 { + + u, _ := url.Parse(currentURL) + p, _ := url.Parse(currentURL) + + stream.URLScheme = u.Scheme + stream.URLHost = req.Host + stream.URLPath = p.Path + stream.URLFile = path.Base(p.Path) + + stream.URLRedirect = fmt.Sprintf("%s://%s%s", stream.URLScheme, stream.URLHost, stream.URLPath) + stream.URLStreamingServer = fmt.Sprintf("%s://%s", stream.URLScheme, stream.URLHost) + + } + + debug = fmt.Sprintf("Server URL:%s", stream.URLStreamingServer) + showDebug(debug, 1) + + debug = fmt.Sprintf("Temp Folder:%s", tmpFolder) + showDebug(debug, 1) + + showInfo("Streaming Status:" + "HTTP Response Status [" + strconv.Itoa(resp.StatusCode) + "] " + http.StatusText(resp.StatusCode)) + showInfo("Content type:" + contentType) + + } else { + + debug = fmt.Sprintf("Content Type:%s", contentType) + showDebug(debug, 2) + + } + + // Content Type bereinigen + if len(contentType) > 0 { + var ct = strings.SplitN(contentType, ";", 2) + contentType = strings.ToLower(ct[0]) + } + + switch contentType { + + // M3U8 Playlist + case "application/x-mpegurl", "application/vnd.apple.mpegurl", "audio/mpegurl": + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + ShowError(err, 0) + addErrorToStream(err) + } + + stream.Body = string(body) + stream.HLS = true + stream.M3U8URL = currentURL + + err = parseM3U8(&stream) + if err != nil { + ShowError(err, 4050) + addErrorToStream(err) + } + + // Video Stream (TS) + case "video/mpeg", "video/mp4", "video/mp2t", "application/octet-stream": + + var fileSize int + + // Größe des Buffers + buffer := make([]byte, 1024*Settings.BufferSize*2) + var tmpFileSize = 1024 * Settings.BufferSize * 1 + + debug = fmt.Sprintf("Buffer Size:%d KB [SERVER CONNECTION]", len(buffer)/1024) + showDebug(debug, 3) + + debug = fmt.Sprintf("Buffer Size:%d KB [CLIENT CONNECTION]", tmpFileSize/1024) + showDebug(debug, 3) + + var tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment) + + if clientConnection(stream) == false { + resp.Body.Close() + return + } + + bufferFile, err := os.Create(tmpFile) + if err != nil { + + addErrorToStream(err) + bufferFile.Close() + resp.Body.Close() + return + + } + + for { + + if fileSize == 0 { + + debug = fmt.Sprintf("Buffer Status:Buffering (%s)", tmpFile) + showDebug(debug, 2) + + } + + timeOut = 0 + // Buffer mit Daten vom Server füllen + n, err := resp.Body.Read(buffer) + + if err != nil && err != io.EOF { + + ShowError(err, 0) + addErrorToStream(err) + resp.Body.Close() + return + + } + + defer resp.Body.Close() + + if _, err := bufferFile.Write(buffer[:n]); err != nil { + + ShowError(err, 0) + addErrorToStream(err) + resp.Body.Close() + return + + } + + defer bufferFile.Close() + + fileSize = fileSize + n + + if clientConnection(stream) == false { + + resp.Body.Close() + bufferFile.Close() + + err = os.RemoveAll(stream.Folder) + if err != nil { + ShowError(err, 4005) + } + return + + } + + // Buffer auf die Festplatte speichern + if fileSize >= tmpFileSize || n == 0 { + + bandwidth.Stop = time.Now() + bandwidth.Size += fileSize + + bandwidth.TimeDiff = bandwidth.Stop.Sub(bandwidth.Start).Seconds() + + networkBandwidth = int(float64(bandwidth.Size) / bandwidth.TimeDiff * 1000) + + stream.NetworkBandwidth = networkBandwidth + bandwidth.NetworkBandwidth = stream.NetworkBandwidth + + debug = fmt.Sprintf("Buffer Status:Done (%s)", tmpFile) + showDebug(debug, 2) + + bufferFile.Close() + + stream.Status = true + playlist.Streams[streamID] = stream + tmpSegment++ + + tmpFile = fmt.Sprintf("%s%d.ts", tmpFolder, tmpSegment) + + if clientConnection(stream) == false { + + bufferFile.Close() + resp.Body.Close() + + err = os.RemoveAll(stream.Folder) + if err != nil { + ShowError(err, 4005) + } + + return + } + + bufferFile, err = os.Create(tmpFile) + if err != nil { + addErrorToStream(err) + resp.Body.Close() + return + } + + fileSize = 0 + + if n == 0 { + bufferFile.Close() + resp.Body.Close() + break + } + + } + + } + + //-- + + // Umbekanntes Format + default: + showInfo("Content type:" + resp.Header.Get("Content-Type")) + err = errors.New("Streaming error") + ShowError(err, 4003) + + addErrorToStream(err) + resp.Body.Close() + return + } + + s++ + + // Wartezeit für den Download das nächste Segments berechnen + if stream.HLS == true { + + var sleep float64 + + if segment.Duration > 0 { + + stream.TimeEnd = time.Now() + stream.TimeDiff = stream.TimeEnd.Sub(stream.TimeStart).Seconds() + + sleep = (segment.Duration - stream.TimeDiff) - (segment.Duration * 0.25) + + if sleep < 0 { + sleep = 0 + } + + debug = fmt.Sprintf("HLS Status:Download time: %f s | Segment duration: %f s | Sleep: %f s Sequence: %d", stream.TimeDiff, segment.Duration, sleep, segment.Sequence) + showDebug(debug, 1) + + if sleep > 0 { + + for i := 0.0; i < sleep*1000; i = i + 100 { + + _ = i + time.Sleep(time.Duration(100) * time.Millisecond) + + if _, err := os.Stat(stream.Folder); os.IsNotExist(err) { + break + } + + } + + } + + } + + } + + stream.Segment = stream.Segment[1:len(stream.Segment)] + + resp.Body.Close() + + } // Ende for loop + +} + +func parseM3U8(stream *ThisStream) (err error) { + + var debug string + var noNewSegment = false + var lastSegmentDuration float64 + var segment Segment + var m3u8Segments []Segment + var sequence int64 + + stream.DynamicBandwidth = false + + debug = fmt.Sprintf(`M3U8 Playlist:`+"\n"+`%s`, stream.Body) + showDebug(debug, 3) + + var getBandwidth = func(line string) int { + + var infos = strings.Split(line, ",") + + for _, info := range infos { + + if strings.Contains(info, "BANDWIDTH=") { + + var bandwidth = strings.Replace(info, "BANDWIDTH=", "", -1) + n, err := strconv.Atoi(bandwidth) + if err == nil { + return n + } + + } + + } + + return 0 + } + + var parseParameter = func(line string, segment *Segment) (err error) { + + line = strings.Trim(line, "\r\n") + + var parameters = []string{"#EXT-X-VERSION:", "#EXT-X-MEDIA-SEQUENCE:", "#EXT-X-STREAM-INF:", "#EXTINF:"} + + for _, parameter := range parameters { + + if strings.Contains(line, parameter) { + + var value = strings.Replace(line, parameter, "", -1) + + switch parameter { + + case "#EXT-X-VERSION:": + version, err := strconv.Atoi(value) + if err == nil { + segment.Version = version + } + + case "#EXT-X-MEDIA-SEQUENCE:": + n, err := strconv.ParseInt(value, 10, 64) + if err == nil { + stream.Sequence = n + sequence = n + } + + case "#EXT-X-STREAM-INF:": + segment.Info = true + segment.StreamInf.Bandwidth = getBandwidth(value) + + case "#EXTINF:": + var d = strings.Split(value, ",") + if len(d) > 0 { + + value = strings.Replace(d[0], ",", "", -1) + duration, err := strconv.ParseFloat(value, 64) + if err == nil { + segment.Duration = duration + } else { + ShowError(err, 1050) + return err + } + + } + + } + + } + + } + + return + } + + var parseURL = func(line string, segment *Segment) { + + // Prüfen ob die Adresse eine gültige URL ist (http://... oder /path/to/stream) + _, err := url.ParseRequestURI(line) + if err == nil { + + // Prüfen ob die Domain in der Adresse enhalten ist + u, _ := url.Parse(line) + + if len(u.Host) == 0 { + // Adresse enthällt nicht die Domain, Redirect wird der Adresse hinzugefügt + segment.URL = stream.URLStreamingServer + line + } else { + // Domain in der Adresse enthalten + segment.URL = line + } + + } else { + + // keine URL, sondern ein Dateipfad (media/file-01.ts) + var serverURLPath = strings.Replace(stream.M3U8URL, path.Base(stream.M3U8URL), line, -1) + segment.URL = serverURLPath + + } + + return + } + + if strings.Contains(stream.Body, "#EXTM3U") { + + var lines = strings.Split(strings.Replace(stream.Body, "\r\n", "\n", -1), "\n") + + if stream.DynamicBandwidth == false { + stream.DynamicStream = make(map[int]DynamicStream) + } + + // Parameter parsen + for i, line := range lines { + + _ = i + + if len(line) > 0 { + + if line[0:1] == "#" { + + err := parseParameter(line, &segment) + if err != nil { + return err + } + + lastSegmentDuration = segment.Duration + + } + + // M3U8 enthällt mehrere Links zu weiteren M3U8 Wiedergabelisten (Bandbreitenoption) + if segment.Info == true && len(line) > 0 && line[0:1] != "#" { + + var dynamicStream DynamicStream + + segment.Duration = 0 + noNewSegment = false + + stream.DynamicBandwidth = true + parseURL(line, &segment) + + dynamicStream.Bandwidth = segment.StreamInf.Bandwidth + dynamicStream.URL = segment.URL + + stream.DynamicStream[dynamicStream.Bandwidth] = dynamicStream + + } + + // Segment mit TS Stream + if segment.Duration > 0 && line[0:1] != "#" { + + parseURL(line, &segment) + + if len(segment.URL) > 0 { + segment.Sequence = sequence + m3u8Segments = append(m3u8Segments, segment) + sequence++ + } + + } + + } + + } + + } else { + + err = errors.New(getErrMsg(4051)) + return + } + + if len(m3u8Segments) > 0 { + + noNewSegment = true + + if stream.Status == false { + + if len(m3u8Segments) >= 2 { + m3u8Segments = m3u8Segments[0 : len(m3u8Segments)-1] + } + + } + + for _, s := range m3u8Segments { + + segment = s + + if stream.Status == false { + + noNewSegment = false + stream.LastSequence = segment.Sequence + + } else { + + if segment.Sequence > stream.LastSequence { + + stream.LastSequence = segment.Sequence + noNewSegment = false + break + + } + + } + + } + + } + + if noNewSegment == false { + + if stream.DynamicBandwidth == true { + switchBandwidth(stream) + } else { + stream.Segment = append(stream.Segment, segment) + } + + } + + if noNewSegment == true { + + var sleep = lastSegmentDuration * 0.5 + + for i := 0.0; i < sleep*1000; i = i + 100 { + + _ = i + time.Sleep(time.Duration(100) * time.Millisecond) + + if _, err := os.Stat(stream.Folder); os.IsNotExist(err) { + break + } + + err := checkFile(stream.Folder + "remove") + if err == nil { + os.RemoveAll(stream.Folder) + break + } + + } + + } + + return +} + +func switchBandwidth(stream *ThisStream) (err error) { + + var bandwidth []int + var dynamicStream DynamicStream + var segment Segment + + for key := range stream.DynamicStream { + bandwidth = append(bandwidth, key) + } + + sort.Ints(bandwidth) + + if len(bandwidth) > 0 { + + for i := range bandwidth { + + segment.StreamInf.Bandwidth = stream.DynamicStream[bandwidth[i]].Bandwidth + + dynamicStream = stream.DynamicStream[bandwidth[0]] + + if stream.NetworkBandwidth == 0 { + + dynamicStream = stream.DynamicStream[bandwidth[0]] + break + + } else { + + if bandwidth[i] > stream.NetworkBandwidth { + break + } + + dynamicStream = stream.DynamicStream[bandwidth[i]] + + } + + } + + } else { + + err = errors.New("M3U8 does not contain streaming URLs") + return + + } + + segment.URL = dynamicStream.URL + segment.Duration = 0 + stream.Segment = append(stream.Segment, segment) + + return +} + +func getTuner(id, playlistType string) (tuner int) { + + switch Settings.Buffer { + + case false: + tuner = Settings.Tuner + + case true: + + i, err := strconv.Atoi(getProviderParameter(id, playlistType, "tuner")) + if err == nil { + tuner = i + } else { + ShowError(err, 0) + tuner = 1 + } + + } + + return +} + +func debugRequest(req *http.Request) { + + var debugLevel = 3 + + if System.Flag.Debug < debugLevel { + return + } + + var debug string + + fmt.Println() + debug = "Request:* * * * * * BEGIN HTTP(S) REQUEST * * * * * * " + showDebug(debug, debugLevel) + + debug = fmt.Sprintf("Method:%s", req.Method) + showDebug(debug, debugLevel) + + debug = fmt.Sprintf("Proto:%s", req.Proto) + showDebug(debug, debugLevel) + + debug = fmt.Sprintf("URL:%s", req.URL) + showDebug(debug, debugLevel) + + for name, headers := range req.Header { + + name = strings.ToLower(name) + + for _, h := range headers { + debug = fmt.Sprintf("Header:%v: %v", name, h) + showDebug(debug, debugLevel) + } + + } + + debug = "Request:* * * * * * END HTTP(S) REQUEST * * * * * *" + showDebug(debug, debugLevel) + + return +} + +func debugResponse(resp *http.Response) { + + var debugLevel = 3 + + if System.Flag.Debug < debugLevel { + return + } + + var debug string + + fmt.Println() + + debug = "Response:* * * * * * BEGIN RESPONSE * * * * * * " + showDebug(debug, debugLevel) + + debug = fmt.Sprintf("Proto:%s", resp.Proto) + showDebug(debug, debugLevel) + + debug = fmt.Sprintf("Status Code:%d", resp.StatusCode) + showDebug(debug, debugLevel) + + debug = fmt.Sprintf("Status Text:%s", http.StatusText(resp.StatusCode)) + showDebug(debug, debugLevel) + + for key, value := range resp.Header { + + switch fmt.Sprintf("%T", value) { + + case "[]string": + debug = fmt.Sprintf("Header:%v: %s", key, strings.Join(value, " ")) + + default: + debug = fmt.Sprintf("Header:%v: %v", key, value) + } + + showDebug(debug, debugLevel) + + } + + debug = "Pesponse:* * * * * * END RESPONSE * * * * * * " + showDebug(debug, debugLevel) + + return +} diff --git a/src/compression.go b/src/compression.go new file mode 100644 index 0000000..0429998 --- /dev/null +++ b/src/compression.go @@ -0,0 +1,148 @@ +package src + +import ( + "archive/zip" + "bytes" + "compress/gzip" + "io" + "os" + "path/filepath" + "strings" +) + +func zipFiles(sourceFiles []string, target string) error { + + zipfile, err := os.Create(target) + if err != nil { + return err + } + defer zipfile.Close() + + archive := zip.NewWriter(zipfile) + defer archive.Close() + + for _, source := range sourceFiles { + + info, err := os.Stat(source) + if err != nil { + return nil + } + + var baseDir string + if info.IsDir() { + baseDir = filepath.Base(System.Folder.Data) + } + + filepath.Walk(source, func(path string, info os.FileInfo, err error) error { + + if err != nil { + return err + } + + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + if baseDir != "" { + header.Name = filepath.Join(strings.TrimPrefix(path, System.Folder.Config)) + } + + if info.IsDir() { + header.Name += string(os.PathSeparator) + } else { + header.Method = zip.Deflate + } + + writer, err := archive.CreateHeader(header) + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(writer, file) + + return err + + }) + + } + + return err +} + +func extractZIP(archive, target string) (err error) { + + reader, err := zip.OpenReader(archive) + if err != nil { + return err + } + + if err := os.MkdirAll(target, 0755); err != nil { + return err + } + + for _, file := range reader.File { + + path := filepath.Join(target, file.Name) + if file.FileInfo().IsDir() { + os.MkdirAll(path, file.Mode()) + continue + } + + fileReader, err := file.Open() + if err != nil { + return err + } + defer fileReader.Close() + + targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) + if err != nil { + return err + } + defer targetFile.Close() + + if _, err := io.Copy(targetFile, fileReader); err != nil { + return err + } + + } + + return +} + +func extractGZIP(gzipBody []byte, fileSource string) (body []byte, err error) { + + var b = bytes.NewBuffer(gzipBody) + + var r io.Reader + r, err = gzip.NewReader(b) + if err != nil { + // Keine gzip Datei + body = gzipBody + err = nil + return + } + + showInfo("Extract gzip:" + fileSource) + + var resB bytes.Buffer + _, err = resB.ReadFrom(r) + if err != nil { + body = gzipBody + err = nil + return + } + + body = resB.Bytes() + return +} diff --git a/src/config.go b/src/config.go new file mode 100644 index 0000000..b357eca --- /dev/null +++ b/src/config.go @@ -0,0 +1,242 @@ +package src + +import ( + "fmt" + "os" + "runtime" + "strings" + "sync" +) + +// System : Beinhaltet alle Systeminformationen +var System SystemStruct + +// WebScreenLog : Logs werden im RAM gespeichert und für das Webinterface bereitgestellt +var WebScreenLog WebScreenLogStruct + +// Settings : Inhalt der settings.json +var Settings SettingsStrcut + +// Data : Alle Daten werden hier abgelegt. (Lineup, XMLTV) +var Data DataStruct + +// SystemFiles : Alle Systemdateien +var SystemFiles = []string{"authentication.json", "pms.json", "settings.json", "xepg.json", "urls.json"} + +// BufferInformation : Informationen über den Buffer (aktive Streams, maximale Streams) +var BufferInformation sync.Map + +// BufferClients : Anzahl der Clients die einen Stream über den Buffer abspielen +var BufferClients sync.Map + +// Init : Systeminitialisierung +func Init() (err error) { + + var debug string + + // System Einstellungen + System.AppName = strings.ToLower(System.Name) + System.ARCH = runtime.GOARCH + System.OS = runtime.GOOS + System.ServerProtocol.API = "http" + System.ServerProtocol.DVR = "http" + System.ServerProtocol.M3U = "http" + System.ServerProtocol.WEB = "http" + System.ServerProtocol.XML = "http" + System.DVRLimit = 480 + System.Compatibility = "1.4.4" + + // Default Logeinträge, wird später von denen aus der settings.json überschrieben. Muss gemacht werden, damit die ersten Einträge auch im Log (webUI aangezeigt werden) + Settings.LogEntriesRAM = 500 + + // Variablen für den Update Prozess + //System.Update.Git = "https://github.com/xteve-project/xTeVe-Downloads/blob" + System.Update.Git = fmt.Sprintf("https://github.com/%s/%s/blob", System.GitHub.User, System.GitHub.Repo) + System.Update.Name = "xteve_2" + + // Ordnerpfade festlegen + var tempFolder = os.TempDir() + string(os.PathSeparator) + System.AppName + string(os.PathSeparator) + tempFolder = getPlatformPath(strings.Replace(tempFolder, "//", "/", -1)) + + if len(System.Folder.Config) == 0 { + System.Folder.Config = GetUserHomeDirectory() + string(os.PathSeparator) + "." + System.AppName + string(os.PathSeparator) + } else { + System.Folder.Config = strings.TrimRight(System.Folder.Config, string(os.PathSeparator)) + string(os.PathSeparator) + } + + System.Folder.Config = getPlatformPath(System.Folder.Config) + + System.Folder.Backup = System.Folder.Config + "backup" + string(os.PathSeparator) + System.Folder.Data = System.Folder.Config + "data" + string(os.PathSeparator) + System.Folder.Cache = System.Folder.Config + "cache" + string(os.PathSeparator) + System.Folder.ImagesCache = System.Folder.Cache + "images" + string(os.PathSeparator) + System.Folder.ImagesUpload = System.Folder.Data + "images" + string(os.PathSeparator) + System.Folder.Temp = tempFolder + + // Dev Info + showDevInfo() + + // System Ordner erstellen + err = createSystemFolders() + if err != nil { + ShowError(err, 1070) + return + } + + System.File.XML = getPlatformFile(fmt.Sprintf("%s%s.xml", System.Folder.Data, System.AppName)) + System.File.M3U = getPlatformFile(fmt.Sprintf("%s%s.m3u", System.Folder.Data, System.AppName)) + + err = activatedSystemAuthentication() + if err != nil { + return + } + + err = resolveHostIP() + if err != nil { + ShowError(err, 1002) + } + + // Menü für das Webinterface + System.WEB.Menu = []string{"playlist", "filter", "xmltv", "mapping", "users", "settings", "log", "logout"} + + fmt.Println("For help run: " + getPlatformFile(os.Args[0]) + " -h") + fmt.Println() + + // Überprüfen ob xTeVe als root läuft + if os.Geteuid() == 0 { + showWarning(2010) + } + + if System.Flag.Debug > 0 { + debug = fmt.Sprintf("Debug Level:%d", System.Flag.Debug) + showDebug(debug, 1) + } + + showInfo(fmt.Sprintf("Version:%s Build: %s", System.Version, System.Build)) + showInfo(fmt.Sprintf("System IP Addresses:IPv4: %d | IPv6: %d", len(System.IPAddressesV4), len(System.IPAddressesV6))) + showInfo("Hostname:" + System.Hostname) + showInfo(fmt.Sprintf("System Folder:%s", getPlatformPath(System.Folder.Config))) + + // Systemdateien erstellen (Falls nicht vorhanden) + err = createSystemFiles() + if err != nil { + ShowError(err, 1071) + return + } + + // Bedingte Update Änderungen durchführen + err = conditionalUpdateChanges() + if err != nil { + ShowError(err, 0) + return + } + + // Einstellungen laden (settings.json) + showInfo(fmt.Sprintf("Load Settings:%s", System.File.Settings)) + + _, err = loadSettings() + if err != nil { + ShowError(err, 0) + return + } + + // Berechtigung aller Ordner überprüfen + err = checkFilePermission(System.Folder.Config) + if err == nil { + err = checkFilePermission(System.Folder.Temp) + } + + // Separaten tmp Ordner für jede Instanz + //System.Folder.Temp = System.Folder.Temp + Settings.UUID + string(os.PathSeparator) + showInfo(fmt.Sprintf("Temporary Folder:%s", getPlatformPath(System.Folder.Temp))) + + err = checkFolder(System.Folder.Temp) + if err != nil { + return + } + + err = removeChildItems(getPlatformPath(System.Folder.Temp)) + if err != nil { + return + } + + // DLNA Server starten + go SSDP() + + // Branch festlegen + System.Branch = Settings.Branch + + if System.Dev == true { + System.Branch = "Development" + } + + if len(System.Branch) == 0 { + System.Branch = "master" + } + + showInfo(fmt.Sprintf("GitHub:https://github.com/%s", System.GitHub.User)) + showInfo(fmt.Sprintf("Git Branch:%s [%s]", System.Branch, System.GitHub.User)) + + // Domainnamen setzten + setGlobalDomain(fmt.Sprintf("%s:%s", System.IPAddress, Settings.Port)) + + System.URLBase = fmt.Sprintf("%s://%s:%s", System.ServerProtocol.WEB, System.IPAddress, Settings.Port) + + // HTML Dateien erstellen, mit dev == true werden die lokalen HTML Dateien verwendet + if System.Dev == true { + + HTMLInit("webUI", "src", "html"+string(os.PathSeparator), "src"+string(os.PathSeparator)+"webUI.go") + err = BuildGoFile() + if err != nil { + return + } + + } + + loadHTMLMap() + + return +} + +// StartSystem : System wird gestartet +func StartSystem(updateProviderFiles bool) (err error) { + + setDeviceID() + + if System.ScanInProgress == 1 { + return + } + + // Systeminformationen in der Konsole ausgeben + showInfo(fmt.Sprintf("UUID:%s", Settings.UUID)) + showInfo(fmt.Sprintf("Tuner (Plex / Emby):%d", Settings.Tuner)) + showInfo(fmt.Sprintf("EPG Source:%s", Settings.EpgSource)) + showInfo(fmt.Sprintf("Plex Channel Limit:%d", System.DVRLimit)) + + // Providerdaten aktualisieren + if len(Settings.Files.M3U) > 0 && Settings.FilesUpdate == true || updateProviderFiles == true { + + err = xTeVeAutoBackup() + if err != nil { + ShowError(err, 1090) + } + + getProviderData("m3u", "") + getProviderData("hdhr", "") + + if Settings.EpgSource == "XEPG" { + getProviderData("xmltv", "") + } + + } + + err = buildDatabaseDVR() + if err != nil { + ShowError(err, 0) + return + } + + buildXEPG(false) + + return +} diff --git a/src/data.go b/src/data.go new file mode 100644 index 0000000..c41ddbf --- /dev/null +++ b/src/data.go @@ -0,0 +1,953 @@ +package src + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path" + "sort" + "strconv" + "strings" + "time" + + "../src/internal/authentication" +) + +// Einstellungen ändern (WebUI) +func updateServerSettings(request RequestStruct) (settings SettingsStrcut, err error) { + + var oldSettings = jsonToMap(mapToJSON(Settings)) + var newSettings = jsonToMap(mapToJSON(request.Settings)) + var reloadData = false + var cacheImages = false + var createXEPGFiles = false + var debug string + + for key, value := range newSettings { + + if _, ok := oldSettings[key]; ok { + + switch key { + + case "tuner": + showWarning(2105) + + case "epgSource": + reloadData = true + + case "update": + // Die Formatierung der Uhrzeit überprüfen (0000 - 2359) + for _, i := range newSettings[key].([]interface{}) { + + _, err := time.Parse("1504", i.(string)) + if err != nil { + ShowError(err, 1012) + return Settings, err + } + + } + + case "cache.images": + cacheImages = true + + case "xepg.replace.missing.images": + createXEPGFiles = true + + case "backup.path": + value = strings.TrimRight(value.(string), string(os.PathSeparator)) + string(os.PathSeparator) + err = checkFolder(value.(string)) + if err == nil { + + err = checkFilePermission(value.(string)) + if err != nil { + return + } + + } + + if err != nil { + return + } + + case "temp.path": + value = strings.TrimRight(value.(string), string(os.PathSeparator)) + string(os.PathSeparator) + err = checkFolder(value.(string)) + if err == nil { + + err = checkFilePermission(value.(string)) + if err != nil { + return + } + + } + + if err != nil { + return + } + + } + + oldSettings[key] = value + + switch fmt.Sprintf("%T", value) { + + case "bool": + debug = fmt.Sprintf("Save Setting:Key: %s | Value: %t (%T)", key, value, value) + + case "string": + debug = fmt.Sprintf("Save Setting:Key: %s | Value: %s (%T)", key, value, value) + + case "[]interface {}": + debug = fmt.Sprintf("Save Setting:Key: %s | Value: %v (%T)", key, value, value) + + case "float64": + debug = fmt.Sprintf("Save Setting:Key: %s | Value: %d (%T)", key, int(value.(float64)), value) + + default: + debug = fmt.Sprintf("%T", value) + } + + showDebug(debug, 1) + + } + + } + + // Einstellungen aktualisieren + err = json.Unmarshal([]byte(mapToJSON(oldSettings)), &Settings) + if err != nil { + return + } + + if Settings.AuthenticationWEB == false { + + Settings.AuthenticationAPI = false + Settings.AuthenticationM3U = false + Settings.AuthenticationPMS = false + Settings.AuthenticationWEB = false + Settings.AuthenticationXML = false + + } + + err = saveSettings(Settings) + if err == nil { + + settings = Settings + + if reloadData == true { + + err = buildDatabaseDVR() + if err != nil { + return + } + + buildXEPG(false) + + } + + if cacheImages == true { + + if Settings.EpgSource == "XEPG" { + + go func() { + + if Settings.CacheImages == true { + + createXMLTVFile() + cachingImages() + createXMLTVFile() + createM3UFile() + + } else { + + createXMLTVFile() + createM3UFile() + + } + + }() + + } + + } + + if createXEPGFiles == true { + + go func() { + createXMLTVFile() + createM3UFile() + }() + + } + + } + + return +} + +// Providerdaten speichern (WebUI) +func saveFiles(request RequestStruct, fileType string) (err error) { + + var filesMap = make(map[string]interface{}) + var newData = make(map[string]interface{}) + var indicator string + var reloadData = false + + switch fileType { + case "m3u": + filesMap = Settings.Files.M3U + newData = request.Files.M3U + indicator = "M" + + case "hdhr": + filesMap = Settings.Files.HDHR + newData = request.Files.HDHR + indicator = "H" + + case "xmltv": + filesMap = Settings.Files.XMLTV + newData = request.Files.XMLTV + indicator = "X" + } + + if len(filesMap) == 0 { + filesMap = make(map[string]interface{}) + } + + for dataID, data := range newData { + + if dataID == "-" { + + // Neue Providerdatei + dataID = indicator + randomString(19) + data.(map[string]interface{})["new"] = true + filesMap[dataID] = data + + } else { + + // Bereits vorhandene Providerdatei + for key, value := range data.(map[string]interface{}) { + + var oldData = filesMap[dataID].(map[string]interface{}) + oldData[key] = value + + } + + } + + switch fileType { + + case "m3u": + Settings.Files.M3U = filesMap + + case "hdhr": + Settings.Files.HDHR = filesMap + + case "xmltv": + Settings.Files.XMLTV = filesMap + + } + + // Neue Providerdatei + if _, ok := data.(map[string]interface{})["new"]; ok { + + reloadData = true + err = getProviderData(fileType, dataID) + delete(data.(map[string]interface{}), "new") + + if err != nil { + delete(filesMap, dataID) + return + } + + } + + if _, ok := data.(map[string]interface{})["delete"]; ok { + + deleteLocalProviderFiles(dataID, fileType) + reloadData = true + + } + + err = saveSettings(Settings) + if err != nil { + return + } + + if reloadData == true { + + err = buildDatabaseDVR() + if err != nil { + return err + } + + buildXEPG(false) + + } + + Settings, _ = loadSettings() + + } + + return +} + +// Providerdaten manuell aktualisieren (WebUI) +func updateFile(request RequestStruct, fileType string) (err error) { + + var updateData = make(map[string]interface{}) + + switch fileType { + + case "m3u": + updateData = request.Files.M3U + + case "hdhr": + updateData = request.Files.HDHR + + case "xmltv": + updateData = request.Files.XMLTV + } + + for dataID := range updateData { + + err = getProviderData(fileType, dataID) + if err == nil { + err = buildDatabaseDVR() + buildXEPG(false) + } + + } + + return +} + +// Providerdaten löschen (WebUI) +func deleteLocalProviderFiles(dataID, fileType string) { + + var removeData = make(map[string]interface{}) + var fileExtension string + + switch fileType { + + case "m3u": + removeData = Settings.Files.M3U + fileExtension = ".m3u" + + case "hdhr": + removeData = Settings.Files.HDHR + fileExtension = ".json" + + case "xmltv": + removeData = Settings.Files.XMLTV + fileExtension = ".xml" + } + + if _, ok := removeData[dataID]; ok { + delete(removeData, dataID) + os.RemoveAll(System.Folder.Data + dataID + fileExtension) + } + + return +} + +// Filtereinstellungen speichern (WebUI) +func saveFilter(request RequestStruct) (settings SettingsStrcut, err error) { + + var filterMap = make(map[int64]interface{}) + var newData = make(map[int64]interface{}) + var defaultFilter FilterStruct + + defaultFilter.Active = true + defaultFilter.CaseSensitive = false + + filterMap = Settings.Filter + newData = request.Filter + + var createNewID = func() (id int64) { + + newID: + if _, ok := filterMap[id]; ok { + id++ + goto newID + } + + return id + } + + for dataID, data := range newData { + + if dataID == -1 { + + // Neuer Filter + dataID = createNewID() + filterMap[dataID] = jsonToMap(mapToJSON(defaultFilter)) + + } + + // Filter aktualisieren / löschen + for key, value := range data.(map[string]interface{}) { + + var oldData = filterMap[dataID].(map[string]interface{}) + oldData[key] = value + + // Filter löschen + if _, ok := data.(map[string]interface{})["delete"]; ok { + + delete(filterMap, dataID) + break + + } + + } + + } + + err = saveSettings(Settings) + if err != nil { + return + } + + settings = Settings + + err = buildDatabaseDVR() + if err != nil { + return + } + + buildXEPG(false) + + return +} + +// XEPG Mapping speichern +func saveXEpgMapping(request RequestStruct) (err error) { + + var tmp = Data.XEPG + + err = json.Unmarshal([]byte(mapToJSON(request.EpgMapping)), &tmp) + if err != nil { + return + } + + err = saveMapToJSONFile(System.File.XEPG, request.EpgMapping) + if err != nil { + return err + } + + Data.XEPG.Channels = request.EpgMapping + + cleanupXEPG() + buildXEPG(true) + return +} + +// Benutzerdaten speichern (WebUI) +func saveUserData(request RequestStruct) (err error) { + + var userData = request.UserData + + var newCredentials = func(userID string, newUserData map[string]interface{}) (err error) { + + var newUsername, newPassword string + if username, ok := newUserData["username"].(string); ok { + newUsername = username + } + + if password, ok := newUserData["password"].(string); ok { + newPassword = password + } + + if len(newUsername) > 0 { + err = authentication.ChangeCredentials(userID, newUsername, newPassword) + } + + return + } + + for userID, newUserData := range userData { + + err = newCredentials(userID, newUserData.(map[string]interface{})) + if err != nil { + return + } + + if request.DeleteUser == true { + err = authentication.RemoveUser(userID) + return + } + + delete(newUserData.(map[string]interface{}), "password") + delete(newUserData.(map[string]interface{}), "confirm") + + if _, ok := newUserData.(map[string]interface{})["delete"]; ok { + + authentication.RemoveUser(userID) + + } else { + + err = authentication.WriteUserData(userID, newUserData.(map[string]interface{})) + if err != nil { + return + } + + } + + } + + return +} + +// Neuen Benutzer anlegen (WebUI) +func saveNewUser(request RequestStruct) (err error) { + + var data = request.UserData + var username = data["username"].(string) + var password = data["password"].(string) + + delete(data, "password") + delete(data, "confirm") + + userID, err := authentication.CreateNewUser(username, password) + if err != nil { + return + } + + err = authentication.WriteUserData(userID, data) + return +} + +// Wizard (WebUI) +func saveWizard(request RequestStruct) (nextStep int, err error) { + + var wizard = jsonToMap(mapToJSON(request.Wizard)) + + for key, value := range wizard { + + switch key { + + case "tuner": + Settings.Tuner = int(value.(float64)) + nextStep = 1 + + case "epgSource": + Settings.EpgSource = value.(string) + nextStep = 2 + + case "m3u", "xmltv": + + var filesMap = make(map[string]interface{}) + var data = make(map[string]interface{}) + var indicator, dataID string + + filesMap = make(map[string]interface{}) + + data["type"] = key + data["new"] = true + + switch key { + + case "m3u": + filesMap = Settings.Files.M3U + data["name"] = "M3U" + indicator = "M" + + case "xmltv": + filesMap = Settings.Files.XMLTV + data["name"] = "XMLTV" + indicator = "X" + + } + + dataID = indicator + randomString(19) + data["file.source"] = value.(string) + + filesMap[dataID] = data + + switch key { + case "m3u": + Settings.Files.M3U = filesMap + nextStep = 3 + + err = getProviderData(key, dataID) + + if err != nil { + ShowError(err, 000) + delete(filesMap, dataID) + return + } + + err = buildDatabaseDVR() + if err != nil { + ShowError(err, 000) + delete(filesMap, dataID) + return + } + + if Settings.EpgSource == "PMS" { + nextStep = 10 + } + + case "xmltv": + Settings.Files.XMLTV = filesMap + nextStep = 10 + + err = getProviderData(key, dataID) + + if err != nil { + + ShowError(err, 000) + delete(filesMap, dataID) + return + + } + + buildXEPG(false) + + } + + } + + } + + err = saveSettings(Settings) + if err != nil { + return + } + + return +} + +// Filterregeln erstellen +func createFilterRules() (err error) { + + Data.Filter = nil + var dataFilter Filter + + for _, f := range Settings.Filter { + + var filter FilterStruct + + var exclude, include string + + err = json.Unmarshal([]byte(mapToJSON(f)), &filter) + if err != nil { + return + } + + switch filter.Type { + + case "custom-filter": + dataFilter.CaseSensitive = filter.CaseSensitive + dataFilter.Rule = filter.Filter + dataFilter.Type = filter.Type + + Data.Filter = append(Data.Filter, dataFilter) + + case "group-title": + if len(filter.Include) > 0 { + include = fmt.Sprintf(" {%s}", filter.Include) + } + + if len(filter.Exclude) > 0 { + exclude = fmt.Sprintf(" !{%s}", filter.Exclude) + } + + dataFilter.CaseSensitive = filter.CaseSensitive + dataFilter.Rule = fmt.Sprintf("%s%s%s", filter.Filter, include, exclude) + dataFilter.Type = filter.Type + + Data.Filter = append(Data.Filter, dataFilter) + } + + } + + return +} + +// Datenbank für das DVR System erstellen +func buildDatabaseDVR() (err error) { + + System.ScanInProgress = 1 + + Data.Streams.All = make([]interface{}, 0) + Data.Streams.Active = make([]interface{}, 0) + Data.Streams.Inactive = make([]interface{}, 0) + Data.Playlist.M3U.Groups.Text = []string{} + Data.Playlist.M3U.Groups.Value = []string{} + Data.StreamPreviewUI.Active = []string{} + Data.StreamPreviewUI.Inactive = []string{} + + var availableFileTypes = []string{"m3u", "hdhr"} + + var tmpGroupsM3U = make(map[string]int64) + + err = createFilterRules() + if err != nil { + return + } + + for _, fileType := range availableFileTypes { + + var playlistFile = getLocalProviderFiles(fileType) + + for n, i := range playlistFile { + + var channels []interface{} + var groupTitle, tvgID, uuid int = 0, 0, 0 + var keys = []string{"group-title", "tvg-id", "uuid"} + var compatibility = make(map[string]int) + + var id = strings.TrimSuffix(getFilenameFromPath(i), path.Ext(getFilenameFromPath(i))) + var playlistName = getProviderParameter(id, fileType, "name") + + switch fileType { + + case "m3u": + channels, err = parsePlaylist(i, fileType) + case "hdhr": + channels, err = parsePlaylist(i, fileType) + + } + + if err != nil { + ShowError(err, 1005) + err = errors.New(playlistName + ": Local copy of the file no longer exists") + ShowError(err, 0) + playlistFile = append(playlistFile[:n], playlistFile[n+1:]...) + } + + // Streams analysieren + for _, stream := range channels { + + var s = stream.(map[string]string) + s["_file.m3u.path"] = i + s["_file.m3u.name"] = playlistName + s["_file.m3u.id"] = id + + // Kompatibilität berechnen + for _, key := range keys { + + switch key { + case "uuid": + if value, ok := s["_uuid.key"]; ok { + if len(value) > 0 { + uuid++ + } + } + + case "group-title": + if value, ok := s[key]; ok { + if len(value) > 0 { + + if _, ok := tmpGroupsM3U[value]; ok { + tmpGroupsM3U[value]++ + } else { + tmpGroupsM3U[value] = 1 + } + + groupTitle++ + } + } + + case "tvg-id": + if value, ok := s[key]; ok { + if len(value) > 0 { + tvgID++ + } + } + + } + + } + + Data.Streams.All = append(Data.Streams.All, stream) + + // Neuer Filter ab Version 1.3.0 + var preview string + var status = filterThisStream(stream) + + if name, ok := s["name"]; ok { + var group string + + if v, ok := s["group-title"]; ok { + group = v + } + + preview = fmt.Sprintf("%s [%s]", name, group) + + } + + switch status { + + case true: + Data.StreamPreviewUI.Active = append(Data.StreamPreviewUI.Active, preview) + Data.Streams.Active = append(Data.Streams.Active, stream) + + case false: + Data.StreamPreviewUI.Inactive = append(Data.StreamPreviewUI.Inactive, preview) + Data.Streams.Inactive = append(Data.Streams.Inactive, stream) + + } + + } + + if tvgID == 0 { + compatibility["tvg.id"] = 0 + } else { + compatibility["tvg.id"] = int(tvgID * 100 / len(channels)) + } + + if groupTitle == 0 { + compatibility["group.title"] = 0 + } else { + compatibility["group.title"] = int(groupTitle * 100 / len(channels)) + } + + if uuid == 0 { + compatibility["stream.id"] = 0 + } else { + compatibility["stream.id"] = int(uuid * 100 / len(channels)) + } + + compatibility["streams"] = len(channels) + + setProviderCompatibility(id, fileType, compatibility) + + } + + } + + for group, count := range tmpGroupsM3U { + var text = fmt.Sprintf("%s (%d)", group, count) + var value = fmt.Sprintf("%s", group) + Data.Playlist.M3U.Groups.Text = append(Data.Playlist.M3U.Groups.Text, text) + Data.Playlist.M3U.Groups.Value = append(Data.Playlist.M3U.Groups.Value, value) + } + + sort.Strings(Data.Playlist.M3U.Groups.Text) + sort.Strings(Data.Playlist.M3U.Groups.Value) + + if len(Data.Streams.Active) == 0 && len(Data.Streams.All) <= System.DVRLimit && len(Settings.Filter) == 0 { + Data.Streams.Active = Data.Streams.All + Data.Streams.Inactive = make([]interface{}, 0) + + Data.StreamPreviewUI.Active = Data.StreamPreviewUI.Inactive + Data.StreamPreviewUI.Inactive = []string{} + + } + + if len(Data.Streams.Active) > System.DVRLimit { + showWarning(2000) + } + + if len(Settings.Filter) == 0 && len(Data.Streams.All) > System.DVRLimit { + showWarning(2001) + } + + System.ScanInProgress = 0 + showInfo(fmt.Sprintf("All streams:%d", len(Data.Streams.All))) + showInfo(fmt.Sprintf("Active streams:%d", len(Data.Streams.Active))) + showInfo(fmt.Sprintf("Filter:%d", len(Data.Filter))) + + sort.Strings(Data.StreamPreviewUI.Active) + sort.Strings(Data.StreamPreviewUI.Inactive) + + return +} + +// Speicherort aller lokalen Providerdateien laden, immer für eine Dateityp (M3U, XMLTV usw.) +func getLocalProviderFiles(fileType string) (localFiles []string) { + + var fileExtension string + var dataMap = make(map[string]interface{}) + + switch fileType { + + case "m3u": + fileExtension = ".m3u" + dataMap = Settings.Files.M3U + + case "hdhr": + fileExtension = ".json" + dataMap = Settings.Files.HDHR + + case "xmltv": + fileExtension = ".xml" + dataMap = Settings.Files.XMLTV + + } + + for dataID := range dataMap { + localFiles = append(localFiles, System.Folder.Data+dataID+fileExtension) + } + + return +} + +// Providerparameter anhand von dem Key ausgeben +func getProviderParameter(id, fileType, key string) (s string) { + + var dataMap = make(map[string]interface{}) + + switch fileType { + case "m3u": + dataMap = Settings.Files.M3U + + case "hdhr": + dataMap = Settings.Files.HDHR + + case "xmltv": + dataMap = Settings.Files.XMLTV + } + + if data, ok := dataMap[id].(map[string]interface{}); ok { + + if v, ok := data[key].(string); ok { + s = v + } + + if v, ok := data[key].(float64); ok { + s = strconv.Itoa(int(v)) + } + + } + + return +} + +// Provider Statistiken Kompatibilität aktualisieren +func setProviderCompatibility(id, fileType string, compatibility map[string]int) { + + var dataMap = make(map[string]interface{}) + + switch fileType { + case "m3u": + dataMap = Settings.Files.M3U + + case "hdhr": + dataMap = Settings.Files.HDHR + + case "xmltv": + dataMap = Settings.Files.XMLTV + } + + if data, ok := dataMap[id].(map[string]interface{}); ok { + + data["compatibility"] = compatibility + + switch fileType { + case "m3u": + Settings.Files.M3U = dataMap + case "hdhr": + Settings.Files.HDHR = dataMap + case "xmltv": + Settings.Files.XMLTV = dataMap + } + + saveSettings(Settings) + + } + +} diff --git a/src/hdhr.go b/src/hdhr.go new file mode 100644 index 0000000..b39e516 --- /dev/null +++ b/src/hdhr.go @@ -0,0 +1,236 @@ +package src + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "fmt" +) + +func makeInteraceFromHDHR(content []byte, playlistName, id string) (channels []interface{}, err error) { + + var hdhrData []interface{} + + err = json.Unmarshal(content, &hdhrData) + if err == nil { + + for _, d := range hdhrData { + + var channel = make(map[string]string) + var data = d.(map[string]interface{}) + + channel["group-title"] = playlistName + channel["name"] = data["GuideName"].(string) + channel["tvg-id"] = data["GuideName"].(string) + channel["url"] = data["URL"].(string) + channel["ID-"+id] = data["GuideNumber"].(string) + channel["_uuid.key"] = "ID-" + id + channel["_values"] = playlistName + " " + channel["name"] + + channels = append(channels, channel) + + } + + } + + return +} + +func getCapability() (xmlContent []byte, err error) { + + var capability Capability + var buffer bytes.Buffer + + capability.Xmlns = "urn:schemas-upnp-org:device-1-0" + capability.URLBase = System.ServerProtocol.WEB + "://" + System.Domain + + capability.SpecVersion.Major = 1 + capability.SpecVersion.Minor = 0 + + capability.Device.DeviceType = "urn:schemas-upnp-org:device:MediaServer:1" + capability.Device.FriendlyName = System.Name + capability.Device.Manufacturer = "Silicondust" + capability.Device.ModelName = "HDTC-2US" + capability.Device.ModelNumber = "HDTC-2US" + capability.Device.SerialNumber = "" + capability.Device.UDN = "uuid:" + System.DeviceID + + output, err := xml.MarshalIndent(capability, " ", " ") + if err != nil { + ShowError(err, 1003) + } + + buffer.Write([]byte(xml.Header)) + buffer.Write([]byte(output)) + xmlContent = buffer.Bytes() + + return +} + +func getDiscover() (jsonContent []byte, err error) { + + var discover Discover + + discover.BaseURL = System.ServerProtocol.WEB + "://" + System.Domain + discover.DeviceAuth = System.AppName + discover.DeviceID = System.DeviceID + discover.FirmwareName = "bin_" + System.Version + discover.FirmwareVersion = System.Version + discover.FriendlyName = System.Name + + discover.LineupURL = fmt.Sprintf("%s://%s/lineup.json", System.ServerProtocol.DVR, System.Domain) + discover.Manufacturer = "Golang" + discover.ModelNumber = System.Version + discover.TunerCount = Settings.Tuner + + jsonContent, err = json.MarshalIndent(discover, "", " ") + + return +} + +func getLineupStatus() (jsonContent []byte, err error) { + + var lineupStatus LineupStatus + + lineupStatus.ScanInProgress = System.ScanInProgress + lineupStatus.ScanPossible = 0 + lineupStatus.Source = "Cable" + lineupStatus.SourceList = []string{"IPTV", "Cable"} + + jsonContent, err = json.MarshalIndent(lineupStatus, "", " ") + + return +} + +func getLineup() (jsonContent []byte, err error) { + + var lineup Lineup + + switch Settings.EpgSource { + + case "PMS": + for i, dsa := range Data.Streams.Active { + + var m3uChannel M3UChannelStructXEPG + + err = json.Unmarshal([]byte(mapToJSON(dsa)), &m3uChannel) + if err != nil { + return + } + + var stream LineupStream + stream.GuideName = m3uChannel.Name + switch len(m3uChannel.UUIDValue) { + + case 0: + stream.GuideNumber = fmt.Sprintf("%d", i+1000) + guideNumber, err := getGuideNumberPMS(stream.GuideName) + if err != nil { + ShowError(err, 0) + } + + stream.GuideNumber = guideNumber + + default: + stream.GuideNumber = m3uChannel.UUIDValue + + } + + stream.URL, err = createStreamingURL("DVR", m3uChannel.FileM3UID, stream.GuideNumber, m3uChannel.Name, m3uChannel.URL) + if err == nil { + lineup = append(lineup, stream) + } else { + ShowError(err, 1202) + } + + } + + case "XEPG": + for _, dxc := range Data.XEPG.Channels { + + var xepgChannel XEPGChannelStruct + err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel) + if err != nil { + return + } + + if xepgChannel.XActive == true { + var stream LineupStream + stream.GuideName = xepgChannel.XName + stream.GuideNumber = xepgChannel.XChannelID + //stream.URL = fmt.Sprintf("%s://%s/stream/%s-%s", System.ServerProtocol.DVR, System.Domain, xepgChannel.FileM3UID, base64.StdEncoding.EncodeToString([]byte(xepgChannel.URL))) + stream.URL, err = createStreamingURL("DVR", xepgChannel.FileM3UID, xepgChannel.XChannelID, xepgChannel.XName, xepgChannel.URL) + if err == nil { + lineup = append(lineup, stream) + } else { + ShowError(err, 1202) + } + + } + + } + + } + + jsonContent, err = json.MarshalIndent(lineup, "", " ") + + Data.Cache.PMS = nil + + saveMapToJSONFile(System.File.URLS, Data.Cache.StreamingURLS) + + return +} + +func getGuideNumberPMS(channelName string) (pmsID string, err error) { + + if len(Data.Cache.PMS) == 0 { + + Data.Cache.PMS = make(map[string]string) + + pms, err := loadJSONFileToMap(System.File.PMS) + + if err != nil { + return "", err + } + + for key, value := range pms { + Data.Cache.PMS[key] = value.(string) + } + + } + + var getNewID = func(channelName string) (id string) { + + var i int + + newID: + + var ids []string + id = fmt.Sprintf("id-%d", i) + + for _, v := range Data.Cache.PMS { + ids = append(ids, v) + } + + if indexOfString(id, ids) != -1 { + i++ + goto newID + } + + return + } + + if value, ok := Data.Cache.PMS[channelName]; ok { + + pmsID = value + + } else { + + pmsID = getNewID(channelName) + Data.Cache.PMS[channelName] = pmsID + saveMapToJSONFile(System.File.PMS, Data.Cache.PMS) + + } + + return +} diff --git a/src/html-build.go b/src/html-build.go new file mode 100644 index 0000000..2b7ba10 --- /dev/null +++ b/src/html-build.go @@ -0,0 +1,147 @@ +package src + +import ( + "bufio" + "encoding/base64" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "runtime" +) + +var htmlFolder string +var goFile string +var mapName string +var packageName string + +var blankMap = make(map[string]interface{}) + +// HTMLInit : Dateipfade festlegen +// mapName = Name der zu erstellenden map +// htmlFolder: Ordner der HTML Dateien +// packageName: Name des package +func HTMLInit(name, pkg, folder, file string) { + + htmlFolder = folder + goFile = file + mapName = name + packageName = pkg + +} + +// BuildGoFile : Erstellt das GO Dokument +func BuildGoFile() error { + + var err = checkHTMLFile(htmlFolder) + + if err != nil { + return err + } + + var content string + content += `package ` + packageName + "\n\n" + content += `var ` + mapName + ` = make(map[string]interface{})` + "\n\n" + content += "func loadHTMLMap() {" + "\n\n" + + content += createMapFromFiles(htmlFolder) + "\n" + + content += "}" + "\n\n" + writeStringToFile(goFile, content) + + return nil +} + +// GetHTMLString : base64 -> string +func GetHTMLString(base string) string { + content, _ := base64.StdEncoding.DecodeString(base) + return string(content) +} + +func createMapFromFiles(folder string) string { + + var path = getLocalPath(folder) + + err := filepath.Walk(path, readFilesToMap) + if err != nil { + checkErr(err) + } + + var content string + + for key := range blankMap { + var newKey = key + content += ` ` + mapName + `["` + newKey + `"` + `] = "` + blankMap[key].(string) + `"` + "\n" + } + + return content +} + +func readFilesToMap(path string, info os.FileInfo, err error) error { + + if info.IsDir() == false { + var base64Str = fileToBase64(getLocalPath(path)) + blankMap[path] = base64Str + } + + return nil +} + +func fileToBase64(file string) string { + + imgFile, _ := os.Open(file) + defer imgFile.Close() + + // create a new buffer base on file size + fInfo, _ := imgFile.Stat() + var size = fInfo.Size() + buf := make([]byte, int64(size)) + + // read file content into buffer + fReader := bufio.NewReader(imgFile) + fReader.Read(buf) + + imgBase64Str := base64.StdEncoding.EncodeToString(buf) + + return imgBase64Str +} + +func getLocalPath(filename string) string { + + path, file := filepath.Split(filename) + var newPath = filepath.Dir(path) + + var newFileName = newPath + "/" + file + + return newFileName +} + +func writeStringToFile(filename, content string) error { + + err := ioutil.WriteFile(getPlatformFile(filename), []byte(content), 0644) + if err != nil { + checkErr(err) + return err + } + + return nil +} + +func checkHTMLFile(filename string) error { + + if _, err := os.Stat(getLocalPath(filename)); os.IsNotExist(err) { + fmt.Println(filename) + checkErr(err) + return err + } + + return nil +} + +func checkErr(err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + log.Println("ERROR: [", err, "] in ", file, line) + } +} diff --git a/src/images.go b/src/images.go new file mode 100644 index 0000000..3265775 --- /dev/null +++ b/src/images.go @@ -0,0 +1,153 @@ +package src + +import ( + b64 "encoding/base64" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" +) + +func getCacheImageURL(url string) (cacheImageURL string) { + + url = strings.Trim(url, "\r\n") + + var urlMD5 = getMD5(url) + var fileExtension = filepath.Ext(url) + + if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesFiles) == -1 { + Data.Cache.ImagesFiles = append(Data.Cache.ImagesFiles, urlMD5+fileExtension) + } + + if Settings.CacheImages == false || System.ImageCachingInProgress == 1 { + return url + } + + if indexOfString(urlMD5+fileExtension, Data.Cache.ImagesCache) != -1 { + + cacheImageURL = fmt.Sprintf("%s://%s/images/%s%s", System.ServerProtocol.WEB, System.Domain, urlMD5, fileExtension) + + } else { + + if strings.Contains(url, System.Domain+"/images/") == false { + + if indexOfString(url, Data.Cache.ImagesURLS) == -1 { + Data.Cache.ImagesURLS = append(Data.Cache.ImagesURLS, url) + } + + } + + cacheImageURL = url + + } + + return +} + +func cachingImages() { + + if Settings.CacheImages == false || System.ImageCachingInProgress == 1 { + return + } + + System.ImageCachingInProgress = 1 + + showInfo("Image Caching:Images are cached") + + for _, url := range Data.Cache.ImagesURLS { + + if len(url) > 0 { + cacheImage(url) + } + + } + + showInfo("Image Caching:Done") + + // Bilder die nicht mehr verwendet werden, werden gelöscht + files, err := ioutil.ReadDir(System.Folder.ImagesCache) + if err != nil { + ShowError(err, 0) + return + } + + for _, file := range files { + + if indexOfString(file.Name(), Data.Cache.ImagesFiles) == -1 { + + var debug = fmt.Sprintf("Image Caching:Remove file: %s %s %d", System.Folder.ImagesCache+file.Name(), file.Name(), len(file.Name())) + showDebug(debug, 1) + err := os.RemoveAll(System.Folder.ImagesCache + file.Name()) + if err != nil { + ShowError(err, 0) + } + + } + + } + + System.ImageCachingInProgress = 0 + + return +} + +func cacheImage(url string) { + + var debug string + var urlMD5 = getMD5(url) + var fileExtension = filepath.Ext(url) + + debug = fmt.Sprintf("Image Caching:File: %s Download: %s", urlMD5+fileExtension, url) + showDebug(debug, 1) + + resp, err := http.Get(url) + if err != nil { + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return + } + + var filePath = System.Folder.ImagesCache + urlMD5 + fileExtension + + // Datei speichern + file, err := os.Create(filePath) + if err != nil { + return + } + + defer file.Close() + + _, err = io.Copy(file, resp.Body) + + return +} + +func uploadLogo(input, filename string) (logoURL string, err error) { + + b64data := input[strings.IndexByte(input, ',')+1:] + + // BAse64 in bytes umwandeln un speichern + sDec, err := b64.StdEncoding.DecodeString(b64data) + + if err != nil { + return + } + + var file = fmt.Sprintf("%s%s", System.Folder.ImagesUpload, filename) + + err = writeByteToFile(file, sDec) + if err != nil { + return + } + + logoURL = fmt.Sprintf("%s://%s/data_images/%s", System.ServerProtocol.WEB, System.Domain, filename) + + return + +} diff --git a/src/internal/authentication/authentication.go b/src/internal/authentication/authentication.go new file mode 100755 index 0000000..0f6bae4 --- /dev/null +++ b/src/internal/authentication/authentication.go @@ -0,0 +1,592 @@ +package authentication + +import ( + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "os" + "path/filepath" + + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + + "time" + //"fmt" + //"log" +) + +const tokenLength = 40 +const saltLength = 20 +const idLength = 10 + +var tokenValidity int +var database string + +var databaseFile = "authentication.json" + +var data = make(map[string]interface{}) +var tokens = make(map[string]interface{}) + +var initAuthentication = false + +// Cookie : cookie +type Cookie struct { + Name string + Value string + Path string + Domain string + Expires time.Time + RawExpires string +} + +// Framework examples + +/* +func main() { + var err error + + var checkErr = func(err error) { + log.Println(err) + os.Exit(0) + } + + err = Init("", 10) // Path to save the data, Validity of tokens in minutes | (error) + if err != nil { + checkErr(err) + } + + + err = CreateDefaultUser("admin", "123") + if err != nil { + checkErr(err) + } + + + + + err = CreateNewUser("xteve", "xteve") // Username, Password | (error) + if err != nil { + checkErr(err) + } + + + + err, token := UserAuthentication("xteve", "xteve") // Username, Password | (error, token) + if err != nil { + checkErr(err) + } else { + fmt.Println("UserAuthentication()") + fmt.Println("Token:", token) + fmt.Println("---") + } + + err, newToken := CheckTheValidityOfTheToken(token) // Current token | (error, new token) + if err != nil { + checkErr(err) + } else { + fmt.Println("CheckTheValidityOfTheToken()") + fmt.Println("New Token:", newToken) + fmt.Println("---") + } + + err, userID := GetUserID(newToken) // Current token | (error, user id) + if err != nil { + checkErr(err) + } else { + fmt.Println("GetUserID()") + fmt.Println("User ID:", userID) + fmt.Println("---") + } + + + var userData = make(map[string]interface{}) + userData["type"] = "Administrator" + err = WriteUserData(userID, userData) // User id, user data | (error) + if err != nil { + checkErr(err) + } + + err, userData = ReadUserData(userID) // User id | (error, userData) + if err != nil { + checkErr(err) + } else { + fmt.Println("ReadUserData()") + fmt.Println("User data:", userData) + fmt.Println("---") + } + + err = RemoveUser(userID) + if err != nil { + checkErr(err) + } + +} +*/ + +// Init : databasePath = Path to authentication.json +func Init(databasePath string, validity int) (err error) { + database = filepath.Dir(databasePath) + string(os.PathSeparator) + databaseFile + + // Check if the database already exists + if _, err = os.Stat(database); os.IsNotExist(err) { + // Create an empty database + var defaults = make(map[string]interface{}) + defaults["dbVersion"] = "1.0" + defaults["hash"] = "sha256" + defaults["users"] = make(map[string]interface{}) + + if saveDatabase(defaults) != nil { + return + } + } + + // Loading the database + err = loadDatabase() + + // Set Token Validity + tokenValidity = validity + initAuthentication = true + return +} + +// CreateDefaultUser = created efault user +func CreateDefaultUser(username, password string) (err error) { + + err = checkInit() + if err != nil { + return + } + + var users = data["users"].(map[string]interface{}) + // Check if the default user exists + if len(users) > 0 { + err = createError(001) + return + } + + var defaults = defaultsForNewUser(username, password) + users[defaults["_id"].(string)] = defaults + saveDatabase(data) + + return +} + +// CreateNewUser : create new user +func CreateNewUser(username, password string) (userID string, err error) { + + err = checkInit() + if err != nil { + return + } + + var checkIfTheUserAlreadyExists = func(username string, userData map[string]interface{}) (err error) { + var salt = userData["_salt"].(string) + var loginUsername = userData["_username"].(string) + + if SHA256(username, salt) == loginUsername { + err = createError(020) + } + + return + } + + var users = data["users"].(map[string]interface{}) + for _, userData := range users { + err = checkIfTheUserAlreadyExists(username, userData.(map[string]interface{})) + if err != nil { + return + } + } + + var defaults = defaultsForNewUser(username, password) + userID = defaults["_id"].(string) + users[userID] = defaults + + saveDatabase(data) + + return +} + +// UserAuthentication : user authentication +func UserAuthentication(username, password string) (token string, err error) { + + err = checkInit() + if err != nil { + return + } + + var login = func(username, password string, loginData map[string]interface{}) (err error) { + err = createError(010) + + var salt = loginData["_salt"].(string) + var loginUsername = loginData["_username"].(string) + var loginPassword = loginData["_password"].(string) + + if SHA256(username, salt) == loginUsername { + if SHA256(password, salt) == loginPassword { + err = nil + } + } + + return + } + + var users = data["users"].(map[string]interface{}) + for id, loginData := range users { + err = login(username, password, loginData.(map[string]interface{})) + if err == nil { + token = setToken(id, "-") + return + } + } + + return +} + +// CheckTheValidityOfTheToken : check token +func CheckTheValidityOfTheToken(token string) (newToken string, err error) { + + err = checkInit() + if err != nil { + return + } + + err = createError(011) + + if v, ok := tokens[token]; ok { + var expires = v.(map[string]interface{})["expires"].(time.Time) + var userID = v.(map[string]interface{})["id"].(string) + + if expires.Sub(time.Now().Local()) < 0 { + return + } + + newToken = setToken(userID, token) + + err = nil + + } else { + return + } + + return +} + +// GetUserID : get user ID +func GetUserID(token string) (userID string, err error) { + + err = checkInit() + if err != nil { + return + } + + err = createError(002) + + if v, ok := tokens[token]; ok { + var expires = v.(map[string]interface{})["expires"].(time.Time) + userID = v.(map[string]interface{})["id"].(string) + + if expires.Sub(time.Now().Local()) < 0 { + return + } + + err = nil + } + + return +} + +// WriteUserData : save user date +func WriteUserData(userID string, userData map[string]interface{}) (err error) { + + err = checkInit() + if err != nil { + return + } + + err = createError(030) + + if v, ok := data["users"].(map[string]interface{})[userID].(map[string]interface{}); ok { + + v["data"] = userData + err = saveDatabase(data) + + } else { + return + } + + return +} + +// ReadUserData : load user date +func ReadUserData(userID string) (userData map[string]interface{}, err error) { + + err = checkInit() + if err != nil { + return + } + + err = createError(031) + + if v, ok := data["users"].(map[string]interface{})[userID].(map[string]interface{}); ok { + userData = v["data"].(map[string]interface{}) + err = nil + + return + } + + return +} + +// RemoveUser : remove user +func RemoveUser(userID string) (err error) { + + err = checkInit() + if err != nil { + return + } + + err = createError(032) + + if _, ok := data["users"].(map[string]interface{})[userID]; ok { + + delete(data["users"].(map[string]interface{}), userID) + err = saveDatabase(data) + + return + } + + return +} + +// SetDefaultUserData : set default user data +func SetDefaultUserData(defaults map[string]interface{}) (err error) { + + allUserData, err := GetAllUserData() + + for _, d := range allUserData { + var data = d.(map[string]interface{})["data"].(map[string]interface{}) + var userID = d.(map[string]interface{})["_id"].(string) + + for k, v := range defaults { + if _, ok := data[k]; ok { + // Key exist + } else { + data[k] = v + } + } + err = WriteUserData(userID, data) + } + return +} + +// ChangeCredentials : change credentials +func ChangeCredentials(userID, username, password string) (err error) { + err = checkInit() + if err != nil { + return + } + + err = createError(032) + + if userData, ok := data["users"].(map[string]interface{})[userID]; ok { + //var userData = tmp.(map[string]interface{}) + var salt = userData.(map[string]interface{})["_salt"].(string) + + if len(username) > 0 { + userData.(map[string]interface{})["_username"] = SHA256(username, salt) + } + + if len(password) > 0 { + userData.(map[string]interface{})["_password"] = SHA256(password, salt) + } + + err = saveDatabase(data) + } + + return +} + +// GetAllUserData : get all user data +func GetAllUserData() (allUserData map[string]interface{}, err error) { + + err = checkInit() + if err != nil { + return + } + + if len(data) == 0 { + var defaults = make(map[string]interface{}) + defaults["dbVersion"] = "1.0" + defaults["hash"] = "sha256" + defaults["users"] = make(map[string]interface{}) + saveDatabase(defaults) + data = defaults + } + + allUserData = data["users"].(map[string]interface{}) + return +} + +// CheckTheValidityOfTheTokenFromHTTPHeader : get token from HTTP header +func CheckTheValidityOfTheTokenFromHTTPHeader(w http.ResponseWriter, r *http.Request) (writer http.ResponseWriter, newToken string, err error) { + err = createError(011) + for _, cookie := range r.Cookies() { + if cookie.Name == "Token" { + var token string + token, err = CheckTheValidityOfTheToken(cookie.Value) + //fmt.Println("T", token, err) + writer = SetCookieToken(w, token) + newToken = token + } + } + //fmt.Println(err) + return +} + +// Framework tools + +func checkInit() (err error) { + if initAuthentication == false { + err = createError(000) + } + + return +} + +func saveDatabase(tmpMap interface{}) (err error) { + + jsonString, err := json.MarshalIndent(tmpMap, "", " ") + + if err != nil { + return + } + + err = ioutil.WriteFile(database, []byte(jsonString), 0600) + if err != nil { + return + } + + return +} + +func loadDatabase() (err error) { + jsonString, err := ioutil.ReadFile(database) + if err != nil { + return + } + + err = json.Unmarshal([]byte(jsonString), &data) + if err != nil { + return + } + + return +} + +// SHA256 : password + salt = sha256 string +func SHA256(secret, salt string) string { + key := []byte(secret) + h := hmac.New(sha256.New, key) + h.Write([]byte("_remote_db")) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +func randomString(n int) string { + const alphanum = "-AbCdEfGhIjKlMnOpQrStUvWxYz0123456789aBcDeFgHiJkLmNoPqRsTuVwXyZ_" + + var bytes = make([]byte, n) + rand.Read(bytes) + for i, b := range bytes { + bytes[i] = alphanum[b%byte(len(alphanum))] + } + return string(bytes) +} + +func randomID(n int) string { + const alphanum = "ABCDEFGHJKLMNOPQRSTUVWXYZ0123456789" + + var bytes = make([]byte, n) + rand.Read(bytes) + for i, b := range bytes { + bytes[i] = alphanum[b%byte(len(alphanum))] + } + return string(bytes) +} + +func createError(errCode int) (err error) { + var errMsg string + switch errCode { + case 000: + errMsg = "Authentication has not yet been initialized" + case 001: + errMsg = "Default user already exists" + case 002: + errMsg = "No user id found for this token" + case 010: + errMsg = "User authentication failed" + case 011: + errMsg = "Session has expired" + case 020: + errMsg = "User already exists" + case 030: + errMsg = "User data could not be saved" + case 031: + errMsg = "User data could not be read" + case 032: + errMsg = "User ID was not found" + } + + err = errors.New(errMsg) + return +} + +func defaultsForNewUser(username, password string) map[string]interface{} { + var defaults = make(map[string]interface{}) + var salt = randomString(saltLength) + defaults["_username"] = SHA256(username, salt) + defaults["_password"] = SHA256(password, salt) + defaults["_salt"] = salt + defaults["_id"] = "id-" + randomID(idLength) + //defaults["_one.time.token"] = randomString(tokenLength) + defaults["data"] = make(map[string]interface{}) + + return defaults +} + +func setToken(id, oldToken string) (newToken string) { + delete(tokens, oldToken) + +loopToken: + newToken = randomString(tokenLength) + if _, ok := tokens[newToken]; ok { + goto loopToken + } + + var tmp = make(map[string]interface{}) + tmp["id"] = id + tmp["expires"] = time.Now().Local().Add(time.Minute * time.Duration(tokenValidity)) + + tokens[newToken] = tmp + + return +} + +func mapToJSON(tmpMap interface{}) string { + jsonString, err := json.MarshalIndent(tmpMap, "", " ") + if err != nil { + return "{}" + } + return string(jsonString) +} + +// SetCookieToken : set cookie +func SetCookieToken(w http.ResponseWriter, token string) http.ResponseWriter { + expiration := time.Now().Add(time.Minute * time.Duration(tokenValidity)) + cookie := http.Cookie{Name: "Token", Value: token, Expires: expiration} + http.SetCookie(w, &cookie) + return w +} diff --git a/src/internal/m3u-parser/m3u-parser_test.go b/src/internal/m3u-parser/m3u-parser_test.go new file mode 100644 index 0000000..ca3e73d --- /dev/null +++ b/src/internal/m3u-parser/m3u-parser_test.go @@ -0,0 +1,84 @@ +package m3u + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "testing" +) + +type M3UStream struct { + GroupTitle string `json:"group-title,required"` + Name string `json:"name,required"` + TvgID string `json:"tvg-id,required"` + TvgLogo string `json:"tvg-logo,required"` + TvgName string `json:"tvg-name,required"` + URL string `json:"url,required"` + UUIDKey string `json:"_uuid.key,omitempty"` + UUIDValue string `json:"_uuid.value,omitempty"` +} + +func TestStream1(t *testing.T) { + + var file = "test_list_1.m3u" + var content, err = ioutil.ReadFile(file) + if err != nil { + t.Error(err) + return + } + + streams, err := MakeInterfaceFromM3U(content) + + if err != nil { + t.Error(err) + } + + err = checkStream(streams) + if err != nil { + t.Error(err) + } + + fmt.Println("Streams:", len(streams)) + t.Log(streams) + +} + +func checkStream(streamInterface []interface{}) (err error) { + + for i, s := range streamInterface { + + var stream = s.(map[string]string) + var m3uStream M3UStream + + jsonString, err := json.MarshalIndent(stream, "", " ") + + if err == nil { + + err = json.Unmarshal(jsonString, &m3uStream) + if err == nil { + + log.Print(fmt.Sprintf("Stream: %d", i)) + log.Print(fmt.Sprintf("Name*: %s", m3uStream.Name)) + log.Print(fmt.Sprintf("URL*: %s", m3uStream.URL)) + log.Print(fmt.Sprintf("tvg-name: %s", m3uStream.TvgName)) + log.Print(fmt.Sprintf("tvg-id**: %s", m3uStream.TvgID)) + log.Print(fmt.Sprintf("tvg-logo: %s", m3uStream.TvgLogo)) + log.Print(fmt.Sprintf("group-title**: %s", m3uStream.GroupTitle)) + + if len(m3uStream.UUIDKey) > 0 { + log.Print(fmt.Sprintf("UUID key***: %s", m3uStream.UUIDKey)) + log.Print(fmt.Sprintf("UUID value: %s", m3uStream.UUIDValue)) + } else { + log.Print(fmt.Sprintf("UUID key: false")) + } + + } + + } + + log.Println(fmt.Sprintf("- - - - - (*: Required) | (**: Nice to have) | (***: Love it) - - - - -")) + } + + return +} diff --git a/src/internal/m3u-parser/test_list_1.m3u b/src/internal/m3u-parser/test_list_1.m3u new file mode 100644 index 0000000..db5c125 --- /dev/null +++ b/src/internal/m3u-parser/test_list_1.m3u @@ -0,0 +1,7 @@ +#EXTM3U url-tvg="http://example.com/file.xml" x-tvg-url="http://example.com/xteve.xml" +#EXTINF:0 channelID="1" tvg-chno="1" tvg-name="Channel.1" tvg-id="tvg.id.1" tvg-logo="https://example/logo.png" group-title="Group 1",Channel 1 +http://example.com/stream/1 + +#EXTINF:0 channelID="2" tvg-chno="2" tvg-name="Channel.2" tvg-id="tvg.id.2" tvg-logo="https://example/logo.png" group-title="Group 2",Channel 2 +#123 +http://example.com/stream/2 diff --git a/src/internal/m3u-parser/xteve_m3uParser.go b/src/internal/m3u-parser/xteve_m3uParser.go new file mode 100755 index 0000000..be73f01 --- /dev/null +++ b/src/internal/m3u-parser/xteve_m3uParser.go @@ -0,0 +1,267 @@ +package m3u + +import ( + "errors" + "net/url" + "regexp" + "strings" +) + +// MakeInterfaceFromM3U : +func MakeInterfaceFromM3U(byteStream []byte) (allChannels []interface{}, err error) { + + var content = string(byteStream) + var channelName string + + var parseMetaData = func(channel string) (stream map[string]string) { + + stream = make(map[string]string) + var exceptForParameter = `[a-z-A-Z=]*(".*?")` + var exceptForChannelName = `,([^\n]*|,[^\r]*)` + + var lines = strings.Split(strings.Replace(channel, "\r\n", "\n", -1), "\n") + + // Zeilen mit # und leerer Zeilen entfernen + for i := len(lines) - 1; i >= 0; i-- { + + if len(lines[i]) == 0 || lines[i][0:1] == "#" { + lines = append(lines[:i], lines[i+1:]...) + } + + } + + if len(lines) >= 2 { + + for _, line := range lines { + + _, err := url.ParseRequestURI(line) + + switch err { + + case nil: + stream["url"] = strings.Trim(line, "\r\n") + + default: + + var value string + // Alle Parameter parsen + var p = regexp.MustCompile(exceptForParameter) + var streamParameter = p.FindAllString(line, -1) + + for _, p := range streamParameter { + + line = strings.Replace(line, p, "", 1) + + p = strings.Replace(p, `"`, "", -1) + var parameter = strings.Split(p, "=") + + if len(parameter) == 2 { + + // TVG Key als Kleinbuchstaben speichern + switch strings.Contains(parameter[0], "tvg") { + + case true: + stream[strings.ToLower(parameter[0])] = parameter[1] + case false: + stream[parameter[0]] = parameter[1] + + } + + // URL's nicht an die Filterfunktion übergeben + if !strings.Contains(parameter[1], "://") && len(parameter[1]) > 0 { + value = value + parameter[1] + " " + } + + } + + } + + // Kanalnamen parsen + n := regexp.MustCompile(exceptForChannelName) + var name = n.FindAllString(line, 1) + + if len(name) > 0 { + channelName = name[0] + channelName = strings.Replace(channelName, `,`, "", 1) + channelName = strings.TrimRight(channelName, "\r\n") + channelName = strings.TrimRight(channelName, " ") + } + + if len(channelName) == 0 { + + if v, ok := stream["tvg-name"]; ok { + channelName = v + } + + } + + channelName = strings.TrimRight(channelName, " ") + + // Kanäle ohne Namen werden augelassen + if len(channelName) == 0 { + return + } + + stream["name"] = channelName + value = value + channelName + + stream["_values"] = value + + } + + } + + } + + // Nach eindeutiger ID im Stream suchen + for key, value := range stream { + + if !strings.Contains(strings.ToLower(key), "tvg-id") { + + if strings.Contains(strings.ToLower(key), "id") { + + stream["_uuid.key"] = key + stream["_uuid.value"] = value + //os.Exit(0) + break + + } + + } + + } + + return + } + + //fmt.Println(content) + + if strings.Contains(content, "#EXTM3U") { + + var channels = strings.Split(content, "#EXTINF") + + channels = append(channels[:0], channels[1:]...) + + for _, channel := range channels { + + var stream = parseMetaData(channel) + + if len(stream) > 0 && stream != nil { + allChannels = append(allChannels, stream) + } + + } + + } else { + + err = errors.New("No valid m3u file") + + } + + return +} + +// MakeInterfaceFromM3U2 : +func MakeInterfaceFromM3U2(byteStream []byte) (allChannels []interface{}, err error) { + var content = string(byteStream) + //var allChannels = make([]interface{}, 0) + + var channels = strings.Split(content, "#EXTINF") + + var parseMetaData = func(metaData string) map[string]string { + var values string // Save all values in a key + var channel = make(map[string]string) + + var exceptForParameter = `[a-z-A-Z=]*(".*?")` + //var exceptForChannelName = `(,[^.$\n]*|,[^.$\r]*)` + var exceptForChannelName = `(,[^\n]*|,[^\r]*)` + + var exceptForStreamingURL = `(\n.*?\n|\r.*?\r|\n.*?\z|\r.*?\z)` + //var exceptForStreamingURL = `^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?` + + // Parse all parameters + p := regexp.MustCompile(exceptForParameter) + var parameter = p.FindAllString(metaData, -1) + //fmt.Println(parameter) + for _, i := range parameter { + var remove = i + i = strings.Replace(i, `"`, "", -1) + if strings.Contains(i, "=") { + var item = strings.Split(i, "=") + switch strings.Contains(item[0], "tvg") { + case true: + channel[strings.ToLower(item[0])] = item[1] + case false: + channel[item[0]] = item[1] + } + + switch strings.Contains(item[1], "://") { + case false: + values = values + item[1] + " " + } + + } + metaData = strings.Replace(metaData, remove, "", 1) + } + + // Parse channel name (after the comma) + n := regexp.MustCompile(exceptForChannelName) + var name = n.FindAllString(metaData, 1) + //name[len(name) - 1] = strings.Replace(name[len(name) - 1], `\r`, "", -1) + + var channelName string + if len(name) == 0 { + if v, ok := channel["tvg-name"]; ok { + channelName = v + } + } else { + channelName = name[len(name)-1][1:len(name[len(name)-1])] + } + + channelName = strings.Replace(channelName, `"`, "", -1) + + var replacer = strings.NewReplacer("\n", "", "\r", "") + channel["name"] = replacer.Replace(channelName) + + values = values + channelName + " " + + // Parse streaming URL + u := regexp.MustCompile(exceptForStreamingURL) + var streamingURL = u.FindAllString(metaData, -1) + var url = strings.Replace(streamingURL[0], "\n", "", -1) + url = strings.Replace(url, "\r", "", -1) + url = strings.Trim(url, "\r\n") + channel["url"] = url + + channel["_values"] = values + + // Search for a unique ID + + for key, value := range channel { + if !strings.Contains(strings.ToLower(key), "tvg-id") { + if strings.Contains(strings.ToLower(key), "id") { + channel["_uuid.key"] = key + channel["_uuid.value"] = value + break + } + } + } + + return channel + } + + if strings.Contains(channels[0], "#EXTM3U") { + + for _, thisStream := range channels { + if !strings.Contains(thisStream, "#EXTM3U") { + var channel = parseMetaData(thisStream) + allChannels = append(allChannels, channel) + } + } + + } else { + err = errors.New("No valid m3u file") + } + + return +} diff --git a/src/internal/up2date/client/client.go b/src/internal/up2date/client/client.go new file mode 100755 index 0000000..13eeccc --- /dev/null +++ b/src/internal/up2date/client/client.go @@ -0,0 +1,129 @@ +package up2date + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "net/url" + "runtime" + "time" +) + +// ClientInfo : Information about the key (NAME OS, ARCH, UUID, KEY) +type ClientInfo struct { + Arch string `json:"arch,required"` + Branch string `json:"branch,required"` + CMD string `json:"cmd,omitempty"` + Name string `json:"name,required"` + OS string `json:"os,required"` + URL string `json:"url,required"` + + Response ServerResponse `json:"response,omitempty"` +} + +//ServerResponse : Response from server after client request +type ServerResponse struct { + Status bool `json:"status,omitempty"` + Reason string `json:"reason,omitempty"` + Version string `json:"version,omitempty"` + UpdateBIN string `json:"update.url.bin,omitempty"` + UpdateZIP string `json:"update.url.zip,omitempty"` + Filename string `json:"filename.bin,omitempty"` +} + +// Updater : Client infos +var Updater ClientInfo + +// UpdateURL : URL for the new binary +var UpdateURL string + +// Init : Init +func Init() { + Updater.OS = runtime.GOOS + Updater.Arch = runtime.GOARCH +} + +// GetVersion : Information about the latest version +func GetVersion() (err error) { + + Updater.CMD = "getVersion" + err = serverRequest() + return +} + +func serverRequest() (err error) { + + var serverResponse ServerResponse + jsonByte, err := json.MarshalIndent(Updater, "", " ") + if err == nil { + + // Serververbindung prüfen + u, err := url.Parse(Updater.URL) + if err != nil { + return err + } + var server = u.Host + + timeout := time.Duration(1 * time.Second) + _, err = net.DialTimeout("tcp", server, timeout) + if err != nil { + return err + } + + // Check redirect 301 <---> 308 + redirect, err := http.NewRequest("POST", Updater.URL, nil) + + client := &http.Client{} + client.CheckRedirect = func(redirect *http.Request, via []*http.Request) error { + return errors.New("Redirect") + } + + resp, err := client.Do(redirect) + + if err != nil { + // Redirect + if resp.StatusCode >= 301 && resp.StatusCode <= 308 { //status code 301 <---> 308 + Updater.URL = resp.Header.Get("Location") + } else { + return err + } + } + // --- + + req, err := http.NewRequest("POST", Updater.URL, bytes.NewBuffer(jsonByte)) + req.Header.Set("Content-Type", "application/json") + + client = &http.Client{} + resp, err = client.Do(req) + + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + //fmt.Println(resp.StatusCode, Updater.URL, Updater.CMD) + err = fmt.Errorf(fmt.Sprintf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), Updater.URL)) + return err + } + + Updater.CMD = "" + defer resp.Body.Close() + + body, _ := ioutil.ReadAll(resp.Body) + + err = json.Unmarshal(body, &serverResponse) + + if err != nil { + return err + } + + Updater.Response = serverResponse + + } + + return +} diff --git a/src/internal/up2date/client/update.go b/src/internal/up2date/client/update.go new file mode 100755 index 0000000..7b3b15b --- /dev/null +++ b/src/internal/up2date/client/update.go @@ -0,0 +1,271 @@ +package up2date + +import ( + "archive/zip" + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "syscall" + + "github.com/kardianos/osext" +) + +// DoUpdate : Update binary +func DoUpdate(fileType, filenameBIN string) (err error) { + + var url string + switch fileType { + case "bin": + url = Updater.Response.UpdateBIN + case "zip": + url = Updater.Response.UpdateZIP + } + + switch runtime.GOOS { + case "windows": + filenameBIN = filenameBIN + ".exe" + } + + if len(url) > 0 { + log.Println("["+strings.ToUpper(fileType)+"]", "New version ("+Updater.Name+"):", Updater.Response.Version) + + // Download new binary + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + log.Println("["+strings.ToUpper(fileType)+"]", "Download new version...") + + if resp.StatusCode != http.StatusOK { + log.Println("["+strings.ToUpper(fileType)+"]", "Download new version...OK") + return fmt.Errorf("bad status: %s", resp.Status) + } + + // Change binary filename to .filename + binary, err := osext.Executable() + var filename = getFilenameFromPath(binary) + var path = getPlatformPath(binary) + var oldBinary = path + "_old_" + filename + var newBinary = binary + + // ZIP + var tmpFolder = path + "tmp" + var tmpFile = tmpFolder + string(os.PathSeparator) + filenameBIN + + //fmt.Println(binary, path+"."+filename) + os.Rename(newBinary, oldBinary) + + // Save the new binary with the old file name + out, err := os.Create(binary) + if err != nil { + restorOldBinary(oldBinary, newBinary) + return err + } + defer out.Close() + + // Write the body to file + + _, err = io.Copy(out, resp.Body) + if err != nil { + restorOldBinary(oldBinary, newBinary) + return err + } + + // Update as a ZIP file + if fileType == "zip" { + + log.Println("["+strings.ToUpper(fileType)+"]", "Update file:", filenameBIN) + log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...") + err = extractZIP(binary, tmpFolder) + + binary = newBinary + + if err != nil { + + log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...ERROR") + + restorOldBinary(oldBinary, newBinary) + + return err + } else { + + log.Println("["+strings.ToUpper(fileType)+"]", "Unzip ZIP file...OK") + log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...") + + err = copyFile(tmpFile, binary) + if err == nil { + log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...OK") + } else { + + log.Println("["+strings.ToUpper(fileType)+"]", "Copy binary file...ERROR") + restorOldBinary(oldBinary, newBinary) + + return err + } + + os.RemoveAll(tmpFolder) + } + + } + + // Set the permission + err = os.Chmod(binary, 0755) + + // Close the new file !Windows + out.Close() + + log.Println("["+strings.ToUpper(fileType)+"]", "Update Successful") + + // Restart binary (Windows) + if runtime.GOOS == "windows" { + + bin, err := os.Executable() + + if err != nil { + restorOldBinary(oldBinary, newBinary) + return err + } + + var pid = os.Getpid() + var process, _ = os.FindProcess(pid) + + if proc, err := start(bin); err == nil { + + os.RemoveAll(oldBinary) + process.Kill() + proc.Wait() + + } else { + restorOldBinary(oldBinary, newBinary) + } + + } else { + + // Restart binary (Linux and UNIX) + file, _ := osext.Executable() + os.RemoveAll(oldBinary) + err = syscall.Exec(file, os.Args, os.Environ()) + if err != nil { + restorOldBinary(oldBinary, newBinary) + log.Fatal(err) + return err + } + + } + + } + + return +} + +func start(args ...string) (p *os.Process, err error) { + + if args[0], err = exec.LookPath(args[0]); err == nil { + //fmt.Println(args[0]) + var procAttr os.ProcAttr + procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr} + p, err := os.StartProcess(args[0], args, &procAttr) + + if err == nil { + return p, nil + } + + } + + return nil, err +} + +func restorOldBinary(oldBinary, newBinary string) { + os.RemoveAll(newBinary) + os.Rename(oldBinary, newBinary) +} + +func getPlatformFile(filename string) string { + + path, file := filepath.Split(filename) + var newPath = filepath.Dir(path) + var newFileName = newPath + string(os.PathSeparator) + file + + return newFileName +} + +func getFilenameFromPath(path string) string { + + file := filepath.Base(path) + + return file +} + +func getPlatformPath(path string) string { + + var newPath = filepath.Dir(path) + string(os.PathSeparator) + + return newPath +} + +func copyFile(src, dst string) (err error) { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + return out.Close() +} + +func extractZIP(archive, target string) (err error) { + + reader, err := zip.OpenReader(archive) + if err != nil { + return err + } + + if err := os.MkdirAll(target, 0755); err != nil { + return err + } + + for _, file := range reader.File { + + path := filepath.Join(target, file.Name) + if file.FileInfo().IsDir() { + os.MkdirAll(path, file.Mode()) + continue + } + + fileReader, err := file.Open() + if err != nil { + return err + } + defer fileReader.Close() + + targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) + if err != nil { + return err + } + defer targetFile.Close() + + if _, err := io.Copy(targetFile, fileReader); err != nil { + return err + } + + } + + return +} diff --git a/src/m3u.go b/src/m3u.go new file mode 100644 index 0000000..84066ea --- /dev/null +++ b/src/m3u.go @@ -0,0 +1,238 @@ +package src + +import ( + "encoding/json" + "fmt" + "path" + "regexp" + "sort" + "strconv" + "strings" + + m3u "../src/internal/m3u-parser" +) + +// Playlisten parsen +func parsePlaylist(filename, fileType string) (channels []interface{}, err error) { + + content, err := readByteFromFile(filename) + var id = strings.TrimSuffix(getFilenameFromPath(filename), path.Ext(getFilenameFromPath(filename))) + var playlistName = getProviderParameter(id, fileType, "name") + + if err == nil { + + switch fileType { + case "m3u": + channels, err = m3u.MakeInterfaceFromM3U(content) + case "hdhr": + channels, err = makeInteraceFromHDHR(content, playlistName, id) + } + + } + + return +} + +// Streams filtern +func filterThisStream(s interface{}) (status bool) { + + status = false + var stream = s.(map[string]string) + var regexpYES = `[{]+[^.]+[}]` + var regexpNO = `!+[{]+[^.]+[}]` + + for _, filter := range Data.Filter { + + var group, name, search string + var exclude, include string + var match = false + + var streamValues = strings.Replace(stream["_values"], "\r", "", -1) + + if v, ok := stream["group-title"]; ok { + group = v + } + + if v, ok := stream["name"]; ok { + name = v + } + + // Unerwünschte Streams !{DEU} + r := regexp.MustCompile(regexpNO) + val := r.FindStringSubmatch(filter.Rule) + + if len(val) == 1 { + + exclude = val[0][2 : len(val[0])-1] + filter.Rule = strings.Replace(filter.Rule, " "+val[0], "", -1) + filter.Rule = strings.Replace(filter.Rule, val[0], "", -1) + + } + + // Muss zusätzlich erfüllt sein {DEU} + r = regexp.MustCompile(regexpYES) + val = r.FindStringSubmatch(filter.Rule) + + if len(val) == 1 { + + include = val[0][1 : len(val[0])-1] + filter.Rule = strings.Replace(filter.Rule, " "+val[0], "", -1) + filter.Rule = strings.Replace(filter.Rule, val[0], "", -1) + + } + + switch filter.CaseSensitive { + + case false: + + streamValues = strings.ToLower(streamValues) + filter.Rule = strings.ToLower(filter.Rule) + exclude = strings.ToLower(exclude) + include = strings.ToLower(include) + group = strings.ToLower(group) + name = strings.ToLower(name) + + } + + switch filter.Type { + + case "group-title": + search = name + + if group == filter.Rule { + match = true + } + + case "custom-filter": + search = streamValues + if strings.Contains(search, filter.Rule) { + match = true + } + } + + if match == true { + + if len(exclude) > 0 { + var status = checkConditions(search, exclude, "exclude") + if status == false { + return false + } + } + + if len(include) > 0 { + var status = checkConditions(search, include, "include") + if status == false { + return false + } + } + + return true + + } + + } + + return false +} + +// Bedingungen für den Filter +func checkConditions(streamValues, conditions, coType string) (status bool) { + + switch coType { + + case "exclude": + status = true + + case "include": + status = false + + } + + conditions = strings.Replace(conditions, ", ", ",", -1) + conditions = strings.Replace(conditions, " ,", ",", -1) + + var keys = strings.Split(conditions, ",") + + for _, key := range keys { + + if strings.Contains(streamValues, key) { + + switch coType { + + case "exclude": + return false + + case "include": + return true + + } + + } + + } + + return +} + +// xTeVe M3U Datei erstellen +func buildM3U(groups []string) (m3u string, err error) { + + var m3uChannels = make(map[float64]XEPGChannelStruct) + var channelNumbers []float64 + + for _, dxc := range Data.XEPG.Channels { + + var xepgChannel XEPGChannelStruct + err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel) + if err == nil { + + if xepgChannel.XActive == true { + + if len(groups) > 0 { + + if indexOfString(xepgChannel.XGroupTitle, groups) == -1 { + goto Done + } + + } + + var channelNumber, err = strconv.ParseFloat(strings.TrimSpace(xepgChannel.XChannelID), 64) + + if err == nil { + m3uChannels[channelNumber] = xepgChannel + channelNumbers = append(channelNumbers, channelNumber) + } + + } + + } + + Done: + } + + // M3U Inhalt erstellen + sort.Float64s(channelNumbers) + + var xmltvURL = fmt.Sprintf("%s://%s/xmltv/xteve.xml", System.ServerProtocol.XML, System.Domain) + m3u = fmt.Sprintf(`#EXTM3U url-tvg="%s" x-tvg-url="%s"`+"\n", xmltvURL, xmltvURL) + + for _, channelNumber := range channelNumbers { + + var channel = m3uChannels[channelNumber] + var parameter = fmt.Sprintf(`#EXTINF:0 channelID="%s" tvg-chno="%s" tvg-name="%s" tvg-id="%s" tvg-logo="%s" group-title="%s",%s`+"\n", channel.XEPG, channel.XChannelID, channel.XName, channel.XChannelID, getCacheImageURL(channel.TvgLogo), channel.XGroupTitle, channel.XName) + var stream, err = createStreamingURL("M3U", channel.FileM3UID, channel.XChannelID, channel.XName, channel.URL) + if err == nil { + m3u = m3u + parameter + stream + "\n" + } + + } + + if len(groups) == 0 { + + var filename = System.Folder.Data + "xteve.m3u" + err = writeByteToFile(filename, []byte(m3u)) + + } + + return +} diff --git a/src/maintenance.go b/src/maintenance.go new file mode 100644 index 0000000..76c499c --- /dev/null +++ b/src/maintenance.go @@ -0,0 +1,84 @@ +package src + +import ( + "fmt" + "math/rand" + "time" +) + +// InitMaintenance : Wartungsprozess initialisieren +func InitMaintenance() (err error) { + + rand.Seed(time.Now().Unix()) + System.TimeForAutoUpdate = fmt.Sprintf("0%d%d", randomTime(0, 2), randomTime(10, 59)) + + go maintenance() + + return +} + +func maintenance() { + + for { + + var t = time.Now() + + // Aktualisierung der Playlist und XMLTV Dateien + if System.ScanInProgress == 0 { + + for _, schedule := range Settings.Update { + + if schedule == t.Format("1504") { + + showInfo("Update:" + schedule) + + // Backup erstellen + err := xTeVeAutoBackup() + if err != nil { + ShowError(err, 000) + } + + // Playlist und XMLTV Dateien aktualisieren + getProviderData("m3u", "") + getProviderData("hdhr", "") + + if Settings.EpgSource == "XEPG" { + getProviderData("xmltv", "") + } + + // Datenbank für DVR erstellen + err = buildDatabaseDVR() + if err != nil { + ShowError(err, 000) + } + + if Settings.CacheImages == false && System.ImageCachingInProgress == 0 { + removeChildItems(System.Folder.ImagesCache) + } + + // XEPG Dateien erstellen + Data.Cache.XMLTV = make(map[string]XMLTV) + buildXEPG(false) + + } + + } + + // Update xTeVe (Binary) + if System.TimeForAutoUpdate == t.Format("1504") { + BinaryUpdate() + } + + } + + time.Sleep(60 * time.Second) + + } + + return +} + +func randomTime(min, max int) int { + rand.Seed(time.Now().Unix()) + return rand.Intn(max-min) + min +} diff --git a/src/provider.go b/src/provider.go new file mode 100644 index 0000000..1e8cb27 --- /dev/null +++ b/src/provider.go @@ -0,0 +1,323 @@ +package src + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + m3u "../src/internal/m3u-parser" +) + +// fileType: Welcher Dateityp soll aktualisiert werden (m3u, hdhr, xml) | fileID: Update einer bestimmten Datei (Provider ID) +func getProviderData(fileType, fileID string) (err error) { + + var fileExtension, serverFileName string + var body = make([]byte, 0) + var newProvider = false + var dataMap = make(map[string]interface{}) + + var saveDateFromProvider = func(fileSource, serverFileName, id string, body []byte) (err error) { + + var data = make(map[string]interface{}) + + if value, ok := dataMap[id].(map[string]interface{}); ok { + data = value + } else { + data["id.provider"] = id + dataMap[id] = data + } + + // Default keys für die Providerdaten + var keys = []string{"name", "description", "type", "file." + System.AppName, "file.source", "tuner", "last.update", "compatibility", "counter.error", "counter.download", "provider.availability"} + + for _, key := range keys { + + if _, ok := data[key]; !ok { + + switch key { + + case "name": + data[key] = serverFileName + + case "description": + data[key] = "" + + case "type": + data[key] = fileType + + case "file." + System.AppName: + data[key] = id + fileExtension + + case "file.source": + data[key] = fileSource + + case "last.update": + data[key] = time.Now().Format("2006-01-02 15:04:05") + + case "tuner": + if fileType == "m3u" || fileType == "hdhr" { + if _, ok := data[key].(float64); !ok { + data[key] = 1 + } + } + + case "compatibility": + data[key] = make(map[string]interface{}) + + case "counter.download": + data[key] = 0.0 + + case "counter.error": + data[key] = 0.0 + + case "provider.availability": + data[key] = 100 + } + + } + + } + + if _, ok := data["id.provider"]; !ok { + data["id.provider"] = id + } + + // Datei extrahieren + body, err = extractGZIP(body, fileSource) + if err != nil { + ShowError(err, 000) + return + } + + // Daten überprüfen + showInfo("Check File:" + fileSource) + switch fileType { + + case "m3u": + _, err = m3u.MakeInterfaceFromM3U(body) + + case "hdhr": + _, err = jsonToInterface(string(body)) + + case "xmltv": + err = checkXMLCompatibility(id, body) + + } + + if err != nil { + return + } + + var filePath = System.Folder.Data + data["file."+System.AppName].(string) + + err = writeByteToFile(filePath, body) + + if err == nil { + data["last.update"] = time.Now().Format("2006-01-02 15:04:05") + data["counter.download"] = data["counter.download"].(float64) + 1 + } + + return + + } + + switch fileType { + + case "m3u": + dataMap = Settings.Files.M3U + fileExtension = ".m3u" + + case "hdhr": + dataMap = Settings.Files.HDHR + fileExtension = ".json" + + case "xmltv": + dataMap = Settings.Files.XMLTV + fileExtension = ".xml" + + } + + for dataID, d := range dataMap { + + var data = d.(map[string]interface{}) + var fileSource = data["file.source"].(string) + newProvider = false + + if _, ok := data["new"]; ok { + newProvider = true + delete(data, "new") + } + + // Wenn eine ID vorhanden ist und nicht mit der aus der Datanbank übereinstimmt, wird die Aktualisierung übersprungen (goto) + if len(fileID) > 0 && newProvider == false { + if dataID != fileID { + goto Done + } + } + + switch fileType { + + case "hdhr": + + // Laden vom HDHomeRun Tuner + showInfo("Tuner:" + fileSource) + var tunerURL = "http://" + fileSource + "/lineup.json" + serverFileName, body, err = downloadFileFromServer(tunerURL) + + default: + + if strings.Contains(fileSource, "http://") || strings.Contains(fileSource, "https://") { + + // Laden vom Remote Server + showInfo("Download:" + fileSource) + serverFileName, body, err = downloadFileFromServer(fileSource) + + } else { + + // Laden einer lokalen Datei + showInfo("Open:" + fileSource) + + err = checkFile(fileSource) + if err == nil { + body, err = readByteFromFile(fileSource) + serverFileName = getFilenameFromPath(fileSource) + } + + } + + } + + if err == nil { + + err = saveDateFromProvider(fileSource, serverFileName, dataID, body) + if err == nil { + showInfo("Save File:" + fileSource + " [ID: " + dataID + "]") + } + + } + + if err != nil { + + ShowError(err, 000) + var downloadErr = err + + if newProvider == false { + + // Prüfen ob ältere Datei vorhanden ist + var file = System.Folder.Data + dataID + fileExtension + + err = checkFile(file) + if err == nil { + + if len(fileID) == 0 { + showWarning(1011) + } + + err = downloadErr + } + + // Fehler Counter um 1 erhöhen + var data = make(map[string]interface{}) + if value, ok := dataMap[dataID].(map[string]interface{}); ok { + + data = value + data["counter.error"] = data["counter.error"].(float64) + 1 + data["counter.download"] = data["counter.download"].(float64) + 1 + + } + + } else { + return downloadErr + } + + } + + // Berechnen der Fehlerquote + if newProvider == false { + + if value, ok := dataMap[dataID].(map[string]interface{}); ok { + + var data = make(map[string]interface{}) + data = value + + if data["counter.error"].(float64) == 0 { + data["provider.availability"] = 100 + } else { + data["provider.availability"] = int(data["counter.error"].(float64)*100/data["counter.download"].(float64)*-1 + 100) + } + + } + + } + + switch fileType { + + case "m3u": + Settings.Files.M3U = dataMap + + case "hdhr": + Settings.Files.HDHR = dataMap + + case "xmltv": + Settings.Files.XMLTV = dataMap + delete(Data.Cache.XMLTV, System.Folder.Data+dataID+fileExtension) + + } + + saveSettings(Settings) + + Done: + } + + return +} + +func downloadFileFromServer(providerURL string) (filename string, body []byte, err error) { + + _, err = url.ParseRequestURI(providerURL) + if err != nil { + return + } + + resp, err := http.Get(providerURL) + if err != nil { + return + } + + resp.Header.Set("User-Agent", Settings.UserAgent) + + if resp.StatusCode != http.StatusOK { + err = fmt.Errorf(fmt.Sprintf("%d: %s "+http.StatusText(resp.StatusCode), resp.StatusCode, providerURL)) + return + } + + // Dateiname aus dem Header holen + var index = strings.Index(resp.Header.Get("Content-Disposition"), "filename") + + if index > -1 { + + var headerFilename = resp.Header.Get("Content-Disposition")[index:len(resp.Header.Get("Content-Disposition"))] + var value = strings.Split(headerFilename, `=`) + var f = strings.Replace(value[1], `"`, "", -1) + + f = strings.Replace(f, `;`, "", -1) + filename = f + showInfo("Header filename:" + filename) + + } else { + + var cleanFilename = strings.SplitN(getFilenameFromPath(providerURL), "?", 2) + filename = cleanFilename[0] + + } + + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + return + } + + return +} diff --git a/src/screen.go b/src/screen.go new file mode 100644 index 0000000..5fcb1e6 --- /dev/null +++ b/src/screen.go @@ -0,0 +1,404 @@ +package src + +import ( + "fmt" + "log" + "runtime" + "strconv" + "strings" + "sync" + "time" +) + +func showInfo(str string) { + + var max = 22 + var msg = strings.SplitN(str, ":", 2) + var length = len(msg[0]) + var space string + + if len(msg) == 2 { + + for i := length; i < max; i++ { + space = space + " " + } + + msg[0] = msg[0] + ":" + space + + var logMsg = fmt.Sprintf("[%s] %s%s", System.Name, msg[0], msg[1]) + + printLogOnScreen(logMsg, "info") + + logMsg = strings.Replace(logMsg, " ", " ", -1) + WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg) + logCleanUp() + + } + + return +} + +func showDebug(str string, level int) { + + if System.Flag.Debug < level { + return + } + + var max = 22 + var msg = strings.SplitN(str, ":", 2) + var length = len(msg[0]) + var space string + var mutex = sync.RWMutex{} + + if len(msg) == 2 { + + for i := length; i < max; i++ { + space = space + " " + } + msg[0] = msg[0] + ":" + space + + var logMsg = fmt.Sprintf("[DEBUG] %s%s", msg[0], msg[1]) + + printLogOnScreen(logMsg, "debug") + + mutex.Lock() + logMsg = strings.Replace(logMsg, " ", " ", -1) + WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg) + logCleanUp() + mutex.Unlock() + + } + + return +} + +func showHighlight(str string) { + + var max = 22 + var msg = strings.SplitN(str, ":", 2) + var length = len(msg[0]) + var space string + + var notification Notification + notification.Type = "info" + + if len(msg) == 2 { + + for i := length; i < max; i++ { + space = space + " " + } + + msg[0] = msg[0] + ":" + space + + var logMsg = fmt.Sprintf("[%s] %s%s", System.Name, msg[0], msg[1]) + + printLogOnScreen(logMsg, "highlight") + + } + + notification.Type = "info" + notification.Message = msg[1] + + addNotification(notification) + + return +} + +func showWarning(errCode int) { + + var errMsg = getErrMsg(errCode) + var logMsg = fmt.Sprintf("[%s] [WARNING] %s", System.Name, errMsg) + var mutex = sync.RWMutex{} + + printLogOnScreen(logMsg, "warning") + + mutex.Lock() + WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg) + WebScreenLog.Warnings++ + mutex.Unlock() + + return +} + +// ShowError : Zeigt die Fehlermeldungen in der Konsole +func ShowError(err error, errCode int) { + + var mutex = sync.RWMutex{} + + var errMsg = getErrMsg(errCode) + var logMsg = fmt.Sprintf("[%s] [ERROR] %s (%s) - EC: %d", System.Name, err, errMsg, errCode) + + printLogOnScreen(logMsg, "error") + + mutex.Lock() + WebScreenLog.Log = append(WebScreenLog.Log, time.Now().Format("2006-01-02 15:04:05")+" "+logMsg) + WebScreenLog.Errors++ + mutex.Unlock() + + return +} + +func printLogOnScreen(logMsg string, logType string) { + + var color string + + switch logType { + + case "info": + color = "\033[0m" + + case "debug": + color = "\033[35m" + + case "highlight": + color = "\033[32m" + + case "warning": + color = "\033[33m" + + case "error": + color = "\033[31m" + + } + + switch runtime.GOOS { + + case "windows": + log.Println(logMsg) + + default: + fmt.Print(color) + log.Println(logMsg) + fmt.Print("\033[0m") + + } + +} + +func logCleanUp() { + + var logEntriesRAM = Settings.LogEntriesRAM + var logs = WebScreenLog.Log + + WebScreenLog.Warnings = 0 + WebScreenLog.Errors = 0 + + if len(logs) > logEntriesRAM { + + var tmp = make([]string, 0) + for i := len(logs) - logEntriesRAM; i < logEntriesRAM; i++ { + tmp = append(tmp, logs[i]) + } + + logs = tmp + } + + for _, log := range logs { + + if strings.Contains(log, "WARNING") { + WebScreenLog.Warnings++ + } + + if strings.Contains(log, "ERROR") { + WebScreenLog.Errors++ + } + + } + + WebScreenLog.Log = logs + + return +} + +// Fehlercodes +func getErrMsg(errCode int) (errMsg string) { + + switch errCode { + + case 0: + return + + // Errors + case 1001: + errMsg = fmt.Sprintf("Web server could not be started.") + case 1002: + errMsg = fmt.Sprintf("No local IP address found.") + case 1003: + errMsg = fmt.Sprintf("Invalid xml") + case 1004: + errMsg = fmt.Sprintf("File not found") + case 1005: + errMsg = fmt.Sprintf("Invalide m3u") + case 1006: + errMsg = fmt.Sprintf("No playlist!") + case 1007: + errMsg = fmt.Sprintf("XEPG requires an XMLTV file.") + case 1010: + errMsg = fmt.Sprintf("Invalid file compression") + case 1011: + errMsg = fmt.Sprintf("Data is corrupt or unavailable, %s now uses an older version of this file", System.Name) + case 1012: + errMsg = fmt.Sprintf("Invalid formatting of the time") + case 1013: + errMsg = fmt.Sprintf("Invalid settings file (%s), file must be at least version %s", System.File.Settings, System.Compatibility) + + case 1020: + errMsg = fmt.Sprintf("Data could not be saved, invalid keyword") + + // Datenbank Update + case 1030: + errMsg = fmt.Sprintf("Invalid settings file (%s)", System.File.Settings) + + // M3U Parser + case 1050: + errMsg = fmt.Sprintf("Invalid duration specification in the M3U8 playlist.") + + // M3U Parser + case 1060: + errMsg = fmt.Sprintf("Invalid characters found in the tvg parameters, streams with invalid parameters were skipped.") + + // Dateisystem + case 1070: + errMsg = fmt.Sprintf("Folder could not be created.") + case 1071: + errMsg = fmt.Sprintf("File could not be created") + + // Backup + case 1090: + errMsg = fmt.Sprintf("Automatic backup failed") + + // Websockets + case 1100: + errMsg = fmt.Sprintf("WebUI build error") + case 1101: + errMsg = fmt.Sprintf("WebUI request error") + case 1102: + errMsg = fmt.Sprintf("WebUI response error") + + // PMS Guide Numbers + case 1200: + errMsg = fmt.Sprintf("Could not create file") + + // Stream URL Fehler + case 1201: + errMsg = fmt.Sprintf("Plex stream error") + case 1202: + errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist") + case 1203: + errMsg = fmt.Sprintf("Steaming URL could not be found in any playlist") + + // Warnings + case 2000: + errMsg = fmt.Sprintf("Plex can not handle more than %d streams. If you do not use Plex, you can ignore this warning.", System.DVRLimit) + case 2001: + errMsg = fmt.Sprintf("%s has loaded more than %d streams. Use the filter to reduce the number of streams.", System.Name, System.DVRLimit) + case 2002: + errMsg = fmt.Sprintf("PMS can not play m3u8 streams") + case 2003: + errMsg = fmt.Sprintf("PMS can not play streams over RTSP.") + case 2004: + errMsg = fmt.Sprintf("Buffer is disabled for this stream.") + case 2005: + errMsg = fmt.Sprintf("There are no channels mapped, use the mapping menu to assign EPG data to the channels.") + case 2010: + errMsg = fmt.Sprintf("No valid streaming URL") + + case 2099: + errMsg = fmt.Sprintf("Updates have been disabled by the developer") + + // Tuner + case 2105: + errMsg = fmt.Sprintf("The number of tuners has changed, you have to delete " + System.Name + " in Plex / Emby HDHR and set it up again.") + case 2106: + errMsg = fmt.Sprintf("This function is only available with XEPG as EPG source") + + case 2110: + errMsg = fmt.Sprintf("Don't run this as Root!") + + case 2300: + errMsg = fmt.Sprintf("No channel logo found in the XMLTV or M3U file.") + case 2301: + errMsg = fmt.Sprintf("XMLTV file no longer available, channel has been deactivated.") + case 2302: + errMsg = fmt.Sprintf("Channel ID in the XMLTV file has changed. Channel has been deactivated.") + + // Benutzerauthentifizierung + case 3000: + errMsg = fmt.Sprintf("Database for user authentication could not be initialized.") + case 3001: + errMsg = fmt.Sprintf("The user has no authorization to load the channels.") + + // Buffer + case 4000: + errMsg = fmt.Sprintf("Connection to streaming source was interrupted.") + case 4001: + errMsg = fmt.Sprintf("Too many errors connecting to the provider. Streaming is canceled.") + case 4002: + errMsg = fmt.Sprintf("New URL for the redirect to the streaming server is missing") + case 4003: + errMsg = fmt.Sprintf("Server sends an incompatible content-type") + case 4004: + errMsg = fmt.Sprintf("This error message comes from the provider") + case 4005: + errMsg = fmt.Sprintf("Temporary buffer files could not be deleted") + + // Buffer (M3U8) + case 4050: + errMsg = fmt.Sprintf("Invalid M3U8 file") + case 4051: + errMsg = fmt.Sprintf("#EXTM3U header is missing") + + // Caching + case 4100: + errMsg = fmt.Sprintf("Unknown content type for downloaded image") + + // API + case 5000: + errMsg = fmt.Sprintf("Invalid API command") + + // Update Server + case 6001: + errMsg = fmt.Sprintf("Ivalid key") + case 6002: + errMsg = fmt.Sprintf("Update failed") + case 6003: + errMsg = fmt.Sprintf("Server not available") + case 6004: + errMsg = fmt.Sprintf("xTeVe update available") + + default: + errMsg = fmt.Sprintf("Unknown error / warning (%d)", errCode) + } + + return errMsg +} + +func addNotification(notification Notification) (err error) { + + var i int + var t = time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond)) + notification.Time = strconv.FormatInt(t, 10) + notification.New = true + + if len(notification.Headline) == 0 { + notification.Headline = strings.ToUpper(notification.Type) + } + + if len(System.Notification) == 0 { + System.Notification = make(map[string]Notification) + } + + System.Notification[notification.Time] = notification + + for key := range System.Notification { + + if i < len(System.Notification)-10 { + delete(System.Notification, key) + } + + i++ + + } + + return +} diff --git a/src/ssdp.go b/src/ssdp.go new file mode 100644 index 0000000..b1cc190 --- /dev/null +++ b/src/ssdp.go @@ -0,0 +1,69 @@ +package src + +import ( + "fmt" + "log" + "os" + "os/signal" + "time" + + "github.com/koron/go-ssdp" +) + +// SSDP : SSPD / DLNA Server +func SSDP() { + + showInfo(fmt.Sprintf("SSDP / DLNA:%t", Settings.SSDP)) + + if Settings.SSDP == false { + return + } + + time.Sleep(10 * time.Second) + ad, err := ssdp.Advertise( + "upnp:"+System.AppName, // send as "ST" + System.DeviceID+"::upnp:"+System.AppName, // send as "USN" + System.URLBase+"/device.xml", // send as "LOCATION" + System.AppName, // send as "SERVER" + 1800) // send as "maxAge" in "CACHE-CONTROL" + + if err != nil { + ShowError(err, 000) + } + + // Debug SSDP + if System.Flag.Debug == 3 { + ssdp.Logger = log.New(os.Stderr, "[SSDP] ", log.LstdFlags) + } + + var aliveTick <-chan time.Time + var ai = 10 + + if ai > 0 { + aliveTick = time.Tick(time.Duration(ai) * time.Second) + } else { + aliveTick = make(chan time.Time) + } + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt) + +loop: + + for { + + select { + + case <-aliveTick: + ad.Alive() + case <-quit: + os.Exit(0) + break loop + + } + + } + + ad.Bye() + ad.Close() +} diff --git a/src/struct-buffer.go b/src/struct-buffer.go new file mode 100644 index 0000000..1c8eedf --- /dev/null +++ b/src/struct-buffer.go @@ -0,0 +1,109 @@ +package src + +import "time" + +// Playlist : Enthält allen Playlistinformationen, die der Buffer benötigr +type Playlist struct { + Folder string + PlaylistID string + PlaylistName string + Tuner int + + Clients map[int]ThisClient + Streams map[int]ThisStream +} + +// ThisClient : Clientinfos +type ThisClient struct { + Connection int +} + +// ThisStream : Enthält Informationen zu dem abzuspielenden Stream einer Playlist +type ThisStream struct { + ChannelName string + Error string + Folder string + MD5 string + NetworkBandwidth int + PlaylistID string + PlaylistName string + Status bool + URL string + + Segment []Segment + + // Serverinformationen + Location string + URLFile string + URLHost string + URLPath string + URLRedirect string + URLScheme string + URLStreamingServer string + + // Wird nur für HLS / M3U8 verwendet + Body string + Difference float64 + Duration float64 + DynamicBandwidth bool + FirstSequence int64 + HLS bool + LastSequence int64 + M3U8URL string + NewSegCount int + OldSegCount int + Sequence int64 + TimeDiff float64 + TimeEnd time.Time + TimeSegDuration float64 + TimeStart time.Time + Version int + Wait float64 + + DynamicStream map[int]DynamicStream + + // Lokale Temp Datein + OldSegments []string +} + +// Segment : URL Segmente (HLS / M3U8) +type Segment struct { + Duration float64 + Info bool + Sequence int64 + URL string + Version int + Wait float64 + + StreamInf struct { + AverageBandwidth int + Bandwidth int + Framerate float64 + Resolution string + SegmentURL string + } +} + +// DynamicStream : Streaminformationen bei dynamischer Bandbreite +type DynamicStream struct { + AverageBandwidth int + Bandwidth int + Framerate float64 + Resolution string + URL string +} + +// ClientConnection : Client Verbindungen +type ClientConnection struct { + Connection int + Error error +} + +// BandwidthCalculation : Bandbreitenberechnung für den Stream +type BandwidthCalculation struct { + NetworkBandwidth int + Size int + Start time.Time + Stop time.Time + TimeDiff float64 +} diff --git a/src/struct-hdhr.go b/src/struct-hdhr.go new file mode 100644 index 0000000..f0376ce --- /dev/null +++ b/src/struct-hdhr.go @@ -0,0 +1,61 @@ +package src + +import "encoding/xml" + +// Capability : HDHR Capability XML +type Capability struct { + URLBase string `xml:"URLBase"` + XMLName xml.Name `xml:"root"` + Xmlns string `xml:"xmlns,attr"` + + SpecVersion struct { + Major int `xml:"major"` + Minor int `xml:"minor"` + } `xml:"specVersion"` + + Device struct { + DeviceType string `xml:"deviceType"` + FriendlyName string `xml:"friendlyName"` + Manufacturer string `xml:"manufacturer"` + ModelName string `xml:"modelName"` + ModelNumber string `xml:"modelNumber"` + SerialNumber string `xml:"serialNumber"` + UDN string `xml:"UDN"` + } `xml:"device"` +} + +// Discover : HDHR Discover /discover.json +type Discover struct { + BaseURL string `json:"BaseURL"` + DeviceAuth string `json:"DeviceAuth"` + DeviceID string `json:"DeviceID"` + FirmwareName string `json:"FirmwareName"` + FirmwareVersion string `json:"FirmwareVersion"` + FriendlyName string `json:"FriendlyName"` + LineupURL string `json:"LineupURL"` + Manufacturer string `json:"Manufacturer"` + ModelNumber string `json:"ModelNumber"` + TunerCount int `json:"TunerCount"` +} + +// LineupStatus : HDHR Lineup status /lineup_status.json +type LineupStatus struct { + ScanInProgress int `json:"ScanInProgress"` + ScanPossible int `json:"ScanPossible"` + Source string `json:"Source"` + SourceList []string `json:"SourceList"` +} + +// Lineup : HDHR Lineup /lineup.json +type Lineup []interface { + //GuideName string `json:"GuideName"` + //GuideNumber string `json:"GuideNumber"` + //URL string `json:"URL"` +} + +// LineupStream : HDHR einzelner Stream im Lineup +type LineupStream struct { + GuideName string `json:"GuideName"` + GuideNumber string `json:"GuideNumber"` + URL string `json:"URL"` +} diff --git a/src/struct-system.go b/src/struct-system.go new file mode 100644 index 0000000..e7eec01 --- /dev/null +++ b/src/struct-system.go @@ -0,0 +1,280 @@ +package src + +// SystemStruct : Beinhaltet alle Systeminformationen +type SystemStruct struct { + Addresses struct { + DVR string + M3U string + XML string + } + + APIVersion string + AppName string + ARCH string + Branch string + Build string + Compatibility string + ConfigurationWizard bool + Dev bool + DeviceID string + Domain string + DVRLimit int + + File struct { + Authentication string + M3U string + PMS string + Settings string + URLS string + XEPG string + XML string + } + + Flag struct { + Branch string + Debug int + Port string + SSDP bool + } + + Folder struct { + Backup string + Cache string + Config string + Data string + ImagesCache string + ImagesUpload string + Temp string + } + + Hostname string + ImageCachingInProgress int + IPAddress string + IPAddressesList []string + IPAddressesV4 []string + IPAddressesV6 []string + Name string + OS string + ScanInProgress int + TimeForAutoUpdate string + + Notification map[string]Notification + + ServerProtocol struct { + API string + DVR string + M3U string + WEB string + XML string + } + + GitHub struct { + Branch string + Repo string + Update bool + User string + } + + Update struct { + Git string + Name string + } + + URLBase string + Version string + WEB struct { + Menu []string + } +} + +// GitStruct : Updateinformationen von GitHub +type GitStruct struct { + Filename string `json:"filename"` + Version string `json:"version"` +} + +// DataStruct : Alle Daten werden hier abgelegt. (Lineup, XMLTV) +type DataStruct struct { + Cache struct { + ImagesCache []string + ImagesFiles []string + ImagesURLS []string + PMS map[string]string + + StreamingURLS map[string]StreamInfo + XMLTV map[string]XMLTV + + Streams struct { + Active []string + } + } + + Filter []Filter + + Playlist struct { + M3U struct { + Groups struct { + Text []string + Value []string + } + } + } + + StreamPreviewUI struct { + Active []string + Inactive []string + } + + Streams struct { + Active []interface{} + All []interface{} + Inactive []interface{} + } + + XMLTV struct { + Files []string + Mapping map[string]interface{} + } + + XEPG struct { + Channels map[string]interface{} + XEPGCount int64 + } +} + +// Filter : Wird für die Filterregeln verwendet +type Filter struct { + CaseSensitive bool + Rule string + Type string +} + +// XEPGChannelStruct : XEPG Struktur +type XEPGChannelStruct struct { + FileM3UID string `json:"_file.m3u.id,required"` + FileM3UName string `json:"_file.m3u.name,required"` + FileM3UPath string `json:"_file.m3u.path,required"` + GroupTitle string `json:"group-title,required"` + Name string `json:"name,required"` + TvgID string `json:"tvg-id,required"` + TvgLogo string `json:"tvg-logo,required"` + TvgName string `json:"tvg-name,required"` + URL string `json:"url,required"` + UUIDKey string `json:"_uuid.key,required"` + UUIDValue string `json:"_uuid.value,omitempty"` + Values string `json:"_values,required"` + XActive bool `json:"x-active,required"` + XCategory string `json:"x-category,required"` + XChannelID string `json:"x-channelID,required"` + XEPG string `json:"x-epg,required"` + XGroupTitle string `json:"x-group-title,required"` + XMapping string `json:"x-mapping,required"` + XmltvFile string `json:"x-xmltv-file,required"` + XName string `json:"x-name,required"` + XUpdateChannelIcon bool `json:"x-update-channel-icon,required"` + XUpdateChannelName bool `json:"x-update-channel-name,required"` +} + +// M3UChannelStructXEPG : M3U Struktur für XEPG +type M3UChannelStructXEPG struct { + FileM3UID string `json:"_file.m3u.id,required"` + FileM3UName string `json:"_file.m3u.name,required"` + FileM3UPath string `json:"_file.m3u.path,required"` + GroupTitle string `json:"group-title,required"` + Name string `json:"name,required"` + TvgID string `json:"tvg-id,required"` + TvgLogo string `json:"tvg-logo,required"` + TvgName string `json:"tvg-name,required"` + URL string `json:"url,required"` + UUIDKey string `json:"_uuid.key,required"` + UUIDValue string `json:"_uuid.value,required"` + Values string `json:"_values,required"` +} + +// FilterStruct : Filter Struktur +type FilterStruct struct { + Active bool `json:"active,required"` + CaseSensitive bool `json:"caseSensitive,required"` + Description string `json:"description,required"` + Exclude string `json:"exclude,required"` + Filter string `json:"filter,required"` + Include string `json:"include,required"` + Name string `json:"name,required"` + Rule string `json:"rule,omitempty"` + Type string `json:"type,required"` +} + +// StreamingURLS : Informationen zu allen streaming URL's +type StreamingURLS struct { + Streams map[string]StreamInfo `json:"channels,required"` +} + +// StreamInfo : Informationen zum Kanal für die streaming URL +type StreamInfo struct { + ChannelNumber string `json:"channelNumber,required"` + Name string `json:"name,required"` + PlaylistID string `json:"playlistID,required"` + URL string `json:"url,required"` + URLid string `json:"urlID,required"` +} + +// Notification : Notifikationen im Webinterface +type Notification struct { + Headline string `json:"headline,required"` + Message string `json:"message,required"` + New bool `json:"new,required"` + Time string `json:"time,required"` + Type string `json:"type,required"` +} + +// SettingsStrcut : Inhalt der settings.json +type SettingsStrcut struct { + API bool `json:"api"` + AuthenticationAPI bool `json:"authentication.api"` + AuthenticationM3U bool `json:"authentication.m3u"` + AuthenticationPMS bool `json:"authentication.pms"` + AuthenticationWEB bool `json:"authentication.web"` + AuthenticationXML bool `json:"authentication.xml"` + BackupKeep int `json:"backup.keep"` + BackupPath string `json:"backup.path"` + Branch string `json:"git.branch,omitempty"` + Buffer bool `json:"buffer"` + BufferSize int `json:"buffer.size.kb"` + BufferTimeout float64 `json:"buffer.timeout"` + CacheImages bool `json:"cache.images"` + EpgSource string `json:"epgSource"` + FileM3U []string `json:"file,omitempty"` // Beim Wizard wird die M3U in ein Slice gespeichert + FileXMLTV []string `json:"xmltv,omitempty"` // Altes Speichersystem der Provider XML Datei Slice (Wird für die Umwandlung auf das neue benötigt) + + Files struct { + HDHR map[string]interface{} `json:"hdhr"` + M3U map[string]interface{} `json:"m3u"` + XMLTV map[string]interface{} `json:"xmltv"` + } `json:"files"` + + FilesUpdate bool `json:"files.update"` + Filter map[int64]interface{} `json:"filter"` + Key string `json:"key,omitempty"` + Language string `json:"language"` + LogEntriesRAM int `json:"log.entries.ram"` + M3U8AdaptiveBandwidthMBPS int `json:"m3u8.adaptive.bandwidth.mbps"` + MappingFirstChannel float64 `json:"mapping.first.channel"` + Port string `json:"port"` + SSDP bool `json:"ssdp"` + TempPath string `json:"temp.path"` + Tuner int `json:"tuner"` + Update []string `json:"update"` + UpdateURL string `json:"update.url,omitempty"` + UserAgent string `json:"user.agent"` + UUID string `json:"uuid"` + Version string `json:"version"` + XepgReplaceMissingImages bool `json:"xepg.replace.missing.images"` + XteveAutoUpdate bool `json:"xteveAutoUpdate"` +} + +// LanguageUI : Sprache für das WebUI +type LanguageUI struct { + Login struct { + Failed string + } +} diff --git a/src/struct-webserver.go b/src/struct-webserver.go new file mode 100644 index 0000000..c101680 --- /dev/null +++ b/src/struct-webserver.go @@ -0,0 +1,145 @@ +package src + +// RequestStruct : Anfragen über die Websocket Schnittstelle +type RequestStruct struct { + // Befehle an xTeVe + Cmd string `json:"cmd,required"` + + // Benutzer + DeleteUser bool `json:"deleteUser,omitempty"` + UserData map[string]interface{} `json:"userData,omitempty"` + + // Mapping + EpgMapping map[string]interface{} `json:"epgMapping,omitempty"` + + // Restore + Base64 string `json:"base64,omitempty"` + + // Neue Werte für die Einstellungen (settings.json) + Settings struct { + API *bool `json:"api,omitempty"` + AuthenticationAPI *bool `json:"authentication.api,omitempty"` + AuthenticationM3U *bool `json:"authentication.m3u,omitempty"` + AuthenticationPMS *bool `json:"authentication.pms,omitempty"` + AuthenticationWEP *bool `json:"authentication.web,omitempty"` + AuthenticationXML *bool `json:"authentication.xml,omitempty"` + BackupKeep *int `json:"backup.keep,omitempty"` + BackupPath *string `json:"backup.path,omitempty"` + Buffer *bool `json:"buffer,omitempty"` + BufferSize *int `json:"buffer.size.kb, omitempty"` + BufferTimeout *float64 `json:"buffer.timeout,omitempty"` + CacheImages *bool `json:"cache.images,omitempty"` + EpgSource *string `json:"epgSource,omitempty"` + FilesUpdate *bool `json:"files.update,omitempty"` + TempPath *string `json:"temp.path,omitempty"` + Tuner *int `json:"tuner,omitempty"` + Update *[]string `json:"update,omitempty"` + UserAgent *string `json:"user.agent,omitempty"` + XepgReplaceMissingImages *bool `json:"xepg.replace.missing.images,omitempty"` + XteveAutoUpdate *bool `json:"xteveAutoUpdate,omitempty"` + } `json:"settings,omitempty"` + + // Upload Logo + Filename string `json:"filename,omitempty"` + + // Filter + Filter map[int64]interface{} `json:"filter,omitempty"` + + // Dateien (M3U, HDHR, XMLTV) + Files struct { + HDHR map[string]interface{} `json:"hdhr,omitempty"` + M3U map[string]interface{} `json:"m3u,omitempty"` + XMLTV map[string]interface{} `json:"xmltv,omitempty"` + } `json:"files,omitempty"` + + // Wizard + Wizard struct { + EpgSource *string `json:"epgSource,omitempty"` + M3U *string `json:"m3u,omitempty"` + Tuner *int `json:"tuner,omitempty"` + XMLTV *string `json:"xmltv,omitempty"` + } `json:"wizard,omitempty"` +} + +// ResponseStruct : Antworten an den Client (WEB) +type ResponseStruct struct { + ClientInfo struct { + ARCH string `json:"arch"` + Branch string `json:"branch,omitempty"` + DVR string `json:"DVR"` + EpgSource string `json:"epgSource"` + Errors int `json:"errors"` + M3U string `json:"m3u-url,required"` + OS string `json:"os"` + Streams string `json:"streams"` + UUID string `json:"uuid"` + Version string `json:"version"` + Warnings int `json:"warnings"` + XEPGCount int64 `json:"xepg"` + XML string `json:"xepg-url,required"` + } `json:"clientInfo,omitempty"` + + Data struct { + Playlist struct { + M3U struct { + Groups struct { + Text []string `json:"text,required"` + Value []string `json:"value,required"` + } `json:"groups,required"` + } `json:"m3u,required"` + } `json:"playlist,required"` + + StreamPreviewUI struct { + Active []string `json:"activeStreams,required"` + Inactive []string `json:"inactiveStreams,required"` + } + } `json:"data,required"` + + Alert string `json:"alert,omitempty"` + ConfigurationWizard bool `json:"configurationWizard,required"` + Error string `json:"err,omitempty"` + Log WebScreenLogStruct `json:"log,required"` + LogoURL string `json:"logoURL,omitempty"` + OpenLink string `json:"openLink,omitempty"` + OpenMenu string `json:"openMenu,omitempty"` + Reload bool `json:"reload,omitempty"` + Settings SettingsStrcut `json:"settings,required"` + Status bool `json:"status,required"` + Token string `json:"token,omitempty"` + Users map[string]interface{} `json:"users,omitempty"` + Wizard int `json:"wizard,omitempty"` + XEPG map[string]interface{} `json:"xepg,required"` + + Notification map[string]Notification `json:"notification,omitempty"` +} + +// APIRequestStruct : Anfrage über die API Schnittstelle +type APIRequestStruct struct { + Cmd string `json:"cmd"` + Password string `json:"password"` + Token string `json:"token"` + Username string `json:"username"` +} + +// APIResponseStruct : Antwort an den Client (API) +type APIResponseStruct struct { + EpgSource string `json:"epg.source,omitempty"` + Error string `json:"err,omitempty"` + Status bool `json:"status,required"` + StreamsActive int64 `json:"streams.active,omitempty"` + StreamsAll int64 `json:"streams.all,omitempty"` + StreamsXepg int64 `json:"streams.xepg,omitempty"` + Token string `json:"token,omitempty"` + URLDvr string `json:"url.dvr,omitempty"` + URLM3U string `json:"url.m3u,omitempty"` + URLXepg string `json:"url.xepg,omitempty"` + VersionAPI string `json:"version.api,omitempty"` + VersionXteve string `json:"version.xteve,omitempty"` +} + +// WebScreenLogStruct : Logs werden im RAM gespeichert und für das Webinterface bereitgestellt +type WebScreenLogStruct struct { + Errors int `json:"errors,required"` + Log []string `json:"log,required"` + Warnings int `json:"warnings,required"` +} diff --git a/src/struct-xml.go b/src/struct-xml.go new file mode 100644 index 0000000..f22c2f0 --- /dev/null +++ b/src/struct-xml.go @@ -0,0 +1,123 @@ +package src + +import "encoding/xml" + +// XMLTV : XMLTV Datei +type XMLTV struct { + Generator string `xml:"generator-info-name,attr"` + Source string `xml:"source-info-name,attr"` + XMLName xml.Name `xml:"tv"` + + Channel []*Channel `xml:"channel"` + Program []*Program `xml:"programme"` +} + +// Channel : Kanäle +type Channel struct { + ID string `xml:"id,attr"` + DisplayName []DisplayName `xml:"display-name"` + Icon Icon `xml:"icon"` +} + +// DisplayName : Kanalname +type DisplayName struct { + Value string `xml:",chardata"` +} + +// Icon : Senderlogo +type Icon struct { + Src string `xml:"src,attr"` +} + +// Program : Programme +type Program struct { + Channel string `xml:"channel,attr"` + Start string `xml:"start,attr"` + Stop string `xml:"stop,attr"` + + Title []*Title `xml:"title"` + SubTitle []*SubTitle `xml:"sub-title"` + Desc []*Desc `xml:"desc"` + Category []*Category `xml:"category"` + Country []*Country `xml:"country"` + EpisodeNum []*EpisodeNum `xml:"episode-num"` + Poster []Poster `xml:"icon"` + Language []*Language `xml:"language"` + Video Video `xml:"video"` + Date string `xml:"date"` + PreviouslyShown *PreviouslyShown `xml:"previously-shown"` + New *New `xml:"new"` + Live *Live `xml:"live"` +} + +// Title : Programmtitel +type Title struct { + Lang string `xml:"lang,attr"` + Value string `xml:",chardata"` +} + +// SubTitle : Kurzbeschreibung +type SubTitle struct { + Lang string `xml:"lang,attr"` + Value string `xml:",chardata"` +} + +//Desc : Programmbeschreibung +type Desc struct { + Lang string `xml:"lang,attr"` + Value string `xml:",chardata"` +} + +// Category : Kategorien +type Category struct { + Lang string `xml:"lang,attr"` + Value string `xml:",chardata"` +} + +// Language : Sprachen +type Language struct { + Value string `xml:",chardata"` +} + +// Country : Länder +type Country struct { + Lang string `xml:"lang,attr"` + Value string `xml:",chardata"` +} + +// EpisodeNum : Episodennummerierung +type EpisodeNum struct { + System string `xml:"system,attr"` + Value string `xml:",chardata"` +} + +// Poster : Programmposter / Cover +type Poster struct { + Height string `xml:"height,attr"` + Src string `xml:"src,attr"` + Value string `xml:",chardata"` + Width string `xml:"width,attr"` +} + +// Video : Video Metadaten +type Video struct { + Aspect string `xml:"aspect,omitempty"` + Colour string `xml:"colour,omitempty"` + Present string `xml:"present,omitempty"` + Quality string `xml:"quality,omitempty"` +} + +// PreviouslyShown : Widerholung bzw. Erstausstrahlung +type PreviouslyShown struct { + Start string `xml:"start,attr"` +} + +// New : Sendung als neu deklarieren +type New struct { + Value string `xml:",chardata"` +} + +// Live : Sendung als Liveübertragung deklarieren +type Live struct { + Value string `xml:",chardata"` +} diff --git a/src/system.go b/src/system.go new file mode 100644 index 0000000..fd45c69 --- /dev/null +++ b/src/system.go @@ -0,0 +1,323 @@ +package src + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "reflect" + "strings" + "time" +) + +// Entwicklerinfos anzeigen +func showDevInfo() { + + if System.Dev == true { + + fmt.Print("\033[31m") + fmt.Println("* * * * * D E V M O D E * * * * *") + fmt.Println("Version: ", System.Version) + fmt.Println("Build: ", System.Build) + fmt.Println("* * * * * * * * * * * * * * * * * *") + fmt.Print("\033[0m") + fmt.Println() + + } + + return +} + +// Alle Systemordner erstellen +func createSystemFolders() (err error) { + + e := reflect.ValueOf(&System.Folder).Elem() + + for i := 0; i < e.NumField(); i++ { + + var folder = e.Field(i).Interface().(string) + + err = checkFolder(folder) + + if err != nil { + return + } + + } + + return +} + +// Alle Systemdateien erstellen +func createSystemFiles() (err error) { + + var debug string + for _, file := range SystemFiles { + + var filename = getPlatformFile(System.Folder.Config + file) + + err = checkFile(filename) + if err != nil { + // Datei existiert nicht, wird jetzt erstellt + err = saveMapToJSONFile(filename, make(map[string]interface{})) + if err != nil { + return + } + + debug = fmt.Sprintf("Create File:%s", filename) + showDebug(debug, 1) + + } + + switch file { + + case "authentication.json": + System.File.Authentication = filename + case "pms.json": + System.File.PMS = filename + case "settings.json": + System.File.Settings = filename + case "xepg.json": + System.File.XEPG = filename + case "urls.json": + System.File.URLS = filename + + } + + } + + return +} + +// Einstellungen laden und default Werte setzen (xTeVe) +func loadSettings() (settings SettingsStrcut, err error) { + + settingsMap, err := loadJSONFileToMap(System.File.Settings) + if err != nil { + return + } + + // Deafult Werte setzten + var defaults = make(map[string]interface{}) + var dataMap = make(map[string]interface{}) + + dataMap["xmltv"] = make(map[string]interface{}) + dataMap["m3u"] = make(map[string]interface{}) + dataMap["hdhr"] = make(map[string]interface{}) + + defaults["api"] = false + defaults["authentication.api"] = false + defaults["authentication.m3u"] = false + defaults["authentication.pms"] = false + defaults["authentication.web"] = false + defaults["authentication.xml"] = false + defaults["backup.keep"] = 10 + defaults["backup.path"] = System.Folder.Backup + defaults["buffer"] = false + defaults["buffer.size.kb"] = 1024 + defaults["buffer.timeout"] = 500 + defaults["cache.images"] = false + defaults["epgSource"] = "XEPG" + defaults["files"] = dataMap + defaults["files.update"] = true + defaults["filter"] = make(map[string]interface{}) + defaults["git.branch"] = System.Branch + defaults["language"] = "en" + defaults["log.entries.ram"] = 500 + defaults["mapping.first.channel"] = 1000 + defaults["xepg.replace.missing.images"] = true + defaults["m3u8.adaptive.bandwidth.mbps"] = 10 + defaults["port"] = "34400" + defaults["ssdp"] = true + defaults["tuner"] = 1 + defaults["update"] = []string{"0000"} + defaults["user.agent"] = System.Name + defaults["uuid"] = createUUID() + defaults["version"] = System.Version + defaults["xteveAutoUpdate"] = true + defaults["temp.path"] = System.Folder.Temp + + // Default Werte setzen + for key, value := range defaults { + if _, ok := settingsMap[key]; !ok { + settingsMap[key] = value + } + } + + err = json.Unmarshal([]byte(mapToJSON(settingsMap)), &settings) + if err != nil { + return + } + + // Einstellungen von den Flags übernehmen + if len(System.Flag.Port) > 0 { + settings.Port = System.Flag.Port + } + + if len(System.Flag.Branch) > 0 { + settings.Branch = System.Flag.Branch + showInfo(fmt.Sprintf("Git Branch:Switching Git Branch to -> %s", settings.Branch)) + } + + err = saveSettings(settings) + + return +} + +// Einstellungen speichern (xTeVe) +func saveSettings(settings SettingsStrcut) (err error) { + + if settings.BackupKeep == 0 { + settings.BackupKeep = 10 + } + + if len(settings.BackupPath) == 0 { + settings.BackupPath = System.Folder.Backup + } + + if settings.BufferTimeout < 0 { + settings.BufferTimeout = 0 + } + + System.Folder.Temp = settings.TempPath + settings.UUID + string(os.PathSeparator) + + err = writeByteToFile(System.File.Settings, []byte(mapToJSON(settings))) + if err != nil { + return + } + + Settings = settings + + if System.Dev == true { + Settings.UUID = "2019-01-DEV-xTeVe!" + } + + setDeviceID() + + return +} + +// Zugriff über die Domain ermöglichen +func setGlobalDomain(domain string) { + + System.Domain = domain + + switch Settings.AuthenticationPMS { + case true: + System.Addresses.DVR = "username:password@" + System.Domain + case false: + System.Addresses.DVR = System.Domain + } + + switch Settings.AuthenticationM3U { + case true: + System.Addresses.M3U = System.ServerProtocol.M3U + "://" + System.Domain + "/m3u/xteve.m3u?username=xxx&password=yyy
(Specific groups: [http://...&group-title=foo,bar])" + case false: + System.Addresses.M3U = System.ServerProtocol.M3U + "://" + System.Domain + "/m3u/xteve.m3u (Specific groups: [http://...?group-title=foo,bar])" + } + + switch Settings.AuthenticationXML { + case true: + System.Addresses.XML = System.ServerProtocol.XML + "://" + System.Domain + "/xmltv/xteve.xml?username=xxx&password=yyy" + case false: + System.Addresses.XML = System.ServerProtocol.XML + "://" + System.Domain + "/xmltv/xteve.xml" + } + + if Settings.EpgSource != "XEPG" { + System.Addresses.M3U = getErrMsg(2106) + System.Addresses.XML = getErrMsg(2106) + } + + return +} + +// UUID generieren +func createUUID() (uuid string) { + uuid = time.Now().Format("2006-01") + "-" + randomString(4) + "-" + randomString(6) + return +} + +// Eindeutige Geräte ID für Plex generieren +func setDeviceID() { + + var id = Settings.UUID + + switch Settings.Tuner { + case 1: + System.DeviceID = id + + default: + System.DeviceID = fmt.Sprintf("%s:%d", id, Settings.Tuner) + } + + return +} + +// Provider Streaming-URL zu xTeVe Streaming-URL konvertieren +func createStreamingURL(streamingType, playlistID, channelNumber, channelName, url string) (streamingURL string, err error) { + + var streamInfo StreamInfo + var serverProtocol string + + if len(Data.Cache.StreamingURLS) == 0 { + Data.Cache.StreamingURLS = make(map[string]StreamInfo) + } + + var urlID = getMD5(fmt.Sprintf("%s-%s", playlistID, url)) + + if s, ok := Data.Cache.StreamingURLS[urlID]; ok { + + streamInfo = s + + } else { + + streamInfo.URL = url + streamInfo.Name = channelName + streamInfo.PlaylistID = playlistID + streamInfo.ChannelNumber = channelNumber + streamInfo.URLid = urlID + + Data.Cache.StreamingURLS[urlID] = streamInfo + + } + + switch streamingType { + + case "DVR": + serverProtocol = System.ServerProtocol.DVR + + case "M3U": + serverProtocol = System.ServerProtocol.M3U + + } + + streamingURL = fmt.Sprintf("%s://%s/stream/%s", serverProtocol, System.Domain, streamInfo.URLid) + + return +} + +func getStreamInfo(urlID string) (streamInfo StreamInfo, err error) { + + if len(Data.Cache.StreamingURLS) == 0 { + + tmp, err := loadJSONFileToMap(System.File.URLS) + if err != nil { + return streamInfo, err + } + + err = json.Unmarshal([]byte(mapToJSON(tmp)), &Data.Cache.StreamingURLS) + if err != nil { + return streamInfo, err + } + + } + + if s, ok := Data.Cache.StreamingURLS[urlID]; ok { + streamInfo = s + streamInfo.URL = strings.Trim(streamInfo.URL, "\r\n") + } else { + err = errors.New("Streaming error") + } + + return +} diff --git a/src/toolchain.go b/src/toolchain.go new file mode 100644 index 0000000..678a463 --- /dev/null +++ b/src/toolchain.go @@ -0,0 +1,356 @@ +package src + +import ( + "bytes" + "crypto/md5" + "crypto/rand" + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "os" + "os/user" + "path/filepath" + "text/template" +) + +// --- System Tools --- + +// Prüft ob der Ordner existiert, falls nicht, wir der Ordner erstellt +func checkFolder(path string) (err error) { + + var debug string + _, err = os.Stat(filepath.Dir(path)) + + if os.IsNotExist(err) { + // Ordner existiert nicht, wird jetzt erstellt + + err = os.MkdirAll(getPlatformPath(path), 0755) + if err == nil { + + debug = fmt.Sprintf("Create Folder:%s", path) + showDebug(debug, 1) + + } else { + return err + } + + return nil + } + + return nil +} + +// Prüft ob die datei im Dateisystem existiert +func checkFile(filename string) (err error) { + + var file = getPlatformFile(filename) + + if _, err = os.Stat(file); os.IsNotExist(err) { + return + } + + return +} + +// GetUserHomeDirectory : Benutzer Homer Verzeichnis +func GetUserHomeDirectory() (userHomeDirectory string) { + + usr, err := user.Current() + + if err != nil { + + for _, name := range []string{"HOME", "USERPROFILE"} { + + if dir := os.Getenv(name); dir != "" { + userHomeDirectory = dir + break + } + + } + + } else { + userHomeDirectory = usr.HomeDir + } + + return +} + +func checkFilePermission(dir string) (err error) { + + var filename = dir + "permission.test" + + err = ioutil.WriteFile(filename, []byte(""), 0644) + if err == nil { + err = os.RemoveAll(filename) + } + + return +} + +// Ordnerpfad für das laufende OS generieren +func getPlatformPath(path string) string { + return filepath.Dir(path) + string(os.PathSeparator) +} + +// Dateipfad für das laufende OS generieren +func getPlatformFile(filename string) (osFilePath string) { + + path, file := filepath.Split(filename) + var newPath = filepath.Dir(path) + osFilePath = newPath + string(os.PathSeparator) + file + + return +} + +// Dateinamen aus dem Dateipfad ausgeben +func getFilenameFromPath(path string) (file string) { + return filepath.Base(path) +} + +// Nicht mehr verwendete Systemdaten löschen +func removeOldSystemData() { + // Temporären Ordner löschen + os.RemoveAll(System.Folder.Temp) +} + +// +func removeChildItems(dir string) error { + + files, err := filepath.Glob(filepath.Join(dir, "*")) + if err != nil { + return err + } + + for _, file := range files { + + err = os.RemoveAll(file) + if err != nil { + return err + } + + } + + return nil +} + +// JSON +func mapToJSON(tmpMap interface{}) string { + + jsonString, err := json.MarshalIndent(tmpMap, "", " ") + if err != nil { + return "{}" + } + + return string(jsonString) +} + +func jsonToMap(content string) map[string]interface{} { + + var tmpMap = make(map[string]interface{}) + json.Unmarshal([]byte(content), &tmpMap) + + return (tmpMap) +} + +func jsonToMapInt64(content string) map[int64]interface{} { + + var tmpMap = make(map[int64]interface{}) + json.Unmarshal([]byte(content), &tmpMap) + + return (tmpMap) +} + +func jsonToInterface(content string) (tmpMap interface{}, err error) { + + err = json.Unmarshal([]byte(content), &tmpMap) + return + +} + +func saveMapToJSONFile(file string, tmpMap interface{}) error { + + var filename = getPlatformFile(file) + jsonString, err := json.MarshalIndent(tmpMap, "", " ") + + if err != nil { + return err + } + + err = ioutil.WriteFile(filename, []byte(jsonString), 0644) + if err != nil { + return err + } + + return nil +} + +func loadJSONFileToMap(file string) (tmpMap map[string]interface{}, err error) { + + f, err := os.Open(getPlatformFile(file)) + defer f.Close() + + content, err := ioutil.ReadAll(f) + + if err == nil { + err = json.Unmarshal([]byte(content), &tmpMap) + } + + f.Close() + + return +} + +// Binary +func readByteFromFile(file string) (content []byte, err error) { + + f, err := os.Open(getPlatformFile(file)) + defer f.Close() + + content, err = ioutil.ReadAll(f) + f.Close() + + return +} + +func writeByteToFile(file string, data []byte) (err error) { + + var filename = getPlatformFile(file) + err = ioutil.WriteFile(filename, data, 0644) + + return +} + +func readStringFromFile(file string) (str string, err error) { + + var content []byte + var filename = getPlatformFile(file) + + err = checkFile(filename) + if err != nil { + return + } + + content, err = ioutil.ReadFile(filename) + if err != nil { + ShowError(err, 0) + return + } + + str = string(content) + + return +} + +// Netzwerk +func resolveHostIP() (err error) { + + netInterfaceAddresses, err := net.InterfaceAddrs() + if err != nil { + return + } + + for _, netInterfaceAddress := range netInterfaceAddresses { + + networkIP, ok := netInterfaceAddress.(*net.IPNet) + System.IPAddressesList = append(System.IPAddressesList, networkIP.IP.String()) + + if ok { + + var ip = networkIP.IP.String() + + if networkIP.IP.To4() != nil { + + System.IPAddressesV4 = append(System.IPAddressesV4, ip) + + if !networkIP.IP.IsLoopback() && ip[0:7] != "169.254" { + System.IPAddress = ip + } + + } else { + System.IPAddressesV6 = append(System.IPAddressesV6, ip) + } + + } + + } + + System.Hostname, err = os.Hostname() + if err != nil { + return + } + + return +} + +// Sonstiges +func randomString(n int) string { + + const alphanum = "AB1CD2EF3GH4IJ5KL6MN7OP8QR9ST0UVWXYZ" + + var bytes = make([]byte, n) + + rand.Read(bytes) + + for i, b := range bytes { + bytes[i] = alphanum[b%byte(len(alphanum))] + } + + return string(bytes) +} + +func parseTemplate(content string, tmpMap map[string]interface{}) (result string) { + + t := template.Must(template.New("template").Parse(content)) + + var tpl bytes.Buffer + + if err := t.Execute(&tpl, tmpMap); err != nil { + ShowError(err, 0) + } + result = tpl.String() + + return +} + +func indexOfString(element string, data []string) int { + + for k, v := range data { + if element == v { + return k + } + } + + return -1 +} + +func indexOfFloat64(element float64, data []float64) int { + + for k, v := range data { + if element == v { + return (k) + } + } + + return -1 +} + +func indexOfInt(element int, data []int) int { + + for k, v := range data { + if element == v { + return (k) + } + } + + return -1 +} + +func getMD5(str string) string { + + md5Hasher := md5.New() + md5Hasher.Write([]byte(str)) + + return hex.EncodeToString(md5Hasher.Sum(nil)) +} diff --git a/src/update.go b/src/update.go new file mode 100644 index 0000000..8125804 --- /dev/null +++ b/src/update.go @@ -0,0 +1,273 @@ +package src + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + + up2date "../src/internal/up2date/client" + + "reflect" +) + +// BinaryUpdate : Binary Update Prozess. Git Branch master und beta wird von GitHub geladen. +func BinaryUpdate() (err error) { + + if System.GitHub.Update == false { + showWarning(2099) + return + } + + var debug string + + var updater = &up2date.Updater + updater.Name = System.Update.Name + updater.Branch = System.Branch + + up2date.Init() + + switch System.Branch { + + // Update von GitHub + case "master", "beta": + + var gitInfo = fmt.Sprintf("%s/%s/info.json?raw=true", System.Update.Git, System.Branch) + var zipFile = fmt.Sprintf("%s/%s/%s_%s_%s.zip?raw=true", System.Update.Git, System.Branch, System.AppName, System.OS, System.ARCH) + var body []byte + + var git GitStruct + + resp, err := http.Get(gitInfo) + if err != nil { + ShowError(err, 0) + return err + } + + if resp.StatusCode != http.StatusOK { + + if resp.StatusCode == 404 { + err = fmt.Errorf(fmt.Sprintf("Update Server: %s (%s)", http.StatusText(resp.StatusCode), gitInfo)) + ShowError(err, 6003) + return nil + } + + err = fmt.Errorf(fmt.Sprintf("%d: %s (%s)", resp.StatusCode, http.StatusText(resp.StatusCode), gitInfo)) + + return err + } + + body, err = ioutil.ReadAll(resp.Body) + + err = json.Unmarshal(body, &git) + if err != nil { + return err + } + + updater.Response.Status = true + updater.Response.UpdateZIP = zipFile + updater.Response.Version = git.Version + updater.Response.Filename = git.Filename + + // Update vom eigenen Server + default: + + updater.URL = Settings.UpdateURL + + if len(updater.URL) == 0 { + showInfo(fmt.Sprintf("Update URL:No server URL specified, update will not be performed. Branch: %s", System.Branch)) + return + } + + showInfo("Update URL:" + updater.URL) + fmt.Println("-----------------") + + // Versionsinformationen vom Server laden + err = up2date.GetVersion() + if err != nil { + + debug = fmt.Sprintf(err.Error()) + showDebug(debug, 1) + + return nil + } + + if len(updater.Response.Reason) > 0 { + + err = fmt.Errorf(fmt.Sprintf("Update Server: %s", updater.Response.Reason)) + ShowError(err, 6002) + + return nil + } + + } + + var currentVersion = System.Version + "." + System.Build + + // Versionsnummer überprüfen + if updater.Response.Version > currentVersion && updater.Response.Status == true { + + if Settings.XteveAutoUpdate == true { + // Update durchführen + var fileType, url string + + showInfo(fmt.Sprintf("Update Available:Version: %s", updater.Response.Version)) + + switch System.Branch { + + // Update von GitHub + case "master", "beta": + showInfo(fmt.Sprintf("Update Server:GitHub")) + + // Update vom eigenen Server + default: + showInfo(fmt.Sprintf("Update Server:%s", Settings.UpdateURL)) + + } + + showInfo(fmt.Sprintf("Start Update:Branch: %s", updater.Branch)) + + // Neue Version als BIN Datei herunterladen + if len(updater.Response.UpdateBIN) > 0 { + url = updater.Response.UpdateBIN + fileType = "bin" + } + + // Neue Version als ZIP Datei herunterladen + if len(updater.Response.UpdateZIP) > 0 { + url = updater.Response.UpdateZIP + fileType = "zip" + } + + if len(url) > 0 { + + err = up2date.DoUpdate(fileType, updater.Response.Filename) + if err != nil { + ShowError(err, 6002) + } + + } + + } else { + // Hinweis ausgeben + showWarning(6004) + } + + } + + return nil +} + +func conditionalUpdateChanges() (err error) { + +checkVersion: + settingsMap, err := loadJSONFileToMap(System.File.Settings) + if err != nil || len(settingsMap) == 0 { + return + } + + if settingsVersion, ok := settingsMap["version"].(string); ok { + + // Letzte Kompatible Version (1.4.4) + if settingsVersion < System.Compatibility { + err = errors.New(getErrMsg(1013)) + return + } + + switch settingsVersion { + + case "1.4.4": + // UUID Wert in xepg.json setzen + err = setValueForUUID() + if err != nil { + return + } + + // Neuer Filter (WebUI). Alte Filtereinstellungen werden konvertiert + if oldFilter, ok := settingsMap["filter"].([]interface{}); ok { + var newFilterMap = convertToNewFilter(oldFilter) + settingsMap["filter"] = newFilterMap + + settingsMap["version"] = "1.9.0" + + err = saveMapToJSONFile(System.File.Settings, settingsMap) + if err != nil { + return + } + + goto checkVersion + + } else { + err = errors.New(getErrMsg(1030)) + return + } + + case "1.9.0": + // Falls es in einem späteren Update Änderungen an der Datenbank gibt, geht es hier weiter + + break + + } + + } else { + // settings.json ist zu alt (älter als Version 1.4.4) + err = errors.New(getErrMsg(1013)) + } + + return +} + +func convertToNewFilter(oldFilter []interface{}) (newFilterMap map[int]interface{}) { + + newFilterMap = make(map[int]interface{}) + + switch reflect.TypeOf(oldFilter).Kind() { + + case reflect.Slice: + s := reflect.ValueOf(oldFilter) + + for i := 0; i < s.Len(); i++ { + + var newFilter FilterStruct + newFilter.Active = true + newFilter.Name = fmt.Sprintf("Custom filter %d", i+1) + newFilter.Filter = s.Index(i).Interface().(string) + newFilter.Type = "custom-filter" + newFilter.CaseSensitive = false + + newFilterMap[i] = newFilter + + } + + } + + return +} + +func setValueForUUID() (err error) { + + xepg, err := loadJSONFileToMap(System.File.XEPG) + + for _, c := range xepg { + + var xepgChannel = c.(map[string]interface{}) + + if uuidKey, ok := xepgChannel["_uuid.key"].(string); ok { + + if value, ok := xepgChannel[uuidKey].(string); ok { + + if len(value) > 0 { + xepgChannel["_uuid.value"] = value + } + + } + + } + + } + + err = saveMapToJSONFile(System.File.XEPG, xepg) + + return +} diff --git a/src/webUI.go b/src/webUI.go new file mode 100644 index 0000000..40b47b4 --- /dev/null +++ b/src/webUI.go @@ -0,0 +1,54 @@ +package src + +var webUI = make(map[string]interface{}) + +func loadHTMLMap() { + + webUI["html/img/m3u.png"] = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAsSwAALEsBpT2WqQAABCRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjU8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjI4ODwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+Mjg4PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NTA8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGRjOnN1YmplY3Q+CiAgICAgICAgICAgIDxyZGY6QmFnLz4KICAgICAgICAgPC9kYzpzdWJqZWN0PgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0wNy0yOFQxOTowNzozMTwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CumjVbcAAAGWSURBVGgF7VoxTgJRFGTFaGKBFnbEcABjb0fiBego7D2ABYmn8ARKRWFNQ6gx4QRQGWJJoY2VhXGdl7Dkh7Dsx3ns3yXvJ5P97L6d92bmhwYqlcWK43gK5L2enP51NP8F7pN721wPnOITZx9qG6HxI8QIZO+9XCHeL+VQKKm8QMyxb6+iCpH528AQYs58xIQWknV8mhDxCjEXWWJCC8maT55fAmOIudpUXAYhMn8dGEHMTZqYsgiR+U+BAcTcrhNTJiEy/xHQg5jOqpjD1RsBP3+id8u3P8TUoij6SuoLIwRDfWOofjLYtteyHa1UfcvvcUT1jqpGauVuHnyAdragjv/TAkley3uhj9Y5ZhDQa2+Olgmhz4IygSWibChNZ4nQFioTWCLKhtJ0lghtoTKBJaJsKE1nidAWKhNYIsqG0nSWCG2hMoElomwoTWeJ0BYqE1giyobSdJYIbaEygSWibChNZ4nQFioTWCLKhtJ0biJzmi1/guXMrpDn/OegO3bXMuAH0TtgAvwARV3y57Q34AGoJkL+AErKZ9cqbH7AAAAAAElFTkSuQmCC" + webUI["html/img/mapping.png"] = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAsSwAALEsBpT2WqQAABCRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjU8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjI4ODwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+Mjg4PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NTA8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGRjOnN1YmplY3Q+CiAgICAgICAgICAgIDxyZGY6QmFnLz4KICAgICAgICAgPC9kYzpzdWJqZWN0PgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0wOC0wMlQxMjowODo5NzwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CpRxQsEAAAJLSURBVGgF7VoxTgMxEMwBHWlAaeABVCEtBcoD+EBewCdCyQsQtLyAPCBvCBSRIBUPCF1ogqBJjlkrezGr6HTrO98lhy1Z9trr2VmvnbMNjUadUhzHLeQB8hw5LS3QOUbubvIf7X3kKXIZibgS51bCZdWgMT6D8nECgArkngagQN0B8dhbkblalVmLIyheCGUthhjuLBq77MihA0xTjHHBEBBOorHLjjghbNOg4Mg2RYO4hIiEiHiaAV5aSwf8hRgjZdHtTTTc2ZFXpZkY+hMx5k3IZYlr7jgudJHp2JElLaF0K1mirYn8nAWgQB3ibM59ERNCA52d6Nghv9isQiUtn0kURe92I9eBcYA6YZxym8dyDuwRuMw82gjQYQbsPdLDdNCROO0US3uEfp3usTZpjf5J2CNtNFwjl7FHvmBnCB5PCQkQoJudJtGvE23sJEFuI39rQArS7dskXK6nlwkAKiB1VxAxLcyUePAH8cQmlbEul4+UM8LkVjPc2ZHcaFUDBEeqjoC0HyIiZ6RqOUSk6ghI+xyRD9mRQTYfIktPylaX16rhzo48KE29QH8kxjxC/hFtZYiGe70OjWVMW7Dx32bA3iO7//iAC8DOPweZFQhH6O+CmkRvW2f28oV8owEoUHdMPPg70rFJZajTkqT7uZ3ObaHEuuHOjnCpsb8vlKUsur2JhruLA94Y5QEOjuSZPR9jQ0R8zGoezNpFhN5RtUm+/bpgaG1u0jd2OSLDTRopbZ/okxcrLUYKvKprbRfHhXr8m5PK/y1V/gWRKLfiNSmxEAAAAABJRU5ErkJggg==" + webUI["html/js/.DS_Store"] = "AAAAAUJ1ZDEAABAAAAAIAAAAEAAAAAAlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAABAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAgLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAACAAAAABAAAAQAAAAAEAAACAAAAAAQAAAQAAAAABAAACAAAAAAEAAAQAAAAAAAAAAAEAABAAAAAAAQAAIAAAAAABAABAAAAAAAEAAIAAAAAAAQABAAAAAAABAAIAAAAAAAEABAAAAAAAAQAIAAAAAAABABAAAAAAAAEAIAAAAAAAAQBAAAAAAAABAIAAAAAAAAEBAAAAAAAAAQIAAAAAAAABBAAAAAAAAAEIAAAAAAAAARAAAAAAAAABIAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAEAsAAABFAAAAJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBERTREIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAYAAAAAAAAAABAAAAgAAAAAEAAAEAAAAAAQAAAgAAAAABAAAEAAAAAAIAAAgAAAAYAAAAAAAAAAABAAAgAAAAAAEAAEAAAAAAAQAAgAAAAAABAAEAAAAAAAEAAgAAAAAAAQAEAAAAAAABAAgAAAAAAAEAEAAAAAAAAQAgAAAAAAABAEAAAAAAAAEAgAAAAAAAAQEAAAAAAAABAgAAAAAAAAEEAAAAAAAAAQgAAAAAAAABEAAAAAAAAAEgAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + webUI["html/lang/en.json"] = "ewogICJtYWluTWVudSI6IHsKICAgICJpdGVtIjp7CiAgICAgICJwbGF5bGlzdCI6ICJQbGF5bGlzdCIsCiAgICAgICJwbXNJRCI6ICJQTVMgSUQiLAogICAgICAiZmlsdGVyIjogIkZpbHRlciIsCiAgICAgICJ4bWx0diI6ICJYTUxUViIsCiAgICAgICJtYXBwaW5nIjogIk1hcHBpbmciLAogICAgICAidXNlcnMiOiAiVXNlcnMiLAogICAgICAic2V0dGluZ3MiOiAiU2V0dGluZ3MiLAogICAgICAibG9nIjogIkxvZyIsCiAgICAgICJsb2dvdXQiOiAiTG9nb3V0IgogICAgfSwKICAgICJoZWFkbGluZSI6IHsKICAgICAgInBsYXlsaXN0IjogIkxvY2FsIG9yIHJlbW90ZSBwbGF5bGlzdHMiLAogICAgICAiZmlsdGVyIjogIkZpbHRlciBwbGF5bGlzdCIsCiAgICAgICJ4bWx0diI6ICJMb2NhbCBvciByZW1vdGUgWE1MVFYgZmlsZXMiLAogICAgICAibWFwcGluZyI6ICJNYXAgcGxheWxpc3QgY2hhbm5lbHMgdG8gRVBHIGNoYW5uZWxzIiwKICAgICAgInVzZXJzIjogIlVzZXIgbWFuYWdlbWVudCIsCiAgICAgICJzZXR0aW5ncyI6ICJTZXR0aW5ncyIsCiAgICAgICJsb2ciOiAiTG9nIiwKICAgICAgImxvZ291dCI6ICJMb2dvdXQiCiAgICB9CiAgfSwKICAiY29uZmlybSI6ewogICAgInJlc3RvcmUiOiAiQWxsIGRhdGEgd2lsbCBiZSByZXBsYWNlZCB3aXRoIHRob3NlIGZyb20gdGhlIGJhY2t1cC5TaG91bGQgdGhlIGZpbGVzIGJlIHJlc3RvcmVkPyIKICB9LAogICJhbGVydCI6IHsKICAgICJmaWxlTG9hZGluZ0Vycm9yIjogIkZpbGUgY291bGRuJ3QgYmUgbG9hZGVkIiwKICAgICJpbnZhbGlkQ2hhbm5lbE51bWJlciI6ICJJbnZhbGlkIGNoYW5uZWwgbnVtYmVyIgogIH0sCiAgImJ1dHRvbiI6ewogICAgImJhY2siOiAiQmFjayIsCiAgICAiYmFja3VwIjogIkJhY2t1cCIsCiAgICAiYnVsa0VkaXQiOiAiQnVsayBFZGl0IiwKICAgICJjYW5jZWwiOiAiQ2FuY2VsIiwKICAgICJkZWxldGUiOiAiRGVsZXRlIiwKICAgICJkb25lIjogIkRvbmUiLAogICAgImxvZ2luIjogIkxvZ2luIiwKICAgICJuZXciOiAiTmV3IiwKICAgICJuZXh0IjogIk5leHQiLAogICAgInJlc3RvcmUiOiAiUmVzdG9yZSIsCiAgICAic2F2ZSI6ICJTYXZlIiwKICAgICJzZWFyY2giOiAiU2VhcmNoIiwKICAgICJ1cGRhdGUiOiAiVXBkYXRlIiwKICAgICJjcmFldGVBY2NvdW50IjogIkNyZWF0ZSBBY2NvdW50IiwKICAgICJyZXNldGxvZ3MiOiAiUmVzZXQgTG9ncyIsCiAgICAidXBsb2FkTG9nbyI6ICJVcGxvYWQgTG9nbyIKICB9LAogICJmaWx0ZXIiOiB7CiAgICAidGFibGUiOiB7CiAgICAgICJuYW1lIjogIkZpbHRlciBOYW1lIiwKICAgICAgInR5cGUiOiAiRmlsdGVyIFR5cGUiLAogICAgICAiZmlsdGVyIjogIkZpbHRlciIKICAgIH0sCiAgICAiY3VzdG9tIjogIkN1c3RvbSIsCiAgICAiZ3JvdXAiOiAiR3JvdXAiLAogICAgIm5hbWUiOiB7CiAgICAgICJ0aXRsZSI6ICJGaWx0ZXIgTmFtZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJGaWx0ZXIgbmFtZSIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgImRlc2NyaXB0aW9uIjogewogICAgICAidGl0bGUiOiAiRGVzY3JpcHRpb24iLAogICAgICAicGxhY2Vob2xkZXIiOiAiRGVzY3JpcHRpb24iLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJ0eXBlIjogewogICAgICAidGl0bGUiOiAiVHlwZSIsCiAgICAgICJncm91cFRpdGxlIjogIkdyb3VwIFRpdGxlIiwKICAgICAgImN1c3RvbUZpbHRlciI6ICJDdXN0b20gRmlsdGVyIgogICAgfSwKICAgICJjYXNlU2Vuc2l0aXZlIjogewogICAgICAidGl0bGUiOiAiQ2FzZSBTZW5zaXRpdmUiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0sCiAgICAiZmlsdGVyUnVsZSI6IHsKICAgICAgInRpdGxlIjogIkZpbHRlciBSdWxlIiwKICAgICAgInBsYWNlaG9sZGVyIjogIlNwb3J0IHtIRH0gIXtFUyxJVH0iLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJmaWx0ZXJHcm91cCI6IHsKICAgICAgInRpdGxlIjogIkdyb3VwIFRpdGxlIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJTZWxlY3QgYSBNM1UgZ3JvdXAuIChDb3VudGVyKTxicj5DaGFuZ2luZyB0aGUgZ3JvdXAgdGl0bGUgaW4gdGhlIE0zVSBpbnZhbGlkYXRlcyB0aGUgZmlsdGVyLiIKICAgIH0sCiAgICAiaW5jbHVkZSI6IHsKICAgICAgInRpdGxlIjogIkluY2x1ZGUiLAogICAgICAicGxhY2Vob2xkZXIiOiAiRkhELFVIRCIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJDaGFubmVsIG5hbWUgbXVzdCBpbmNsdWRlLjxicj4oQ29tbWEgc2VwYXJhdGVkKSBDb21tYSBtZWFucyBvciIKICAgIH0sCiAgICAiZXhjbHVkZSI6IHsKICAgICAgInRpdGxlIjogIkV4Y2x1ZGUiLAogICAgICAicGxhY2Vob2xkZXIiOiAiRVMsSVQiLAogICAgICAiZGVzY3JpcHRpb24iOiAiQ2hhbm5lbCBuYW1lIG11c3Qgbm90IGNvbnRhaW4uPGJyPihDb21tYSBzZXBhcmF0ZWQpIENvbW1hIG1lYW5zIG9yIgogICAgfQoKICB9LAogICJwbGF5bGlzdCI6IHsKICAgICJ0YWJsZSI6IHsKICAgICAgInBsYXlsaXN0IjogIlBsYXlsaXN0IiwKICAgICAgInR1bmVyIjogIlR1bmVyIiwKICAgICAgImxhc3RVcGRhdGUiOiAiTGFzdCBVcGRhdGUiLAogICAgICAiYXZhaWxhYmlsaXR5IjogIkF2YWlsYWJpbGl0eSIsCiAgICAgICJ0eXBlIjogIlR5cGUiLAogICAgICAic3RyZWFtcyI6ICJTdHJlYW1zIiwKICAgICAgImdyb3VwVGl0bGUiOiAiZ3JvdXAtdGl0bGUiLAogICAgICAidHZnSUQiOiAidHZnLWlkIiwKICAgICAgInVuaXF1ZUlEIjogIlVuaXF1ZSBJRCIKICAgIH0sCiAgICAicGxheWxpc3RUeXBlIjogewogICAgICAidGl0bGUiOiAiUGxheWxpc3QgdHlwZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJ0eXBlIjogewogICAgICAidGl0bGUiOiAiVHlwZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJuYW1lIjogewogICAgICAidGl0bGUiOiAiTmFtZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJQbGF5bGlzdCBuYW1lIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0sCiAgICAiZGVzY3JpcHRpb24iOiB7CiAgICAgICJ0aXRsZSI6ICJEZXNjcmlwdGlvbiIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJEZXNjcmlwdGlvbiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgImZpbGVNM1UiOiB7CiAgICAgICJ0aXRsZSI6ICJNM1UgRmlsZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJGaWxlIHBhdGggb3IgVVJMIG9mIHRoZSBNM1UiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJmaWxlSERIUiI6IHsKICAgICAgInRpdGxlIjogIkhESG9tZVJ1biBJUCIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJJUCBhZGRyZXNzIGFuZCBwb3J0ICgxOTIuMTY4LjEuMTA6NTAwNCkiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJ0dW5lciI6IHsKICAgICAgInRpdGxlIjogIlR1bmVyIC8gU3RyZWFtcyIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiTnVtYmVyIG9mIHBhcmFsbGVsIGNvbm5lY3Rpb25zIHRoYXQgY2FuIGJlIGVzdGFibGlzaGVkIHRvIHRoZSBwcm92aWRlci4gPGJyPk9ubHkgYXZhaWxhYmxlIHdpdGggYWN0aXZhdGVkIGJ1ZmZlci48YnI+TmV3IHNldHRpbmdzIHdpbGwgb25seSBiZSBhcHBsaWVkIGFmdGVyIHF1aXR0aW5nIGFsbCBzdHJlYW1zLiIKICAgIH0KICB9LAogICJ4bWx0diI6IHsKICAgICJ0YWJsZSI6IHsKICAgICAgImd1aWRlIjogIkd1aWRlIiwKICAgICAgImxhc3RVcGRhdGUiOiAiTGFzdCBVcGRhdGUiLAogICAgICAiYXZhaWxhYmlsaXR5IjogIkF2YWlsYWJpbGl0eSIsCiAgICAgICJjaGFubmVscyI6ICJDaGFubmVscyIsCiAgICAgICJwcm9ncmFtcyI6ICJQcm9ncmFtcyIKICAgIH0sCiAgICAibmFtZSI6IHsKICAgICAgInRpdGxlIjogIk5hbWUiLAogICAgICAicGxhY2Vob2xkZXIiOiAiR3VpZGUgbmFtZSIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgImRlc2NyaXB0aW9uIjogewogICAgICAidGl0bGUiOiAiRGVzY3JpcHRpb24iLAogICAgICAicGxhY2Vob2xkZXIiOiAiRGVzY3JpcHRpb24iLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJmaWxlWE1MVFYiOiB7CiAgICAgICJ0aXRsZSI6ICJYTUxUViBGaWxlIiwKICAgICAgInBsYWNlaG9sZGVyIjogIkZpbGUgcGF0aCBvciBVUkwgb2YgdGhlIFhNTFRWIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0KICB9LAogICJtYXBwaW5nIjogewogICAgInRhYmxlIjogewogICAgICAiY2hObyI6ICJDaC4gTm8uIiwKICAgICAgImxvZ28iOiAiTG9nbyIsCiAgICAgICJjaGFubmVsTmFtZSI6ICJDaGFubmVsIE5hbWUiLAogICAgICAicGxheWxpc3QiOiAiUGxheWxpc3QiLAogICAgICAiZ3JvdXBUaXRsZSI6ICJHcm91cCBUaXRsZSIsCiAgICAgICJ4bWx0dkZpbGUiOiAiWE1MVFYgRmlsZSIsCiAgICAgICJ4bWx0dklEIjogIlhNTFRWIElEIgogICAgfSwKICAgICJhY3RpdmUiOiB7CiAgICAgICJ0aXRsZSI6ICJBY3RpdmUiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIgICAgICAKICAgIH0sCiAgICAiY2hhbm5lbE5hbWUiOiB7CiAgICAgICJ0aXRsZSI6ICJDaGFubmVsIE5hbWUiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIgICAgICAKICAgIH0sCiAgICAidXBkYXRlQ2hhbm5lbE5hbWUiOiB7CiAgICAgICJ0aXRsZSI6ICJVcGRhdGUgQ2hhbm5lbCBOYW1lIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiICAgICAgCiAgICB9LAogICAgImNoYW5uZWxMb2dvIjogewogICAgICAidGl0bGUiOiAiTG9nbyBVUkwiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIgICAgICAKICAgIH0sCiAgICAidXBkYXRlQ2hhbm5lbExvZ28iOiB7CiAgICAgICJ0aXRsZSI6ICJVcGRhdGUgQ2hhbm5lbCBMb2dvIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiICAgICAgCiAgICB9LAogICAgImVwZ0NhdGVnb3J5IjogewogICAgICAidGl0bGUiOiAiRVBHIENhdGVnb3J5IiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiICAgICAgCiAgICB9LAogICAgIm0zdUdyb3VwVGl0bGUiOiB7CiAgICAgICJ0aXRsZSI6ICJHcm91cCBUaXRsZSAoeHRldmUubTN1KSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIiAgICAgIAogICAgfSwKICAgICJ4bWx0dkZpbGUiOiB7CiAgICAgICJ0aXRsZSI6ICJYTUxUViBGaWxlIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiICAgICAgCiAgICB9LAogICAgInhtbHR2Q2hhbm5lbCI6IHsKICAgICAgInRpdGxlIjogIlhNTFRWIENoYW5uZWwiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIgICAgICAKICAgIH0KICB9LAogICJ1c2VycyI6IHsKICAgICJ0YWJsZSI6IHsKICAgICAgInVzZXJuYW1lIjogIlVzZXJuYW1lIiwKICAgICAgInBhc3N3b3JkIjogIlBhc3N3b3JkIiwKICAgICAgIndlYiI6ICJXRUIiLAogICAgICAicG1zIjogIlBNUyIsCiAgICAgICJtM3UiOiAiTTNVIiwKICAgICAgInhtbCI6ICJYTUwiLAogICAgICAiYXBpIjogIkFQSSIKICAgIH0sCiAgICAidXNlcm5hbWUiOiB7CiAgICAgICJ0aXRsZSI6ICJVc2VybmFtZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJVc2VybmFtZSIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgInBhc3N3b3JkIjogewogICAgICAidGl0bGUiOiAiUGFzc3dvcmQiLAogICAgICAicGxhY2Vob2xkZXIiOiAiUGFzc293b3JkIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0sCiAgICAiY29uZmlybSI6IHsKICAgICAgInRpdGxlIjogIkNvbmZpcm0iLAogICAgICAicGxhY2Vob2xkZXIiOiAiUGFzc3dvcmQgY29uZmlybSIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgIndlYiI6IHsKICAgICAgInRpdGxlIjogIldlYiBBY2Nlc3MiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0sCiAgICAicG1zIjogewogICAgICAidGl0bGUiOiAiUE1TIEFjY2VzcyIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJtM3UiOiB7CiAgICAgICJ0aXRsZSI6ICJNM1UgQWNjZXNzIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgInhtbCI6IHsKICAgICAgInRpdGxlIjogIlhNTCBBY2Nlc3MiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0sCiAgICAiYXBpIjogewogICAgICAidGl0bGUiOiAiQVBJIEFjY2VzcyIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfQogIH0sCiAgInNldHRpbmdzIjogewogICAgImNhdGVnb3J5IjogewogICAgICAiZ2VuZXJhbCI6ICJHZW5lcmFsIiwKICAgICAgImZpbGVzIjogIkZpbGVzIiwKICAgICAgInN0cmVhbWluZyI6ICJTdHJlYW1pbmciLAogICAgICAiYmFja3VwIjogIkJhY2t1cCIsCiAgICAgICJhdXRoZW50aWNhdGlvbiI6ICJBdXRoZW50aWNhdGlvbiIKICAgIH0sCiAgICAidXBkYXRlIjogewogICAgICAidGl0bGUiOiAiU2NoZWR1bGUgZm9yIHVwZGF0aW5nIChQbGF5bGlzdCwgWE1MVFYsIEJhY2t1cCkiLAogICAgICAicGxhY2Vob2xkZXIiOiAiMDAwMCwxMDAwLDIwMDAiLAogICAgICAiZGVzY3JpcHRpb24iOiAiVGltZSBpbiAyNCBob3VyIGZvcm1hdCAoMDgwMCA9IDg6MDAgYW0pLiBNb3JlIHRpbWVzIGNhbiBiZSBlbnRlcmVkIGNvbW1hIHNlcGFyYXRlZC4iCiAgICB9LAogICAgImFwaSI6IHsKICAgICAgInRpdGxlIjogIkFQSSBJbnRlcmZhY2UiLAogICAgICAiZGVzY3JpcHRpb24iOiAiVmlhIEFQSSBpbnRlcmZhY2UgaXQgaXMgcG9zc2libGUgdG8gc2VuZCBjb21tYW5kcyB0byB4VGVWZS4gQVBJIGRvY3VtZW50YXRpb24gaXMgPGEgaHJlZj0naHR0cHM6Ly9naXRodWIuY29tL3h0ZXZlLXByb2plY3QveFRlVmUtRG9jdW1lbnRhdGlvbi9ibG9iL21hc3Rlci9lbi9jb25maWd1cmF0aW9uLm1kI2FwaSc+aGVyZTwvYT4iCiAgICB9LAogICAgImVwZ1NvdXJjZSI6IHsKICAgICAgInRpdGxlIjogIkVQRyBTb3VyY2UiLAogICAgICAiZGVzY3JpcHRpb24iOiAiUE1TOjxicj4tIFVzZSBFUEcgZGF0YSBmcm9tIFBsZXggb3IgRW1ieSA8YnI+PGJyPlhFUEc6PGJyPi0gVXNlIG9mIG9uZSBvciBtb3JlIFhNTFRWIGZpbGVzPGJyPi0gQ2hhbm5lbCBtYW5hZ2VtZW50PGJyPi0gTTNVIC8gWE1MVFYgZXhwb3J0IChIVFRQIGxpbmsgZm9yIElQVFYgYXBwcykiCiAgICB9LAogICAgInR1bmVyIjp7CiAgICAgICJ0aXRsZSI6ICJOdW1iZXIgb2YgVHVuZXJzIiwKICAgICAgImRlc2NyaXB0aW9uIjogIk51bWJlciBvZiBwYXJhbGxlbCBjb25uZWN0aW9ucyB0aGF0IGNhbiBiZSBlc3RhYmxpc2hlZCB0byB0aGUgcHJvdmlkZXIuPGJyPkF2YWlsYWJsZSBmb3I6IFBsZXgsIEVtYnkgKEhESFIpLCBNM1UgKHdpdGggYWN0aXZlIGJ1ZmZlcikuPGJyPkFmdGVyIGEgY2hhbmdlLCB4VGVWZSBtdXN0IGJlIGRlbGV0ZSBpbiB0aGUgUGxleCAvIEVtYnkgRFZSIHNldHRpbmdzIGFuZCBzZXQgdXAgYWdhaW4uIgogICAgfSwKICAgICJmaWxlc1VwZGF0ZSI6IHsKICAgICAgInRpdGxlIjogIlVwZGF0ZXMgYWxsIGZpbGVzIGF0IHN0YXJ0dXAiLAogICAgICAiZGVzY3JpcHRpb24iOiAiVXBkYXRlcyBhbGwgcGxheWxpc3RzLCB0dW5lciBhbmQgWE1MVFYgZmlsZXMgYXQgc3RhcnR1cC4iCiAgICB9LAogICAgImNhY2hlSW1hZ2VzIjogewogICAgICAidGl0bGUiOiAiSW1hZ2UgY2FjaGluZyIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJBbGwgaW1hZ2VzIGZyb20gdGhlIFhNTFRWIGZpbGUgYXJlIGNhY2hlZCwgYWxsb3dpbmcgZmFzdGVyIHJlbmRlcmluZyBvZiB0aGUgZ3JpZCBpbiB0aGUgY2xpZW50Ljxicj5Eb3dubG9hZGluZyB0aGUgaW1hZ2VzIG1heSB0YWtlIGEgd2hpbGUgYW5kIHdpbGwgYmUgZG9uZSBpbiB0aGUgYmFja2dyb3VuZC4iCiAgICB9LAogICAgInJlcGxhY2VFbXB0eUltYWdlcyI6IHsKICAgICAgInRpdGxlIjogIlJlcGxhY2UgbWlzc2luZyBwcm9ncmFtIGltYWdlcyIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJJZiB0aGUgcG9zdGVyIGluIHRoZSBYTUxUViBwcm9ncmFtIGlzIG1pc3NpbmcsIHRoZSBjaGFubmVsIGxvZ28gd2lsbCBiZSB1c2VkLiIKICAgIH0sCiAgICAieHRldmVBdXRvVXBkYXRlIjogewogICAgICAidGl0bGUiOiAiQXV0b21hdGljIHVwZGF0ZSBvZiB4VGVWZSIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJJZiBhIG5ldyB2ZXJzaW9uIG9mIHhUZVZlIGlzIGF2YWlsYWJsZSwgaXQgd2lsbCBiZSBhdXRvbWF0aWNhbGx5IGluc3RhbGxlZC4gVGhlIHVwZGF0ZXMgYXJlIGRvd25sb2FkZWQgZnJvbSBHaXRIdWIuIgogICAgfSwKICAgICJzdHJlYW1CdWZmZXJpbmciOiB7CiAgICAgICJ0aXRsZSI6ICJTdHJlYW0gQnVmZmVyIiwKICAgICAgImRlc2NyaXB0aW9uIjogIi0gVGhlIHN0cmVhbSBpcyBwYXNzZWQgZnJvbSB4VGVWZSB0byBQbGV4IC8gRW1ieSAvIE0zVSBQbGF5ZXI8YnI+LSBTbWFsbCBqZXJraW5nIG9mIHRoZSBzdHJlYW1zIGNhbiBiZSBjb21wZW5zYXRlZDxicj4tIEhMUyAvIE0zVTggc3VwcG9ydCIKICAgIH0sCiAgICAiYnVmZmVyU2l6ZSI6IHsKICAgICAgInRpdGxlIjogIkJ1ZmZlciBTaXplIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkJ1ZmZlciBzaXplIGluIE1CLjxicj5NM1U4OiBJZiB0aGUgVFMgc2VnbWVudCBzbWFsbGVyIHRoZW4gdGhlIGJ1ZmZlciBzaXplLCB0aGUgZmlsZSBzaXplIG9mIHRoZSBzZWdtZW50IGlzIHVzZWQuIgogICAgfSwKICAgICJidWZmZXJUaW1lb3V0IjogewogICAgICAidGl0bGUiOiAiVGltZW91dCBmb3IgbmV3IGNsaWVudCBjb25uZWN0aW9ucyIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJUaGUgeFRlVmUgYnVmZmVyIHdhaXRzIHVudGlsIG5ldyBjbGllbnQgY29ubmVjdGlvbnMgYXJlIGVzdGFibGlzaGVkLiBIZWxwZnVsIGZvciBmYXN0IGNoYW5uZWwgc3dpdGNoaW5nLiBWYWx1ZSBpbiBtaWxsaXNlY29uZHMuIiwKICAgICAgInBsYWNlaG9sZGVyIjogIjEwMCIKICAgIH0sCiAgICAidXNlckFnZW50IjogewogICAgICAidGl0bGUiOiAiVXNlciBhZ2VudCIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJVc2VyIEFnZW50IGZvciBIVFRQIHJlcXVlc3RzIiwKICAgICAgInBsYWNlaG9sZGVyIjogInhUZVZlIgogICAgfSwKICAgICJiYWNrdXBQYXRoIjogewogICAgICAidGl0bGUiOiAiTG9jYXRpb24gZm9yIGF1dG9tYXRpYyBiYWNrdXBzIiwKICAgICAgInBsYWNlaG9sZGVyIjogIi9tbnQvZGF0YS9iYWNrdXAveHRldmUvIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkJlZm9yZSBhbnkgdXBkYXRlIG9mIHRoZSBwcm92aWRlciBkYXRhIGJ5IHRoZSBzY2hlZHVsZSwgeFRlVmUgY3JlYXRlcyBhIGJhY2t1cC4gVGhlIHBhdGggZm9yIHRoZSBhdXRvbWF0aWMgYmFja3VwcyBjYW4gYmUgY2hhbmdlZC4geFRlVmUgcmVxdWlyZXMgd3JpdGUgcGVybWlzc2lvbiBmb3IgdGhpcyBmb2xkZXIuIgogICAgfSwKICAgICJ0ZW1wUGF0aCI6IHsKICAgICAgInRpdGxlIjogIkxvY2F0aW9uIGZvciB0aGUgdGVtcG9yYXJ5IGZpbGVzIiwKICAgICAgInBsYWNlaG9sZGVyIjogIi90bXAveHRldmUvIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkxvY2F0aW9uIGZvciB0aGUgYnVmZmVyIGZpbGVzLiIKICAgIH0sCiAgICAiYmFja3VwS2VlcCI6IHsKICAgICAgInRpdGxlIjogIk51bWJlciBvZiBiYWNrdXBzIHRvIGtlZXAiLAogICAgICAiZGVzY3JpcHRpb24iOiAiTnVtYmVyIG9mIGJhY2t1cHMgdG8ga2VlcC4gT2xkZXIgYmFja3VwcyBhcmUgYXV0b21hdGljYWxseSBkZWxldGVkLiIKICAgIH0sCiAgICAiYXV0aGVudGljYXRpb25XRUIiOiB7CiAgICAgICJ0aXRsZSI6ICJXRUIgQXV0aGVudGljYXRpb24iLAogICAgICAiZGVzY3JpcHRpb24iOiAiQWNjZXNzIHRvIHRoZSB3ZWIgaW50ZXJmYWNlIG9ubHkgcG9zc2libGUgd2l0aCBjcmVkZW50aWFscy4iCiAgICB9LAogICAgImF1dGhlbnRpY2F0aW9uUE1TIjogewogICAgICAidGl0bGUiOiAiUE1TIEF1dGhlbnRpY2F0aW9uIiwKICAgICAgImRlc2NyaXB0aW9uIjogIlBsZXggcmVxdWVzdHMgYXJlIG9ubHkgcG9zc2libGUgd2l0aCBhdXRoZW50aWNhdGlvbi4gPGJyPjxiPldhcm5pbmchISE8L2I+IEFmdGVyIGFjdGl2YXRpbmcgdGhpcyBmdW5jdGlvbiB4VGVWZSBtdXN0IGJlIGRlbGV0ZSBpbiB0aGUgUE1TIERWUiBzZXR0aW5ncyBhbmQgc2V0IHVwIGFnYWluLiIKICAgIH0sCiAgICAiYXV0aGVudGljYXRpb25NM1UiOiB7CiAgICAgICJ0aXRsZSI6ICJNM1UgQXV0aGVudGljYXRpb24iLAogICAgICAiZGVzY3JpcHRpb24iOiAiRG93bmxvYWRpbmcgdGhlIHh0ZXZlLm0zdSBmaWxlIHZpYSBhbiBIVFRQIHJlcXVlc3QgaXMgb25seSBwb3NzaWJsZSB3aXRoIGF1dGhlbnRpY2F0aW9uLiIKICAgIH0sCiAgICAiYXV0aGVudGljYXRpb25YTUwiOiB7CiAgICAgICJ0aXRsZSI6ICJYTUwgQXV0aGVudGljYXRpb24iLAogICAgICAiZGVzY3JpcHRpb24iOiAiRG93bmxvYWRpbmcgdGhlIHh0ZXZlLnhtbCBmaWxlIHZpYSBhbiBIVFRQIHJlcXVlc3QgaXMgb25seSBwb3NzaWJsZSB3aXRoIGF1dGhlbnRpY2F0aW9uIgogICAgfSwKICAgICJhdXRoZW50aWNhdGlvbkFQSSI6IHsKICAgICAgInRpdGxlIjogIkFQSSBBdXRoZW50aWNhdGlvbiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJBY2Nlc3MgdG8gdGhlIEFQSSBpbnRlcmZhY2UgaXMgb25seSBwb3NzaWJsZSB3aXRoIGF1dGhlbnRpY2F0aW9uLiIKICAgIH0KICB9LAogICJ3aXphcmQiOiB7CiAgICAiZXBnU291cmNlIjogewogICAgICAidGl0bGUiOiAiRVBHIFNvdXJjZSIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJQTVM6PGJyPi0gVXNlIEVQRyBkYXRhIGZyb20gUGxleCBvciBFbWJ5IDxicj48YnI+WEVQRzo8YnI+LSBVc2Ugb2Ygb25lIG9yIG1vcmUgWE1MVFYgZmlsZXM8YnI+LSBDaGFubmVsIG1hbmFnZW1lbnQ8YnI+LSBNM1UgLyBYTUxUViBleHBvcnQgKEhUVFAgbGluayBmb3IgSVBUViBhcHBzKSIKICAgIH0sCiAgICAidHVuZXIiOnsKICAgICAgInRpdGxlIjogIk51bWJlciBvZiB0dW5lcnMiLAogICAgICAiZGVzY3JpcHRpb24iOiAiTnVtYmVyIG9mIHBhcmFsbGVsIGNvbm5lY3Rpb25zIHRoYXQgY2FuIGJlIGVzdGFibGlzaGVkIHRvIHRoZSBwcm92aWRlci48YnI+QXZhaWxhYmxlIGZvcjogUGxleCwgRW1ieSAoSERIUiksIE0zVSAod2l0aCBhY3RpdmUgYnVmZmVyKS48YnI+QWZ0ZXIgYSBjaGFuZ2UsIHhUZVZlIG11c3QgYmUgZGVsZXRlIGluIHRoZSBQbGV4IC8gRW1ieSBEVlIgc2V0dGluZ3MgYW5kIHNldCB1cCBhZ2Fpbi4iCiAgICB9LAogICAgIm0zdSI6IHsKICAgICAgInRpdGxlIjogIk0zVSBQbGF5bGlzdCIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJMb2NhbCBvciByZW1vdGUgcGxheWxpc3RzIgogICAgfSwKICAgICJ4bWx0diI6IHsKICAgICAgInRpdGxlIjogIlhNTFRWIEZpbGUiLAogICAgICAiZGVzY3JpcHRpb24iOiAiTG9jYWwgb3IgcmVtb3RlIFhNTFRWIGZpbGUiCiAgICB9CiAgfSwKICAibG9naW4iOiB7CiAgICAiZmFpbGVkIjogIlVzZXIgYXV0aGVudGljYXRpb24gZmFpbGVkIiwKICAgICJoZWFkbGluZSI6ICJMb2dpbiIsCiAgICAidXNlcm5hbWUiOiB7CiAgICAgICJ0aXRsZSI6ICJVc2VybmFtZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJVc2VybmFtZSIKICAgIH0sCiAgICAicGFzc3dvcmQiOiB7CiAgICAgICJ0aXRsZSI6ICJQYXNzd29yZCIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJQYXNzd29yZCIKICAgIH0KICB9LAogICJhY2NvdW50IjogewogICAgImZhaWxlZCI6ICJQYXNzd29yZCBkb2VzIG5vdCBtYXRjaCIsCiAgICAiaGVhZGxpbmUiOiAiQ3JlYXRlIHVzZXIgYWNjb3VudCIsCiAgICAidXNlcm5hbWUiOiB7CiAgICAgICJ0aXRsZSI6ICJVc2VybmFtZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJVc2VybmFtZSIKICAgIH0sCiAgICAicGFzc3dvcmQiOiB7CiAgICAgICJ0aXRsZSI6ICJQYXNzd29yZCIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJQYXNzd29yZCIKICAgIH0sCiAgICAiY29uZmlybSI6IHsKICAgICAgInRpdGxlIjogIkNvbmZpcm0iLAogICAgICAicGxhY2Vob2xkZXIiOiAiQ29uZmlybSIKICAgIH0KICB9Cn0K" + webUI["html/img/.DS_Store"] = "AAAAAUJ1ZDEAABAAAAAIAAAAEAAAAAAlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAABAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAgLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAACAAAAABAAAAQAAAAAEAAACAAAAAAQAAAQAAAAABAAACAAAAAAEAAAQAAAAAAAAAAAEAABAAAAAAAQAAIAAAAAABAABAAAAAAAEAAIAAAAAAAQABAAAAAAABAAIAAAAAAAEABAAAAAAAAQAIAAAAAAABABAAAAAAAAEAIAAAAAAAAQBAAAAAAAABAIAAAAAAAAEBAAAAAAAAAQIAAAAAAAABBAAAAAAAAAEIAAAAAAAAARAAAAAAAAABIAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAEAsAAABFAAAAJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBERTREIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAYAAAAAAAAAABAAAAgAAAAAEAAAEAAAAAAQAAAgAAAAABAAAEAAAAAAIAAAgAAAAYAAAAAAAAAAABAAAgAAAAAAEAAEAAAAAAAQAAgAAAAAABAAEAAAAAAAEAAgAAAAAAAQAEAAAAAAABAAgAAAAAAAEAEAAAAAAAAQAgAAAAAAABAEAAAAAAAAEAgAAAAAAAAQEAAAAAAAABAgAAAAAAAAEEAAAAAAAAAQgAAAAAAAABEAAAAAAAAAEgAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + webUI["html/img/filter.png"] = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAsSwAALEsBpT2WqQAABCRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjU8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjI4ODwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+Mjg4PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NTA8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGRjOnN1YmplY3Q+CiAgICAgICAgICAgIDxyZGY6QmFnLz4KICAgICAgICAgPC9kYzpzdWJqZWN0PgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0wNy0yOFQxOTowNzo2OTwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Cs038OQAAAOISURBVGgF5ZpLSBVRGMfvvfmqCKKiKCqKtE3SQ8haJJm1ctPCZYHrFhXtohcUBLVsU1BRBJKLXpsSKs1aCL1oUUhvMgIloodZKmLZ72+OzNVx7jzOXGfqg/89Z875zv/7f35n7px7vanUP2JptzyGhoYKmK8GW0EZmA/mgW7wHrwDN0BTOp0epPVtxBBnLdgIVoE5YBboBR2gDTTA/5DWn0FeDPaBz8CLdeF0EBR5jYRvKbgABoAXa8Opwit/CudK8NoLs4PPM8ZyBsNnB/jpsD7X0C8cjgDXnaQkqkEPCGMSuGWivxxzx8OQj6xtpHWuPhPloHfEMWzTD0HN2GQY2xuW2Lb+/Fh+VaIQPLE5meh+gmSxFYz+BjBogtjGscfiH26Z2GmbNNltVQAIpwDdP6ZNO2iRYqTpZGhfgWUaiMC2w1kCzkbALcqLvDVvUyK6MW9HFES0b8BvsFwXEZi4F+iBtykCcjtlqf0igr52VJ1eqiIgzzdlrRIZfWfJd3SD8ZbqHvkB4XSDpJNB9V0VcX/cT4Ys/zEzSuSj/3WxW9GpRD7ETpZ/QcOJ6LyfdHugitxNehbob9G7lo4PXWBmQhPqQ/fsDOeUfjqNCU1Csi+TQ5+2luzc3yaRr6elevQZwhZr4bomYam0U41yabYqov5hvSTMjlp6RyuiAarSTLPZmox5246+lVREx/isiuh6NxhUJwF2yEpCWu1bK8WEsjyRgCSa0XrVrjNra2mC7TWD5ilYAuJoA4jSlnppF5dVEU3g0ENTD4b3nsZiZsfGJuGqj8qY+CINGqP2GLZCJ+HjtpblxAJ9k3cPrLfGJrnVUaSCarxw0jFua1lOLNBerANx+byya6IkLM2uLZWpAl6/Mcc1EjvjKtLrJNLqI5HnjfQ+bsVeteb0g2y/t7hGvd7CNjenOL8OkJ40KtOdTF+Cl/nV6Mkf4gxocI9vZPYLLKs9iQrqRIACcM2IXGeSboYrg+rztY5ARaDJWUeo0W+sXudLTFhnApaAW6FkZy/+yuXasLoCrSfwVHAnW0+gK93YawKJMLUIAdNAKwhqnSxcYUpPKB6EBE2mg7VR///EX24jybTQerXnOC70FyVP3gjTPXPTQyaP8NFPNeJrCNTPP667JKOq6VNo/A2hes5ccUjmEmPmDoD5+FMgWCcA+3FG57QJP//kQ1PgGBIOToEDgUn+t4V/AJeGknwARIKLAAAAAElFTkSuQmCC" + webUI["html/js/authentication_ts.js"] = "ZnVuY3Rpb24gbG9naW4oKSB7CiAgICB2YXIgZXJyID0gZmFsc2U7CiAgICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKICAgIHZhciBkaXYgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiY29udGVudCIpOwogICAgdmFyIGZvcm0gPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYXV0aGVudGljYXRpb24iKTsKICAgIHZhciBpbnB1dHMgPSBkaXYuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIklOUFVUIik7CiAgICBjb25zb2xlLmxvZyhpbnB1dHMpOwogICAgZm9yICh2YXIgaSA9IGlucHV0cy5sZW5ndGggLSAxOyBpID49IDA7IGktLSkgewogICAgICAgIHZhciBrZXkgPSBpbnB1dHNbaV0ubmFtZTsKICAgICAgICB2YXIgdmFsdWUgPSBpbnB1dHNbaV0udmFsdWU7CiAgICAgICAgaWYgKHZhbHVlLmxlbmd0aCA9PSAwKSB7CiAgICAgICAgICAgIGlucHV0c1tpXS5zdHlsZS5ib3JkZXJDb2xvciA9ICJyZWQiOwogICAgICAgICAgICBlcnIgPSB0cnVlOwogICAgICAgIH0KICAgICAgICBkYXRhW2tleV0gPSB2YWx1ZTsKICAgIH0KICAgIGlmIChlcnIgPT0gdHJ1ZSkgewogICAgICAgIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgICAgICAgcmV0dXJuOwogICAgfQogICAgaWYgKGRhdGEuaGFzT3duUHJvcGVydHkoImNvbmZpcm0iKSkgewogICAgICAgIGlmIChkYXRhWyJjb25maXJtIl0gIT0gZGF0YVsicGFzc3dvcmQiXSkgewogICAgICAgICAgICBhbGVydCgic2RhZnNkIik7CiAgICAgICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdwYXNzd29yZCcpLnN0eWxlLmJvcmRlckNvbG9yID0gInJlZCI7CiAgICAgICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdjb25maXJtJykuc3R5bGUuYm9yZGVyQ29sb3IgPSAicmVkIjsKICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImVyciIpLmlubmVySFRNTCA9ICJ7ey5hY2NvdW50LmZhaWxlZH19IjsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KICAgIH0KICAgIGNvbnNvbGUubG9nKGRhdGEpOwogICAgZm9ybS5zdWJtaXQoKTsKfQo=" + webUI["html/js/configuration_ts.js"] = "dmFyIF9fZXh0ZW5kcyA9ICh0aGlzICYmIHRoaXMuX19leHRlbmRzKSB8fCAoZnVuY3Rpb24gKCkgewogICAgdmFyIGV4dGVuZFN0YXRpY3MgPSBmdW5jdGlvbiAoZCwgYikgewogICAgICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHwKICAgICAgICAgICAgKHsgX19wcm90b19fOiBbXSB9IGluc3RhbmNlb2YgQXJyYXkgJiYgZnVuY3Rpb24gKGQsIGIpIHsgZC5fX3Byb3RvX18gPSBiOyB9KSB8fAogICAgICAgICAgICBmdW5jdGlvbiAoZCwgYikgeyBmb3IgKHZhciBwIGluIGIpIGlmIChiLmhhc093blByb3BlcnR5KHApKSBkW3BdID0gYltwXTsgfTsKICAgICAgICByZXR1cm4gZXh0ZW5kU3RhdGljcyhkLCBiKTsKICAgIH07CiAgICByZXR1cm4gZnVuY3Rpb24gKGQsIGIpIHsKICAgICAgICBleHRlbmRTdGF0aWNzKGQsIGIpOwogICAgICAgIGZ1bmN0aW9uIF9fKCkgeyB0aGlzLmNvbnN0cnVjdG9yID0gZDsgfQogICAgICAgIGQucHJvdG90eXBlID0gYiA9PT0gbnVsbCA/IE9iamVjdC5jcmVhdGUoYikgOiAoX18ucHJvdG90eXBlID0gYi5wcm90b3R5cGUsIG5ldyBfXygpKTsKICAgIH07Cn0pKCk7CnZhciBXaXphcmRDYXRlZ29yeSA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uICgpIHsKICAgIGZ1bmN0aW9uIFdpemFyZENhdGVnb3J5KCkgewogICAgICAgIHRoaXMuRG9jdW1lbnRJRCA9ICJjb250ZW50IjsKICAgIH0KICAgIFdpemFyZENhdGVnb3J5LnByb3RvdHlwZS5jcmVhdGVDYXRlZ29yeUhlYWRsaW5lID0gZnVuY3Rpb24gKHZhbHVlKSB7CiAgICAgICAgdmFyIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJINCIpOwogICAgICAgIGVsZW1lbnQuaW5uZXJIVE1MID0gdmFsdWU7CiAgICAgICAgcmV0dXJuIGVsZW1lbnQ7CiAgICB9OwogICAgcmV0dXJuIFdpemFyZENhdGVnb3J5Owp9KCkpOwp2YXIgV2l6YXJkSXRlbSA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uIChfc3VwZXIpIHsKICAgIF9fZXh0ZW5kcyhXaXphcmRJdGVtLCBfc3VwZXIpOwogICAgZnVuY3Rpb24gV2l6YXJkSXRlbShrZXksIGhlYWRsaW5lKSB7CiAgICAgICAgdmFyIF90aGlzID0gX3N1cGVyLmNhbGwodGhpcykgfHwgdGhpczsKICAgICAgICBfdGhpcy5oZWFkbGluZSA9IGhlYWRsaW5lOwogICAgICAgIF90aGlzLmtleSA9IGtleTsKICAgICAgICByZXR1cm4gX3RoaXM7CiAgICB9CiAgICBXaXphcmRJdGVtLnByb3RvdHlwZS5jcmVhdGVXaXphcmQgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgdmFyIGhlYWRsaW5lID0gdGhpcy5jcmVhdGVDYXRlZ29yeUhlYWRsaW5lKHRoaXMuaGVhZGxpbmUpOwogICAgICAgIHZhciBrZXkgPSB0aGlzLmtleTsKICAgICAgICB2YXIgY29udGVudCA9IG5ldyBQb3B1cENvbnRlbnQoKTsKICAgICAgICB2YXIgZGVzY3JpcHRpb247CiAgICAgICAgdmFyIGRvYyA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHRoaXMuRG9jdW1lbnRJRCk7CiAgICAgICAgZG9jLmlubmVySFRNTCA9ICIiOwogICAgICAgIGRvYy5hcHBlbmRDaGlsZChoZWFkbGluZSk7CiAgICAgICAgc3dpdGNoIChrZXkpIHsKICAgICAgICAgICAgY2FzZSAidHVuZXIiOgogICAgICAgICAgICAgICAgdmFyIHRleHQgPSBuZXcgQXJyYXkoKTsKICAgICAgICAgICAgICAgIHZhciB2YWx1ZXMgPSBuZXcgQXJyYXkoKTsKICAgICAgICAgICAgICAgIGZvciAodmFyIGkgPSAxOyBpIDw9IDEwMDsgaSsrKSB7CiAgICAgICAgICAgICAgICAgICAgdGV4dC5wdXNoKGkpOwogICAgICAgICAgICAgICAgICAgIHZhbHVlcy5wdXNoKGkpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgdmFyIHNlbGVjdCA9IGNvbnRlbnQuY3JlYXRlU2VsZWN0KHRleHQsIHZhbHVlcywgIjEiLCBrZXkpOwogICAgICAgICAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgiY2xhc3MiLCAid2l6YXJkIik7CiAgICAgICAgICAgICAgICBzZWxlY3QuaWQgPSBrZXk7CiAgICAgICAgICAgICAgICBkb2MuYXBwZW5kQ2hpbGQoc2VsZWN0KTsKICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uID0gInt7LndpemFyZC50dW5lci5kZXNjcmlwdGlvbn19IjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJlcGdTb3VyY2UiOgogICAgICAgICAgICAgICAgdmFyIHRleHQgPSBbIlBNUyIsICJYRVBHIl07CiAgICAgICAgICAgICAgICB2YXIgdmFsdWVzID0gWyJQTVMiLCAiWEVQRyJdOwogICAgICAgICAgICAgICAgdmFyIHNlbGVjdCA9IGNvbnRlbnQuY3JlYXRlU2VsZWN0KHRleHQsIHZhbHVlcywgIlhFUEciLCBrZXkpOwogICAgICAgICAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgiY2xhc3MiLCAid2l6YXJkIik7CiAgICAgICAgICAgICAgICBzZWxlY3QuaWQgPSBrZXk7CiAgICAgICAgICAgICAgICBkb2MuYXBwZW5kQ2hpbGQoc2VsZWN0KTsKICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uID0gInt7LndpemFyZC5lcGdTb3VyY2UuZGVzY3JpcHRpb259fSI7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAibTN1IjoKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCBrZXksICIiKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgiY2xhc3MiLCAid2l6YXJkIik7CiAgICAgICAgICAgICAgICBpbnB1dC5pZCA9IGtleTsKICAgICAgICAgICAgICAgIGRvYy5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbiA9ICJ7ey53aXphcmQubTN1LmRlc2NyaXB0aW9ufX0iOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgInhtbHR2IjoKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCBrZXksICIiKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgiY2xhc3MiLCAid2l6YXJkIik7CiAgICAgICAgICAgICAgICBpbnB1dC5pZCA9IGtleTsKICAgICAgICAgICAgICAgIGRvYy5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbiA9ICJ7ey53aXphcmQueG1sdHYuZGVzY3JpcHRpb259fSI7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgZGVmYXVsdDoKICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKGtleSk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICB9CiAgICAgICAgdmFyIHByZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlBSRSIpOwogICAgICAgIHByZS5pbm5lckhUTUwgPSBkZXNjcmlwdGlvbjsKICAgICAgICBkb2MuYXBwZW5kQ2hpbGQocHJlKTsKICAgICAgICBjb25zb2xlLmxvZyhoZWFkbGluZSwga2V5KTsKICAgIH07CiAgICByZXR1cm4gV2l6YXJkSXRlbTsKfShXaXphcmRDYXRlZ29yeSkpOwpmdW5jdGlvbiByZWFkeUZvckNvbmZpZ3VyYXRpb24od2l6YXJkKSB7CiAgICB2YXIgc2VydmVyID0gbmV3IFNlcnZlcigiZ2V0U2VydmVyQ29uZmlnIik7CiAgICBzZXJ2ZXIucmVxdWVzdChuZXcgT2JqZWN0KCkpOwogICAgc2hvd0VsZW1lbnQoImxvYWRpbmciLCBmYWxzZSk7CiAgICBjb25maWd1cmF0aW9uV2l6YXJkW3dpemFyZF0uY3JlYXRlV2l6YXJkKCk7Cn0KZnVuY3Rpb24gc2F2ZVdpemFyZCgpIHsKICAgIHZhciBjbWQgPSAic2F2ZVdpemFyZCI7CiAgICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImNvbnRlbnQiKTsKICAgIHZhciBjb25maWcgPSBkaXYuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgid2l6YXJkIik7CiAgICB2YXIgd2l6YXJkID0gbmV3IE9iamVjdCgpOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBjb25maWcubGVuZ3RoOyBpKyspIHsKICAgICAgICB2YXIgbmFtZTsKICAgICAgICB2YXIgdmFsdWU7CiAgICAgICAgc3dpdGNoIChjb25maWdbaV0udGFnTmFtZSkgewogICAgICAgICAgICBjYXNlICJTRUxFQ1QiOgogICAgICAgICAgICAgICAgbmFtZSA9IGNvbmZpZ1tpXS5uYW1lOwogICAgICAgICAgICAgICAgdmFsdWUgPSBjb25maWdbaV0udmFsdWU7CiAgICAgICAgICAgICAgICAvLyBXZW5uIGRlciBXZXJ0IGVpbmUgWmFobCBpc3QsIHdpcmQgZGllc2VyIGFscyBaYWhsIGdlc3BlaWNoZXJ0CiAgICAgICAgICAgICAgICBpZiAoaXNOYU4odmFsdWUpKSB7CiAgICAgICAgICAgICAgICAgICAgd2l6YXJkW25hbWVdID0gdmFsdWU7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgICAgICB3aXphcmRbbmFtZV0gPSBwYXJzZUludCh2YWx1ZSk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAiSU5QVVQiOgogICAgICAgICAgICAgICAgc3dpdGNoIChjb25maWdbaV0udHlwZSkgewogICAgICAgICAgICAgICAgICAgIGNhc2UgInRleHQiOgogICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gY29uZmlnW2ldLm5hbWU7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gY29uZmlnW2ldLnZhbHVlOwogICAgICAgICAgICAgICAgICAgICAgICB3aXphcmRbbmFtZV0gPSB2YWx1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgZGVmYXVsdDoKICAgICAgICAgICAgICAgIC8vIGNvZGUuLi4KICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgIH0KICAgIH0KICAgIHZhciBkYXRhID0gbmV3IE9iamVjdCgpOwogICAgZGF0YVsid2l6YXJkIl0gPSB3aXphcmQ7CiAgICB2YXIgc2VydmVyID0gbmV3IFNlcnZlcihjbWQpOwogICAgc2VydmVyLnJlcXVlc3QoZGF0YSk7CiAgICBjb25zb2xlLmxvZyhkYXRhKTsKfQovLyBXaXphcmQKdmFyIGNvbmZpZ3VyYXRpb25XaXphcmQgPSBuZXcgQXJyYXkoKTsKY29uZmlndXJhdGlvbldpemFyZC5wdXNoKG5ldyBXaXphcmRJdGVtKCJ0dW5lciIsICJ7ey53aXphcmQudHVuZXIudGl0bGV9fSIpKTsKY29uZmlndXJhdGlvbldpemFyZC5wdXNoKG5ldyBXaXphcmRJdGVtKCJlcGdTb3VyY2UiLCAie3sud2l6YXJkLmVwZ1NvdXJjZS50aXRsZX19IikpOwpjb25maWd1cmF0aW9uV2l6YXJkLnB1c2gobmV3IFdpemFyZEl0ZW0oIm0zdSIsICJ7ey53aXphcmQubTN1LnRpdGxlfX0iKSk7CmNvbmZpZ3VyYXRpb25XaXphcmQucHVzaChuZXcgV2l6YXJkSXRlbSgieG1sdHYiLCAie3sud2l6YXJkLnhtbHR2LnRpdGxlfX0iKSk7Cg==" + webUI["html/js/log.js"] = "dmFyIGxvZ0ludGVydmFsCgoKZnVuY3Rpb24gdXBkYXRlTG9nKCkgewogIHZhciBkYXRhID0gbmV3IE9iamVjdCgpOwogIGRhdGFbImNtZCJdID0gImdldExvZyI7CiAgeFRlVmUoZGF0YSk7CiAgd3JpdGVMb2dJbkRpdigpOwogIHJldHVybgp9CgpmdW5jdGlvbiB3cml0ZUxvZ0luRGl2KCkgewogIHZhciBsb2dzID0gbG9nWyJsb2ciXTsKICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNldHRpbmdzIikubGFzdENoaWxkLmxhc3RDaGlsZDsKICBkaXYuaW5uZXJIVE1MID0gIiI7CgogIHZhciBtYXggPSA1MDsKICAKICAKICBmb3IgKHZhciBpID0gMDsgaSA8IGxvZ3MubGVuZ3RoOyBpKyspIHsKICAgIHZhciBuZXdFbnRyeSA9IG5ldyBPYmplY3QoKTsKICAgIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJQIjsKCiAgICBpZiAobG9nc1tpXS5pbmNsdWRlcygiRVJST1IiKSkgewovLyAgICAgIGNhc2UgIndhcm5pbmdzIjogIG1zZ1R5cGUgPSAid2FybmluZ01zZyI7IGJyZWFrOwogICAgICBuZXdFbnRyeVsiY2xhc3MiXSAgID0gImVycm9yTXNnIjsKICAgIH0KCiAgICBpZiAobG9nc1tpXS5pbmNsdWRlcygiV0FSTklORyIpKSB7Ci8vICAgICAgY2FzZSAid2FybmluZ3MiOiAgbXNnVHlwZSA9ICJ3YXJuaW5nTXNnIjsgYnJlYWs7CiAgICAgIG5ld0VudHJ5WyJjbGFzcyJdICAgPSAid2FybmluZ01zZyI7CiAgICB9CgogICAgbmV3RW50cnlbIl90ZXh0Il0gICAgID0gbG9nc1tpXTsKICAgIAogICAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKICB9CgogIGNhbGN1bGF0ZVdyYXBwZXJIZWlnaHQoKTsKICB2YXIgc2Nyb2xsRGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImJveC13cmFwcGVyIik7CiAgc2Nyb2xsRGl2LnNjcm9sbFRvcCA9IHNjcm9sbERpdi5zY3JvbGxIZWlnaHQ7Cn0KCmZ1bmN0aW9uIHNob3dMb2cob2JqKSB7CiAgLy9sb2dJbnRlcnZhbCA9IHNldEludGVydmFsKHVwZGF0ZUxvZywgNTAwMCk7CgogIHZhciBsb2dzID0gbG9nWyJsb2ciXTsKCiAgdmFyIGRpdiA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJzZXR0aW5ncyIpOwoKICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0KCk7CiAgbmV3RW50cnlbIl9lbGVtZW50Il0gID0gIkhSIjsKICBkaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwogIC8vZGl2ID0gZGl2Lmxhc3RDaGlsZDsKCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICAgID0gIklOUFVUIjsKICBuZXdFbnRyeVsidHlwZSJdICAgICAgICA9ICJidXR0b24iOwogIG5ld0VudHJ5WyJjbGFzcyJdICAgICAgID0gImJ1dHRvbiI7CiAgbmV3RW50cnlbInZhbHVlIl0gICAgICAgPSAiRW1wdHkgTG9nIjsKICBuZXdFbnRyeVsib25jbGljayJdICAgICA9ICJlbXB0eUxvZygpIjsKICBkaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwoKICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0KCk7CiAgbmV3RW50cnlbIl9lbGVtZW50Il0gICAgPSAiY29kZSI7CiAgbmV3RW50cnlbIl90ZXh0Il0gICAgICAgID0gIlVwZGF0ZSBMb2c6ICI7CiAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICAgID0gIklOUFVUIjsKICBuZXdFbnRyeVsidHlwZSJdICAgICAgICA9ICJjaGVja2JveCI7CiAgLy9uZXdFbnRyeVsiY2hlY2tlZCJdICAgICA9ICJjaGVja2JveCI7CiAgbmV3RW50cnlbIm9uY2xpY2siXSAgICAgPSAibG9nVXBkYXRlcyh0aGlzKSI7CiAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJIUiI7CiAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKCiAgCgoKICAKICB2YXIgbmV3V3JhcHBlciA9IG5ldyBPYmplY3QoKTsKICBuZXdXcmFwcGVyWyJfZWxlbWVudCJdICA9ICJESVYiOwogIG5ld1dyYXBwZXJbImlkIl0gICAgICAgID0gImJveC13cmFwcGVyIjsKICBkaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdXcmFwcGVyKSk7CiAgZGl2ID0gZGl2Lmxhc3RDaGlsZDsKCiAgdmFyIG5ld1ByZSA9IG5ldyBPYmplY3QoKTsKICBuZXdQcmVbIl9lbGVtZW50Il0gID0gIlBSRSI7CiAgbmV3UHJlWyJpZCJdICAgICAgICA9ICJsb2dTY3JlZW4iOwogIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld1ByZSkpOwoKICBkaXYgPSBkaXYubGFzdENoaWxkOwoKICB3cml0ZUxvZ0luRGl2KCkKICByZXR1cm4KfQoKZnVuY3Rpb24gZW1wdHlMb2coKSB7CiAgdmFyIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgZGF0YVsiY21kIl0gPSAiZW1wdHlMb2ciOwogIHhUZVZlKGRhdGEpOwogIHJldHVybgp9CgpmdW5jdGlvbiBsb2dVcGRhdGVzKGVsbSkgewogIHN3aXRjaChlbG0uY2hlY2tlZCkgewogICAgY2FzZSBmYWxzZTogY2xlYXJJbnRlcnZhbChsb2dJbnRlcnZhbCk7IGJyZWFrOwogICAgY2FzZSB0cnVlOiBsb2dJbnRlcnZhbCA9IHNldEludGVydmFsKHVwZGF0ZUxvZywgNTAwMCk7IGJyZWFrOwogICAgCiAgfQp9" + webUI["html/js/logs_ts.js"] = "dmFyIExvZyA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uICgpIHsKICAgIGZ1bmN0aW9uIExvZygpIHsKICAgIH0KICAgIExvZy5wcm90b3R5cGUuY3JlYXRlTG9nID0gZnVuY3Rpb24gKGVudHJ5KSB7CiAgICAgICAgdmFyIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJQUkUiKTsKICAgICAgICBpZiAoZW50cnkuaW5kZXhPZigiV0FSTklORyIpICE9IC0xKSB7CiAgICAgICAgICAgIGVsZW1lbnQuY2xhc3NOYW1lID0gIndhcm5pbmdNc2ciOwogICAgICAgIH0KICAgICAgICBpZiAoZW50cnkuaW5kZXhPZigiRVJST1IiKSAhPSAtMSkgewogICAgICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSA9ICJlcnJvck1zZyI7CiAgICAgICAgfQogICAgICAgIGlmIChlbnRyeS5pbmRleE9mKCJERUJVRyIpICE9IC0xKSB7CiAgICAgICAgICAgIGVsZW1lbnQuY2xhc3NOYW1lID0gImRlYnVnTXNnIjsKICAgICAgICB9CiAgICAgICAgZWxlbWVudC5pbm5lckhUTUwgPSBlbnRyeTsKICAgICAgICByZXR1cm4gZWxlbWVudDsKICAgIH07CiAgICByZXR1cm4gTG9nOwp9KCkpOwpmdW5jdGlvbiBzaG93TG9ncyhib3R0b20pIHsKICAgIHZhciBsb2cgPSBuZXcgTG9nKCk7CiAgICB2YXIgbG9ncyA9IFNFUlZFUlsibG9nIl1bImxvZyJdOwogICAgdmFyIGRpdiA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJjb250ZW50X2xvZyIpOwogICAgZGl2LmlubmVySFRNTCA9ICIiOwogICAgdmFyIGtleXMgPSBnZXRPYmpLZXlzKGxvZ3MpOwogICAga2V5cy5mb3JFYWNoKGZ1bmN0aW9uIChsb2dJRCkgewogICAgICAgIHZhciBlbnRyeSA9IGxvZy5jcmVhdGVMb2cobG9nc1tsb2dJRF0pOwogICAgICAgIGRpdi5hcHBlbmQoZW50cnkpOwogICAgfSk7CiAgICBzZXRUaW1lb3V0KGZ1bmN0aW9uICgpIHsKICAgICAgICBpZiAoYm90dG9tID09IHRydWUpIHsKICAgICAgICAgICAgdmFyIHdyYXBwZXIgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYm94LXdyYXBwZXIiKTsKICAgICAgICAgICAgd3JhcHBlci5zY3JvbGxUb3AgPSB3cmFwcGVyLnNjcm9sbEhlaWdodDsKICAgICAgICB9CiAgICB9LCAxMCk7Cn0KZnVuY3Rpb24gcmVzZXRMb2dzKCkgewogICAgdmFyIGNtZCA9ICJyZXNldExvZ3MiOwogICAgdmFyIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgICB2YXIgc2VydmVyID0gbmV3IFNlcnZlcihjbWQpOwogICAgc2VydmVyLnJlcXVlc3QoZGF0YSk7Cn0K" + webUI["html/css/base.css"] = "KiB7CiAgLXdlYmtpdC1hcHBlYXJhbmNlOiBub25lOwogIC1tb3otYXBwZWFyYW5jZTogbm9uZTsKICAtbXMtYXBwZWFyYW5jZTogbm9uZTsKICBmb250LWZhbWlseTogIkFyaWFsIiwgc2Fucy1zZXJpZjsKICBsZXR0ZXItc3BhY2luZzogMnB4OyAKfQoKLyoKOjotd2Via2l0LXNjcm9sbGJhciB7IAogICAgZGlzcGxheTogbm9uZTsgCn0KKi8KCjo6LXdlYmtpdC1zY3JvbGxiYXIgewogIHdpZHRoOiAxMnB4OwogIGhlaWdodDogMTJweDsKfQoKIAo6Oi13ZWJraXQtc2Nyb2xsYmFyLXRyYWNrIHsKICAtd2Via2l0LWJveC1zaGFkb3c6IGluc2V0IDAgMCA2cHggcmdiYSgwLDAsMCwwLjMpOyAKICBib3JkZXItcmFkaXVzOiA1cHg7CiAgICAKfQogCjo6LXdlYmtpdC1zY3JvbGxiYXItdGh1bWIgewogIGJvcmRlci1yYWRpdXM6IDVweDsKICAtd2Via2l0LWJveC1zaGFkb3c6IGluc2V0IDAgMCA2cHggcmdiYSgwLCAwLCAwLDAuNik7IAogIGJhY2tncm91bmQtY29sb3I6ICM0NDQ7Cn0KCjo6LXdlYmtpdC1zY3JvbGxiYXItdGh1bWI6aG92ZXIgewogIGJhY2tncm91bmQ6ICMzMzM7IAp9Cgo6Oi13ZWJraXQtc2Nyb2xsYmFyLWNvcm5lciB7IAogIGJhY2tncm91bmQ6IHRyYW5zcGFyZW50OyAKfQoKYSB7CiAgY29sb3I6ICMwMEU2RkY7Cn0KCmh0bWwsIGJvZHkgewogIGNvbG9yOiAjZmZmOwogIG1hcmdpbjogMHB4IGF1dG87CiAgaGVpZ2h0OiAxMDAlOwogIGZvbnQtc2l6ZTogMTRweDsKfQoKaDIgewogIGZvbnQtc2l6ZTogMjRweDsKICBsZXR0ZXItc3BhY2luZzogMnB4Owp9CgpoMyB7CiAgZm9udC1zaXplOiAyMnB4OwogIGxldHRlci1zcGFjaW5nOiAxcHg7Cn0KCmg0IHsKICBmb250LXNpemU6IDIwcHg7CiAgbGV0dGVyLXNwYWNpbmc6IDFweDsKICBsaW5lLWhlaWdodDogMS41ZW07Cgp9CgpoNSB7CiAgZm9udC1zaXplOiAxNnB4OwogIGxldHRlci1zcGFjaW5nOiAxcHg7CiAgbGluZS1oZWlnaHQ6IDEuMmVtOwogIG1hcmdpbjogMjVweCAwcHggMTBweCAwcHg7Cn0KCmhyIHsKICBib3JkZXI6IDA7CiAgaGVpZ2h0OiAxcHg7CiAgYmFja2dyb3VuZDogIzMzMzsKICBtYXJnaW46IDEwcHggMHB4Owp9CgpwIHsKICBtYXJnaW46IDJweDsKICBwYWRkaW5nOiAycHggNXB4Owp9CgpwcmUgewogIG1hcmdpbjogMHB4IDBweCA1cHggMHB4OwogIGZvbnQtc2l6ZTogMTJweDsKICBjb2xvcjogI2RkZDsKICBsZXR0ZXItc3BhY2luZzogMXB4OwogIHdoaXRlLXNwYWNlOiBwcmUtd3JhcDsKICBmb250LWZhbWlseTogbW9ub3NwYWNlOyAKICBmb250LXNpemU6IDEycHg7IAogIGZvbnQtc3R5bGU6IG5vcm1hbDsgCiAgZm9udC12YXJpYW50OiBub3JtYWw7IAogIGxpbmUtaGVpZ2h0OiAxLjZlbTsgCn0KCmxhYmVsIHsKICBtYXJnaW4tYm90dG9tOiAyMHB4OwogIGRpc3BsYXk6IGJsb2NrOwp9CgpsaSB7CiAgbGlzdC1zdHlsZS10eXBlOiBub25lOwogIGJhY2tncm91bmQtY29sb3I6ICMxMTE7CiAgcGFkZGluZzogMTBweCAyMHB4OwogIGN1cnNvcjogcG9pbnRlcjsKICBib3JkZXItbGVmdDogc29saWQgMnB4ICMxMTE7CiAgdHJhbnNpdGlvbjogYWxsIDAuMzsKfQoKbGk6aG92ZXIgewogIGJvcmRlci1jb2xvcjogIzAwRTZGRgp9CgpzZWxlY3QgewogIGN1cnNvcjogcG9pbnRlcjsKICB3aWR0aDogY2FsYygxMDAlICsgMnB4KTsKICBib3JkZXI6IHNvbGlkIDBweCAjMDBFNkZGOwogIGJvcmRlci1yYWRpdXM6IDBweDsKICBvdXRsaW5lOiBub25lOwogIGNvbG9yOiAjZmZmOwogIHBhZGRpbmc6IDlweCAxMHB4OwogIGRpc3BsYXk6YmxvY2s7CiAgYmFja2dyb3VuZC1jb2xvcjogIzMzMzsKICBmb250LXNpemU6IDE0cHg7CiAgbWFyZ2luOiA1cHggMHB4IDVweCAwcHg7Cn0KCnNlbGVjdDpmb2N1cyB7CiAgb3V0bGluZTogbm9uZTsKfQoKaW5wdXQgewogIC13ZWJraXQtYXBwZWFyYW5jZTogbm9uZTsKICBtYXJnaW46IDVweCAwcHg7CiAgcGFkZGluZzogMi41cHggMTBweDsKICBvdXRsaW5lOiBub25lOwogIGZvbnQtc2l6ZTogMTRweDsKfQoKaW5wdXRbdHlwZT1idXR0b25dLCBpbnB1dFt0eXBlPXN1Ym1pdF0gewogIGN1cnNvcjogcG9pbnRlcjsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDAwOwogIG1hcmdpbjogMTBweCAxMHB4OwogIHBhZGRpbmc6IDEwcHggMjVweDsKICBib3JkZXI6IHNvbGlkIDBweDsKICBib3JkZXItY29sb3I6ICMwMDA7CiAgYm9yZGVyLXJhZGl1czogM3B4OwogIG91dGxpbmU6IG5vbmU7CiAgY29sb3I6ICNmZmY7Cn0KCmlucHV0W3R5cGU9YnV0dG9uXTpmb2N1cyB7IAogICBvdXRsaW5lOiBub25lOwp9CgppbnB1dFt0eXBlPWJ1dHRvbl06aG92ZXIgewogIGJhY2tncm91bmQtY29sb3I6ICMwMEU2RkY7CiAgY29sb3I6ICMwMDA7Cn0KCmlucHV0W3R5cGU9YnV0dG9uXTpob3Zlci5kZWxldGUgIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiByZWQ7CiAgY29sb3I6ICNmZmY7Cn0KCmlucHV0W3R5cGU9dGV4dF0sIGlucHV0W3R5cGU9c2VhcmNoXSwgaW5wdXRbdHlwZT1wYXNzd29yZF0gewogIGNvbG9yOiAjZmZmOwogIHdpZHRoOiAtd2Via2l0LWNhbGMoMTAwJSAtIDBweCk7CiAgd2lkdGg6IC1tb3otY2FsYygxMDAlIC0gMHB4KTsKICB3aWR0aDogY2FsYygxMDAlIC0gMHB4KTsKICBvdXRsaW5lOiBub25lOwogIGJvcmRlcjogc29saWQgMXB4IHRyYW5zcGFyZW50OwogIGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OwogIGJvcmRlci1ib3R0b20tY29sb3I6ICM1NTU7CiAgYm9yZGVyLXJhZGl1czogMHB4OwogIHBhZGRpbmc6IDhweCAxMHB4Owp9CgppbnB1dFt0eXBlPSJjaGVja2JveCJdIHsKICBib3JkZXI6IHNvbGlkIDFweCAjMDBFNkZGOwogIGJhY2tncm91bmQtY29sb3I6ICMzMzM7CiAgaGVpZ2h0OiAyNXB4OwogIHdpZHRoOiAyNXB4OwogIGN1cnNvcjogcG9pbnRlcjsKICAvKgogIC13ZWJraXQtYXBwZWFyYW5jZTogY2hlY2tib3g7CiAgKi8KfQoKaW5wdXRbdHlwZT0iY2hlY2tib3giXTpjaGVja2VkIHsKICBjb2xvcjogI2ZmZjsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDBFNkZGOwogIC8qZGlzcGxheTogaW5saW5lLWJsb2NrOyovCn0KCmlucHV0W3R5cGU9ImNoZWNrYm94Il06YmVmb3JlIHsKICBwb3NpdGlvbjogaW5pdGlhbDsKICBsZWZ0OiAwcHg7CiAgbWFyZ2luLWxlZnQ6IC00cHg7CiAgY29udGVudDogIiAiOwp9CgppbnB1dFt0eXBlPSJjaGVja2JveCJdOmNoZWNrZWQ6YmVmb3JlIHsKICBwb3NpdGlvbjogaW5pdGlhbDsKICBsZWZ0OiAwcHg7CiAgbWFyZ2luLWxlZnQ6IC0zcHg7CiAgY29udGVudDogIuKckyI7CiAgY29sb3I6ICMwMDA7Cn0KCgppbnB1dFt0eXBlPWJ1dHRvbl0uY2FuY2VsIHsKICAKICBiYWNrZ3JvdW5kLWNvbG9yOiB0cmFuc3BhcmVudDsKICBib3JkZXItY29sb3I6IHJlZDsKfQoKaW5wdXRbdHlwZT1idXR0b25dLnNhdmV7CiAgYmFja2dyb3VuZC1jb2xvcjogIzExMTsKICBmbG9hdDogcmlnaHQ7Cn0KCgppbnB1dFt0eXBlPWJ1dHRvbl0uYmxhY2ssIGlucHV0W3R5cGU9c3VibWl0XS5ibGFja3sKICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDAwOwogIGJvcmRlci1jb2xvcjogIzAwMDsKfQoKaW5wdXRbdHlwZT1idXR0b25dLmNlbnRlcnsKICBtYXJnaW4tcmlnaHQ6IGF1dG87CiAgbWFyZ2luLWxlZnQ6IGF1dG87CiAgYmFja2dyb3VuZC1jb2xvcjogIzAwMDsKICBib3JkZXItY29sb3I6ICMwMDA7Cn0KCi5wb2ludGVyIHsKICBjdXJzb3I6IHBvaW50ZXI7Cn0KCi5wb2ludGVyOmhvdmVyIHsKICBjb2xvcjogIzAwRTZGRjsKICBjdXJzb3I6IHBvaW50ZXI7Cn0KCi5zb3J0VGhpcyB7CiAgY29sb3I6ICMwMEU2RkY7Cn0KCi53NDBweCB7CiAgbWF4LXdpZHRoOiA0MHB4Owp9CgoudzUwcHggewogIG1heC13aWR0aDogNTBweDsKfQoKLnc4MHB4IHsKICBtYXgtd2lkdGg6IDgwcHg7Cn0KCi53MTUwcHggewogIG1heC13aWR0aDogMTUwcHg7Cn0KCi53MjAwcHggewogIG1heC13aWR0aDogMjAwcHg7CiAgbWluLXdpZHRoOiAxMDBweDsKICB3aWR0aDogMjAwcHg7CiAgb3ZlcmZsb3cteDogaGlkZGVuOwogIHdoaXRlLXNwYWNlOiBub3dyYXA7CiAgb3ZlcmZsb3c6IGhpZGRlbjsKICB0ZXh0LW92ZXJmbG93OiBlbGxpcHNpczsKfQoKLnczMDBweCB7CiAgbWF4LXdpZHRoOiAzMDBweDsKfQoKLncyMjBweCB7CiAgbWF4LXdpZHRoOiAyMjBweDsKICBjdXJzb3I6IGFsaWFzOwp9CgouZm9vdGVyIHsKICBmb250LXNpemU6IDEwcHg7Cn0KCi5jZW50ZXIgewogIHRleHQtYWxpZ246IGNlbnRlcjsKfQoKLnNjcmVlbkxvZ0hpZGRlbiB7CiAgdHJhbnNmb3JtOiB0cmFuc2xhdGUoMHB4LCAtMTEwcHgpOwp9CgouYm9yZGVyU3BhY2UgewogIG1hcmdpbi1ib3R0b206IDMwcHg7Cn0KCi5ibG9jayB7Cgp9Cgoubm9uZSB7CiAgZGlzcGxheTogbm9uZTsKfQoKCi5ub3RWaXNpYmxlIHsKICBoZWlnaHQ6IDBweDsKICBkaXNwbGF5OiBub25lOwogIG9wYWNpdHk6IDA7CiAgYm9yZGVyLWJvdHRvbTogIzAwMCBzb2xpZCAwcHg7CiAgCn0KCi52aXNpYmxlIHsKICBvcGFjaXR5OiAxOwogIGRpc3BsYXk6IGJsb2NrOwogIGJvcmRlci1ib3R0b206ICM0NDQgc29saWQgMXB4OyAKICBwYWRkaW5nOiAxMHB4Owp9CgouZmxvYXRSaWdodCB7CiAgZmxvYXQ6IHJpZ2h0Owp9CgouZmxvYXRMZWZ0IHsKICBmbG9hdDogbGVmdDsKfQoKLm1lbnUtYWN0aXZlIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjMDBFNkZGOwp9CgoubWVudS1ub3RBY3RpdmUgewogIAp9CgojYnJhbmNoIHsKICBkaXNwbGF5OiB0YWJsZTsKICBtYXJnaW46IGF1dG87CiAgY29sb3I6IHJlZDsKfQoKI2ludGVyYWN0aW9uIHsKICBtYXJnaW4tYm90dG9tOiAxMDBweDsKICB0ZXh0LWFsaWduOiBjZW50ZXI7CiAgYm9yZGVyLWJvdHRvbTogc29saWQgMHB4ICM3Nzc7Cn0KCgouaGFsZiB7CiAgZGlzcGxheTogYmxvY2s7CiAgd2lkdGg6IDQ1JTsKfQoKLm1lbnUgewogIGJvcmRlcjogc29saWQgMXB4ICMwMEU2RkY7Cn0KCi5pbmZvTXNnIHsKICBjb2xvcjogI2FhYTsKfQoKLmVycm9yTXNnIHsKICBjb2xvcjogcmVkOwp9Cgoud2FybmluZ01zZyB7CiAgY29sb3I6IHllbGxvdzsKfQoKLmRlYnVnTXNnIHsKICBjb2xvcjogbWFnZW50YTsKfQoKLk5ld3MsIC5Nb3ZpZSwgLlNlcmllcywgLlNwb3J0cywgLktpZHMgewogIGJvcmRlci1sZWZ0OiBzb2xpZCAycHgKfQoKLk5ld3MgewogIGJvcmRlci1jb2xvcjogdG9tYXRvCn0KCi5Nb3ZpZSB7CiAgYm9yZGVyLWNvbG9yOiByb3lhbGJsdWU7Cn0KCi5TZXJpZXMgewogIGJvcmRlci1jb2xvcjogZ29sZDsKfQoKLlNwb3J0cyB7CiAgYm9yZGVyLWNvbG9yOiB5ZWxsb3dncmVlbjsKfQoKLktpZHMgewogIGJvcmRlci1jb2xvcjogbWVkaXVtcHVycGxlOwp9CgovKiBMb2FkaW5nICovCiNsb2FkaW5nIHsKICBsZWZ0OiAwcHg7CiAgdG9wOiAwcHg7CiAgei1pbmRleDogMTAwMDA7CiAgcG9zaXRpb246IGFic29sdXRlOwogIGJhY2tncm91bmQtY29sb3I6IHJnYmEoMCwwLDAsIDAuOCk7CiAgbWFyZ2luOiBhdXRvOwogIHdpZHRoOiAxMDAlOwogIGhlaWdodDogMTAwJTsKfQoKCi5sb2FkZXIgewogIGJvcmRlcjogNXB4IHNvbGlkIHRyYW5zcGFyZW50OwogIGJvcmRlci1yYWRpdXM6IDUwJTsKICBib3JkZXItdG9wOiA1cHggc29saWQgIzAwRTZGRjsKICBib3JkZXItYm90dG9tOiA1cHggc29saWQgIzAwRTZGRjsKICB3aWR0aDogNTBweDsKICBoZWlnaHQ6IDUwcHg7CiAgLXdlYmtpdC1hbmltYXRpb246IHNwaW4gMS4ycyBsaW5lYXIgaW5maW5pdGU7CiAgYW5pbWF0aW9uOiBzcGluIDEuMnMgbGluZWFyIGluZmluaXRlOwoKICBwb3NpdGlvbjogZml4ZWQ7CiAgbWFyZ2luOiBhdXRvOwoKICB0b3A6IDA7CiAgcmlnaHQ6IDA7CiAgYm90dG9tOiAwOwogIGxlZnQ6IDA7CiAgCn0KCkAtd2Via2l0LWtleWZyYW1lcyBzcGluIHsKICAwJSB7IC13ZWJraXQtdHJhbnNmb3JtOiByb3RhdGUoMGRlZyk7IH0KICAxMDAlIHsgLXdlYmtpdC10cmFuc2Zvcm06IHJvdGF0ZSgzNjBkZWcpOyB9Cn0KCkBrZXlmcmFtZXMgc3BpbiB7CiAgMCUgeyB0cmFuc2Zvcm06IHJvdGF0ZSgwZGVnKTsgfQogIDEwMCUgeyB0cmFuc2Zvcm06IHJvdGF0ZSgzNjBkZWcpOyB9Cn0K" + webUI["html/css/screen.css"] = "bmF2IGltZyB7CiAgZGlzcGxheTogYmxvY2s7CiAgbWF4LWhlaWdodDogMjBweDsKICBtYXgtd2lkdGg6IDIwcHg7CiAgZmxvYXQ6IGxlZnQ7Cn0KCm5hdiBwIHsKICB0ZXh0LWFsaWduOiBsZWZ0OwogIHBhZGRpbmc6IDBweCAzMHB4Owp9CgojbGF5b3V0IHsKICBkaXNwbGF5OiBibG9jazsKICBoZWlnaHQ6IDEwMCU7Cn0KCgoubGF5b3V0LWxlZnQgewogIGRpc3BsYXk6IGJsb2NrOwogIG1pbi13aWR0aDogMTUwcHg7CiAgbWF4LXdpZHRoOiAyMCU7CiAgYmFja2dyb3VuZC1jb2xvcjogIzExMTsKICBoZWlnaHQ6IGluaGVyaXQ7CiAgZmxvYXQ6IGxlZnQ7Cn0KCi5sYXlvdXQtcmlnaHQgewogIGRpc3BsYXk6IGJsb2NrOwogIGJhY2tncm91bmQtY29sb3I6ICM0NDQ7Cn0KCiNtZW51LXdyYXBwZXIgewogIGhlaWdodDogMTAwJTsKfQoKCiNsb2dvIHsKICBkaXNwbGF5OiBibG9jazsKICBtaW4td2lkdGg6IDE4MHB4OwogIHdpZHRoOiAxMDAlOwogIGhlaWdodDogMTAwcHg7CiAgYmFja2dyb3VuZDogdXJsKCIuLi9pbWcvbG9nb193XzYwMHgyMDAucG5nIik7CiAgYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDsKICBiYWNrZ3JvdW5kLXBvc2l0aW9uOiBjZW50ZXI7IAogIGJhY2tncm91bmQtc2l6ZTogMTAwJTsKfQoKCiNwYWdlIHsKICBtYXgtd2lkdGg6IDk1MHB4OwogIG1hcmdpbjogYXV0bzsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjNDQ0OwoKICAvKgogIGhlaWdodDogLXdlYmtpdC1jYWxjKDEwMCUgLSAxMzBweCk7CiAgaGVpZ2h0OiAtbW96LWNhbGMoMTAwJSAtIDEzMHB4KTsKICBoZWlnaHQ6IGNhbGMoMTAwJSAtIDEzMHB4KTsKICAqLwogIAogIG1pbi1oZWlnaHQ6IC13ZWJraXQtY2FsYygxMDAlIC0gMTIwcHgpOwogIG1pbi1oZWlnaHQ6IC1tb3otY2FsYygxMDAlIC0gMTIwcHgpOwogIG1pbi1oZWlnaHQ6IGNhbGMoMTAwJSAtIDEyMHB4KTsKICAKICAKICBib3gtc2hhZG93OiAwcHggNXB4IDVweCAjMjIyOwogIAp9CgojdWlTZXR0aW5nIHsKICBmbG9hdDogcmlnaHQ7CiAgbWFyZ2luLXJpZ2h0OiAyNXB4Owp9CgojYm94IGlucHV0W3R5cGU9dGV4dF0sICNib3ggaW5wdXRbdHlwZT1wYXNzd29yZF0gewogIHdpZHRoOiAtd2Via2l0LWNhbGMoMTAwJSAtIDIwcHgpOwogIHdpZHRoOiAtbW96LWNhbGMoMTAwJSAtIDIwcHgpOwogIHdpZHRoOiBjYWxjKDEwMCUgLSAyMHB4KTsKfQoKI2JveCBpbnB1dFt0eXBlPXN1Ym1pdF17CiAgbWFyZ2luOiA1MHB4IGF1dG87Cn0KCiNzZXR0aW5ncyB7CiAgZGlzcGxheTogYmxvY2s7CiAgcGFkZGluZzogMTBweCAxMHB4Owp9Cgojc2V0dGluZ3MgaDUgewogIG1hcmdpbjogNTBweCAwcHggMTBweCAwcHg7Cn0KCiNjb250ZW50LWludGVyYWN0aW9uIC5zZWFyY2ggewogIHdpZHRoOiAyMDBweDsKICBib3JkZXI6IDFweCBzb2xpZCAjMDAwOwogIHBhZGRpbmc6IDlweDsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjMzMzOwogIG1hcmdpbjogMTBweDsKICBmbG9hdDogcmlnaHQ7CiAgYm9yZGVyLXJhZGl1czogM3B4OwoKfQoKI215U3RyZWFtcyB7CiAgcG9zaXRpb246IGZpeGVkOwogIGJvdHRvbTogMHB4OwogIGJhY2tncm91bmQtY29sb3I6ICMxMTE7CiAgd2lkdGg6IDEwMCU7CiAgbWF4LXdpZHRoOiA5NTBweDsKCiAgLyoKICBtYXgtaGVpZ2h0OiAxMDBweDsKICAqLwogIG1hcmdpbi1ib3R0b206IDBweDsKfQoKI215U3RyZWFtcyBpbWcgewogIHdpZHRoOiA0JTsKICBwYWRkaW5nOiAycHggNXB4OwogIGN1cnNvcjogcG9pbnRlcjsKICBmbG9hdDogcmlnaHQ7Cn0KCiNzZXR0aW5ncy1mb290ZXIgewoKfQoKCi8qIFdpemFyZCovCiNib3ggewogIGJhY2tncm91bmQtY29sb3I6ICM0NDQ7CiAgbWluLWhlaWdodDogNDAwcHg7CiAgCiAgZGlzcGxheTogZmxleDsKICBmbGV4LWRpcmVjdGlvbjogY29sdW1uOwogIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsKfQoKI2JveCBwewogIHBhZGRpbmc6IDEwcHggMHB4Owp9CgojYm94LWZvb3RlciB7CiAgbWFyZ2luLXRvcDogYXV0bzsKfQoKI2JveC1mb290ZXIgewogIG1hcmdpbjogYXV0bzsKICBwYWRkaW5nOiAxMHB4Owp9CgojaGVhZGxpbmUgewogIGJhY2tncm91bmQtY29sb3I6ICMyMjI7CiAgYm9yZGVyLWJvdHRvbTogc29saWQgMnB4ICMyMjI7CiAgdHJhbnNpdGlvbjogYWxsIDAuNXM7CiAgcGFkZGluZzogMTBweCAwcHg7CiAgZGlzcGxheTogYmxvY2s7Cn0KCiNjb250ZW50IHsKICBkaXNwbGF5OiBibG9jazsKICBvdmVyZmxvdzogYXV0bzsKICBwYWRkaW5nOiAxMHB4Owp9CgovKiAtLS0gKi8KCgojY2xpZW50SW5mbywgI2FjdGl2ZVN0cmVhbXMsICNpbmFjdGl2ZVN0cmVhbXMgewogIGZvbnQtZmFtaWx5OiBtb25vc3BhY2U7CiAgZGlzcGxheTogYmxvY2s7CiAgZm9udC1zaXplOiA5cHg7CiAgYmFja2dyb3VuZC1jb2xvcjogIzExMTsKICBjb2xvcjogIzAwRTZGRjsKICBib3JkZXItYm90dG9tOiBzb2xpZCAwcHg7OwogIHBhZGRpbmc6IDBweDsKICBsZXR0ZXItc3BhY2luZzogMXB4OwogIG92ZXJmbG93LXg6IGhpZGRlbjsKICBib3JkZXItc3BhY2luZzogNHB4IDRweDsKICBib3JkZXItYm90dG9tOiBzb2xpZCAxcHggIzQ0NDsKfQoKI215U3RyZWFtc0JveCB7CiAgcG9zaXRpb246IHJlbGF0aXZlOwogIHBhZGRpbmc6IDBweDsKICAvKmhlaWdodDogMTAwcHg7Ki8KICBtYXgtaGVpZ2h0OiAxNTBweDsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjMTExOwogIGNvbG9yOiB3aGl0ZTsKICBkaXNwbGF5OmZsZXg7CiAganVzdGlmeS1jb250ZW50OmNlbnRlcjsKICBhbGlnbi1pdGVtczpjZW50ZXI7Cn0KCiNvcGVuU3RyZWFtcyB7CiAgd2lkdGg6IDIwcHg7CiAgaGVpZ2h0OiAyMHB4OwogIGN1cnNvcjogcG9pbnRlcjsKICBmbG9hdDogcmlnaHQ7CiAgcG9zaXRpb246IGFic29sdXRlOwogIHJpZ2h0OiAwcHg7CiAgYm90dG9tOiAwcHg7CiAgYmFja2dyb3VuZDogdXJsKCIuLi9pbWcvdG91Y2gucG5nIik7CiAgYmFja2dyb3VuZC1jb2xvcjogIzExMTsKICAKICBiYWNrZ3JvdW5kLXBvc2l0aW9uOiBib3R0b20gcmlnaHQ7ICAKfQoKI2FsbFN0cmVhbXMgewogIHdpZHRoOiAxMDAlOwogIGhlaWdodDogMTAwJTsKICBwYWRkaW5nOiAycHg7ICAgICAKfQoKI2FjdGl2ZVN0cmVhbXMsICNpbmFjdGl2ZVN0cmVhbXMgewogIG92ZXJmbG93LXk6IHNjcm9sbDsKICB3aWR0aDogNTAlOwogIG1heC1oZWlnaHQ6IDEwMHB4OwogIGZsb2F0OiBsZWZ0Owp9CgojYWN0aXZlU3RyZWFtcyAudGRLZXksICNpbmFjdGl2ZVN0cmVhbXMgLnRkS2V5IHsKICB3aWR0aDogNzVweDsKfQoKCgoKI2luYWN0aXZlU3RyZWFtcyAudGRLZXkgewogIGNvbG9yOiByZWQ7Cn0KCiNjbGllbnRJbmZvIC50ZFZhbCwgI2xvZ0luZm8gLnRkVmFsLCAjYWN0aXZlU3RyZWFtcyAudGRWYWwsICNpbmFjdGl2ZVN0cmVhbXMgLnRkVmFsLCAjbWFwcGluZ0luZm8gLnRkVmFsewogIGNvbG9yOiAjYWFhOwogIHdoaXRlLXNwYWNlOiBpbmhlcml0Owp9CgojYm94LXdyYXBwZXIgewogIGRpc3BsYXk6IGlubGluZS1ibG9jazsKICB3aWR0aDogMTAwJTsKICAKICBvdmVyZmxvdy15OiBzY3JvbGw7Cn0KCiNjb250ZW50X3RhYmxlLCAjbWFwcGluZy1kZXRhaWwtdGFibGUsICNjb250ZW50X3RhYmxlIHsKICBkaXNwbGF5OiB0YWJsZTsKICAKICBib3JkZXItY29sbGFwc2U6IGNvbGxhcHNlOwogIG92ZXJmbG93LXk6IHNjcm9sbDsKICB3aWR0aDogMTAwJTsKfQoKCiNjb250ZW50X3RhYmxlIC5jb250ZW50X3RhYmxlX2hlYWRlciB7CiAgYmFja2dyb3VuZC1jb2xvcjogIzMzMzsKICBoZWlnaHQ6IDUwcHg7CiAgYm9yZGVyLWJvdHRvbTogc29saWQgMXB4ICMxMTE7CiAgYm9yZGVyLWxlZnQ6IHNvbGlkIDNweCAjMzMzOwogIGN1cnNvcjogYXV0bzsKCn0KCgp0Ym9keSB7CiAgd2lkdGg6IDEwMCU7Cn0KCgoudGFibGVFbGxpcHNpcyB7CiAgd2lkdGg6IDE1MHB4OwogIG92ZXJmbG93OiBoaWRkZW47CiAgdGV4dC1vdmVyZmxvdzogZWxsaXBzaXM7CiAgd2hpdGUtc3BhY2U6IG5vd3JhcDsKfQoKI2NvbnRlbnRfdGFibGUgaW1nIHsKICBkaXNwbGF5OiBibG9jazsKICBtYXgtaGVpZ2h0OiAyOHB4OwogIG1hcmdpbi1sZWZ0OiBhdXRvOwogIG1hcmdpbi1yaWdodDogYXV0bzsKICBtYXgtd2lkdGg6IDMwcHg7Cn0KCiNjb250ZW50X3RhYmxlIHRyewogIGJvcmRlci1sZWZ0OiBzb2xpZCAzcHggNDQ0OwogIGJvcmRlci1ib3R0b206IHNvbGlkIDFweCAjMzMzOwogIGN1cnNvcjogcG9pbnRlcjsKfQoKI2NvbnRlbnRfdGFibGUgdHI6aG92ZXIgewogIGJhY2tncm91bmQtY29sb3I6ICMzMzM7Cn0KCiNjb250ZW50X3RhYmxlIHRkIHsKCiAgcGFkZGluZzogMHB4IDJweDsKfQoKI2NvbnRlbnRfdGFibGUgaW5wdXRbdHlwZT10ZXh0XXsKICB3aWR0aDogODAlOwogIGJvcmRlcjogMHB4OwogIGJhY2tncm91bmQtY29sb3I6ICMzMzM7CiAgbWFyZ2luLWxlZnQ6IDVweDsKICB0ZXh0LWFsaWduOiBsZWZ0Owp9CgojY29udGVudF90YWJsZSBpbnB1dFt0eXBlPWNoZWNrYm94XXsKICBtYXgtd2lkdGg6IDI1cHg7CiAgbWFyZ2luOiBhdXRvOwp9CgoKLnNob3dCdWxrIHsKICBkaXNwbGF5OiBibG9jazsKfQoKLmhpZGVCdWxrIHsKICBkaXNwbGF5OiBub25lOwp9Cgoubm9CdWxrIHsKCn0KCiNjb250ZW50X3RhYmxlIHRyLmFjdGl2ZUVQR3sKICBib3JkZXItbGVmdDogc29saWQgM3B4IGxhd25ncmVlbjsKfQoKI2NvbnRlbnRfdGFibGUgdHIubm90QWN0aXZlRVBHewogIGJvcmRlci1sZWZ0OiBzb2xpZCAzcHggcmVkOwp9CgoKI2xvZ1NjcmVlbiBwewogIHdoaXRlLXNwYWNlOiBwcmU7CiAgZm9udC1zaXplOiAxMHB4OwogIC8qCiAgbGluZS1oZWlnaHQ6IDEuNmVtOwogIGZvbnQtZmFtaWx5OiAiQXJpYWwiLCBzYW5zLXNlcmlmOwogICovCiAgbGV0dGVyLXNwYWNpbmc6IDFweDsKICBmb250LWZhbWlseTogbW9ub3NwYWNlOyAKICBmb250LXNpemU6IDEycHg7IAogIGZvbnQtc3R5bGU6IG5vcm1hbDsgCiAgZm9udC12YXJpYW50OiBub3JtYWw7IAogIGxpbmUtaGVpZ2h0OiAxLjZlbTsgCn0KCiNwb3B1cCB7CiAgYmFja2dyb3VuZC1jb2xvcjogcmdiYSgwLCAwLCAwLCAwLjQpOwogIHBvc2l0aW9uOiBmaXhlZDsKICBsZWZ0OiAwcHg7CiAgd2lkdGg6IDEwMCU7CiAgei1pbmRleDogMTAwOwogIGhlaWdodDogMTAwJTsKfQoKI21hcHBpbmctZGV0YWlsLCAjdXNlci1kZXRhaWwsICNmaWxlLWRldGFpbCwgI3BvcHVwLWN1c3RvbSB7CiAgYm94LXNoYWRvdzogMHB4IDVweCA0MHB4ICMwMDA7CiAgbWFyZ2luLXRvcDogMjBweDsKICBtYXJnaW4tbGVmdDogYXV0bzsKICBtYXJnaW4tcmlnaHQ6IGF1dG87CiAgCiAgbWF4LXdpZHRoOiA2MDBweDsKICBiYWNrZ3JvdW5kLWNvbG9yOiAjMjIyOwogIHBhZGRpbmc6IDEwcHg7CiAgb3ZlcmZsb3c6YXV0bzsKfQoKI3BvcHVwLWN1c3RvbSBoMyB7CiAgdGV4dC1hbGlnbjogY2VudGVyOwp9CgojZmlsZS1kZXRhaWwgaW5wdXRbdHlwZT10ZXh0XSB7CiAgd2lkdGg6IC13ZWJraXQtY2FsYygxMDAlIC0gMjBweCk7CiAgd2lkdGg6IC1tb3otY2FsYygxMDAlIC0gMjBweCk7CiAgd2lkdGg6IGNhbGMoMTAwJSAtIDIwcHgpOwp9CgojbWFwcGluZy1kZXRhaWwgaW1nIHsKICBkaXNwbGF5OiBibG9jazsKICBtYXgtaGVpZ2h0OiAzMHB4OwogIG1hcmdpbi1ib3R0b206IDIwcHg7CiAgbWFyZ2luLWxlZnQ6IGF1dG87CiAgbWFyZ2luLXJpZ2h0OiBhdXRvOwp9CgojcG9wdXAtY3VzdG9tIGlucHV0W3R5cGU9dGV4dF0sICNwb3B1cC1jdXN0b20gaW5wdXRbdHlwZT1wYXNzd29yZF0sICNtYXBwaW5nLWRldGFpbCBpbnB1dFt0eXBlPXRleHRdLCAjY29udGVudF9zZXR0aW5ncyBpbnB1dFt0eXBlPXRleHRdLCAjY29udGVudF9zZXR0aW5ncyBpbnB1dFt0eXBlPXBhc3N3b3JkXXsKICBib3JkZXI6IHNvbGlkIDFweDsKICBib3JkZXItY29sb3I6IHRyYW5zcGFyZW50OwogIGJhY2tncm91bmQtY29sb3I6ICMzMzM7CiAgdGV4dC1hbGlnbjogbGVmdDsKICB3aWR0aDogLXdlYmtpdC1jYWxjKDEwMCUgLSAyMHB4KTsKICB3aWR0aDogICAgLW1vei1jYWxjKDEwMCUgLSAyMHB4KTsKICB3aWR0aDogICAgICAgICBjYWxjKDEwMCUgLSAyMHB4KTsKfQoKI3BvcHVwLWN1c3RvbSBpbnB1dFt0eXBlPXRleHRdLm5vdEF2YWlsYWJsZSB7CiAgYm9yZGVyLWNvbG9yOiByZWQ7CiAgY29sb3I6ICM2NjY7CiAgY3Vyc29yOiBub3QtYWxsb3dlZDsKfQoKI21hcHBpbmctZGV0YWlsLXRhYmxlLCAjdXNlci1kZXRhaWwtdGFibGUgewogIGRpc3BsYXk6IGlubGluZS10YWJsZTsKICB3aWR0aDogMTAwJTsKfQoKI3BvcHVwLWN1c3RvbSB0YWJsZSwgI2NvbnRlbnRfc2V0dGluZ3MgdGFibGUgewogIGRpc3BsYXk6IGlubGluZS10YWJsZTsKICB0YWJsZS1sYXlvdXQ6IGZpeGVkOwogIHdpZHRoOiAxMDAlOwp9CgoKI21hcHBpbmctZGV0YWlsLXRhYmxlIHRkLCAjdXNlci1kZXRhaWwtdGFibGUgdGQgewogIHBhZGRpbmc6IDEwcHggMHB4OwoKfQoKI21hcHBpbmctZGV0YWlsLXRhYmxlIHRkLmxlZnQsICN1c2VyLWRldGFpbC10YWJsZSB0ZC5sZWZ0LCAjcG9wdXAtY3VzdG9tIHRkLmxlZnQgewogIHdpZHRoOiAzOCU7Cn0KCi5pbnRlcmFjdGlvbiwgI2ludGVyYWN0aW9uIHsKICBtYXJnaW4tdG9wOiAyMHB4OwogIGRpc3BsYXk6IGlubGluZS1mbGV4OwogIGZsb2F0OiByaWdodDsKfQoKLmludGVyYWN0aW9uIGlucHV0W3R5cGU9YnV0dG9uXSwgLmludGVyYWN0aW9uIGlucHV0W3R5cGU9c3VibWl0XSB7CiAgYmFja2dyb3VuZC1jb2xvcjogIzAwMDsKICBtaW4td2lkdGg6IDEwMHB4OwogIG1hcmdpbjogMHB4IDEwcHg7CiAgdGV4dC1hbGlnbjogY2VudGVyOwp9Cgojbm90aWZpY2F0aW9uIHsKICBkaXNwbGF5OiBibG9jazsKICBwb3NpdGlvbjogZml4ZWQ7CiAgcmlnaHQ6IDBweDsKICBoZWlnaHQ6IDEwMCU7CiAgd2lkdGg6IDI1MHB4OwogIAogIGJhY2tncm91bmQtY29sb3I6ICMyMjI7CiAgYm94LXNoYWRvdzogMHB4IDBweCAyMHB4ICMwMDA7Cn0KCiNub3RpZmljYXRpb24gaDUgewogIGJhY2tncm91bmQtY29sb3I6ICMxMjEyMTI7CiAgcGFkZGluZzogNXB4IDEwcHggNXB4IDEwcHg7Cn0KCiNub3RpZmljYXRpb24gcHJlIHsKICBwYWRkaW5nOiAwcHggMTBweCAwcHggMTBweDsKfQoKI25vdGlmaWNhdGlvbiBwIHsKICBmb250LXNpemU6IDEwIHB4OwogIG1hcmdpbjogMHB4OwogIHBhZGRpbmc6IDBweCAxMHB4IDVweCAxMHB4Owp9Cgojbm90aWZpY2F0aW9uIC5lbGVtZW50IHsKICAvKnBhZGRpbmc6IDBweCA1cHg7Ki8KICBtYXJnaW46IDVweCA1cHg7CiAgYm9yZGVyLXJhZGl1czogNXB4OwogIGJhY2tncm91bmQtY29sb3I6ICMxODE4MTg7CiAgYm9yZGVyLWxlZnQ6IDEwcHggc29saWQgZ3JlZW47Cn0KCgpAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDYyMHB4KXsKICBib2R5IHsKICAgIHdpZHRoOiAxMDAlOwogICAgYmFja2dyb3VuZC1jb2xvcjogIzQ0NDsKICB9CgogIGgxIHsKICAgIGZvbnQtc2l6ZTogMjZweDsKICAgIGxldHRlci1zcGFjaW5nOiAzcHg7CiAgfQoKICBuYXYgcCB7CiAgICBkaXNwbGF5OiBibG9jazsKICB9CgoKCiAgI2hlYWRlcl9jb25maWcgewogICAgZGlzcGxheTogYmxvY2s7CiAgICBoZWlnaHQ6IDEwMHB4OwogICAgYmFja2dyb3VuZDogdXJsKCIuLi9pbWcvbG9nb193XzYwMHgyMDAucG5nIik7CiAgICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0OwogICAgCiAgICBiYWNrZ3JvdW5kLXNpemU6IDMwMHB4IDEwMHB4OwogIH0KCiAgI3NjcmVlbkxvZyB7CiAgICBtYXJnaW4tbGVmdDogMzAwcHg7CgogICAgdHJhbnNpdGlvbjogbm9uZTsKICAgIGJhY2tncm91bmQtY29sb3I6IHRyYW5zcGFyZW50OwogICAgYm9yZGVyLWJvdHRvbTogc29saWQgMXB4IHRyYW5zcGFyZW50OwogICAgYm94LXNoYWRvdzogMHB4IDBweCAwcHggIzIyMjsKICB9CgogICNzZXR0aW5ncyB7CiAgICAvKgogICAgaGVpZ2h0OiAtd2Via2l0LWNhbGMoMTAwJSAtIDEwMHB4KTsKICAgIGhlaWdodDogLW1vei1jYWxjKDEwMCUgLSAxMDBweCk7CiAgICBoZWlnaHQ6IGNhbGMoMTAwJSAtIDEwMHB4KTsKICAgICovCiAgICBwb3NpdGlvbjogcmVsYXRpdmU7CiAgICBvdmVyZmxvdzogYXV0bzsKICB9CgoKICAuc2NyZWVuTG9nSGlkZGVuIHsKICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlKDBweCwgMHB4KTsKICB9CgoKICAjYm94IHsKICAgIGRpc3BsYXk6IGJsb2NrOwogICAgbWluLWhlaWdodDogNTAwcHg7CiAgICBtYXgtd2lkdGg6IDUwMHB4OwogICAgbWFyZ2luOiAxMHB4IGF1dG87CiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjNDQ0OwogICAgYm94LXNoYWRvdzogMHB4IDVweCA1cHggIzIyMjsKCiAgICBkaXNwbGF5OiBmbGV4OwogICAgZmxleC1kaXJlY3Rpb246IGNvbHVtbjsKICB9CgogICNzZXR0aW5ncywgI3NldHRpbmdzLWZvb3RlciB7CiAgICAKICB9Cn0K" + webUI["html/img/log.png"] = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAsSwAALEsBpT2WqQAABCRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjU8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjI4ODwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+Mjg4PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NTA8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGRjOnN1YmplY3Q+CiAgICAgICAgICAgIDxyZGY6QmFnLz4KICAgICAgICAgPC9kYzpzdWJqZWN0PgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0xMC0xNFQxMToxMDo0MjwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CkP32mEAAANASURBVGgF7ZlPiE1RHMffw1AYhiL5k1HEijJiYaFZiHoyFspW2RFRkkSZGgtFWJIwi9FYmo0VOywkKTbKgjSlWcifRCPX51dv6s2d33md77vvXjfmV9859/7u93x/v98959x73p1KRbAkSargAhgDH0E/qIYkuHYEvAPGvwbmNOEerXM/0Q6BRSFuZj/ix0DaDnvCkGppIucDAe4OhzvkcUO+GaELAf8ux7/b8Zlrr+Pvc3zm6nX8NccXdKmFLHaUPJ/Ruhyu5zNap8Nd6PiCLrUQTyi0Rjy/5/M0Zd+smB7M32XwtgBr07aC6yfSTs7XO77OALfH4VbgHsD/HLytVquJx4nyIdQFBsHftscksK5Z0sGhpuN8Or4Aa5sJFHjtO7G2MjKvvZjN1sglOpSlCMt9LrDZ4S4Hd0Qg24vrK+gAZbPtjMqTdFKhEdkIsYxFWP720JlioUK6pzDL41jtpRIqxOOWxecuB3fhRGT8E85YBK8VygI6GSRTCxlH3TaJgyw4O2678aCxO94LbNPovYDdmOrUukIBN/MqwjJEOwGPOHR31W4VONVCLEBR9lAJpBbSrYhn5K5R+quFnGUOr1ICtMIlxjz6XVb6qot9JeKvCPSAdlQJJHDtd8hOIN0wtRDLxx6Ntr0ulalTq1TJNyYzXUjj3SjD8X87Iva7uR8s5+2bi6FtX1oOgi8g2tSn1g2yPx+t3gIR/c90s1+C1vuO/YkxdWrdjxFtE2dE0VELWaqIZ+QuUfqrhZxmyKUvgEoyE1xizOR4YOI8plXXyAZEbYsyTJvXFsUW+x6wGUSbWogJ237rZHSEgojq1CooLT3MdCH6Pcu3xz8zIq0s9lvc2+sgz6dWH/rnQPB/jlybZGoh99hCHJqk0P6TD0jaI/4b7dVYeXVq2bemokyKpRZiHwWKMimWWshxhryjoEpOKXHUNbIN8WcUc5s2r8Vue7l9oAaiTS3EhDeB6EUYnUlGojq1MobLr3uokB/5hcys7OYWKuRl5nD5Cbi5uYXw0ntPHoay2S8Seuol5RZSJ0r/n/DEc/BdrN9kTZrH7BkwDspgwyQxW6uggU3nHnAXvAG/QZE2SrARsL8hJffwDxM0mNDPvT8IAAAAAElFTkSuQmCC" + webUI["html/img/settings.png"] = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAsSwAALEsBpT2WqQAABCRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjU8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjI4ODwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+Mjg4PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NTA8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGRjOnN1YmplY3Q+CiAgICAgICAgICAgIDxyZGY6QmFnLz4KICAgICAgICAgPC9kYzpzdWJqZWN0PgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0wOC0xMFQxODowODo4OTwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Ckxt87EAAAS7SURBVGgFzZpdiFVVGIbn5N+IMgVhaRLqWI7mQIJoIVRgOhQZNDAhWcbcVuPV3BRE6I14440iQQSaN0p6kz8zI0k5inUhZSSaYOVYODMo5eiMOOPf+LzH2cPMPmvttdbe53jOBy9rr2+938/ae51vr733qaoqgQwPD68B/wDJfXAP3AE94JMShCy+SxJ9CvSDJFlU7MiPFdsh/hrBdIffBsd48HApJjLPI4tnPThBlFJMZI5HBuWZCIt9AmgEzeAJR6JzHeMadnKI8wbYCz4Fkz18JlNwMgf8BCL5j4O3bVaMdUfEhPZqgv1U7HbEbDvoT7HZOPUYNwAlHheV09a4A3RTgMqtj9QY7JdgeM5i3I4+fDIYrQeq/UnyNYOTlBDtS+BwEjk2doz+KyO2OY5bwVCME+/qylTHT4C1D3k+uB33Yul3ov/eMuajlu1RH+II5wtT4hNNSnQ6U/kzbRkfq351bCfF8apAm7kmvq38njWRK0S305SHcSK5XO4U5O9MBmXWbSe3E6YccialdKzH2TTnQEF10XgZ5DdivsxEhkyxjVdERAwu03xmMiqD7hYx19km4cyHq6KyeHKkWpSz+ciVrHVpRYZk/wLHp0H2bULkNKzt4Eq86TKxLq3IECf6nXwV9QPbAfjHQSfoD7SN6L63gYhvbrkiTwKf/dPYpbdPVxKMnigdg0VAG8FQye8AzBl6aIk2A+wPiNoHd63LNZx3wbUAvz/CfTzJb/43AmkGpLfAi0DPE/NALQgtve+xFPdi5xRiNkHa5ySOJ/TR7RrBJdqL4AAxL+p+8ToYAFklNCnF3pM1KPba2L6v8vons5oPsko9ZyZoa0PsOoKezxoY+y5N5DYHWSvDTXzUMJH7IUkRW0tbyyV0CcfDDKmqDMe1KfqnQyehGNgo9q8p4hWYFGsiBY4fsSK/BRkkaPgj5PhMy720BnVFesfnlKo3Das0bw8XYJf196GEuzWRLSDoRypLg2w06FyqTS6Cx7iK1eeqGqrnugmuBgvB8+A5UAuqQYiU+oZ4h2T+BV3gAlDp/pai0U1rFpVGMAv8AHzFd4vShMOQLcoB+DNB/sSbM3ZoMV4BQsW0adSJSbNp1Hu1mY40/YZxpPdJaUSfFzrBMXAjjQNsdvll6cHCWVvKJIphdhcny11pqmolCk5aIDif0BKdZBucgPlO8kh/r8O4HtwClSCbk86HtQqQuUrvKVCf5OARjt0lll4H/WKKmbS0tmJQKZNQ7nq9+w0n2LhTN04E8jKMPpZ1hcli8tGTZYEYJwJrZQGzchTGz3a2ifwdkHc73LYAfpx6DcWXQK1LeiDsdpFGx7UOwUGQJKpmGyIjjvWh50iSQWzsZ/ofgvx+jnYp+B/YpJcB7QXDBKPJ4JDF6+/oCwoBuhoL36R+Jp4RJH16u2IgaxJpHhMehsBY3wTjV2YbOuuumLHrwCW6msbSj74O/DHGgV4Ohl8Jw1nSMmsB+tL6Wnw83odzBrhEr2GtgvFE8A74ADxtJZZygMC25cjQqBwqdg62qpUlziUP45Cq6OGuqqoUE/FJ8i+v7MpJYvEsHl1A5gNty2vLmaN3bBLdAvqB3svqXxLRvyEuc9zs7SiA+ACpw05pJx8SoAAAAABJRU5ErkJggg==" + webUI["html/js/authentication.js"] = "ZnVuY3Rpb24gY3JlYXRlRmlyc3RBY2NvdW50KGVsbSkgewogIHZhciBlcnIgPSBmYWxzZTsKICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoZWxtKTsKICBjb25zb2xlLmxvZyhkaXYpOwoKICB2YXIgZm9ybSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhdXRoZW50aWNhdGlvbicpOwogIAogIGNvbnN0IHVzZXJuYW1lICA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd1c2VybmFtZScpOwogIGNvbnN0IHBhc3N3b3JkICA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdwYXNzd29yZCcpOwogIGNvbnN0IGNvbmZpcm0gICA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdjb25maXJtJyk7CgogIHZhciBpbnB1dHMgPSBkaXYuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ0lOUFVUJykKICBjb25zb2xlLmxvZyhjb25maXJtKTsKCiAgc3dpdGNoKGNvbmZpcm0pIHsKICAgIGNhc2UgbnVsbDogYnJlYWs7CiAgICAKICAgIGRlZmF1bHQ6IAogICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGlucHV0cy5sZW5ndGg7IGkrKykgewogICAgICAgIGlmIChpbnB1dHNbaV0udmFsdWUubGVuZ3RoID09IDApIHsKICAgICAgICAgIGlucHV0c1tpXS5zdHlsZS5ib3JkZXJDb2xvciA9ICdyZWQnOwogICAgICAgICAgZXJyID0gdHJ1ZQogICAgICAgIH0KICAgICAgfQoKICAgICAgc3dpdGNoKGVycikgewogICAgICAgIGNhc2UgdHJ1ZTogcmV0dXJuOyBicmVhazsKICAgICAgICBjYXNlIGZhbHNlOiAKICAgICAgICAgIGlmIChwYXNzd29yZC52YWx1ZSAhPSBjb25maXJtLnZhbHVlKSB7CiAgICAgICAgICAgIGNvbmZpcm0uc3R5bGUuYm9yZGVyQ29sb3IgPSAncmVkJzsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgfQogICAgICAgICAgYnJlYWs7CiAgICAgIH0KICB9CgoKICAKCiAgZm9ybS5zdWJtaXQoKTsKICByZXR1cm47Cn0=" + webUI["html/js/data.js"] = "ZnVuY3Rpb24gc2hvd0NvbmZpZyhvYmopIHsKICBjb25maWcgPSBvYmo7CiAgLy9zZXRNZW51SXRlbSgpOwogIGNyZWF0ZU1lbnUoKTsKICAvL2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJwYWdlIikuY2xhc3NOYW1lID0gIiI7Cn0KCnNob3dNeVN0cmVhbXMKCmZ1bmN0aW9uIHNob3dNeVN0cmVhbXMoYWxsU3RyZWFtc09iaikgewoKICB2YXIgc3RyZWFtVHlwZUtleXMgPSBnZXRPYmpLZXlzKGFsbFN0cmVhbXNPYmopCgogIGZvciAodmFyIHMgPSAwOyBzIDwgc3RyZWFtVHlwZUtleXMubGVuZ3RoOyBzKyspIHsKICAgIHZhciBzdHJlYW1UeXBlID0gc3RyZWFtVHlwZUtleXNbc107CiAgICB2YXIgb2JqID0gbmV3IE9iamVjdCgpOwogICAgb2JqID0gYWxsU3RyZWFtc09ialtzdHJlYW1UeXBlXTsKICAgIHN3aXRjaChzdHJlYW1UeXBlKSB7CiAgICAgIGNhc2UgImFjdGl2ZVN0cmVhbXMiOiBhY3RpdmVTdHJlYW1zICAgICA9IG9iajsgYnJlYWs7CiAgICB9CgogICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoc3RyZWFtVHlwZSkuaW5uZXJIVE1MID0gIiI7CgogICAgdmFyIHN0cmVhbXNPYmogICAgPSBuZXcgT2JqZWN0KCk7CiAgICB2YXIgc3RyZWFtc05hbWVzICA9IG5ldyBBcnJheSgpOwogICAgCiAgICB2YXIga2V5cyA9IGdldE9iaktleXMob2JqKQogICAgCiAgICAvLyBDcmVhdGUgT2JqZWN0IChzdHJlYW1zT2JqKSBmb3IgdGhlIHN0cmVhbXMgYW5kIHNvcnQgYnkgbmFtZSAoc3RyZWFtc05hbWVzKQogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBrZXlzLmxlbmd0aDsgaSsrKSB7CiAgICAgIHZhciBuYW1lICAgID0gb2JqW2tleXNbaV1dWyJuYW1lIl07CiAgICAgIHZhciB0bXAgPSBuZXcgT2JqZWN0KCk7CiAgICAgIHZhciBzdHJlYW1LZXkgPSBnZXRPYmpLZXlzKG9ialtrZXlzW2ldXSk7CgogICAgICBmb3IgKHZhciBqID0gMDsgaiA8IHN0cmVhbUtleS5sZW5ndGg7IGorKykgewogICAgICAgIHRtcFtzdHJlYW1LZXlbal1dID0gb2JqW2tleXNbaV1dW3N0cmVhbUtleVtqXV07ICAgICAgCiAgICAgIH0KCiAgICAgIHN0cmVhbXNPYmpbbmFtZV0gPSB0bXA7CiAgICAgIHN0cmVhbXNOYW1lcy5wdXNoKG5hbWUpCiAgICB9CgogICAgc3RyZWFtc05hbWVzLnNvcnQoKTsKICAgIAogICAgLy8gQ3JlYXRlIFRhYmxlIGZvciBhY3RpdmVTdHJlYW1zCiAgICB2YXIgdGFibGUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChzdHJlYW1UeXBlKTsKCiAgICBmb3IgKHZhciBpID0gMDsgaSA8IHN0cmVhbXNOYW1lcy5sZW5ndGg7IGkrKykgewogICAgICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0KCk7CiAgICAgIG5ld0VudHJ5WyJfZWxlbWVudCJdID0gIlRSIjsKICAgICAgdGFibGUuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwogICAgICB2YXIgbGluZSA9IHRhYmxlLmxhc3RDaGlsZDsKCiAgICAgIHZhciB0bXAgPSBzdHJlYW1zT2JqW3N0cmVhbXNOYW1lc1tpXV0KICAgICAgdmFyIGtleXMgPSBnZXRPYmpLZXlzKHRtcCkKCiAgICAgIHZhciBuZXdLZXkgPSBuZXcgT2JqZWN0KCkKICAgICAgbmV3S2V5WyJfZWxlbWVudCJdICA9ICJURCI7CiAgICAgIC8vbmV3S2V5WyJfdGV4dCJdICAgICA9IHN0cmVhbXNOYW1lc1tpXTsKICAgICAgc3dpdGNoKHN0cmVhbVR5cGUpIHsKICAgICAgICBjYXNlICJhY3RpdmVTdHJlYW1zIjogbmV3S2V5WyJfdGV4dCJdICAgICA9ICJDaGFubmVsICgrKToiOyBicmVhazsKICAgICAgICBjYXNlICJpbmFjdGl2ZVN0cmVhbXMiOiBuZXdLZXlbIl90ZXh0Il0gICA9ICJDaGFubmVsICgtKToiOyBicmVhazsKICAgICAgfQogICAgICAKICAgICAgbmV3S2V5WyJjbGFzcyJdICAgICA9ICJ0ZEtleSI7CiAgICAgIGNvbnNvbGUubG9nKCk7CgoKICAgICAgdmFyIG5ld1ZhbCA9IG5ldyBPYmplY3QoKQogICAgICBuZXdWYWxbIl9lbGVtZW50Il0gID0gIlREIjsKICAgICAgbmV3VmFsWyJfdGV4dCJdICAgICA9IHN0cmVhbXNOYW1lc1tpXTsKICAgICAgbmV3VmFsWyJjbGFzcyJdICAgICA9ICJ0ZFZhbCI7CiAgICAgIC8vbmV3VmFsWyJfdGV4dCJdICAgICA9IHZhbHVlOwogICAgICAKICAgICAgbGluZS5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0tleSkpOwogICAgICBsaW5lLmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3VmFsKSk7CiAgICAgIAogICAgfQoKICB9CgogIHJldHVybgp9CgpmdW5jdGlvbiBzaG93QWN0aXZlU3RyZWFtcyhvYmopIHsKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYWN0aXZlU3RyZWFtcyIpLmlubmVySFRNTCA9ICIiOwogIGFjdGl2ZVN0cmVhbXMgICAgID0gb2JqOwogIHZhciBzdHJlYW1zT2JqICAgID0gbmV3IE9iamVjdCgpOwogIHZhciBzdHJlYW1zTmFtZXMgID0gbmV3IEFycmF5KCk7CiAgCiAgdmFyIGtleXMgPSBnZXRPYmpLZXlzKG9iaikKICAKICAvLyBDcmVhdGUgT2JqZWN0IChzdHJlYW1zT2JqKSBmb3IgdGhlIHN0cmVhbXMgYW5kIHNvcnQgYnkgbmFtZSAoc3RyZWFtc05hbWVzKQogIGZvciAodmFyIGkgPSAwOyBpIDwga2V5cy5sZW5ndGg7IGkrKykgewogICAgdmFyIG5hbWUgICAgPSBvYmpba2V5c1tpXV1bIm5hbWUiXTsKICAgIHZhciB0bXAgPSBuZXcgT2JqZWN0KCk7CiAgICB2YXIgc3RyZWFtS2V5ID0gZ2V0T2JqS2V5cyhvYmpba2V5c1tpXV0pOwoKICAgIGZvciAodmFyIGogPSAwOyBqIDwgc3RyZWFtS2V5Lmxlbmd0aDsgaisrKSB7CiAgICAgIHRtcFtzdHJlYW1LZXlbal1dID0gb2JqW2tleXNbaV1dW3N0cmVhbUtleVtqXV07ICAgICAgCiAgICB9CgogICAgc3RyZWFtc09ialtuYW1lXSA9IHRtcDsKICAgIHN0cmVhbXNOYW1lcy5wdXNoKG5hbWUpCiAgfQoKICBzdHJlYW1zTmFtZXMuc29ydCgpOwogIAogIC8vIENyZWF0ZSBUYWJsZSBmb3IgYWN0aXZlU3RyZWFtcwogIHZhciB0YWJsZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJhY3RpdmVTdHJlYW1zIik7CgogIGZvciAodmFyIGkgPSAwOyBpIDwgc3RyZWFtc05hbWVzLmxlbmd0aDsgaSsrKSB7CiAgICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0KCk7CiAgICBuZXdFbnRyeVsiX2VsZW1lbnQiXSA9ICJUUiI7CiAgICB0YWJsZS5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0VudHJ5KSk7CiAgICB2YXIgbGluZSA9IHRhYmxlLmxhc3RDaGlsZDsKCiAgICB2YXIgdG1wID0gc3RyZWFtc09ialtzdHJlYW1zTmFtZXNbaV1dCiAgICB2YXIga2V5cyA9IGdldE9iaktleXModG1wKQoKICAgIHZhciBuZXdLZXkgPSBuZXcgT2JqZWN0KCkKICAgIG5ld0tleVsiX2VsZW1lbnQiXSAgPSAiVEQiOwogICAgLy9uZXdLZXlbIl90ZXh0Il0gICAgID0gc3RyZWFtc05hbWVzW2ldOwogICAgbmV3S2V5WyJfdGV4dCJdICAgICA9ICJDaGFubmVsOiI7CiAgICBuZXdLZXlbImNsYXNzIl0gICAgID0gInRkS2V5IjsKICAgIGNvbnNvbGUubG9nKCk7CgoKICAgIHZhciBuZXdWYWwgPSBuZXcgT2JqZWN0KCkKICAgIG5ld1ZhbFsiX2VsZW1lbnQiXSAgPSAiVEQiOwogICAgbmV3VmFsWyJfdGV4dCJdICAgICA9IHN0cmVhbXNOYW1lc1tpXTsKICAgIG5ld1ZhbFsiY2xhc3MiXSAgICAgPSAidGRWYWwiOwogICAgLy9uZXdWYWxbIl90ZXh0Il0gICAgID0gdmFsdWU7CiAgICAKICAgIGxpbmUuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdLZXkpKTsKICAgIGxpbmUuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdWYWwpKTsKICAgIAogIH0KCn0KCmZ1bmN0aW9uIHBhcnNlTG9ncyhvYmopIHsKICBsb2cgPSBvYmoKICB2YXIga2V5cyA9IGdldE9iaktleXMob2JqKQoKICB2YXIgbXNnVHlwZTsKICBmb3IgKHZhciBpID0gMDsgaSA8IGtleXMubGVuZ3RoOyBpKyspIHsKICAgIHN3aXRjaChrZXlzW2ldKSB7CiAgICAgIGNhc2UgIndhcm5pbmdzIjogIG1zZ1R5cGUgPSAid2FybmluZ01zZyI7IGJyZWFrOwogICAgICBjYXNlICJlcnJvcnMiOiAgICBtc2dUeXBlID0gImVycm9yTXNnIjsgYnJlYWs7CiAgICB9CgogICAgc3dpdGNoKG9ialtrZXlzW2ldXSkgewogICAgICBjYXNlIDA6IG1zZ1R5cGUgPSAidGRWYWwiOyBicmVhazsKICAgICAgZGVmYXVsdDogYnJlYWs7CiAgICB9CgogICAgaWYoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoa2V5c1tpXSkpewogICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChrZXlzW2ldKS5jbGFzc05hbWUgPSBtc2dUeXBlOwogICAgfQogICAgCiAgICAKICB9CiAgcmV0dXJuCn0KCgpmdW5jdGlvbiBjYW5jZWxEYXRhKGVsZW1lbnQpIHsKICBjcmVhdGVNZW51KCk7Cn0KCmZ1bmN0aW9uIHNhdmVEYXRhKGVsZW1lbnQpIHsKICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKICB2YXIgZGl2ID0gZWxlbWVudC5wYXJlbnROb2RlLnBhcmVudE5vZGU7CiAgdmFyIGlucHV0cyA9IGRpdi5nZXRFbGVtZW50c0J5VGFnTmFtZSgiSU5QVVQiKTsKICAKICB2YXIgY29uZmlnS2V5ID0gZGl2LmdldEF0dHJpYnV0ZSgiZGF0YS1jb25maWdrZXkiKTsKICB2YXIgbWVudVR5cGUgPSBkaXYuZ2V0QXR0cmlidXRlKCJkYXRhLW1lbnV0eXBlIik7CiAgdmFyIHZhbHVlOwogIHZhciB2YWx1ZUFyciA9IG5ldyBBcnJheSgpOwogIAogIGZvciAodmFyIGkgPSAwOyBpIDwgaW5wdXRzLmxlbmd0aDsgaSsrKSB7CiAgICBpZiAoaW5wdXRzW2ldLnR5cGUgPT0gInRleHQiICYmIGlucHV0c1tpXS52YWx1ZSAhPSB1bmRlZmluZWQgJiYgaW5wdXRzW2ldLnZhbHVlICE9ICIiICkgewogICAgICBjb25zb2xlLmxvZyhpbnB1dHNbaV0udmFsdWUsIG1lbnVUeXBlKQogICAgICBzd2l0Y2gobWVudVR5cGUpIHsKICAgICAgICBjYXNlICJpbnB1dEFycmF5IjogdmFsdWVBcnIucHVzaChpbnB1dHNbaV0udmFsdWUpOyBicmVhazsKICAgICAgICBjYXNlICJzaW5nbGVJbnB1dCI6IHZhbHVlID0gaW5wdXRzW2ldLnZhbHVlOyBicmVhazsKICAgICAgfQogICAgfQogIH0KCiAgc3dpdGNoKG1lbnVUeXBlKSB7CiAgICBjYXNlICJpbnB1dEFycmF5IjogIGRhdGFbY29uZmlnS2V5XSA9IHZhbHVlQXJyOyBicmVhazsKICAgIGNhc2UgInNpbmdsZUlucHV0IjogIAogICAgICBpZiAoaXNOYU4odmFsdWUpID09IGZhbHNlKSB7CiAgICAgICAgdmFsdWUgPSBwYXJzZUludCh2YWx1ZSk7CiAgICAgICAgZGF0YVtjb25maWdLZXldID0gdmFsdWU7CiAgICAgICAgYnJlYWs7CiAgICAgIH0KCiAgICAgIGlmICh2YWx1ZSA9PSB1bmRlZmluZWQpIHsKICAgICAgICBkYXRhWyJkZWxldGUiXSA9IGNvbmZpZ0tleTsgCiAgICAgIH0gZWxzZSB7CiAgICAgICAgZGF0YVtjb25maWdLZXldID0gdmFsdWUKICAgICAgfQoKICAgICAgYnJlYWs7CiAgfQoKICBkYXRhWyJjbWQiXSA9ICJzYXZlQ29uZmlnIjsKICBjb25zb2xlLmxvZyhkYXRhKTsKICB4VGVWZShkYXRhKQp9CgpmdW5jdGlvbiB4VGVWZShkYXRhKSB7CiAgaWYgKHdlYlNvY2tldHMgPT0gZmFsc2UpIHsKICAgIGFsZXJ0KCJZb3VyIGJyb3dzZXIgZG9lcyBub3Qgc3VwcG9ydCBXZWJTb2NrZXRzIik7CiAgICByZXR1cm47CiAgfSBlbHNlIHsKICAgIGlmIChkYXRhWyJjbWQiXSAhPSAiZ2V0TG9nIikgewogICAgICBzaG93TG9hZGluZ1NjcmVlbih0cnVlKQogICAgfQogIH0KICBkZWxldGUgdW5kb1siZXBnTWFwcGluZyJdOwogIAogIHZhciBwcm90b2NvbFdTCiAgc3dpdGNoKHdpbmRvdy5sb2NhdGlvbi5wcm90b2NvbCkgewogICAgY2FzZSAiaHR0cDoiOiAgIHByb3RvY29sV1MgPSAid3M6Ly8iOyBicmVhazsKICAgIGNhc2UgImh0dHBzOiI6ICBwcm90b2NvbFdTID0gIndzczovLyI7IGJyZWFrOwogIH0KCgogIHZhciB3cyA9IG5ldyBXZWJTb2NrZXQocHJvdG9jb2xXUyArIHdpbmRvdy5sb2NhdGlvbi5ob3N0bmFtZSArICI6IiArIHdpbmRvdy5sb2NhdGlvbi5wb3J0ICsgIi9kYXRhLyIgKyAiP1Rva2VuPSIgKyBnZXRDb29raWUoIlRva2VuIikpOwogIHdzLm9ub3BlbiA9IGZ1bmN0aW9uKCkgewogICAgY29uc29sZS5sb2coZGF0YSkKICAgIHdzLnNlbmQoSlNPTi5zdHJpbmdpZnkoZGF0YSkpOwogIH0KCiAgd3Mub25tZXNzYWdlID0gZnVuY3Rpb24gKGUpIHsKICAgIHZhciByZXNwb25zZSA9IEpTT04ucGFyc2UoZS5kYXRhKTsKICAgIGNvbnNvbGUubG9nKHJlc3BvbnNlKTsKICAgIAogICAgaWYgKHJlc3BvbnNlLmhhc093blByb3BlcnR5KCJjbGllbnRJbmZvIikpIHsKICAgICAgY3JlYXRlQ2xpbnRJbmZvKHJlc3BvbnNlWyJjbGllbnRJbmZvIl0pOwogICAgfQoKICAgIGlmIChyZXNwb25zZS5oYXNPd25Qcm9wZXJ0eSgibG9nIikpIHsKICAgICAgY3JlYXRlQ2xpbnRJbmZvKHJlc3BvbnNlWyJsb2ciXSk7CiAgICB9CgogICAgaWYgKHJlc3BvbnNlLmhhc093blByb3BlcnR5KCJzdGF0dXMiKSkgewogICAgICBpZiAocmVzcG9uc2VbInN0YXR1cyJdID09IGZhbHNlKSB7CiAgICAgICAgYWxlcnQocmVzcG9uc2VbImVyciJdKQogICAgICAgIGlmKHJlc3BvbnNlLmhhc093blByb3BlcnR5KCJyZWxvYWQiKSkgewogICAgICAgICAgbG9jYXRpb24ucmVsb2FkKCk7CiAgICAgICAgfQogICAgICAgIC8vY2hlY2tFcnIocmVzcG9uc2UpCiAgICAgICAgY29uc29sZS5sb2cocmVzcG9uc2UpOwogICAgICAgIHVwZGF0ZVh0ZXZlU3RhdHVzKHJlc3BvbnNlKTsKICAgICAgICBzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7IHNob3dMb2FkaW5nU2NyZWVuKGZhbHNlKTsgfSwgMzAwKTsKICAgICAgICAKICAgICAgICByZXR1cm4KICAgICAgfQoKICAgICAgdXBkYXRlWHRldmVTdGF0dXMocmVzcG9uc2UpCgogICAgICAvL2NvbnNvbGUubG9nKGRhdGFbImNtZCJdKTsKICAgICAgc3dpdGNoKGRhdGFbImNtZCJdKSB7CiAgICAgICAgY2FzZSAic2F2ZVVzZXJEYXRhIjogICAgY3JlYXRlTWVudSgpOyBicmVhazsKICAgICAgICBjYXNlICJzYXZlTmV3VXNlciI6ICAgICBjcmVhdGVNZW51KCk7IGJyZWFrOwogICAgICAgIGNhc2UgInNhdmVGaWxlc1hNTFRWIjogIC8vY3JlYXRlTWVudSgpOyBicmVhazsKICAgICAgICBjYXNlICJzYXZlRmlsZXNNM1UiOiAgICAvL2NyZWF0ZU1lbnUoKTsgcmV0dXJuOyBicmVhazsKICAgICAgICBjYXNlICJzYXZlQ29uZmlnIjogICAgCiAgICAgICAgICBkYXRhID0gbmV3IE9iamVjdCgpOwogICAgICAgICAgZGF0YVsiY21kIl0gPSAiY2hlY2tUb2tlbiI7CiAgICAgICAgICB4VGVWZShkYXRhKTsKICAgICAgICAgIGJyZWFrOwoKICAgICAgICBjYXNlICJlbXB0eUxvZyI6IHdyaXRlTG9nSW5EaXYoKTsgYnJlYWs7CiAgICAgICAgY2FzZSAiZ2V0TG9nIjogIHJldHVybjsgYnJlYWs7CiAgICAgIH0KCgogICAgfQoKICAgIGlmIChjb25maWdbImZpbGVzIl0gPT0gdW5kZWZpbmVkIHx8IGNvbmZpZ1siZmlsZXMiXS5sZW5ndGggPT0gMCkgewogICAgICBjcmVhdGVNZW51KCk7CiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKDEwKS5jbGljaygpCiAgICB9CgogICAgc2V0VGltZW91dChmdW5jdGlvbigpeyBzaG93TG9hZGluZ1NjcmVlbihmYWxzZSk7IH0sIDApOwogIH0KICAKfQoKZnVuY3Rpb24gdXBkYXRlWHRldmVTdGF0dXMocmVzcG9uc2UpIHsKICB2YXIga2V5cyA9IGdldE9iaktleXMocmVzcG9uc2UpOwogIC8vY29uc29sZS5sb2coa2V5cyk7CgogIGZvciAodmFyIGkgPSAwOyBpIDwga2V5cy5sZW5ndGg7IGkrKykgewogICAgc3dpdGNoKGtleXNbaV0pIHsKICAgICAgY2FzZSAiYWxlcnQiOiAgICAgICAgIGFsZXJ0KHJlc3BvbnNlW2tleXNbaV1dKTsgYnJlYWs7CiAgICAgIGNhc2UgImNvbmZpZyI6ICAgICAgICBzaG93Q29uZmlnKHJlc3BvbnNlW2tleXNbaV1dKTsgYnJlYWs7CiAgICAgIGNhc2UgImxvZyI6ICAgICAgICAgICBwYXJzZUxvZ3MocmVzcG9uc2Vba2V5c1tpXV0pOyBicmVhazsKICAgICAgY2FzZSAibXlTdHJlYW1zIjogICAgIHNob3dNeVN0cmVhbXMocmVzcG9uc2Vba2V5c1tpXV0pOyBicmVhazsKICAgICAgY2FzZSAieEVQRyI6ICAgICAgICAgIHhFUEcgPSByZXNwb25zZVtrZXlzW2ldXTsgYnJlYWs7CiAgICAgIGNhc2UgInVzZXJzIjogICAgICAgICB1c2VycyA9IHJlc3BvbnNlW2tleXNbaV1dOyBicmVhazsKICAgICAgY2FzZSAidG9rZW4iOiAgICAgICAgIGRvY3VtZW50LmNvb2tpZSA9ICJUb2tlbj0iICsgcmVzcG9uc2Vba2V5c1tpXV07IGJyZWFrOwogICAgICBjYXNlICJyZWxvYWQiOiAgICAgICAgbG9jYXRpb24ucmVsb2FkKCk7IGJyZWFrOwogICAgICBjYXNlICJvcGVuTGluayI6ICAgICAgd2luZG93LmxvY2F0aW9uID0gcmVzcG9uc2VbIm9wZW5MaW5rIl07IGJyZWFrOwogICAgICAvL2Nhc2UgInZlcnNpb24iOiB2ZXJzaW9uID0gcmVzcG9uc2Vba2V5c1tpXV07IGJyZWFrOwogICAgfQogIH0KfQoKZnVuY3Rpb24gZ2V0VmFsdWVGcm9tUHJvdmlkZXJGaWxlKHhYbWx0dkZpbGUsIGZpbGVUeXBlLCBrZXkpIHsKCiAgdmFyIGZpbGVJRCA9IHhYbWx0dkZpbGUuc3Vic3RyaW5nKDAsIHhYbWx0dkZpbGUubGFzdEluZGV4T2YoJy4nKSkKCiAgaWYgKGNvbmZpZ1siZmlsZXMiXVtmaWxlVHlwZV0uaGFzT3duUHJvcGVydHkoZmlsZUlEKSA9PSB0cnVlKSB7CiAgICB2YXIgZGF0YSA9IGNvbmZpZ1siZmlsZXMiXVtmaWxlVHlwZV1bZmlsZUlEXTsKICAgIHJldHVybiBkYXRhW2tleV0KICB9Cgp9CgoKCgo=" + webUI["html/js/users.js"] = "ZnVuY3Rpb24gb3BlblVzZXJzKGVsbSkgewogIGNvbG9tblNvcnQgPSAwOwoKICB2YXIgbmV3RGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNldHRpbmdzIik7CiAgCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJIUiI7CiAgbmV3RGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJJTlBVVCI7CiAgbmV3RW50cnlbInR5cGUiXSA9ICJidXR0b24iOwogIG5ld0VudHJ5WyJjbGFzcyJdID0gImJ1dHRvbiI7CiAgbmV3RW50cnlbInZhbHVlIl0gPSAiTmV3IjsKICBuZXdFbnRyeVsib25jbGljayJdID0gInVzZXJEZXRhaWwoMCkiOwogIG5ld0Rpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0VudHJ5KSk7CgogIHZhciBkaXYgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgic2V0dGluZ3MiKTsKCiAgIC8vIEJ1aWxkIHRhYmxlCiAgdmFyIG5ld1RhYmxlID0gbmV3IE9iamVjdCgpOwogIG5ld1RhYmxlWyJfZWxlbWVudCJdICA9ICJUQUJMRSI7CiAgbmV3VGFibGVbImlkIl0gICAgICAgID0gImlkX21hcHBpbmciOwogIG5ld1RhYmxlWyJjbGFzcyJdICAgICA9ICJ0YWJsZS1tYXBwaW5nIjsKICBkaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdUYWJsZSkpOwoKICBzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7IAogICAgY3JlYXRlVXNlcnNUYWJsZSgpOyAKICB9LCAxMCk7Cn0KCmZ1bmN0aW9uIGNyZWF0ZVVzZXJzVGFibGUoKSB7CiAgdmFyIHRhYmxlID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImlkX21hcHBpbmciKTsKICB0YWJsZS5pbm5lckhUTUwgPSAiIjsKICB2YXIgbmV3VFIgPSBuZXcgT2JqZWN0KCk7CiAgbmV3VFJbIl9lbGVtZW50Il0gPSAiVFIiOwogIG5ld1RSWyJjbGFzcyJdICAgID0gInRhYmxlLW1hcHBpbmctaGVhZGVyIjsKICB0YWJsZS5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld1RSKSk7CgogIHZhciB0ciA9IHRhYmxlLmxhc3RDaGlsZDsKICB2YXIgdHJIZWFkbGluZXMgPSBuZXcgQXJyYXkoIlVzZXJuYW1lIiwgIlBhc3N3b3JkIiwgIldFQiIsICJQTVMiLCAiTTNVIiwgIlhNTCIsICJBUEkiKQoKICBmb3IgKHZhciBpID0gMDsgaSA8IHRySGVhZGxpbmVzLmxlbmd0aDsgaSsrKSB7CiAgICB2YXIgbmV3VEQgPSBuZXcgT2JqZWN0KCk7CiAgICBuZXdURFsiX2VsZW1lbnQiXSA9ICJURCI7CiAgICBuZXdURFsiX3RleHQiXSAgICA9IHRySGVhZGxpbmVzW2ldOwogICAgdHIuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdURCkpOwogIH0KCgogIC8vIFNvcnQgdXNlcnMKICB2YXIgdXNlcklkcyA9IGdldE9iaktleXModXNlcnMpOwoKICB2YXIgdXNlck9iaiA9IG5ldyBPYmplY3QoKTsKCiAgZm9yICh2YXIgaSA9IDA7IGkgPCB1c2VySWRzLmxlbmd0aDsgaSsrKSB7CiAgICB2YXIgdXNlcm5hbWUgPSB1c2Vyc1t1c2VySWRzW2ldXVsiZGF0YSJdWyJ1c2VybmFtZSJdOwogICAgdXNlck9ialt1c2VybmFtZV0gPSB1c2VySWRzW2ldOwogIH0KCiAgdmFyIGFsbFVzZXJzID0gZ2V0T2JqS2V5cyh1c2VyT2JqKTsKICBhbGxVc2Vycy5zb3J0KCk7CiAgLy8gLS0KCiAgZm9yICh2YXIgaSA9IDA7IGkgPCBhbGxVc2Vycy5sZW5ndGg7IGkrKykgewogICAgdmFyIHRhYmxlICAgICA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJpZF9tYXBwaW5nIik7CiAgICB2YXIgdXNlcklEICAgID0gdXNlck9ialthbGxVc2Vyc1tpXV07CiAgICB2YXIgdXNlcm5hbWUgID0gYWxsVXNlcnNbaV07CiAgICB2YXIgaXRlbSAgICAgID0gdXNlcnNbdXNlcklEXVsiZGF0YSJdOwoKICAgIC8vIENyZWF0ZSBUUgogICAgdmFyIG5ld1RSID0gbmV3IE9iamVjdCgpOwogICAgbmV3VFJbIl9lbGVtZW50Il0gICAgICAgPSAiVFIiOwogICAgbmV3VFJbImNsYXNzIl0gICAgICAgICAgPSAiIjsKICAgIG5ld1RSWyJpZCJdICAgICAgICAgICAgID0gdXNlcklEOwogICAgbmV3VFJbIm9uY2xpY2siXSAgICAgICAgPSAnamF2YXNjcmlwdDogdXNlckRldGFpbCgiJyArIHVzZXJJRCArICciKTsnOwogICAgdGFibGUuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdUUikpOwoKICAgIHZhciB0ciA9IHRhYmxlLmxhc3RDaGlsZDsKCiAgICAvLyBDcmVhdGUgdXNlcm5hbWUgVEQKICAgIHZhciBuZXdURCA9IG5ldyBPYmplY3QoKTsKICAgIG5ld1REWyJfZWxlbWVudCJdID0gIlAiOwogICAgbmV3VERbIl90ZXh0Il0gICAgPSB1c2VybmFtZTsKICAgIGNyZWF0ZU5ld1REKG5ld1RELCB0cik7CgogICAgLy8gQ3JlYXRlIHBhc3N3b3JkIFRECiAgICB2YXIgbmV3VEQgPSBuZXcgT2JqZWN0KCk7CiAgICBuZXdURFsiX2VsZW1lbnQiXSA9ICJQIjsKICAgIG5ld1REWyJfdGV4dCJdICAgID0gIi4uLi4uIjsKICAgIGNyZWF0ZU5ld1REKG5ld1RELCB0cik7CgogICAgLy8gQ3JlYXRlIHdlYiBhY2Nlc3MKICAgIHZhciBuZXdURCA9IG5ldyBPYmplY3QoKTsKICAgIG5ld1REWyJfZWxlbWVudCJdID0gIlAiOwogICAgc3dpdGNoKGl0ZW1bImF1dGhlbnRpY2F0aW9uLndlYiJdKXsKICAgICAgY2FzZSB0cnVlOiBuZXdURFsiX3RleHQiXSAgICA9ICLinJMiOyBicmVhazsKICAgICAgZGVmYXVsdDogICBuZXdURFsiX3RleHQiXSAgICA9ICItIjsgYnJlYWs7CiAgICB9CiAgICBjcmVhdGVOZXdURChuZXdURCwgdHIpOwoKICAgIC8vIENyZWF0ZSBQTVMgYWNjZXNzCiAgICB2YXIgbmV3VEQgPSBuZXcgT2JqZWN0KCk7CiAgICBuZXdURFsiX2VsZW1lbnQiXSA9ICJQIjsKICAgIHN3aXRjaChpdGVtWyJhdXRoZW50aWNhdGlvbi5wbXMiXSl7CiAgICAgIGNhc2UgdHJ1ZTogbmV3VERbIl90ZXh0Il0gICAgPSAi4pyTIjsgYnJlYWs7CiAgICAgIGRlZmF1bHQ6ICAgbmV3VERbIl90ZXh0Il0gICAgPSAiLSI7IGJyZWFrOwogICAgfQogICAgY3JlYXRlTmV3VEQobmV3VEQsIHRyKTsKCiAgICAvLyBDcmVhdGUgTTNVIGFjY2VzcwogICAgdmFyIG5ld1REID0gbmV3IE9iamVjdCgpOwogICAgbmV3VERbIl9lbGVtZW50Il0gPSAiUCI7CiAgICBzd2l0Y2goaXRlbVsiYXV0aGVudGljYXRpb24ubTN1Il0pewogICAgICBjYXNlIHRydWU6IG5ld1REWyJfdGV4dCJdICAgID0gIuKckyI7IGJyZWFrOwogICAgICBkZWZhdWx0OiAgIG5ld1REWyJfdGV4dCJdICAgID0gIi0iOyBicmVhazsKICAgIH0KICAgIGNyZWF0ZU5ld1REKG5ld1RELCB0cik7CgogICAgLy8gQ3JlYXRlIFhNTFRWIGFjY2VzcwogICAgdmFyIG5ld1REID0gbmV3IE9iamVjdCgpOwogICAgbmV3VERbIl9lbGVtZW50Il0gPSAiUCI7CiAgICBzd2l0Y2goaXRlbVsiYXV0aGVudGljYXRpb24ueG1sIl0pewogICAgICBjYXNlIHRydWU6IG5ld1REWyJfdGV4dCJdICAgID0gIuKckyI7IGJyZWFrOwogICAgICBkZWZhdWx0OiAgIG5ld1REWyJfdGV4dCJdICAgID0gIi0iOyBicmVhazsKICAgIH0KICAgIGNyZWF0ZU5ld1REKG5ld1RELCB0cik7CgogICAgLy8gQ3JlYXRlIEFQSSBhY2Nlc3MKICAgIHZhciBuZXdURCA9IG5ldyBPYmplY3QoKTsKICAgIG5ld1REWyJfZWxlbWVudCJdID0gIlAiOwoKICAgIHN3aXRjaChpdGVtWyJhdXRoZW50aWNhdGlvbi5hcGkiXSl7CiAgICAgIGNhc2UgdHJ1ZTogbmV3VERbIl90ZXh0Il0gICAgPSAi4pyTIjsgYnJlYWs7CiAgICAgIGRlZmF1bHQ6ICAgbmV3VERbIl90ZXh0Il0gICAgPSAiLSI7IGJyZWFrOwogICAgfQogICAgY3JlYXRlTmV3VEQobmV3VEQsIHRyKTsKCiAgfQoKICAvLyB1c2FnZSBJbmZvICAKICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNldHRpbmdzIik7CiAgc3dpdGNoKG1lbnVbYWN0aXZlTWVudS5pZF0uaGFzT3duUHJvcGVydHkoIl91c2FnZSIpKSB7CiAgICBjYXNlIHRydWU6IAogICAgICB2YXIgdXNhZ2VJdGVtID0gbmV3IE9iamVjdCgpOwogICAgICB1c2FnZUl0ZW1bIl9lbGVtZW50Il0gPSAiUFJFIgogICAgICB1c2FnZUl0ZW1bIl90ZXh0Il0gICAgPSBtZW51W2FjdGl2ZU1lbnUuaWRdWyJfdXNhZ2UiXTsKCiAgICAgIHZhciBuZXdIUiA9IG5ldyBPYmplY3QoKTsKICAgICAgbmV3SFJbIl9lbGVtZW50Il0gPSAiSFIiCiAgICAgIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0hSKSk7CiAgICAgIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KHVzYWdlSXRlbSkpOwogIH0KCiAgc29ydFRhYmxlKDApOwp9CgpmdW5jdGlvbiB1c2VyRGV0YWlsKHVzZXJJRCkgewogIHNob3dQb3BVcEVsZW1lbnQoJ3VzZXItZGV0YWlsJyk7CiAgc2V0VGltZW91dChmdW5jdGlvbigpeyAKICAgIHNob3dFbGVtZW50KCJwb3B1cCIsIHRydWUpOwogIH0sIDEwKTsKICB2YXIgZGVmYXVsdFVzZXI7CgogIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJzYXZlVXNlckRldGFpbCIpLnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiBzYXZlVXNlckRldGFpbCgiJyArIHVzZXJJRCArICciLCBmYWxzZSknKTsKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZGVsZXRlVXNlckRldGFpbCIpLnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiBzYXZlVXNlckRldGFpbCgiJyArIHVzZXJJRCArICciLCB0cnVlKScpOwoKICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKICAKICBzd2l0Y2godXNlcklEKSB7CiAgICBjYXNlIDA6ICAgLy8gTmV3IFVzZXIKICAgICAgZGF0YVsidXNlcm5hbWUiXSA9ICIiOwogICAgICBkYXRhWyJhdXRoZW50aWNhdGlvbi53ZWIiXSA9IGZhbHNlOwogICAgICBkYXRhWyJhdXRoZW50aWNhdGlvbi5wbXMiXSA9IHRydWU7CiAgICAgIGRhdGFbImF1dGhlbnRpY2F0aW9uLnhtbCJdID0gdHJ1ZTsKICAgICAgZGF0YVsiYXV0aGVudGljYXRpb24ubTN1Il0gPSBmYWxzZTsKICAgICAgZGF0YVsiYXV0aGVudGljYXRpb24uYXBpIl0gPSBmYWxzZTsKICAgICAgZGF0YVsiZGVmYXVsdFVzZXIiXSAgICAgICAgPSBmYWxzZTsKICAgICAgc2V0VGltZW91dChmdW5jdGlvbigpeyAKICAgICAgICBzaG93RWxlbWVudCgiZGVsZXRlVXNlckRldGFpbCIsIGZhbHNlKQogICAgICB9LCAxKTsKCiAgICAgIGJyZWFrOyAKICAgIAogICAgZGVmYXVsdDogCiAgICAgIGRhdGEgPSB1c2Vyc1t1c2VySURdWyJkYXRhIl07IAogICAgICBzaG93RWxlbWVudCgiZGVsZXRlVXNlckRldGFpbCIsIHRydWUpICAgCiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJkZWxldGVVc2VyRGV0YWlsIikuY2xhc3NOYW1lID0gImRlbGV0ZSI7CiAgIAogICAgICBicmVhawogIH0KICAKCiAgdmFyIHVzZXJuYW1lID0gZGF0YVsidXNlcm5hbWUiXTsKICBkYXRhWyJwYXNzd29yZCJdID0gIiI7CiAgZGF0YVsiY29uZmlybSJdID0gIiI7CgogIHZhciBrZXlzID0gZ2V0T2JqS2V5cyhkYXRhKTsKICBkZWZhdWx0VXNlciA9IGRhdGFbImRlZmF1bHRVc2VyIl07CiAgaWYgKGRhdGEuaGFzT3duUHJvcGVydHkoImRlZmF1bHRVc2VyIikpIHsKICAgIGRlZmF1bHRVc2VyID0gSlNPTi5wYXJzZShkYXRhWyJkZWZhdWx0VXNlciJdKTsKICB9CgogIGZvciAodmFyIGkgPSAwOyBpIDwga2V5cy5sZW5ndGg7IGkrKykgewoKICAgIGlmKGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGtleXNbaV0pKXsKICAgICAgdmFyIHRkID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoa2V5c1tpXSkKICAgIH0gZWxzZSB7CiAgICAgIHZhciB0ZCA9IHVuZGVmaW5lZDsKICAgIH0KCiAgICB2YXIgbmV3SXRlbSA9IG5ldyBPYmplY3QoKTsKCiAgICBuZXdJdGVtWyJfZWxlbWVudCJdID0gIklOUFVUIjsKICAgIAogICAgbmV3SXRlbVsidmFsdWUiXSAgICA9IGRhdGFba2V5c1tpXV07CiAgICBuZXdJdGVtWyJuYW1lIl0gICAgID0ga2V5c1tpXTsKCgoKCgogICAgc3dpdGNoKGtleXNbaV0uaW5kZXhPZigiYXV0aGVudGljYXRpb24iKSkgewogICAgICBjYXNlIC0xOiAKICAgICAgICBpZiAoa2V5c1tpXSA9PSAicGFzc3dvcmQiIHx8IGtleXNbaV0gPT0gImNvbmZpcm0iKSB7CiAgICAgICAgICBuZXdJdGVtWyJ0eXBlIl0gICAgID0gInBhc3N3b3JkIjsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgbmV3SXRlbVsidHlwZSJdICAgICA9ICJ0ZXh0IjsKICAgICAgICB9CiAgICAgICAgYnJlYWs7CgogICAgICBkZWZhdWx0OiAKICAgICAgICBuZXdJdGVtWyJ0eXBlIl0gICAgID0gImNoZWNrYm94IjsKICAgICAgICAKICAgICAgICBpZiAoa2V5c1tpXSA9PSAiYXV0aGVudGljYXRpb24ud2ViIiAmJiBkZWZhdWx0VXNlciA9PSB0cnVlKSB7CiAgICAgICAgICBuZXdJdGVtWyJvbmNsaWNrIl0gPSAicmV0dXJuIGZhbHNlIjsgCiAgICAgICAgfQogICAgICAgIAogICAgICAgIGlmIChkYXRhW2tleXNbaV1dID09IHRydWUpIHsgICAgICAgICAgCiAgICAgICAgICBuZXdJdGVtWyJjaGVja2VkIl0gID0gZGF0YVtrZXlzW2ldXTsgIAogICAgICAgIH0KICAgICAgICAKICAgICAgICBicmVhazsKICAgIH0KCiAgICBzd2l0Y2goa2V5c1tpXSkgewogICAgICBjYXNlICJkZWZhdWx0VXNlciI6IAogICAgICAgIC8vaWYgKGRhdGFba2V5c1tpXV0gPT0gdHJ1ZSkgewogICAgICAgIG5ld0l0ZW1bInR5cGUiXSAgICAgPSAiaGlkZGVuIjsKICAgICAgICAvL30KICAgIH0KCgogICAgaWYgKHRkICE9IHVuZGVmaW5lZCkgewogICAgICB0ZC5pbm5lckhUTUwgPSAiIjsKICAgICAgdmFyIGVsZW1lbnQgPSBjcmVhdGVOZXdFbGVtZW50KG5ld0l0ZW0pCiAgICAgIC8vY29uc29sZS5sb2coZWxlbWVudCk7CiAgICAgIHRkLmFwcGVuZENoaWxkKGVsZW1lbnQpOwogICAgfQoKCiAgfQoKCiAgaWYgKGRlZmF1bHRVc2VyID09IHRydWUpIHsKICAgIHNob3dFbGVtZW50KCJkZWxldGVVc2VyRGV0YWlsIiwgZmFsc2UpCiAgfSBlbHNlIHsKICAgIHNob3dFbGVtZW50KCJkZWxldGVVc2VyRGV0YWlsIiwgdHJ1ZSkKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJkZWxldGVVc2VyRGV0YWlsIikuY2xhc3NOYW1lID0gImRlbGV0ZSI7CiAgfQoKfQoKZnVuY3Rpb24gc2F2ZVVzZXJEZXRhaWwodXNlcklELCBkZWxldGVVc2VyKSB7CgogIHZhciBpbnB1dHMgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgidXNlci1kZXRhaWwtdGFibGUiKS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiSU5QVVQiKTsKCiAgdmFyIG5ld1VzZXJEYXRhID0gbmV3IE9iamVjdCgpOwogIGZvciAodmFyIGkgPSAwOyBpIDwgaW5wdXRzLmxlbmd0aDsgaSsrKSB7CiAgICBzd2l0Y2goaW5wdXRzW2ldLnR5cGUpIHsKICAgICAgY2FzZSAiY2hlY2tib3giOiAgbmV3VXNlckRhdGFbaW5wdXRzW2ldLm5hbWVdID0gaW5wdXRzW2ldLmNoZWNrZWQ7IGJyZWFrOwogICAgICBkZWZhdWx0OiAgICAgICAgICBuZXdVc2VyRGF0YVtpbnB1dHNbaV0ubmFtZV0gPSBpbnB1dHNbaV0udmFsdWU7IGJyZWFrOwogICAgfQogICAgCiAgICBpZiAoaW5wdXRzWyJ1c2VybmFtZSJdLnZhbHVlLmxlbmd0aCA9PSAwKSB7CiAgICAgIGlucHV0c1sidXNlcm5hbWUiXS5zdHlsZS5ib3JkZXIgPSAic29saWQgMXB4IHJlZCI7CiAgICAgIHJldHVybjsKICAgIH0KCiAgICBzd2l0Y2godXNlcklEKSB7CiAgICAgIGNhc2UgIjAiOiAKICAgICAgICBpZiAoaW5wdXRzWyJwYXNzd29yZCJdLnZhbHVlLmxlbmd0aCA9PSAwKSB7CiAgICAgICAgICBjb25zb2xlLmxvZyhpbnB1dHNbInBhc3N3b3JkIl0udmFsdWUubGVuZ3RoKTsKICAgICAgICAgIGlucHV0c1sicGFzc3dvcmQiXS5zdHlsZS5ib3JkZXIgPSAic29saWQgMXB4IHJlZCI7CiAgICAgICAgICByZXR1cm4KICAgICAgICB9CiAgICAgICAgYnJlYWs7CiAgICB9CgogICAgaWYgKGlucHV0c1sicGFzc3dvcmQiXS52YWx1ZS5sZW5ndGggPiAwKSB7CiAgICAgIGlmIChpbnB1dHNbInBhc3N3b3JkIl0udmFsdWUgIT0gaW5wdXRzWyJjb25maXJtIl0udmFsdWUpIHsKICAgICAgICBpbnB1dHNbInBhc3N3b3JkIl0uc3R5bGUuYm9yZGVyID0gInNvbGlkIDFweCByZWQiOwogICAgICAgIGlucHV0c1siY29uZmlybSJdLnN0eWxlLmJvcmRlciA9ICJzb2xpZCAxcHggcmVkIjsKICAgICAgICByZXR1cm47CiAgICAgIH0KICAgIH0KICAgIAogIH0KCiAgdmFyIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgCiAgc3dpdGNoKHVzZXJJRCkgewogICAgY2FzZSAiMCI6CiAgICAgIC8vZGF0YSA9IG5ld1VzZXJEYXRhCiAgICAgIGRhdGFbInVzZXJEYXRhIl0gID0gbmV3VXNlckRhdGEKICAgICAgZGF0YVsiY21kIl0gICAgICAgPSAic2F2ZU5ld1VzZXIiOyBicmVhazsKICAgIAogICAgZGVmYXVsdDogIAogICAgICB2YXIgdGhpc1VzZXIgICAgICA9IG5ldyBPYmplY3QoKTsKICAgICAgCiAgICAgIGlmIChkZWxldGVVc2VyID09IHRydWUpIHsKICAgICAgICBpZiAoY29uZmlybSgnRGVsZXRlIHRoZSBzZWxlY3RlZCB1c2VyPycpKSB7CiAgICAgICAgICBkYXRhWyJkZWxldGVVc2VyIl0gPSB0cnVlOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICBzaG93RWxlbWVudCgicG9wdXAiLCBmYWxzZSk7CiAgICAgICAgICByZXR1cm4KICAgICAgICB9CiAgICAgIH0KICAgICAgCiAgICAgIHRoaXNVc2VyW3VzZXJJRF0gID0gbmV3VXNlckRhdGE7CgogICAgICBkYXRhWyJ1c2VyRGF0YSJdID0gdGhpc1VzZXI7CiAgICAgIGRhdGFbImNtZCJdID0gInNhdmVVc2VyRGF0YSI7IGJyZWFrOwogIH0KICAKICB4VGVWZShkYXRhKTsKICAvL2NyZWF0ZVVzZXJzVGFibGUoKQogIHNob3dFbGVtZW50KCJwb3B1cCIsIGZhbHNlKTsKfQoKCg==" + webUI["html/configuration.html"] = "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xLjAiIC8+IAogICAgPHRpdGxlPnhUZVZlPC90aXRsZT4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iY3NzL3NjcmVlbi5jc3MiIHR5cGU9InRleHQvY3NzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iY3NzL2Jhc2UuY3NzIiB0eXBlPSJ0ZXh0L2NzcyI+CiAgICA8c2NyaXB0IGxhbmd1YWdlPSJqYXZhc2NyaXB0IiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiIHNyYz0ianMvY29uZmlndXJhdGlvbl90cy5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IGxhbmd1YWdlPSJqYXZhc2NyaXB0IiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiIHNyYz0ianMvbmV0d29ya190cy5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IGxhbmd1YWdlPSJqYXZhc2NyaXB0IiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiIHNyYz0ianMvbWVudV90cy5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IGxhbmd1YWdlPSJqYXZhc2NyaXB0IiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiIHNyYz0ianMvc2V0dGluZ3NfdHMuanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBsYW5ndWFnZT0iamF2YXNjcmlwdCIgdHlwZT0idGV4dC9qYXZhc2NyaXB0IiBzcmM9ImpzL2Jhc2VfdHMuanMiPjwvc2NyaXB0PgogIDwvaGVhZD4KCiAgICA8Ym9keSBvbmxvYWQ9ImphdmFzY3JpcHQ6IHJlYWR5Rm9yQ29uZmlndXJhdGlvbigwKTsiPgogICAgICAgICAgCiAgICAgIDxkaXYgaWQ9ImxvYWRpbmciIGNsYXNzPSJibG9jayI+CiAgICAgICAgPGRpdiBjbGFzcz0ibG9hZGVyIj48L2Rpdj4KICAgICAgPC9kaXY+CgogICAgICA8ZGl2IGlkPSJoZWFkZXIiIGNsYXNzPSJpbWdDZW50ZXIiPjwvZGl2PgogICAgICA8ZGl2IGlkPSJib3giPgogICAgICAgIAogICAgICAgIDx0YWJsZSBpZD0iY2xpZW50SW5mbyIgY2xhc3M9InZpc2libGUiPgogICAgICAgICAgPHRyPgogICAgICAgICAgICA8dGQgY2xhc3M9InRkS2V5Ij5WZXJzaW9uOjwvdGQ+CiAgICAgICAgICAgIDx0ZCBpZD0idmVyc2lvbiIgY2xhc3M9InRkVmFsIj4mbmJzcDs8L3RkPgogICAgICAgICAgICA8dGQgY2xhc3M9InRkS2V5Ij5PUzo8L3RkPgogICAgICAgICAgICA8dGQgaWQ9Im9zIiBjbGFzcz0idGRWYWwiPiZuYnNwOzwvdGQ+CiAgICAgICAgICA8L3RyPgogICAgICAgICAgPHRyPgogICAgICAgICAgICA8dGQgY2xhc3M9InRkS2V5Ij5VVUlEOjwvdGQ+CiAgICAgICAgICAgIDx0ZCBpZD0idXVpZCIgY2xhc3M9InRkVmFsIj4mbmJzcDs8L3RkPgogICAgICAgICAgICA8dGQgY2xhc3M9InRkS2V5Ij5BcmNoOjwvdGQ+CiAgICAgICAgICAgIDx0ZCBpZD0iYXJjaCIgY2xhc3M9InRkVmFsIj4mbmJzcDs8L3RkPgogICAgICAgICAgPC90cj4KICAgICAgICAgIDx0cj4KICAgICAgICAgICAgPHRkIGNsYXNzPSJ0ZEtleSI+U3RyZWFtczo8L3RkPgogICAgICAgICAgICA8dGQgaWQ9InN0cmVhbXMiIGNsYXNzPSJ0ZFZhbCI+Jm5ic3A7PC90ZD4KICAgICAgICAgICAgPHRkIGNsYXNzPSJ0ZEtleSI+RFZSOjwvdGQ+CiAgICAgICAgICAgIDx0ZCBpZD0iRFZSIiBjbGFzcz0idGRWYWwiPiZuYnNwOzwvdGQ+CiAgICAgICAgICA8L3RyPgogICAgICAgIDwvdGFibGU+CiAgICAgICAgCiAgICAgICAgPGRpdiBpZD0iaGVhZGxpbmUiPgogICAgICAgICAgPGgxIGlkPSJoZWFkLXRleHQiIGNsYXNzPSJjZW50ZXIiPkNvbmZpZ3VyYXRpb248L2gxPiAgICAgIAogICAgICAgIDwvZGl2PgogICAgICAgIDxwIGlkPSJlcnIiIGNsYXNzPSJlcnJvck1zZyBjZW50ZXIiPjwvcD4gICAKICAgICAgICA8ZGl2IGlkPSJjb250ZW50Ij4KICAgICAgICAgICAgCiAgICAgICAgPC9kaXY+CiAgICAgICAgPGRpdiBpZD0iYm94LWZvb3RlciI+CiAgICAgICAgICA8aW5wdXQgaWQ9Im5leHQiIGNsYXNzPSIiIHR5cGU9ImJ1dHRvbiIgbmFtZT0ibmV4dCIgdmFsdWU9Ik5leHQiIG9uY2xpY2s9ImphdmFzY3JpcHQ6IHNhdmVXaXphcmQoKTsiPgogICAgICAgIDwvZGl2PgogICAgICA8L2Rpdj4KICAgIDwvYm9keT4KPC9odG1sPg==" + webUI["html/css/.DS_Store"] = "AAAAAUJ1ZDEAABAAAAAIAAAAEAAAAAAlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAABAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAgLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAACAAAAABAAAAQAAAAAEAAACAAAAAAQAAAQAAAAABAAACAAAAAAEAAAQAAAAAAAAAAAEAABAAAAAAAQAAIAAAAAABAABAAAAAAAEAAIAAAAAAAQABAAAAAAABAAIAAAAAAAEABAAAAAAAAQAIAAAAAAABABAAAAAAAAEAIAAAAAAAAQBAAAAAAAABAIAAAAAAAAEBAAAAAAAAAQIAAAAAAAABBAAAAAAAAAEIAAAAAAAAARAAAAAAAAABIAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAEAsAAABFAAAAJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBERTREIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAYAAAAAAAAAABAAAAgAAAAAEAAAEAAAAAAQAAAgAAAAABAAAEAAAAAAIAAAgAAAAYAAAAAAAAAAABAAAgAAAAAAEAAEAAAAAAAQAAgAAAAAABAAEAAAAAAAEAAgAAAAAAAQAEAAAAAAABAAgAAAAAAAEAEAAAAAAAAQAgAAAAAAABAEAAAAAAAAEAgAAAAAAAAQEAAAAAAAABAgAAAAAAAAEEAAAAAAAAAQgAAAAAAAABEAAAAAAAAAEgAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + webUI["html/css/screen2.css"] = "aDEgewogIGNvbG9yOiBncmVlbjsKfQ==" + webUI["html/img/logo_w_600x200.png"] = "iVBORw0KGgoAAAANSUhEUgAAAlgAAADICAYAAAA0n5+2AAAAAXNSR0IArs4c6QAAAAlwSFlzAAAuIwAALiMBeKU/dgAABCZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjU8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjMwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MzAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NjAwPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6Q29sb3JTcGFjZT4xPC9leGlmOkNvbG9yU3BhY2U+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4yMDA8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAg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" + webUI["html/img/logout.png"] = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAsSwAALEsBpT2WqQAABCRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjU8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjI4ODwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+Mjg4PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NTA8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGRjOnN1YmplY3Q+CiAgICAgICAgICAgIDxyZGY6QmFnLz4KICAgICAgICAgPC9kYzpzdWJqZWN0PgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0xMC0xM1QxMToxMDoxODwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Cg27QeEAAANQSURBVGgF7Zk9aBRBGIZzSRSN8YdoCpEkghCCqK1o4g9IsBCCNmIniCAiWAhqI8G02qiIoqCCWggSEcFKEFPYpVAU8QdFUyiKRpRokBjP55OdZDLZvZnZ7N3Ngh887M7sN/O9797t7c5eTc3/8D8DxWKxGS5Ai//oAEYgvB4OwleQaAtAlp8ERG+Bp6Jei/wYQfRy6NfE67vhG0FtA/TBqK7c2A/bCGJ3wpAhOq4ZphGUroEHcYoT+sIygsgmOAu/EwQndYdhBHV1sB8+Jym19Lf6/faVIRuBG+GRRajtcPWMoKwFbtgUOh6vvBGEzYFj8MNRpEtaZY2gaAe8cVHmmXOc/K3QXIZv/+SUFFgJ96AS8ZwiZ6Ab6iZVzGCPiRbCKRiDasQHip6EdD/PDKyFvfAJQgg5kVfB/VoieT0MQojxE1G9UG/7otWS0A7pPkrb7DM/Ppcp+mAAM/bFGEmL4RL8gVDjI8JWOZ0bEjvhSahO0CXXsLMZWZYegREIMV4jaoHTJyNJJLfC7RCdoOm6sxGVyKAeeBegoU6l0XmLiXlwAqp1o4w7j3d1AwW9YdtnNrnQzkOXLddyfAPHv8Ey6ABpd8N88In2QqHwymfARC5mCrAH0i6qGDr9jk3fbNgFz8A1DkwIS7tDpSVwGdLcexIfPZhPHpf2wS+wRX9a/dPGUakLzBdwNgGJRlQBJtgMw5aJ3qr8TLYUmwVHwXXxZTUiwphvHYxDUnzPxIA5CdXa4E5SVa3fyUhkRtYqpSKbNYxpJiq+ncpDJar7GGlknvcl5mqM05BZH4Xl3iOLpLh7j7OR6MScSzAykplg20QIWA0PDSG+RuTdQVy8sNXP9DgK5N4jK9AvkRpfI4sYF/c2836mQl0nQ4zce66AfZFkTMqYl2DGaSMt/CYOHpsuaPco5bLUzUuMGkLHaQ+ovjwZUZrVdpAHRnnw/Bd5MrJCiY62t4x2+E2uBfkfRg95TdSkK8/LJ7JWF83+Nb5Ww0Zf+E3OviwXVMgT8dLwVRsKEd0A+uP8biMlH01MHAYVF/Oh2lCJenkDqpbUN9nPyzU91QnCRbyEPEXn1sQhxMv7tG1T7cW3rK/r44eVtxfxm6gwBh38zJqPJuUtXu3Z/wLwuBaBLgMkKwAAAABJRU5ErkJggg==" + webUI["html/js/menu_ts.js"] = "dmFyIF9fZXh0ZW5kcyA9ICh0aGlzICYmIHRoaXMuX19leHRlbmRzKSB8fCAoZnVuY3Rpb24gKCkgewogICAgdmFyIGV4dGVuZFN0YXRpY3MgPSBmdW5jdGlvbiAoZCwgYikgewogICAgICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHwKICAgICAgICAgICAgKHsgX19wcm90b19fOiBbXSB9IGluc3RhbmNlb2YgQXJyYXkgJiYgZnVuY3Rpb24gKGQsIGIpIHsgZC5fX3Byb3RvX18gPSBiOyB9KSB8fAogICAgICAgICAgICBmdW5jdGlvbiAoZCwgYikgeyBmb3IgKHZhciBwIGluIGIpIGlmIChiLmhhc093blByb3BlcnR5KHApKSBkW3BdID0gYltwXTsgfTsKICAgICAgICByZXR1cm4gZXh0ZW5kU3RhdGljcyhkLCBiKTsKICAgIH07CiAgICByZXR1cm4gZnVuY3Rpb24gKGQsIGIpIHsKICAgICAgICBleHRlbmRTdGF0aWNzKGQsIGIpOwogICAgICAgIGZ1bmN0aW9uIF9fKCkgeyB0aGlzLmNvbnN0cnVjdG9yID0gZDsgfQogICAgICAgIGQucHJvdG90eXBlID0gYiA9PT0gbnVsbCA/IE9iamVjdC5jcmVhdGUoYikgOiAoX18ucHJvdG90eXBlID0gYi5wcm90b3R5cGUsIG5ldyBfXygpKTsKICAgIH07Cn0pKCk7CnZhciBNYWluTWVudSA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uICgpIHsKICAgIGZ1bmN0aW9uIE1haW5NZW51KCkgewogICAgICAgIHRoaXMuRG9jdW1lbnRJRCA9ICJtYWluLW1lbnUiOwogICAgICAgIHRoaXMuSFRNTFRhZyA9ICJMSSI7CiAgICAgICAgdGhpcy5JbWFnZVBhdGggPSAiaW1nLyI7CiAgICB9CiAgICBNYWluTWVudS5wcm90b3R5cGUuY3JlYXRlSU1HID0gZnVuY3Rpb24gKHNyYykgewogICAgICAgIHZhciBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiSU1HIik7CiAgICAgICAgZWxlbWVudC5zZXRBdHRyaWJ1dGUoInNyYyIsIHRoaXMuSW1hZ2VQYXRoICsgc3JjKTsKICAgICAgICByZXR1cm4gZWxlbWVudDsKICAgIH07CiAgICBNYWluTWVudS5wcm90b3R5cGUuY3JlYXRlVmFsdWUgPSBmdW5jdGlvbiAodmFsdWUpIHsKICAgICAgICB2YXIgZWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlAiKTsKICAgICAgICBlbGVtZW50LmlubmVySFRNTCA9IHZhbHVlOwogICAgICAgIHJldHVybiBlbGVtZW50OwogICAgfTsKICAgIHJldHVybiBNYWluTWVudTsKfSgpKTsKdmFyIE1haW5NZW51SXRlbSA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uIChfc3VwZXIpIHsKICAgIF9fZXh0ZW5kcyhNYWluTWVudUl0ZW0sIF9zdXBlcik7CiAgICBmdW5jdGlvbiBNYWluTWVudUl0ZW0obWVudUtleSwgdmFsdWUsIGltYWdlLCBoZWFkbGluZSkgewogICAgICAgIHZhciBfdGhpcyA9IF9zdXBlci5jYWxsKHRoaXMpIHx8IHRoaXM7CiAgICAgICAgX3RoaXMubWVudUtleSA9IG1lbnVLZXk7CiAgICAgICAgX3RoaXMudmFsdWUgPSB2YWx1ZTsKICAgICAgICBfdGhpcy5pbWdTcmMgPSBpbWFnZTsKICAgICAgICBfdGhpcy5oZWFkbGluZSA9IGhlYWRsaW5lOwogICAgICAgIHJldHVybiBfdGhpczsKICAgIH0KICAgIE1haW5NZW51SXRlbS5wcm90b3R5cGUuY3JlYXRlSXRlbSA9IGZ1bmN0aW9uICgpIHsKICAgICAgICB2YXIgaXRlbSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkxJIik7CiAgICAgICAgaXRlbS5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAiamF2YXNjcmlwdDogb3BlblRoaXNNZW51KHRoaXMpIik7CiAgICAgICAgaXRlbS5zZXRBdHRyaWJ1dGUoImlkIiwgdGhpcy5pZCk7CiAgICAgICAgdmFyIGltZyA9IHRoaXMuY3JlYXRlSU1HKHRoaXMuaW1nU3JjKTsKICAgICAgICB2YXIgdmFsdWUgPSB0aGlzLmNyZWF0ZVZhbHVlKHRoaXMudmFsdWUpOwogICAgICAgIGl0ZW0uYXBwZW5kQ2hpbGQoaW1nKTsKICAgICAgICBpdGVtLmFwcGVuZENoaWxkKHZhbHVlKTsKICAgICAgICB2YXIgZG9jID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQodGhpcy5Eb2N1bWVudElEKTsKICAgICAgICBkb2MuYXBwZW5kQ2hpbGQoaXRlbSk7CiAgICAgICAgc3dpdGNoICh0aGlzLm1lbnVLZXkpIHsKICAgICAgICAgICAgY2FzZSAicGxheWxpc3QiOgogICAgICAgICAgICAgICAgdGhpcy50YWJsZUhlYWRlciA9IFsie3sucGxheWxpc3QudGFibGUucGxheWxpc3R9fSIsICJ7ey5wbGF5bGlzdC50YWJsZS50dW5lcn19IiwgInt7LnBsYXlsaXN0LnRhYmxlLmxhc3RVcGRhdGV9fSIsICJ7ey5wbGF5bGlzdC50YWJsZS5hdmFpbGFiaWxpdHl9fSAlIiwgInt7LnBsYXlsaXN0LnRhYmxlLnR5cGV9fSIsICJ7ey5wbGF5bGlzdC50YWJsZS5zdHJlYW1zfX0iLCAie3sucGxheWxpc3QudGFibGUuZ3JvdXBUaXRsZX19ICUiLCAie3sucGxheWxpc3QudGFibGUudHZnSUR9fSAlIiwgInt7LnBsYXlsaXN0LnRhYmxlLnVuaXF1ZUlEfX0gJSJdOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgInhtbHR2IjoKICAgICAgICAgICAgICAgIHRoaXMudGFibGVIZWFkZXIgPSBbInt7LnhtbHR2LnRhYmxlLmd1aWRlfX0iLCAie3sueG1sdHYudGFibGUubGFzdFVwZGF0ZX19IiwgInt7LnhtbHR2LnRhYmxlLmF2YWlsYWJpbGl0eX19ICUiLCAie3sueG1sdHYudGFibGUuY2hhbm5lbHN9fSIsICJ7ey54bWx0di50YWJsZS5wcm9ncmFtc319Il07CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAiZmlsdGVyIjoKICAgICAgICAgICAgICAgIHRoaXMudGFibGVIZWFkZXIgPSBbInt7LmZpbHRlci50YWJsZS5uYW1lfX0iLCAie3suZmlsdGVyLnRhYmxlLnR5cGV9fSIsICJ7ey5maWx0ZXIudGFibGUuZmlsdGVyfX0iXTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJ1c2VycyI6CiAgICAgICAgICAgICAgICB0aGlzLnRhYmxlSGVhZGVyID0gWyJ7ey51c2Vycy50YWJsZS51c2VybmFtZX19IiwgInt7LnVzZXJzLnRhYmxlLnBhc3N3b3JkfX0iLCAie3sudXNlcnMudGFibGUud2VifX0iLCAie3sudXNlcnMudGFibGUucG1zfX0iLCAie3sudXNlcnMudGFibGUubTN1fX0iLCAie3sudXNlcnMudGFibGUueG1sfX0iLCAie3sudXNlcnMudGFibGUuYXBpfX0iXTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJtYXBwaW5nIjoKICAgICAgICAgICAgICAgIHRoaXMudGFibGVIZWFkZXIgPSBbIkJVTEsiLCAie3subWFwcGluZy50YWJsZS5jaE5vfX0iLCAie3subWFwcGluZy50YWJsZS5sb2dvfX0iLCAie3subWFwcGluZy50YWJsZS5jaGFubmVsTmFtZX19IiwgInt7Lm1hcHBpbmcudGFibGUucGxheWxpc3R9fSIsICJ7ey5tYXBwaW5nLnRhYmxlLmdyb3VwVGl0bGV9fSIsICJ7ey5tYXBwaW5nLnRhYmxlLnhtbHR2RmlsZX19IiwgInt7Lm1hcHBpbmcudGFibGUueG1sdHZJRH19Il07CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICB9CiAgICAgICAgLy9jb25zb2xlLmxvZyh0aGlzLm1lbnVLZXksIHRoaXMudGFibGVIZWFkZXIpOwogICAgfTsKICAgIHJldHVybiBNYWluTWVudUl0ZW07Cn0oTWFpbk1lbnUpKTsKdmFyIENvbnRlbnQgPSAvKiogQGNsYXNzICovIChmdW5jdGlvbiAoKSB7CiAgICBmdW5jdGlvbiBDb250ZW50KCkgewogICAgICAgIHRoaXMuRG9jdW1lbnRJRCA9ICJjb250ZW50IjsKICAgICAgICB0aGlzLlRhYmxlSUQgPSAiY29udGVudF90YWJsZSI7CiAgICAgICAgdGhpcy5oZWFkZXJDbGFzcyA9ICJjb250ZW50X3RhYmxlX2hlYWRlciI7CiAgICAgICAgdGhpcy5pbnRlcmFjdGlvbklEID0gImNvbnRlbnQtaW50ZXJhY3Rpb24iOwogICAgfQogICAgQ29udGVudC5wcm90b3R5cGUuY3JlYXRlSGVhZGxpbmUgPSBmdW5jdGlvbiAodmFsdWUpIHsKICAgICAgICB2YXIgZWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkgzIik7CiAgICAgICAgZWxlbWVudC5pbm5lckhUTUwgPSB2YWx1ZTsKICAgICAgICByZXR1cm4gZWxlbWVudDsKICAgIH07CiAgICBDb250ZW50LnByb3RvdHlwZS5jcmVhdGVIUiA9IGZ1bmN0aW9uICgpIHsKICAgICAgICB2YXIgZWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkhSIik7CiAgICAgICAgcmV0dXJuIGVsZW1lbnQ7CiAgICB9OwogICAgQ29udGVudC5wcm90b3R5cGUuY3JlYXRlSW50ZXJhY3Rpb24gPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgdmFyIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJESVYiKTsKICAgICAgICBlbGVtZW50LnNldEF0dHJpYnV0ZSgiaWQiLCB0aGlzLmludGVyYWN0aW9uSUQpOwogICAgICAgIHJldHVybiBlbGVtZW50OwogICAgfTsKICAgIENvbnRlbnQucHJvdG90eXBlLmNyZWF0ZURJViA9IGZ1bmN0aW9uICgpIHsKICAgICAgICB2YXIgZWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkRJViIpOwogICAgICAgIGVsZW1lbnQuaWQgPSB0aGlzLkRpdklEOwogICAgICAgIHJldHVybiBlbGVtZW50OwogICAgfTsKICAgIENvbnRlbnQucHJvdG90eXBlLmNyZWF0ZVRBQkxFID0gZnVuY3Rpb24gKCkgewogICAgICAgIHZhciBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEFCTEUiKTsKICAgICAgICBlbGVtZW50LmlkID0gdGhpcy5UYWJsZUlEOwogICAgICAgIHJldHVybiBlbGVtZW50OwogICAgfTsKICAgIENvbnRlbnQucHJvdG90eXBlLmNyZWF0ZVRhYmxlUm93ID0gZnVuY3Rpb24gKCkgewogICAgICAgIHZhciBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVFIiKTsKICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSA9IHRoaXMuaGVhZGVyQ2xhc3M7CiAgICAgICAgcmV0dXJuIGVsZW1lbnQ7CiAgICB9OwogICAgQ29udGVudC5wcm90b3R5cGUuY3JlYXRlVGFibGVDb250ZW50ID0gZnVuY3Rpb24gKG1lbnVLZXkpIHsKICAgICAgICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKICAgICAgICB2YXIgcm93cyA9IG5ldyBBcnJheSgpOwogICAgICAgIHN3aXRjaCAobWVudUtleSkgewogICAgICAgICAgICBjYXNlICJwbGF5bGlzdCI6CiAgICAgICAgICAgICAgICB2YXIgZmlsZVR5cGVzID0gbmV3IEFycmF5KCJtM3UiLCAiaGRociIpOwogICAgICAgICAgICAgICAgZmlsZVR5cGVzLmZvckVhY2goZnVuY3Rpb24gKGZpbGVUeXBlKSB7CiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IFNFUlZFUlsic2V0dGluZ3MiXVsiZmlsZXMiXVtmaWxlVHlwZV07CiAgICAgICAgICAgICAgICAgICAgdmFyIGtleXMgPSBnZXRPYmpLZXlzKGRhdGEpOwogICAgICAgICAgICAgICAgICAgIGtleXMuZm9yRWFjaChmdW5jdGlvbiAoa2V5KSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciB0ciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlRSIik7CiAgICAgICAgICAgICAgICAgICAgICAgIHRyLmlkID0ga2V5OwogICAgICAgICAgICAgICAgICAgICAgICB0ci5zZXRBdHRyaWJ1dGUoJ29uY2xpY2snLCAnamF2YXNjcmlwdDogb3BlblBvcFVwKCInICsgZmlsZVR5cGUgKyAnIiwgdGhpcyknKTsKICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZFR5cGUgPSAiUCI7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSBkYXRhW2tleV1bIm5hbWUiXTsKICAgICAgICAgICAgICAgICAgICAgICAgdHIuYXBwZW5kQ2hpbGQoY2VsbC5jcmVhdGVDZWxsKCkpOwogICAgICAgICAgICAgICAgICAgICAgICB2YXIgY2VsbCA9IG5ldyBDZWxsKCk7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKFNFUlZFUlsic2V0dGluZ3MiXVsiYnVmZmVyIl0gPT0gdHJ1ZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9IGRhdGFba2V5XVsidHVuZXIiXTsKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSAiLSI7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgdHIuYXBwZW5kQ2hpbGQoY2VsbC5jcmVhdGVDZWxsKCkpOwogICAgICAgICAgICAgICAgICAgICAgICB2YXIgY2VsbCA9IG5ldyBDZWxsKCk7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9IGRhdGFba2V5XVsibGFzdC51cGRhdGUiXTsKICAgICAgICAgICAgICAgICAgICAgICAgdHIuYXBwZW5kQ2hpbGQoY2VsbC5jcmVhdGVDZWxsKCkpOwogICAgICAgICAgICAgICAgICAgICAgICB2YXIgY2VsbCA9IG5ldyBDZWxsKCk7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9IGRhdGFba2V5XVsicHJvdmlkZXIuYXZhaWxhYmlsaXR5Il07CiAgICAgICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKGNlbGwuY3JlYXRlQ2VsbCgpKTsKICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZFR5cGUgPSAiUCI7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSBkYXRhW2tleV1bInR5cGUiXS50b1VwcGVyQ2FzZSgpOwogICAgICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZCA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIlAiOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gZGF0YVtrZXldWyJjb21wYXRpYmlsaXR5Il1bInN0cmVhbXMiXTsKICAgICAgICAgICAgICAgICAgICAgICAgdHIuYXBwZW5kQ2hpbGQoY2VsbC5jcmVhdGVDZWxsKCkpOwogICAgICAgICAgICAgICAgICAgICAgICB2YXIgY2VsbCA9IG5ldyBDZWxsKCk7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9IGRhdGFba2V5XVsiY29tcGF0aWJpbGl0eSJdWyJncm91cC50aXRsZSJdOwogICAgICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZCA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIlAiOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gZGF0YVtrZXldWyJjb21wYXRpYmlsaXR5Il1bInR2Zy5pZCJdOwogICAgICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZCA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIlAiOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gZGF0YVtrZXldWyJjb21wYXRpYmlsaXR5Il1bInN0cmVhbS5pZCJdOwogICAgICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHJvd3MucHVzaCh0cik7CiAgICAgICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJmaWx0ZXIiOgogICAgICAgICAgICAgICAgZGVsZXRlIFNFUlZFUlsic2V0dGluZ3MiXVsiZmlsdGVyIl1bLTFdOwogICAgICAgICAgICAgICAgZGF0YSA9IFNFUlZFUlsic2V0dGluZ3MiXVsiZmlsdGVyIl07CiAgICAgICAgICAgICAgICB2YXIga2V5cyA9IGdldE9iaktleXMoZGF0YSk7CiAgICAgICAgICAgICAgICBrZXlzLmZvckVhY2goZnVuY3Rpb24gKGtleSkgewogICAgICAgICAgICAgICAgICAgIHZhciB0ciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlRSIik7CiAgICAgICAgICAgICAgICAgICAgdHIuaWQgPSBrZXk7CiAgICAgICAgICAgICAgICAgICAgdHIuc2V0QXR0cmlidXRlKCdvbmNsaWNrJywgJ2phdmFzY3JpcHQ6IG9wZW5Qb3BVcCgiJyArIGRhdGFba2V5XVsidHlwZSJdICsgJyIsIHRoaXMpJyk7CiAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIlAiOwogICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSBkYXRhW2tleV1bIm5hbWUiXTsKICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIlAiOwogICAgICAgICAgICAgICAgICAgIHN3aXRjaCAoZGF0YVtrZXldWyJ0eXBlIl0pIHsKICAgICAgICAgICAgICAgICAgICAgICAgY2FzZSAiY3VzdG9tLWZpbHRlciI6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gInt7LmZpbHRlci5jdXN0b219fSI7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgICAgICAgICAgY2FzZSAiZ3JvdXAtdGl0bGUiOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9ICJ7ey5maWx0ZXIuZ3JvdXB9fSI7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgICAgICAgICAgZGVmYXVsdDoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIlAiOwogICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSBkYXRhW2tleV1bImZpbHRlciJdOwogICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKGNlbGwuY3JlYXRlQ2VsbCgpKTsKICAgICAgICAgICAgICAgICAgICByb3dzLnB1c2godHIpOwogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAieG1sdHYiOgogICAgICAgICAgICAgICAgdmFyIGZpbGVUeXBlcyA9IG5ldyBBcnJheSgieG1sdHYiKTsKICAgICAgICAgICAgICAgIGZpbGVUeXBlcy5mb3JFYWNoKGZ1bmN0aW9uIChmaWxlVHlwZSkgewogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBTRVJWRVJbInNldHRpbmdzIl1bImZpbGVzIl1bZmlsZVR5cGVdOwogICAgICAgICAgICAgICAgICAgIHZhciBrZXlzID0gZ2V0T2JqS2V5cyhkYXRhKTsKICAgICAgICAgICAgICAgICAgICBrZXlzLmZvckVhY2goZnVuY3Rpb24gKGtleSkgewogICAgICAgICAgICAgICAgICAgICAgICB2YXIgdHIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJUUiIpOwogICAgICAgICAgICAgICAgICAgICAgICB0ci5pZCA9IGtleTsKICAgICAgICAgICAgICAgICAgICAgICAgdHIuc2V0QXR0cmlidXRlKCdvbmNsaWNrJywgJ2phdmFzY3JpcHQ6IG9wZW5Qb3BVcCgiJyArIGZpbGVUeXBlICsgJyIsIHRoaXMpJyk7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZCA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIlAiOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gZGF0YVtrZXldWyJuYW1lIl07CiAgICAgICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKGNlbGwuY3JlYXRlQ2VsbCgpKTsKICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZFR5cGUgPSAiUCI7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSBkYXRhW2tleV1bImxhc3QudXBkYXRlIl07CiAgICAgICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKGNlbGwuY3JlYXRlQ2VsbCgpKTsKICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZFR5cGUgPSAiUCI7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSBkYXRhW2tleV1bInByb3ZpZGVyLmF2YWlsYWJpbGl0eSJdOwogICAgICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZCA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIlAiOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gZGF0YVtrZXldWyJjb21wYXRpYmlsaXR5Il1bInhtbHR2LmNoYW5uZWxzIl07CiAgICAgICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKGNlbGwuY3JlYXRlQ2VsbCgpKTsKICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZFR5cGUgPSAiUCI7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSBkYXRhW2tleV1bImNvbXBhdGliaWxpdHkiXVsieG1sdHYucHJvZ3JhbXMiXTsKICAgICAgICAgICAgICAgICAgICAgICAgdHIuYXBwZW5kQ2hpbGQoY2VsbC5jcmVhdGVDZWxsKCkpOwogICAgICAgICAgICAgICAgICAgICAgICByb3dzLnB1c2godHIpOwogICAgICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAidXNlcnMiOgogICAgICAgICAgICAgICAgdmFyIGZpbGVUeXBlcyA9IG5ldyBBcnJheSgidXNlcnMiKTsKICAgICAgICAgICAgICAgIGZpbGVUeXBlcy5mb3JFYWNoKGZ1bmN0aW9uIChmaWxlVHlwZSkgewogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBTRVJWRVJbZmlsZVR5cGVdOwogICAgICAgICAgICAgICAgICAgIHZhciBrZXlzID0gZ2V0T2JqS2V5cyhkYXRhKTsKICAgICAgICAgICAgICAgICAgICBrZXlzLmZvckVhY2goZnVuY3Rpb24gKGtleSkgewogICAgICAgICAgICAgICAgICAgICAgICB2YXIgdHIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJUUiIpOwogICAgICAgICAgICAgICAgICAgICAgICB0ci5pZCA9IGtleTsKICAgICAgICAgICAgICAgICAgICAgICAgdHIuc2V0QXR0cmlidXRlKCdvbmNsaWNrJywgJ2phdmFzY3JpcHQ6IG9wZW5Qb3BVcCgiJyArIGZpbGVUeXBlICsgJyIsIHRoaXMpJyk7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZCA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIlAiOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gZGF0YVtrZXldWyJkYXRhIl1bInVzZXJuYW1lIl07CiAgICAgICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKGNlbGwuY3JlYXRlQ2VsbCgpKTsKICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZFR5cGUgPSAiUCI7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSAiKioqKioqIjsKICAgICAgICAgICAgICAgICAgICAgICAgdHIuYXBwZW5kQ2hpbGQoY2VsbC5jcmVhdGVDZWxsKCkpOwogICAgICAgICAgICAgICAgICAgICAgICB2YXIgY2VsbCA9IG5ldyBDZWxsKCk7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGRhdGFba2V5XVsiZGF0YSJdWyJhdXRoZW50aWNhdGlvbi53ZWIiXSA9PSB0cnVlKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gIuKckyI7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gIi0iOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKGNlbGwuY3JlYXRlQ2VsbCgpKTsKICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZFR5cGUgPSAiUCI7CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChkYXRhW2tleV1bImRhdGEiXVsiYXV0aGVudGljYXRpb24ucG1zIl0gPT0gdHJ1ZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9ICLinJMiOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9ICItIjsKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZCA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIlAiOwogICAgICAgICAgICAgICAgICAgICAgICBpZiAoZGF0YVtrZXldWyJkYXRhIl1bImF1dGhlbnRpY2F0aW9uLm0zdSJdID09IHRydWUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSAi4pyTIjsKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSAiLSI7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgdHIuYXBwZW5kQ2hpbGQoY2VsbC5jcmVhdGVDZWxsKCkpOwogICAgICAgICAgICAgICAgICAgICAgICB2YXIgY2VsbCA9IG5ldyBDZWxsKCk7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGRhdGFba2V5XVsiZGF0YSJdWyJhdXRoZW50aWNhdGlvbi54bWwiXSA9PSB0cnVlKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gIuKckyI7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gIi0iOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKGNlbGwuY3JlYXRlQ2VsbCgpKTsKICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZFR5cGUgPSAiUCI7CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChkYXRhW2tleV1bImRhdGEiXVsiYXV0aGVudGljYXRpb24uYXBpIl0gPT0gdHJ1ZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9ICLinJMiOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9ICItIjsKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHJvd3MucHVzaCh0cik7CiAgICAgICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJtYXBwaW5nIjoKICAgICAgICAgICAgICAgIEJVTEtfRURJVCA9IGZhbHNlOwogICAgICAgICAgICAgICAgY3JlYXRlU2VhcmNoT2JqKCk7CiAgICAgICAgICAgICAgICBjaGVja1VuZG8oImVwZ01hcHBpbmciKTsKICAgICAgICAgICAgICAgIGNvbnNvbGUubG9nKCJNQVBQSU5HIik7CiAgICAgICAgICAgICAgICBkYXRhID0gU0VSVkVSWyJ4ZXBnIl1bImVwZ01hcHBpbmciXTsKICAgICAgICAgICAgICAgIHZhciBrZXlzID0gZ2V0T2JqS2V5cyhkYXRhKTsKICAgICAgICAgICAgICAgIGtleXMuZm9yRWFjaChmdW5jdGlvbiAoa2V5KSB7CiAgICAgICAgICAgICAgICAgICAgdmFyIHRyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVFIiKTsKICAgICAgICAgICAgICAgICAgICB0ci5pZCA9IGtleTsKICAgICAgICAgICAgICAgICAgICAvL3RyLnNldEF0dHJpYnV0ZSgnb25jb250ZXh0bWVudScsICdqYXZhc2NyaXB0OiByaWdodENsaWNrKHRoaXMpJykKICAgICAgICAgICAgICAgICAgICBzd2l0Y2ggKGRhdGFba2V5XVsieC1hY3RpdmUiXSkgewogICAgICAgICAgICAgICAgICAgICAgICBjYXNlIHRydWU6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ci5jbGFzc05hbWUgPSAiYWN0aXZlRVBHIjsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgICAgICAgICBjYXNlIGZhbHNlOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHIuY2xhc3NOYW1lID0gIm5vdEFjdGl2ZUVQRyI7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgLy8gQnVsawogICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJCVUxLIjsKICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gZmFsc2U7CiAgICAgICAgICAgICAgICAgICAgdHIuYXBwZW5kQ2hpbGQoY2VsbC5jcmVhdGVDZWxsKCkpOwogICAgICAgICAgICAgICAgICAgIC8vIEthbmFsbnVtbWVyCiAgICAgICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGQgPSB0cnVlOwogICAgICAgICAgICAgICAgICAgIGNlbGwuY2hpbGRUeXBlID0gIklOUFVUQ0hBTk5FTCI7CiAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9IGRhdGFba2V5XVsieC1jaGFubmVsSUQiXTsKICAgICAgICAgICAgICAgICAgICAvL3RkLnNldEF0dHJpYnV0ZSgnb25jbGljaycsICdqYXZhc2NyaXB0OiBjaGFuZ2VDaGFubmVsTnVtYmVyKCInICsga2V5ICsgJyIsIHRoaXMpJykKICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgICAgICAgICAgLy8gTG9nbwogICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJJTUciOwogICAgICAgICAgICAgICAgICAgIGNlbGwuaW1hZ2VVUkwgPSBkYXRhW2tleV1bInR2Zy1sb2dvIl07CiAgICAgICAgICAgICAgICAgICAgdmFyIHRkID0gY2VsbC5jcmVhdGVDZWxsKCk7CiAgICAgICAgICAgICAgICAgICAgdGQuc2V0QXR0cmlidXRlKCdvbmNsaWNrJywgJ2phdmFzY3JpcHQ6IG9wZW5Qb3BVcCgibWFwcGluZyIsIHRoaXMpJyk7CiAgICAgICAgICAgICAgICAgICAgdGQuaWQgPSBrZXk7CiAgICAgICAgICAgICAgICAgICAgdHIuYXBwZW5kQ2hpbGQodGQpOwogICAgICAgICAgICAgICAgICAgIC8vIEthbmFsbmFtZQogICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNsYXNzTmFtZSA9IGRhdGFba2V5XVsieC1jYXRlZ29yeSJdOwogICAgICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSBkYXRhW2tleV1bIngtbmFtZSJdOwogICAgICAgICAgICAgICAgICAgIHZhciB0ZCA9IGNlbGwuY3JlYXRlQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgIHRkLnNldEF0dHJpYnV0ZSgnb25jbGljaycsICdqYXZhc2NyaXB0OiBvcGVuUG9wVXAoIm1hcHBpbmciLCB0aGlzKScpOwogICAgICAgICAgICAgICAgICAgIHRkLmlkID0ga2V5OwogICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKHRkKTsKICAgICAgICAgICAgICAgICAgICAvLyBQbGF5bGlzdAogICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgICAgICAvL2NlbGwudmFsdWUgPSBkYXRhW2tleV1bIl9maWxlLm0zdS5uYW1lIl0gCiAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9IGdldFZhbHVlRnJvbVByb3ZpZGVyRmlsZShkYXRhW2tleV1bIl9maWxlLm0zdS5pZCJdLCAibTN1IiwgIm5hbWUiKTsKICAgICAgICAgICAgICAgICAgICB2YXIgdGQgPSBjZWxsLmNyZWF0ZUNlbGwoKTsKICAgICAgICAgICAgICAgICAgICB0ZC5zZXRBdHRyaWJ1dGUoJ29uY2xpY2snLCAnamF2YXNjcmlwdDogb3BlblBvcFVwKCJtYXBwaW5nIiwgdGhpcyknKTsKICAgICAgICAgICAgICAgICAgICB0ZC5pZCA9IGtleTsKICAgICAgICAgICAgICAgICAgICB0ci5hcHBlbmRDaGlsZCh0ZCk7CiAgICAgICAgICAgICAgICAgICAgLy8gR3J1cHBlIChncm91cC10aXRsZSkKICAgICAgICAgICAgICAgICAgICB2YXIgY2VsbCA9IG5ldyBDZWxsKCk7CiAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZCA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZFR5cGUgPSAiUCI7CiAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9IGRhdGFba2V5XVsieC1ncm91cC10aXRsZSJdOwogICAgICAgICAgICAgICAgICAgIHZhciB0ZCA9IGNlbGwuY3JlYXRlQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgIHRkLnNldEF0dHJpYnV0ZSgnb25jbGljaycsICdqYXZhc2NyaXB0OiBvcGVuUG9wVXAoIm1hcHBpbmciLCB0aGlzKScpOwogICAgICAgICAgICAgICAgICAgIHRkLmlkID0ga2V5OwogICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKHRkKTsKICAgICAgICAgICAgICAgICAgICAvLyBYTUxUViBEYXRlaQogICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgICAgICBpZiAoZGF0YVtrZXldWyJ4LXhtbHR2LWZpbGUiXSAhPSAiLSIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9IGdldFZhbHVlRnJvbVByb3ZpZGVyRmlsZShkYXRhW2tleV1bIngteG1sdHYtZmlsZSJdLCAieG1sdHYiLCAibmFtZSIpOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9IGRhdGFba2V5XVsieC14bWx0di1maWxlIl07CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIHZhciB0ZCA9IGNlbGwuY3JlYXRlQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgIHRkLnNldEF0dHJpYnV0ZSgnb25jbGljaycsICdqYXZhc2NyaXB0OiBvcGVuUG9wVXAoIm1hcHBpbmciLCB0aGlzKScpOwogICAgICAgICAgICAgICAgICAgIHRkLmlkID0ga2V5OwogICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKHRkKTsKICAgICAgICAgICAgICAgICAgICAvLyBYTUxUViBLYW5hbAogICAgICAgICAgICAgICAgICAgIHZhciBjZWxsID0gbmV3IENlbGwoKTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgICAgICAvL3ZhciB2YWx1ZSA9IHN0ci5zdWJzdHJpbmcoMSwgNCk7CiAgICAgICAgICAgICAgICAgICAgdmFyIHZhbHVlID0gZGF0YVtrZXldWyJ4LW1hcHBpbmciXTsKICAgICAgICAgICAgICAgICAgICBpZiAodmFsdWUubGVuZ3RoID4gMjApIHsKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBkYXRhW2tleV1bIngtbWFwcGluZyJdLnN1YnN0cmluZygwLCAyMCkgKyAiLi4uIjsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgY2VsbC52YWx1ZSA9IHZhbHVlOwogICAgICAgICAgICAgICAgICAgIHZhciB0ZCA9IGNlbGwuY3JlYXRlQ2VsbCgpOwogICAgICAgICAgICAgICAgICAgIHRkLnNldEF0dHJpYnV0ZSgnb25jbGljaycsICdqYXZhc2NyaXB0OiBvcGVuUG9wVXAoIm1hcHBpbmciLCB0aGlzKScpOwogICAgICAgICAgICAgICAgICAgIHRkLmlkID0ga2V5OwogICAgICAgICAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKHRkKTsKICAgICAgICAgICAgICAgICAgICByb3dzLnB1c2godHIpOwogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAic2V0dGluZ3MiOgogICAgICAgICAgICAgICAgYWxlcnQoKTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBkZWZhdWx0OgogICAgICAgICAgICAgICAgY29uc29sZS5sb2coIlRhYmxlIGNvbnRlbnQgKG1lbnVLZXkpOiIsIG1lbnVLZXkpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgfQogICAgICAgIHJldHVybiByb3dzOwogICAgfTsKICAgIHJldHVybiBDb250ZW50Owp9KCkpOwp2YXIgQ2VsbCA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uICgpIHsKICAgIGZ1bmN0aW9uIENlbGwoKSB7CiAgICB9CiAgICBDZWxsLnByb3RvdHlwZS5jcmVhdGVDZWxsID0gZnVuY3Rpb24gKCkgewogICAgICAgIHZhciB0ZCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgaWYgKHRoaXMuY2hpbGQgPT0gdHJ1ZSkgewogICAgICAgICAgICB2YXIgZWxlbWVudDsKICAgICAgICAgICAgc3dpdGNoICh0aGlzLmNoaWxkVHlwZSkgewogICAgICAgICAgICAgICAgY2FzZSAiUCI6CiAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQodGhpcy5jaGlsZFR5cGUpOwogICAgICAgICAgICAgICAgICAgIGVsZW1lbnQuaW5uZXJIVE1MID0gdGhpcy52YWx1ZTsKICAgICAgICAgICAgICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSA9IHRoaXMuY2xhc3NOYW1lOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgY2FzZSAiSU5QVVQiOgogICAgICAgICAgICAgICAgICAgIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KHRoaXMuY2hpbGRUeXBlKTsKICAgICAgICAgICAgICAgICAgICBlbGVtZW50LnZhbHVlID0gdGhpcy52YWx1ZTsKICAgICAgICAgICAgICAgICAgICBlbGVtZW50LnR5cGUgPSAidGV4dCI7CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICBjYXNlICJJTlBVVENIQU5ORUwiOgogICAgICAgICAgICAgICAgICAgIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJJTlBVVCIpOwogICAgICAgICAgICAgICAgICAgIGVsZW1lbnQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZzY3JpcHQ6IGNoYW5nZUNoYW5uZWxOdW1iZXIodGhpcykiKTsKICAgICAgICAgICAgICAgICAgICBlbGVtZW50LnZhbHVlID0gdGhpcy52YWx1ZTsKICAgICAgICAgICAgICAgICAgICBlbGVtZW50LnR5cGUgPSAidGV4dCI7CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICBjYXNlICJCVUxLIjoKICAgICAgICAgICAgICAgICAgICBlbGVtZW50ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiSU5QVVQiKTsKICAgICAgICAgICAgICAgICAgICBlbGVtZW50LmNoZWNrZWQgPSB0aGlzLnZhbHVlOwogICAgICAgICAgICAgICAgICAgIGVsZW1lbnQudHlwZSA9ICJjaGVja2JveCI7CiAgICAgICAgICAgICAgICAgICAgZWxlbWVudC5jbGFzc05hbWUgPSAiYnVsayBoaWRlQnVsayI7CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICBjYXNlICJCVUxLX0hFQUQiOgogICAgICAgICAgICAgICAgICAgIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJJTlBVVCIpOwogICAgICAgICAgICAgICAgICAgIGVsZW1lbnQuY2hlY2tlZCA9IHRoaXMudmFsdWU7CiAgICAgICAgICAgICAgICAgICAgZWxlbWVudC50eXBlID0gImNoZWNrYm94IjsKICAgICAgICAgICAgICAgICAgICBlbGVtZW50LmNsYXNzTmFtZSA9ICJidWxrIGhpZGVCdWxrIjsKICAgICAgICAgICAgICAgICAgICBlbGVtZW50LnNldEF0dHJpYnV0ZSgib25jbGljayIsICJqYXZhc2NyaXB0OiBzZWxlY3RBbGxDaGFubmVscygpIik7CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICBjYXNlICJJTUciOgogICAgICAgICAgICAgICAgICAgIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KHRoaXMuY2hpbGRUeXBlKTsKICAgICAgICAgICAgICAgICAgICBlbGVtZW50LnNldEF0dHJpYnV0ZSgic3JjIiwgdGhpcy5pbWFnZVVSTCk7CiAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXMuaW1hZ2VVUkwgIT0gIiIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudC5zZXRBdHRyaWJ1dGUoIm9uZXJyb3IiLCAiamF2YXNjcmlwdDogdGhpcy5vbmVycm9yPW51bGw7dGhpcy5zcmM9JyciKTsKICAgICAgICAgICAgICAgICAgICAgICAgLy9vbmVycm9yPSJ0aGlzLm9uZXJyb3I9bnVsbDt0aGlzLnNyYz0nbWlzc2luZy5naWYnOyIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgdGQuYXBwZW5kQ2hpbGQoZWxlbWVudCk7CiAgICAgICAgfQogICAgICAgIGVsc2UgewogICAgICAgICAgICB0ZC5pbm5lckhUTUwgPSB0aGlzLnZhbHVlOwogICAgICAgIH0KICAgICAgICBpZiAodGhpcy5vbmNsaWNrID09IHRydWUpIHsKICAgICAgICAgICAgdGQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgdGhpcy5vbmNsaWNrRnVua3Rpb24pOwogICAgICAgICAgICB0ZC5jbGFzc05hbWUgPSAicG9pbnRlciI7CiAgICAgICAgfQogICAgICAgIGlmICh0aGlzLnRkQ2xhc3NOYW1lICE9IHVuZGVmaW5lZCkgewogICAgICAgICAgICB0ZC5jbGFzc05hbWUgPSB0aGlzLnRkQ2xhc3NOYW1lOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdGQ7CiAgICB9OwogICAgcmV0dXJuIENlbGw7Cn0oKSk7CnZhciBTaG93Q29udGVudCA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uIChfc3VwZXIpIHsKICAgIF9fZXh0ZW5kcyhTaG93Q29udGVudCwgX3N1cGVyKTsKICAgIGZ1bmN0aW9uIFNob3dDb250ZW50KG1lbnVJRCkgewogICAgICAgIHZhciBfdGhpcyA9IF9zdXBlci5jYWxsKHRoaXMpIHx8IHRoaXM7CiAgICAgICAgX3RoaXMubWVudUlEID0gbWVudUlEOwogICAgICAgIHJldHVybiBfdGhpczsKICAgIH0KICAgIFNob3dDb250ZW50LnByb3RvdHlwZS5jcmVhdGVJbnB1dCA9IGZ1bmN0aW9uICh0eXBlLCBuYW1lLCB2YWx1ZSkgewogICAgICAgIHZhciBpbnB1dCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIklOUFVUIik7CiAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJ0eXBlIiwgdHlwZSk7CiAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJuYW1lIiwgbmFtZSk7CiAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJ2YWx1ZSIsIHZhbHVlKTsKICAgICAgICByZXR1cm4gaW5wdXQ7CiAgICB9OwogICAgU2hvd0NvbnRlbnQucHJvdG90eXBlLnNob3cgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgQ09MVU1OX1RPX1NPUlQgPSAtMTsKICAgICAgICAvLyBBbHRlbiBJbmhhbHQgbMO2c2NoZW4KICAgICAgICB2YXIgZG9jID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQodGhpcy5Eb2N1bWVudElEKTsKICAgICAgICBkb2MuaW5uZXJIVE1MID0gIiI7CiAgICAgICAgc2hvd1ByZXZpZXcoZmFsc2UpOwogICAgICAgIC8vIMOcYmVyc2NocmlmdAogICAgICAgIHZhciBoZWFkbGluZSA9IG1lbnVJdGVtc1t0aGlzLm1lbnVJRF0uaGVhZGxpbmU7CiAgICAgICAgdmFyIG1lbnVLZXkgPSBtZW51SXRlbXNbdGhpcy5tZW51SURdLm1lbnVLZXk7CiAgICAgICAgdmFyIGggPSB0aGlzLmNyZWF0ZUhlYWRsaW5lKGhlYWRsaW5lKTsKICAgICAgICBkb2MuYXBwZW5kQ2hpbGQoaCk7CiAgICAgICAgdmFyIGhyID0gdGhpcy5jcmVhdGVIUigpOwogICAgICAgIGRvYy5hcHBlbmRDaGlsZChocik7CiAgICAgICAgLy8gSW50ZXJha3Rpb24KICAgICAgICB2YXIgZGl2ID0gdGhpcy5jcmVhdGVJbnRlcmFjdGlvbigpOwogICAgICAgIGRvYy5hcHBlbmRDaGlsZChkaXYpOwogICAgICAgIHZhciBpbnRlcmFjdGlvbiA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHRoaXMuaW50ZXJhY3Rpb25JRCk7CiAgICAgICAgc3dpdGNoIChtZW51S2V5KSB7CiAgICAgICAgICAgIGNhc2UgInBsYXlsaXN0IjoKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IHRoaXMuY3JlYXRlSW5wdXQoImJ1dHRvbiIsIG1lbnVLZXksICJ7ey5idXR0b24ubmV3fX0iKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgiaWQiLCAiLSIpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IG9wZW5Qb3BVcCgicGxheWxpc3QiKScpOwogICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24uYXBwZW5kQ2hpbGQoaW5wdXQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImZpbHRlciI6CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSB0aGlzLmNyZWF0ZUlucHV0KCJidXR0b24iLCBtZW51S2V5LCAie3suYnV0dG9uLm5ld319Iik7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoImlkIiwgLTEpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IG9wZW5Qb3BVcCgiZmlsdGVyIiwgdGhpcyknKTsKICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmFwcGVuZENoaWxkKGlucHV0KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJ4bWx0diI6CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSB0aGlzLmNyZWF0ZUlucHV0KCJidXR0b24iLCBtZW51S2V5LCAie3suYnV0dG9uLm5ld319Iik7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoImlkIiwgInhtbHR2Iik7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAnamF2YXNjcmlwdDogb3BlblBvcFVwKCJ4bWx0diIpJyk7CiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbi5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAidXNlcnMiOgogICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gdGhpcy5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgbWVudUtleSwgInt7LmJ1dHRvbi5uZXd9fSIpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJpZCIsICJ1c2VycyIpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IG9wZW5Qb3BVcCgidXNlcnMiKScpOwogICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24uYXBwZW5kQ2hpbGQoaW5wdXQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgIm1hcHBpbmciOgogICAgICAgICAgICAgICAgc2hvd0VsZW1lbnQoImxvYWRpbmciLCB0cnVlKTsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IHRoaXMuY3JlYXRlSW5wdXQoImJ1dHRvbiIsIG1lbnVLZXksICJ7ey5idXR0b24uc2F2ZX19Iik7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAnamF2YXNjcmlwdDogc2F2ZVBvcHVwRGF0YSgibWFwcGluZyIsICIiLCAiIiknKTsKICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmFwcGVuZENoaWxkKGlucHV0KTsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IHRoaXMuY3JlYXRlSW5wdXQoImJ1dHRvbiIsIG1lbnVLZXksICJ7ey5idXR0b24uYnVsa0VkaXR9fSIpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IGJ1bGtFZGl0KCknKTsKICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmFwcGVuZENoaWxkKGlucHV0KTsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IHRoaXMuY3JlYXRlSW5wdXQoInNlYXJjaCIsICJzZWFyY2giLCAiIik7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoImlkIiwgInNlYXJjaE1hcHBpbmciKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAie3suYnV0dG9uLnNlYXJjaH19Iik7CiAgICAgICAgICAgICAgICBpbnB1dC5jbGFzc05hbWUgPSAic2VhcmNoIjsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jaGFuZ2UiLCAnamF2YXNjcmlwdDogc2VhcmNoSW5NYXBwaW5nKCknKTsKICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmFwcGVuZENoaWxkKGlucHV0KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJzZXR0aW5ncyI6CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSB0aGlzLmNyZWF0ZUlucHV0KCJidXR0b24iLCBtZW51S2V5LCAie3suYnV0dG9uLnNhdmV9fSIpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IHNhdmVTZXR0aW5ncygpOycpOwogICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24uYXBwZW5kQ2hpbGQoaW5wdXQpOwogICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gdGhpcy5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgbWVudUtleSwgInt7LmJ1dHRvbi5iYWNrdXB9fSIpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IGJhY2t1cCgpOycpOwogICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24uYXBwZW5kQ2hpbGQoaW5wdXQpOwogICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gdGhpcy5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgbWVudUtleSwgInt7LmJ1dHRvbi5yZXN0b3JlfX0iKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiByZXN0b3JlKCk7Jyk7CiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbi5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICB2YXIgd3JhcHBlciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkRJViIpOwogICAgICAgICAgICAgICAgd3JhcHBlci5zZXRBdHRyaWJ1dGUoImlkIiwgImJveC13cmFwcGVyIik7CiAgICAgICAgICAgICAgICBkb2MuYXBwZW5kQ2hpbGQod3JhcHBlcik7CiAgICAgICAgICAgICAgICB0aGlzLkRpdklEID0gImNvbnRlbnRfc2V0dGluZ3MiOwogICAgICAgICAgICAgICAgdmFyIHNldHRpbmdzID0gdGhpcy5jcmVhdGVESVYoKTsKICAgICAgICAgICAgICAgIHdyYXBwZXIuYXBwZW5kQ2hpbGQoc2V0dGluZ3MpOwogICAgICAgICAgICAgICAgc2hvd1NldHRpbmdzKCk7CiAgICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAibG9nIjoKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IHRoaXMuY3JlYXRlSW5wdXQoImJ1dHRvbiIsIG1lbnVLZXksICJ7ey5idXR0b24ucmVzZXRsb2dzfX0iKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiByZXNldExvZ3MoKTsnKTsKICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmFwcGVuZENoaWxkKGlucHV0KTsKICAgICAgICAgICAgICAgIHZhciB3cmFwcGVyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiRElWIik7CiAgICAgICAgICAgICAgICB3cmFwcGVyLnNldEF0dHJpYnV0ZSgiaWQiLCAiYm94LXdyYXBwZXIiKTsKICAgICAgICAgICAgICAgIGRvYy5hcHBlbmRDaGlsZCh3cmFwcGVyKTsKICAgICAgICAgICAgICAgIHRoaXMuRGl2SUQgPSAiY29udGVudF9sb2ciOwogICAgICAgICAgICAgICAgdmFyIGxvZ3MgPSB0aGlzLmNyZWF0ZURJVigpOwogICAgICAgICAgICAgICAgd3JhcHBlci5hcHBlbmRDaGlsZChsb2dzKTsKICAgICAgICAgICAgICAgIHNob3dMb2dzKHRydWUpOwogICAgICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImxvZ291dCI6CiAgICAgICAgICAgICAgICBsb2NhdGlvbi5yZWxvYWQoKTsKICAgICAgICAgICAgICAgIGRvY3VtZW50LmNvb2tpZSA9ICJUb2tlbj0gOyBleHBpcmVzID0gVGh1LCAwMSBKYW4gMTk3MCAwMDowMDowMCBHTVQiOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGRlZmF1bHQ6CiAgICAgICAgICAgICAgICBjb25zb2xlLmxvZygiU2hvdyBjb250ZW50IChtZW51S2V5KToiLCBtZW51S2V5KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgIH0KICAgICAgICAvLyBUYWJlbGxlIGVyc3RlbGxlbiAoZmFsbHMgYmVuw7Z0aWd0KQogICAgICAgIHZhciB0YWJsZUhlYWRlciA9IG1lbnVJdGVtc1t0aGlzLm1lbnVJRF0udGFibGVIZWFkZXI7CiAgICAgICAgaWYgKHRhYmxlSGVhZGVyLmxlbmd0aCA+IDApIHsKICAgICAgICAgICAgdmFyIHdyYXBwZXIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJESVYiKTsKICAgICAgICAgICAgZG9jLmFwcGVuZENoaWxkKHdyYXBwZXIpOwogICAgICAgICAgICB3cmFwcGVyLnNldEF0dHJpYnV0ZSgiaWQiLCAiYm94LXdyYXBwZXIiKTsKICAgICAgICAgICAgdmFyIHRhYmxlID0gdGhpcy5jcmVhdGVUQUJMRSgpOwogICAgICAgICAgICB3cmFwcGVyLmFwcGVuZENoaWxkKHRhYmxlKTsKICAgICAgICAgICAgdmFyIGhlYWRlciA9IHRoaXMuY3JlYXRlVGFibGVSb3coKTsKICAgICAgICAgICAgdGFibGUuYXBwZW5kQ2hpbGQoaGVhZGVyKTsKICAgICAgICAgICAgLy8gS29wZnplaWxlIGRlciBUYWJsbGUKICAgICAgICAgICAgdGFibGVIZWFkZXIuZm9yRWFjaChmdW5jdGlvbiAoZWxlbWVudCkgewogICAgICAgICAgICAgICAgdmFyIGNlbGwgPSBuZXcgQ2VsbCgpOwogICAgICAgICAgICAgICAgY2VsbC5jaGlsZCA9IHRydWU7CiAgICAgICAgICAgICAgICBjZWxsLmNoaWxkVHlwZSA9ICJQIjsKICAgICAgICAgICAgICAgIGNlbGwudmFsdWUgPSBlbGVtZW50OwogICAgICAgICAgICAgICAgaWYgKGVsZW1lbnQgPT0gIkJVTEsiKSB7CiAgICAgICAgICAgICAgICAgICAgY2VsbC5jaGlsZFR5cGUgPSAiQlVMS19IRUFEIjsKICAgICAgICAgICAgICAgICAgICBjZWxsLnZhbHVlID0gZmFsc2U7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBpZiAobWVudUtleSA9PSAibWFwcGluZyIpIHsKICAgICAgICAgICAgICAgICAgICBpZiAoZWxlbWVudCA9PSAie3subWFwcGluZy50YWJsZS5jaE5vfX0iKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwub25jbGljayA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwub25jbGlja0Z1bmt0aW9uID0gImphdmFzY3JpcHQ6IHNvcnRUYWJsZSgxKTsiOwogICAgICAgICAgICAgICAgICAgICAgICBjZWxsLnRkQ2xhc3NOYW1lID0gInNvcnRUaGlzIjsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgaWYgKGVsZW1lbnQgPT0gInt7Lm1hcHBpbmcudGFibGUuY2hhbm5lbE5hbWV9fSIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5vbmNsaWNrID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgY2VsbC5vbmNsaWNrRnVua3Rpb24gPSAiamF2YXNjcmlwdDogc29ydFRhYmxlKDMpOyI7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIGlmIChlbGVtZW50ID09ICJ7ey5tYXBwaW5nLnRhYmxlLnBsYXlsaXN0fX0iKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwub25jbGljayA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwub25jbGlja0Z1bmt0aW9uID0gImphdmFzY3JpcHQ6IHNvcnRUYWJsZSg0KTsiOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBpZiAoZWxlbWVudCA9PSAie3subWFwcGluZy50YWJsZS5ncm91cFRpdGxlfX0iKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwub25jbGljayA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwub25jbGlja0Z1bmt0aW9uID0gImphdmFzY3JpcHQ6IHNvcnRUYWJsZSg1KTsiOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGhlYWRlci5hcHBlbmRDaGlsZChjZWxsLmNyZWF0ZUNlbGwoKSk7CiAgICAgICAgICAgIH0pOwogICAgICAgICAgICB0YWJsZS5hcHBlbmRDaGlsZChoZWFkZXIpOwogICAgICAgICAgICAvLyBJbmhhbHQgZGVyIFRhYmVsbGUKICAgICAgICAgICAgdmFyIHJvd3MgPSB0aGlzLmNyZWF0ZVRhYmxlQ29udGVudChtZW51S2V5KTsKICAgICAgICAgICAgcm93cy5mb3JFYWNoKGZ1bmN0aW9uICh0cikgewogICAgICAgICAgICAgICAgdGFibGUuYXBwZW5kQ2hpbGQodHIpOwogICAgICAgICAgICB9KTsKICAgICAgICB9CiAgICAgICAgc3dpdGNoIChtZW51S2V5KSB7CiAgICAgICAgICAgIGNhc2UgIm1hcHBpbmciOgogICAgICAgICAgICAgICAgc29ydFRhYmxlKDEpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImZpbHRlciI6CiAgICAgICAgICAgICAgICBzaG93UHJldmlldyh0cnVlKTsKICAgICAgICAgICAgICAgIHNvcnRUYWJsZSgwKTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBkZWZhdWx0OgogICAgICAgICAgICAgICAgQ09MVU1OX1RPX1NPUlQgPSAtMTsKICAgICAgICAgICAgICAgIHNvcnRUYWJsZSgwKTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgIH0KICAgICAgICBzaG93RWxlbWVudCgibG9hZGluZyIsIGZhbHNlKTsKICAgIH07CiAgICByZXR1cm4gU2hvd0NvbnRlbnQ7Cn0oQ29udGVudCkpOwpmdW5jdGlvbiBQYWdlUmVhZHkoKSB7CiAgICB2YXIgc2VydmVyID0gbmV3IFNlcnZlcigiZ2V0U2VydmVyQ29uZmlnIik7CiAgICBzZXJ2ZXIucmVxdWVzdChuZXcgT2JqZWN0KCkpOwogICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoInJlc2l6ZSIsIGZ1bmN0aW9uICgpIHsKICAgICAgICBjYWxjdWxhdGVXcmFwcGVySGVpZ2h0KCk7CiAgICB9LCB0cnVlKTsKICAgIHNldEludGVydmFsKGZ1bmN0aW9uICgpIHsKICAgICAgICB1cGRhdGVMb2coKTsKICAgIH0sIDEwMDAwKTsKICAgIHJldHVybjsKfQpmdW5jdGlvbiBjcmVhdGVMYXlvdXQoKSB7CiAgICAvLyBDbGllbnQgSW5mbwogICAgdmFyIG9iaiA9IFNFUlZFUlsiY2xpZW50SW5mbyJdOwogICAgdmFyIGtleXMgPSBnZXRPYmpLZXlzKG9iaik7CiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGtleXMubGVuZ3RoOyBpKyspIHsKICAgICAgICBpZiAoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoa2V5c1tpXSkpIHsKICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoa2V5c1tpXSkuaW5uZXJIVE1MID0gb2JqW2tleXNbaV1dOwogICAgICAgIH0KICAgIH0KICAgIGlmICghZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIm1haW4tbWVudSIpKSB7CiAgICAgICAgcmV0dXJuOwogICAgfQogICAgLy8gTWVuw7wgZXJzdGVsbGVuCiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgibWFpbi1tZW51IikuaW5uZXJIVE1MID0gIiI7CiAgICBmb3IgKHZhciBpXzEgPSAwOyBpXzEgPCBtZW51SXRlbXMubGVuZ3RoOyBpXzErKykgewogICAgICAgIG1lbnVJdGVtc1tpXzFdLmlkID0gaV8xOwogICAgICAgIHN3aXRjaCAobWVudUl0ZW1zW2lfMV1bIm1lbnVLZXkiXSkgewogICAgICAgICAgICBjYXNlICJ1c2VycyI6CiAgICAgICAgICAgIGNhc2UgImxvZ291dCI6CiAgICAgICAgICAgICAgICBpZiAoU0VSVkVSWyJzZXR0aW5ncyJdWyJhdXRoZW50aWNhdGlvbi53ZWIiXSA9PSB0cnVlKSB7CiAgICAgICAgICAgICAgICAgICAgbWVudUl0ZW1zW2lfMV0uY3JlYXRlSXRlbSgpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgIm1hcHBpbmciOgogICAgICAgICAgICBjYXNlICJ4bWx0diI6CiAgICAgICAgICAgICAgICBpZiAoU0VSVkVSWyJjbGllbnRJbmZvIl1bImVwZ1NvdXJjZSJdID09ICJYRVBHIikgewogICAgICAgICAgICAgICAgICAgIG1lbnVJdGVtc1tpXzFdLmNyZWF0ZUl0ZW0oKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBkZWZhdWx0OgogICAgICAgICAgICAgICAgbWVudUl0ZW1zW2lfMV0uY3JlYXRlSXRlbSgpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgfQogICAgfQogICAgcmV0dXJuOwp9CmZ1bmN0aW9uIG9wZW5UaGlzTWVudShlbGVtZW50KSB7CiAgICB2YXIgaWQgPSBlbGVtZW50LmlkOwogICAgdmFyIGNvbnRlbnQgPSBuZXcgU2hvd0NvbnRlbnQoaWQpOwogICAgY29udGVudC5zaG93KCk7CiAgICBjYWxjdWxhdGVXcmFwcGVySGVpZ2h0KCk7CiAgICByZXR1cm47Cn0KdmFyIFBvcHVwV2luZG93ID0gLyoqIEBjbGFzcyAqLyAoZnVuY3Rpb24gKCkgewogICAgZnVuY3Rpb24gUG9wdXBXaW5kb3coKSB7CiAgICAgICAgdGhpcy5Eb2N1bWVudElEID0gInBvcHVwLWN1c3RvbSI7CiAgICAgICAgdGhpcy5JbnRlcmFjdGlvbklEID0gImludGVyYWN0aW9uIjsKICAgICAgICB0aGlzLmRvYyA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHRoaXMuRG9jdW1lbnRJRCk7CiAgICB9CiAgICBQb3B1cFdpbmRvdy5wcm90b3R5cGUuY3JlYXRlVGl0bGUgPSBmdW5jdGlvbiAodGl0bGUpIHsKICAgICAgICB2YXIgdGQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJURCIpOwogICAgICAgIHRkLmNsYXNzTmFtZSA9ICJsZWZ0IjsKICAgICAgICB0ZC5pbm5lckhUTUwgPSB0aXRsZSArICI6IjsKICAgICAgICByZXR1cm4gdGQ7CiAgICB9OwogICAgUG9wdXBXaW5kb3cucHJvdG90eXBlLmNyZWF0ZUNvbnRlbnQgPSBmdW5jdGlvbiAoZWxlbWVudCkgewogICAgICAgIHZhciB0ZCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgdGQuYXBwZW5kQ2hpbGQoZWxlbWVudCk7CiAgICAgICAgcmV0dXJuIHRkOwogICAgfTsKICAgIFBvcHVwV2luZG93LnByb3RvdHlwZS5jcmVhdGVJbnRlcmFjdGlvbiA9IGZ1bmN0aW9uICgpIHsKICAgICAgICB2YXIgZGl2ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZGl2Iik7CiAgICAgICAgZGl2LnNldEF0dHJpYnV0ZSgiaWQiLCAicG9wdXAtaW50ZXJhY3Rpb24iKTsKICAgICAgICBkaXYuY2xhc3NOYW1lID0gImludGVyYWN0aW9uIjsKICAgICAgICB0aGlzLmRvYy5hcHBlbmRDaGlsZChkaXYpOwogICAgfTsKICAgIHJldHVybiBQb3B1cFdpbmRvdzsKfSgpKTsKdmFyIFBvcHVwQ29udGVudCA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uIChfc3VwZXIpIHsKICAgIF9fZXh0ZW5kcyhQb3B1cENvbnRlbnQsIF9zdXBlcik7CiAgICBmdW5jdGlvbiBQb3B1cENvbnRlbnQoKSB7CiAgICAgICAgdmFyIF90aGlzID0gX3N1cGVyICE9PSBudWxsICYmIF9zdXBlci5hcHBseSh0aGlzLCBhcmd1bWVudHMpIHx8IHRoaXM7CiAgICAgICAgX3RoaXMudGFibGUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJUQUJMRSIpOwogICAgICAgIHJldHVybiBfdGhpczsKICAgIH0KICAgIFBvcHVwQ29udGVudC5wcm90b3R5cGUuY3JlYXRlSGVhZGxpbmUgPSBmdW5jdGlvbiAoaGVhZGxpbmUpIHsKICAgICAgICB0aGlzLmRvYy5pbm5lckhUTUwgPSAiIjsKICAgICAgICB2YXIgZWxlbWVudCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIkgzIik7CiAgICAgICAgZWxlbWVudC5pbm5lckhUTUwgPSBoZWFkbGluZS50b1VwcGVyQ2FzZSgpOwogICAgICAgIHRoaXMuZG9jLmFwcGVuZENoaWxkKGVsZW1lbnQpOwogICAgICAgIC8vIFRhYmVsbGUgZXJzdGVsbGVuCiAgICAgICAgdGhpcy50YWJsZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlRBQkxFIik7CiAgICAgICAgdGhpcy5kb2MuYXBwZW5kQ2hpbGQodGhpcy50YWJsZSk7CiAgICB9OwogICAgUG9wdXBDb250ZW50LnByb3RvdHlwZS5hcHBlbmRSb3cgPSBmdW5jdGlvbiAodGl0bGUsIGVsZW1lbnQpIHsKICAgICAgICB2YXIgdHIgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJUUiIpOwogICAgICAgIC8vIEJlemVpY2hudW5nCiAgICAgICAgaWYgKHRpdGxlLmxlbmd0aCAhPSAwKSB7CiAgICAgICAgICAgIHRyLmFwcGVuZENoaWxkKHRoaXMuY3JlYXRlVGl0bGUodGl0bGUpKTsKICAgICAgICB9CiAgICAgICAgLy8gQ29udGVudAogICAgICAgIHRyLmFwcGVuZENoaWxkKHRoaXMuY3JlYXRlQ29udGVudChlbGVtZW50KSk7CiAgICAgICAgdGhpcy50YWJsZS5hcHBlbmRDaGlsZCh0cik7CiAgICB9OwogICAgUG9wdXBDb250ZW50LnByb3RvdHlwZS5jcmVhdGVJbnB1dCA9IGZ1bmN0aW9uICh0eXBlLCBuYW1lLCB2YWx1ZSkgewogICAgICAgIHZhciBpbnB1dCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIklOUFVUIik7CiAgICAgICAgaWYgKHZhbHVlID09IHVuZGVmaW5lZCkgewogICAgICAgICAgICB2YWx1ZSA9ICIiOwogICAgICAgIH0KICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoInR5cGUiLCB0eXBlKTsKICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm5hbWUiLCBuYW1lKTsKICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoInZhbHVlIiwgdmFsdWUpOwogICAgICAgIHJldHVybiBpbnB1dDsKICAgIH07CiAgICBQb3B1cENvbnRlbnQucHJvdG90eXBlLmNyZWF0ZUNoZWNrYm94ID0gZnVuY3Rpb24gKG5hbWUpIHsKICAgICAgICB2YXIgaW5wdXQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJJTlBVVCIpOwogICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgidHlwZSIsICJjaGVja2JveCIpOwogICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgibmFtZSIsIG5hbWUpOwogICAgICAgIHJldHVybiBpbnB1dDsKICAgIH07CiAgICBQb3B1cENvbnRlbnQucHJvdG90eXBlLmNyZWF0ZVNlbGVjdCA9IGZ1bmN0aW9uICh0ZXh0LCB2YWx1ZXMsIHNldCwgZGJLZXkpIHsKICAgICAgICB2YXIgc2VsZWN0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiU0VMRUNUIik7CiAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgibmFtZSIsIGRiS2V5KTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRleHQubGVuZ3RoOyBpKyspIHsKICAgICAgICAgICAgdmFyIG9wdGlvbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIk9QVElPTiIpOwogICAgICAgICAgICBvcHRpb24uc2V0QXR0cmlidXRlKCJ2YWx1ZSIsIHZhbHVlc1tpXSk7CiAgICAgICAgICAgIG9wdGlvbi5pbm5lclRleHQgPSB0ZXh0W2ldOwogICAgICAgICAgICBzZWxlY3QuYXBwZW5kQ2hpbGQob3B0aW9uKTsKICAgICAgICB9CiAgICAgICAgaWYgKHNldCAhPSAiIikgewogICAgICAgICAgICBzZWxlY3QudmFsdWUgPSBzZXQ7CiAgICAgICAgfQogICAgICAgIGlmIChzZXQgPT0gdW5kZWZpbmVkKSB7CiAgICAgICAgICAgIHNlbGVjdC52YWx1ZSA9IHZhbHVlc1swXTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIHNlbGVjdDsKICAgIH07CiAgICBQb3B1cENvbnRlbnQucHJvdG90eXBlLnNlbGVjdE9wdGlvbiA9IGZ1bmN0aW9uIChzZWxlY3QsIHZhbHVlKSB7CiAgICAgICAgLy9zZWxlY3Quc2VsZWN0ZWRPcHRpb25zID0gdmFsdWUKICAgICAgICB2YXIgcyA9IHNlbGVjdDsKICAgICAgICBzLm9wdGlvbnNbcy5zZWxlY3RlZEluZGV4XS52YWx1ZSA9IHZhbHVlOwogICAgICAgIHJldHVybiBzZWxlY3Q7CiAgICB9OwogICAgUG9wdXBDb250ZW50LnByb3RvdHlwZS5kZXNjcmlwdGlvbiA9IGZ1bmN0aW9uICh2YWx1ZSkgewogICAgICAgIHZhciB0ciA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlRSIik7CiAgICAgICAgdmFyIHRkID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICB2YXIgc3BhbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlBSRSIpOwogICAgICAgIHNwYW4uaW5uZXJIVE1MID0gdmFsdWU7CiAgICAgICAgdHIuYXBwZW5kQ2hpbGQodGQpOwogICAgICAgIHRyLmFwcGVuZENoaWxkKHRoaXMuY3JlYXRlQ29udGVudChzcGFuKSk7CiAgICAgICAgdGhpcy50YWJsZS5hcHBlbmRDaGlsZCh0cik7CiAgICB9OwogICAgLy8gSW50ZXJha3Rpb24KICAgIFBvcHVwQ29udGVudC5wcm90b3R5cGUuYWRkSW50ZXJhY3Rpb24gPSBmdW5jdGlvbiAoZWxlbWVudCkgewogICAgICAgIHZhciBpbnRlcmFjdGlvbiA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJwb3B1cC1pbnRlcmFjdGlvbiIpOwogICAgICAgIGludGVyYWN0aW9uLmFwcGVuZENoaWxkKGVsZW1lbnQpOwogICAgfTsKICAgIHJldHVybiBQb3B1cENvbnRlbnQ7Cn0oUG9wdXBXaW5kb3cpKTsKZnVuY3Rpb24gb3BlblBvcFVwKGRhdGFUeXBlLCBlbGVtZW50KSB7CiAgICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKICAgIHZhciBpZDsKICAgIHN3aXRjaCAoZWxlbWVudCkgewogICAgICAgIGNhc2UgdW5kZWZpbmVkOgogICAgICAgICAgICBzd2l0Y2ggKGRhdGFUeXBlKSB7CiAgICAgICAgICAgICAgICBjYXNlICJncm91cC10aXRsZSI6CiAgICAgICAgICAgICAgICAgICAgaWYgKGlkID09IHVuZGVmaW5lZCkgewogICAgICAgICAgICAgICAgICAgICAgICBpZCA9IC0xOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBkYXRhID0gZ2V0TG9jYWxEYXRhKCJmaWx0ZXIiLCBpZCk7CiAgICAgICAgICAgICAgICAgICAgZGF0YVsidHlwZSJdID0gImdyb3VwLXRpdGxlIjsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGNhc2UgImN1c3RvbS1maWx0ZXIiOgogICAgICAgICAgICAgICAgICAgIGlmIChpZCA9PSB1bmRlZmluZWQpIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAtMTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGdldExvY2FsRGF0YSgiZmlsdGVyIiwgaWQpOwogICAgICAgICAgICAgICAgICAgIGRhdGFbInR5cGUiXSA9ICJjdXN0b20tZmlsdGVyIjsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGRlZmF1bHQ6CiAgICAgICAgICAgICAgICAgICAgZGF0YVsiaWQucHJvdmlkZXIiXSA9ICItIjsKICAgICAgICAgICAgICAgICAgICBkYXRhWyJ0eXBlIl0gPSBkYXRhVHlwZTsKICAgICAgICAgICAgICAgICAgICBpZCA9ICItIjsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgfQogICAgICAgICAgICBicmVhazsKICAgICAgICBkZWZhdWx0OgogICAgICAgICAgICBpZCA9IGVsZW1lbnQuaWQ7CiAgICAgICAgICAgIGRhdGEgPSBnZXRMb2NhbERhdGEoZGF0YVR5cGUsIGlkKTsKICAgICAgICAgICAgYnJlYWs7CiAgICB9CiAgICB2YXIgY29udGVudCA9IG5ldyBQb3B1cENvbnRlbnQoKTsKICAgIHN3aXRjaCAoZGF0YVR5cGUpIHsKICAgICAgICBjYXNlICJwbGF5bGlzdCI6CiAgICAgICAgICAgIGNvbnRlbnQuY3JlYXRlSGVhZGxpbmUoInt7LnBsYXlsaXN0LnBsYXlsaXN0VHlwZS50aXRsZX19Iik7CiAgICAgICAgICAgIC8vIFR5cGUKICAgICAgICAgICAgdmFyIHRleHQgPSBbIk0zVSIsICJIREhvbWVSdW4iXTsKICAgICAgICAgICAgdmFyIHZhbHVlcyA9IFsiamF2YXNjcmlwdDogb3BlblBvcFVwKCdtM3UnKSIsICJqYXZhc2NyaXB0OiBvcGVuUG9wVXAoJ2hkaHInKSJdOwogICAgICAgICAgICB2YXIgc2VsZWN0ID0gY29udGVudC5jcmVhdGVTZWxlY3QodGV4dCwgdmFsdWVzLCAiIiwgInR5cGUiKTsKICAgICAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgiaWQiLCAidHlwZSIpOwogICAgICAgICAgICBzZWxlY3Quc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICdqYXZhc2NyaXB0OiBjaGFuZ2VCdXR0b25BY3Rpb24odGhpcywgIm5leHQiLCAib25jbGljayIpJyk7IC8vIGNoYW5nZUJ1dHRvbkFjdGlvbgogICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3sucGxheWxpc3QudHlwZS50aXRsZX19Iiwgc2VsZWN0KTsKICAgICAgICAgICAgLy8gSW50ZXJha3Rpb24KICAgICAgICAgICAgY29udGVudC5jcmVhdGVJbnRlcmFjdGlvbigpOwogICAgICAgICAgICAvLyBBYmJyZWNoZW4KICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgImNhbmNlbCIsICJ7ey5idXR0b24uY2FuY2VsfX0iKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IHNob3dFbGVtZW50KCJwb3B1cCIsIGZhbHNlKTsnKTsKICAgICAgICAgICAgY29udGVudC5hZGRJbnRlcmFjdGlvbihpbnB1dCk7CiAgICAgICAgICAgIC8vIFdlaXRlcgogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJidXR0b24iLCAibmV4dCIsICJ7ey5idXR0b24ubmV4dH19Iik7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiBvcGVuUG9wVXAoIm0zdSIpJyk7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgiaWQiLCAnbmV4dCcpOwogICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAibTN1IjoKICAgICAgICAgICAgY29udGVudC5jcmVhdGVIZWFkbGluZShkYXRhVHlwZSk7CiAgICAgICAgICAgIC8vIE5hbWUKICAgICAgICAgICAgdmFyIGRiS2V5ID0gIm5hbWUiOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJ0ZXh0IiwgZGJLZXksIGRhdGFbZGJLZXldKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJwbGFjZWhvbGRlciIsICJ7ey5wbGF5bGlzdC5uYW1lLnBsYWNlaG9sZGVyfX0iKTsKICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7LnBsYXlsaXN0Lm5hbWUudGl0bGV9fSIsIGlucHV0KTsKICAgICAgICAgICAgLy8gQmVzY2hyZWlidW5nCiAgICAgICAgICAgIHZhciBkYktleSA9ICJkZXNjcmlwdGlvbiI7CiAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCBkYktleSwgZGF0YVtkYktleV0pOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoInBsYWNlaG9sZGVyIiwgInt7LnBsYXlsaXN0LmRlc2NyaXB0aW9uLnBsYWNlaG9sZGVyfX0iKTsKICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7LnBsYXlsaXN0LmRlc2NyaXB0aW9uLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIFVSTAogICAgICAgICAgICB2YXIgZGJLZXkgPSAiZmlsZS5zb3VyY2UiOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJ0ZXh0IiwgZGJLZXksIGRhdGFbZGJLZXldKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJwbGFjZWhvbGRlciIsICJ7ey5wbGF5bGlzdC5maWxlTTNVLnBsYWNlaG9sZGVyfX0iKTsKICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7LnBsYXlsaXN0LmZpbGVNM1UudGl0bGV9fSIsIGlucHV0KTsKICAgICAgICAgICAgLy8gVHVuZXIKICAgICAgICAgICAgaWYgKFNFUlZFUlsic2V0dGluZ3MiXVsiYnVmZmVyIl0gPT0gdHJ1ZSkgewogICAgICAgICAgICAgICAgdmFyIHRleHQgPSBuZXcgQXJyYXkoKTsKICAgICAgICAgICAgICAgIHZhciB2YWx1ZXMgPSBuZXcgQXJyYXkoKTsKICAgICAgICAgICAgICAgIGZvciAodmFyIGkgPSAxOyBpIDw9IDEwMDsgaSsrKSB7CiAgICAgICAgICAgICAgICAgICAgdGV4dC5wdXNoKGkudG9TdHJpbmcoKSk7CiAgICAgICAgICAgICAgICAgICAgdmFsdWVzLnB1c2goaS50b1N0cmluZygpKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIHZhciBkYktleSA9ICJ0dW5lciI7CiAgICAgICAgICAgICAgICB2YXIgc2VsZWN0ID0gY29udGVudC5jcmVhdGVTZWxlY3QodGV4dCwgdmFsdWVzLCBkYXRhW2RiS2V5XSwgZGJLZXkpOwogICAgICAgICAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgib25mb2N1cyIsICJqYXZhc2NyaXB0OiByZXR1cm47Iik7CiAgICAgICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3sucGxheWxpc3QudHVuZXIudGl0bGV9fSIsIHNlbGVjdCk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICB2YXIgZGJLZXkgPSAidHVuZXIiOwogICAgICAgICAgICAgICAgaWYgKGRhdGFbZGJLZXldID09IHVuZGVmaW5lZCkgewogICAgICAgICAgICAgICAgICAgIGRhdGFbZGJLZXldID0gMTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCBkYktleSwgZGF0YVtkYktleV0pOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJyZWFkb25seSIsICJ0cnVlIik7CiAgICAgICAgICAgICAgICBpbnB1dC5jbGFzc05hbWUgPSAibm90QXZhaWxhYmxlIjsKICAgICAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5wbGF5bGlzdC50dW5lci50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGNvbnRlbnQuZGVzY3JpcHRpb24oInt7LnBsYXlsaXN0LnR1bmVyLmRlc2NyaXB0aW9ufX0iKTsKICAgICAgICAgICAgLy8gSW50ZXJha3Rpb24KICAgICAgICAgICAgY29udGVudC5jcmVhdGVJbnRlcmFjdGlvbigpOwogICAgICAgICAgICAvLyBMw7ZzY2hlbgogICAgICAgICAgICBpZiAoZGF0YVsiaWQucHJvdmlkZXIiXSAhPSAiLSIpIHsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoImJ1dHRvbiIsICJkZWxldGUiLCAie3suYnV0dG9uLmRlbGV0ZX19Iik7CiAgICAgICAgICAgICAgICBpbnB1dC5jbGFzc05hbWUgPSAiZGVsZXRlIjsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgnb25jbGljaycsICdqYXZhc2NyaXB0OiBzYXZlUG9wdXBEYXRhKCJtM3UiLCAiJyArIGlkICsgJyIsIHRydWUsIDApJyk7CiAgICAgICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgfQogICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoImJ1dHRvbiIsICJiYWNrIiwgInt7LmJ1dHRvbi5iYWNrfX0iKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiBvcGVuUG9wVXAoInBsYXlsaXN0IiknKTsKICAgICAgICAgICAgICAgIGNvbnRlbnQuYWRkSW50ZXJhY3Rpb24oaW5wdXQpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIC8vIEFiYnJlY2hlbgogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJidXR0b24iLCAiY2FuY2VsIiwgInt7LmJ1dHRvbi5jYW5jZWx9fSIpOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAnamF2YXNjcmlwdDogc2hvd0VsZW1lbnQoInBvcHVwIiwgZmFsc2UpOycpOwogICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgLy8gQWt0dWFsaXNpZXJlbgogICAgICAgICAgICBpZiAoZGF0YVsiaWQucHJvdmlkZXIiXSAhPSAiLSIpIHsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoImJ1dHRvbiIsICJ1cGRhdGUiLCAie3suYnV0dG9uLnVwZGF0ZX19Iik7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoJ29uY2xpY2snLCAnamF2YXNjcmlwdDogc2F2ZVBvcHVwRGF0YSgibTN1IiwgIicgKyBpZCArICciLCBmYWxzZSwgMSknKTsKICAgICAgICAgICAgICAgIGNvbnRlbnQuYWRkSW50ZXJhY3Rpb24oaW5wdXQpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIC8vIFNwZWljaGVybgogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJidXR0b24iLCAic2F2ZSIsICJ7ey5idXR0b24uc2F2ZX19Iik7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgnb25jbGljaycsICdqYXZhc2NyaXB0OiBzYXZlUG9wdXBEYXRhKCJtM3UiLCAiJyArIGlkICsgJyIsIGZhbHNlLCAwKScpOwogICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAiaGRociI6CiAgICAgICAgICAgIGNvbnRlbnQuY3JlYXRlSGVhZGxpbmUoZGF0YVR5cGUpOwogICAgICAgICAgICAvLyBOYW1lCiAgICAgICAgICAgIHZhciBkYktleSA9ICJuYW1lIjsKICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgidGV4dCIsIGRiS2V5LCBkYXRhW2RiS2V5XSk7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAie3sucGxheWxpc3QubmFtZS5wbGFjZWhvbGRlcn19Iik7CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5wbGF5bGlzdC5uYW1lLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIEJlc2NocmVpYnVuZwogICAgICAgICAgICB2YXIgZGJLZXkgPSAiZGVzY3JpcHRpb24iOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJ0ZXh0IiwgZGJLZXksIGRhdGFbZGJLZXldKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJwbGFjZWhvbGRlciIsICJ7ey5wbGF5bGlzdC5kZXNjcmlwdGlvbi5wbGFjZWhvbGRlcn19Iik7CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5wbGF5bGlzdC5kZXNjcmlwdGlvbi5wbGFjZWhvbGRlcn19IiwgaW5wdXQpOwogICAgICAgICAgICAvLyBVUkwKICAgICAgICAgICAgdmFyIGRiS2V5ID0gImZpbGUuc291cmNlIjsKICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgidGV4dCIsIGRiS2V5LCBkYXRhW2RiS2V5XSk7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAie3sucGxheWxpc3QuZmlsZUhESFIucGxhY2Vob2xkZXJ9fSIpOwogICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3sucGxheWxpc3QuZmlsZUhESFIudGl0bGV9fSIsIGlucHV0KTsKICAgICAgICAgICAgLy8gVHVuZXIKICAgICAgICAgICAgaWYgKFNFUlZFUlsic2V0dGluZ3MiXVsiYnVmZmVyIl0gPT0gdHJ1ZSkgewogICAgICAgICAgICAgICAgdmFyIHRleHQgPSBuZXcgQXJyYXkoKTsKICAgICAgICAgICAgICAgIHZhciB2YWx1ZXMgPSBuZXcgQXJyYXkoKTsKICAgICAgICAgICAgICAgIGZvciAodmFyIGkgPSAxOyBpIDw9IDEwMDsgaSsrKSB7CiAgICAgICAgICAgICAgICAgICAgdGV4dC5wdXNoKGkudG9TdHJpbmcoKSk7CiAgICAgICAgICAgICAgICAgICAgdmFsdWVzLnB1c2goaS50b1N0cmluZygpKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIHZhciBkYktleSA9ICJ0dW5lciI7CiAgICAgICAgICAgICAgICB2YXIgc2VsZWN0ID0gY29udGVudC5jcmVhdGVTZWxlY3QodGV4dCwgdmFsdWVzLCBkYXRhW2RiS2V5XSwgZGJLZXkpOwogICAgICAgICAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgib25mb2N1cyIsICJqYXZhc2NyaXB0OiByZXR1cm47Iik7CiAgICAgICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3sucGxheWxpc3QudHVuZXIudGl0bGV9fSIsIHNlbGVjdCk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICB2YXIgZGJLZXkgPSAidHVuZXIiOwogICAgICAgICAgICAgICAgaWYgKGRhdGFbZGJLZXldID09IHVuZGVmaW5lZCkgewogICAgICAgICAgICAgICAgICAgIGRhdGFbZGJLZXldID0gMTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCBkYktleSwgZGF0YVtkYktleV0pOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJyZWFkb25seSIsICJ0cnVlIik7CiAgICAgICAgICAgICAgICBpbnB1dC5jbGFzc05hbWUgPSAibm90QXZhaWxhYmxlIjsKICAgICAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5wbGF5bGlzdC50dW5lci50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGNvbnRlbnQuZGVzY3JpcHRpb24oInt7LnBsYXlsaXN0LnR1bmVyLmRlc2NyaXB0aW9ufX0iKTsKICAgICAgICAgICAgLy8gSW50ZXJha3Rpb24KICAgICAgICAgICAgY29udGVudC5jcmVhdGVJbnRlcmFjdGlvbigpOwogICAgICAgICAgICAvLyBMw7ZzY2hlbgogICAgICAgICAgICBpZiAoZGF0YVsiaWQucHJvdmlkZXIiXSAhPSAiLSIpIHsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoImJ1dHRvbiIsICJkZWxldGUiLCAie3suYnV0dG9uLmRlbGV0ZX19Iik7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoJ29uY2xpY2snLCAnamF2YXNjcmlwdDogc2F2ZVBvcHVwRGF0YSgiaGRociIsICInICsgaWQgKyAnIiwgdHJ1ZSwgMCknKTsKICAgICAgICAgICAgICAgIGlucHV0LmNsYXNzTmFtZSA9ICJkZWxldGUiOwogICAgICAgICAgICAgICAgY29udGVudC5hZGRJbnRlcmFjdGlvbihpbnB1dCk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJidXR0b24iLCAiYmFjayIsICJ7ey5idXR0b24uYmFja319Iik7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAnamF2YXNjcmlwdDogb3BlblBvcFVwKCJwbGF5bGlzdCIpJyk7CiAgICAgICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgfQogICAgICAgICAgICAvLyBBYmJyZWNoZW4KICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgImNhbmNlbCIsICJ7ey5idXR0b24uY2FuY2VsfX0iKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IHNob3dFbGVtZW50KCJwb3B1cCIsIGZhbHNlKTsnKTsKICAgICAgICAgICAgY29udGVudC5hZGRJbnRlcmFjdGlvbihpbnB1dCk7CiAgICAgICAgICAgIC8vIEFrdHVhbGlzaWVyZW4KICAgICAgICAgICAgaWYgKGRhdGFbImlkLnByb3ZpZGVyIl0gIT0gIi0iKSB7CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJidXR0b24iLCAidXBkYXRlIiwgInt7LmJ1dHRvbi51cGRhdGV9fSIpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCdvbmNsaWNrJywgJ2phdmFzY3JpcHQ6IHNhdmVQb3B1cERhdGEoImhkaHIiLCAiJyArIGlkICsgJyIsIGZhbHNlLCAxKScpOwogICAgICAgICAgICAgICAgY29udGVudC5hZGRJbnRlcmFjdGlvbihpbnB1dCk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgLy8gU3BlaWNoZXJuCiAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoImJ1dHRvbiIsICJzYXZlIiwgInt7LmJ1dHRvbi5zYXZlfX0iKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCdvbmNsaWNrJywgJ2phdmFzY3JpcHQ6IHNhdmVQb3B1cERhdGEoImhkaHIiLCAiJyArIGlkICsgJyIsIGZhbHNlLCAwKScpOwogICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAiZmlsdGVyIjoKICAgICAgICAgICAgY29udGVudC5jcmVhdGVIZWFkbGluZShkYXRhVHlwZSk7CiAgICAgICAgICAgIC8vIFR5cGUKICAgICAgICAgICAgdmFyIGRiS2V5ID0gInR5cGUiOwogICAgICAgICAgICB2YXIgdGV4dCA9IFsiTTNVOiAiICsgInt7LmZpbHRlci50eXBlLmdyb3VwVGl0bGV9fSIsICJ4VGVWZTogIiArICJ7ey5maWx0ZXIudHlwZS5jdXN0b21GaWx0ZXJ9fSJdOwogICAgICAgICAgICB2YXIgdmFsdWVzID0gWyJqYXZhc2NyaXB0OiBvcGVuUG9wVXAoJ2dyb3VwLXRpdGxlJykiLCAiamF2YXNjcmlwdDogb3BlblBvcFVwKCdjdXN0b20tZmlsdGVyJykiXTsKICAgICAgICAgICAgdmFyIHNlbGVjdCA9IGNvbnRlbnQuY3JlYXRlU2VsZWN0KHRleHQsIHZhbHVlcywgImphdmFzY3JpcHQ6IG9wZW5Qb3BVcCgnZ3JvdXAtdGl0bGUnKSIsIGRiS2V5KTsKICAgICAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgiaWQiLCBpZCk7CiAgICAgICAgICAgIHNlbGVjdC5zZXRBdHRyaWJ1dGUoIm9uY2hhbmdlIiwgJ2phdmFzY3JpcHQ6IGNoYW5nZUJ1dHRvbkFjdGlvbih0aGlzLCAibmV4dCIsICJvbmNsaWNrIik7Jyk7IC8vIGNoYW5nZUJ1dHRvbkFjdGlvbgogICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3suZmlsdGVyLnR5cGUudGl0bGV9fSIsIHNlbGVjdCk7CiAgICAgICAgICAgIC8vIEludGVyYWt0aW9uCiAgICAgICAgICAgIGNvbnRlbnQuY3JlYXRlSW50ZXJhY3Rpb24oKTsKICAgICAgICAgICAgLy8gQWJicmVjaGVuCiAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoImJ1dHRvbiIsICJjYW5jZWwiLCAie3suYnV0dG9uLmNhbmNlbH19Iik7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiBzaG93RWxlbWVudCgicG9wdXAiLCBmYWxzZSk7Jyk7CiAgICAgICAgICAgIGNvbnRlbnQuYWRkSW50ZXJhY3Rpb24oaW5wdXQpOwogICAgICAgICAgICAvLyBXZWl0ZXIKICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgIm5leHQiLCAie3suYnV0dG9uLm5leHR9fSIpOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAnamF2YXNjcmlwdDogb3BlblBvcFVwKCJncm91cC10aXRsZSIpJyk7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgiaWQiLCAnbmV4dCcpOwogICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAiY3VzdG9tLWZpbHRlciI6CiAgICAgICAgY2FzZSAiZ3JvdXAtdGl0bGUiOgogICAgICAgICAgICBzd2l0Y2ggKGRhdGFUeXBlKSB7CiAgICAgICAgICAgICAgICBjYXNlICJjdXN0b20tZmlsdGVyIjoKICAgICAgICAgICAgICAgICAgICBjb250ZW50LmNyZWF0ZUhlYWRsaW5lKCJ7ey5maWx0ZXIuY3VzdG9tfX0iKTsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGNhc2UgImdyb3VwLXRpdGxlIjoKICAgICAgICAgICAgICAgICAgICBjb250ZW50LmNyZWF0ZUhlYWRsaW5lKCJ7ey5maWx0ZXIuZ3JvdXB9fSIpOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICB9CiAgICAgICAgICAgIC8vIE5hbWUgICAgICAKICAgICAgICAgICAgdmFyIGRiS2V5ID0gIm5hbWUiOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJ0ZXh0IiwgZGJLZXksIGRhdGFbZGJLZXldKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJwbGFjZWhvbGRlciIsICJ7ey5maWx0ZXIubmFtZS5wbGFjZWhvbGRlcn19Iik7CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5maWx0ZXIubmFtZS50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICAvLyBCZXNjaHJlaWJ1bmcKICAgICAgICAgICAgdmFyIGRiS2V5ID0gImRlc2NyaXB0aW9uIjsKICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgidGV4dCIsIGRiS2V5LCBkYXRhW2RiS2V5XSk7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAie3suZmlsdGVyLmRlc2NyaXB0aW9uLnBsYWNlaG9sZGVyfX0iKTsKICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7LmZpbHRlci5kZXNjcmlwdGlvbi50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICAvLyBUeXAKICAgICAgICAgICAgdmFyIGRiS2V5ID0gInR5cGUiOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJoaWRkZW4iLCBkYktleSwgZGF0YVtkYktleV0pOwogICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygiIiwgaW5wdXQpOwogICAgICAgICAgICB2YXIgZmlsdGVyVHlwZSA9IGRhdGFbZGJLZXldOwogICAgICAgICAgICBzd2l0Y2ggKGZpbHRlclR5cGUpIHsKICAgICAgICAgICAgICAgIGNhc2UgImN1c3RvbS1maWx0ZXIiOgogICAgICAgICAgICAgICAgICAgIC8vIEdyb8OfLSBLbGVpbnNjaHJlaWJ1bmcgYmVhY2h0ZW4KICAgICAgICAgICAgICAgICAgICB2YXIgZGJLZXkgPSAiY2FzZVNlbnNpdGl2ZSI7CiAgICAgICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVDaGVja2JveChkYktleSk7CiAgICAgICAgICAgICAgICAgICAgaW5wdXQuY2hlY2tlZCA9IGRhdGFbZGJLZXldOwogICAgICAgICAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5maWx0ZXIuY2FzZVNlbnNpdGl2ZS50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICAgICAgICAgIC8vIEZpbHRlcnJlZ2VsIChCZW51dHplcmRlZmluaWVydCkKICAgICAgICAgICAgICAgICAgICB2YXIgZGJLZXkgPSAiZmlsdGVyIjsKICAgICAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJ0ZXh0IiwgZGJLZXksIGRhdGFbZGJLZXldKTsKICAgICAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoInBsYWNlaG9sZGVyIiwgInt7LmZpbHRlci5maWx0ZXJSdWxlLnBsYWNlaG9sZGVyfX0iKTsKICAgICAgICAgICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3suZmlsdGVyLmZpbHRlclJ1bGUudGl0bGV9fSIsIGlucHV0KTsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGNhc2UgImdyb3VwLXRpdGxlIjoKICAgICAgICAgICAgICAgICAgICAvL2FsZXJ0KGRiS2V5ICsgIiAiICsgZmlsdGVyVHlwZSkKICAgICAgICAgICAgICAgICAgICAvLyBGaWx0ZXIgYmFzaWVyZW5kIGF1ZiBkZW4gR3J1cHBlbiBpbiBkZXIgTTNVCiAgICAgICAgICAgICAgICAgICAgdmFyIGRiS2V5ID0gImZpbHRlciI7CiAgICAgICAgICAgICAgICAgICAgdmFyIGdyb3Vwc00zVSA9IGdldExvY2FsRGF0YSgibTN1R3JvdXBzIiwgIiIpOwogICAgICAgICAgICAgICAgICAgIHZhciB0ZXh0ID0gZ3JvdXBzTTNVWyJ0ZXh0Il07CiAgICAgICAgICAgICAgICAgICAgdmFyIHZhbHVlcyA9IGdyb3Vwc00zVVsidmFsdWUiXTsKICAgICAgICAgICAgICAgICAgICB2YXIgc2VsZWN0ID0gY29udGVudC5jcmVhdGVTZWxlY3QodGV4dCwgdmFsdWVzLCBkYXRhW2RiS2V5XSwgZGJLZXkpOwogICAgICAgICAgICAgICAgICAgIHNlbGVjdC5zZXRBdHRyaWJ1dGUoIm9uY2hhbmdlIiwgImphdmFzY3JpcHQ6IHRoaXMuY2xhc3NOYW1lID0gJ2NoYW5nZWQnIik7CiAgICAgICAgICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7LmZpbHRlci5maWx0ZXJHcm91cC50aXRsZX19Iiwgc2VsZWN0KTsKICAgICAgICAgICAgICAgICAgICBjb250ZW50LmRlc2NyaXB0aW9uKCJ7ey5maWx0ZXIuZmlsdGVyR3JvdXAuZGVzY3JpcHRpb259fSIpOwogICAgICAgICAgICAgICAgICAgIC8vIEdyb8OfLSBLbGVpbnNjaHJlaWJ1bmcgYmVhY2h0ZW4KICAgICAgICAgICAgICAgICAgICB2YXIgZGJLZXkgPSAiY2FzZVNlbnNpdGl2ZSI7CiAgICAgICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVDaGVja2JveChkYktleSk7CiAgICAgICAgICAgICAgICAgICAgaW5wdXQuY2hlY2tlZCA9IGRhdGFbZGJLZXldOwogICAgICAgICAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5maWx0ZXIuY2FzZVNlbnNpdGl2ZS50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICAgICAgICAgIHZhciBkYktleSA9ICJpbmNsdWRlIjsKICAgICAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJ0ZXh0IiwgZGJLZXksIGRhdGFbZGJLZXldKTsKICAgICAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoInBsYWNlaG9sZGVyIiwgInt7LmZpbHRlci5pbmNsdWRlLnBsYWNlaG9sZGVyfX0iKTsKICAgICAgICAgICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3suZmlsdGVyLmluY2x1ZGUudGl0bGV9fSIsIGlucHV0KTsKICAgICAgICAgICAgICAgICAgICBjb250ZW50LmRlc2NyaXB0aW9uKCJ7ey5maWx0ZXIuaW5jbHVkZS5kZXNjcmlwdGlvbn19Iik7CiAgICAgICAgICAgICAgICAgICAgdmFyIGRiS2V5ID0gImV4Y2x1ZGUiOwogICAgICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCBkYktleSwgZGF0YVtkYktleV0pOwogICAgICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAie3suZmlsdGVyLmV4Y2x1ZGUucGxhY2Vob2xkZXJ9fSIpOwogICAgICAgICAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5maWx0ZXIuZXhjbHVkZS50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICAgICAgICAgIGNvbnRlbnQuZGVzY3JpcHRpb24oInt7LmZpbHRlci5leGNsdWRlLmRlc2NyaXB0aW9ufX0iKTsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGRlZmF1bHQ6CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgLy8gSW50ZXJha3Rpb24KICAgICAgICAgICAgY29udGVudC5jcmVhdGVJbnRlcmFjdGlvbigpOwogICAgICAgICAgICAvLyBMw7ZzY2hlbgogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJidXR0b24iLCAiZGVsZXRlIiwgInt7LmJ1dHRvbi5kZWxldGV9fSIpOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoJ29uY2xpY2snLCAnamF2YXNjcmlwdDogc2F2ZVBvcHVwRGF0YSgiZmlsdGVyIiwgIicgKyBpZCArICciLCB0cnVlLCAwKScpOwogICAgICAgICAgICBpbnB1dC5jbGFzc05hbWUgPSAiZGVsZXRlIjsKICAgICAgICAgICAgY29udGVudC5hZGRJbnRlcmFjdGlvbihpbnB1dCk7CiAgICAgICAgICAgIC8vIEFiYnJlY2hlbgogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJidXR0b24iLCAiY2FuY2VsIiwgInt7LmJ1dHRvbi5jYW5jZWx9fSIpOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAnamF2YXNjcmlwdDogc2hvd0VsZW1lbnQoInBvcHVwIiwgZmFsc2UpOycpOwogICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgLy8gU3BlaWNoZXJuCiAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoImJ1dHRvbiIsICJzYXZlIiwgInt7LmJ1dHRvbi5zYXZlfX0iKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCdvbmNsaWNrJywgJ2phdmFzY3JpcHQ6IHNhdmVQb3B1cERhdGEoImZpbHRlciIsICInICsgaWQgKyAnIiwgZmFsc2UsIDApJyk7CiAgICAgICAgICAgIGNvbnRlbnQuYWRkSW50ZXJhY3Rpb24oaW5wdXQpOwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICJ4bWx0diI6CiAgICAgICAgICAgIGNvbnRlbnQuY3JlYXRlSGVhZGxpbmUoZGF0YVR5cGUpOwogICAgICAgICAgICAvLyBOYW1lCiAgICAgICAgICAgIHZhciBkYktleSA9ICJuYW1lIjsKICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgidGV4dCIsIGRiS2V5LCBkYXRhW2RiS2V5XSk7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAie3sueG1sdHYubmFtZS5wbGFjZWhvbGRlcn19Iik7CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey54bWx0di5uYW1lLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIEJlc2NocmVpYnVuZwogICAgICAgICAgICB2YXIgZGJLZXkgPSAiZGVzY3JpcHRpb24iOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJ0ZXh0IiwgZGJLZXksIGRhdGFbZGJLZXldKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJwbGFjZWhvbGRlciIsICJ7ey54bWx0di5kZXNjcmlwdGlvbi5wbGFjZWhvbGRlcn19Iik7CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey54bWx0di5kZXNjcmlwdGlvbi50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICAvLyBVUkwKICAgICAgICAgICAgdmFyIGRiS2V5ID0gImZpbGUuc291cmNlIjsKICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgidGV4dCIsIGRiS2V5LCBkYXRhW2RiS2V5XSk7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAie3sueG1sdHYuZmlsZVhNTFRWLnBsYWNlaG9sZGVyfX0iKTsKICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7LnhtbHR2LmZpbGVYTUxUVi50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICAvLyBJbnRlcmFrdGlvbgogICAgICAgICAgICBjb250ZW50LmNyZWF0ZUludGVyYWN0aW9uKCk7CiAgICAgICAgICAgIC8vIEzDtnNjaGVuCiAgICAgICAgICAgIGlmIChkYXRhWyJpZC5wcm92aWRlciJdICE9ICItIikgewogICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgImRlbGV0ZSIsICJ7ey5idXR0b24uZGVsZXRlfX0iKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgnb25jbGljaycsICdqYXZhc2NyaXB0OiBzYXZlUG9wdXBEYXRhKCJ4bWx0diIsICInICsgaWQgKyAnIiwgdHJ1ZSwgMCknKTsKICAgICAgICAgICAgICAgIGlucHV0LmNsYXNzTmFtZSA9ICJkZWxldGUiOwogICAgICAgICAgICAgICAgY29udGVudC5hZGRJbnRlcmFjdGlvbihpbnB1dCk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgLy8gQWJicmVjaGVuCiAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoImJ1dHRvbiIsICJjYW5jZWwiLCAie3suYnV0dG9uLmNhbmNlbH19Iik7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiBzaG93RWxlbWVudCgicG9wdXAiLCBmYWxzZSk7Jyk7CiAgICAgICAgICAgIGNvbnRlbnQuYWRkSW50ZXJhY3Rpb24oaW5wdXQpOwogICAgICAgICAgICAvLyBBa3R1YWxpc2llcmVuCiAgICAgICAgICAgIGlmIChkYXRhWyJpZC5wcm92aWRlciJdICE9ICItIikgewogICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgInVwZGF0ZSIsICJ7ey5idXR0b24udXBkYXRlfX0iKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgnb25jbGljaycsICdqYXZhc2NyaXB0OiBzYXZlUG9wdXBEYXRhKCJ4bWx0diIsICInICsgaWQgKyAnIiwgZmFsc2UsIDEpJyk7CiAgICAgICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgfQogICAgICAgICAgICAvLyBTcGVpY2hlcm4KICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgInNhdmUiLCAie3suYnV0dG9uLnNhdmV9fSIpOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoJ29uY2xpY2snLCAnamF2YXNjcmlwdDogc2F2ZVBvcHVwRGF0YSgieG1sdHYiLCAiJyArIGlkICsgJyIsIGZhbHNlLCAwKScpOwogICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAidXNlcnMiOgogICAgICAgICAgICBjb250ZW50LmNyZWF0ZUhlYWRsaW5lKCJ7ey5tYWluTWVudS5pdGVtLnVzZXJzfX0iKTsKICAgICAgICAgICAgLy8gQmVudXR6ZXJuYW1lIAogICAgICAgICAgICB2YXIgZGJLZXkgPSAidXNlcm5hbWUiOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJ0ZXh0IiwgZGJLZXksIGRhdGFbZGJLZXldKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJwbGFjZWhvbGRlciIsICJ7ey51c2Vycy51c2VybmFtZS5wbGFjZWhvbGRlcn19Iik7CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey51c2Vycy51c2VybmFtZS50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICAvLyBOZXVlcyBQYXNzd29ydCAKICAgICAgICAgICAgdmFyIGRiS2V5ID0gInBhc3N3b3JkIjsKICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgicGFzc3dvcmQiLCBkYktleSwgIiIpOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoInBsYWNlaG9sZGVyIiwgInt7LnVzZXJzLnBhc3N3b3JkLnBsYWNlaG9sZGVyfX0iKTsKICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7LnVzZXJzLnBhc3N3b3JkLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIEJlc3TDpHRpZ3VuZyAKICAgICAgICAgICAgdmFyIGRiS2V5ID0gImNvbmZpcm0iOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJwYXNzd29yZCIsIGRiS2V5LCAiIik7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAie3sudXNlcnMuY29uZmlybS5wbGFjZWhvbGRlcn19Iik7CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey51c2Vycy5jb25maXJtLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIEJlcmVjaHRpZ3VuZyBXRUIKICAgICAgICAgICAgdmFyIGRiS2V5ID0gImF1dGhlbnRpY2F0aW9uLndlYiI7CiAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlQ2hlY2tib3goZGJLZXkpOwogICAgICAgICAgICBpbnB1dC5jaGVja2VkID0gZGF0YVtkYktleV07CiAgICAgICAgICAgIGlmIChkYXRhWyJkZWZhdWx0VXNlciJdID09IHRydWUpIHsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jbGljayIsICJqYXZhc2NyaXB0OiByZXR1cm4gZmFsc2UiKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3sudXNlcnMud2ViLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIEJlcmVjaHRpZ3VuZyBQTVMKICAgICAgICAgICAgdmFyIGRiS2V5ID0gImF1dGhlbnRpY2F0aW9uLnBtcyI7CiAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlQ2hlY2tib3goZGJLZXkpOwogICAgICAgICAgICBpbnB1dC5jaGVja2VkID0gZGF0YVtkYktleV07CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey51c2Vycy5wbXMudGl0bGV9fSIsIGlucHV0KTsKICAgICAgICAgICAgLy8gQmVyZWNodGlndW5nIE0zVQogICAgICAgICAgICB2YXIgZGJLZXkgPSAiYXV0aGVudGljYXRpb24ubTN1IjsKICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVDaGVja2JveChkYktleSk7CiAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhW2RiS2V5XTsKICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7LnVzZXJzLm0zdS50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICAvLyBCZXJlY2h0aWd1bmcgWE1MCiAgICAgICAgICAgIHZhciBkYktleSA9ICJhdXRoZW50aWNhdGlvbi54bWwiOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUNoZWNrYm94KGRiS2V5KTsKICAgICAgICAgICAgaW5wdXQuY2hlY2tlZCA9IGRhdGFbZGJLZXldOwogICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3sudXNlcnMueG1sLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIEJlcmVjaHRpZ3VuZyBBUEkKICAgICAgICAgICAgdmFyIGRiS2V5ID0gImF1dGhlbnRpY2F0aW9uLmFwaSI7CiAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlQ2hlY2tib3goZGJLZXkpOwogICAgICAgICAgICBpbnB1dC5jaGVja2VkID0gZGF0YVtkYktleV07CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey51c2Vycy5hcGkudGl0bGV9fSIsIGlucHV0KTsKICAgICAgICAgICAgLy8gSW50ZXJha3Rpb24KICAgICAgICAgICAgY29udGVudC5jcmVhdGVJbnRlcmFjdGlvbigpOwogICAgICAgICAgICAvLyBMw7ZzY2hlbgogICAgICAgICAgICBpZiAoZGF0YVsiZGVmYXVsdFVzZXIiXSAhPSB0cnVlICYmIGlkICE9ICItIikgewogICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgImRlbGV0ZSIsICJ7ey5idXR0b24uZGVsZXRlfX0iKTsKICAgICAgICAgICAgICAgIGlucHV0LmNsYXNzTmFtZSA9ICJkZWxldGUiOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCdvbmNsaWNrJywgJ2phdmFzY3JpcHQ6IHNhdmVQb3B1cERhdGEoIicgKyBkYXRhVHlwZSArICciLCAiJyArIGlkICsgJyIsIHRydWUsIDApJyk7CiAgICAgICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgfQogICAgICAgICAgICAvLyBBYmJyZWNoZW4KICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgImNhbmNlbCIsICJ7ey5idXR0b24uY2FuY2VsfX0iKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IHNob3dFbGVtZW50KCJwb3B1cCIsIGZhbHNlKTsnKTsKICAgICAgICAgICAgY29udGVudC5hZGRJbnRlcmFjdGlvbihpbnB1dCk7CiAgICAgICAgICAgIC8vIFNwZWljaGVybgogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJidXR0b24iLCAic2F2ZSIsICJ7ey5idXR0b24uc2F2ZX19Iik7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiBzYXZlUG9wdXBEYXRhKCInICsgZGF0YVR5cGUgKyAnIiwgIicgKyBpZCArICciLCAiZmFsc2UiKTsnKTsKICAgICAgICAgICAgY29udGVudC5hZGRJbnRlcmFjdGlvbihpbnB1dCk7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgIm1hcHBpbmciOgogICAgICAgICAgICBjb250ZW50LmNyZWF0ZUhlYWRsaW5lKCJ7ey5tYWluTWVudS5pdGVtLm1hcHBpbmd9fSIpOwogICAgICAgICAgICAvLyBBa3RpdiAKICAgICAgICAgICAgdmFyIGRiS2V5ID0gIngtYWN0aXZlIjsKICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVDaGVja2JveChkYktleSk7CiAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhW2RiS2V5XTsKICAgICAgICAgICAgaW5wdXQuaWQgPSAiYWN0aXZlIjsKICAgICAgICAgICAgLy9pbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2hhbmdlIiwgImphdmFzY3JpcHQ6IHRoaXMuY2xhc3NOYW1lID0gJ2NoYW5nZWQnIikKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0b2dnbGVDaGFubmVsU3RhdHVzKCciICsgaWQgKyAiJywgdGhpcykiKTsKICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7Lm1hcHBpbmcuYWN0aXZlLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIEthbmFsbmFtZSAKICAgICAgICAgICAgdmFyIGRiS2V5ID0gIngtbmFtZSI7CiAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCBkYktleSwgZGF0YVtkYktleV0pOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2hhbmdlIiwgImphdmFzY3JpcHQ6IHRoaXMuY2xhc3NOYW1lID0gJ2NoYW5nZWQnIik7CiAgICAgICAgICAgIGlmIChCVUxLX0VESVQgPT0gdHJ1ZSkgewogICAgICAgICAgICAgICAgaW5wdXQuc3R5bGUuYm9yZGVyID0gInNvbGlkIDFweCByZWQiOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJyZWFkb25seSIsICJ0cnVlIik7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7Lm1hcHBpbmcuY2hhbm5lbE5hbWUudGl0bGV9fSIsIGlucHV0KTsKICAgICAgICAgICAgLy8gQWt0dWFsaXNpZXJ1bmcgZGVzIEthbmFsbmFtZW5zCiAgICAgICAgICAgIGlmIChkYXRhLmhhc093blByb3BlcnR5KCJfdXVpZC5rZXkiKSkgewogICAgICAgICAgICAgICAgaWYgKGRhdGFbIl91dWlkLmtleSJdICE9ICIiKSB7CiAgICAgICAgICAgICAgICAgICAgdmFyIGRiS2V5ID0gIngtdXBkYXRlLWNoYW5uZWwtbmFtZSI7CiAgICAgICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVDaGVja2JveChkYktleSk7CiAgICAgICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhW2RiS2V5XTsKICAgICAgICAgICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3subWFwcGluZy51cGRhdGVDaGFubmVsTmFtZS50aXRsZX19IiwgaW5wdXQpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIC8vIExvZ28gVVJMIChLYW5hbCkgCiAgICAgICAgICAgIHZhciBkYktleSA9ICJ0dmctbG9nbyI7CiAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCBkYktleSwgZGF0YVtkYktleV0pOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2hhbmdlIiwgImphdmFzY3JpcHQ6IHRoaXMuY2xhc3NOYW1lID0gJ2NoYW5nZWQnIik7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgiaWQiLCAiY2hhbm5lbC1pY29uIik7CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5tYXBwaW5nLmNoYW5uZWxMb2dvLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIEFrdHVhbGlzaWVydW5nIGRlcyBLYW5hbGxvZ29zCiAgICAgICAgICAgIHZhciBkYktleSA9ICJ4LXVwZGF0ZS1jaGFubmVsLWljb24iOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUNoZWNrYm94KGRiS2V5KTsKICAgICAgICAgICAgaW5wdXQuY2hlY2tlZCA9IGRhdGFbZGJLZXldOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoImlkIiwgInVwZGF0ZS1pY29uIik7CiAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jaGFuZ2UiLCAiamF2YXNjcmlwdDogdGhpcy5jbGFzc05hbWUgPSAnY2hhbmdlZCc7IGNoYW5nZUNoYW5uZWxMb2dvKCciICsgaWQgKyAiJyk7Iik7CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5tYXBwaW5nLnVwZGF0ZUNoYW5uZWxMb2dvLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIEVyd2VpdGVybiBkZXIgRVBHIEthdGVnb3JpZQogICAgICAgICAgICB2YXIgZGJLZXkgPSAieC1jYXRlZ29yeSI7CiAgICAgICAgICAgIHZhciB0ZXh0ID0gWyItIiwgIktpZHMgKEVtYnkgb25seSkiLCAiTmV3cyIsICJNb3ZpZSIsICJTZXJpZXMiLCAiU3BvcnRzIl07CiAgICAgICAgICAgIHZhciB2YWx1ZXMgPSBbIi0iLCAiS2lkcyIsICJOZXdzIiwgIk1vdmllIiwgIlNlcmllcyIsICJTcG9ydHMiXTsKICAgICAgICAgICAgdmFyIHNlbGVjdCA9IGNvbnRlbnQuY3JlYXRlU2VsZWN0KHRleHQsIHZhbHVlcywgZGF0YVtkYktleV0sIGRiS2V5KTsKICAgICAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgib25jaGFuZ2UiLCAiamF2YXNjcmlwdDogdGhpcy5jbGFzc05hbWUgPSAnY2hhbmdlZCciKTsKICAgICAgICAgICAgY29udGVudC5hcHBlbmRSb3coInt7Lm1hcHBpbmcuZXBnQ2F0ZWdvcnkudGl0bGV9fSIsIHNlbGVjdCk7CiAgICAgICAgICAgIC8vIE0zVSBHcnVwcGVudGl0ZWwKICAgICAgICAgICAgdmFyIGRiS2V5ID0gIngtZ3JvdXAtdGl0bGUiOwogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJ0ZXh0IiwgZGJLZXksIGRhdGFbZGJLZXldKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3subWFwcGluZy5tM3VHcm91cFRpdGxlLnRpdGxlfX0iLCBpbnB1dCk7CiAgICAgICAgICAgIC8vIFhNTFRWIERhdGVpCiAgICAgICAgICAgIHZhciBkYktleSA9ICJ4LXhtbHR2LWZpbGUiOwogICAgICAgICAgICB2YXIgeG1sRmlsZSA9IGRhdGFbZGJLZXldOwogICAgICAgICAgICB2YXIgeG1sdHYgPSBuZXcgWE1MVFZGaWxlKCk7CiAgICAgICAgICAgIHZhciBzZWxlY3QgPSB4bWx0di5nZXRGaWxlcyhkYXRhW2RiS2V5XSk7CiAgICAgICAgICAgIHNlbGVjdC5zZXRBdHRyaWJ1dGUoIm5hbWUiLCBkYktleSk7CiAgICAgICAgICAgIHNlbGVjdC5zZXRBdHRyaWJ1dGUoImlkIiwgInBvcHVwLXhtbHR2Iik7CiAgICAgICAgICAgIHNlbGVjdC5zZXRBdHRyaWJ1dGUoIm9uY2hhbmdlIiwgImphdmFzY3JpcHQ6IHRoaXMuY2xhc3NOYW1lID0gJ2NoYW5nZWQnOyBzZXRYbWx0dkNoYW5uZWwoJyIgKyBpZCArICInLHRoaXMpOyIpOwogICAgICAgICAgICBjb250ZW50LmFwcGVuZFJvdygie3subWFwcGluZy54bWx0dkZpbGUudGl0bGV9fSIsIHNlbGVjdCk7CiAgICAgICAgICAgIHZhciBmaWxlID0gZGF0YVtkYktleV07CiAgICAgICAgICAgIC8vIFhNTFRWIE1hcHBpbmcKICAgICAgICAgICAgdmFyIGRiS2V5ID0gIngtbWFwcGluZyI7CiAgICAgICAgICAgIHZhciB4bWx0diA9IG5ldyBYTUxUVkZpbGUoKTsKICAgICAgICAgICAgdmFyIHNlbGVjdCA9IHhtbHR2LmdldFByb2dyYW1zKGZpbGUsIGRhdGFbZGJLZXldKTsKICAgICAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgibmFtZSIsIGRiS2V5KTsKICAgICAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgiaWQiLCAicG9wdXAtbWFwcGluZyIpOwogICAgICAgICAgICBzZWxlY3Quc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJzsgY2hlY2tYbWx0dkNoYW5uZWwoJyIgKyBpZCArICInLHRoaXMsJyIgKyB4bWxGaWxlICsgIicpOyIpOwogICAgICAgICAgICBzb3J0U2VsZWN0KHNlbGVjdCk7CiAgICAgICAgICAgIGNvbnRlbnQuYXBwZW5kUm93KCJ7ey5tYXBwaW5nLnhtbHR2Q2hhbm5lbC50aXRsZX19Iiwgc2VsZWN0KTsKICAgICAgICAgICAgLy8gSW50ZXJha3Rpb24KICAgICAgICAgICAgY29udGVudC5jcmVhdGVJbnRlcmFjdGlvbigpOwogICAgICAgICAgICAvLyBMb2dvIGhvY2hsYWRlbgogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJidXR0b24iLCAiY2FuY2VsIiwgInt7LmJ1dHRvbi51cGxvYWRMb2dvfX0iKTsKICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IHVwbG9hZExvZ28oKTsnKTsKICAgICAgICAgICAgY29udGVudC5hZGRJbnRlcmFjdGlvbihpbnB1dCk7CiAgICAgICAgICAgIC8vIEFiYnJlY2hlbgogICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUlucHV0KCJidXR0b24iLCAiY2FuY2VsIiwgInt7LmJ1dHRvbi5jYW5jZWx9fSIpOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAnamF2YXNjcmlwdDogc2hvd0VsZW1lbnQoInBvcHVwIiwgZmFsc2UpOycpOwogICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgLy8gRmVydGlnCiAgICAgICAgICAgIHZhciBpZHMgPSBuZXcgQXJyYXkoKTsKICAgICAgICAgICAgaWRzID0gZ2V0QWxsU2VsZWN0ZWRDaGFubmVscygpOwogICAgICAgICAgICBpZiAoaWRzLmxlbmd0aCA9PSAwKSB7CiAgICAgICAgICAgICAgICBpZHMucHVzaChpZCk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgiYnV0dG9uIiwgInNhdmUiLCAie3suYnV0dG9uLmRvbmV9fSIpOwogICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAnamF2YXNjcmlwdDogZG9uZVBvcHVwRGF0YSgiJyArIGRhdGFUeXBlICsgJyIsICInICsgaWRzICsgJyIsICJmYWxzZSIpOycpOwogICAgICAgICAgICBjb250ZW50LmFkZEludGVyYWN0aW9uKGlucHV0KTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgZGVmYXVsdDoKICAgICAgICAgICAgYnJlYWs7CiAgICB9CiAgICBzaG93UG9wVXBFbGVtZW50KCdwb3B1cC1jdXN0b20nKTsKfQp2YXIgWE1MVFZGaWxlID0gLyoqIEBjbGFzcyAqLyAoZnVuY3Rpb24gKCkgewogICAgZnVuY3Rpb24gWE1MVFZGaWxlKCkgewogICAgfQogICAgWE1MVFZGaWxlLnByb3RvdHlwZS5nZXRGaWxlcyA9IGZ1bmN0aW9uIChzZXQpIHsKICAgICAgICB2YXIgZmlsZUlEcyA9IGdldE9iaktleXMoU0VSVkVSWyJ4ZXBnIl1bInhtbHR2TWFwIl0pOwogICAgICAgIHZhciB2YWx1ZXMgPSBuZXcgQXJyYXkoIi0iKTsKICAgICAgICB2YXIgdGV4dCA9IG5ldyBBcnJheSgiLSIpOwogICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgZmlsZUlEcy5sZW5ndGg7IGkrKykgewogICAgICAgICAgICBpZiAoZmlsZUlEc1tpXSAhPSAieFRlVmUgRHVtbXkiKSB7CiAgICAgICAgICAgICAgICB2YWx1ZXMucHVzaChnZXRWYWx1ZUZyb21Qcm92aWRlckZpbGUoZmlsZUlEc1tpXSwgInhtbHR2IiwgImZpbGUueHRldmUiKSk7CiAgICAgICAgICAgICAgICB0ZXh0LnB1c2goZ2V0VmFsdWVGcm9tUHJvdmlkZXJGaWxlKGZpbGVJRHNbaV0sICJ4bWx0diIsICJuYW1lIikpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGVsc2UgewogICAgICAgICAgICAgICAgdmFsdWVzLnB1c2goZmlsZUlEc1tpXSk7CiAgICAgICAgICAgICAgICB0ZXh0LnB1c2goZmlsZUlEc1tpXSk7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgdmFyIHNlbGVjdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlNFTEVDVCIpOwogICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGV4dC5sZW5ndGg7IGkrKykgewogICAgICAgICAgICB2YXIgb3B0aW9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiT1BUSU9OIik7CiAgICAgICAgICAgIG9wdGlvbi5zZXRBdHRyaWJ1dGUoInZhbHVlIiwgdmFsdWVzW2ldKTsKICAgICAgICAgICAgb3B0aW9uLmlubmVyVGV4dCA9IHRleHRbaV07CiAgICAgICAgICAgIHNlbGVjdC5hcHBlbmRDaGlsZChvcHRpb24pOwogICAgICAgIH0KICAgICAgICBpZiAoc2V0ICE9ICIiKSB7CiAgICAgICAgICAgIHNlbGVjdC52YWx1ZSA9IHNldDsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIHNlbGVjdDsKICAgIH07CiAgICBYTUxUVkZpbGUucHJvdG90eXBlLmdldFByb2dyYW1zID0gZnVuY3Rpb24gKGZpbGUsIHNldCkgewogICAgICAgIC8vdmFyIGZpbGVJRHM6c3RyaW5nW10gPSBnZXRPYmpLZXlzKFNFUlZFUlsieGVwZyJdWyJ4bWx0dk1hcCJdKQogICAgICAgIHZhciB2YWx1ZXMgPSBnZXRPYmpLZXlzKFNFUlZFUlsieGVwZyJdWyJ4bWx0dk1hcCJdW2ZpbGVdKTsKICAgICAgICB2YXIgdGV4dCA9IG5ldyBBcnJheSgpOwogICAgICAgIHZhciBkaXNwbGF5TmFtZTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHZhbHVlcy5sZW5ndGg7IGkrKykgewogICAgICAgICAgICBpZiAoU0VSVkVSWyJ4ZXBnIl1bInhtbHR2TWFwIl1bZmlsZV1bdmFsdWVzW2ldXS5oYXNPd25Qcm9wZXJ0eSgnZGlzcGxheS1uYW1lJykgPT0gdHJ1ZSkgewogICAgICAgICAgICAgICAgZGlzcGxheU5hbWUgPSBTRVJWRVJbInhlcGciXVsieG1sdHZNYXAiXVtmaWxlXVt2YWx1ZXNbaV1dWyJkaXNwbGF5LW5hbWUiXTsKICAgICAgICAgICAgfQogICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgIGRpc3BsYXlOYW1lID0gIi0iOwogICAgICAgICAgICB9CiAgICAgICAgICAgIHRleHRbaV0gPSBkaXNwbGF5TmFtZSArICIgKCIgKyB2YWx1ZXNbaV0gKyAiKSI7CiAgICAgICAgfQogICAgICAgIHRleHQudW5zaGlmdCgiLSIpOwogICAgICAgIHZhbHVlcy51bnNoaWZ0KCItIik7CiAgICAgICAgdmFyIHNlbGVjdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlNFTEVDVCIpOwogICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGV4dC5sZW5ndGg7IGkrKykgewogICAgICAgICAgICB2YXIgb3B0aW9uID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiT1BUSU9OIik7CiAgICAgICAgICAgIG9wdGlvbi5zZXRBdHRyaWJ1dGUoInZhbHVlIiwgdmFsdWVzW2ldKTsKICAgICAgICAgICAgb3B0aW9uLmlubmVyVGV4dCA9IHRleHRbaV07CiAgICAgICAgICAgIHNlbGVjdC5hcHBlbmRDaGlsZChvcHRpb24pOwogICAgICAgIH0KICAgICAgICBpZiAoc2V0ICE9ICIiKSB7CiAgICAgICAgICAgIHNlbGVjdC52YWx1ZSA9IHNldDsKICAgICAgICB9CiAgICAgICAgaWYgKHNlbGVjdC52YWx1ZSAhPSBzZXQpIHsKICAgICAgICAgICAgc2VsZWN0LnZhbHVlID0gIi0iOwogICAgICAgIH0KICAgICAgICByZXR1cm4gc2VsZWN0OwogICAgfTsKICAgIHJldHVybiBYTUxUVkZpbGU7Cn0oKSk7CmZ1bmN0aW9uIGdldFZhbHVlRnJvbVByb3ZpZGVyRmlsZShmaWxlLCBmaWxlVHlwZSwga2V5KSB7CiAgICBpZiAoZmlsZSA9PSAieFRlVmUgRHVtbXkiKSB7CiAgICAgICAgcmV0dXJuIGZpbGU7CiAgICB9CiAgICB2YXIgZmlsZUlEOwogICAgdmFyIGluZGljYXRvciA9IGZpbGUuY2hhckF0KDApOwogICAgc3dpdGNoIChpbmRpY2F0b3IpIHsKICAgICAgICBjYXNlICJNIjoKICAgICAgICAgICAgZmlsZVR5cGUgPSAibTN1IjsKICAgICAgICAgICAgZmlsZUlEID0gZmlsZTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAiSCI6CiAgICAgICAgICAgIGZpbGVUeXBlID0gImhkaHIiOwogICAgICAgICAgICBmaWxlSUQgPSBmaWxlOwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICJYIjoKICAgICAgICAgICAgZmlsZVR5cGUgPSAieG1sdHYiOwogICAgICAgICAgICBmaWxlSUQgPSBmaWxlLnN1YnN0cmluZygwLCBmaWxlLmxhc3RJbmRleE9mKCcuJykpOwogICAgICAgICAgICBicmVhazsKICAgIH0KICAgIGlmIChTRVJWRVJbInNldHRpbmdzIl1bImZpbGVzIl1bZmlsZVR5cGVdLmhhc093blByb3BlcnR5KGZpbGVJRCkgPT0gdHJ1ZSkgewogICAgICAgIHZhciBkYXRhID0gU0VSVkVSWyJzZXR0aW5ncyJdWyJmaWxlcyJdW2ZpbGVUeXBlXVtmaWxlSURdOwogICAgICAgIHJldHVybiBkYXRhW2tleV07CiAgICB9CiAgICByZXR1cm47Cn0KZnVuY3Rpb24gc2V0WG1sdHZDaGFubmVsKGlkLCBlbGVtZW50KSB7CiAgICB2YXIgeG1sdHYgPSBuZXcgWE1MVFZGaWxlKCk7CiAgICB2YXIgeG1sRmlsZSA9IGVsZW1lbnQudmFsdWU7CiAgICB2YXIgdHZnSWQgPSBTRVJWRVJbInhlcGciXVsiZXBnTWFwcGluZyJdW2lkXVsidHZnLWlkIl07CiAgICB2YXIgdGQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgicG9wdXAtbWFwcGluZyIpLnBhcmVudEVsZW1lbnQ7CiAgICB0ZC5pbm5lckhUTUwgPSAiIjsKICAgIHZhciBzZWxlY3QgPSB4bWx0di5nZXRQcm9ncmFtcyhlbGVtZW50LnZhbHVlLCB0dmdJZCk7CiAgICBzZWxlY3Quc2V0QXR0cmlidXRlKCJuYW1lIiwgIngtbWFwcGluZyIpOwogICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgiaWQiLCAicG9wdXAtbWFwcGluZyIpOwogICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgib25jaGFuZ2UiLCAiamF2YXNjcmlwdDogdGhpcy5jbGFzc05hbWUgPSAnY2hhbmdlZCc7IGNoZWNrWG1sdHZDaGFubmVsKCciICsgaWQgKyAiJyx0aGlzLCciICsgeG1sRmlsZSArICInKTsiKTsKICAgIHNlbGVjdC5jbGFzc05hbWUgPSAiY2hhbmdlZCI7CiAgICBzb3J0U2VsZWN0KHNlbGVjdCk7CiAgICB0ZC5hcHBlbmRDaGlsZChzZWxlY3QpOwogICAgY2hlY2tYbWx0dkNoYW5uZWwoaWQsIHNlbGVjdCwgeG1sRmlsZSk7Cn0KZnVuY3Rpb24gY2hlY2tYbWx0dkNoYW5uZWwoaWQsIGVsZW1lbnQsIHhtbEZpbGUpIHsKICAgIHZhciB2YWx1ZSA9IGVsZW1lbnQudmFsdWU7CiAgICB2YXIgYm9vbDsKICAgIHZhciBjaGVja2JveCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhY3RpdmUnKTsKICAgIHZhciBjaGFubmVsID0gU0VSVkVSWyJ4ZXBnIl1bImVwZ01hcHBpbmciXVtpZF07CiAgICB2YXIgdXBkYXRlTG9nbzsKICAgIGlmICh2YWx1ZSA9PSAiLSIpIHsKICAgICAgICBib29sID0gZmFsc2U7CiAgICB9CiAgICBlbHNlIHsKICAgICAgICBib29sID0gdHJ1ZTsKICAgIH0KICAgIGNoZWNrYm94LmNoZWNrZWQgPSBib29sOwogICAgY2hlY2tib3guY2xhc3NOYW1lID0gImNoYW5nZWQiOwogICAgY29uc29sZS5sb2coeG1sRmlsZSk7CiAgICAvLyBLYW5hbGxvZ28gYWt0dWFsaXNpZXJlbgogICAgLyoKICAgIHVwZGF0ZUxvZ28gPSAoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInVwZGF0ZS1pY29uIikgYXMgSFRNTElucHV0RWxlbWVudCkuY2hlY2tlZAogICAgY29uc29sZS5sb2codXBkYXRlTG9nbyk7CiAgICAqLwogICAgaWYgKHhtbEZpbGUgIT0gInhUZVZlIER1bW15IiAmJiBib29sID09IHRydWUpIHsKICAgICAgICAvLyhkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgidXBkYXRlLWljb24iKSBhcyBIVE1MSW5wdXRFbGVtZW50KS5jaGVja2VkID0gdHJ1ZTsKICAgICAgICAvLyhkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgidXBkYXRlLWljb24iKSBhcyBIVE1MSW5wdXRFbGVtZW50KS5jbGFzc05hbWUgPSAiY2hhbmdlZCI7CiAgICAgICAgY29uc29sZS5sb2coIklEIiwgaWQpOwogICAgICAgIGNoYW5nZUNoYW5uZWxMb2dvKGlkKTsKICAgICAgICByZXR1cm47CiAgICB9CiAgICBpZiAoeG1sRmlsZSA9PSAieFRlVmUgRHVtbXkiKSB7CiAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInVwZGF0ZS1pY29uIikuY2hlY2tlZCA9IGZhbHNlOwogICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJ1cGRhdGUtaWNvbiIpLmNsYXNzTmFtZSA9ICJjaGFuZ2VkIjsKICAgIH0KICAgIHJldHVybjsKfQpmdW5jdGlvbiBjaGFuZ2VDaGFubmVsTG9nbyhpZCkgewogICAgdmFyIHVwZGF0ZUxvZ287CiAgICB2YXIgY2hhbm5lbCA9IFNFUlZFUlsieGVwZyJdWyJlcGdNYXBwaW5nIl1baWRdOwogICAgdmFyIGYgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgicG9wdXAteG1sdHYiKTsKICAgIHZhciB4bWx0dkZpbGUgPSBmLm9wdGlvbnNbZi5zZWxlY3RlZEluZGV4XS52YWx1ZTsKICAgIHZhciBtID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInBvcHVwLW1hcHBpbmciKTsKICAgIHZhciB4TWFwcGluZyA9IG0ub3B0aW9uc1ttLnNlbGVjdGVkSW5kZXhdLnZhbHVlOwogICAgdmFyIHhtbHR2TG9nbyA9IFNFUlZFUlsieGVwZyJdWyJ4bWx0dk1hcCJdW3htbHR2RmlsZV1beE1hcHBpbmddWyJpY29uIl07CiAgICB1cGRhdGVMb2dvID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInVwZGF0ZS1pY29uIikuY2hlY2tlZDsKICAgIGlmICh1cGRhdGVMb2dvID09IHRydWUgJiYgeG1sdHZGaWxlICE9ICJ4VGVWZSBEdW1teSIpIHsKICAgICAgICBpZiAoU0VSVkVSWyJ4ZXBnIl1bInhtbHR2TWFwIl1beG1sdHZGaWxlXS5oYXNPd25Qcm9wZXJ0eSh4TWFwcGluZykpIHsKICAgICAgICAgICAgdmFyIGxvZ28gPSB4bWx0dkxvZ287CiAgICAgICAgfQogICAgICAgIGVsc2UgewogICAgICAgICAgICBsb2dvID0gY2hhbm5lbFsidHZnLWxvZ28iXTsKICAgICAgICB9CiAgICAgICAgdmFyIGxvZ29JbnB1dCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJjaGFubmVsLWljb24iKTsKICAgICAgICBsb2dvSW5wdXQudmFsdWUgPSBsb2dvOwogICAgICAgIGlmIChCVUxLX0VESVQgPT0gZmFsc2UpIHsKICAgICAgICAgICAgbG9nb0lucHV0LmNsYXNzTmFtZSA9ICJjaGFuZ2VkIjsKICAgICAgICB9CiAgICB9Cn0KZnVuY3Rpb24gc2F2ZVBvcHVwRGF0YShkYXRhVHlwZSwgaWQsIHJlbW92ZSwgb3B0aW9uKSB7CiAgICBpZiAoZGF0YVR5cGUgPT0gIm1hcHBpbmciKSB7CiAgICAgICAgdmFyIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgICAgICAgY29uc29sZS5sb2coIlNhdmUgbWFwcGluZyBkYXRhIik7CiAgICAgICAgY21kID0gInNhdmVFcGdNYXBwaW5nIjsKICAgICAgICBkYXRhWyJlcGdNYXBwaW5nIl0gPSBTRVJWRVJbInhlcGciXVsiZXBnTWFwcGluZyJdOwogICAgICAgIGNvbnNvbGUubG9nKCJTRU5EIFRPIFNFUlZFUiIpOwogICAgICAgIHZhciBzZXJ2ZXIgPSBuZXcgU2VydmVyKGNtZCk7CiAgICAgICAgc2VydmVyLnJlcXVlc3QoZGF0YSk7CiAgICAgICAgZGVsZXRlIFVORE9bImVwZ01hcHBpbmciXTsKICAgICAgICByZXR1cm47CiAgICB9CiAgICBjb25zb2xlLmxvZygiU2F2ZSBwb3B1cCBkYXRhIik7CiAgICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInBvcHVwLWN1c3RvbSIpOwogICAgdmFyIGlucHV0cyA9IGRpdi5nZXRFbGVtZW50c0J5VGFnTmFtZSgiVEFCTEUiKVswXS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiSU5QVVQiKTsKICAgIHZhciBzZWxlY3RzID0gZGl2LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJUQUJMRSIpWzBdLmdldEVsZW1lbnRzQnlUYWdOYW1lKCJTRUxFQ1QiKTsKICAgIHZhciBpbnB1dCA9IG5ldyBPYmplY3QoKTsKICAgIHZhciBjb25maXJtTXNnOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzZWxlY3RzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIG5hbWU7CiAgICAgICAgbmFtZSA9IHNlbGVjdHNbaV0ubmFtZTsKICAgICAgICB2YXIgdmFsdWUgPSBzZWxlY3RzW2ldLnZhbHVlOwogICAgICAgIHN3aXRjaCAobmFtZSkgewogICAgICAgICAgICBjYXNlICJ0dW5lciI6CiAgICAgICAgICAgICAgICBpbnB1dFtuYW1lXSA9IHBhcnNlSW50KHZhbHVlKTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBkZWZhdWx0OgogICAgICAgICAgICAgICAgaW5wdXRbbmFtZV0gPSB2YWx1ZTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgIH0KICAgIH0KICAgIGZvciAodmFyIGkgPSAwOyBpIDwgaW5wdXRzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgc3dpdGNoIChpbnB1dHNbaV0udHlwZSkgewogICAgICAgICAgICBjYXNlICJjaGVja2JveCI6CiAgICAgICAgICAgICAgICBuYW1lID0gaW5wdXRzW2ldLm5hbWU7CiAgICAgICAgICAgICAgICBpbnB1dFtuYW1lXSA9IGlucHV0c1tpXS5jaGVja2VkOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgInRleHQiOgogICAgICAgICAgICBjYXNlICJoaWRkZW4iOgogICAgICAgICAgICBjYXNlICJwYXNzd29yZCI6CiAgICAgICAgICAgICAgICBuYW1lID0gaW5wdXRzW2ldLm5hbWU7CiAgICAgICAgICAgICAgICBzd2l0Y2ggKG5hbWUpIHsKICAgICAgICAgICAgICAgICAgICBjYXNlICJ0dW5lciI6CiAgICAgICAgICAgICAgICAgICAgICAgIGlucHV0W25hbWVdID0gcGFyc2VJbnQoaW5wdXRzW2ldLnZhbHVlKTsKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICAgICAgZGVmYXVsdDoKICAgICAgICAgICAgICAgICAgICAgICAgaW5wdXRbbmFtZV0gPSBpbnB1dHNbaV0udmFsdWU7CiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgfQogICAgfQogICAgdmFyIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgICB2YXIgY21kOwogICAgaWYgKHJlbW92ZSA9PSB0cnVlKSB7CiAgICAgICAgaW5wdXRbImRlbGV0ZSJdID0gdHJ1ZTsKICAgIH0KICAgIHN3aXRjaCAoZGF0YVR5cGUpIHsKICAgICAgICBjYXNlICJ1c2VycyI6CiAgICAgICAgICAgIGNvbmZpcm1Nc2cgPSAiRGVsZXRlIHRoaXMgdXNlcj8iOwogICAgICAgICAgICBpZiAoaWQgPT0gIi0iKSB7CiAgICAgICAgICAgICAgICBjbWQgPSAic2F2ZU5ld1VzZXIiOwogICAgICAgICAgICAgICAgZGF0YVsidXNlckRhdGEiXSA9IGlucHV0OwogICAgICAgICAgICB9CiAgICAgICAgICAgIGVsc2UgewogICAgICAgICAgICAgICAgY21kID0gInNhdmVVc2VyRGF0YSI7CiAgICAgICAgICAgICAgICB2YXIgZCA9IG5ldyBPYmplY3QoKTsKICAgICAgICAgICAgICAgIGRbaWRdID0gaW5wdXQ7CiAgICAgICAgICAgICAgICBkYXRhWyJ1c2VyRGF0YSJdID0gZDsKICAgICAgICAgICAgfQogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICJtM3UiOgogICAgICAgICAgICBjb25maXJtTXNnID0gIkRlbGV0ZSB0aGlzIHBsYXlsaXN0PyI7CiAgICAgICAgICAgIHN3aXRjaCAob3B0aW9uKSB7CiAgICAgICAgICAgICAgICAvLyBQb3B1cDogU2F2ZQogICAgICAgICAgICAgICAgY2FzZSAwOgogICAgICAgICAgICAgICAgICAgIGNtZCA9ICJzYXZlRmlsZXNNM1UiOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgLy8gUG9wdXA6IFVwZGF0ZQogICAgICAgICAgICAgICAgY2FzZSAxOgogICAgICAgICAgICAgICAgICAgIGNtZCA9ICJ1cGRhdGVGaWxlTTNVIjsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgfQogICAgICAgICAgICBkYXRhWyJmaWxlcyJdID0gbmV3IE9iamVjdDsKICAgICAgICAgICAgZGF0YVsiZmlsZXMiXVtkYXRhVHlwZV0gPSBuZXcgT2JqZWN0OwogICAgICAgICAgICBkYXRhWyJmaWxlcyJdW2RhdGFUeXBlXVtpZF0gPSBpbnB1dDsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAiaGRociI6CiAgICAgICAgICAgIGNvbmZpcm1Nc2cgPSAiRGVsZXRlIHRoaXMgSERIb21lUnVuIHR1bmVyPyI7CiAgICAgICAgICAgIHN3aXRjaCAob3B0aW9uKSB7CiAgICAgICAgICAgICAgICAvLyBQb3B1cDogU2F2ZQogICAgICAgICAgICAgICAgY2FzZSAwOgogICAgICAgICAgICAgICAgICAgIGNtZCA9ICJzYXZlRmlsZXNIREhSIjsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIC8vIFBvcHVwOiBVcGRhdGUKICAgICAgICAgICAgICAgIGNhc2UgMToKICAgICAgICAgICAgICAgICAgICBjbWQgPSAidXBkYXRlRmlsZUhESFIiOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGRhdGFbImZpbGVzIl0gPSBuZXcgT2JqZWN0OwogICAgICAgICAgICBkYXRhWyJmaWxlcyJdW2RhdGFUeXBlXSA9IG5ldyBPYmplY3Q7CiAgICAgICAgICAgIGRhdGFbImZpbGVzIl1bZGF0YVR5cGVdW2lkXSA9IGlucHV0OwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICJ4bWx0diI6CiAgICAgICAgICAgIGNvbmZpcm1Nc2cgPSAiRGVsZXRlIHRoaXMgWE1MVFYgZmlsZT8iOwogICAgICAgICAgICBzd2l0Y2ggKG9wdGlvbikgewogICAgICAgICAgICAgICAgLy8gUG9wdXA6IFNhdmUKICAgICAgICAgICAgICAgIGNhc2UgMDoKICAgICAgICAgICAgICAgICAgICBjbWQgPSAic2F2ZUZpbGVzWE1MVFYiOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgLy8gUG9wdXA6IFVwZGF0ZQogICAgICAgICAgICAgICAgY2FzZSAxOgogICAgICAgICAgICAgICAgICAgIGNtZCA9ICJ1cGRhdGVGaWxlWE1MVFYiOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGRhdGFbImZpbGVzIl0gPSBuZXcgT2JqZWN0OwogICAgICAgICAgICBkYXRhWyJmaWxlcyJdW2RhdGFUeXBlXSA9IG5ldyBPYmplY3Q7CiAgICAgICAgICAgIGRhdGFbImZpbGVzIl1bZGF0YVR5cGVdW2lkXSA9IGlucHV0OwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICJmaWx0ZXIiOgogICAgICAgICAgICBjb25maXJtTXNnID0gIkRlbGV0ZSB0aGlzIGZpbHRlcj8iOwogICAgICAgICAgICBjbWQgPSAic2F2ZUZpbHRlciI7CiAgICAgICAgICAgIGRhdGFbImZpbHRlciJdID0gbmV3IE9iamVjdDsKICAgICAgICAgICAgZGF0YVsiZmlsdGVyIl1baWRdID0gaW5wdXQ7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGRlZmF1bHQ6CiAgICAgICAgICAgIGNvbnNvbGUubG9nKGRhdGFUeXBlLCBpZCk7CiAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgYnJlYWs7CiAgICB9CiAgICBpZiAocmVtb3ZlID09IHRydWUpIHsKICAgICAgICBpZiAoIWNvbmZpcm0oY29uZmlybU1zZykpIHsKICAgICAgICAgICAgc2hvd0VsZW1lbnQoInBvcHVwIiwgZmFsc2UpOwogICAgICAgICAgICByZXR1cm47CiAgICAgICAgfQogICAgfQogICAgY29uc29sZS5sb2coIlNFTkQgVE8gU0VSVkVSIik7CiAgICBjb25zb2xlLmxvZyhkYXRhKTsKICAgIHZhciBzZXJ2ZXIgPSBuZXcgU2VydmVyKGNtZCk7CiAgICBzZXJ2ZXIucmVxdWVzdChkYXRhKTsKfQpmdW5jdGlvbiBkb25lUG9wdXBEYXRhKGRhdGFUeXBlLCBpZHNTdHIpIHsKICAgIHZhciBpZHMgPSBpZHNTdHIuc3BsaXQoJywnKTsKICAgIHZhciBkaXYgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgicG9wdXAtY3VzdG9tIik7CiAgICB2YXIgaW5wdXRzID0gZGl2LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoImNoYW5nZWQiKTsKICAgIGlkcy5mb3JFYWNoKGZ1bmN0aW9uIChpZCkgewogICAgICAgIHZhciBpbnB1dCA9IG5ldyBPYmplY3QoKTsKICAgICAgICBpbnB1dCA9IFNFUlZFUlsieGVwZyJdWyJlcGdNYXBwaW5nIl1baWRdOwogICAgICAgIGNvbnNvbGUubG9nKGlucHV0KTsKICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGlucHV0cy5sZW5ndGg7IGkrKykgewogICAgICAgICAgICB2YXIgbmFtZTsKICAgICAgICAgICAgdmFyIHZhbHVlOwogICAgICAgICAgICBzd2l0Y2ggKGlucHV0c1tpXS50YWdOYW1lKSB7CiAgICAgICAgICAgICAgICBjYXNlICJJTlBVVCI6CiAgICAgICAgICAgICAgICAgICAgc3dpdGNoIChpbnB1dHNbaV0udHlwZSkgewogICAgICAgICAgICAgICAgICAgICAgICBjYXNlICJjaGVja2JveCI6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gaW5wdXRzW2ldLm5hbWU7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IGlucHV0c1tpXS5jaGVja2VkOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5wdXRbbmFtZV0gPSB2YWx1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgICAgICAgICBjYXNlICJ0ZXh0IjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSBpbnB1dHNbaV0ubmFtZTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gaW5wdXRzW2ldLnZhbHVlOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5wdXRbbmFtZV0gPSB2YWx1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGNhc2UgIlNFTEVDVCI6CiAgICAgICAgICAgICAgICAgICAgbmFtZSA9IGlucHV0c1tpXS5uYW1lOwogICAgICAgICAgICAgICAgICAgIHZhbHVlID0gaW5wdXRzW2ldLnZhbHVlOwogICAgICAgICAgICAgICAgICAgIGlucHV0W25hbWVdID0gdmFsdWU7CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgc3dpdGNoIChuYW1lKSB7CiAgICAgICAgICAgICAgICBjYXNlICJ0dmctbG9nbyI6CiAgICAgICAgICAgICAgICAgICAgLy8oZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpLmNoaWxkTm9kZXNbMl0uZmlyc3RDaGlsZCBhcyBIVE1MRWxlbWVudCkuc2V0QXR0cmlidXRlKCJzcmMiLCB2YWx1ZSkKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGNhc2UgIngtbmFtZSI6CiAgICAgICAgICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpLmNoaWxkTm9kZXNbM10uZmlyc3RDaGlsZC5pbm5lckhUTUwgPSB2YWx1ZTsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGNhc2UgIngtY2F0ZWdvcnkiOgogICAgICAgICAgICAgICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlkKS5jaGlsZE5vZGVzWzNdLmZpcnN0Q2hpbGQuY2xhc3NOYW1lID0gdmFsdWU7CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICBjYXNlICJ4LWdyb3VwLXRpdGxlIjoKICAgICAgICAgICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuY2hpbGROb2Rlc1s1XS5maXJzdENoaWxkLmlubmVySFRNTCA9IHZhbHVlOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgY2FzZSAieC14bWx0di1maWxlIjoKICAgICAgICAgICAgICAgICAgICBpZiAodmFsdWUgIT0gInhUZVZlIER1bW15IiAmJiB2YWx1ZSAhPSAiLSIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBnZXRWYWx1ZUZyb21Qcm92aWRlckZpbGUodmFsdWUsICJ4bWx0diIsICJuYW1lIik7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIGlmICh2YWx1ZSA9PSAiLSIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgaW5wdXRbIngtYWN0aXZlIl0gPSBmYWxzZTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpLmNoaWxkTm9kZXNbNl0uZmlyc3RDaGlsZC5pbm5lckhUTUwgPSB2YWx1ZTsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGNhc2UgIngtbWFwcGluZyI6CiAgICAgICAgICAgICAgICAgICAgaWYgKHZhbHVlID09ICItIikgewogICAgICAgICAgICAgICAgICAgICAgICBpbnB1dFsieC1hY3RpdmUiXSA9IGZhbHNlOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuY2hpbGROb2Rlc1s3XS5maXJzdENoaWxkLmlubmVySFRNTCA9IHZhbHVlOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgZGVmYXVsdDoKICAgICAgICAgICAgfQogICAgICAgICAgICBjcmVhdGVTZWFyY2hPYmooKTsKICAgICAgICAgICAgc2VhcmNoSW5NYXBwaW5nKCk7CiAgICAgICAgfQogICAgICAgIGlmIChpbnB1dFsieC1hY3RpdmUiXSA9PSBmYWxzZSkgewogICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuY2xhc3NOYW1lID0gIm5vdEFjdGl2ZUVQRyI7CiAgICAgICAgfQogICAgICAgIGVsc2UgewogICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuY2xhc3NOYW1lID0gImFjdGl2ZUVQRyI7CiAgICAgICAgfQogICAgICAgIGNvbnNvbGUubG9nKGlucHV0WyJ0dmctbG9nbyJdKTsKICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuY2hpbGROb2Rlc1syXS5maXJzdENoaWxkLnNldEF0dHJpYnV0ZSgic3JjIiwgaW5wdXRbInR2Zy1sb2dvIl0pOwogICAgfSk7CiAgICBzaG93RWxlbWVudCgicG9wdXAiLCBmYWxzZSk7CiAgICByZXR1cm47Cn0KZnVuY3Rpb24gc2hvd1ByZXZpZXcoZWxlbWVudCkgewogICAgdmFyIGRpdiA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJteVN0cmVhbXNCb3giKTsKICAgIHN3aXRjaCAoZWxlbWVudCkgewogICAgICAgIGNhc2UgZmFsc2U6CiAgICAgICAgICAgIGRpdi5jbGFzc05hbWUgPSAibm90VmlzaWJsZSI7CiAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgYnJlYWs7CiAgICB9CiAgICB2YXIgc3RyZWFtcyA9IFsiYWN0aXZlU3RyZWFtcyIsICJpbmFjdGl2ZVN0cmVhbXMiXTsKICAgIHN0cmVhbXMuZm9yRWFjaChmdW5jdGlvbiAocHJldmlldykgewogICAgICAgIHZhciB0YWJsZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHByZXZpZXcpOwogICAgICAgIHRhYmxlLmlubmVySFRNTCA9ICIiOwogICAgICAgIHZhciBvYmogPSBTRVJWRVJbImRhdGEiXVsiU3RyZWFtUHJldmlld1VJIl1bcHJldmlld107CiAgICAgICAgb2JqLmZvckVhY2goZnVuY3Rpb24gKGNoYW5uZWwpIHsKICAgICAgICAgICAgdmFyIHRyID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVFIiKTsKICAgICAgICAgICAgdmFyIHRkS2V5ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgdmFyIHRkVmFsID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgdGRLZXkuY2xhc3NOYW1lID0gInRkS2V5IjsKICAgICAgICAgICAgdGRWYWwuY2xhc3NOYW1lID0gInRkVmFsIjsKICAgICAgICAgICAgc3dpdGNoIChwcmV2aWV3KSB7CiAgICAgICAgICAgICAgICBjYXNlICJhY3RpdmVTdHJlYW1zIjoKICAgICAgICAgICAgICAgICAgICB0ZEtleS5pbm5lclRleHQgPSAiQ2hhbm5lbDogKCspIjsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGNhc2UgImluYWN0aXZlU3RyZWFtcyI6CiAgICAgICAgICAgICAgICAgICAgdGRLZXkuaW5uZXJUZXh0ID0gIkNoYW5uZWw6ICgtKSI7CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgdGRWYWwuaW5uZXJUZXh0ID0gY2hhbm5lbDsKICAgICAgICAgICAgdHIuYXBwZW5kQ2hpbGQodGRLZXkpOwogICAgICAgICAgICB0ci5hcHBlbmRDaGlsZCh0ZFZhbCk7CiAgICAgICAgICAgIHRhYmxlLmFwcGVuZENoaWxkKHRyKTsKICAgICAgICB9KTsKICAgIH0pOwogICAgc2hvd0VsZW1lbnQoImxvYWRpbmciLCBmYWxzZSk7CiAgICBkaXYuY2xhc3NOYW1lID0gInZpc2libGUiOwogICAgcmV0dXJuOwp9Cg==" + webUI["html/login.html"] = "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xLjAiIC8+IAogICAgPHRpdGxlPnhUZVZlPC90aXRsZT4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iY3NzL3NjcmVlbi5jc3MiIHR5cGU9InRleHQvY3NzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iY3NzL2Jhc2UuY3NzIiB0eXBlPSJ0ZXh0L2NzcyI+CiAgICA8c2NyaXB0IGxhbmd1YWdlPSJqYXZhc2NyaXB0IiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiIHNyYz0ianMvbmV0d29ya190cy5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IGxhbmd1YWdlPSJqYXZhc2NyaXB0IiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiIHNyYz0ianMvYXV0aGVudGljYXRpb25fdHMuanMiPjwvc2NyaXB0PgogIDwvaGVhZD4KCiAgICA8Ym9keT4KICAgICAgICAgIAogICAgICA8ZGl2IGlkPSJoZWFkZXIiIGNsYXNzPSJpbWdDZW50ZXIiPjwvZGl2PgogICAgICA8ZGl2IGlkPSJib3giPgogICAgICAgIDxkaXYgaWQ9ImhlYWRsaW5lIj4KICAgICAgICAgIDxoMSBpZD0iaGVhZC10ZXh0IiBjbGFzcz0iY2VudGVyIj57ey5sb2dpbi5oZWFkbGluZX19PC9oMT4KICAgICAgICA8L2Rpdj4KICAgICAgICA8cCBpZD0iZXJyIiBjbGFzcz0iZXJyb3JNc2cgY2VudGVyIj57ey5hdXRoZW50aWNhdGlvbkVycn19PC9wPiAgIAogICAgICAgIDxkaXYgaWQ9ImNvbnRlbnQiPgogICAgICAgICAgICA8Zm9ybSBpZD0iYXV0aGVudGljYXRpb24iIGFjdGlvbj0iL3dlYi8iIG1ldGhvZD0icG9zdCI+CiAgICAgICAgICAgICAgPGg1Pnt7LmxvZ2luLnVzZXJuYW1lLnRpdGxlfX06PC9oNT4KICAgICAgICAgICAgICA8aW5wdXQgaWQ9InVzZXJuYW1lIiB0eXBlPSJ0ZXh0IiBuYW1lPSJ1c2VybmFtZSIgcGxhY2Vob2xkZXI9IlVzZXJuYW1lIiB2YWx1ZT0iIj4KICAgICAgICAgICAgICA8aDU+e3subG9naW4ucGFzc3dvcmQudGl0bGV9fTo8L2g1PgogICAgICAgICAgICAgIDxpbnB1dCBpZD0icGFzc3dvcmQiIHR5cGU9InBhc3N3b3JkIiBuYW1lPSJwYXNzd29yZCIgcGxhY2Vob2xkZXI9IlBhc3N3b3JkIiB2YWx1ZT0iIj4KICAgICAgICAgICAgPC9mb3JtPgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgaWQ9ImJveC1mb290ZXIiPgogICAgICAgICAgICA8aW5wdXQgaWQ9InN1Ym1pdCIgY2xhc3M9IiIgdHlwZT0iYnV0dG9uIiB2YWx1ZT0ie3suYnV0dG9uLmxvZ2lufX0iIG9uY2xpY2s9ImphdmFzY3JpcHQ6IGxvZ2luKCk7Ij4KICAgICAgICAgIDwvZGl2PgogICAgICAgIAogICAgICA8L2Rpdj4KICAgIDwvYm9keT4KPC9odG1sPg==" + webUI["html/maintenance.html"] = "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xLjAiIC8+IAogICAgPHRpdGxlPnhUZVZlPC90aXRsZT4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iY3NzL3NjcmVlbi5jc3MiIHR5cGU9InRleHQvY3NzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iY3NzL2Jhc2UuY3NzIiB0eXBlPSJ0ZXh0L2NzcyI+CiAgPC9oZWFkPgoKICAgIDxib2R5PgogICAgICAgICAgCiAgICAgIDxkaXYgaWQ9ImhlYWRlciIgY2xhc3M9ImltZ0NlbnRlciI+PC9kaXY+CiAgICAgIDxkaXYgaWQ9ImJveCI+CiAgICAgICAgPGRpdiBpZD0iaGVhZGxpbmUiPgogICAgICAgICAgPGgxIGlkPSJoZWFkLXRleHQiIGNsYXNzPSJjZW50ZXIiPk1haW50ZW5hbmNlPC9oMT4gICAgICAKICAgICAgICA8L2Rpdj4gIAoKICAgICAgICA8ZGl2IGlkPSJjb250ZW50Ij4KICAgICAgICAgIHhUZVZlIGlzIHVwZGF0aW5nIHRoZSBkYXRhYmFzZSwgcGxlYXNlIHRyeSBhZ2FpbiBsYXRlci4KICAgICAgICA8L2Rpdj4KCiAgICAgICAgPGRpdiBpZD0iYm94LWZvb3RlciI+CiAgICAgICAgICAKICAgICAgICA8L2Rpdj4KICAgICAgICAKICAgICAgPC9kaXY+CiAgICA8L2JvZHk+CjwvaHRtbD4=" + webUI["html/.DS_Store"] = "AAAAAUJ1ZDEAACAAAAAIAAAAIAAAABAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAA0AAAABAAAQAHNwYmxvYgAAAMpicAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAgLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAACAAAAABAAAAQAAAAAEAAACAAAAAAQAAAQAAAAABAAACAAAAAAEAAAQAAAAAAAAAAAEAABAAAAAAAQAAIAAAAAABAABAAAAAAAEAAIAAAAAAAQABAAAAAAABAAIAAAAAAAEABAAAAAAAAQAIAAAAAAABABAAAAAAAAEAIAAAAAAAAQBAAAAAAAABAIAAAAAAAAEBAAAAAAAAAQIAAAAAAAABBAAAAAAAAAEIAAAAAAAAARAAAAAAAAABIAAAAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAACALAAAARQAAEAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQREU0RCAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAACAAAABgAAAAAAAAAAEAAACAAAAAAQAAAQAAAAABAAACAAAAAAEAAAQAAAAAAgAACAAAACgAAAAAAQAAMAAAAAAAAAAAAQAAQAAAAAABAACAAAAAAAEAAQAAAAAAAQACAAAAAAABAAQAAAAAAAEACAAAAAAAAQAQAAAAAAABACAAAAAAAAEAQAAAAAAAAQCAAAAAAAABAQAAAAAAAAECAAAAAAAAAQQAAAAAAAABCAAAAAAAAAEQAAAAAAAAASAAAAAAAAABQAAAAAAAAAAQBxEBLAkI1BkaGxwiIw0NEAgQyAgI1BkaGxwLJw0MELUICdQZGhscKycNDRACCAjUGRobHC8wDQwQAxBhCAnUGRobHDQ1DA0QBRBkCQjUGRobHDk6DAwQBBBzCQnUGRobHD4/DA0QBhBLCQjUGRobHENEDAwQABECzQkJIwAAAAAAAAAAI0AoAAAAAAAACSNAMAAAAAAAAAAIAB0AMgBEAFgAYAByAHsAjQCYAKsAtAC2ALcAuADLANQA4wDwAPwBAQEHAQwBFAEZASIBKAEuATgBQAFCAUUBRgFHAVABUgFUAVUBVgFfAWEBYgFjAWwBbgFvAXABeQF7AX0BfgF/AYgBigGMAY0BjgGXAZkBmwGcAZ0BpgGoAaoBqwGsAbUBtwG6AbsBvAHFAc4BzwAAAAAAAAIBAAAAAAAAAEsAAAAAAAAAAAAAAAAAAAHYAAAAAwBjAHMAc3ZTcm5sb25nAAAAAQAAAAMAaQBtAGdid3NwYmxvYgAAAMlicGxpc3QwMNcBAgMEBQYHCAgICwgNCF1TaG93U3RhdHVzQmFyW1Nob3dQYXRoYmFyW1Nob3dUb29sYmFyW1Nob3dUYWJWaWV3XxAUQ29udGFpbmVyU2hvd1NpZGViYXJcV2luZG93Qm91bmRzW1Nob3dTaWRlYmFyCQkJCAlfEBh7ezQ0NywgNTA2fSwgezc3MCwgNDM2fX0JCBclMT1JYG15ent8fX6ZAAAAAAAAAQEAAAAAAAAADwAAAAAAAAAAAAAAAAAAAJoAAAADAGkAbQBnZHNjbGJvb2wAAAAAAwBpAG0AZ2xzdkNibG9iAAACumJwbGlzdDAw2gECAwQFBgcICQoLDA0OSElKS0wMXxASdmlld09wdGlvbnNWZXJzaW9uXxAPc2hvd0ljb25QcmV2aWV3XxARY2FsY3VsYXRlQWxsU2l6ZXNXY29sdW1uc18QD3Njcm9sbFBvc2l0aW9uWVh0ZXh0U2l6ZV8QD3Njcm9sbFBvc2l0aW9uWFpzb3J0Q29sdW0=" + webUI["html/img/x_ transparent.png"] = "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAA6ppVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0xMS0xNVQxNzoxMTo3NjwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6Q29tcHJlc3Npb24+NTwvdGlmZjpDb21wcmVzc2lvbj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MTQ0PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpYUmVzb2x1dGlvbj4xNDQ8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4yNTY8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjI1NjwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgoeXQb1AAAaJElEQVR4Ae2dCZhcVZXHz+1KSFiGkISkqyIoOgI68EkgOBNkEcQRlIFRZDGABAQEhInGbxw+RUZRNhWRLawDYR0GHAaHUVkGvwGURUcgwIiAOGxjV3UHAslHQrbuM/9TlQ6d7qruWt527/u/7+uuqvvucu7vvnveuffde54IDxIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARLwiIDzQtayHi0q23shaydCOnlISu7nnWThRdrFOkPWyCleyNqJkF1yjhTd8k6yiDvtuLgLiCR/lVnIZ14keWU7k2Xyuu4oU92r2RazQ+nWypXI4W86zCXbyZ28KBvJ97MtpEhX1gWsyleS08TJU17I2omQKpvLarmqkywyn7ZHPw9rLvTOvwbX6xEyxS3Nenv4oQCcWwmQc/D3dtaBdiyfyv5S1mM7zieLGSzWEsS6KIuiRSzTGTD9H404z1iy80MBWNVL7hn8nx8LhaxlqnKBvKbvyppYHctTM/0nd5xPljNwcrcUs2/6DyL0RwGYxDOcjR3vGBQ+4M8tMElmdQ3n6NGjYPofGE6F6tTESRmm/1xxTuuczWSQH08BhqJbqlNkuTyJoK2GBgf53S6mkrvB+7qZ6b9Gfod6hHv3dzKAGbX9pNvd51N7+WUBGNlJbgm07FH4G/AJdFuyqlwotXFzW8kzk2itXAFZwu38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" + webUI["html/img/xmltv.png"] = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAsSwAALEsBpT2WqQAABCRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjU8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjI4ODwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+Mjg4PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NTA8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGRjOnN1YmplY3Q+CiAgICAgICAgICAgIDxyZGY6QmFnLz4KICAgICAgICAgPC9kYzpzdWJqZWN0PgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0wNy0yOFQyMDowNzozMzwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Co6j9bsAAAGgSURBVGgF7VqxTsNADL0gYGEon8DOwsbAAH8BYmJn6cYIn8DC3h0+oixISFRiYYCJL0AwMMBAeK5w2rpV6uikxHfYknW98+vds18TK21DWGBlWR7CH+BfcEv2AzLP8HP42gLqkyUATuEp2PWEdQjF9ATs1zF/g29Mrxt+vVcUxR3xWxEktzFPJQmivsv8ZSI9DiQyVnxlIonwn6fpiczXpNuVVXH8O+a3Ys3y9NUyuf/NTTbEHZTjUlGSRzSiPhroUIENwB4AS/vS/susDwDhTpYBER9g7wHh5DWyibV9CiitCZbIafDEYUuJHQI3Nr/9ciWsjK6IFSWYhyvClbAyZqOIlYJG84jq7E1Ot97Zm+TinV1TrWwudk9EI3ebGFekzWprzspGEU2ySWCiOrs/s9dr7M/s9fVJJJrNXcsTsfaJc0WsKZINH+/sf1Jqvl1n1f2ZnStRN/pdq646XcSkIh9dkIg4s+IrE3nCpp8RG7f91ns+cCYR/LD4jcAZB42PN+A7/mcQ8ZxJhBYQvMJwBB/BKTFLVoLMC/wCfgyv7BesTKUC2LKM3wAAAABJRU5ErkJggg==" + webUI["html/js/configuaration.js"] = "dmFyIGNvbmZpZ01lbnUgPSBuZXcgT2JqZWN0KCk7CnZhciB3aXphcmQgPSBuZXcgQXJyYXkoImtleSIsICJ0dW5lciIsICJlcGdTb3VyY2UiLCAibTN1IiwgImNvbXBsZXRlIik7CnZhciBhY3RpdmVXaXphcmQ7CnZhciBkdnJJUAoKdmFyIGNvbmZpZ01lbnVfdHVuZXIgPSBuZXcgT2JqZWN0KCk7CmNvbmZpZ01lbnVfdHVuZXJbIl9lbGVtZW50Il0gICAgICA9ICJTRUxFQ1QiOwpjb25maWdNZW51X3R1bmVyWyJfbWVudVR5cGUiXSAgICAgPSAic2luZ2xlSW5wdXQiOwpjb25maWdNZW51X3R1bmVyWyJfY29uZmlnS2V5Il0gICAgPSAidHVuZXIiOwpjb25maWdNZW51X3R1bmVyWyJfbGFiZWwiXSAgICAgICAgPSAiQXZhaWxhYmxlIHR1bmVycyI7CmNvbmZpZ01lbnVfdHVuZXJbIm5hbWUiXSAgICAgICAgICA9ICJ0dW5lciI7CmNvbmZpZ01lbnVfdHVuZXJbImlkIl0gICAgICAgICAgICA9ICJUdW5lciI7CmNvbmZpZ01lbnVfdHVuZXJbInBsYWNlaG9sZGVyIl0gICA9ICJUdW5lciI7CmNvbmZpZ01lbnVfdHVuZXJbIl91c2FnZSJdICAgICAgICA9ICJUaGlzIHNldHRpbmcgaXMgb25seSB1c2VkIGJ5IFBsZXggYW5kIEVtYnkuPGJyPlRoZSBudW1iZXIgb2YgY29uY3VycmVudCBzdHJlYW1zIGFsbG93ZWQgYnkgdGhlIElQVFYgcHJvdmlkZXIuIgoKCnZhciBvcHRpb25WYWx1ZXMgPSBuZXcgQXJyYXkoKTsKZm9yICh2YXIgaSA9IDE7IGkgPD0gMTAwOyBpKyspIHsKICBvcHRpb25WYWx1ZXMucHVzaChpKQp9CmNvbmZpZ01lbnVfdHVuZXJbIl9vcHRpb25WYWx1ZXMiXSA9IG9wdGlvblZhbHVlczsKCnZhciBjb25maWdNZW51X2VwZyA9IG5ldyBPYmplY3QoKTsKY29uZmlnTWVudV9lcGdbIl9lbGVtZW50Il0gICAgICA9ICJTRUxFQ1QiOwpjb25maWdNZW51X2VwZ1siX21lbnVUeXBlIl0gICAgID0gInNpbmdsZUlucHV0IjsKY29uZmlnTWVudV9lcGdbIl9jb25maWdLZXkiXSAgICA9ICJlcGdTb3VyY2UiOwpjb25maWdNZW51X2VwZ1siX2xhYmVsIl0gICAgICAgID0gIlNlbGVjdGlvbiBvZiB0aGUgRVBHIHNvdXJjZSI7CmNvbmZpZ01lbnVfZXBnWyJuYW1lIl0gICAgICAgICAgPSAiZXBnU291cmNlIjsKY29uZmlnTWVudV9lcGdbImlkIl0gICAgICAgICAgICA9ICJFUEcgc291cmNlIjsKY29uZmlnTWVudV9lcGdbInBsYWNlaG9sZGVyIl0gICA9ICJFUEcgc291cmNlIjsKY29uZmlnTWVudV9lcGdbIl9vcHRpb25WYWx1ZXMiXSA9IG5ldyBBcnJheSgiUE1TIiwgIlhFUEciKTsKY29uZmlnTWVudV9lcGdbIl91c2FnZSJdICAgICAgICA9ICJQTVM6ICAgVXNlIEVQRyBkYXRhIGZyb20gUGxleCBvciBFbWJ5PGJyPlhFUEc6ICBVc2Ugb2YgZXh0ZXJuYWwgRVBHIGRhdGEgKFhNTFRWKTxicj4gICAgICAgU2V2ZXJhbCBYTUxUViBzb3VyY2VzIHBvc3NpYmxlPGJyPiAgICAgICBBbGxvd3MgZWRpdGluZyBhbmQgb3JkZXIgY2hhbm5lbHM8YnI+ICAgICAgIE0zVSAvIFhNTFRWIGV4cG9ydCAoSFRUUCBsaW5rIGZvciBJUFRWIGFwcHMpIgoKdmFyIGNvbmZpZ01lbnVfbTN1ID0gbmV3IE9iamVjdCgpOwpjb25maWdNZW51X20zdVsiX2VsZW1lbnQiXSAgICAgICAgPSAiSU5QVVQiOwpjb25maWdNZW51X20zdVsiX21lbnVUeXBlIl0gICAgICAgPSAiaW5wdXRBcnJheSI7CmNvbmZpZ01lbnVfbTN1WyJfY29uZmlnS2V5Il0gICAgICA9ICJmaWxlIjsKY29uZmlnTWVudV9tM3VbIl9sYWJlbCJdICAgICAgICAgID0gIk0zVSBGaWxlOiBsb2NhbCBvciByZW1vdGUiOwpjb25maWdNZW51X20zdVsibmFtZSJdICAgICAgICAgICAgPSAiZmlsZSI7CmNvbmZpZ01lbnVfbTN1WyJpZCJdICAgICAgICAgICAgICA9ICJtM3UiOwpjb25maWdNZW51X20zdVsidHlwZSJdICAgICAgICAgICAgPSAidGV4dCI7CmNvbmZpZ01lbnVfbTN1WyJwbGFjZWhvbGRlciJdICAgICA9ICJNM1UgRmlsZSI7CmNvbmZpZ01lbnVfbTN1WyJfdXNhZ2UiXSAgICAgICAgICA9ICJSZW1vdGUgcGxheWxpc3Q6IGh0dHA6Ly95b3VyLnByb3ZpZGVyLmNvbS9maWxlLm0zdTxicj5Mb2NhbCAgcGxheWxpc3Q6IC9wYXRoL3RvL2ZpbGUubTN1IgoKCmNvbmZpZ01lbnVfbTN1WyJ2YWx1ZSJdICAgICAgICAgICA9ICJodHRwOi8vd2Vic3J2LmxvY2FsOjgwODAva2FiZWwubTN1IjsKCnZhciBjb25maWdNZW51X2NvbXBsZXRlID0gbmV3IE9iamVjdCgpOwpjb25maWdNZW51X2NvbXBsZXRlWyJfZWxlbWVudCJdICAgICAgICA9ICJIMiI7CmNvbmZpZ01lbnVfY29tcGxldGVbIl9tZW51VHlwZSJdICAgICAgID0gImlucHV0QXJyYXkiOwpjb25maWdNZW51X2NvbXBsZXRlWyJfY29uZmlnS2V5Il0gICAgICA9ICJmaWxlIjsKY29uZmlnTWVudV9jb21wbGV0ZVsiX3RleHQiXSAgICAgICAgICAgPSAieFRlVmUgd2FzIHN1Y2Nlc3NmdWxseSBzZXQgdXAiOwpjb25maWdNZW51X2NvbXBsZXRlWyJuYW1lIl0gICAgICAgICAgICA9ICJjb21wbGV0ZSI7CmNvbmZpZ01lbnVfY29tcGxldGVbImlkIl0gICAgICAgICAgICAgID0gImNvbXBsZXRlIjsKY29uZmlnTWVudV9jb21wbGV0ZVsidHlwZSJdICAgICAgICAgICAgPSAidGV4dCI7CmNvbmZpZ01lbnVfY29tcGxldGVbImNsYXNzIl0gICAgICAgICAgID0gImNlbnRlciI7Cgpjb25maWdNZW51WyJ0dW5lciJdICAgICA9IGNvbmZpZ01lbnVfdHVuZXI7CmNvbmZpZ01lbnVbImVwZ1NvdXJjZSJdID0gY29uZmlnTWVudV9lcGc7CmNvbmZpZ01lbnVbIm0zdSJdICAgICAgID0gY29uZmlnTWVudV9tM3U7CmNvbmZpZ01lbnVbImNvbXBsZXRlIl0gID0gY29uZmlnTWVudV9jb21wbGV0ZTsKCmZ1bmN0aW9uIHJlYWR5Rm9yQ29uZmlndXJhdGlvbigpIHsKICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKICBkYXRhWyJjbWQiXSA9ICJnZXRTZXJ2ZXJDb25maWciOwogIHhUZVZlKGRhdGEpOwogIHNob3dMb2FkaW5nU2NyZWVuKGZhbHNlKTsKfQoKZnVuY3Rpb24gY3JlYXRlQ29uZmlndXJhdGlvbihlbG0pIHsKCiAgYWN0aXZlV2l6YXJkID0gZWxtOwogIHZhciBpdGVtICA9IGNvbmZpZ01lbnVbZWxtXTsKCiAgdmFyIGRpdiAgID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImNvbnRlbnQiKTsKICBkaXYuaW5uZXJIVE1MID0gIiI7CiAgZGl2LnNldEF0dHJpYnV0ZSgiZGF0YS1jb25maWdLZXkiLCBpdGVtWyJfY29uZmlnS2V5Il0pOwogIGRpdi5zZXRBdHRyaWJ1dGUoImRhdGEtbWVudVR5cGUiLCBpdGVtWyJfbWVudVR5cGUiXSk7CiAgCiAgc3dpdGNoKGl0ZW0uaGFzT3duUHJvcGVydHkoIl9sYWJlbCIpKSB7CiAgICBjYXNlIHRydWU6CiAgICAgIHZhciBuZXdJdGVtID0gbmV3IE9iamVjdCgpOwogICAgICBuZXdJdGVtWyJfZWxlbWVudCJdID0gIkxBQkVMIjsKICAgICAgbmV3SXRlbVsiX3RleHQiXSAgICA9IGl0ZW1bIl9sYWJlbCJdOyAKICAgICAgbmV3SXRlbVsiZm9yIl0gICAgICA9IGl0ZW1bImlkIl07CiAgICAgIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0l0ZW0pKTsKICAgICAgYnJlYWsKICB9CgogIHN3aXRjaChpdGVtWyJfZWxlbWVudCJdKSB7CiAgICBjYXNlICJTRUxFQ1QiOgogICAgICBkaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChpdGVtKSk7CiAgICAgIHZhciBzZWxlY3RFbGVtZW50ID0gZGl2LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJTRUxFQ1QiKVswXTsKICAgICAgdmFyIHZhbHVlcyA9IGl0ZW1bIl9vcHRpb25WYWx1ZXMiXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB2YWx1ZXMubGVuZ3RoOyBpKyspIHsKICAgICAgICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0OwogICAgICAgIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJPUFRJT04iOwogICAgICAgIG5ld0VudHJ5WyJfdGV4dCJdICAgICA9IGl0ZW1bImlkIl0gKyAiOiAiICsgdmFsdWVzW2ldOwogICAgICAgIG5ld0VudHJ5WyJ2YWx1ZSJdICAgICA9IHZhbHVlc1tpXTsKICAgICAgICBzZWxlY3RFbGVtZW50LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKICAgICAgfQogICAgICAvL3JldHVybgogICAgICBicmVhazsKCiAgICBkZWZhdWx0OiAKICAgICAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQoaXRlbSkpOwogICAgICBicmVhazsKCgogIH0KICAvL2FsZXJ0KCkKCiAgc3dpdGNoKGl0ZW0uaGFzT3duUHJvcGVydHkoIl91c2FnZSIpKSB7CiAgICBjYXNlIHRydWU6IAogICAgICB2YXIgdXNhZ2VJdGVtID0gbmV3IE9iamVjdCgpOwogICAgICB1c2FnZUl0ZW1bIl9lbGVtZW50Il0gPSAiUFJFIgogICAgICB1c2FnZUl0ZW1bIl90ZXh0Il0gICAgPSBpdGVtWyJfdXNhZ2UiXTsKICAgICAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQodXNhZ2VJdGVtKSk7CiAgfQoKICBpZiAoYWN0aXZlV2l6YXJkID09ICJjb21wbGV0ZSIpIHsKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJuZXh0IikudmFsdWUgPSAiRmluaXNoZWQiCiAgICAvL2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJuZXh0Iikuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgImphdmFzY3JpcHQ6IGxvY2F0aW9uLnJlbG9hZCgpOyIpCiAgfQoKICAvL2Rpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KGl0ZW0pKTsKfQoKZnVuY3Rpb24gc2F2ZURhdGEoKSB7CgogIHZhciBkaXYgICA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJjb250ZW50Iik7CiAgdmFyIGlucHV0cyA9IGRpdi5nZXRFbGVtZW50c0J5VGFnTmFtZSgiSU5QVVQiKTsKICB2YXIgc2VsZWN0cyA9IGRpdi5nZXRFbGVtZW50c0J5VGFnTmFtZSgiU0VMRUNUIik7CiAgdmFyIHZhbHVlOwogIHZhciBkYXRhID0gbmV3IE9iamVjdCgpOwogIHZhciB2YWx1ZUFyciA9IG5ldyBBcnJheSgpOwogIHZhciBuZXdEYXRhID0gZmFsc2U7CgogIGlmIChhY3RpdmVXaXphcmQgPT0gImNvbXBsZXRlIikgewogICAgZGF0YVsiY21kIl0gPSAid2l6YXJkQ29tcGxldGVkIjsKICAgIHNob3dMb2FkaW5nU2NyZWVuKHRydWUpCiAgICB4VGVWZShkYXRhKTsKICAgIHJldHVybgogIH0KICAKICBmb3IgKHZhciBpID0gMDsgaSA8IGlucHV0cy5sZW5ndGg7IGkrKykgewogICAgdmFyIG1lbnVUeXBlID0gaW5wdXRzW2ldLnBhcmVudEVsZW1lbnQuZ2V0QXR0cmlidXRlKCJkYXRhLW1lbnV0eXBlIik7CiAgICBpZiAoaW5wdXRzW2ldLnZhbHVlICE9IHVuZGVmaW5lZCAmJiBpbnB1dHNbaV0udmFsdWUgIT0gIiIgKSB7CiAgICAgIG5ld0RhdGEgPSB0cnVlOwoKICAgICAgY29uc29sZS5sb2coaW5wdXRzW2ldLmlkKQogICAgICBzd2l0Y2goaW5wdXRzW2ldLmlkKSB7CiAgICAgICAgY2FzZSAibTN1IjogCiAgICAgICAgICB2YXIgbmV3UGxheWxpc3QgPSBuZXcgT2JqZWN0KCk7CiAgICAgICAgICBuZXdQbGF5bGlzdFsiZmlsZS5zb3VyY2UiXSA9IGlucHV0c1tpXS52YWx1ZTsKICAgICAgICAgIC8vbmV3UGxheWxpc3RbIm5hbWUiXSA9IGlucHV0c1tpXS52YWx1ZTsKICAgICAgICAgIG5ld1BsYXlsaXN0WyJ0eXBlIl0gPSAibTN1IjsKICAgICAgICAgIG5ld1BsYXlsaXN0WyJuZXciXSA9IHRydWU7CgogICAgICAgICAgZGF0YVsiZmlsZXMiXSA9IG5ldyBPYmplY3QoKTsKICAgICAgICAgIGRhdGFbImZpbGVzIl1bIm0zdSJdID0gbmV3IE9iamVjdCgpOwogICAgICAgICAgZGF0YVsiZmlsZXMiXVsibTN1Il1bIi0iXSA9IG5ld1BsYXlsaXN0OwogICAgICAgICAgCiAgICAgICAgICBkYXRhWyJjbWQiXSA9ICJzYXZlRmlsZXNNM1UiOwogICAgICAgICAgeFRlVmUoZGF0YSkKICAgICAgICAgIHJldHVybgogICAgICB9CiAgICAgIC8qCiAgICAgIHN3aXRjaChtZW51VHlwZSkgewogICAgICAgIGNhc2UgInNpbmdsZUlucHV0IjoKICAgICAgICAgIGRhdGFbaW5wdXRzW2ldLm5hbWVdID0gaW5wdXRzW2ldLnZhbHVlOyBicmVhazsKICAgICAgICBjYXNlICJpbnB1dEFycmF5IjogCiAgICAgICAgICB2YWx1ZUFyci5wdXNoKGlucHV0c1tpXS52YWx1ZSk7CiAgICAgICAgICBkYXRhW2lucHV0c1tpXS5uYW1lXSA9IHZhbHVlQXJyOyBicmVhawoKICAgICAgfQogICAgICAqLwogICAgfSBlbHNlIHsKICAgICAgaW5wdXRzW2ldLnN0eWxlLmJvcmRlckJvdHRvbUNvbG9yID0gInJlZCI7CiAgICAgIHJldHVybjsKICAgIH0KICB9CgoKICBmb3IgKHZhciBpID0gMDsgaSA8IHNlbGVjdHMubGVuZ3RoOyBpKyspIHsKICAgIHZhciB2YWx1ZSA9IHNlbGVjdHNbaV0ub3B0aW9uc1tzZWxlY3RzW2ldLnNlbGVjdGVkSW5kZXhdLnZhbHVlOwogICAgaWYgKGlzTmFOKHZhbHVlKSA9PSBmYWxzZSkgewogICAgICB2YWx1ZSA9IHBhcnNlSW50KHZhbHVlKTsKICAgICAgZGF0YVtzZWxlY3RzW2ldLm5hbWVdID0gdmFsdWU7CiAgICAgIG5ld0RhdGEgPSB0cnVlOwogICAgICBicmVhazsKICAgIH0KICAgIGRhdGFbc2VsZWN0c1tpXS5uYW1lXSA9IHZhbHVlOwogICAgbmV3RGF0YSA9IHRydWU7CiAgfQoKCiAgLy9jb25zb2xlLmxvZyhkYXRhLCBuZXdEYXRhKTsKICBpZiAobmV3RGF0YSA9PSB0cnVlKSB7CiAgICBjb25maWcgPSBkYXRhCiAgICBkYXRhWyJjbWQiXSA9ICJzYXZlQ29uZmlnIjsKICAgIHhUZVZlKGRhdGEpOwogIH0KfQoKZnVuY3Rpb24geFRlVmUoZGF0YSkgewoKICBpZiAod2ViU29ja2V0cyA9PSBmYWxzZSkgewogICAgYWxlcnQoIllvdXIgYnJvd3NlciBkb2VzIG5vdCBzdXBwb3J0IFdlYlNvY2tldHMiKTsKICAgIHJldHVybjsKICB9CgogIGlmIChhY3RpdmVXaXphcmQgPT0gIm0zdSIgfHwgYWN0aXZlV2l6YXJkID09ICJlcGdTb3VyY2UiKSB7CiAgICBzaG93TG9hZGluZ1NjcmVlbih0cnVlKTsKICB9CgogIHZhciBwcm90b2NvbFdTCiAgc3dpdGNoKHdpbmRvdy5sb2NhdGlvbi5wcm90b2NvbCkgewogICAgY2FzZSAiaHR0cDoiOiAgIHByb3RvY29sV1MgPSAid3M6Ly8iOyBicmVhazsKICAgIGNhc2UgImh0dHBzOiI6ICBwcm90b2NvbFdTID0gIndzczovLyI7IGJyZWFrOwogIH0KCiAgdmFyIHdzID0gbmV3IFdlYlNvY2tldChwcm90b2NvbFdTICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgIjoiICsgd2luZG93LmxvY2F0aW9uLnBvcnQgKyAiL2RhdGEvIiArICI/VG9rZW49IiArIGdldENvb2tpZSgiVG9rZW4iKSk7CiAgCiAgd3Mub25vcGVuID0gZnVuY3Rpb24oKSB7CiAgICB3cy5zZW5kKEpTT04uc3RyaW5naWZ5KGRhdGEpKTsKICB9CgogIHdzLm9ubWVzc2FnZSA9IGZ1bmN0aW9uIChlKSB7CiAgICAKICAgIHZhciByZXNwb25zZSA9IEpTT04ucGFyc2UoZS5kYXRhKTsKICAgIAogICAgaWYgKHJlc3BvbnNlLmhhc093blByb3BlcnR5KCJjbGllbnRJbmZvIikpIHsKICAgICAgY3JlYXRlQ2xpbnRJbmZvKHJlc3BvbnNlWyJjbGllbnRJbmZvIl0pOwogICAgfQoKICAgIGlmIChyZXNwb25zZS5oYXNPd25Qcm9wZXJ0eSgic3RhdHVzIikpIHsKICAgICAgaWYgKHJlc3BvbnNlWyJzdGF0dXMiXSA9PSBmYWxzZSkgewogICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJoZWFkbGluZSIpLnN0eWxlLmJvcmRlckNvbG9yID0gInJlZCI7CiAgICAgICAgc2hvd0VycihyZXNwb25zZVsiZXJyIl0pOwogICAgICAgIHNob3dMb2FkaW5nU2NyZWVuKGZhbHNlKQogICAgICAgIHJldHVybgogICAgICB9IGVsc2UgewogICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJlcnIiKS5pbm5lckhUTUwgPSAiIjsKICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiaGVhZGxpbmUiKS5zdHlsZS5ib3JkZXJDb2xvciA9ICJsYXduZ3JlZW4iOwogICAgICB9CgogICAgICBkdnJJUCA9IHJlc3BvbnNlWyJEVlIiXQogICAgICBzd2l0Y2gocmVzcG9uc2VbImNvbmZpZ3VyYXRpb25XaXphcmQiXSkgewogICAgICAgIGNhc2UgdHJ1ZTogCiAgICAgICAgICBpZiAoYWN0aXZlV2l6YXJkID09IHVuZGVmaW5lZCkgewogICAgICAgICAgICBhY3RpdmVXaXphcmQgPSB3aXphcmRbMF0KICAgICAgICAgIH0KICAgICAgICAgIHZhciBuID0gd2l6YXJkLmluZGV4T2YoYWN0aXZlV2l6YXJkKTsKICAgICAgICAgIG4rKzsKICAgICAgICAgIGFjdGl2ZVdpemFyZCA9IHdpemFyZFtuXQoKICAgICAgICAgIGlmIChhY3RpdmVXaXphcmQgPT0gdW5kZWZpbmVkKSB7CiAgICAgICAgICAgIGRhdGFbImNtZCJdID0gIndpemFyZENvbXBsZXRlZCI7CiAgICAgICAgICAgIHhUZVZlKGRhdGEpCiAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAvL2NvbnNvbGUubG9nKGFjdGl2ZVdpemFyZCk7CiAgICAgICAgICAgIGNyZWF0ZUNvbmZpZ3VyYXRpb24oYWN0aXZlV2l6YXJkKTsgCiAgICAgICAgICB9CiAgICAgICAgICAKICAgICAgICBicmVhazsKICAgICAgfQoKICAgICAgc3dpdGNoKHJlc3BvbnNlWyJyZWxvYWQiXSkgewogICAgICAgIAogICAgICAgIAogICAgICAgIGNhc2UgdHJ1ZTogCiAgICAKICAgICAgICAgIHNldFRpbWVvdXQoZnVuY3Rpb24oKXsgCiAgICAgICAgICAgIGxvY2F0aW9uLnJlbG9hZCgpOwogICAgICAgICAgfSwgMTAwKTsKICAgICAgICAgIAogICAgICAgICAgLy9sb2NhdGlvbi5yZWxvYWQoKTsKICAgICAgICAgIAogICAgICAgICAgYnJlYWs7CiAgICAgICAgCiAgICAgIH0KCiAgICAgIAogICAgfQoKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24oKXsgc2hvd0xvYWRpbmdTY3JlZW4oZmFsc2UpOyB9LCAzMDApOwogIH0KICAKfQoKZnVuY3Rpb24gc2hvd0VycihlbG0pIHsKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZXJyIikuaW5uZXJIVE1MID0gZWxtOwp9" + webUI["html/js/files.js"] = "ZnVuY3Rpb24gb3BlbkZpbGVzKGVsbSwgZmlsZVR5cGUpIHsKICAvL2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJzZXR0aW5ncyIpLmlubmVySFRNTCA9ICJUZXN0IjsKICAKICBjb2x1bW5Ub1NvcnQgPSAwOyAKICB2YXIgbmV3RGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNldHRpbmdzIik7CiAgCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJIUiI7CiAgbmV3RGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJJTlBVVCI7CiAgbmV3RW50cnlbInR5cGUiXSA9ICJidXR0b24iOwogIG5ld0VudHJ5WyJjbGFzcyJdID0gImJ1dHRvbiI7CiAgbmV3RW50cnlbInZhbHVlIl0gPSAiTmV3IjsKICBuZXdFbnRyeVsib25jbGljayJdID0gJ2ZpbGVEZXRhaWwoIi0iLCAiJyArIGZpbGVUeXBlICsgJyIpJzsKICBuZXdEaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwoKICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0KCk7CiAgbmV3RW50cnlbIl9lbGVtZW50Il0gID0gIklOUFVUIjsKICBuZXdFbnRyeVsidHlwZSJdID0gImJ1dHRvbiI7CiAgbmV3RW50cnlbImNsYXNzIl0gPSAiYnV0dG9uIjsKICBuZXdFbnRyeVsidmFsdWUiXSA9ICJVcGRhdGUiOwogIG5ld0VudHJ5WyJvbmNsaWNrIl0gPSAiZmlsZURldGFpbCgwKSI7CiAgLy9uZXdEaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwoKICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNldHRpbmdzIik7CgogICAvLyBCdWlsZCB0YWJsZQogIHZhciBuZXdUYWJsZSA9IG5ldyBPYmplY3QoKTsKICBuZXdUYWJsZVsiX2VsZW1lbnQiXSAgPSAiVEFCTEUiOwogIG5ld1RhYmxlWyJpZCJdICAgICAgICA9ICJpZF9tYXBwaW5nIjsKICBuZXdUYWJsZVsiY2xhc3MiXSAgICAgPSAidGFibGUtbWFwcGluZyI7CiAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3VGFibGUpKTsKCiAgc2V0VGltZW91dChmdW5jdGlvbigpeyAKICAgIGNyZWF0ZUZpbGVzVGFibGUoZmlsZVR5cGUpOyAKICB9LCAxMCk7Cgp9CgpmdW5jdGlvbiBjcmVhdGVGaWxlc1RhYmxlKGZpbGVUeXBlKSB7CiAgdmFyIHRhYmxlID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImlkX21hcHBpbmciKTsKICB2YXIgYXZhaWxhYmxlRmlsZVR5cGVzID0gbmV3IEFycmF5KCk7CiAgCiAgdGFibGUuaW5uZXJIVE1MID0gIiI7CiAgdmFyIG5ld1RSID0gbmV3IE9iamVjdCgpOwogIG5ld1RSWyJfZWxlbWVudCJdID0gIlRSIjsKICBuZXdUUlsiY2xhc3MiXSAgICA9ICJ0YWJsZS1tYXBwaW5nLWhlYWRlciI7CiAgdGFibGUuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdUUikpOwoKICB2YXIgdHIgPSB0YWJsZS5sYXN0Q2hpbGQ7CgogIHN3aXRjaChmaWxlVHlwZSkgewogICAgY2FzZSAieG1sdHYiOiAKICAgICAgYXZhaWxhYmxlRmlsZVR5cGVzID0gbmV3IEFycmF5KCJ4bWx0diIpOyAKICAgICAgdmFyIHRySGVhZGxpbmVzID0gbmV3IEFycmF5KCJHdWlkZSIsICJMYXN0IFVwZGF0ZSIsICJBdmFpbGFiaWxpdHkgJSIsICJDaGFubmVscyIsICJQcm9ncmFtcyIpCiAgICAgIHZhciBjb21wYXRpYmlsaXR5S2V5cyA9IG5ldyBBcnJheSgieG1sdHYuY2hhbm5lbHMiLCAieG1sdHYucHJvZ3JhbXMiKQogICAgICBicmVhazsKCiAgICBjYXNlICJtM3UiOgogICAgICBhdmFpbGFibGVGaWxlVHlwZXMgPSBuZXcgQXJyYXkoIm0zdSIsICJoZGhyIik7IAogICAgICB2YXIgdHJIZWFkbGluZXMgPSBuZXcgQXJyYXkoIlBsYXlsaXN0IiwgIkxhc3QgVXBkYXRlIiwgIkF2YWlsYWJpbGl0eSAlIiwgIlR5cGUiLCAiU3RyZWFtcyIsICJncm91cC10aXRsZSAlIiwgInR2Zy1pZCAlIiwgIlVuaXF1ZSBJRCAlIik7CiAgICAgIHZhciBjb21wYXRpYmlsaXR5S2V5cyA9IG5ldyBBcnJheSgic3RyZWFtcyIsICJncm91cC50aXRsZSIsICJ0dmcuaWQiLCAic3RyZWFtLmlkIik7CiAgICAgIGJyZWFrOwogIH0KCiAgZm9yICh2YXIgaSA9IDA7IGkgPCB0ckhlYWRsaW5lcy5sZW5ndGg7IGkrKykgewogICAgdmFyIG5ld1REID0gbmV3IE9iamVjdCgpOwogICAgbmV3VERbIl9lbGVtZW50Il0gPSAiVEQiOwogICAgbmV3VERbIl90ZXh0Il0gICAgPSB0ckhlYWRsaW5lc1tpXTsKICAgIHRyLmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3VEQpKTsKICB9CiAgCiAgZm9yICh2YXIgaSA9IDA7IGkgPCBhdmFpbGFibGVGaWxlVHlwZXMubGVuZ3RoOyBpKyspIHsKICAgIAogICAgdmFyIGZpbGVUeXBlID0gYXZhaWxhYmxlRmlsZVR5cGVzW2ldCgogICAgdmFyIGRhdGEgPSBjb25maWdbImZpbGVzIl1bZmlsZVR5cGVdOwogICAgCiAgICB2YXIgYWxsRmlsZXMgPSBnZXRPYmpLZXlzKGRhdGEpCiAgCiAgICBmb3IgKHZhciBmID0gMDsgZiA8IGFsbEZpbGVzLmxlbmd0aDsgZisrKSB7CiAgICAgIHZhciBlbG0gICAgICAgICAgID0gZGF0YVthbGxGaWxlc1tmXV07CiAgICAgIHZhciB0YWJsZSAgICAgICAgID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImlkX21hcHBpbmciKTsKICAgICAgdmFyIGZpbGVJRCAgICAgICAgPSBlbG1bImlkLnByb3ZpZGVyIl07CiAgICAgIHZhciBuYW1lICAgICAgICAgID0gZWxtWyJuYW1lIl07CiAgICAgIHZhciBsYXN0VXBkYXRlICAgID0gZWxtWyJsYXN0LnVwZGF0ZSJdOwogICAgICB2YXIgYXZhaWxhYmlsaXR5ICA9IGVsbVsicHJvdmlkZXIuYXZhaWxhYmlsaXR5Il07CiAgICAgIHZhciB0eXBlICAgICAgICAgID0gZWxtWyJ0eXBlIl0udG9VcHBlckNhc2UoKTsKICAgICAgdmFyIGNvbXBhdGliaWxpdHkgPSBlbG1bImNvbXBhdGliaWxpdHkiXTsKCiAgICAgIC8vIENyZWF0ZSBUUgogICAgICB2YXIgbmV3VFIgPSBuZXcgT2JqZWN0KCk7CiAgICAgIG5ld1RSWyJfZWxlbWVudCJdICAgICAgID0gIlRSIjsKICAgICAgbmV3VFJbImNsYXNzIl0gICAgICAgICAgPSAiIjsKICAgICAgbmV3VFJbImlkIl0gICAgICAgICAgICAgPSBmaWxlSUQ7CiAgICAgIG5ld1RSWyJvbmNsaWNrIl0gICAgICAgID0gJ2phdmFzY3JpcHQ6IGZpbGVEZXRhaWwoIicgKyBmaWxlSUQgKyAnIiwiJyArIGZpbGVUeXBlICsgJyIpOyc7CiAgICAgIHRhYmxlLmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3VFIpKTsKCiAgICAgIHZhciB0ciA9IHRhYmxlLmxhc3RDaGlsZDsKCiAgICAgIC8vIENyZWF0ZSBmaWxlIG5hbWUgVEQKICAgICAgdmFyIG5ld1REID0gbmV3IE9iamVjdCgpOwogICAgICBuZXdURFsiX2VsZW1lbnQiXSA9ICJQIjsKICAgICAgbmV3VERbIl90ZXh0Il0gICAgPSBuYW1lOwogICAgICBjcmVhdGVOZXdURChuZXdURCwgdHIpOwoKICAgICAgLy8gQ3JlYXRlIGxhc3QgdXBkYXRlIFRECiAgICAgIHZhciBuZXdURCA9IG5ldyBPYmplY3QoKTsKICAgICAgbmV3VERbIl9lbGVtZW50Il0gPSAiUCI7CiAgICAgIG5ld1REWyJfdGV4dCJdICAgID0gbGFzdFVwZGF0ZTsKICAgICAgY3JlYXRlTmV3VEQobmV3VEQsIHRyKTsKCiAgICAgIC8vIENyZWF0ZSBhdmFpbGFiaWxpdHkgVEQKICAgICAgdmFyIG5ld1REID0gbmV3IE9iamVjdCgpOwogICAgICBuZXdURFsiX2VsZW1lbnQiXSA9ICJQIjsKICAgICAgbmV3VERbIl90ZXh0Il0gICAgPSBhdmFpbGFiaWxpdHk7CiAgICAgIGNyZWF0ZU5ld1REKG5ld1RELCB0cik7CgogICAgICBpZiAoZmlsZVR5cGUgPT0gIm0zdSIgfHwgZmlsZVR5cGUgPT0gImhkaHIiKSB7CgogICAgICAgIC8vIENyZWF0ZSBUeXBlIFRECiAgICAgICAgdmFyIG5ld1REID0gbmV3IE9iamVjdCgpOwogICAgICAgIG5ld1REWyJfZWxlbWVudCJdID0gIlAiOwogICAgICAgIG5ld1REWyJfdGV4dCJdICAgID0gdHlwZTsKICAgICAgICBjcmVhdGVOZXdURChuZXdURCwgdHIpOwogIAogICAgICB9CiAgICAgIAogICAgICAvLyBDcmVhdGUgYWxsIGNvbXBhdGliaWxpdHkgVERzCgogICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGNvbXBhdGliaWxpdHlLZXlzLmxlbmd0aDsgaisrKSB7CiAgICAgICAgdmFyIG5ld1REID0gbmV3IE9iamVjdCgpOwogICAgICAgIG5ld1REWyJfZWxlbWVudCJdID0gIlAiOwogICAgICAgIG5ld1REWyJfdGV4dCJdICAgID0gY29tcGF0aWJpbGl0eVtjb21wYXRpYmlsaXR5S2V5c1tqXV07CiAgICAgICAgY3JlYXRlTmV3VEQobmV3VEQsIHRyKTsKICAgICAgfQoKICAgIH0KCiAgfQogIAogIAogIHNvcnRUYWJsZSgwKQoKICAvLyB1c2FnZSBJbmZvICAKICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNldHRpbmdzIik7CiAgc3dpdGNoKG1lbnVbYWN0aXZlTWVudS5pZF0uaGFzT3duUHJvcGVydHkoIl91c2FnZSIpKSB7CiAgICBjYXNlIHRydWU6IAogICAgICB2YXIgdXNhZ2VJdGVtID0gbmV3IE9iamVjdCgpOwogICAgICB1c2FnZUl0ZW1bIl9lbGVtZW50Il0gPSAiUFJFIgogICAgICB1c2FnZUl0ZW1bIl90ZXh0Il0gICAgPSBtZW51W2FjdGl2ZU1lbnUuaWRdWyJfdXNhZ2UiXTsKCiAgICAgIHZhciBuZXdIUiA9IG5ldyBPYmplY3QoKTsKICAgICAgbmV3SFJbIl9lbGVtZW50Il0gPSAiSFIiCiAgICAgIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0hSKSk7CiAgICAgIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KHVzYWdlSXRlbSkpOwogICAgICBicmVhazsKICB9CgogIGNhbGN1bGF0ZVdyYXBwZXJIZWlnaHQoKTsKICByZXR1cm47Cn0KCgpmdW5jdGlvbiBmaWxlRGV0YWlsKGZpbGVJRCwgZmlsZVR5cGUpIHsKCiAgb3B0aW9uc1RleHQgID0gbmV3IEFycmF5KCJNM1UiLCAiSERIb21lUnVuIC0gW0V4cGVyaW1lbnRhbF0iKQogIG9wdGlvbnNWYWx1ZSA9IG5ldyBBcnJheSgibTN1IiwgImhkaHIiKQoKICBzd2l0Y2ggKGZpbGVUeXBlKSB7CiAgICAKICAgIGNhc2UgIm0zdSI6IAogICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgibmFtZSIpLnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAiUGxheWxpc3QgbmFtZSIpOyAKICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImRlc2NyaXB0aW9uIikuc2V0QXR0cmlidXRlKCJwbGFjZWhvbGRlciIsICJEZXNjcmlwdGlvbiBvZiB0aGlzIHBsYXlsaXN0Iik7IAogICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZmlsZS1kZXRhaWwtaGVhZGxpbmUiKS5pbm5lckhUTUwgPSAiTTNVIFBsYXlsaXN0IjsgCiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJmaWxlLXBhdGgiKS5pbm5lckhUTUwgPSAiTTNVIEZpbGU6IjsgCiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJmaWxlLnNvdXJjZSIpLnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAiTG9jYWwgb3IgcmVtb3RlIik7CiAgICAgIGJyZWFrOwoKICAgIGNhc2UgImhkaHIiOiAKICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIm5hbWUiKS5zZXRBdHRyaWJ1dGUoInBsYWNlaG9sZGVyIiwgIkhESG9tZVJ1biBuYW1lIik7IAogICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZGVzY3JpcHRpb24iKS5zZXRBdHRyaWJ1dGUoInBsYWNlaG9sZGVyIiwgIkRlc2NyaXB0aW9uIG9mIHRoaXMgSERIb21lUnVuIHR1bmVyIik7IAogICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZmlsZS1kZXRhaWwtaGVhZGxpbmUiKS5pbm5lckhUTUwgPSAiSERIb21lUnVuIjsgCiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJmaWxlLXBhdGgiKS5pbm5lckhUTUwgPSAiSERIb21lUnVuIElQOiI7IAogICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZmlsZS5zb3VyY2UiKS5zZXRBdHRyaWJ1dGUoInBsYWNlaG9sZGVyIiwgIklQIGFkZHJlc3MgYW5kIHBvcnQgb2YgdGhlIHR1bmVyICgxOTIuMTY4LjEuMTA6NTAwNCkiKTsKICAgICAgYnJlYWs7CiAgICAKICAgIGNhc2UgInhtbHR2IjogCiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJuYW1lIikuc2V0QXR0cmlidXRlKCJwbGFjZWhvbGRlciIsICJYTUxUViBuYW1lIik7IAogICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZGVzY3JpcHRpb24iKS5zZXRBdHRyaWJ1dGUoInBsYWNlaG9sZGVyIiwgIkRlc2NyaXB0aW9uIG9mIHRoaXMgWE1MVFYgZmlsZSIpOyAKICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImZpbGUtZGV0YWlsLWhlYWRsaW5lIikuaW5uZXJIVE1MID0gIlhNTFRWIEZpbGUiOyAKICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImZpbGUtcGF0aCIpLmlubmVySFRNTCA9ICJYTUxUViBGaWxlOiI7CiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJmaWxlLnNvdXJjZSIpLnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAiTG9jYWwgb3IgcmVtb3RlIik7CgogICAgICBvcHRpb25zVGV4dCAgPSBuZXcgQXJyYXkoIlhNTFRWIikKICAgICAgb3B0aW9uc1ZhbHVlID0gbmV3IEFycmF5KCJ4bWx0diIpCiAgICAgIGJyZWFrOwogIH0KCiAgbW9kaWZ5T3B0aW9uKCJ0eXBlIiwgb3B0aW9uc1RleHQsIG9wdGlvbnNWYWx1ZSkKICAKICBzaG93UG9wVXBFbGVtZW50KCdmaWxlLWRldGFpbCcpOwoKICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgic2F2ZUZpbGVEZXRhaWwiKS5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAnamF2YXNjcmlwdDogc2F2ZUZpbGVEZXRhaWwoIicgKyBmaWxlSUQgKyAnIiwiJyArIGZpbGVUeXBlICsgJyIsIGZhbHNlKScpOwogIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJ1cGRhdGVGaWxlRGV0YWlsIikuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IHVwZGF0ZUZpbGUoIicgKyBmaWxlSUQgKyAnIiwiJyArIGZpbGVUeXBlICsgJyIsIGZhbHNlKScpOwogIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJkZWxldGVGaWxlRGV0YWlsIikuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IHNhdmVGaWxlRGV0YWlsKCInICsgZmlsZUlEICsgJyIsIicgKyBmaWxlVHlwZSArICciLCB0cnVlKScpOwoKICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKCiAgc3dpdGNoKGZpbGVJRCkgewoKICAgIGNhc2UgIi0iOiAvLyBOZXcgZmlsZQogICAgICBkYXRhWyJuYW1lIl0gICAgICAgID0gIiI7CiAgICAgIGRhdGFbImRlc2NyaXB0aW9uIl0gPSAiIjsKICAgICAgZGF0YVsiZmlsZS5zb3VyY2UiXSA9ICIiOwogICAgICBkYXRhWyJ0eXBlIl0gPSBmaWxlVHlwZTsKICAgICAgCiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJkZWxldGVGaWxlRGV0YWlsIikuY2xhc3NOYW1lID0gImRlbGV0ZSI7CiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJ0eXBlIikuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJjaGFuZ2VGaWxlVHlwZSh0aGlzKTsiKQogICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgidHlwZSIpLnNldEF0dHJpYnV0ZSgiZGF0YS1pZCIsIGZpbGVJRCkKICAgICAgCiAgICAgIHNob3dFbGVtZW50KCJkZWxldGVGaWxlRGV0YWlsIiwgZmFsc2UpOwogICAgICBzaG93RWxlbWVudCgidXBkYXRlRmlsZURldGFpbCIsIGZhbHNlKTsKICAgICAgCiAgICAgIGlmIChmaWxlVHlwZSA9PSAieG1sdHYiKSB7CiAgICAgICAgc2hvd0VsZW1lbnQoInR5cGUiLCBmYWxzZSk7CiAgICAgICAgc2hvd0VsZW1lbnQoImZpbGUtdHlwZSIsIGZhbHNlKTsKICAgICAgfSBlbHNlIHsKICAgICAgICBzaG93RWxlbWVudCgidHlwZSIsIHRydWUpOwogICAgICAgIHNob3dFbGVtZW50KCJmaWxlLXR5cGUiLCB0cnVlKTsKICAgICAgfQogICAgICAKICAgICAgYnJlYWs7CgogICAgZGVmYXVsdDogCiAgICAgIGRhdGEgPSBjb25maWdbImZpbGVzIl1bZmlsZVR5cGVdW2ZpbGVJRF07CiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJkZWxldGVGaWxlRGV0YWlsIikuY2xhc3NOYW1lID0gImRlbGV0ZSI7CiAgICAgIAogICAgICBzaG93RWxlbWVudCgidXBkYXRlRmlsZURldGFpbCIsIHRydWUpOwogICAgICBzaG93RWxlbWVudCgidHlwZSIsIGZhbHNlKTsKICAgICAgc2hvd0VsZW1lbnQoImZpbGUtdHlwZSIsIGZhbHNlKTsKICAgICAgCiAgICAgIGJyZWFrOwoKICB9CgogIHZhciBrZXlzID0gZ2V0T2JqS2V5cyhkYXRhKTsKICAKICBmb3IgKHZhciBpID0gMDsgaSA8IGtleXMubGVuZ3RoOyBpKyspIHsKCiAgICBpZihkb2N1bWVudC5nZXRFbGVtZW50QnlJZChrZXlzW2ldKSl7CiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGtleXNbaV0pLnZhbHVlID0gZGF0YVtrZXlzW2ldXTsKICAgIH0gCgoKICB9Cgp9CgpmdW5jdGlvbiBjaGFuZ2VGaWxlVHlwZShlbG0pIHsKCiAgdmFyIGZpbGVJRCA9IGVsbS5nZXRBdHRyaWJ1dGUoImRhdGEtaWQiKTsKICB2YXIgZmlsZVR5cGUgPSBlbG0ub3B0aW9uc1tlbG0uc2VsZWN0ZWRJbmRleF0udmFsdWU7CiAgCiAgZmlsZURldGFpbChmaWxlSUQsIGZpbGVUeXBlKQoKfQoKCmZ1bmN0aW9uIHNhdmVGaWxlRGV0YWlsKGZpbGVJRCwgZmlsZVR5cGUsIGRlbGV0ZUZpbGUpIHsKCiAgaWYgKGZpbGVJRCA9PSB1bmRlZmluZWQpIHsKICAgIGFsZXJ0KCJJRCBpcyBtaXNzaW5nISEhIik7CiAgICByZXR1cm4gCiAgfQoKICB2YXIgaW5wdXRzICAgICAgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZmlsZS1kZXRhaWwiKS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiSU5QVVQiKTsKICB2YXIgc2VsZWN0cyAgICAgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiZmlsZS1kZXRhaWwiKS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiU0VMRUNUIik7CiAgdmFyIG5ld0ZpbGVEYXRhID0gbmV3IE9iamVjdCgpOwogIHZhciBkYXRhICAgICAgICA9IG5ldyBPYmplY3QoKTsKCiAgZm9yICh2YXIgaSA9IDA7IGkgPCBpbnB1dHMubGVuZ3RoOyBpKyspIHsKICAgIHN3aXRjaChpbnB1dHNbaV0udHlwZSkgewogICAgICBjYXNlICJ0ZXh0IjogbmV3RmlsZURhdGFbaW5wdXRzW2ldLm5hbWVdID0gaW5wdXRzW2ldLnZhbHVlOyBicmVhazsKICAgIH0KICB9CgogIGZvciAodmFyIGkgPSAwOyBpIDwgc2VsZWN0cy5sZW5ndGg7IGkrKykgewogICAgbmV3RmlsZURhdGFbc2VsZWN0c1tpXS5pZF0gPSBzZWxlY3RzW2ldLm9wdGlvbnNbc2VsZWN0c1tpXS5zZWxlY3RlZEluZGV4XS52YWx1ZTsKICB9CgogIGlmIChkZWxldGVGaWxlID09IHRydWUpIHsKICAgIHN3aXRjaChmaWxlVHlwZSkgewogICAgICBjYXNlICJtM3UiOiAgIHZhciBhbGVydFRleHQgPSAiRGVsZXRlIHRoaXMgcGxheWxpc3Q/IjsgYnJlYWs7CiAgICAgIGNhc2UgImhkaHIiOiB2YXIgYWxlcnRUZXh0ID0gIkRlbGV0ZSB0aGlzIEhESG9tZVJ1biB0dW5lcj8iOyBicmVhazsKICAgICAgY2FzZSAieG1sdHYiOiB2YXIgYWxlcnRUZXh0ID0gIkRlbGV0ZSB0aGlzIFhNTFRWIGZpbGU/IjsgYnJlYWs7CiAgICB9CgogICAgaWYgKGNvbmZpcm0oYWxlcnRUZXh0KSkgewogICAgICBuZXdGaWxlRGF0YVsiZGVsZXRlIl0gPSB0cnVlCiAgICAgIGRhdGEgPSBidWlsZEZpbGVzT2JqKGZpbGVUeXBlLCBmaWxlSUQsIG5ld0ZpbGVEYXRhKTsKICAgICAgY29uc29sZS5sb2coZGF0YSk7CiAgICAgIAogICAgfSBlbHNlIHsKICAgICAgc2hvd0VsZW1lbnQoInBvcHVwIiwgZmFsc2UpOwogICAgICByZXR1cm4KICAgIAogICAgfQoKICB9IGVsc2UgewogIAogICAgc3dpdGNoKGNvbmZpZ1siZmlsZXMiXVtmaWxlVHlwZV0uaGFzT3duUHJvcGVydHkoZmlsZUlEKSkgewoKICAgICAgY2FzZSB0cnVlOiAKICAgICAgICBkYXRhID0gY29uZmlnWyJmaWxlcyJdW2ZpbGVUeXBlXVtmaWxlSURdOyAKICAgICAgICBpZiAoZGF0YVsiZmlsZS5zb3VyY2UiXSAhPSBuZXdGaWxlRGF0YVsiZmlsZS5zb3VyY2UiXSkgewogICAgICAgICAgZGF0YVsidXBkYXRlIl0gPSB0cnVlCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIGRhdGFbInVwZGF0ZVBsYXlsaXN0TmFtZSJdID0gdHJ1ZTsKICAgICAgICB9CiAgICAgICAgYnJlYWs7CiAgICAgIAogICAgICBjYXNlIGZhbHNlOiAKICAgICAgICBuZXdGaWxlRGF0YVsibmV3Il0gPSB0cnVlOwogICAgICAgIGRhdGEgPSBidWlsZEZpbGVzT2JqKGZpbGVUeXBlLCBmaWxlSUQsIG5ld0ZpbGVEYXRhKTsKICAgICAgICBicmVhawoKICAgIH0KICAKICB9ICAKICAKICBzd2l0Y2goZmlsZVR5cGUpIHsKCiAgICBjYXNlICJtM3UiOiAgIGRhdGFbImNtZCJdID0gInNhdmVGaWxlc00zVSI7IGJyZWFrOwogICAgY2FzZSAiaGRociI6ICBkYXRhWyJjbWQiXSA9ICJzYXZlRmlsZXNIREhSIjsgYnJlYWs7CiAgICBjYXNlICJ4bWx0diI6IGRhdGFbImNtZCJdID0gInNhdmVGaWxlc1hNTFRWIjsgYnJlYWs7CgogIH0KICAvL2NvbnNvbGUubG9nKGRhdGEpOwogIHhUZVZlKGRhdGEpOwogIHJldHVybgp9CgpmdW5jdGlvbiB1cGRhdGVGaWxlKGZpbGVJRCwgZmlsZVR5cGUsIGFsbEZpbGVzKSB7CiAgCiAgc3dpdGNoKGNvbmZpZ1siZmlsZXMiXVtmaWxlVHlwZV0uaGFzT3duUHJvcGVydHkoZmlsZUlEKSkgewoKICAgIGNhc2UgdHJ1ZTogCiAgICAKICAgICAgdmFyIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgICAgIHZhciBkYXRhID0gYnVpbGRGaWxlc09iaihmaWxlVHlwZSwgZmlsZUlELCBjb25maWdbImZpbGVzIl1bZmlsZVR5cGVdW2ZpbGVJRF0pCiAgICAgIGRhdGFbIm5ldyJdID0gdHJ1ZQoKICAgICAgc3dpdGNoKGZpbGVUeXBlKSB7CgogICAgICAgIGNhc2UgIm0zdSI6ICAgZGF0YVsiY21kIl0gPSAidXBkYXRlRmlsZU0zVSI7IGJyZWFrOwogICAgICAgIGNhc2UgImhkaHIiOiAgZGF0YVsiY21kIl0gPSAidXBkYXRlRmlsZUhESFIiOyBicmVhazsKICAgICAgICBjYXNlICJ4bWx0diI6IGRhdGFbImNtZCJdID0gInVwZGF0ZUZpbGVYTUxUViI7IGJyZWFrOwoKICAgICAgfQogICAgICAKICAgICAgeFRlVmUoZGF0YSk7CiAgICAgIAogICAgICBicmVhazsKICB9Cgp9CgpmdW5jdGlvbiBidWlsZEZpbGVzT2JqKGZpbGVUeXBlLCBmaWxlSUQsIG9iaikgewoKICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKICBkYXRhWyJmaWxlcyJdID0gbmV3IE9iamVjdCgpOwogIGRhdGFbImZpbGVzIl1bZmlsZVR5cGVdID0gbmV3IE9iamVjdCgpOwogIGRhdGFbImZpbGVzIl1bZmlsZVR5cGVdW2ZpbGVJRF0gPSBvYmoKICByZXR1cm4gZGF0YQoKfQ==" + webUI["html/img/BC-QR.jpg"] = "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" + webUI["html/img/x_black.png"] = "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAA6ppVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0xMS0xNVQxNzoxMTozNzwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6Q29tcHJlc3Npb24+NTwvdGlmZjpDb21wcmVzc2lvbj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MTQ0PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpYUmVzb2x1dGlvbj4xNDQ8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4yNTY8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFj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a1jSekqmtA1rrGsx7JYkDuvrVahwRdgVXD1xcwri6mmYRULLe4CuPc2wfrGo4U+7Y/3S9ZHD+GcflbnfiCJbr77whoaS6ucCXVkVkuLKgORQwNJFygRDJnqwb9i3b0HxkqjT0L4erGy5Rh1IRnhXrpaZq6GZCyRLf6cvhoTY6V9koX8bGdEUlyWVm6W5q1GuostihNm97L/dpm5k+QHCbDna0zRzgSS/lRHWL7iHPOa+9YsYkpttoCwSMcQRkrWioz5Qsc7aFJ1coHY3QyJ9jSjfJEOCfTUpWzehVBnUbqCJOCHEkB1xsmXDwq9DnObUz9q4QOUqYPaPKFzUKcVj6Eeu461q67Vhlqy6+Xo11G52JhRZNNW1FRbN00FWPVwguSa+8YEZ1i+D9vQjelm/iHTwAB69Vwd7CkuGLOcWtxfR4o+dHhNg4DNmhDvJ8M6ehknvhTXMDp8kQQgT33W4z+i7k/2gho+Jvrp1NTVwgW5opV76yl8F/cufe9CyJiRllZ6lYCHMXWPAXmNp9CRb8Pi30r658sHtCSCPsZNXQDYdMaLIek/No13V/rBfGMFSCXnsKNo1dDelnKt/dyUH8uvjjLH+zyfpbv1iUl/NVBt4mVJy58HoDyFZLt0rrj4G7TfIjARvMjy7d6JnR/UXS/+yeD5kLYkETxpRZL2jZJVzb7cl9yZA/cYqh4z+20AEzOj+BKxbZYRFqXQssvLs5gQzpBUp616rNsX55WdXBHbPBRrwlBm5v2VYJo1FsjmOtQg8ezo+fd8Ve4qy04HPRlmR1UiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABGwg8D8UnyVeZFwGAQAAAABJRU5ErkJggg==" + webUI["html/js/base_ts.js"] = "dmFyIFNFUlZFUiA9IG5ldyBPYmplY3QoKTsKdmFyIEJVTEtfRURJVCA9IGZhbHNlOwp2YXIgQ09MVU1OX1RPX1NPUlQ7CnZhciBTRUFSQ0hfTUFQUElORyA9IG5ldyBPYmplY3QoKTsKdmFyIFVORE8gPSBuZXcgT2JqZWN0KCk7CnZhciBTRVJWRVJfQ09OTkVDVElPTiA9IGZhbHNlOwp2YXIgV1NfQVZBSUxBQkxFID0gZmFsc2U7Ci8vIE1lbsO8CnZhciBtZW51SXRlbXMgPSBuZXcgQXJyYXkoKTsKbWVudUl0ZW1zLnB1c2gobmV3IE1haW5NZW51SXRlbSgicGxheWxpc3QiLCAie3subWFpbk1lbnUuaXRlbS5wbGF5bGlzdH19IiwgIm0zdS5wbmciLCAie3subWFpbk1lbnUuaGVhZGxpbmUucGxheWxpc3R9fSIpKTsKLy9tZW51SXRlbXMucHVzaChuZXcgTWFpbk1lbnVJdGVtKCJwbXNJRCIsICJ7ey5tYWluTWVudS5pdGVtLnBtc0lEfX0iLCAibnVtYmVyLnBuZyIsICJ7ey5tYWluTWVudS5oZWFkbGluZS5wbXNJRH19IikpCm1lbnVJdGVtcy5wdXNoKG5ldyBNYWluTWVudUl0ZW0oImZpbHRlciIsICJ7ey5tYWluTWVudS5pdGVtLmZpbHRlcn19IiwgImZpbHRlci5wbmciLCAie3subWFpbk1lbnUuaGVhZGxpbmUuZmlsdGVyfX0iKSk7Cm1lbnVJdGVtcy5wdXNoKG5ldyBNYWluTWVudUl0ZW0oInhtbHR2IiwgInt7Lm1haW5NZW51Lml0ZW0ueG1sdHZ9fSIsICJ4bWx0di5wbmciLCAie3subWFpbk1lbnUuaGVhZGxpbmUueG1sdHZ9fSIpKTsKbWVudUl0ZW1zLnB1c2gobmV3IE1haW5NZW51SXRlbSgibWFwcGluZyIsICJ7ey5tYWluTWVudS5pdGVtLm1hcHBpbmd9fSIsICJtYXBwaW5nLnBuZyIsICJ7ey5tYWluTWVudS5oZWFkbGluZS5tYXBwaW5nfX0iKSk7Cm1lbnVJdGVtcy5wdXNoKG5ldyBNYWluTWVudUl0ZW0oInVzZXJzIiwgInt7Lm1haW5NZW51Lml0ZW0udXNlcnN9fSIsICJ1c2Vycy5wbmciLCAie3subWFpbk1lbnUuaGVhZGxpbmUudXNlcnN9fSIpKTsKbWVudUl0ZW1zLnB1c2gobmV3IE1haW5NZW51SXRlbSgic2V0dGluZ3MiLCAie3subWFpbk1lbnUuaXRlbS5zZXR0aW5nc319IiwgInNldHRpbmdzLnBuZyIsICJ7ey5tYWluTWVudS5oZWFkbGluZS5zZXR0aW5nc319IikpOwptZW51SXRlbXMucHVzaChuZXcgTWFpbk1lbnVJdGVtKCJsb2ciLCAie3subWFpbk1lbnUuaXRlbS5sb2d9fSIsICJsb2cucG5nIiwgInt7Lm1haW5NZW51LmhlYWRsaW5lLmxvZ319IikpOwptZW51SXRlbXMucHVzaChuZXcgTWFpbk1lbnVJdGVtKCJsb2dvdXQiLCAie3subWFpbk1lbnUuaXRlbS5sb2dvdXR9fSIsICJsb2dvdXQucG5nIiwgInt7Lm1haW5NZW51LmhlYWRsaW5lLmxvZ291dH19IikpOwovLyBLYXRlZ29yaWVuIGbDvHIgZGllIEVpbnN0ZWxsdW5nZW4KdmFyIHNldHRpbmdzQ2F0ZWdvcnkgPSBuZXcgQXJyYXkoKTsKc2V0dGluZ3NDYXRlZ29yeS5wdXNoKG5ldyBTZXR0aW5nc0NhdGVnb3J5SXRlbSgie3suc2V0dGluZ3MuY2F0ZWdvcnkuZ2VuZXJhbH19IiwgInh0ZXZlQXV0b1VwZGF0ZSx0dW5lcixlcGdTb3VyY2UsYXBpIikpOwpzZXR0aW5nc0NhdGVnb3J5LnB1c2gobmV3IFNldHRpbmdzQ2F0ZWdvcnlJdGVtKCJ7ey5zZXR0aW5ncy5jYXRlZ29yeS5maWxlc319IiwgInVwZGF0ZSxmaWxlcy51cGRhdGUsdGVtcC5wYXRoLGNhY2hlLmltYWdlcyx4ZXBnLnJlcGxhY2UubWlzc2luZy5pbWFnZXMiKSk7CnNldHRpbmdzQ2F0ZWdvcnkucHVzaChuZXcgU2V0dGluZ3NDYXRlZ29yeUl0ZW0oInt7LnNldHRpbmdzLmNhdGVnb3J5LnN0cmVhbWluZ319IiwgImJ1ZmZlcixidWZmZXIuc2l6ZS5rYixidWZmZXIudGltZW91dCx1c2VyLmFnZW50IikpOwpzZXR0aW5nc0NhdGVnb3J5LnB1c2gobmV3IFNldHRpbmdzQ2F0ZWdvcnlJdGVtKCJ7ey5zZXR0aW5ncy5jYXRlZ29yeS5iYWNrdXB9fSIsICJiYWNrdXAucGF0aCxiYWNrdXAua2VlcCIpKTsKc2V0dGluZ3NDYXRlZ29yeS5wdXNoKG5ldyBTZXR0aW5nc0NhdGVnb3J5SXRlbSgie3suc2V0dGluZ3MuY2F0ZWdvcnkuYXV0aGVudGljYXRpb259fSIsICJhdXRoZW50aWNhdGlvbi53ZWIsYXV0aGVudGljYXRpb24ucG1zLGF1dGhlbnRpY2F0aW9uLm0zdSxhdXRoZW50aWNhdGlvbi54bWwsYXV0aGVudGljYXRpb24uYXBpIikpOwpmdW5jdGlvbiBzaG93UG9wVXBFbGVtZW50KGVsbSkgewogICAgdmFyIGFsbEVsZW1lbnRzID0gbmV3IEFycmF5KCJwb3B1cC1jdXN0b20iKTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgYWxsRWxlbWVudHMubGVuZ3RoOyBpKyspIHsKICAgICAgICBzaG93RWxlbWVudChhbGxFbGVtZW50c1tpXSwgZmFsc2UpOwogICAgfQogICAgc2hvd0VsZW1lbnQoZWxtLCB0cnVlKTsKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24gKCkgewogICAgICAgIHNob3dFbGVtZW50KCJwb3B1cCIsIHRydWUpOwogICAgfSwgMTApOwogICAgcmV0dXJuOwp9CmZ1bmN0aW9uIHNob3dFbGVtZW50KGVsbUlELCB0eXBlKSB7CiAgICB2YXIgY3NzQ2xhc3M7CiAgICBzd2l0Y2ggKHR5cGUpIHsKICAgICAgICBjYXNlIHRydWU6CiAgICAgICAgICAgIGNzc0NsYXNzID0gImJsb2NrIjsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSBmYWxzZToKICAgICAgICAgICAgY3NzQ2xhc3MgPSAibm9uZSI7CiAgICAgICAgICAgIGJyZWFrOwogICAgfQogICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoZWxtSUQpLmNsYXNzTmFtZSA9IGNzc0NsYXNzOwp9CmZ1bmN0aW9uIGNoYW5nZUJ1dHRvbkFjdGlvbihlbGVtZW50LCBidXR0b25JRCwgYXR0cmlidXRlKSB7CiAgICB2YXIgdmFsdWUgPSBlbGVtZW50Lm9wdGlvbnNbZWxlbWVudC5zZWxlY3RlZEluZGV4XS52YWx1ZTsKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGJ1dHRvbklEKS5zZXRBdHRyaWJ1dGUoYXR0cmlidXRlLCB2YWx1ZSk7Cn0KZnVuY3Rpb24gZ2V0TG9jYWxEYXRhKGRhdGFUeXBlLCBpZCkgewogICAgdmFyIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgICBzd2l0Y2ggKGRhdGFUeXBlKSB7CiAgICAgICAgY2FzZSAibTN1IjoKICAgICAgICAgICAgZGF0YSA9IFNFUlZFUlsic2V0dGluZ3MiXVsiZmlsZXMiXVtkYXRhVHlwZV1baWRdOwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICJoZGhyIjoKICAgICAgICAgICAgZGF0YSA9IFNFUlZFUlsic2V0dGluZ3MiXVsiZmlsZXMiXVtkYXRhVHlwZV1baWRdOwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICJmaWx0ZXIiOgogICAgICAgIGNhc2UgImN1c3RvbS1maWx0ZXIiOgogICAgICAgIGNhc2UgImdyb3VwLXRpdGxlIjoKICAgICAgICAgICAgaWYgKGlkID09IC0xKSB7CiAgICAgICAgICAgICAgICBkYXRhWyJhY3RpdmUiXSA9IHRydWU7CiAgICAgICAgICAgICAgICBkYXRhWyJjYXNlU2Vuc2l0aXZlIl0gPSBmYWxzZTsKICAgICAgICAgICAgICAgIGRhdGFbImRlc2NyaXB0aW9uIl0gPSAiIjsKICAgICAgICAgICAgICAgIGRhdGFbImV4Y2x1ZGUiXSA9ICIiOwogICAgICAgICAgICAgICAgZGF0YVsiZmlsdGVyIl0gPSAiIjsKICAgICAgICAgICAgICAgIGRhdGFbImluY2x1ZGUiXSA9ICIiOwogICAgICAgICAgICAgICAgZGF0YVsibmFtZSJdID0gIiI7CiAgICAgICAgICAgICAgICBkYXRhWyJ0eXBlIl0gPSAiZ3JvdXAtdGl0bGUiOwogICAgICAgICAgICAgICAgU0VSVkVSWyJzZXR0aW5ncyJdWyJmaWx0ZXIiXVtpZF0gPSBkYXRhOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGRhdGEgPSBTRVJWRVJbInNldHRpbmdzIl1bImZpbHRlciJdW2lkXTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAieG1sdHYiOgogICAgICAgICAgICBkYXRhID0gU0VSVkVSWyJzZXR0aW5ncyJdWyJmaWxlcyJdW2RhdGFUeXBlXVtpZF07CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgInVzZXJzIjoKICAgICAgICAgICAgZGF0YSA9IFNFUlZFUlsidXNlcnMiXVtpZF1bImRhdGEiXTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAibWFwcGluZyI6CiAgICAgICAgICAgIGRhdGEgPSBTRVJWRVJbInhlcGciXVsiZXBnTWFwcGluZyJdW2lkXTsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAibTN1R3JvdXBzIjoKICAgICAgICAgICAgZGF0YSA9IFNFUlZFUlsiZGF0YSJdWyJwbGF5bGlzdCJdWyJtM3UiXVsiZ3JvdXBzIl07CiAgICAgICAgICAgIGJyZWFrOwogICAgfQogICAgcmV0dXJuIGRhdGE7Cn0KZnVuY3Rpb24gZ2V0T2JqS2V5cyhvYmopIHsKICAgIHZhciBrZXlzID0gbmV3IEFycmF5KCk7CiAgICBmb3IgKHZhciBpIGluIG9iaikgewogICAgICAgIGlmIChvYmouaGFzT3duUHJvcGVydHkoaSkpIHsKICAgICAgICAgICAga2V5cy5wdXNoKGkpOwogICAgICAgIH0KICAgIH0KICAgIHJldHVybiBrZXlzOwp9CmZ1bmN0aW9uIGdldEFsbFNlbGVjdGVkQ2hhbm5lbHMoKSB7CiAgICB2YXIgY2hhbm5lbHMgPSBuZXcgQXJyYXkoKTsKICAgIGlmIChCVUxLX0VESVQgPT0gZmFsc2UpIHsKICAgICAgICByZXR1cm4gY2hhbm5lbHM7CiAgICB9CiAgICB2YXIgdHJzID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImNvbnRlbnRfdGFibGUiKS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiVFIiKTsKICAgIGZvciAodmFyIGkgPSAxOyBpIDwgdHJzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgaWYgKHRyc1tpXS5zdHlsZS5kaXNwbGF5ICE9ICJub25lIikgewogICAgICAgICAgICBpZiAodHJzW2ldLmZpcnN0Q2hpbGQuZmlyc3RDaGlsZC5jaGVja2VkID09IHRydWUpIHsKICAgICAgICAgICAgICAgIGNoYW5uZWxzLnB1c2godHJzW2ldLmlkKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0KICAgIHJldHVybiBjaGFubmVsczsKfQpmdW5jdGlvbiBzZWxlY3RBbGxDaGFubmVscygpIHsKICAgIHZhciBidWxrID0gZmFsc2U7CiAgICB2YXIgdHJzID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImNvbnRlbnRfdGFibGUiKS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiVFIiKTsKICAgIGlmICh0cnNbMF0uZmlyc3RDaGlsZC5maXJzdENoaWxkLmNoZWNrZWQgPT0gdHJ1ZSkgewogICAgICAgIGJ1bGsgPSB0cnVlOwogICAgfQogICAgZm9yICh2YXIgaSA9IDE7IGkgPCB0cnMubGVuZ3RoOyBpKyspIHsKICAgICAgICBpZiAodHJzW2ldLnN0eWxlLmRpc3BsYXkgIT0gIm5vbmUiKSB7CiAgICAgICAgICAgIHN3aXRjaCAoYnVsaykgewogICAgICAgICAgICAgICAgY2FzZSB0cnVlOgogICAgICAgICAgICAgICAgICAgIHRyc1tpXS5maXJzdENoaWxkLmZpcnN0Q2hpbGQuY2hlY2tlZCA9IHRydWU7CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICBjYXNlIGZhbHNlOgogICAgICAgICAgICAgICAgICAgIHRyc1tpXS5maXJzdENoaWxkLmZpcnN0Q2hpbGQuY2hlY2tlZCA9IGZhbHNlOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfQogICAgcmV0dXJuOwp9CmZ1bmN0aW9uIGJ1bGtFZGl0KCkgewogICAgQlVMS19FRElUID0gIUJVTEtfRURJVDsKICAgIHZhciBjbGFzc05hbWU7CiAgICB2YXIgcm93cyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoImJ1bGsiKTsKICAgIHN3aXRjaCAoQlVMS19FRElUKSB7CiAgICAgICAgY2FzZSB0cnVlOgogICAgICAgICAgICBjbGFzc05hbWUgPSAiYnVsayBzaG93QnVsayI7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgZmFsc2U6CiAgICAgICAgICAgIGNsYXNzTmFtZSA9ICJidWxrIGhpZGVCdWxrIjsKICAgICAgICAgICAgYnJlYWs7CiAgICB9CiAgICBmb3IgKHZhciBpID0gMDsgaSA8IHJvd3MubGVuZ3RoOyBpKyspIHsKICAgICAgICByb3dzW2ldLmNsYXNzTmFtZSA9IGNsYXNzTmFtZTsKICAgICAgICByb3dzW2ldLmNoZWNrZWQgPSBmYWxzZTsKICAgIH0KICAgIHJldHVybjsKfQpmdW5jdGlvbiBzb3J0VGFibGUoY29sdW1uKSB7CiAgICAvL2NvbnNvbGUubG9nKGNvbHVtbSk7CiAgICBpZiAoY29sdW1uID09IENPTFVNTl9UT19TT1JUKSB7CiAgICAgICAgcmV0dXJuOwogICAgfQogICAgdmFyIHRhYmxlID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImNvbnRlbnRfdGFibGUiKTsKICAgIHZhciB0YWJsZUhlYWQgPSB0YWJsZS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiVFIiKVswXTsKICAgIHZhciB0YWJsZUl0ZW1zID0gdGFibGVIZWFkLmdldEVsZW1lbnRzQnlUYWdOYW1lKCJURCIpOwogICAgdmFyIHNvcnRPYmogPSBuZXcgT2JqZWN0KCk7CiAgICB2YXIgeCwgeFZhbHVlOwogICAgdmFyIHRhYmxlSGVhZGVyOwogICAgdmFyIHNvcnRCeVN0cmluZyA9IGZhbHNlOwogICAgaWYgKGNvbHVtbiA+IDAgJiYgQ09MVU1OX1RPX1NPUlQgPiAwKSB7CiAgICAgICAgdGFibGVJdGVtc1tDT0xVTU5fVE9fU09SVF0uY2xhc3NOYW1lID0gInBvaW50ZXIiOwogICAgICAgIHRhYmxlSXRlbXNbY29sdW1uXS5jbGFzc05hbWUgPSAic29ydFRoaXMiOwogICAgfQogICAgQ09MVU1OX1RPX1NPUlQgPSBjb2x1bW47CiAgICB2YXIgcm93cyA9IHRhYmxlLnJvd3M7CiAgICBpZiAocm93c1sxXSAhPSB1bmRlZmluZWQpIHsKICAgICAgICB0YWJsZUhlYWRlciA9IHJvd3NbMF07CiAgICAgICAgeCA9IHJvd3NbMV0uZ2V0RWxlbWVudHNCeVRhZ05hbWUoIlREIilbY29sdW1uXTsKICAgICAgICBmb3IgKGkgPSAxOyBpIDwgcm93cy5sZW5ndGg7IGkrKykgewogICAgICAgICAgICB4ID0gcm93c1tpXS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiVEQiKVtjb2x1bW5dOwogICAgICAgICAgICBzd2l0Y2ggKHguY2hpbGROb2Rlc1swXS50YWdOYW1lLnRvTG93ZXJDYXNlKCkpIHsKICAgICAgICAgICAgICAgIGNhc2UgImlucHV0IjoKICAgICAgICAgICAgICAgICAgICB4VmFsdWUgPSB4LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJJTlBVVCIpWzBdLnZhbHVlLnRvTG93ZXJDYXNlKCk7CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICBjYXNlICJwIjoKICAgICAgICAgICAgICAgICAgICB4VmFsdWUgPSB4LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJQIilbMF0uaW5uZXJUZXh0LnRvTG93ZXJDYXNlKCk7CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICBkZWZhdWx0OiBjb25zb2xlLmxvZyh4LmNoaWxkTm9kZXNbMF0udGFnTmFtZSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgaWYgKHhWYWx1ZSA9PSAiIiB8fCB4VmFsdWUgPT0gTmFOKSB7CiAgICAgICAgICAgICAgICB4VmFsdWUgPSBpOwogICAgICAgICAgICAgICAgc29ydE9ialtpXSA9IHJvd3NbaV07CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICBzd2l0Y2ggKGlzTmFOKHhWYWx1ZSkpIHsKICAgICAgICAgICAgICAgICAgICBjYXNlIGZhbHNlOgogICAgICAgICAgICAgICAgICAgICAgICB4VmFsdWUgPSBwYXJzZUZsb2F0KHhWYWx1ZSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHNvcnRPYmpbeFZhbHVlXSA9IHJvd3NbaV07CiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgICAgIGNhc2UgdHJ1ZToKICAgICAgICAgICAgICAgICAgICAgICAgc29ydEJ5U3RyaW5nID0gdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgc29ydE9ialt4VmFsdWUudG9Mb3dlckNhc2UoKSArIGldID0gcm93c1tpXTsKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgd2hpbGUgKHRhYmxlLmZpcnN0Q2hpbGQpIHsKICAgICAgICAgICAgdGFibGUucmVtb3ZlQ2hpbGQodGFibGUuZmlyc3RDaGlsZCk7CiAgICAgICAgfQogICAgICAgIHZhciBzb3J0VmFsdWVzID0gZ2V0T2JqS2V5cyhzb3J0T2JqKTsKICAgICAgICBpZiAoc29ydEJ5U3RyaW5nID09IHRydWUpIHsKICAgICAgICAgICAgc29ydFZhbHVlcy5zb3J0KCk7CiAgICAgICAgICAgIGNvbnNvbGUubG9nKHNvcnRWYWx1ZXMpOwogICAgICAgIH0KICAgICAgICBlbHNlIHsKICAgICAgICAgICAgZnVuY3Rpb24gc29ydEZsb2F0KGEsIGIpIHsKICAgICAgICAgICAgICAgIHJldHVybiBhIC0gYjsKICAgICAgICAgICAgfQogICAgICAgICAgICBzb3J0VmFsdWVzLnNvcnQoc29ydEZsb2F0KTsKICAgICAgICB9CiAgICAgICAgdGFibGUuYXBwZW5kQ2hpbGQodGFibGVIZWFkZXIpOwogICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgc29ydFZhbHVlcy5sZW5ndGg7IGkrKykgewogICAgICAgICAgICB0YWJsZS5hcHBlbmRDaGlsZChzb3J0T2JqW3NvcnRWYWx1ZXNbaV1dKTsKICAgICAgICB9CiAgICB9CiAgICByZXR1cm47Cn0KZnVuY3Rpb24gY3JlYXRlU2VhcmNoT2JqKCkgewogICAgU0VBUkNIX01BUFBJTkcgPSBuZXcgT2JqZWN0KCk7CiAgICB2YXIgZGF0YSA9IFNFUlZFUlsieGVwZyJdWyJlcGdNYXBwaW5nIl07CiAgICB2YXIgY2hhbm5lbHMgPSBnZXRPYmpLZXlzKGRhdGEpOwogICAgdmFyIGNoYW5uZWxLZXlzID0gWyJ4LWFjdGl2ZSIsICJ4LWNoYW5uZWxJRCIsICJ4LW5hbWUiLCAiX2ZpbGUubTN1Lm5hbWUiLCAieC1ncm91cC10aXRsZSJdOwogICAgY2hhbm5lbHMuZm9yRWFjaChmdW5jdGlvbiAoaWQpIHsKICAgICAgICBjaGFubmVsS2V5cy5mb3JFYWNoKGZ1bmN0aW9uIChrZXkpIHsKICAgICAgICAgICAgaWYgKGtleSA9PSAieC1hY3RpdmUiKSB7CiAgICAgICAgICAgICAgICBzd2l0Y2ggKGRhdGFbaWRdW2tleV0pIHsKICAgICAgICAgICAgICAgICAgICBjYXNlIHRydWU6CiAgICAgICAgICAgICAgICAgICAgICAgIFNFQVJDSF9NQVBQSU5HW2lkXSA9ICJvbmxpbmUgIjsKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICAgICAgY2FzZSBmYWxzZToKICAgICAgICAgICAgICAgICAgICAgICAgU0VBUkNIX01BUFBJTkdbaWRdID0gIm9mZmxpbmUgIjsKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICBTRUFSQ0hfTUFQUElOR1tpZF0gPSBTRUFSQ0hfTUFQUElOR1tpZF0gKyBkYXRhW2lkXVtrZXldICsgIiAiOwogICAgICAgICAgICB9CiAgICAgICAgfSk7CiAgICB9KTsKICAgIHJldHVybjsKfQpmdW5jdGlvbiBzZWFyY2hJbk1hcHBpbmcoKSB7CiAgICB2YXIgc2VhcmNoVmFsdWUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgic2VhcmNoTWFwcGluZyIpLnZhbHVlOwogICAgdmFyIHRycyA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJjb250ZW50X3RhYmxlIikuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIlRSIik7CiAgICBmb3IgKHZhciBpID0gMTsgaSA8IHRycy5sZW5ndGg7ICsraSkgewogICAgICAgIHZhciBpZCA9IHRyc1tpXS5nZXRBdHRyaWJ1dGUoImlkIik7CiAgICAgICAgdmFyIGVsZW1lbnQgPSBTRUFSQ0hfTUFQUElOR1tpZF07CiAgICAgICAgc3dpdGNoIChlbGVtZW50LnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoc2VhcmNoVmFsdWUudG9Mb3dlckNhc2UoKSkpIHsKICAgICAgICAgICAgY2FzZSB0cnVlOgogICAgICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpLnN0eWxlLmRpc3BsYXkgPSAiIjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlIGZhbHNlOgogICAgICAgICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpLnN0eWxlLmRpc3BsYXkgPSAibm9uZSI7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICB9CiAgICB9CiAgICByZXR1cm47Cn0KZnVuY3Rpb24gY2FsY3VsYXRlV3JhcHBlckhlaWdodCgpIHsKICAgIGlmIChkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYm94LXdyYXBwZXIiKSkgewogICAgICAgIHZhciBlbG0gPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYm94LXdyYXBwZXIiKTsKICAgICAgICB2YXIgZGl2cyA9IG5ldyBBcnJheSgibXlTdHJlYW1zQm94IiwgImNsaWVudEluZm8iLCAiY29udGVudCIpOwogICAgICAgIHZhciBlbGVtZW50c0hlaWdodCA9IDAgLSBlbG0ub2Zmc2V0SGVpZ2h0OwogICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgZGl2cy5sZW5ndGg7IGkrKykgewogICAgICAgICAgICBlbGVtZW50c0hlaWdodCA9IGVsZW1lbnRzSGVpZ2h0ICsgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoZGl2c1tpXSkub2Zmc2V0SGVpZ2h0OwogICAgICAgIH0KICAgICAgICBlbG0uc3R5bGUuaGVpZ2h0ID0gd2luZG93LmlubmVySGVpZ2h0IC0gZWxlbWVudHNIZWlnaHQgKyAicHgiOwogICAgfQogICAgcmV0dXJuOwp9CmZ1bmN0aW9uIGNoYW5nZUNoYW5uZWxOdW1iZXIoZWxlbWVudCkgewogICAgdmFyIGRiSUQgPSBlbGVtZW50LnBhcmVudE5vZGUucGFyZW50Tm9kZS5pZDsKICAgIHZhciBuZXdOdW1iZXIgPSBwYXJzZUZsb2F0KGVsZW1lbnQudmFsdWUpOwogICAgdmFyIGNoYW5uZWxOdW1iZXJzID0gW107CiAgICB2YXIgZGF0YSA9IFNFUlZFUlsieGVwZyJdWyJlcGdNYXBwaW5nIl07CiAgICB2YXIgY2hhbm5lbHMgPSBnZXRPYmpLZXlzKGRhdGEpOwogICAgaWYgKGlzTmFOKG5ld051bWJlcikpIHsKICAgICAgICBhbGVydCgie3suYWxlcnQuaW52YWxpZENoYW5uZWxOdW1iZXJ9fSIpOwogICAgICAgIHJldHVybjsKICAgIH0KICAgIGNoYW5uZWxzLmZvckVhY2goZnVuY3Rpb24gKGlkKSB7CiAgICAgICAgdmFyIGNoYW5uZWxOdW1iZXIgPSBwYXJzZUZsb2F0KGRhdGFbaWRdWyJ4LWNoYW5uZWxJRCJdKTsKICAgICAgICBjaGFubmVsTnVtYmVycy5wdXNoKGNoYW5uZWxOdW1iZXIpOwogICAgfSk7CiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGNoYW5uZWxOdW1iZXJzLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgaWYgKGNoYW5uZWxOdW1iZXJzLmluZGV4T2YobmV3TnVtYmVyKSA9PSAtMSkgewogICAgICAgICAgICBicmVhazsKICAgICAgICB9CiAgICAgICAgaWYgKE1hdGguZmxvb3IobmV3TnVtYmVyKSA9PSBuZXdOdW1iZXIpIHsKICAgICAgICAgICAgbmV3TnVtYmVyID0gbmV3TnVtYmVyICsgMTsKICAgICAgICB9CiAgICAgICAgZWxzZSB7CiAgICAgICAgICAgIG5ld051bWJlciA9IG5ld051bWJlciArIDAuMTsKICAgICAgICAgICAgbmV3TnVtYmVyLnRvRml4ZWQoMSk7CiAgICAgICAgICAgIG5ld051bWJlciA9IE1hdGgucm91bmQobmV3TnVtYmVyICogMTApIC8gMTA7CiAgICAgICAgfQogICAgfQogICAgZGF0YVtkYklEXVsieC1jaGFubmVsSUQiXSA9IG5ld051bWJlci50b1N0cmluZygpOwogICAgZWxlbWVudC52YWx1ZSA9IG5ld051bWJlcjsKICAgIGNvbnNvbGUubG9nKGRhdGFbZGJJRF1bIngtY2hhbm5lbElEIl0pOwogICAgaWYgKENPTFVNTl9UT19TT1JUID09IDEpIHsKICAgICAgICBDT0xVTU5fVE9fU09SVCA9IC0xOwogICAgICAgIHNvcnRUYWJsZSgxKTsKICAgIH0KICAgIHJldHVybjsKfQpmdW5jdGlvbiBiYWNrdXAoKSB7CiAgICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKICAgIGNvbnNvbGUubG9nKCJCYWNrdXAgZGF0YSIpOwogICAgdmFyIGNtZCA9ICJ4dGV2ZUJhY2t1cCI7CiAgICBjb25zb2xlLmxvZygiU0VORCBUTyBTRVJWRVIiKTsKICAgIGNvbnNvbGUubG9nKGRhdGEpOwogICAgdmFyIHNlcnZlciA9IG5ldyBTZXJ2ZXIoY21kKTsKICAgIHNlcnZlci5yZXF1ZXN0KGRhdGEpOwogICAgcmV0dXJuOwp9CmZ1bmN0aW9uIHRvZ2dsZUNoYW5uZWxTdGF0dXMoaWQpIHsKICAgIHZhciBlbGVtZW50OwogICAgdmFyIHN0YXR1czsKICAgIGlmIChkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYWN0aXZlIikpIHsKICAgICAgICB2YXIgY2hlY2tib3ggPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYWN0aXZlIik7CiAgICAgICAgc3RhdHVzID0gKGNoZWNrYm94KS5jaGVja2VkOwogICAgfQogICAgdmFyIGlkcyA9IGdldEFsbFNlbGVjdGVkQ2hhbm5lbHMoKTsKICAgIGlmIChpZHMubGVuZ3RoID09IDApIHsKICAgICAgICBpZHMucHVzaChpZCk7CiAgICB9CiAgICBpZHMuZm9yRWFjaChmdW5jdGlvbiAoaWQpIHsKICAgICAgICB2YXIgY2hhbm5lbCA9IFNFUlZFUlsieGVwZyJdWyJlcGdNYXBwaW5nIl1baWRdOwogICAgICAgIGNoYW5uZWxbIngtYWN0aXZlIl0gPSBzdGF0dXM7CiAgICAgICAgc3dpdGNoIChjaGFubmVsWyJ4LWFjdGl2ZSJdKSB7CiAgICAgICAgICAgIGNhc2UgdHJ1ZToKICAgICAgICAgICAgICAgIGlmIChjaGFubmVsWyJ4LXhtbHR2LWZpbGUiXSA9PSAiLSIgfHwgY2hhbm5lbFsieC1tYXBwaW5nIl0gPT0gIi0iKSB7CiAgICAgICAgICAgICAgICAgICAgaWYgKEJVTEtfRURJVCA9PSBmYWxzZSkgewogICAgICAgICAgICAgICAgICAgICAgICBhbGVydChjaGFubmVsWyJ4LW5hbWUiXSArICI6IE1pc3NpbmcgWE1MVFYgZmlsZSAvIGNoYW5uZWwiKTsKICAgICAgICAgICAgICAgICAgICAgICAgY2hlY2tib3guY2hlY2tlZCA9IGZhbHNlOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBjaGFubmVsWyJ4LWFjdGl2ZSJdID0gZmFsc2U7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSBmYWxzZToKICAgICAgICAgICAgICAgIC8vIGNvZGUuLi4KICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgIH0KICAgICAgICBpZiAoY2hhbm5lbFsieC1hY3RpdmUiXSA9PSBmYWxzZSkgewogICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuY2xhc3NOYW1lID0gIm5vdEFjdGl2ZUVQRyI7CiAgICAgICAgfQogICAgICAgIGVsc2UgewogICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuY2xhc3NOYW1lID0gImFjdGl2ZUVQRyI7CiAgICAgICAgfQogICAgfSk7Cn0KZnVuY3Rpb24gcmVzdG9yZSgpIHsKICAgIGlmIChkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndXBsb2FkJykpIHsKICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndXBsb2FkJykucmVtb3ZlKCk7CiAgICB9CiAgICB2YXIgcmVzdG9yZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIklOUFVUIik7CiAgICByZXN0b3JlLnNldEF0dHJpYnV0ZSgidHlwZSIsICJmaWxlIik7CiAgICByZXN0b3JlLnNldEF0dHJpYnV0ZSgiY2xhc3MiLCAibm90VmlzaWJsZSIpOwogICAgcmVzdG9yZS5zZXRBdHRyaWJ1dGUoIm5hbWUiLCAiIik7CiAgICByZXN0b3JlLmlkID0gInVwbG9hZCI7CiAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHJlc3RvcmUpOwogICAgcmVzdG9yZS5jbGljaygpOwogICAgcmVzdG9yZS5vbmNoYW5nZSA9IGZ1bmN0aW9uICgpIHsKICAgICAgICB2YXIgZmlsZW5hbWUgPSByZXN0b3JlLmZpbGVzWzBdLm5hbWU7CiAgICAgICAgdmFyIGNoZWNrID0gY29uZmlybSgiRmlsZTogIiArIGZpbGVuYW1lICsgIlxue3suY29uZmlybS5yZXN0b3JlfX0iKTsKICAgICAgICBpZiAoY2hlY2sgPT0gdHJ1ZSkgewogICAgICAgICAgICB2YXIgcmVhZGVyID0gbmV3IEZpbGVSZWFkZXIoKTsKICAgICAgICAgICAgdmFyIGZpbGUgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdpbnB1dFt0eXBlPWZpbGVdJykuZmlsZXNbMF07CiAgICAgICAgICAgIGlmIChmaWxlKSB7CiAgICAgICAgICAgICAgICByZWFkZXIucmVhZEFzRGF0YVVSTChmaWxlKTsKICAgICAgICAgICAgICAgIHJlYWRlci5vbmxvYWQgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgICAgICAgICAgICAgY29uc29sZS5sb2cocmVhZGVyLnJlc3VsdCk7CiAgICAgICAgICAgICAgICAgICAgdmFyIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgICAgICAgICAgICAgICAgICAgdmFyIGNtZCA9ICJ4dGV2ZVJlc3RvcmUiOwogICAgICAgICAgICAgICAgICAgIGRhdGFbImJhc2U2NCJdID0gcmVhZGVyLnJlc3VsdDsKICAgICAgICAgICAgICAgICAgICB2YXIgc2VydmVyID0gbmV3IFNlcnZlcihjbWQpOwogICAgICAgICAgICAgICAgICAgIHNlcnZlci5yZXF1ZXN0KGRhdGEpOwogICAgICAgICAgICAgICAgfTsKICAgICAgICAgICAgfQogICAgICAgICAgICBlbHNlIHsKICAgICAgICAgICAgICAgIGFsZXJ0KCJGaWxlIGNvdWxkIG5vdCBiZSBsb2FkZWQiKTsKICAgICAgICAgICAgfQogICAgICAgICAgICByZXN0b3JlLnJlbW92ZSgpOwogICAgICAgICAgICByZXR1cm47CiAgICAgICAgfQogICAgfTsKICAgIHJldHVybjsKfQpmdW5jdGlvbiB1cGxvYWRMb2dvKCkgewogICAgaWYgKGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd1cGxvYWQnKSkgewogICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd1cGxvYWQnKS5yZW1vdmUoKTsKICAgIH0KICAgIHZhciB1cGxvYWQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJJTlBVVCIpOwogICAgdXBsb2FkLnNldEF0dHJpYnV0ZSgidHlwZSIsICJmaWxlIik7CiAgICB1cGxvYWQuc2V0QXR0cmlidXRlKCJjbGFzcyIsICJub3RWaXNpYmxlIik7CiAgICB1cGxvYWQuc2V0QXR0cmlidXRlKCJuYW1lIiwgIiIpOwogICAgdXBsb2FkLmlkID0gInVwbG9hZCI7CiAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHVwbG9hZCk7CiAgICB1cGxvYWQuY2xpY2soKTsKICAgIHVwbG9hZC5vbmJsdXIgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgYWxlcnQoKTsKICAgIH07CiAgICB1cGxvYWQub25jaGFuZ2UgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgdmFyIGZpbGVuYW1lID0gdXBsb2FkLmZpbGVzWzBdLm5hbWU7CiAgICAgICAgdmFyIHJlYWRlciA9IG5ldyBGaWxlUmVhZGVyKCk7CiAgICAgICAgdmFyIGZpbGUgPSBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdpbnB1dFt0eXBlPWZpbGVdJykuZmlsZXNbMF07CiAgICAgICAgaWYgKGZpbGUpIHsKICAgICAgICAgICAgcmVhZGVyLnJlYWRBc0RhdGFVUkwoZmlsZSk7CiAgICAgICAgICAgIHJlYWRlci5vbmxvYWQgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgICAgICAgICBjb25zb2xlLmxvZyhyZWFkZXIucmVzdWx0KTsKICAgICAgICAgICAgICAgIHZhciBkYXRhID0gbmV3IE9iamVjdCgpOwogICAgICAgICAgICAgICAgdmFyIGNtZCA9ICJ1cGxvYWRMb2dvIjsKICAgICAgICAgICAgICAgIGRhdGFbImJhc2U2NCJdID0gcmVhZGVyLnJlc3VsdDsKICAgICAgICAgICAgICAgIGRhdGFbImZpbGVuYW1lIl0gPSBmaWxlLm5hbWU7CiAgICAgICAgICAgICAgICB2YXIgc2VydmVyID0gbmV3IFNlcnZlcihjbWQpOwogICAgICAgICAgICAgICAgc2VydmVyLnJlcXVlc3QoZGF0YSk7CiAgICAgICAgICAgICAgICB2YXIgdXBkYXRlTG9nbyA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd1cGRhdGUtaWNvbicpOwogICAgICAgICAgICAgICAgdXBkYXRlTG9nby5jaGVja2VkID0gZmFsc2U7CiAgICAgICAgICAgICAgICB1cGRhdGVMb2dvLmNsYXNzTmFtZSA9ICJjaGFuZ2VkIjsKICAgICAgICAgICAgfTsKICAgICAgICB9CiAgICAgICAgZWxzZSB7CiAgICAgICAgICAgIGFsZXJ0KCJGaWxlIGNvdWxkIG5vdCBiZSBsb2FkZWQiKTsKICAgICAgICB9CiAgICAgICAgdXBsb2FkLnJlbW92ZSgpOwogICAgICAgIHJldHVybjsKICAgIH07Cn0KZnVuY3Rpb24gY2hlY2tVbmRvKGtleSkgewogICAgc3dpdGNoIChrZXkpIHsKICAgICAgICBjYXNlICJlcGdNYXBwaW5nIjoKICAgICAgICAgICAgaWYgKFVORE8uaGFzT3duUHJvcGVydHkoa2V5KSkgewogICAgICAgICAgICAgICAgU0VSVkVSWyJ4ZXBnIl1ba2V5XSA9IEpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkoVU5ET1trZXldKSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICBVTkRPW2tleV0gPSBKU09OLnBhcnNlKEpTT04uc3RyaW5naWZ5KFNFUlZFUlsieGVwZyJdW2tleV0pKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBicmVhazsKICAgICAgICBkZWZhdWx0OgogICAgICAgICAgICBicmVhazsKICAgIH0KICAgIHJldHVybjsKfQpmdW5jdGlvbiBzb3J0U2VsZWN0KGVsZW0pIHsKICAgIHZhciB0bXBBcnkgPSBbXTsKICAgIHZhciBzZWxlY3RlZFZhbHVlID0gZWxlbVtlbGVtLnNlbGVjdGVkSW5kZXhdLnZhbHVlOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBlbGVtLm9wdGlvbnMubGVuZ3RoOyBpKyspCiAgICAgICAgdG1wQXJ5LnB1c2goZWxlbS5vcHRpb25zW2ldKTsKICAgIHRtcEFyeS5zb3J0KGZ1bmN0aW9uIChhLCBiKSB7IHJldHVybiAoYS50ZXh0IDwgYi50ZXh0KSA/IC0xIDogMTsgfSk7CiAgICB3aGlsZSAoZWxlbS5vcHRpb25zLmxlbmd0aCA+IDApCiAgICAgICAgZWxlbS5vcHRpb25zWzBdID0gbnVsbDsKICAgIHZhciBuZXdTZWxlY3RlZEluZGV4ID0gMDsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdG1wQXJ5Lmxlbmd0aDsgaSsrKSB7CiAgICAgICAgZWxlbS5vcHRpb25zW2ldID0gdG1wQXJ5W2ldOwogICAgICAgIGlmIChlbGVtLm9wdGlvbnNbaV0udmFsdWUgPT0gc2VsZWN0ZWRWYWx1ZSkKICAgICAgICAgICAgbmV3U2VsZWN0ZWRJbmRleCA9IGk7CiAgICB9CiAgICBlbGVtLnNlbGVjdGVkSW5kZXggPSBuZXdTZWxlY3RlZEluZGV4OyAvLyBTZXQgbmV3IHNlbGVjdGVkIGluZGV4IGFmdGVyIHNvcnRpbmcKICAgIHJldHVybjsKfQpmdW5jdGlvbiB1cGRhdGVMb2coKSB7CiAgICBjb25zb2xlLmxvZygiVE9LRU4iKTsKICAgIHZhciBzZXJ2ZXIgPSBuZXcgU2VydmVyKCJ1cGRhdGVMb2ciKTsKICAgIHNlcnZlci5yZXF1ZXN0KG5ldyBPYmplY3QoKSk7Cn0K" + webUI["html/js/menu.js"] = "CmZ1bmN0aW9uIHNldE1lbnVJdGVtKCkgewoKICBtZW51ID0gbmV3IE9iamVjdCgpOwogIHN1Yk1lbnUgPSBuZXcgT2JqZWN0KCk7CgogIHZhciBtZW51X20zdSA9IG5ldyBPYmplY3QoKTsKICBtZW51X20zdVsiX21lbnVUeXBlIl0gICAgICAgPSAiaW5wdXRBcnJheSI7CiAgbWVudV9tM3VbIl9lbGVtZW50Il0gICAgICAgID0gIkxJIjsKICBtZW51X20zdVsiX2NvbmZpZ0tleSJdICAgICAgPSAiZmlsZXMubTN1IjsKICBtZW51X20zdVsiX3RleHQiXSAgICAgICAgICAgPSAiUGxheWxpc3QiOwogIG1lbnVfbTN1WyJfaWNvbiJdICAgICAgICAgICA9ICJpbWcvbTN1LnBuZyI7CiAgbWVudV9tM3VbIl9oZWFkbGluZSJdICAgICAgID0gIlBsYXlsaXN0czogTG9jYWwgb3IgcmVtb3RlIjsKICBtZW51X20zdVsiX3VzYWdlIl0gICAgICAgICAgPSAiPGI+SW5mbzwvYj48YnI+QXZhaWxhYmlsaXR5OiBGaWxlIGF2YWlsYWJpbGl0eSBpbiBwZXJjZW50PGJyPlN0cmVhbXM6ICAgICAgTnVtYmVyIG9mIHN0cmVhbXMgaW4gdGhlIGZpbGUuPGJyPmdyb3VwLXRpdGxlOiAgU3RyZWFtcyB0aGF0IGFyZSBhc3NpZ25lZCB0byBhIGdyb3VwLiBTaW1wbGlmaWVzIGZpbHRlcmluZyBzdHJlYW1zPGJyPnR2Zy1pZDogICAgICAgVGhpcyBJRCBpcyB1c2VkIGZvciBhdXRvbWF0aWMgbWFwcGluZywgbXVzdCBtYXRjaCB3aXRoIHRoZSBjaGFubmVsIElEIGluIHRoZSBYTUxUViBmaWxlLjxicj5VbmlxdWUgSUQ6ICAgIFN0cmVhbXMgd2l0aCBhIHVuaXF1ZSBJRCB0byBpZGVudGlmeSB0aGVtLiBBbGxvd3MgY2hhbm5lbCBuYW1lIGNoYW5nZXMgaW4gdGhlIE0zVSB3aXRob3V0IGxvc2luZyB0aGUgWE1MVFYgbWFwcGluZyAoUFBWIC8gbGl2ZSBldmVudHMpLjxicj48YnI+PGI+VXNhZ2UgTTNVOjwvYj48YnI+UmVtb3RlIHBsYXlsaXN0OiBodHRwOi8veW91ci5pcHR2LnByb3ZpZGVyLmNvbS9maWxlLm0zdTxicj5Mb2NhbCAgcGxheWxpc3Q6IC9wYXRoL3RvL2ZpbGUubTN1PGJyPjxicj48Yj5Vc2FnZSBIREhvbWVSdW46PC9iPjxicj5JUDogMTkyLjE2OC4xLjEwOjUwMDQ8YnI+IgogIG1lbnVfbTN1WyJuYW1lIl0gICAgICAgICAgICA9ICJmaWxlIjsKICBtZW51X20zdVsiaWQiXSAgICAgICAgICAgICAgPSAiZmlsZSI7CiAgbWVudV9tM3VbInZhbHVlIl0gICAgICAgICAgID0gbWVudV9tM3VbIm5hbWUiXTsKICBtZW51X20zdVsicGxhY2Vob2xkZXIiXSAgICAgPSAiUGxheWxpc3Q6IGxvY2FsIG9yIHJlbW90ZSI7CiAgbWVudV9tM3VbIm9uY2xpY2siXSAgICAgICAgID0gImphdmFzY3JpcHQ6IHRvZ2dsZU1lbnUodGhpcyk7IjsKICBtZW51X20zdVsiY2xhc3MiXSAgICAgICAgICAgPSAibWVudS1ub3RBY3RpdmUiOwoKCiAgdmFyIG1lbnVfZmlsdGVyID0gbmV3IE9iamVjdCgpOwogIG1lbnVfZmlsdGVyWyJfbWVudVR5cGUiXSAgICA9ICJpbnB1dEFycmF5IjsKICBtZW51X2ZpbHRlclsiX2VsZW1lbnQiXSAgICAgPSAiTEkiOwogIG1lbnVfZmlsdGVyWyJfY29uZmlnS2V5Il0gICA9ICJmaWx0ZXIiOwogIG1lbnVfZmlsdGVyWyJfdGV4dCJdICAgICAgICA9ICJGaWx0ZXIiOwogIG1lbnVfZmlsdGVyWyJfaWNvbiJdICAgICAgICA9ICJpbWcvZmlsdGVyLnBuZyI7CiAgbWVudV9maWx0ZXJbIl9oZWFkbGluZSJdICAgID0gIkZpbHRlciBieSBNM1UgcGFyYW1ldGVycywgZS5nLiBncm91cC10aXRsZSI7CiAgbWVudV9maWx0ZXJbIl91c2FnZSJdICAgICAgID0gIjxiPlVzYWdlOjwvYj48YnI+U3BvcnQgLSBBbGwgc3BvcnRzIGNoYW5uZWxzPGJyPlNwb3J0IHtIRH0gLSBBbGwgSEQgc3BvcnRzIGNoYW5uZWxzPGJyPlNwb3J0IHtIRH0gIXtFUyxERX0gLSBBbGwgSEQgc3BvcnRzIGNoYW5uZWxzLCBidXQgbm8gU3BhbmlzaCBhbmQgR2VybWFuPGJyPjxicj5UbyBmaWx0ZXIgdGhlIHN0cmVhbXMgb2YgYSBIREhvbWVSdW4sIHRoZSBwbGF5bGlzdCBuYW1lIGNhbiBiZSBlbnRlcmVkOjxicj5NeSB0dW5lciB7SER9IgogIC8vbWVudV9maWx0ZXJbIl91c2FnZSJdICAgICAgID0gIjxiPlVzYWdlOjwvYj48YnI+QWxsIHNwb3J0cyBjaGFubmVsczogU3BvcnQ8YnI+QWxsIEhEIHNwb3J0cyBjaGFubmVsczogU3BvcnQge0hEfTxicj5BbGwgSEQgc3BvcnRzIGNoYW5uZWxzLCBidXQgbm8gU3BhbmlzaCBhbmQgR2VybWFuOiBTcG9ydCB7SER9ICF7RVMsREV9IgogIG1lbnVfZmlsdGVyWyJuYW1lIl0gICAgICAgICA9ICJmaWx0ZXIiOwogIG1lbnVfZmlsdGVyWyJpZCJdICAgICAgICAgICA9ICJNM1UiOwogIG1lbnVfZmlsdGVyWyJ2YWx1ZSJdICAgICAgICA9IG1lbnVfZmlsdGVyWyJuYW1lIl07CiAgbWVudV9maWx0ZXJbInBsYWNlaG9sZGVyIl0gID0gIkZpbHRlciBzdHJlYW1zOiBTcG9ydCI7CiAgbWVudV9maWx0ZXJbIm9uY2xpY2siXSAgICAgID0gImphdmFzY3JpcHQ6IHRvZ2dsZU1lbnUodGhpcyk7IjsKICBtZW51X2ZpbHRlclsiY2xhc3MiXSAgICAgICAgPSAibWVudS1ub3RBY3RpdmUiOwoKICB2YXIgbWVudV9pZCA9IG5ldyBPYmplY3QoKTsKICBtZW51X2lkWyJfbWVudVR5cGUiXSAgICAgICAgPSAiaW5wdXRBcnJheSI7CiAgbWVudV9pZFsiX2VsZW1lbnQiXSAgICAgICAgID0gIkxJIjsKICBtZW51X2lkWyJfY29uZmlnS2V5Il0gICAgICAgPSAiaWQiOwogIG1lbnVfaWRbIl90ZXh0Il0gICAgICAgICAgICA9ICJQTVMgSUQiOwogIG1lbnVfaWRbIl9pY29uIl0gICAgICAgICAgICA9ICJpbWcvbnVtYmVyLnBuZyI7CiAgbWVudV9pZFsiX2hlYWRsaW5lIl0gICAgICAgID0gIlNldHVwIFBNUyBndWlkZSBudW1iZXIiOwogIG1lbnVfaWRbIl91c2FnZSJdICAgICAgICAgICA9ICdTb21lIHBsYXlsaXN0cyBoYXZlIHVuaXF1ZSBjaGFubmVsIElEcy48YnI+RW50ZXIgdGhlIGtleXdvcmQgb2YgdGhlIElELiBUaGUgY2hhbm5lbCBhc3NpZ25tZW50IGluIFBNUyB3aWxsIGNoYW5nZSBhcyBhIHJlc3VsdC48YnI+PGJyPmUuZy4gY2hhbm5lbElEPGJyPiNFWFRJTkY6MCB0eXBlPSJzdHJlYW0iIDxiPmNoYW5uZWxJZDwvYj49IjgxIiwgTXkgU3RyZWFtaW5nIENoYW5uZWwgSEQ8YnI+PGJyPk9ubHkgZW50ZXIgaGVyZSBpZiB5b3Uga25vdyB3aGF0IHlvdSBhcmUgZG9pbmchJwogIG1lbnVfaWRbIm5hbWUiXSAgICAgICAgICAgICA9ICJpZCI7CiAgbWVudV9pZFsiaWQiXSAgICAgICAgICAgICAgID0gImlkIjsKICBtZW51X2lkWyJ2YWx1ZSJdICAgICAgICAgICAgPSBtZW51X2lkWyJuYW1lIl07CiAgbWVudV9pZFsicGxhY2Vob2xkZXIiXSAgICAgID0gIlVuaXF1ZSBJRCBmcm9tIHRoZSBNM1UgZmlsZSI7CiAgbWVudV9pZFsib25jbGljayJdICAgICAgICAgID0gImphdmFzY3JpcHQ6IHRvZ2dsZU1lbnUodGhpcyk7IjsKICBtZW51X2lkWyJjbGFzcyJdICAgICAgICAgICAgPSAibWVudS1ub3RBY3RpdmUiOwoKCiAgdmFyIG1lbnVfeG1sdHYgPSBuZXcgT2JqZWN0KCk7CiAgbWVudV94bWx0dlsiX21lbnVUeXBlIl0gICAgID0gImlucHV0QXJyYXkiOwogIG1lbnVfeG1sdHZbIl9lbGVtZW50Il0gICAgICA9ICJMSSI7CiAgbWVudV94bWx0dlsiX2NvbmZpZ0tleSJdICAgID0gImZpbGVzLnhtbHR2IjsKICBtZW51X3htbHR2WyJfdGV4dCJdICAgICAgICAgPSAiWE1MVFYiOwogIG1lbnVfeG1sdHZbIl9pY29uIl0gICAgICAgICA9ICJpbWcveG1sdHYucG5nIjsKICBtZW51X3htbHR2WyJfaGVhZGxpbmUiXSAgICAgPSAiWE1MVFYgZmlsZXM6IExvY2FsIG9yIHJlbW90ZSI7CiAgbWVudV94bWx0dlsiX3VzYWdlIl0gICAgICAgID0gIjxiPkluZm86PC9iPjxicj5BdmFpbGFiaWxpdHk6IEZpbGUgYXZhaWxhYmlsaXR5IGluIHBlcmNlbnQ8YnI+Q2hhbm5lbHM6ICAgICBOdW1iZXIgb2YgY2hhbm5lbHMgaW4gdGhlIGZpbGU8YnI+UHJvZ3JhbXM6ICAgICBOdW1iZXIgb2YgRVBHIGRhdGE8YnI+PGJyPjxiPlVzYWdlOjwvYj48YnI+UmVtb3RlIFhNTFRWIGZpbGU6IGh0dHA6Ly95b3VyLmVwZy5wcm92aWRlci5jb20vZ3VpZGUueG1sPGJyPkxvY2FsICBYTUxUViBmaWxlOiAvcGF0aC90by9ndWlkZS54bWwiCiAgbWVudV94bWx0dlsibmFtZSJdICAgICAgICAgID0gInhtbHR2IjsKICBtZW51X3htbHR2WyJpZCJdICAgICAgICAgICAgPSAieG1sdHYiOwogIG1lbnVfeG1sdHZbInZhbHVlIl0gICAgICAgICA9IG1lbnVfeG1sdHZbIm5hbWUiXTsKICBtZW51X3htbHR2WyJwbGFjZWhvbGRlciJdICAgPSAiWE1MVFYgRmlsZTogbG9jYWwgb3IgcmVtb3RlIjsKICBtZW51X3htbHR2WyJvbmNsaWNrIl0gICAgICAgPSAiamF2YXNjcmlwdDogdG9nZ2xlTWVudSh0aGlzKTsiOwogIG1lbnVfeG1sdHZbImNsYXNzIl0gICAgICAgICA9ICJtZW51LW5vdEFjdGl2ZSI7CgogIG1lbnVfbWFwcGluZyA9IG5ldyBPYmplY3QoKTsKICBtZW51X21hcHBpbmdbIl9lbGVtZW50Il0gICA9ICJMSSI7CiAgbWVudV9tYXBwaW5nWyJfdGV4dCJdICAgICAgPSAiTWFwcGluZyI7CiAgbWVudV9tYXBwaW5nWyJfaWNvbiJdICAgICAgPSAiaW1nL21hcHBpbmcucG5nIjsKICBtZW51X21hcHBpbmdbIl9jb25maWdLZXkiXSA9ICJtYXBwaW5nIjsKICBtZW51X21hcHBpbmdbIl9oZWFkbGluZSJdICA9ICJYTUxUViBhc3NpZ25tZW50IGFuZCBzb3J0aW5nIG9mIGNoYW5uZWxzIjsKICBtZW51X21hcHBpbmdbImlkIl0gICAgICAgICA9ICJtYXBwaW5nIjsKICBtZW51X21hcHBpbmdbIm9uY2xpY2siXSAgICA9ICJqYXZhc2NyaXB0OiB0b2dnbGVNZW51KHRoaXMpOyI7CiAgbWVudV9tYXBwaW5nWyJjbGFzcyJdICAgICAgPSAibWVudS1ub3RBY3RpdmUgcGhvbmUiOwoKICBtZW51X3VzZXJzID0gbmV3IE9iamVjdCgpOwogIG1lbnVfdXNlcnNbIl9lbGVtZW50Il0gICA9ICJMSSI7CiAgbWVudV91c2Vyc1siX3RleHQiXSAgICAgID0gIlVzZXJzIjsKICBtZW51X3VzZXJzWyJfaWNvbiJdICAgICAgPSAiaW1nL3VzZXJzLnBuZyI7CiAgbWVudV91c2Vyc1siX2NvbmZpZ0tleSJdID0gInVzZXJzIjsKICBtZW51X3VzZXJzWyJfaGVhZGxpbmUiXSAgPSAiQWRtaW5pc3RyYXRpb24gb2YgdXNlcnMgYW5kIHBlcm1pc3Npb25zIjsKICBtZW51X3VzZXJzWyJpZCJdICAgICAgICAgPSAidXNlcnMiOwogIG1lbnVfdXNlcnNbIm9uY2xpY2siXSAgICA9ICJqYXZhc2NyaXB0OiB0b2dnbGVNZW51KHRoaXMpOyI7CiAgbWVudV91c2Vyc1siY2xhc3MiXSAgICAgID0gIm1lbnUtbm90QWN0aXZlIjsKICBtZW51X3VzZXJzWyJfdXNhZ2UiXSAgICAgPSAiPGI+QXV0aG9yaXphdGlvbiBncm91cHM6PC9iPjxicj5XRUI6IFVzZXJzIGNhbiBsb2cgaW4gdG8gdGhlIHdlYiBpbnRlcmZhY2U8YnI+UE1TOiBQcm9ncmFtcyBsaWtlIFBsZXggY2FuIGFjY2VzcyB0aGUgY2hhbm5lbCBsaXN0LiBMb2dpbiB2aWEgRFZSIElQOiB1c2VybmFtZTpwYXNzd29yZEB4dGV2ZS5pcDpwb3J0PGJyPk0zVTogQWxsb3dzIGNsaWVudHMgdG8gZG93bmxvYWQgdGhlIE0zVSBwbGF5bGlzdC48YnI+WE1MOiBBbGxvd3MgY2xpZW50cyB0byBkb3dubG9hZCB0aGUgWE1MVFYgZmlsZS48YnI+QVBJOiBBbGxvd3MgY2xpZW50cyB0byB1c2UgdGhlIEFQSSBpbnRlcmZhY2UuPGJyPjxicj4hISEgRm9yIFBNUyBhdXRoZW50aWNhdGlvbiwgb25seSB0aGUgZm9sbG93aW5nIHNwZWNpYWwgY2hhcmFjdGVycyBhcmUgdmFsaWQ6ICEkKCk9LiwtOjs8YnI+PGJyPlRoZSBpbmRpdmlkdWFsIGF1dGhlbnRpY2F0aW9uIGdyb3VwcyBjYW4gYmUgYWN0aXZhdGVkIC8gZGVhY3RpdmF0ZWQgaW4gdGhlIHNldHRpbmdzIG1lbnUuIgogIAogIG1lbnVfc2V0dGluZ3MgPSBuZXcgT2JqZWN0KCk7CiAgbWVudV9zZXR0aW5nc1siX2VsZW1lbnQiXSAgID0gIkxJIjsKICBtZW51X3NldHRpbmdzWyJfdGV4dCJdICAgICAgPSAiU2V0dGluZ3MiOwogIG1lbnVfc2V0dGluZ3NbIl9pY29uIl0gICAgICA9ICJpbWcvc2V0dGluZ3MucG5nIjsKICBtZW51X3NldHRpbmdzWyJfY29uZmlnS2V5Il0gPSAic2V0dGluZ3MiOwogIG1lbnVfc2V0dGluZ3NbIl9oZWFkbGluZSJdICA9ICJTZXR0aW5ncyI7CiAgbWVudV9zZXR0aW5nc1siX3N1Yk1lbnUiXSAgID0gIjcwMSw3MDIsNzAzLDcwNCw3MDUsNzA2LDcwNyw3MDgsNzk5LDcxMCw3MTEsNzEyLDcxMyw3MTQiOwogIG1lbnVfc2V0dGluZ3NbImlkIl0gICAgICAgICA9ICJzZXR0aW5ncyI7CiAgbWVudV9zZXR0aW5nc1sib25jbGljayJdICAgID0gImphdmFzY3JpcHQ6IHRvZ2dsZU1lbnUodGhpcyk7IjsKICBtZW51X3NldHRpbmdzWyJjbGFzcyJdICAgICAgPSAibWVudS1ub3RBY3RpdmUiOwoKICBtZW51X2xvZyA9IG5ldyBPYmplY3QoKTsKICBtZW51X2xvZ1siX2VsZW1lbnQiXSAgICAgICAgPSAiTEkiOwogIG1lbnVfbG9nWyJfdGV4dCJdICAgICAgICAgICA9ICJMb2ciOwogIG1lbnVfbG9nWyJfaWNvbiJdICAgICAgICAgICA9ICJpbWcvbG9nLnBuZyI7CiAgbWVudV9sb2dbIl9oZWFkbGluZSJdICAgICAgID0gIkxvZyI7CiAgbWVudV9sb2dbIl9jb25maWdLZXkiXSAgICAgID0gImxvZyI7CiAgbWVudV9sb2dbImlkIl0gICAgICAgICAgICAgID0gImxvZyI7CiAgbWVudV9sb2dbIm9uY2xpY2siXSAgICAgICAgID0gImphdmFzY3JpcHQ6IHRvZ2dsZU1lbnUodGhpcyk7IjsKICBtZW51X2xvZ1siY2xhc3MiXSAgICAgICAgICAgPSAibWVudS1ub3RBY3RpdmUiOwoKICBtZW51X2xvZ291dCA9IG5ldyBPYmplY3QoKTsKICBtZW51X2xvZ291dFsiX2VsZW1lbnQiXSAgID0gIkxJIjsKICBtZW51X2xvZ291dFsiX3RleHQiXSAgICAgID0gIkxvZ291dCI7CiAgbWVudV9sb2dvdXRbIl9pY29uIl0gICAgICA9ICJpbWcvbG9nb3V0LnBuZyI7CiAgbWVudV9sb2dvdXRbImlkIl0gICAgICAgICA9ICJsb2dvdXQiOwogIG1lbnVfbG9nb3V0WyJvbmNsaWNrIl0gICAgPSAiamF2YXNjcmlwdDogbG9nb3V0KCk7IjsKICBtZW51X2xvZ291dFsiY2xhc3MiXSAgICAgID0gIm1lbnUtbm90QWN0aXZlIjsKCiAgdmFyIG1lbnVfc2NoZWR1bGUgPSBuZXcgT2JqZWN0KCk7CiAgbWVudV9zY2hlZHVsZVsiX21lbnVUeXBlIl0gID0gImlucHV0QXJyYXkiOwogIG1lbnVfc2NoZWR1bGVbIl9lbGVtZW50Il0gICA9ICJMSSI7CiAgbWVudV9zY2hlZHVsZVsiX2NvbmZpZ0tleSJdID0gInVwZGF0ZSI7CiAgbWVudV9zY2hlZHVsZVsiX3RleHQiXSAgICAgID0gIlNjaGVkdWxlIjsKICBtZW51X3NjaGVkdWxlWyJfaWNvbiJdICAgICAgPSAiaW1nL3NjaGVkdWxlLnBuZyI7CiAgbWVudV9zY2hlZHVsZVsiX2hlYWRsaW5lIl0gID0gIlNjaGVkdWxlIGZvciB1cGRhdGluZyBNM1UsIFhNTFRWIGZpbGVzIGFuZCBjcmVhdGluZyBhIGxvY2FsIGJhY2t1cCI7CiAgbWVudV9zY2hlZHVsZVsiX3VzYWdlIl0gICAgID0gIjxiPlVzYWdlOjwvYj48YnI+MDgxNSA9IDg6MTUgYW08YnI+MTkzMCA9IDc6MzAgcG0iCiAgbWVudV9zY2hlZHVsZVsibmFtZSJdICAgICAgID0gInVwZGF0ZSI7CiAgbWVudV9zY2hlZHVsZVsiaWQiXSAgICAgICAgID0gInVwZGF0ZSI7CiAgbWVudV9zY2hlZHVsZVsidmFsdWUiXSAgICAgID0gbWVudV9pZFsibmFtZSJdOwogIG1lbnVfc2NoZWR1bGVbInBsYWNlaG9sZGVyIl09ICJ0aW1lIG9mIGRheSAoMjQtaG91ciBjbG9jaykiOwogIG1lbnVfc2NoZWR1bGVbIm9uY2xpY2siXSAgICA9ICJqYXZhc2NyaXB0OiB0b2dnbGVNZW51KHRoaXMpOyI7CiAgbWVudV9zY2hlZHVsZVsiY2xhc3MiXSAgICAgID0gIm1lbnUtbm90QWN0aXZlIjsKCiAgdmFyIG1lbnVfZmlsZXNVcGRhdGUgPSBuZXcgT2JqZWN0KCk7CiAgbWVudV9maWxlc1VwZGF0ZVsiX2VsZW1lbnQiXSA9ICJMSSI7CiAgbWVudV9maWxlc1VwZGF0ZVsiX21lbnVUeXBlIl0gICAgID0gImNoZWNrYm94IjsKICBtZW51X2ZpbGVzVXBkYXRlWyJfY29uZmlnS2V5Il0gICAgPSAiZmlsZXMudXBkYXRlIjsKICBtZW51X2ZpbGVzVXBkYXRlWyJfbGFiZWwiXSAgICAgICAgPSAiVXBkYXRlIHRoZSBwcm92aWRlciBmaWxlcyBhdCBzeXN0ZW0gc3RhcnR1cCI7CiAgbWVudV9maWxlc1VwZGF0ZVsiX2hlYWRsaW5lIl0gICAgID0gIlVwZGF0ZSB0aGUgcHJvdmlkZXIgZmlsZXMgYXQgc3lzdGVtIHN0YXJ0dXAiOwogIG1lbnVfZmlsZXNVcGRhdGVbIl91c2FnZSJdICAgICAgICA9ICJQbGF5bGlzdHMgYW5kIFhNTFRWIGZpbGVzIGFyZSB1cGRhdGVkIGJ5IHhUZVZlIGF0IHN5c3RlbSBzdGFydHVwLiIKICBtZW51X2ZpbGVzVXBkYXRlWyJuYW1lIl0gICAgICAgICAgPSAiZmlsZXMudXBkYXRlIjsKICBtZW51X2ZpbGVzVXBkYXRlWyJpZCJdICAgICAgICAgICAgPSAiZmlsZXMudXBkYXRlIjsKICBtZW51X2ZpbGVzVXBkYXRlWyJ2YWx1ZSJdICAgICAgICAgPSBtZW51X2ZpbGVzVXBkYXRlWyJuYW1lIl07CiAgbWVudV9maWxlc1VwZGF0ZVsib25jbGljayJdICAgICAgID0gImphdmFzY3JpcHQ6IHRvZ2dsZU1lbnUodGhpcyk7IjsKICBtZW51X2ZpbGVzVXBkYXRlWyJjbGFzcyJdICAgICAgICAgPSAibWVudS1ub3RBY3RpdmUiOwogIAogIHZhciBtZW51X3R1bmVyID0gbmV3IE9iamVjdCgpOwogIG1lbnVfdHVuZXJbIl9lbGVtZW50Il0gICAgICA9ICJMSSI7CiAgbWVudV90dW5lclsiX21lbnVUeXBlIl0gICAgID0gInNlbGVjdCI7CiAgbWVudV90dW5lclsiX2NvbmZpZ0tleSJdICAgID0gInR1bmVyIjsKICBtZW51X3R1bmVyWyJfbGFiZWwiXSAgICAgICAgPSAiQXZhaWxhYmxlIHR1bmVycyI7CiAgbWVudV90dW5lclsiX3RleHQiXSAgICAgICAgID0gIlR1bmVyIjsKICBtZW51X3R1bmVyWyJfaWNvbiJdICAgICAgICAgPSAiaW1nL3R1bmVyLnBuZyI7CiAgbWVudV90dW5lclsiX2hlYWRsaW5lIl0gICAgID0gIk51bWJlciBvZiB0dW5lcnMiOwogIG1lbnVfdHVuZXJbIl91c2FnZSJdICAgICAgICA9ICJUaGlzIHNldHRpbmcgaXMgb25seSB1c2VkIGJ5IFBsZXggYW5kIEVtYnkuPGJyPlRoZSBudW1iZXIgb2YgY29uY3VycmVudCBzdHJlYW1zIGFsbG93ZWQgYnkgdGhlIElQVFYgcHJvdmlkZXIuPGJyPkFmdGVyIGEgY2hhbmdlLCB4VGVWZSBtdXN0IGJlIGRlbGV0ZSBpbiB0aGUgUE1TIERWUiBzZXR0aW5ncyBhbmQgc2V0IHVwIGFnYWluLiIKICBtZW51X3R1bmVyWyJuYW1lIl0gICAgICAgICAgPSAidHVuZXIiOwogIG1lbnVfdHVuZXJbImlkIl0gICAgICAgICAgICA9ICJ0dW5lciI7CiAgbWVudV90dW5lclsidmFsdWUiXSAgICAgICAgID0gbWVudV90dW5lclsibmFtZSJdOwogIG1lbnVfdHVuZXJbInBsYWNlaG9sZGVyIl0gICA9ICJOdW1iZXIgb2YgdHVuZXJzIjsKICBtZW51X3R1bmVyWyJvbmNsaWNrIl0gICAgICAgPSAiamF2YXNjcmlwdDogdG9nZ2xlTWVudSh0aGlzKTsiOwogIG1lbnVfdHVuZXJbImNsYXNzIl0gICAgICAgICA9ICJtZW51LW5vdEFjdGl2ZSI7CgogIHZhciBvcHRpb25WYWx1ZXMgPSBuZXcgQXJyYXkoKTsKICBmb3IgKHZhciBpID0gMTsgaSA8PSAxMDA7IGkrKykgewogICAgb3B0aW9uVmFsdWVzLnB1c2goaSkKICB9CiAgbWVudV90dW5lclsiX29wdGlvblZhbHVlcyJdID0gb3B0aW9uVmFsdWVzOwogIAogIHZhciBtZW51X2VwZyA9IG5ldyBPYmplY3QoKTsKICBtZW51X2VwZ1siX2VsZW1lbnQiXSA9ICJMSSI7CiAgbWVudV9lcGdbIl9tZW51VHlwZSJdICAgICA9ICJzZWxlY3QiOwogIG1lbnVfZXBnWyJfY29uZmlnS2V5Il0gICAgPSAiZXBnU291cmNlIjsKICBtZW51X2VwZ1siX2xhYmVsIl0gICAgICAgID0gIlNlbGVjdGlvbiBvZiB0aGUgRVBHIHNvdXJjZSI7CiAgbWVudV9lcGdbIl90ZXh0Il0gICAgICAgICA9ICJFUEcgc291cmNlIjsKICBtZW51X2VwZ1siX2hlYWRsaW5lIl0gICAgID0gIlNlbGVjdGlvbiBvZiB0aGUgRVBHIHNvdXJjZSI7CiAgbWVudV9lcGdbIl91c2FnZSJdICAgICAgICA9ICJQTVM6ICAgVXNlIEVQRyBkYXRhIGZyb20gUGxleCBvciBFbWJ5Ljxicj5YRVBHOiAgVXNlIG9mIGV4dGVybmFsIEVQRyBkYXRhIChYTUxUVikuPGJyPiAgICAgICBTZXZlcmFsIFhNTFRWIHNvdXJjZXMgcG9zc2libGUuPGJyPiAgICAgICBBbGxvd3MgZWRpdGluZyBhbmQgb3JkZXIgY2hhbm5lbHMuPGJyPiAgICAgICBNM1UgLyBYTUxUViBleHBvcnQgKEhUVFAgbGluayBmb3IgSVBUViBhcHBzKS4iCiAgbWVudV9lcGdbIm5hbWUiXSAgICAgICAgICA9ICJlcGdTb3VyY2UiOwogIG1lbnVfZXBnWyJpZCJdICAgICAgICAgICAgPSAiZXBnU291cmNlIjsKICBtZW51X2VwZ1sidmFsdWUiXSAgICAgICAgID0gbWVudV9lcGdbIm5hbWUiXTsKICBtZW51X2VwZ1sicGxhY2Vob2xkZXIiXSAgID0gIkVQRyBzb3VyY2UiOwogIG1lbnVfZXBnWyJvbmNsaWNrIl0gICAgICAgPSAiamF2YXNjcmlwdDogdG9nZ2xlTWVudSh0aGlzKTsiOwogIG1lbnVfZXBnWyJjbGFzcyJdICAgICAgICAgPSAibWVudS1ub3RBY3RpdmUiOwogIG1lbnVfZXBnWyJfb3B0aW9uVmFsdWVzIl0gPSBuZXcgQXJyYXkoIlBNUyIsICJYRVBHIik7CgogIHZhciBtZW51X3hlcGcgPSBuZXcgT2JqZWN0KCk7CiAgbWVudV94ZXBnWyJfZWxlbWVudCJdID0gIkxJIjsKICBtZW51X3hlcGdbIl9tZW51VHlwZSJdICAgICA9ICJjaGVja2JveCI7CiAgbWVudV94ZXBnWyJfY29uZmlnS2V5Il0gICAgPSAieHRldmVBdXRvVXBkYXRlIjsKICBtZW51X3hlcGdbIl9sYWJlbCJdICAgICAgICA9ICJBdXRvbWF0aWMgdXBkYXRlIG9mIHhUZVZlIjsKICBtZW51X3hlcGdbIl9oZWFkbGluZSJdICAgICA9ICJBdXRvbWF0aWMgdXBkYXRlIG9mIHhUZVZlIjsKICBtZW51X3hlcGdbIl91c2FnZSJdICAgICAgICA9ICJJZiBhIG5ldyB2ZXJzaW9uIG9mIHhUZVZlIGlzIGF2YWlsYWJsZSwgaXQgd2lsbCBiZSBhdXRvbWF0aWNhbGx5IGluc3RhbGxlZC4iCiAgbWVudV94ZXBnWyJuYW1lIl0gICAgICAgICAgPSAieHRldmVBdXRvVXBkYXRlIjsKICBtZW51X3hlcGdbImlkIl0gICAgICAgICAgICA9ICJ4dGV2ZUF1dG9VcGRhdGUiOwogIG1lbnVfeGVwZ1sidmFsdWUiXSAgICAgICAgID0gbWVudV94ZXBnWyJuYW1lIl07CiAgbWVudV94ZXBnWyJvbmNsaWNrIl0gICAgICAgPSAiamF2YXNjcmlwdDogdG9nZ2xlTWVudSh0aGlzKTsiOwogIG1lbnVfeGVwZ1siY2xhc3MiXSAgICAgICAgID0gIm1lbnUtbm90QWN0aXZlIjsKCiAgdmFyIG1lbnVfYXV0b0JhY2t1cFBhdGggPSBuZXcgT2JqZWN0KCk7CiAgbWVudV9hdXRvQmFja3VwUGF0aFsiX2VsZW1lbnQiXSAgID0gIkxJIjsKICBtZW51X2F1dG9CYWNrdXBQYXRoWyJfbWVudVR5cGUiXSAgPSAic2luZ2xlSW5wdXQiOwogIG1lbnVfYXV0b0JhY2t1cFBhdGhbIl9jb25maWdLZXkiXSA9ICJiYWNrdXAucGF0aCI7CiAgbWVudV9hdXRvQmFja3VwUGF0aFsiX2xhYmVsIl0gICAgID0gIkxvY2F0aW9uIGZvciBhdXRvbWF0aWMgYmFja3VwcyI7CiAgbWVudV9hdXRvQmFja3VwUGF0aFsiX2hlYWRsaW5lIl0gID0gIkxvY2F0aW9uIGZvciBhdXRvbWF0aWMgYmFja3VwcyI7CiAgbWVudV9hdXRvQmFja3VwUGF0aFsiX3VzYWdlIl0gICAgID0gIkJlZm9yZSBhbnkgdXBkYXRlIG9mIHRoZSBwcm92aWRlciBkYXRhIGJ5IHRoZSBzY2hlZHVsZSwgeFRlVmUgY3JlYXRlcyBhIGJhY2t1cC4gVGhlIHBhdGggZm9yIHRoZSBhdXRvbWF0aWMgYmFja3VwcyBjYW4gYmUgY2hhbmdlZC4geFRlVmUgcmVxdWlyZXMgd3JpdGUgcGVybWlzc2lvbiBmb3IgdGhpcyBmb2xkZXIuIgogIG1lbnVfYXV0b0JhY2t1cFBhdGhbIm5hbWUiXSAgICAgICA9ICJiYWNrdXAucGF0aCI7CiAgbWVudV9hdXRvQmFja3VwUGF0aFsiaWQiXSAgICAgICAgID0gImJhY2t1cC5wYXRoIjsKICBtZW51X2F1dG9CYWNrdXBQYXRoWyJ2YWx1ZSJdICAgICAgPSBtZW51X2F1dG9CYWNrdXBQYXRoWyJuYW1lIl07CiAgbWVudV9hdXRvQmFja3VwUGF0aFsib25jbGljayJdICAgID0gImphdmFzY3JpcHQ6IHRvZ2dsZU1lbnUodGhpcyk7IjsKICBtZW51X2F1dG9CYWNrdXBQYXRoWyJjbGFzcyJdICAgICAgPSAibWVudS1ub3RBY3RpdmUiOwoKICB2YXIgbWVudV9hdXRvQmFja3VwS2VlcCA9IG5ldyBPYmplY3QoKTsKICBtZW51X2F1dG9CYWNrdXBLZWVwWyJfZWxlbWVudCJdICAgPSAiTEkiOwogIG1lbnVfYXV0b0JhY2t1cEtlZXBbIl9tZW51VHlwZSJdICA9ICJzZWxlY3QiOwogIG1lbnVfYXV0b0JhY2t1cEtlZXBbIl9jb25maWdLZXkiXSA9ICJiYWNrdXAua2VlcCI7CiAgbWVudV9hdXRvQmFja3VwS2VlcFsiX3RleHQiXSAgICAgID0gIktlZXAiOwogIG1lbnVfYXV0b0JhY2t1cEtlZXBbIl9sYWJlbCJdICAgICA9ICJOdW1iZXIgb2YgYmFja3VwcyB0byBrZWVwIjsKICBtZW51X2F1dG9CYWNrdXBLZWVwWyJfaGVhZGxpbmUiXSAgPSAiTnVtYmVyIG9mIGJhY2t1cHMgdG8ga2VlcCI7CiAgbWVudV9hdXRvQmFja3VwS2VlcFsiX3VzYWdlIl0gICAgID0gIiIKICBtZW51X2F1dG9CYWNrdXBLZWVwWyJuYW1lIl0gICAgICAgPSAiYmFja3VwLmtlZXAiOwogIG1lbnVfYXV0b0JhY2t1cEtlZXBbImlkIl0gICAgICAgICA9ICJiYWNrdXAua2VlcCI7CiAgbWVudV9hdXRvQmFja3VwS2VlcFsidmFsdWUiXSAgICAgID0gbWVudV9hdXRvQmFja3VwS2VlcFsibmFtZSJdOwogIG1lbnVfYXV0b0JhY2t1cEtlZXBbIm9uY2xpY2siXSAgICA9ICJqYXZhc2NyaXB0OiB0b2dnbGVNZW51KHRoaXMpOyI7CiAgbWVudV9hdXRvQmFja3VwS2VlcFsiY2xhc3MiXSAgICAgID0gIm1lbnUtbm90QWN0aXZlIjsKCiAgdmFyIG9wdGlvblZhbHVlcyA9IG5ldyBBcnJheSg1LCAxMCwgMjAsIDMwLCA0MCwgNTApOwogIG1lbnVfYXV0b0JhY2t1cEtlZXBbIl9vcHRpb25WYWx1ZXMiXSA9IG9wdGlvblZhbHVlczsKCgogIHZhciBtZW51X2J1ZmZlciA9IG5ldyBPYmplY3QoKTsKICBtZW51X2J1ZmZlclsiX2VsZW1lbnQiXSA9ICJMSSI7CiAgbWVudV9idWZmZXJbIl9tZW51VHlwZSJdICAgICA9ICJjaGVja2JveCI7CiAgbWVudV9idWZmZXJbIl9jb25maWdLZXkiXSAgICA9ICJidWZmZXIiOwogIG1lbnVfYnVmZmVyWyJfbGFiZWwiXSAgICAgICAgPSAiU3RyZWFtIGJ1ZmZlcmluZyBbRXhwZXJpbWVudGFsXSI7CiAgbWVudV9idWZmZXJbIl9oZWFkbGluZSJdICAgICA9ICJTdHJlYW0gYnVmZmVyaW5nIFtFeHBlcmltZW50YWxdIjsKICBtZW51X2J1ZmZlclsiX3VzYWdlIl0gICAgICAgID0gIldpdGggYWN0aXZhdGVkIGJ1ZmZlciwgc3RyZWFtcyBjYW4gYmUgcGxheWVkIGFuZCByZWNvcmRlZCBtb3JlIGZsdWVudGx5Ljxicj5UaGUgc3RyZWFtIGlzIHBhc3NlZCBmcm9tIHhUZVZlIHRvIFBsZXggLyBFbWJ5IgogIG1lbnVfYnVmZmVyWyJuYW1lIl0gICAgICAgICAgPSAiYnVmZmVyIjsKICBtZW51X2J1ZmZlclsiaWQiXSAgICAgICAgICAgID0gImJ1ZmZlciI7CiAgbWVudV9idWZmZXJbInZhbHVlIl0gICAgICAgICA9IG1lbnVfYnVmZmVyWyJuYW1lIl07CiAgbWVudV9idWZmZXJbIm9uY2xpY2siXSAgICAgICA9ICJqYXZhc2NyaXB0OiB0b2dnbGVNZW51KHRoaXMpOyI7CiAgbWVudV9idWZmZXJbImNsYXNzIl0gICAgICAgICA9ICJtZW51LW5vdEFjdGl2ZSI7CgogIHZhciBtZW51X2FwaSA9IG5ldyBPYmplY3QoKTsKICBtZW51X2FwaVsiX2VsZW1lbnQiXSA9ICJMSSI7CiAgbWVudV9hcGlbIl9tZW51VHlwZSJdICAgICA9ICJjaGVja2JveCI7CiAgbWVudV9hcGlbIl9jb25maWdLZXkiXSAgICA9ICJhcGkiOwogIG1lbnVfYXBpWyJfbGFiZWwiXSAgICAgICAgPSAiQVBJIGludGVyZmFjZSI7CiAgbWVudV9hcGlbIl9oZWFkbGluZSJdICAgICA9ICJBUEkgaW50ZXJmYWNlIjsKICBtZW51X2FwaVsiX3VzYWdlIl0gICAgICAgID0gJ1ZpYSBBUEkgaW50ZXJmYWNlIGl0IGlzIHBvc3NpYmxlIHRvIHNlbmQgY29tbWFuZHMgdG8geFRlVmUuIEFQSSBkb2N1bWVudGF0aW9uIGlzIGF2YWlsYWJsZSA8YSBocmVmPSJodHRwczovL3h0ZXZlLmRlP3Njcm9sbD1hcGkiPmhlcmU8L2E+ICcKICAvL21lbnVfYXBpWyJfdXNhZ2UiXSAgICAgICAgPSAnVmlhIEFQSSBpbnRlcmZhY2UgaXQgaXMgcG9zc2libGUgdG8gc2VuZCBjb21tYW5kcyB0byB4VGVWZS4gQVBJIGRvY3VtZW50YXRpb24gaXMgYXZhaWxhYmxlIDxhIGhyZWY9Imh0dHA6Ly9sb2NhbGhvc3Q6MTMxMz9zY3JvbGw9YXBpIj5oZXJlPC9hPiAnCiAgbWVudV9hcGlbIm5hbWUiXSAgICAgICAgICA9ICJhcGkiOwogIG1lbnVfYXBpWyJpZCJdICAgICAgICAgICAgPSAiYXBpIjsKICBtZW51X2FwaVsidmFsdWUiXSAgICAgICAgID0gbWVudV9hcGlbIm5hbWUiXTsKICBtZW51X2FwaVsib25jbGljayJdICAgICAgID0gImphdmFzY3JpcHQ6IHRvZ2dsZU1lbnUodGhpcyk7IjsKICBtZW51X2FwaVsiY2xhc3MiXSAgICAgICAgID0gIm1lbnUtbm90QWN0aXZlIjsKCiAgdmFyIG1lbnVfYXV0aGVudGljYXRpb25XZWIgPSBuZXcgT2JqZWN0KCk7CiAgbWVudV9hdXRoZW50aWNhdGlvbldlYlsiX2VsZW1lbnQiXSA9ICJMSSI7CiAgbWVudV9hdXRoZW50aWNhdGlvbldlYlsiX21lbnVUeXBlIl0gICAgID0gImNoZWNrYm94IjsKICBtZW51X2F1dGhlbnRpY2F0aW9uV2ViWyJfY29uZmlnS2V5Il0gICAgPSAiYXV0aGVudGljYXRpb24ud2ViIjsKICBtZW51X2F1dGhlbnRpY2F0aW9uV2ViWyJfbGFiZWwiXSAgICAgICAgPSAiVXNlciBhdXRoZW50aWNhdGlvbiI7CiAgbWVudV9hdXRoZW50aWNhdGlvbldlYlsiX2hlYWRsaW5lIl0gICAgID0gIlVzZXIgYXV0aGVudGljYXRpb24iOwogIG1lbnVfYXV0aGVudGljYXRpb25XZWJbIl91c2FnZSJdICAgICAgICA9ICJBY2Nlc3MgdG8geFRlVmUgcmVxdWlyZXMgYXV0aGVudGljYXRpb24uIgogIG1lbnVfYXV0aGVudGljYXRpb25XZWJbIm5hbWUiXSAgICAgICAgICA9ICJhdXRoZW50aWNhdGlvbi53ZWIiOwogIG1lbnVfYXV0aGVudGljYXRpb25XZWJbImlkIl0gICAgICAgICAgICA9ICJhdXRoZW50aWNhdGlvbi53ZWIiOwogIG1lbnVfYXV0aGVudGljYXRpb25XZWJbInZhbHVlIl0gICAgICAgICA9IG1lbnVfYXV0aGVudGljYXRpb25XZWJbIm5hbWUiXTsKICBtZW51X2F1dGhlbnRpY2F0aW9uV2ViWyJvbmNsaWNrIl0gICAgICAgPSAiamF2YXNjcmlwdDogdG9nZ2xlTWVudSh0aGlzKTsiOwogIG1lbnVfYXV0aGVudGljYXRpb25XZWJbImNsYXNzIl0gICAgICAgICA9ICJtZW51LW5vdEFjdGl2ZSI7CiAgCiAgdmFyIG1lbnVfYXV0aGVudGljYXRpb25QbXMgPSBuZXcgT2JqZWN0KCk7CiAgbWVudV9hdXRoZW50aWNhdGlvblBtc1siX2VsZW1lbnQiXSA9ICJMSSI7CiAgbWVudV9hdXRoZW50aWNhdGlvblBtc1siX21lbnVUeXBlIl0gICAgID0gImNoZWNrYm94IjsKICBtZW51X2F1dGhlbnRpY2F0aW9uUG1zWyJfY29uZmlnS2V5Il0gICAgPSAiYXV0aGVudGljYXRpb24ucG1zIjsKICBtZW51X2F1dGhlbnRpY2F0aW9uUG1zWyJfbGFiZWwiXSAgICAgICAgPSAiUGxleCBhdXRoZW50aWNhdGlvbi4iOwogIG1lbnVfYXV0aGVudGljYXRpb25QbXNbIl9oZWFkbGluZSJdICAgICA9ICJQbGV4IGF1dGhlbnRpY2F0aW9uLiI7CiAgbWVudV9hdXRoZW50aWNhdGlvblBtc1siX3VzYWdlIl0gICAgICAgID0gIlBsZXggcmVxdWVzdHMgYXJlIG9ubHkgcG9zc2libGUgd2l0aCBhdXRoZW50aWNhdGlvbi48YnI+V2FybmluZyEhISBBZnRlciBhY3RpdmF0aW5nIHRoaXMgZnVuY3Rpb24geFRlVmUgbXVzdCBiZSBkZWxldGUgaW4gdGhlIFBNUyBEVlIgc2V0dGluZ3MgYW5kIHNldCB1cCBhZ2Fpbi4iCiAgbWVudV9hdXRoZW50aWNhdGlvblBtc1sibmFtZSJdICAgICAgICAgID0gImF1dGhlbnRpY2F0aW9uLnBtcyI7CiAgbWVudV9hdXRoZW50aWNhdGlvblBtc1siaWQiXSAgICAgICAgICAgID0gImF1dGhlbnRpY2F0aW9uLnBtcyI7CiAgbWVudV9hdXRoZW50aWNhdGlvblBtc1sidmFsdWUiXSAgICAgICAgID0gbWVudV9hdXRoZW50aWNhdGlvblBtc1sibmFtZSJdOwogIG1lbnVfYXV0aGVudGljYXRpb25QbXNbIm9uY2xpY2siXSAgICAgICA9ICJqYXZhc2NyaXB0OiB0b2dnbGVNZW51KHRoaXMpOyI7CiAgbWVudV9hdXRoZW50aWNhdGlvblBtc1siY2xhc3MiXSAgICAgICAgID0gIm1lbnUtbm90QWN0aXZlIjsKICAKICB2YXIgbWVudV9hdXRoZW50aWNhdGlvbk0zdSA9IG5ldyBPYmplY3QoKTsKICBtZW51X2F1dGhlbnRpY2F0aW9uTTN1WyJfZWxlbWVudCJdID0gIkxJIjsKICBtZW51X2F1dGhlbnRpY2F0aW9uTTN1WyJfbWVudVR5cGUiXSAgICAgPSAiY2hlY2tib3giOwogIG1lbnVfYXV0aGVudGljYXRpb25NM3VbIl9jb25maWdLZXkiXSAgICA9ICJhdXRoZW50aWNhdGlvbi5tM3UiOwogIG1lbnVfYXV0aGVudGljYXRpb25NM3VbIl9sYWJlbCJdICAgICAgICA9ICJNM1UgYXV0aGVudGljYXRpb24uIjsKICBtZW51X2F1dGhlbnRpY2F0aW9uTTN1WyJfaGVhZGxpbmUiXSAgICAgPSAiTTNVIGF1dGhlbnRpY2F0aW9uLiI7CiAgbWVudV9hdXRoZW50aWNhdGlvbk0zdVsiX3VzYWdlIl0gICAgICAgID0gIkRvd25sb2FkaW5nIHRoZSBNM1UgZmlsZSB2aWEgYW4gSFRUUCByZXF1ZXN0IGlzIG9ubHkgcG9zc2libGUgd2l0aCBhdXRoZW50aWNhdGlvbi4iCiAgbWVudV9hdXRoZW50aWNhdGlvbk0zdVsibmFtZSJdICAgICAgICAgID0gImF1dGhlbnRpY2F0aW9uLm0zdSI7CiAgbWVudV9hdXRoZW50aWNhdGlvbk0zdVsiaWQiXSAgICAgICAgICAgID0gImF1dGhlbnRpY2F0aW9uLm0zdSI7CiAgbWVudV9hdXRoZW50aWNhdGlvbk0zdVsidmFsdWUiXSAgICAgICAgID0gbWVudV9hdXRoZW50aWNhdGlvbk0zdVsibmFtZSJdOwogIG1lbnVfYXV0aGVudGljYXRpb25NM3VbIm9uY2xpY2siXSAgICAgICA9ICJqYXZhc2NyaXB0OiB0b2dnbGVNZW51KHRoaXMpOyI7CiAgbWVudV9hdXRoZW50aWNhdGlvbk0zdVsiY2xhc3MiXSAgICAgICAgID0gIm1lbnUtbm90QWN0aXZlIjsKICAKCiAgdmFyIG1lbnVfYXV0aGVudGljYXRpb25YbWwgPSBuZXcgT2JqZWN0KCk7CiAgbWVudV9hdXRoZW50aWNhdGlvblhtbFsiX2VsZW1lbnQiXSA9ICJMSSI7CiAgbWVudV9hdXRoZW50aWNhdGlvblhtbFsiX21lbnVUeXBlIl0gICAgID0gImNoZWNrYm94IjsKICBtZW51X2F1dGhlbnRpY2F0aW9uWG1sWyJfY29uZmlnS2V5Il0gICAgPSAiYXV0aGVudGljYXRpb24ueG1sIjsKICBtZW51X2F1dGhlbnRpY2F0aW9uWG1sWyJfbGFiZWwiXSAgICAgICAgPSAiWEVQRyBhdXRoZW50aWNhdGlvbiI7CiAgbWVudV9hdXRoZW50aWNhdGlvblhtbFsiX2hlYWRsaW5lIl0gICAgID0gIlhFUEcgYXV0aGVudGljYXRpb24iOwogIG1lbnVfYXV0aGVudGljYXRpb25YbWxbIl91c2FnZSJdICAgICAgICA9ICJEb3dubG9hZGluZyB0aGUgWEVQRyAoWE1MVFYpIGZpbGUgdmlhIGFuIEhUVFAgcmVxdWVzdCBpcyBvbmx5IHBvc3NpYmxlIHdpdGggYXV0aGVudGljYXRpb24uIgogIG1lbnVfYXV0aGVudGljYXRpb25YbWxbIm5hbWUiXSAgICAgICAgICA9ICJhdXRoZW50aWNhdGlvbi54bWwiOwogIG1lbnVfYXV0aGVudGljYXRpb25YbWxbImlkIl0gICAgICAgICAgICA9ICJhdXRoZW50aWNhdGlvbi54bWwiOwogIG1lbnVfYXV0aGVudGljYXRpb25YbWxbInZhbHVlIl0gICAgICAgICA9IG1lbnVfYXV0aGVudGljYXRpb25YbWxbIm5hbWUiXTsKICBtZW51X2F1dGhlbnRpY2F0aW9uWG1sWyJvbmNsaWNrIl0gICAgICAgPSAiamF2YXNjcmlwdDogdG9nZ2xlTWVudSh0aGlzKTsiOwogIG1lbnVfYXV0aGVudGljYXRpb25YbWxbImNsYXNzIl0gICAgICAgICA9ICJtZW51LW5vdEFjdGl2ZSI7CgogIHZhciBtZW51X2F1dGhlbnRpY2F0aW9uQXBpID0gbmV3IE9iamVjdCgpOwogIG1lbnVfYXV0aGVudGljYXRpb25BcGlbIl9lbGVtZW50Il0gPSAiTEkiOwogIG1lbnVfYXV0aGVudGljYXRpb25BcGlbIl9tZW51VHlwZSJdICAgICA9ICJjaGVja2JveCI7CiAgbWVudV9hdXRoZW50aWNhdGlvbkFwaVsiX2NvbmZpZ0tleSJdICAgID0gImF1dGhlbnRpY2F0aW9uLmFwaSI7CiAgbWVudV9hdXRoZW50aWNhdGlvbkFwaVsiX2xhYmVsIl0gICAgICAgID0gIkFQSSBhdXRoZW50aWNhdGlvbiI7CiAgbWVudV9hdXRoZW50aWNhdGlvbkFwaVsiX2hlYWRsaW5lIl0gICAgID0gIkFQSSBhdXRoZW50aWNhdGlvbiI7CiAgbWVudV9hdXRoZW50aWNhdGlvbkFwaVsiX3VzYWdlIl0gICAgICAgID0gIkFjY2VzcyB0byB0aGUgQVBJIGludGVyZmFjZSBpcyBvbmx5IHBvc3NpYmxlIHdpdGggYXV0aGVudGljYXRpb24uIgogIG1lbnVfYXV0aGVudGljYXRpb25BcGlbIm5hbWUiXSAgICAgICAgICA9ICJhdXRoZW50aWNhdGlvbi5hcGkiOwogIG1lbnVfYXV0aGVudGljYXRpb25BcGlbImlkIl0gICAgICAgICAgICA9ICJhdXRoZW50aWNhdGlvbi5hcGkiOwogIG1lbnVfYXV0aGVudGljYXRpb25BcGlbInZhbHVlIl0gICAgICAgICA9IG1lbnVfYXV0aGVudGljYXRpb25BcGlbIm5hbWUiXTsKICBtZW51X2F1dGhlbnRpY2F0aW9uQXBpWyJvbmNsaWNrIl0gICAgICAgPSAiamF2YXNjcmlwdDogdG9nZ2xlTWVudSh0aGlzKTsiOwogIG1lbnVfYXV0aGVudGljYXRpb25BcGlbImNsYXNzIl0gICAgICAgICA9ICJtZW51LW5vdEFjdGl2ZSI7CiAgCiAgCiAgLy8gTWFpbiBtZW51CiAgbWVudVsxMF0gPSBtZW51X20zdTsKCiAgc3dpdGNoKGNvbmZpZ1siZXBnU291cmNlIl0pIHsKICAgIGNhc2UgIlBNUyI6CiAgICAgIG1lbnVbMjBdID0gbWVudV9pZDsKICAgICAgYnJlYWs7CiAgICAKICAgIGNhc2UgIlhNTFRWIjoKICAgICAgbWVudVs0MF0gPSBtZW51X3htbHR2OwogICAgICBicmVhazsKCiAgICBjYXNlICJYRVBHIjoKICAgICAgbWVudVs0MF0gPSBtZW51X3htbHR2OwogICAgICBtZW51WzUwXSA9IG1lbnVfbWFwcGluZzsKICAgICAgYnJlYWs7CiAgfQogIAogIG1lbnVbMzBdID0gbWVudV9maWx0ZXI7CiAgCiAgaWYgKGNvbmZpZ1siYXV0aGVudGljYXRpb24ud2ViIl0gPT0gdHJ1ZSkgewogICAgbWVudVs2MF0gPSBtZW51X3VzZXJzOwogIH0KCiAgbWVudVs3MF0gPSBtZW51X3NldHRpbmdzOwogIG1lbnVbODBdID0gbWVudV9sb2c7CiAgaWYgKGNvbmZpZ1siYXV0aGVudGljYXRpb24ud2ViIl0gPT0gdHJ1ZSkgewogICAgbWVudVsxMDBdID0gbWVudV9sb2dvdXQ7CiAgfSAKICAKCiAgLy8gU3ViLU1lbnUKCiAgc3ViTWVudVs3MDFdID0gbWVudV9zY2hlZHVsZTsKICBzdWJNZW51WzcwMl0gPSBtZW51X2ZpbGVzVXBkYXRlOwogIHN1Yk1lbnVbNzAzXSA9IG1lbnVfdHVuZXI7CiAgc3ViTWVudVs3MDRdID0gbWVudV9lcGc7CiAgc3ViTWVudVs3MDVdID0gbWVudV94ZXBnOwogIHN1Yk1lbnVbNzA2XSA9IG1lbnVfYXV0b0JhY2t1cFBhdGg7CiAgc3ViTWVudVs3MDddID0gbWVudV9hdXRvQmFja3VwS2VlcDsKICBzdWJNZW51WzcwOF0gPSBtZW51X2J1ZmZlcjsKICAKICBzdWJNZW51WzcxMF0gPSBtZW51X2F1dGhlbnRpY2F0aW9uV2ViOwogIAogIGlmIChjb25maWdbImF1dGhlbnRpY2F0aW9uLndlYiJdID09IHRydWUpIHsKICAgIHN1Yk1lbnVbNzExXSA9IG1lbnVfYXV0aGVudGljYXRpb25QbXM7CiAgICBzdWJNZW51WzcxMl0gPSBtZW51X2F1dGhlbnRpY2F0aW9uTTN1OwogICAgc3ViTWVudVs3MTNdID0gbWVudV9hdXRoZW50aWNhdGlvblhtbDsKICAgIHN1Yk1lbnVbNzE0XSA9IG1lbnVfYXV0aGVudGljYXRpb25BcGk7CiAgfQoKICBzdWJNZW51Wzc5OV0gPSBtZW51X2FwaTsKCiAgCgogIHJldHVybgp9CgpmdW5jdGlvbiBjcmVhdGVNZW51KCkgewoKICBzaG93RWxlbWVudCgicG9wdXAiLCBmYWxzZSk7CgogIC8vY29uc29sZS5sb2coY29uZmlnKTsKICBzZXRNZW51SXRlbSgpOwogIHZhciBtZW51SXRlbXMgPSBnZXRPYmpLZXlzKG1lbnUpCiAgdmFyIG5hdiA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJOQVYiKVswXTsKICBuYXYuaW5uZXJIVE1MID0gIiI7CiAgdmFyIG5ld0l0ZW0gPSBuZXcgT2JqZWN0KCk7CgogIGZvciAodmFyIGkgPSAwOyBpIDwgbWVudUl0ZW1zLmxlbmd0aDsgaSsrKSB7CgogICAgCiAgICB2YXIgbmV3SXRlbSA9IG1lbnVbbWVudUl0ZW1zW2ldXTsKICAgIG5ld0l0ZW1bImlkIl0gPSBtZW51SXRlbXNbaV07CgogICAgCiAgICBzd2l0Y2gobmV3SXRlbS5oYXNPd25Qcm9wZXJ0eSgiX2ljb24iKSkgewogICAgICBjYXNlIHRydWU6IAogICAgICAgIHZhciBpdGVtVGV4dCA9IG5ld0l0ZW1bIl90ZXh0Il07CiAgICAgICAgZGVsZXRlIG5ld0l0ZW1bIl90ZXh0Il0KICAgICAgICBuYXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdJdGVtKSk7CiAgICAgICAgbmV3SXRlbVsiX3RleHQiXSA9IGl0ZW1UZXh0OwogICAgICAgIHZhciBuZXdJY29uID0gbmV3IE9iamVjdCgpOwogICAgICAgIG5ld0ljb25bIl9lbGVtZW50Il0gPSAiSU1HIjsKICAgICAgICBuZXdJY29uWyJzcmMiXSA9IG5ld0l0ZW1bIl9pY29uIl07CgogICAgICAgIHZhciBjdXJyZW50RWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKG1lbnVJdGVtc1tpXSk7CiAgICAgICAgY3VycmVudEVsZW1lbnQuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdJY29uKSk7CgoKICAgICAgICB2YXIgdGV4dCA9IG5ldyBPYmplY3QoKTsKICAgICAgICB0ZXh0WyJfZWxlbWVudCJdID0gIlAiCiAgICAgICAgdGV4dFsiX3RleHQiXSA9IGl0ZW1UZXh0OwogICAgICAgIHRleHRbImNsYXNzIl0gPSAibmF2LXRleHQiCiAgICAgICAgY3VycmVudEVsZW1lbnQuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudCh0ZXh0KSk7CiAgICAgICAgYnJlYWs7CgogICAgICBkZWZhdWx0OgogICAgICAgIG5hdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0ljb24pKTsKICAgICAgICBicmVhazsKICAgIH0KCiAgfQogIGlmIChhY3RpdmVNZW51ICE9IHVuZGVmaW5lZCkgewogICAgLy9jb25zb2xlLmxvZyhhY3RpdmVNZW51KTsKICAgIHRvZ2dsZU1lbnUoYWN0aXZlTWVudSk7CiAgfQoKICByZXR1cm4KfQoKZnVuY3Rpb24gdG9nZ2xlTWVudShlbG0pIHsKICAvL3Nob3dTdHJlYW1zKGZhbHNlKTsKICBjbGVhckludGVydmFsKGxvZ0ludGVydmFsKQogIGFjdGl2ZU1lbnUgPSBlbG07CiAgdmFyIGl0ZW0gPSBtZW51W2VsbS5pZF0KICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNldHRpbmdzIik7CiAgZGl2LmlubmVySFRNTCA9ICIiOwogIAogIC8vIFNldCBIZWFkbGluZQogIHZhciBoZWFkbGluZSA9IG5ldyBPYmplY3QoKTsKICBoZWFkbGluZVsiX2VsZW1lbnQiXSA9ICJINCI7CiAgaGVhZGxpbmVbIl90ZXh0Il0gPSBpdGVtWyJfaGVhZGxpbmUiXTsKICBkaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChoZWFkbGluZSkpOwoKICAvLyBTdWItTWVudQogIGlmIChpdGVtLmhhc093blByb3BlcnR5KCJfc3ViTWVudSIpID09IHRydWUpIHsKICAgIG9wZW5TdWJNZW51KGl0ZW0pOwogICAgcmV0dXJuCiAgfQoKICAvLyBNYXBwaW5nLCBVc2VycywgTG9nLCBGaWxlcwogIHN3aXRjaChpdGVtWyJfY29uZmlnS2V5Il0pIHsKICAgIGNhc2UgIm1hcHBpbmciOiAgICAgb3Blbk1hcHBpbmdFZGl0b3IoaXRlbSk7IHJldHVybjsgYnJlYWs7CiAgICBjYXNlICJ1c2VycyI6ICAgICAgIG9wZW5Vc2VycyhpdGVtKTsgcmV0dXJuOyBicmVhazsKICAgIGNhc2UgImxvZyI6ICAgICAgICAgc2hvd0xvZyhpdGVtKTsgcmV0dXJuOyBicmVhazsKICAgIGNhc2UgImZpbGVzLm0zdSI6ICAgb3BlbkZpbGVzKGl0ZW0sICJtM3UiKTsgcmV0dXJuOyBicmVhazsKICAgIGNhc2UgImZpbGVzLnhtbHR2Ijogb3BlbkZpbGVzKGl0ZW0sICJ4bWx0diIpOyByZXR1cm47IGJyZWFrOwoKICAgIGNhc2UgImZpbHRlciI6ICAgICAgc2hvd1N0cmVhbXModHJ1ZSk7IGJyZWFrOwogIH0KCiAKCiAgdmFyIG5ld0hSID0gbmV3IE9iamVjdCgpOwogIG5ld0hSWyJfZWxlbWVudCJdID0gIkhSIgogIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0hSKSk7CiAgCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJJTlBVVCI7CiAgbmV3RW50cnlbInR5cGUiXSA9ICJidXR0b24iOwogIC8vbmV3RW50cnlbImNsYXNzIl0gPSAic2F2ZSI7CiAgbmV3RW50cnlbInZhbHVlIl0gPSAiU2F2ZSI7CiAgbmV3RW50cnlbIm9uY2xpY2siXSA9ICJzYXZlRGF0YTIoJ3NldHRpbmdzJykiCiAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKCgogIHZhciBuZXdXcmFwcGVyID0gbmV3IE9iamVjdCgpOwogIG5ld1dyYXBwZXJbIl9lbGVtZW50Il0gID0gIkRJViI7CiAgbmV3V3JhcHBlclsiaWQiXSAgICAgICAgPSAiYm94LXdyYXBwZXIiOwogIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld1dyYXBwZXIpKTsKCiAgZGl2ID0gZGl2Lmxhc3RDaGlsZDsKICAKICBkaXYuYXBwZW5kQ2hpbGQoY3JlYXRlTWVudUl0ZW0oaXRlbSkpCgogIC8vIHVzYWdlIEluZm8gIAogIHN3aXRjaChtZW51W2FjdGl2ZU1lbnUuaWRdLmhhc093blByb3BlcnR5KCJfdXNhZ2UiKSkgewogICAgY2FzZSB0cnVlOiAKICAgICAgdmFyIHVzYWdlSXRlbSA9IG5ldyBPYmplY3QoKTsKICAgICAgdXNhZ2VJdGVtWyJfZWxlbWVudCJdID0gIlBSRSIKICAgICAgdXNhZ2VJdGVtWyJfdGV4dCJdICAgID0gbWVudVthY3RpdmVNZW51LmlkXVsiX3VzYWdlIl07CiAgICAgIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KHVzYWdlSXRlbSkpOwogIH0KICAKICBjYWxjdWxhdGVXcmFwcGVySGVpZ2h0KCk7Cgp9CgpmdW5jdGlvbiBjcmVhdGVNZW51SXRlbShpdGVtKSB7CiAgdmFyIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJESVYiKTsKICBzd2l0Y2goaXRlbVsiX21lbnVUeXBlIl0pIHsKICAgIGNhc2UgImlucHV0QXJyYXkiOgogICAgICBpZiAoY29uZmlnLmhhc093blByb3BlcnR5KGl0ZW1bIl9jb25maWdLZXkiXSkgPT0gdHJ1ZSkgewogICAgICAgIHZhciB2YWx1ZSA9IGNvbmZpZ1tpdGVtWyJfY29uZmlnS2V5Il1dOwogICAgICB9IGVsc2UgewogICAgICAgIHZhciB2YWx1ZSA9IG5ldyBBcnJheSgpOwogICAgICB9CiAgICAgIAogICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHZhbHVlLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogICAgICAgIG5ld0VudHJ5ID0gaXRlbQogICAgICAgIGRlbGV0ZSBuZXdFbnRyeVsib25jbGljayJdOwogICAgICAgIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJJTlBVVCI7CiAgICAgICAgbmV3RW50cnlbInZhbHVlIl0gICAgID0gdmFsdWVbaV07CiAgICAgICAgbmV3RW50cnlbInR5cGUiXSAgICAgID0gInNlYXJjaCI7CiAgICAgICAgbmV3RW50cnlbImRhdGEtbWVudXR5cGUiXSA9IGl0ZW1bIl9tZW51VHlwZSJdOwogICAgICAgIG5ld0VudHJ5WyJkYXRhLW1lbnVrZXkiXSA9IGl0ZW1bIl9jb25maWdLZXkiXTsKICAgICAgICBlbGVtZW50LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKCiAgICAgIH0KICAgICAgLy8gTmV3IGVudHJ5IGZvciBhcnJheQogICAgICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0KCk7CiAgICAgIG5ld0VudHJ5WyJfZWxlbWVudCJdICAgICAgPSAiSU5QVVQiOwogICAgICBuZXdFbnRyeVsidHlwZSJdICAgICAgICAgID0gInNlYXJjaCI7CiAgICAgIG5ld0VudHJ5WyJuYW1lIl0gICAgICAgICAgPSBpdGVtWyJuYW1lIl07CiAgICAgIG5ld0VudHJ5WyJwbGFjZWhvbGRlciJdICAgPSBpdGVtWyJwbGFjZWhvbGRlciJdOwogICAgICBuZXdFbnRyeVsidmFsdWUiXSAgICAgICAgID0gIiI7CiAgICAgIG5ld0VudHJ5WyJkYXRhLW1lbnV0eXBlIl0gPSBpdGVtWyJfbWVudVR5cGUiXTsKICAgICAgbmV3RW50cnlbImRhdGEtbWVudWtleSJdICA9IGl0ZW1bIl9jb25maWdLZXkiXTsKICAgICAgZWxlbWVudC5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0VudHJ5KSk7CiAgICAgIGJyZWFrOwogIAogICAgY2FzZSAic2luZ2xlSW5wdXQiOgogICAgICB2YXIgdmFsdWUgPSBjb25maWdbaXRlbVsiX2NvbmZpZ0tleSJdXTsKICAgICAgaWYgKHZhbHVlID09IHVuZGVmaW5lZCkgewogICAgICAgIHZhbHVlID0gIiI7CiAgICAgIH0KICAgICAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogICAgICBuZXdFbnRyeSA9IGl0ZW07CiAgICAgIGRlbGV0ZSBuZXdFbnRyeVsib25jbGljayJdOwogICAgICBuZXdFbnRyeVsiX2VsZW1lbnQiXSAgPSAiSU5QVVQiOwogICAgICBuZXdFbnRyeVsidmFsdWUiXSAgICAgPSB2YWx1ZTsKICAgICAgbmV3RW50cnlbInR5cGUiXSAgICAgID0gInNlYXJjaCI7CiAgICAgIG5ld0VudHJ5WyJkYXRhLW1lbnV0eXBlIl0gPSBpdGVtWyJfbWVudVR5cGUiXTsKICAgICAgbmV3RW50cnlbImRhdGEtbWVudWtleSJdID0gaXRlbVsiX2NvbmZpZ0tleSJdOwogICAgICBlbGVtZW50LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKICAgICAgYnJlYWs7CgogICAgY2FzZSAiY2hlY2tib3giOgogICAgICB2YXIgdmFsdWUgPSBjb25maWdbaXRlbVsiX2NvbmZpZ0tleSJdXTsKICAgICAgaWYgKHZhbHVlID09IHVuZGVmaW5lZCkgewogICAgICAgIHZhbHVlID0gZmFsc2U7CiAgICAgIH0KICAgICAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogICAgICBuZXdFbnRyeSA9IGl0ZW07CiAgICAgIGRlbGV0ZSBuZXdFbnRyeVsib25jbGljayJdOwogICAgICBuZXdFbnRyeVsiX2VsZW1lbnQiXSAgPSAiSU5QVVQiOwogICAgICBuZXdFbnRyeVsidmFsdWUiXSAgICAgPSB2YWx1ZTsKICAgICAgbmV3RW50cnlbInR5cGUiXSAgICAgID0gImNoZWNrYm94IjsKICAgICAgbmV3RW50cnlbImRhdGEtbWVudXR5cGUiXSA9IGl0ZW1bIl9tZW51VHlwZSJdOwogICAgICBuZXdFbnRyeVsiZGF0YS1tZW51a2V5Il0gPSBpdGVtWyJfY29uZmlnS2V5Il07CiAgICAgIGVsZW1lbnQuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwogICAgICBlbGVtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJJTlBVVCIpWzBdLmNoZWNrZWQgPSB2YWx1ZTsKICAgICAgYnJlYWs7CiAgICAKICAgIGNhc2UgInNlbGVjdCI6CiAgICAgIHZhciB2YWx1ZSA9IGNvbmZpZ1tpdGVtWyJfY29uZmlnS2V5Il1dOwogICAgICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0KCk7CiAgICAgIG5ld0VudHJ5ID0gaXRlbTsKICAgICAgZGVsZXRlIG5ld0VudHJ5WyJvbmNsaWNrIl0KICAgICAgbmV3RW50cnlbIl9lbGVtZW50Il0gID0gIlNFTEVDVCI7CiAgICAgIGVsZW1lbnQuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwogICAgICB2YXIgc2VsZWN0RWxlbWVudCA9IGVsZW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIlNFTEVDVCIpWzBdOwogICAgICB2YXIgdmFsdWVzID0gaXRlbVsiX29wdGlvblZhbHVlcyJdOwogICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHZhbHVlcy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBuZXdFbnRyeSA9IG5ldyBPYmplY3Q7CiAgICAgICAgbmV3RW50cnlbIl9lbGVtZW50Il0gID0gIk9QVElPTiI7CiAgICAgICAgbmV3RW50cnlbIl90ZXh0Il0gICAgID0gaXRlbVsiX3RleHQiXSArICI6ICIgKyB2YWx1ZXNbaV07CiAgICAgICAgbmV3RW50cnlbInZhbHVlIl0gICAgID0gdmFsdWVzW2ldOwogICAgICAgIHNlbGVjdEVsZW1lbnQuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwogICAgICB9CiAgICAgIHNlbGVjdEVsZW1lbnQudmFsdWUgPSB2YWx1ZTsKICAgICAgYnJlYWs7CiAgICAKICB9CiAgcmV0dXJuIGVsZW1lbnQ7Cn0KCmZ1bmN0aW9uIG9wZW5TdWJNZW51KGl0ZW0pIHsKICB2YXIgZW50cnlzID0gaXRlbVsiX3N1Yk1lbnUiXS5zcGxpdCgiLCIpOwogIHZhciBkaXYgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgic2V0dGluZ3MiKTsKCiAgdmFyIG5ld0hSID0gbmV3IE9iamVjdCgpOwogIG5ld0hSWyJfZWxlbWVudCJdID0gIkhSIgogIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0hSKSk7CiAgCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJJTlBVVCI7CiAgbmV3RW50cnlbInR5cGUiXSA9ICJidXR0b24iOwogIC8vbmV3RW50cnlbImNsYXNzIl0gPSAic2F2ZSI7CiAgbmV3RW50cnlbInZhbHVlIl0gPSAiU2F2ZSI7CiAgbmV3RW50cnlbIm9uY2xpY2siXSA9ICJzYXZlRGF0YTIoJ3NldHRpbmdzJykiCiAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKCiAgaWYgKGl0ZW1bIl9jb25maWdLZXkiXSA9PSAic2V0dGluZ3MiKSB7CiAgICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0KCk7CiAgICBuZXdFbnRyeVsiX2VsZW1lbnQiXSAgPSAiSU5QVVQiOwogICAgbmV3RW50cnlbInR5cGUiXSA9ICJidXR0b24iOwogICAgLy9uZXdFbnRyeVsiY2xhc3MiXSA9ICJzYXZlIjsKICAgIG5ld0VudHJ5WyJ2YWx1ZSJdID0gIkJhY2t1cCI7CiAgICBuZXdFbnRyeVsib25jbGljayJdID0gInh0ZXZlQmFja3VwKCkiCiAgICBkaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwogIH0KCiAgaWYgKGl0ZW1bIl9jb25maWdLZXkiXSA9PSAic2V0dGluZ3MiKSB7CiAgICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0KCk7CiAgICBuZXdFbnRyeVsiX2VsZW1lbnQiXSAgPSAiSU5QVVQiOwogICAgbmV3RW50cnlbInR5cGUiXSA9ICJidXR0b24iOwogICAgLy9uZXdFbnRyeVsiY2xhc3MiXSA9ICJzYXZlIjsKICAgIG5ld0VudHJ5WyJ2YWx1ZSJdID0gIlJlc3RvcmUiOwogICAgbmV3RW50cnlbIm9uY2xpY2siXSA9ICJ4dGV2ZVJlc3RvcmUodGhpcykiCiAgICBkaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwogIH0KCgogIHZhciBuZXdXcmFwcGVyID0gbmV3IE9iamVjdCgpOwogIG5ld1dyYXBwZXJbIl9lbGVtZW50Il0gID0gIkRJViI7CiAgbmV3V3JhcHBlclsiaWQiXSAgICAgICAgPSAiYm94LXdyYXBwZXIiOwogIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld1dyYXBwZXIpKTsKICAKICBkaXYgPSBkaXYubGFzdENoaWxkOwogIAoKICBmb3IgKHZhciBpID0gMDsgaSA8IGVudHJ5cy5sZW5ndGg7IGkrKykgewogICAgdmFyIGl0ZW0gPSBzdWJNZW51W2VudHJ5c1tpXV07CiAgICBpZiAoaXRlbSA9PSB1bmRlZmluZWQpIHsKICAgICAgYnJlYWs7CiAgICB9CiAgICAKICAgIHZhciBjb250YWluZXIgPSBuZXcgT2JqZWN0KCk7CiAgICBjb250YWluZXJbIl9lbGVtZW50Il0gPSAiRElWIjsKICAgIGRpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KGNvbnRhaW5lcikpOwoKICAgIHZhciBkaXZDb250YWluZXIgPSBkaXYubGFzdENoaWxkOwogICAgCiAgICB2YXIgaGVhZGxpbmUgPSBuZXcgT2JqZWN0KCk7CiAgICBoZWFkbGluZVsiX2VsZW1lbnQiXSA9ICJINSI7CiAgICBoZWFkbGluZVsiX3RleHQiXSA9IGl0ZW1bIl9oZWFkbGluZSJdOwogICAgZGl2Q29udGFpbmVyLmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQoaGVhZGxpbmUpKTsKCiAgICBkaXZDb250YWluZXIuYXBwZW5kQ2hpbGQoY3JlYXRlTWVudUl0ZW0oaXRlbSkpCgogICAgc3dpdGNoKGl0ZW0uaGFzT3duUHJvcGVydHkoIl91c2FnZSIpKSB7CiAgICAgIGNhc2UgdHJ1ZTogCiAgICAgICAgdmFyIHVzYWdlSXRlbSA9IG5ldyBPYmplY3QoKTsKICAgICAgICB1c2FnZUl0ZW1bIl9lbGVtZW50Il0gPSAiUFJFIgogICAgICAgIHVzYWdlSXRlbVsiX3RleHQiXSAgICA9IGl0ZW1bIl91c2FnZSJdOwogICAgICAgIGRpdkNvbnRhaW5lci5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KHVzYWdlSXRlbSkpOwogICAgfQoKICAgIHZhciBociA9IG5ldyBPYmplY3QoKTsKICAgIGhyWyJfZWxlbWVudCJdID0gIkhSIjsKICAgIGRpdkNvbnRhaW5lci5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KGhyKSk7CiAgCiAgfQoKICBjYWxjdWxhdGVXcmFwcGVySGVpZ2h0KCk7CiAgcmV0dXJuCn0KCmZ1bmN0aW9uIHNhdmVEYXRhMihlbG0pIHsKICB2YXIgZGl2ICAgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChlbG0pOwogIHZhciBpbnB1dHMgPSBkaXYuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIklOUFVUIik7CiAgdmFyIHNlbGVjdHMgPSBkaXYuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIlNFTEVDVCIpOwogIHZhciB2YWx1ZSwgY29uZmlnS2V5OwogIHZhciBkYXRhID0gbmV3IE9iamVjdCgpOwogIHZhciB2YWx1ZUFyciA9IG5ldyBBcnJheSgpOwogIHZhciBuZXdEYXRhID0gZmFsc2U7CiAgCiAgZm9yICh2YXIgaSA9IDA7IGkgPCBpbnB1dHMubGVuZ3RoOyBpKyspIHsKICAgIGlmIChpbnB1dHNbaV0udHlwZSAhPSAiYnV0dG9uIikgewogICAgICB2YXIgbWVudVR5cGUgPSBpbnB1dHNbaV0uZ2V0QXR0cmlidXRlKCJkYXRhLW1lbnV0eXBlIik7CiAgICAgIAogICAgICAvL2NvbnNvbGUubG9nKG1lbnVUeXBlKTsKICAgICAgc3dpdGNoKG1lbnVUeXBlKSB7CiAgICAgICAgY2FzZSAic2luZ2xlSW5wdXQiOgogICAgICAgICAgdmFsdWUgPSBpbnB1dHNbaV0udmFsdWU7CiAgICAgICAgICBpZiAodmFsdWUgPT0gIiIgfHwgdmFsdWUgPT0gdW5kZWZpbmVkKSB7CiAgICAgICAgICAgIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgICAgICAgICAgIGRhdGFbImRlbGV0ZSJdID0gaW5wdXRzW2ldLm5hbWUKICAgICAgICAgICAgbmV3RGF0YSA9IHRydWU7CiAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBuZXdEYXRhID0gdHJ1ZTsKICAgICAgICAgICAgZGF0YVtpbnB1dHNbaV0ubmFtZV0gPSB2YWx1ZTsKICAgICAgICAgICAgY29uc29sZS5sb2coZGF0YSk7CiAgICAgICAgICB9CiAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICJpbnB1dEFycmF5IjogCiAgICAgICAgICB2YWx1ZSA9IGlucHV0c1tpXS52YWx1ZTsKICAgICAgICAgIGlmICh2YWx1ZSAhPSAiIiAmJiB2YWx1ZSAhPSB1bmRlZmluZWQpIHsKICAgICAgICAgICAgbmV3RGF0YSA9IHRydWU7CiAgICAgICAgICAgIHZhbHVlQXJyLnB1c2godmFsdWUpCiAgICAgICAgICAgIGRhdGFbaW5wdXRzW2ldLm5hbWVdID0gdmFsdWVBcnI7CiAgICAgICAgICAgIGNvbmZpZ0tleSA9IGlucHV0c1tpXS5uYW1lOwogICAgICAgICAgfSAKICAgICAgICAgIAogICAgICAgICAgYnJlYWs7CgogICAgICAgIGNhc2UgImNoZWNrYm94IjoKICAgICAgICAgIHZhbHVlID0gaW5wdXRzW2ldLmNoZWNrZWQKICAgICAgICAgIGRhdGFbaW5wdXRzW2ldLm5hbWVdID0gdmFsdWU7CiAgICAgIH0KICAgICAgCiAgICB9CiAgICAKICB9CgoKICAvLyBEZWxldGUgY29uZmlnIGtleQogIGlmICh2YWx1ZUFyci5sZW5ndGggPT0gMCAmJiBuZXdEYXRhID09IGZhbHNlKSB7CiAgICBuZXdEYXRhID0gdHJ1ZTsKICAgIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgICBkYXRhWyJkZWxldGUiXSA9IGNvbmZpZ0tleTsKICB9IAoKCiAgZm9yICh2YXIgaSA9IDA7IGkgPCBzZWxlY3RzLmxlbmd0aDsgaSsrKSB7CiAgICB2YXIgdmFsdWUgPSBzZWxlY3RzW2ldLm9wdGlvbnNbc2VsZWN0c1tpXS5zZWxlY3RlZEluZGV4XS52YWx1ZTsKICAgIHN3aXRjaChpc05hTih2YWx1ZSkpIHsKICAgICAgY2FzZSBmYWxzZTogdmFsdWUgPSBwYXJzZUludCh2YWx1ZSk7IGJyZWFrOwogICAgfQoKICAgIGRhdGFbc2VsZWN0c1tpXS5uYW1lXSA9IHZhbHVlOwogICAgbmV3RGF0YSA9IHRydWU7CiAgfQoKICAvL2NvbnNvbGUubG9nKGRhdGEsIG5ld0RhdGEpOwoKICBpZiAobmV3RGF0YSA9PSB0cnVlKSB7CiAgICBkYXRhWyJjbWQiXSA9ICJzYXZlQ29uZmlnIjsKICAgIGlmICghZGF0YS5oYXNPd25Qcm9wZXJ0eSgnZmlsdGVyJykpIHsKICAgICAgZGF0YVsiZmlsdGVyIl0gPSBjb25maWdbImZpbHRlciJdCiAgICB9CiAgICB2YXIgc2V0dGluZ3MgPSBuZXcgT2JqZWN0KCk7CiAgICBzZXR0aW5nc1siY21kIl0gPSBkYXRhWyJjbWQiXTsKICAgIHNldHRpbmdzWyJzZXR0aW5ncyJdID0gZGF0YTsKICAgIGNvbnNvbGUubG9nKHNldHRpbmdzKTsKICAgIHhUZVZlKHNldHRpbmdzKTsKICB9Cn0K" + webUI["html/video/stream-limit.ts"] = "R0AREABC8CUAAcEAAP8B/wAB/IAUSBIBBkZGbXBlZwlTZXJ2aWNlMDF3fEPK//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9HQAAQAACwDQABwQAAAAHwACqxBLL//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////0dQABAAArASAAHBAADhAPAAG+EA8AAVvU1W////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////R0EAMAdQAAB7DH4AAAAB4AAAgMAKMQAJEKERAAfYYQAAAAEJ8AAAAAFnZAAorNlAeAIn5cBEAAADAAQAAAMAyDxgxlgAAAABaOvjyyLAAAABBgX//6rcRem95tlIt5Ys2CDZI+7veDI2NCAtIGNvcmUgMTUyIHIyODU0IGU5YTU5MDMgLSBILjI2NC9NUEVHLTQgQVZDIGNvZGVjIC0gQ29weWxlZnQgMjAwMy0yMDE3IC0gaHR0cDovL3dHAQARd3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2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ACqxBLL//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////0dQABEAArASAAHBAADhAPAAG+EA8AAVvU1W////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////RwEAGO10tGlFAwyE91Tzg8923ovJk1dv2xh1gVlYQFtWdZ6NSQ8vgHM7/tMfIZPsqgCU856rdBJJRMDaor/oWA7pkvSmC2aqaC5fd/mkWUPTuG8fRhGqOY1rKxLNSI3HtTQAcuVBxRM0NfMw0ky/JMJYZiCgXcNsxpxl+khr/9fKtO5Fg2VNBti92xcZimHUZDJMQTBOY6vSR9L1N2O/kJQCpdfuc9bkcUTMjH/es/lAySxvbiislkohqkpHAQAZVatwHI0t9eHCUfDyWUEtuCNyDwQFOVMtaYEQJrTrhQX0VduT86F0u0wN9ag0uPsrmX6Rid/x5S7KI1SkS+qP6cdmgSP57uiF4dG6IKsWNfGBLY09H/CFlrg73WINQkkp2diKkdEmtZetn8+ZAKzR5eXN5FebiKt6pIkNLuSofjosypEs2o6boKsfpmbtOV0idOeWDM9C8EIcn3aZwk1s2KBDvt1DlY+i2Cuvhk3XoKLT+pq/bOC5QkcBABolcOXneZXLoQw9W1PH1f/of92Ytjj8okrWijcQAKlE5l8XoTb9qrOiZnDOhqwcMlB9H9rorq041I89hChohYoSkJElcM8V+r8WYN5axD1BNyfwaHAOm4UN2/VEYOkNSfMjmx2S0WW/F2ha8Jj4fc4QgqcXr+TnnbwqLkLTValK4Qa1pcv7PIbDq6zCUfxe04guZ8xEhPQjp17/G6IB/6uq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wnibxQ/cXEblVnM47wbCN3iy4eHjYB5MoIICrSYmd8nt4V0toLs084KJlmnur5FbApYM4W79ff4oZkW88Cg8Fv8TnWhh1Hqc4UlhMbRKm2QxDgfBxar3U7BwNR6prrXXjqfR7ja3tPli+H7Jez/xcTg32QpHAQAdTa7pSSgBvmOqzz175NAFIhwE9p3d71F9jLvhaPv8q1iKopL0kizwzEWElnXWmNhfLkl/Au8KujnoKUaxVdTe+zAZm2dOkejnpNVtVj0Aw9wKcG3ZQ1iRZrUFRIoPKYlmR+oi0LrupuhYABtgPbdCbDtezGTr0nw9wVd/rZ6V+1wcoJ6+8VAVueIdwI3brqm6EoFHiI8L6lER+9SefB8fHo4NYgr/I8jSxaa/2PLX2K/DjrlY6bN8fEcBAB7DXsuh8DITF6CYWj2HTFvqfzlic3QHXnHtvsTFNpyVY1LY8y/fRK2mwALN6IB5W3OuyoKiA3K0lJdQUv2gYhHWvPTV218HYrD0rkbMxRH0SiEEkrHDeOt5T1HtWD13D2/QvMu535fsfZyeaFDjdCE9M0Xi8Lfc7Xj1ZNGend7Jo7/cEiczFkc1vB4z2JpFGZreV2EdquyEzC2STFJtfimyx8QlLngEuR1cxH+lWEcFX+mvBa/eqktIRwEAH6VazFLPNsnHZqsI66TVoArQuUUGFAzr8MT8GZ9V6qpZa5D0jA/4eGi6Kq0smUgRExnqIbSMLWqDf/Pk2YT3MpHTu9VE3Dn9blgB6KsVIkWnB98Y2n/6DrZDcF94Ndu0Ka4U/RZwdV5O5KcO+lU2ZiIOYTYZ6Ie0d+tApxYxJ+YmxaDoCyRNe6crw/yzR5SflT8ddi/E2BM+l+nwVDiKZ/714fmrBxFKWuqLiQNHV19o1cZ8Ubun+ZtHQAASAACwDQABwQAAAAHwACqxBLL//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////0dQABIAArASAAHBAADhAPAAG+EA8AAVvU1W////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////RwEAEH1s3BdtHdF1ln55B24wdACF/ggcRtPmB4IUB6cZJ+m32I4R48ZkdhAFBulgWcsv0wZwH0o1yi3i4qm3rJ98AUKdB9PsNyG9gURV0bTw4myAj6g5Dgodfv6w9bsbOxsGUWkvgAJ3rrR010BvZbIa6CMRCQRjxkCfRF77ansRJZT0JzhtYeSS/ZJob8Rr2jrng3W2rkmHtE+rdqlPiHxmwkRZvYGhBxwiJO2L6f+l/cABsfs09rA+A0pHAQAREZay3TBUIAfMWhApE+LaPgeyOYtaPFONA9aC0h4QrFkaLZfb86678MK+zLgqDBsDoj50eZHMVcczRjs4sYlv7RG8RIWKb5lB9Kn/8+CkxiiA11cHH1yzLX2SKCmhsak/+P/zYQ1EDvEj33bl9e3Jd62cTI+Qw99KrsWnUMN8hFNeXzoGHA3aec+Li4NFboF8sa6lCPR6Csxsvxw8DJaWCnER5Z0p+nKhXavF/eNHQ0DX47ohycc87UcBABKsYHCHXXe3vK1cFYDWO6rhwM6hxCdJ+/X+VB1akQejL6iBMdUPuadUqBnbprwJM0dWpSSn9uuZZeqe46iZOGIJUo69d8x+ZSvPcBH/dK1Y0LJnoWdgWH51SQg7eYqcwjQW3BOy7ZF02Hn1EhTuhsN7ms8zv3WIeEBDBq0T3Idl+Tmw9iJU9skVYiwygHG2+0XwrLa6y+uiQPwMzrnEr1kuf24XoAqrJTZQiVH3aQF4d94yDreWu2KPRwEAE2FsynHZaUcBXEFbeX0nSxZ7sRcRjJvc7RYOny6N8QFwKDVO7jpRWWYM55kuepBOyZ+7BFYeG+FU1kR+ZfmqyDNkD4D1IPwNh6u9JQxDymfN/aMcQC3WxdvCKquPsAut6dugpGkOFtIe+W3ayc4V/J978P+h1EDFMbFjxRPuXht0K2dBQZCJ0ybISUDqWzhPuep8CdxSU4MvMVfdXh7FLl/BqKJUTT+vM+Pz3XnqCQQmH5TjQ4CK55VHAQAUEtgfkBwIYgtwHkhjmtLw7HNTeJeMfTjATEKcyuKzItSeYxHBmGzQ/+G2otpPCOkVAXJ9mfx7LxrW4joZ0ttTRQJ/wqMUK1IN349LiJoo8y/+FwN4jPNDwIleJG697riqnmLOweFW8spHKLBfnHcyC1pG6EIxFpTqUQ0KHqXD/KXIHKioJNAOVwQ1Gfloxebo/Tdtd00dpHP3gcsAOnV75APCYOjVIlwm2X6ViDeZw6Btdb/Br/+yokcBABU3+eOESLlneGbR5SLiL8wPkoRaZ5X7xFlMTMvsv9yHk26bVdmsubSdbYS1Hy64n0+NnL7C1aLD7aJxOudMjVW5goZbfO2KxBQkCD4HFA284wbaB5vOL7mRilCn6YEs1kiwf22+fBLNUmXDb2p353wrD/ygTDhlki66USvUmd/ZAs9uzAddegEzy7Xn00YysWTu/hegYyZtWFC+X5dhLubWJ7n96y5wjrP7PAOXoQQtFUH9j/rBiP/7RwEAFnL/wYKYuj80mPGtimSbF/E9K7XwjwBz8yHZubmVMTCmwkZBM4h0FR1thnUGiSjqjJT6SfunWCZ/dZDQplrzS00UKcUP399SyM+nNG1br8Za0ZYwWch8nyLHYXSRuxdnrfUUespsOQo1cWSLGf0pV2HIcHoiAAEKwPU6irB5BSoFvB1qKVjgEk+QwAEYAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAABHAQA3pAD/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AwAAAwAAAwAAAwAAAwAAAwBbwUdBABgAAAHgAACAwAoxAAmBIREAB/SBAAAAAQnwAAAAAUGaJGxDv/6plgAAAwAAAwAAAwAAAwAAAwAAAwAAAwAKCKB0AIhy1ftwLNISWucTFC7xeBcaIzP3oZjZo8uT9hqZVstWGnrPz+vjaA5FuCO+K2w7/o39LygDO7aL+0zSrN90LW7r133x8+f+PEKk8d7pd2fj4RYuMOXOYunzFzRX/4IvR1XsK5WJYwVkOca3yfo7DTvUBP9rgSiTRwEAGQD6CFK1PBC4qYCa+coLVnX+jgODNrau5YJXIekDI4+wUNV+5T5HE4e2t5EGAjn77+kC1Vghmvx8KqPnylVUvsaTofheggxHsPuxn1zQt6nA59UglrRUMn8b4pmrvfqTBSqU949+AcNAI6M/eAqz6r0RG5KNeBhx8R2f0x2kujwHU3pc3rlPb6ZrFM/2HiG97eEIezrvWFG7YAAAAwAAAwAE1KRo1VfbdZQ/tOpNGxFmTcQoFidyD2tHAQAapTTIErUKA2Rc9uIzGJu+oW0uNOxRyl6oJNUNJp5L9TIshNS1CmUKgdCaE3LkLwdE8iUkUnIGfWLiWpWmjzsINYC8dnDaSptB53yJkBu0SsR00oo1PZ0pl4ZzoXGSYQJTPaVCbj6FNqm5wcnUqKTB/rgHdPdnDj0K8asLABeo/n3XCXrDorI+LYSD1FBgmV9O7RZdjfL0NnXOQkiq/ZGirwVV0cUF1aHtZx1yHlAAZKuuu7TqB13CAUcBADuRAP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////yj/IrOvAEoXDNGy+nrxWN0l8cmuqWYaUJpuM9XgAAADAAADAAKmR0EAPAcQAACJHH4AAAAB4AAAgMAKMQAJSOERAAkQoQAAAAEJ8AAAAAFBnkJ4hf8AAAMAAAMAAAMAAAMAAAMAAAMAAAMAC/BmhyuvLmkCgKTgACEXfOVvHRHdpBEEmTTz9pIZCa2FD0CXukzDCakOHekTpJQSdsfd7a/jjM1/5lBtKCAGQ6zOCSQ6DeKtyw+CugDuuglGipwmvQW+FIDjnZ6HRd0uaSOGy6VB3h9MI1o4nqaCqVwwIgubYZFHAQA9XgD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////WcZrO6BEiqf8pCxYwlz2mkdUzY/ipM/TIHS4m5xQAAAMAAAMAAA5sVm90p14dlvWgBgY7XQ9KVOJKLtaMAN5KiffVQtNuzW5DfL+RewZpAAADAAADAAAQMUdBAB4AAAHgAACAgAUhAAkswQAAAAEJ8AAAAAEBnmF0Qr8AAAMAAAMAAAMAAAMAAAMAAAMAAAMAABJfBjP2+4+W7ZybyT130+Ma4WSqICfd/dS4YtjrLlmh5VBlfrtwJqdN1PBTYDkwGOOONqZQeFN6lLGJoDzvuh3kv1aDBLqug+JhvrSeu5ebnvcrtQzb2REwJL3h9mvtkr0WfDu4AAADAAADAAs32Y4C/6Ns79oCGj6oqLltY3T4I0/QRwEAP5wA//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8BeoWLRnKQmVgO/RBoBfcK85AAAAMAAAMACFhHQQAwBxAAAJcsfgAAAAHgAACAwAoxAAllAREACUjhAAAAAQnwAAAAAQGeY2pCvwAAAwAAAwAAAwAAAwAAAwAAAwAAAwAAEmDKfLGXsvBBQPNYXlEd6WueJ4deApfcHESlG+7V6u0AiWQqJvNZYM+3Js3Lj9Ldc9yYIHqruVwfMtCYGlX3wMavI/DrhMuJw+RreIF8SKsJD8CMccAAAAMAAAMACrFZOBgEqaP/8EyWsAHV6CgcAgfyox/roEcBADGuAP//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AAADAAADAHTBR0EAEgAAAeAAAIDACjEACfGhEQAJZQEAAAABCfAAAAABQZpoSahBaJlMCHf//qmWAAADAAADAAADAAADAAADAAADAAADAAALf8A2bry6lUyIAQGNFfVK2Cd7h4qDqmqpvmXfkfTwo2Ab79AHhajxJ4MPWIgAAA84xLRIx7I2CrWjWe96OvgkzgS9mtvhuxdzAgpuIOJI3DEY9mviDDZz8XSAAAADAAADAB7AZkj9AAHHGvk+Pzlku8UkWa5HAQAzogD//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////6HjAkbF/gsWuvw4QAAAAwAAAwAccUdBADQHEAAApTx+AAAAAeAAAIDACjEACblhEQAJgSEAAAABCfAAAAABQZ6GRREsL/8AAAMAAAMAAAMAAAMAAAMAAAMAAAMAAA3URJB/3lMHSmOd0GYZnfpoR80WdqxlyHzPX+V81VUMeXIGrRgq5agd+ACdjoGI036yQ/rmpGw0CmMBrKwpI+nwFo3SP0s1rI2yHBZaiPJjSUgw33rBkxXcq55kfLf2sAAAAwAAAwACBWolidWDnIeQAAADRwEANbEA//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8AAAMADulHQQA2NAD///////////////////////////////////////////////////////////////////8AAAHgAACAgAUhAAmdQQAAAAEJ8AAAAAEBnqV0Qr8AAAMAAAMAAAMAAAMAAAMAAAMAAAMAABJXNihCnx11JUosH2hBE53O/YN+GPHqcnJiQBIf3maTAAj5iRt+7GYUKBAU9iCXTiouCjGYgAAAAwAAAwA9p4HQBg2cAAADAAADAADGgUdBADc4EAAAs0x+AP////////////////////////////////////////////////////////////////8AAAHgAACAwAoxAAnVgREACblhAAAAAQnwAAAAAQGep2pCvwAAAwAAAwAAAwAAAwAAAwAAAwAAAwAAAwBR6/L83y+ZevtMZ0FW4lp8qV7fVXQAhLAWlS6mDxvpaSUgChorvgTMXtxJEIAAAAMAAAMACqFCAAADAAADAAADAAz4R0EAOCsA////////////////////////////////////////////////////////AAAB4AAAgMAKMQALYiERAAnVgQAAAAEJ8AAAAAFBmqxJqEFsmUwId//+qZYAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAApQTq0hABLvkcWCYrAaRRPNDrECJGlF/11BYvG9+JJ9Y86nxnmQzPxtAAe1UgOsXAAADAAADAAADAAADAAADAAADAAADA/JHQQA5JhAAAMFcfgD/////////////////////////////////////////AAAB4AAAgMAKMQALKeERAAnxoQAAAAEJ8AAAAAFBnspFFSwv/wAAAwAAAwAAAwAAAwAAAwAAAwAAAwAADdRFmvrVE0KXDOGwWddlwYa1foib7tXZKmkua6R4APb4b8+tXwdSNNqgf2IVNwGTohgQAcMvp6SiQxpiQym4AAADAAADAANUNUAAAAMAAAMAAAMBN0dBADo8AP//////////////////////////////////////////////////////////////////////////////AAAB4AAAgIAFIQALDcEAAAABCfAAAAABAZ7pdEK/AAADAAADAAADAAADAAADAAADAAADAAADAFHTAfujIUaaLSuklb9y0/so9AEtQAqkAxOviALQZ5jNt/iOkABrCOZ4md0iUnFwAAADAAADAALwckAAAAMAAAMAAATcR0EAO0IQAADPbH4A//////////////////////////////////////////////////////////////////////////////8AAAHgAACAwAoxAAtGAREACynhAAAAAQnwAAAAAQGe62pCvwAAAwAAAwAAAwAAAwAAAwAAAwAAAwAAAwBR6/L83y+WryR9gmVKPGbIhJpkgAFgHCej3gB6rAHPgHHAAAADAAADAAAYIV0AAAMAAAMAAAMABnxHQQA8UQD//////////////////////////////////////////////////////////////////////////////////////////////////////////wAAAeAAAIDACjEAC9KhEQALRgEAAAABCfAAAAABQZrwSahBbJlMCG///qeEAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAADAAAZ8UdBAD00EAAA3Xx+AP///////////////////////////////////////////////////////////wAAAeAAAIDACjEAC5phEQALYiEAAAABCfAAAAABQZ8ORRUsL/8AAAMAAAMAAAMAAAMAAAMAAAMAAAMAAA3URZr61RNClwzhI2ZXdRHYLZWc1yTgKRhTIXBNABHQKIIyYNyc+AaEn1jhATEAAAMAAAMAAAMAckkIAAADAAADAAADAEbBR0EAPkYA////////////////////////////////////////////////////////////////////////////////////////////AAAB4AAAgIAFIQALfkEAAAABCfAAAAABAZ8tdEK/AAADAAADAAADAAADAAADAAADAAADAAADAFHTAfujIUAYRtY0f/43cIs2OjxACR2hMR1QBGYX+34JGAAAAwAAAwAAAwKEJ6AAAAMAAAMAAAMA3oFHQQA/QhAAAOuMfgD//////////////////////////////////////////////////////////////////////////////wAAAeAAAIDACjEAC7aBEQALmmEAAAABCfAAAAABAZ8vakK/AAADAAADAAADAAADAAADAAADAAADAAADAFHr8vzfL5avJH2CZUo8ZsiEmmSAAWAcJ6PeAHqsAc+AccAAAAMAAAMAABghXQAAAwAAAwAAAwAGfEdBADBRAP//////////////////////////////////////////////////////////////////////////////////////////////////////////AAAB4AAAgMAKMQANQyERAAu2gQAAAAEJ8AAAAAFBmzRJqEFsmUwIZ//+nhAAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAGVAR0EAMTQQAAD5nH4A////////////////////////////////////////////////////////////AAAB4AAAgMAKMQANCuERAAvSoQAAAAEJ8AAAAAFBn1JFFSwv/wAAAwAAAwAAAwAAAwAAAwAAAwAAAwAADdRFmvrVE0KXDOEjZld1EdgtlZzXJOApGFMhcE0AEdAogjJg3Jz4BoSfWOEBMQAAAwAAAwAAAwBySQgAAAMAAAMAAAMARsFHQQAyRgD///////////////////////////////////////////////////////////////////////////////////////////8AAAHgAACAgAUhAAvuwQAAAAEJ8AAAAAEBn3F0Qr8AAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAUdMB+6MhQBhG1jR//jdwizY6PEAJHaExHVAEZhf7fgkYAAADAAADAAADAoQnoAAAAwAAAwAAAwDegEdBADNCEAABB6x+AP//////////////////////////////////////////////////////////////////////////////AAAB4AAAgMAKMQANJwERAA0K4QAAAAEJ8AAAAAEBn3NqQr8AAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAUevy/N8vlq8kfYJlSjxmyISaZIABYBwno94AeqwBz4BxwAAAAwAAAwAAGCFdAAADAAADAAADAAZ8R0EANFIA////////////////////////////////////////////////////////////////////////////////////////////////////////////AAAB4AAAgMAKMQANs6ERAA0nAQAAAAEJ8AAAAAFBm3hJqEFsmUwIV//+OEAAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAAMAAYtHQQA1NBAAARW8fgD///////////////////////////////////////////////////////////8AAAHgAACAwAoxAA17YREADUMhAAAAAQnwAAAAAUGflkUVLC//AAADAAADAAADAAADAAADAAADAAADAAAN1EWa+tUTQpcM4SNmV3UR2C2VnNck4CkYUyFwTQAR0CiCMmDcnPgGhJ9Y4QExAAADAAADAAADAHJJCAAAAwAAAwAAAwBGwEdBADZGAP///////////////////////////////////////////////////////////////////////////////////////////wAAAeAAAICABSEADV9BAAAAAQnwAAAAAQGftXRCvwAAAwAAAwAAAwAAAwAAAwAAAwAAAwAAAwBR0wH7oyFAGEbWNH/+N3CLNjo8QAkdoTEdUARmF/t+CRgAAAMAAAMAAAMChCegAAADAAADAAADAN6BR0EAN0IQAAEjzH4A//////////////////////////////////////////////////////////////////////////////8AAAHgAACAwAoxAA2XgREADXthAAAAAQnwAAAAAQGft2pCvwAAAwAAAwAAAwAAAwAAAwAAAwAAAwAAAwBR6/L83y+WryR9gmVKPGbIhJpkgAFgHCej3gB6rAHPgHHAAAADAAADAAAYIV0AAAMAAAMAAAMABn0=" + webUI["html/lang/de.json"] = "ewogICJtYWluTWVudSI6IHsKICAgICJpdGVtIjp7CiAgICAgICJwbGF5bGlzdCI6ICJXaWVkZXJnYWJlbGlzdGUiLAogICAgICAicG1zSUQiOiAiUE1TIElEIiwKICAgICAgImZpbHRlciI6ICJGaWx0ZXIiLAogICAgICAieG1sdHYiOiAiWE1MVFYiLAogICAgICAibWFwcGluZyI6ICJNYXBwaW5nIiwKICAgICAgInVzZXJzIjogIkJlbnV0emVyIiwKICAgICAgInNldHRpbmdzIjogIkVpbnN0ZWxsdW5nZW4iLAogICAgICAibG9nIjogIkxvZyIsCiAgICAgICJsb2dvdXQiOiAiQWJtZWxkZW4iCiAgICB9LAogICAgImhlYWRsaW5lIjogewogICAgICAicGxheWxpc3QiOiAiTG9rYWxlIG9kZXIgZW50ZmVybnRlIFdpZWRlcmdhYmVsaXN0ZW4iLAogICAgICAiZmlsdGVyIjogIkZpbHRlcm4gdm9uIEthbsOkbGVuIiwKICAgICAgInhtbHR2IjogIkxva2FsZSBvZGVyIGVudGZlcm50ZSBYTUxUViBEYXRlaWVuIiwKICAgICAgIm1hcHBpbmciOiAiS2Fuw6RsZSBkZXIgV2llZGVyZ2FiZWxpc3RlbiBFUEcgS2Fuw6RsZW4genVvcmRuZW4iLAogICAgICAidXNlcnMiOiAiQmVudXR6ZXJ2ZXJ3YWx0dW5nIiwKICAgICAgInNldHRpbmdzIjogIkVpbnN0ZWxsdW5nZW4iLAogICAgICAibG9nIjogIkxvZyIsCiAgICAgICJsb2dvdXQiOiAiQWJtZWxkZW4iCiAgICB9CiAgfSwKICAiY29uZmlybSI6ewogICAgInJlc3RvcmUiOiAiQWxsZSBEYXRlbiB3ZXJkZW4gbWl0IGRlbmVuIGF1cyBkZW0gQmFja3VwIGVyc2V0enQuIFdpZWRlcmhlc3RlbGx1bmcgZHVyY2hmw7xocmVuPyIKICB9LAogICJhbGVydCI6IHsKICAgICJmaWxlTG9hZGluZ0Vycm9yIjogIkRhdGVpIGtvbm50ZSBuaWNodCBnZWxhZGVuIHdlcmRlbiIsCiAgICAiaW52YWxpZENoYW5uZWxOdW1iZXIiOiAiVW5nw7xsdGlnZSBLYW5hbG51bW1lciIKICB9LAogICJidXR0b24iOnsKICAgICJiYWNrIjogIlp1csO8Y2siLAogICAgImJhY2t1cCI6ICJCYWNrdXAiLAogICAgImJ1bGtFZGl0IjogIk1hc3NlbmJlYXJiZWl0dW5nIiwKICAgICJjYW5jZWwiOiAiQWJicmVjaGVuIiwKICAgICJkZWxldGUiOiAiTMO2c2NoZW4iLAogICAgImRvbmUiOiAiRmVydGlnIiwKICAgICJsb2dpbiI6ICJBbm1lbGRlbiIsCiAgICAibmV3IjogIk5ldSIsCiAgICAibmV4dCI6ICJXZWl0ZXIiLAogICAgInJlc3RvcmUiOiAiV2llZGVyaGVyc3RlbGxlbiIsCiAgICAic2F2ZSI6ICJTcGVjaWhlcm4iLAogICAgInNlYXJjaCI6ICJTdWNoZW4iLAogICAgInVwZGF0ZSI6ICJVcGRhdGUiLAogICAgImNyYWV0ZUFjY291bnQiOiAiQWNjb3VudCBlcnN0ZWxsZW4iLAogICAgInJlc2V0bG9ncyI6ICJMb2dzIGzDtnNjaGVuIiwKICAgICJ1cGxvYWRMb2dvIjogIkxvZ28gaG9jaGxhZGVuIgogIH0sCiAgImZpbHRlciI6IHsKICAgICJ0YWJsZSI6IHsKICAgICAgIm5hbWUiOiAiRmlsdGVyIE5hbWUiLAogICAgICAidHlwZSI6ICJGaWx0ZXIgVHlwIiwKICAgICAgImZpbHRlciI6ICJGaWx0ZXIiCiAgICB9LAogICAgImN1c3RvbSI6ICJCZW51dHplcmRlZmluaWVydCIsCiAgICAiZ3JvdXAiOiAiR3J1cHBlIiwKICAgICJuYW1lIjogewogICAgICAidGl0bGUiOiAiRmlsdGVyIE5hbWUiLAogICAgICAicGxhY2Vob2xkZXIiOiAiRmlsdGVyIE5hbWUiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJkZXNjcmlwdGlvbiI6IHsKICAgICAgInRpdGxlIjogIkJlc2NocmVpYnVuZyIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJCZXNjaHJlaWJ1bmciLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJ0eXBlIjogewogICAgICAidGl0bGUiOiAiVHlwIiwKICAgICAgImdyb3VwVGl0bGUiOiAiR3JvdXBlbnRpdGVsIiwKICAgICAgImN1c3RvbUZpbHRlciI6ICJCZW51dHplcmRlZmluaWVydGVyIEZpbHRlciIKICAgIH0sCiAgICAiY2FzZVNlbnNpdGl2ZSI6IHsKICAgICAgInRpdGxlIjogIkdyb8OfLSAvIEtsZWluc2NocmVpYnVuZyBiZWFjaHRlbiIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJmaWx0ZXJSdWxlIjogewogICAgICAidGl0bGUiOiAiRmlsdGVycmVnZWwiLAogICAgICAicGxhY2Vob2xkZXIiOiAiU3BvcnQge0hEfSAhe0VTLElUfSIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgImZpbHRlckdyb3VwIjogewogICAgICAidGl0bGUiOiAiR3JvdXBlbnRpdGVsIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJHcnVwcGUgYXVzIGRlciBNM1UgYXVzd8OkaGxlbi4gKFrDpGhsZXIpPGJyPsOkbmRlcnVuZyBkZXMgR3J1cHBlbnRpdGVscyBpbiBkZXIgTTNVIG1hY2h0IGRlbiBGaWx0ZXIgdW5nw7xsdGlnLiIKICAgIH0sCiAgICAiaW5jbHVkZSI6IHsKICAgICAgInRpdGxlIjogIk11c3MgYmVpbmhhbHRlbiIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJGSEQsVUhEIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkthbmFsbmFtZSBtdXNzIGRpZXNlIFfDtnJ0ZXIgZW50aGFsdGVuLjxicj4oS29tbWFnZXRyZW5udCkgS29tbWEgYmVkZXV0ZXQgb2RlciIKICAgIH0sCiAgICAiZXhjbHVkZSI6IHsKICAgICAgInRpdGxlIjogIkRhcmYgbmljaHQgYmVpbmhhbHRlbiIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJFUyxJVCIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJLYW5hbG5hbWUgZGFyZiBkaWVzZSBXw7ZydGVyIG5pY2h0IGVudGhhbHRlbi48YnI+KEtvbW1hZ2V0cmVubnQpS29tbWEgYmVkZXV0ZXQgb2RlciIKICAgIH0KCiAgfSwKICAicGxheWxpc3QiOiB7CiAgICAidGFibGUiOiB7CiAgICAgICJwbGF5bGlzdCI6ICJXaWVkZXJnYWJlbGlzdGUiLAogICAgICAidHVuZXIiOiAiVHVuZXIiLAogICAgICAibGFzdFVwZGF0ZSI6ICJMZXR6dGUgQWt0dWFsaXNpZXJ1bmciLAogICAgICAiYXZhaWxhYmlsaXR5IjogIlZlcmbDvGdiYXJrZWl0IiwKICAgICAgInR5cGUiOiAiVHlwIiwKICAgICAgInN0cmVhbXMiOiAiU3RyZWFtcyIsCiAgICAgICJncm91cFRpdGxlIjogIkdyb3VwZW50aXRlbCIsCiAgICAgICJ0dmdJRCI6ICJ0dmctaWQiLAogICAgICAidW5pcXVlSUQiOiAiRWluZGV1dGlnZSBJRCIKICAgIH0sCiAgICAicGxheWxpc3RUeXBlIjogewogICAgICAidGl0bGUiOiAiV2llZGVyZ2FiZWxpc3RlbiBUeXAiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0sCiAgICAidHlwZSI6IHsKICAgICAgInRpdGxlIjogIlR5cCIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJuYW1lIjogewogICAgICAidGl0bGUiOiAiTmFtZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJOYW1lIGRlciBXaWVkZXJnYWJlbGlzdGUiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJkZXNjcmlwdGlvbiI6IHsKICAgICAgInRpdGxlIjogIkJlc2NocmVpYnVuZyIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJCZXNjaHJlaWJ1bmciLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJmaWxlTTNVIjogewogICAgICAidGl0bGUiOiAiTTNVIERhdGVpIiwKICAgICAgInBsYWNlaG9sZGVyIjogIkRhdGVpcGZhZCBvZGVyIFVSTCBkZXIgTTNVIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0sCiAgICAiZmlsZUhESFIiOiB7CiAgICAgICJ0aXRsZSI6ICJIREhvbWVSdW4gSVAiLAogICAgICAicGxhY2Vob2xkZXIiOiAiSVAgQWRyZXNzIHVuZCBQb3J0ICgxOTIuMTY4LjEuMTA6NTAwNCkiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJ0dW5lciI6IHsKICAgICAgInRpdGxlIjogIlR1bmVyIC8gU3RyZWFtcyIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiQW56YWhsIGRlciBWZXJiaW5kdW5nZW4gZGllIGdsZWljaHplaXRpZyBhdWZnZWJhdXQgd2VyZGVuIGTDvHJmZW4uPGJyPk51ciBtaXQgYWt0aXZpZXJ0ZW4gQnVmZmVyIHZlcmbDvGdiYXIuPGJyPk5ldWUgRWluc3RlbGx1bmdlbiB3ZXJkZW4gZXJzdCBuYWNoIEJlZW5kZW4gYWxsZXIgU3RyZWFtcyDDvGJlcm5vbW1lbi4iCiAgICB9CiAgfSwKICAieG1sdHYiOiB7CiAgICAidGFibGUiOiB7CiAgICAgICJndWlkZSI6ICJHdWlkZSIsCiAgICAgICJsYXN0VXBkYXRlIjogIkxhc3QgVXBkYXRlIiwKICAgICAgImF2YWlsYWJpbGl0eSI6ICJBdmFpbGFiaWxpdHkiLAogICAgICAiY2hhbm5lbHMiOiAiQ2hhbm5lbHMiLAogICAgICAicHJvZ3JhbXMiOiAiUHJvZ3JhbXMiCiAgICB9LAogICAgIm5hbWUiOiB7CiAgICAgICJ0aXRsZSI6ICJOYW1lIiwKICAgICAgInBsYWNlaG9sZGVyIjogIkd1aWRlIG5hbWUiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJkZXNjcmlwdGlvbiI6IHsKICAgICAgInRpdGxlIjogIkRlc2NyaXB0aW9uIiwKICAgICAgInBsYWNlaG9sZGVyIjogIkRlc2NyaXB0aW9uIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0sCiAgICAiZmlsZVhNTFRWIjogewogICAgICAidGl0bGUiOiAiWE1MVFYgRmlsZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJGaWxlIHBhdGggb3IgVVJMIG9mIHRoZSBYTUxUViIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9CiAgfSwKICAibWFwcGluZyI6IHsKICAgICJ0YWJsZSI6IHsKICAgICAgImNoTm8iOiAiQ2guIE5vLiIsCiAgICAgICJsb2dvIjogIkxvZ28iLAogICAgICAiY2hhbm5lbE5hbWUiOiAiQ2hhbm5lbCBOYW1lIiwKICAgICAgInBsYXlsaXN0IjogIlBsYXlsaXN0IiwKICAgICAgImdyb3VwVGl0bGUiOiAiR3JvdXAgVGl0bGUiLAogICAgICAieG1sdHZGaWxlIjogIlhNTFRWIEZpbGUiLAogICAgICAieG1sdHZJRCI6ICJYTUxUViBJRCIKICAgIH0sCiAgICAiYWN0aXZlIjogewogICAgICAidGl0bGUiOiAiQWN0aXZlIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgImNoYW5uZWxOYW1lIjogewogICAgICAidGl0bGUiOiAiQ2hhbm5lbCBOYW1lIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgInVwZGF0ZUNoYW5uZWxOYW1lIjogewogICAgICAidGl0bGUiOiAiVXBkYXRlIENoYW5uZWwgTmFtZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJjaGFubmVsTG9nbyI6IHsKICAgICAgInRpdGxlIjogIkxvZ28gVVJMIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgInVwZGF0ZUNoYW5uZWxMb2dvIjogewogICAgICAidGl0bGUiOiAiVXBkYXRlIENoYW5uZWwgTG9nbyIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJlcGdDYXRlZ29yeSI6IHsKICAgICAgInRpdGxlIjogIkVQRyBDYXRlZ29yeSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJtM3VHcm91cFRpdGxlIjogewogICAgICAidGl0bGUiOiAiR3JvdXAgVGl0bGUgKHh0ZXZlLm0zdSkiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0sCiAgICAieG1sdHZGaWxlIjogewogICAgICAidGl0bGUiOiAiWE1MVFYgRmlsZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJ4bWx0dkNoYW5uZWwiOiB7CiAgICAgICJ0aXRsZSI6ICJYTUxUViBDaGFubmVsIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9CiAgfSwKICAidXNlcnMiOiB7CiAgICAidGFibGUiOiB7CiAgICAgICJ1c2VybmFtZSI6ICJVc2VybmFtZSIsCiAgICAgICJwYXNzd29yZCI6ICJQYXNzd29yZCIsCiAgICAgICJ3ZWIiOiAiV0VCIiwKICAgICAgInBtcyI6ICJQTVMiLAogICAgICAibTN1IjogIk0zVSIsCiAgICAgICJ4bWwiOiAiWE1MIiwKICAgICAgImFwaSI6ICJBUEkiCiAgICB9LAogICAgInVzZXJuYW1lIjogewogICAgICAidGl0bGUiOiAiVXNlcm5hbWUiLAogICAgICAicGxhY2Vob2xkZXIiOiAiVXNlcm5hbWUiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJwYXNzd29yZCI6IHsKICAgICAgInRpdGxlIjogIlBhc3N3b3JkIiwKICAgICAgInBsYWNlaG9sZGVyIjogIlBhc3Nvd29yZCIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgImNvbmZpcm0iOiB7CiAgICAgICJ0aXRsZSI6ICJDb25maXJtIiwKICAgICAgInBsYWNlaG9sZGVyIjogIlBhc3N3b3JkIGNvbmZpcm0iLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJ3ZWIiOiB7CiAgICAgICJ0aXRsZSI6ICJXZWIgQWNjZXNzIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgInBtcyI6IHsKICAgICAgInRpdGxlIjogIlBNUyBBY2Nlc3MiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0sCiAgICAibTN1IjogewogICAgICAidGl0bGUiOiAiTTNVIEFjY2VzcyIsCiAgICAgICJwbGFjZWhvbGRlciI6ICIiLAogICAgICAiZGVzY3JpcHRpb24iOiAiIgogICAgfSwKICAgICJ4bWwiOiB7CiAgICAgICJ0aXRsZSI6ICJYTUwgQWNjZXNzIiwKICAgICAgInBsYWNlaG9sZGVyIjogIiIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICIiCiAgICB9LAogICAgImFwaSI6IHsKICAgICAgInRpdGxlIjogIkFQSSBBY2Nlc3MiLAogICAgICAicGxhY2Vob2xkZXIiOiAiIiwKICAgICAgImRlc2NyaXB0aW9uIjogIiIKICAgIH0KICB9LAogICJzZXR0aW5ncyI6IHsKICAgICJjYXRlZ29yeSI6IHsKICAgICAgImdlbmVyYWwiOiAiR2VuZXJhbCIsCiAgICAgICJmaWxlcyI6ICJGaWxlcyIsCiAgICAgICJzdHJlYW1pbmciOiAiU3RyZWFtaW5nIiwKICAgICAgImJhY2t1cCI6ICJCYWNrdXAiLAogICAgICAiYXV0aGVudGljYXRpb24iOiAiQXV0aGVudGljYXRpb24iCiAgICB9LAogICAgInVwZGF0ZSI6IHsKICAgICAgInRpdGxlIjogIlNjaGVkdWxlIGZvciB1cGRhdGluZyAoUGxheWxpc3QsIFhNTFRWLCBCYWNrdXApIiwKICAgICAgInBsYWNlaG9sZGVyIjogIjAwMDAsMTAwMCwyMDAwIiwKICAgICAgImRlc2NyaXB0aW9uIjogIlRpbWUgaW4gMjQgaG91ciBmb3JtYXQgKDA4MDAgPSA4OjAwIGFtKS4gTW9yZSB0aW1lcyBjYW4gYmUgZW50ZXJlZCBjb21tYSBzZXBhcmF0ZWQuIgogICAgfSwKICAgICJhcGkiOiB7CiAgICAgICJ0aXRsZSI6ICJBUEkgaW50ZXJmYWNlIiwKICAgICAgImRlc2NyaXB0aW9uIjogIlZpYSBBUEkgaW50ZXJmYWNlIGl0IGlzIHBvc3NpYmxlIHRvIHNlbmQgY29tbWFuZHMgdG8geFRlVmUuIEFQSSBkb2N1bWVudGF0aW9uIGlzIDxhIGhyZWY9J2h0dHBzOi8veHRldmUuZGU/c2Nyb2xsPWFwaSc+aGVyZTwvYT4iCiAgICB9LAogICAgImVwZ1NvdXJjZSI6IHsKICAgICAgInRpdGxlIjogIlNlbGVjdGlvbiBvZiB0aGUgRVBHIHNvdXJjZSIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJQTVM6PGJyPi0gVXNlIEVQRyBkYXRhIGZyb20gUGxleCBvciBFbWJ5IDxicj48YnI+WEVQRzo8YnI+LSBVc2Ugb2Ygb25lIG9yIG1vcmUgWE1MVFYgZmlsZXM8YnI+LSBDaGFubmVsIG1hbmFnZW1lbnQ8YnI+LSBNM1UgLyBYTUxUViBleHBvcnQgKEhUVFAgbGluayBmb3IgSVBUViBhcHBzKSIKICAgIH0sCiAgICAidHVuZXIiOnsKICAgICAgInRpdGxlIjogIk51bWJlciBvZiB0dW5lcnMiLAogICAgICAiZGVzY3JpcHRpb24iOiAiTnVtYmVyIG9mIHBhcmFsbGVsIGNvbm5lY3Rpb25zIHRoYXQgY2FuIGJlIGVzdGFibGlzaGVkIHRvIHRoZSBwcm92aWRlci48YnI+QXZhaWxhYmxlIGZvcjogUGxleCwgRW1ieSAoSERIUiksIE0zVSAod2l0aCBhY3RpdmUgYnVmZmVyKS48YnI+QWZ0ZXIgYSBjaGFuZ2UsIHhUZVZlIG11c3QgYmUgZGVsZXRlIGluIHRoZSBQbGV4IC8gRW1ieSBEVlIgc2V0dGluZ3MgYW5kIHNldCB1cCBhZ2Fpbi4iCiAgICB9LAogICAgImZpbGVzVXBkYXRlIjogewogICAgICAidGl0bGUiOiAiVXBkYXRlcyBhbGwgcGxheWxpc3RzIGFuZCBYTUxUViBmaWxlcyBhdCBzdGFydHVwIiwKICAgICAgImRlc2NyaXB0aW9uIjogIlVwZGF0ZXMgYWxsIHBsYXlsaXN0cyBhbmQgWE1MVFYgZmlsZXMgYXQgc3RhcnR1cC4iCiAgICB9LAogICAgImNhY2hlSW1hZ2VzIjogewogICAgICAidGl0bGUiOiAiSW1hZ2UgY2FjaGluZyIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJBbGwgaW1hZ2VzIGZyb20gdGhlIFhNTFRWIGZpbGUgYXJlIGNhY2hlZCwgYWxsb3dpbmcgZmFzdGVyIHJlbmRlcmluZyBvZiB0aGUgZ3JpZCBpbiB0aGUgY2xpZW50Ljxicj5Eb3dubG9hZGluZyB0aGUgaW1hZ2VzIG1heSB0YWtlIGEgd2hpbGUgYW5kIHdpbGwgYmUgZG9uZSBpbiB0aGUgYmFja2dyb3VuZC4iCiAgICB9LAogICAgInh0ZXZlQXV0b1VwZGF0ZSI6IHsKICAgICAgInRpdGxlIjogIkF1dG9tYXRpYyB1cGRhdGUgb2YgeFRlVmUiLAogICAgICAiZGVzY3JpcHRpb24iOiAiSWYgYSBuZXcgdmVyc2lvbiBvZiB4VGVWZSBpcyBhdmFpbGFibGUsIGl0IHdpbGwgYmUgYXV0b21hdGljYWxseSBpbnN0YWxsZWQuIgogICAgfSwKICAgICJzdHJlYW1CdWZmZXJpbmciOiB7CiAgICAgICJ0aXRsZSI6ICJTdHJlYW0gYnVmZmVyaW5nIFtFeHBlcmltZW50YWxdIiwKICAgICAgImRlc2NyaXB0aW9uIjogIi0gVGhlIHN0cmVhbSBpcyBwYXNzZWQgZnJvbSB4VGVWZSB0byBQbGV4IC8gRW1ieSAvIE0zVSBQbGF5ZXI8YnI+LSBTbWFsbCBqZXJraW5nIG9mIHRoZSBzdHJlYW1zIGNhbiBiZSBjb21wZW5zYXRlZDxicj4tIEhMUyAvIE0zVTggc3VwcG9ydCA8YnI+LSBCdWZmZXIgc2l6ZSA9IDEgTUIiCiAgICB9LAogICAgImJ1ZmZlclRpbWVvdXQiOiB7CiAgICAgICJ0aXRsZSI6ICJUaW1lb3V0IGZvciBuZXcgY2xpZW50IGNvbm5lY3Rpb25zIiwKICAgICAgImRlc2NyaXB0aW9uIjogIlRoZSB4VGVWZSBidWZmZXIgd2FpdHMgdW50aWwgbmV3IGNsaWVudCBjb25uZWN0aW9ucyBhcmUgZXN0YWJsaXNoZWQuIEhlbHBmdWwgZm9yIGZhc3QgY2hhbm5lbCBzd2l0Y2hpbmcuIFZhbHVlIGluIG1pbGxpc2Vjb25kcy4iLAogICAgICAicGxhY2Vob2xkZXIiOiAiMTAwIgogICAgfSwKICAgICJ1c2VyQWdlbnQiOiB7CiAgICAgICJ0aXRsZSI6ICJVc2VyIGFnZW50IiwKICAgICAgImRlc2NyaXB0aW9uIjogIlVzZXIgQWdlbnQgZm9yIEhUVFAgcmVxdWVzdHMiLAogICAgICAicGxhY2Vob2xkZXIiOiAieFRlVmUiCiAgICB9LAogICAgImJhY2t1cFBhdGgiOiB7CiAgICAgICJ0aXRsZSI6ICJMb2NhdGlvbiBmb3IgYXV0b21hdGljIGJhY2t1cHMiLAogICAgICAicGxhY2Vob2xkZXIiOiAiL21udC9kYXRhL2JhY2t1cC94dGV2ZS8iLAogICAgICAiZGVzY3JpcHRpb24iOiAiQmVmb3JlIGFueSB1cGRhdGUgb2YgdGhlIHByb3ZpZGVyIGRhdGEgYnkgdGhlIHNjaGVkdWxlLCB4VGVWZSBjcmVhdGVzIGEgYmFja3VwLiBUaGUgcGF0aCBmb3IgdGhlIGF1dG9tYXRpYyBiYWNrdXBzIGNhbiBiZSBjaGFuZ2VkLiB4VGVWZSByZXF1aXJlcyB3cml0ZSBwZXJtaXNzaW9uIGZvciB0aGlzIGZvbGRlci4iCiAgICB9LAogICAgInRlbXBQYXRoIjogewogICAgICAidGl0bGUiOiAiTG9jYXRpb24gZm9yIHRoZSB0ZW1wb3JhcnkgZmlsZXMiLAogICAgICAicGxhY2Vob2xkZXIiOiAiL3RtcC94dGV2ZS8iLAogICAgICAiZGVzY3JpcHRpb24iOiAiTG9jYXRpb24gZm9yIHRoZSBidWZmZXIgZmlsZXMuIgogICAgfSwKICAgICJiYWNrdXBLZWVwIjogewogICAgICAidGl0bGUiOiAiTnVtYmVyIG9mIGJhY2t1cHMgdG8ga2VlcCIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJOdW1iZXIgb2YgYmFja3VwcyB0byBrZWVwLiBPbGRlciBiYWNrdXBzIGFyZSBhdXRvbWF0aWNhbGx5IGRlbGV0ZWQuIgogICAgfSwKICAgICJhdXRoZW50aWNhdGlvbldFQiI6IHsKICAgICAgInRpdGxlIjogIlVzZXIgYXV0aGVudGljYXRpb24iLAogICAgICAiZGVzY3JpcHRpb24iOiAiQWNjZXNzIHRvIHhUZVZlIHJlcXVpcmVzIGF1dGhlbnRpY2F0aW9uLiIKICAgIH0sCiAgICAiYXV0aGVudGljYXRpb25QTVMiOiB7CiAgICAgICJ0aXRsZSI6ICJQbGV4IGF1dGhlbnRpY2F0aW9uIiwKICAgICAgImRlc2NyaXB0aW9uIjogIlBsZXggcmVxdWVzdHMgYXJlIG9ubHkgcG9zc2libGUgd2l0aCBhdXRoZW50aWNhdGlvbi4gPGJyPjxiPldhcm5pbmchISE8L2I+IEFmdGVyIGFjdGl2YXRpbmcgdGhpcyBmdW5jdGlvbiB4VGVWZSBtdXN0IGJlIGRlbGV0ZSBpbiB0aGUgUE1TIERWUiBzZXR0aW5ncyBhbmQgc2V0IHVwIGFnYWluLiIKICAgIH0sCiAgICAiYXV0aGVudGljYXRpb25NM1UiOiB7CiAgICAgICJ0aXRsZSI6ICJNM1UgYXV0aGVudGljYXRpb24iLAogICAgICAiZGVzY3JpcHRpb24iOiAiRG93bmxvYWRpbmcgdGhlIHh0ZXZlLm0zdSBmaWxlIHZpYSBhbiBIVFRQIHJlcXVlc3QgaXMgb25seSBwb3NzaWJsZSB3aXRoIGF1dGhlbnRpY2F0aW9uLiIKICAgIH0sCiAgICAiYXV0aGVudGljYXRpb25YTUwiOiB7CiAgICAgICJ0aXRsZSI6ICJYRVBHIGF1dGhlbnRpY2F0aW9uIiwKICAgICAgImRlc2NyaXB0aW9uIjogIkRvd25sb2FkaW5nIHRoZSB4dGV2ZS54bWwgZmlsZSB2aWEgYW4gSFRUUCByZXF1ZXN0IGlzIG9ubHkgcG9zc2libGUgd2l0aCBhdXRoZW50aWNhdGlvbiIKICAgIH0sCiAgICAiYXV0aGVudGljYXRpb25BUEkiOiB7CiAgICAgICJ0aXRsZSI6ICJBUEkgYXV0aGVudGljYXRpb24iLAogICAgICAiZGVzY3JpcHRpb24iOiAiQWNjZXNzIHRvIHRoZSBBUEkgaW50ZXJmYWNlIGlzIG9ubHkgcG9zc2libGUgd2l0aCBhdXRoZW50aWNhdGlvbi4iCiAgICB9CiAgfSwKICAid2l6YXJkIjogewogICAgImVwZ1NvdXJjZSI6IHsKICAgICAgInRpdGxlIjogIlNlbGVjdGlvbiBvZiB0aGUgRVBHIHNvdXJjZSIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJQTVM6PGJyPi0gVXNlIEVQRyBkYXRhIGZyb20gUGxleCBvciBFbWJ5IDxicj48YnI+WEVQRzo8YnI+LSBVc2Ugb2Ygb25lIG9yIG1vcmUgWE1MVFYgZmlsZXM8YnI+LSBDaGFubmVsIG1hbmFnZW1lbnQ8YnI+LSBNM1UgLyBYTUxUViBleHBvcnQgKEhUVFAgbGluayBmb3IgSVBUViBhcHBzKSIKICAgIH0sCiAgICAidHVuZXIiOnsKICAgICAgInRpdGxlIjogIk51bWJlciBvZiB0dW5lcnMiLAogICAgICAiZGVzY3JpcHRpb24iOiAiTnVtYmVyIG9mIHBhcmFsbGVsIGNvbm5lY3Rpb25zIHRoYXQgY2FuIGJlIGVzdGFibGlzaGVkIHRvIHRoZSBwcm92aWRlci48YnI+QXZhaWxhYmxlIGZvcjogUGxleCwgRW1ieSAoSERIUiksIE0zVSAod2l0aCBhY3RpdmUgYnVmZmVyKS48YnI+QWZ0ZXIgYSBjaGFuZ2UsIHhUZVZlIG11c3QgYmUgZGVsZXRlIGluIHRoZSBQbGV4IC8gRW1ieSBEVlIgc2V0dGluZ3MgYW5kIHNldCB1cCBhZ2Fpbi4iCiAgICB9LAogICAgIm0zdSI6IHsKICAgICAgInRpdGxlIjogIk0zVSBQbGF5bGlzdCIsCiAgICAgICJkZXNjcmlwdGlvbiI6ICJMb2NhbCBvciByZW1vdGUgcGxheWxpc3RzIgogICAgfSwKICAgICJ4bWx0diI6IHsKICAgICAgInRpdGxlIjogIlhNTFRWIEZpbGUiLAogICAgICAiZGVzY3JpcHRpb24iOiAiTG9jYWwgb3IgcmVtb3RlIFhNTFRWIGZpbGUiCiAgICB9CiAgfSwKICAibG9naW4iOiB7CiAgICAiZmFpbGVkIjogIlVzZXIgYXV0aGVudGljYXRpb24gZmFpbGVkIiwKICAgICJoZWFkbGluZSI6ICJMb2dpbiIsCiAgICAidXNlcm5hbWUiOiB7CiAgICAgICJ0aXRsZSI6ICJVc2VybmFtZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJVc2VybmFtZSIKICAgIH0sCiAgICAicGFzc3dvcmQiOiB7CiAgICAgICJ0aXRsZSI6ICJQYXNzd29yZCIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJQYXNzd29yZCIKICAgIH0KICB9LAogICJhY2NvdW50IjogewogICAgImZhaWxlZCI6ICJQYXNzd29yZCBkb2VzIG5vdCBtYXRjaCIsCiAgICAiaGVhZGxpbmUiOiAiQ3JlYXRlIHVzZXIgYWNjb3VudCIsCiAgICAidXNlcm5hbWUiOiB7CiAgICAgICJ0aXRsZSI6ICJVc2VybmFtZSIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJVc2VybmFtZSIKICAgIH0sCiAgICAicGFzc3dvcmQiOiB7CiAgICAgICJ0aXRsZSI6ICJQYXNzd29yZCIsCiAgICAgICJwbGFjZWhvbGRlciI6ICJQYXNzd29yZCIKICAgIH0sCiAgICAiY29uZmlybSI6IHsKICAgICAgInRpdGxlIjogIkNvbmZpcm0iLAogICAgICAicGxhY2Vob2xkZXIiOiAiQ29uZmlybSIKICAgIH0KICB9Cn0K" + webUI["html/create-first-user.html"] = "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCwgaW5pdGlhbC1zY2FsZT0xLjAiIC8+IAogICAgPHRpdGxlPnhUZVZlPC90aXRsZT4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iY3NzL3NjcmVlbi5jc3MiIHR5cGU9InRleHQvY3NzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iY3NzL2Jhc2UuY3NzIiB0eXBlPSJ0ZXh0L2NzcyI+CiAgICA8c2NyaXB0IGxhbmd1YWdlPSJqYXZhc2NyaXB0IiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiIHNyYz0ianMvbmV0d29ya190cy5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0IGxhbmd1YWdlPSJqYXZhc2NyaXB0IiB0eXBlPSJ0ZXh0L2phdmFzY3JpcHQiIHNyYz0ianMvYXV0aGVudGljYXRpb25fdHMuanMiPjwvc2NyaXB0PgogIDwvaGVhZD4KCiAgICA8Ym9keT4KICAgICAgICAgIAogICAgICA8ZGl2IGlkPSJoZWFkZXIiIGNsYXNzPSJpbWdDZW50ZXIiPjwvZGl2PgogICAgICA8ZGl2IGlkPSJib3giPgogICAgICAgIDxkaXYgaWQ9ImhlYWRsaW5lIj4KICAgICAgICAgIDxoMSBpZD0iaGVhZC10ZXh0IiBjbGFzcz0iY2VudGVyIj57ey5hY2NvdW50LmhlYWRsaW5lfX08L2gxPgogICAgICAgIDwvZGl2PgogICAgICAgIDxwIGlkPSJlcnIiIGNsYXNzPSJlcnJvck1zZyBjZW50ZXIiPjwvcD4gICAKICAgICAgICA8ZGl2IGlkPSJjb250ZW50Ij4KICAgICAgICAgICAgPGZvcm0gaWQ9ImF1dGhlbnRpY2F0aW9uIiBhY3Rpb249Ii93ZWIvIiBtZXRob2Q9InBvc3QiPgogICAgICAgICAgICAgIDxoNT57ey5hY2NvdW50LnVzZXJuYW1lLnRpdGxlfX06PC9oNT4KICAgICAgICAgICAgICA8aW5wdXQgaWQ9InVzZXJuYW1lIiB0eXBlPSJ0ZXh0IiBuYW1lPSJ1c2VybmFtZSIgcGxhY2Vob2xkZXI9IlVzZXJuYW1lIiB2YWx1ZT0iIj4KICAgICAgICAgICAgICA8aDU+e3suYWNjb3VudC5wYXNzd29yZC50aXRsZX19OjwvaDU+CiAgICAgICAgICAgICAgPGlucHV0IGlkPSJwYXNzd29yZCIgdHlwZT0icGFzc3dvcmQiIG5hbWU9InBhc3N3b3JkIiBwbGFjZWhvbGRlcj0iUGFzc3dvcmQiIHZhbHVlPSIiPgogICAgICAgICAgICAgIDxoNT57ey5hY2NvdW50LmNvbmZpcm0udGl0bGV9fTo8L2g1PgogICAgICAgICAgICAgIDxpbnB1dCBpZD0iY29uZmlybSIgIHR5cGU9InBhc3N3b3JkIiBuYW1lPSJjb25maXJtIiAgcGxhY2Vob2xkZXI9IkNvbmZpcm0iIHZhbHVlPSIiPgogICAgICAgICAgICA8L2Zvcm0+CiAgICAgICAgPC9kaXY+CiAgICAgICAgCiAgICAgICAgPGRpdiBpZD0iYm94LWZvb3RlciI+CiAgICAgICAgICA8aW5wdXQgaWQ9InN1Ym1pdCIgY2xhc3M9IiIgdHlwZT0iYnV0dG9uIiB2YWx1ZT0ie3suYnV0dG9uLmNyYWV0ZUFjY291bnR9fSIgb25jbGljaz0iamF2YXNjcmlwdDogbG9naW4oKTsiPgogICAgICAgIDwvZGl2PgogICAgICAKICAgICAgICAKICAgICAgPC9kaXY+CiAgICA8L2JvZHk+CjwvaHRtbD4=" + webUI["html/img/users.png"] = "iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAsSwAALEsBpT2WqQAABCRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDx0aWZmOkNvbXByZXNzaW9uPjU8L3RpZmY6Q29tcHJlc3Npb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjI4ODwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+Mjg4PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NTA8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjUwPC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgICAgPGRjOnN1YmplY3Q+CiAgICAgICAgICAgIDxyZGY6QmFnLz4KICAgICAgICAgPC9kYzpzdWJqZWN0PgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0wOC0zMFQxNzowODozODwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CjU01MMAAANJSURBVGgF1ZnLaxNRFMYz2rpoFbW+SCsihaJQRMT6rPhAXAq6c+HChSguRFd1oRtd+A/oWhQ3oiiWFB91UbqRqlXXgoIIQhFbQTTQao2/GzLDZEjIPefO7SQHPjJz55zzfd/MZCa5N8h5ilKptJvWp8FekAed4Bv4Cj6Dh2A4CIJZPpszMHEV2MQ0SZdB0DROENMHjoEzQBoFCpZnbgYR58E/qfpE/kv2F2dmBvJ94G9ClHb3SiZGUBuASa3qGnVzjG3RmFmkKYrVHGB7e2zfdbOdBhc1TVyNnNSQNqg5wVURf/FdjRxpIEpzuIOio9JCtRHO2hrINkgJLfPFt6vaCII2WYrSpPVJi1RGuBrmTbxeSibIX5j3CUauAfOo9BXzNH4nMJ5TXREI1gHzqPQVRtc2zCyxJdAaGbclcMj7zi/jOdt6rZFhCH7akijznknqVEY4U78heSAhUuTekNSojFQIChIiYe57TtZrSY2LkecQzUjIBLljglz3VJ4qg+AXSDNGabbWXZ2wA6Qjabqg136hhHK6y60V8o2EGyl8/qHHpKZPGkYeQzyvIa9RM86XvFhjvOGQsxGIp2B50ZDJLuG2XZqnLO5rM3viGh9o0OZJon1bRLxxdHLcns1jJiYOAu2U0D2P0uStMXJdcVU+UdMlZ/NYgaA28ERg5ge5/R4l6VsjrANMWJiZJWeXnqm60vnxW90ul6u8ByaS4zX2i+S+qjGuGkrdSEWFzT1vlhlSC19Gui0UtnNriSfi6vX1ZWRjPcLEeGrzYqkb4SyvQGxvQnC93cF6BzIfx8gpYBsfSdycuei4AAR1ggugCCRh1lbugIF4vwXbhtisjfSDc+ARSOOf4lv6mNWv1VIj1ouQNF9K851gDzArtWbV1uYxS5o4zB+sp+AuKPC+cVv5RXwPuATMmUpreY1Wopgh+ybYKj4dFHWB+8DMwTZTjCHmsJUhEgfAF9DMcQtx9V8dHMyDqWZ2ENM2FL8yVV92kkY56GM5Lc6Z1vY0jbp5EJQnuqPLg4kdLWTCnIxVIJoDi4wweNYcbbGIfuLEjRxqMRNGbvTvsmyE22oZg70taKQn1BxekXw40GKfK0O9oZHsJ8ZCRbLPaI3xP7YzeQoHxWckAAAAAElFTkSuQmCC" + webUI["html/img/x_white.png"] = "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAA6ppVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOk1vZGlmeURhdGU+MjAxOC0xMS0xNVQxNzoxMToyNTwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+UGl4ZWxtYXRvciAzLjM8L3htcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHRpZmY6T3JpZW50YXRpb24+MTwvdGlmZjpPcmllbnRhdGlvbj4KICAgICAgICAgPHRpZmY6Q29tcHJlc3Npb24+NTwvdGlmZjpDb21wcmVzc2lvbj4KICAgICAgICAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MTQ0PC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpYUmVzb2x1dGlvbj4xNDQ8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4yNTY8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFj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" + webUI["html/index.html"] = "PCFkb2N0eXBlIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgiPgogICAgPCEtLS0KICAgIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MS4wIiAvPiAKICAgIC0tPgogICAgPHRpdGxlPnhUZVZlPC90aXRsZT4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iY3NzL3NjcmVlbi5jc3MiIHR5cGU9InRleHQvY3NzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iY3NzL2Jhc2UuY3NzIiB0eXBlPSJ0ZXh0L2NzcyI+CgogICAgPHNjcmlwdCBsYW5ndWFnZT0iamF2YXNjcmlwdCIgdHlwZT0idGV4dC9qYXZhc2NyaXB0IiBzcmM9ImpzL25ldHdvcmtfdHMuanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBsYW5ndWFnZT0iamF2YXNjcmlwdCIgdHlwZT0idGV4dC9qYXZhc2NyaXB0IiBzcmM9ImpzL21lbnVfdHMuanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBsYW5ndWFnZT0iamF2YXNjcmlwdCIgdHlwZT0idGV4dC9qYXZhc2NyaXB0IiBzcmM9ImpzL3NldHRpbmdzX3RzLmpzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQgbGFuZ3VhZ2U9ImphdmFzY3JpcHQiIHR5cGU9InRleHQvamF2YXNjcmlwdCIgc3JjPSJqcy9sb2dzX3RzLmpzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQgbGFuZ3VhZ2U9ImphdmFzY3JpcHQiIHR5cGU9InRleHQvamF2YXNjcmlwdCIgc3JjPSJqcy9iYXNlX3RzLmpzIj48L3NjcmlwdD4KCiAgPC9oZWFkPgoKICAgIDxib2R5IG9ubG9hZD0iamF2YXNjcmlwdDogUGFnZVJlYWR5KCk7Ij4KICAgICAgPGRpdiBpZD0ibG9hZGluZyIgY2xhc3M9Im5vbmUiPgogICAgICAgIDxkaXYgY2xhc3M9ImxvYWRlciI+CiAgICAgICAgICAKICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CgogICAgICA8ZGl2IGlkPSJwb3B1cCIgY2xhc3M9Im5vbmUiPgoKICAgICAgICA8ZGl2IGlkPSJwb3B1cC1jdXN0b20iPjwvZGl2PgoKICAgICAgPC9kaXY+CgogICAgICA8ZGl2IGlkPSJsYXlvdXQiPgoKICAgICAgICA8IS0tCiAgICAgICAgPGRpdiBpZD0ibm90aWZpY2F0aW9uIj4KICAgICAgICAgIDxkaXYgY2xhc3M9ImVsZW1lbnQiPgogICAgICAgICAgICA8aDU+WEVQRzwvaDU+CiAgICAgICAgICAgIDxwcmU+MTEuMDUuMjAxOSAtIDIwOjIxPC9wcmU+CiAgICAgICAgICAgIDxocj4KICAgICAgICAgICAgPHA+SGFsbG8gZGFzIGlzdCBlaW4gVGVzdC4gVW5kIG5vY2ggbWVociBUZXh0LjwvcD4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgICAtLT4KCiAgICAgICAgPGRpdiBpZD0ibWVudS13cmFwcGVyIiBjbGFzcz0ibGF5b3V0LWxlZnQiPgogICAgICAgICAgPGRpdiBpZD0gImJyYW5jaCI+PC9kaXY+CiAgICAgICAgICA8ZGl2IGlkPSJsb2dvIj48L2Rpdj4KICAgICAgICAgIDxuYXYgaWQ9Im1haW4tbWVudSI+PC9uYXY+CiAgICAgICAgPC9kaXY+CgogICAgICAgIDxkaXYgY2xhc3M9ImxheW91dC1yaWdodCI+CiAgICAgICAgICA8dGFibGUgaWQ9ImNsaWVudEluZm8iIGNsYXNzPSIiPgogICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgPHRkIGNsYXNzPSJ0ZEtleSI+eFRlVmU6PC90ZD4KICAgICAgICAgICAgICA8dGQgaWQ9InZlcnNpb24iIGNsYXNzPSJ0ZFZhbCI+Jm5ic3A7PC90ZD4KICAgICAgICAgICAgICA8dGQgY2xhc3M9InRkS2V5Ij5PUzo8L3RkPgogICAgICAgICAgICAgIDx0ZCBpZD0ib3MiIGNsYXNzPSJ0ZFZhbCI+Jm5ic3A7PC90ZD4KICAgICAgICAgICAgICA8dGQgY2xhc3M9InRkS2V5IHBob25lIj5EVlIgSVA6PC90ZD4KICAgICAgICAgICAgICA8dGQgaWQ9IkRWUiIgY2xhc3M9InRkVmFsIHBob25lIj4mbmJzcDs8L3RkPgogICAgICAgICAgICA8L3RyPgogICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgPHRkIGNsYXNzPSJ0ZEtleSI+VVVJRDo8L3RkPgogICAgICAgICAgICAgIDx0ZCBpZD0idXVpZCIgY2xhc3M9InRkVmFsIj4mbmJzcDs8L3RkPgogICAgICAgICAgICAgIDx0ZCBjbGFzcz0idGRLZXkiPkFyY2g6PC90ZD4KICAgICAgICAgICAgICA8dGQgaWQ9ImFyY2giIGNsYXNzPSJ0ZFZhbCI+Jm5ic3A7PC90ZD4KICAgICAgICAgICAgICA8dGQgY2xhc3M9InRkS2V5IHBob25lIj5NM1UgVVJMOjwvdGQ+CiAgICAgICAgICAgICAgPHRkIGlkPSJtM3UtdXJsIiBjbGFzcz0idGRWYWwgcGhvbmUiPiZuYnNwOzwvdGQ+CiAgICAgICAgICAgICAgCiAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICA8dGQgY2xhc3M9InRkS2V5Ij5BdmFpbGFibGUgU3RyZWFtczo8L3RkPgogICAgICAgICAgICAgIDx0ZCBpZD0ic3RyZWFtcyIgY2xhc3M9InRkVmFsIj4mbmJzcDs8L3RkPgogICAgICAgICAgICAgIDx0ZCBjbGFzcz0idGRLZXkiPkVQRyBTb3VyY2U6PC90ZD4KICAgICAgICAgICAgICA8dGQgaWQ9ImVwZ1NvdXJjZSIgY2xhc3M9InRkVmFsIj4mbmJzcDs8L3RkPgogICAgICAgICAgICAgIDx0ZCBjbGFzcz0idGRLZXkgcGhvbmUiPlhFUEcgVVJMOjwvdGQ+CiAgICAgICAgICAgICAgPHRkIGlkPSJ4ZXBnLXVybCIgY2xhc3M9InRkVmFsIHBob25lIj4mbmJzcDs8L3RkPgogICAgICAgICAgICA8L3RyPgogICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgPHRkIGNsYXNzPSJ0ZEtleSI+WEVQRyBDaGFubmVsczo8L3RkPgogICAgICAgICAgICAgIDx0ZCBpZD0ieGVwZyIgY2xhc3M9InRkVmFsIj4mbmJzcDs8L3RkPgogICAgICAgICAgICAgIDx0ZCBjbGFzcz0idGRLZXkiPkVycm9yczo8L3RkPgogICAgICAgICAgICAgIDx0ZCBpZD0iZXJyb3JzIiBjbGFzcz0idGRWYWwiPiZuYnNwOzwvdGQ+CiAgICAgICAgICAgICAgPHRkIGNsYXNzPSJ0ZEtleSI+V2FybmluZ3M6PC90ZD4KICAgICAgICAgICAgICA8dGQgaWQ9Indhcm5pbmdzIiBjbGFzcz0idGRWYWwiPiZuYnNwOzwvdGQ+CiAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAKICAgICAgICAgIDxkaXYgaWQ9Im15U3RyZWFtc0JveCIgY2xhc3M9Im5vdFZpc2libGUiPgogICAgICAgICAgICA8ZGl2IGlkPSJhbGxTdHJlYW1zIj4KICAgICAgICAgICAgICA8dGFibGUgaWQ9ImFjdGl2ZVN0cmVhbXMiPjwvdGFibGU+CiAgICAgICAgICAgICAgPHRhYmxlIGlkPSJpbmFjdGl2ZVN0cmVhbXMiPjwvdGFibGU+CiAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgPCEtLTxkaXYgaWQ9Im9wZW5TdHJlYW1zIiBvbmNsaWNrPSJqYXZhc2NyaXB0OiBzaG93U3RyZWFtcygpOyI+PC9kaXY+LS0+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIAogICAgICAgICAgPGRpdiBpZD0iY29udGVudCIgY2xhc3M9IiI+PC9kaXY+CiAgICAgICAgICAgIAogICAgICAgIDwvZGl2PgoKICAgICAgPC9kaXY+CiAgICAgIAogICAgPC9ib2R5Pgo8L2h0bWw+" + webUI["html/js/settings_ts.js"] = "dmFyIF9fZXh0ZW5kcyA9ICh0aGlzICYmIHRoaXMuX19leHRlbmRzKSB8fCAoZnVuY3Rpb24gKCkgewogICAgdmFyIGV4dGVuZFN0YXRpY3MgPSBmdW5jdGlvbiAoZCwgYikgewogICAgICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHwKICAgICAgICAgICAgKHsgX19wcm90b19fOiBbXSB9IGluc3RhbmNlb2YgQXJyYXkgJiYgZnVuY3Rpb24gKGQsIGIpIHsgZC5fX3Byb3RvX18gPSBiOyB9KSB8fAogICAgICAgICAgICBmdW5jdGlvbiAoZCwgYikgeyBmb3IgKHZhciBwIGluIGIpIGlmIChiLmhhc093blByb3BlcnR5KHApKSBkW3BdID0gYltwXTsgfTsKICAgICAgICByZXR1cm4gZXh0ZW5kU3RhdGljcyhkLCBiKTsKICAgIH07CiAgICByZXR1cm4gZnVuY3Rpb24gKGQsIGIpIHsKICAgICAgICBleHRlbmRTdGF0aWNzKGQsIGIpOwogICAgICAgIGZ1bmN0aW9uIF9fKCkgeyB0aGlzLmNvbnN0cnVjdG9yID0gZDsgfQogICAgICAgIGQucHJvdG90eXBlID0gYiA9PT0gbnVsbCA/IE9iamVjdC5jcmVhdGUoYikgOiAoX18ucHJvdG90eXBlID0gYi5wcm90b3R5cGUsIG5ldyBfXygpKTsKICAgIH07Cn0pKCk7CnZhciBTZXR0aW5nc0NhdGVnb3J5ID0gLyoqIEBjbGFzcyAqLyAoZnVuY3Rpb24gKCkgewogICAgZnVuY3Rpb24gU2V0dGluZ3NDYXRlZ29yeSgpIHsKICAgICAgICB0aGlzLkRvY3VtZW50SUQgPSAiY29udGVudF9zZXR0aW5ncyI7CiAgICB9CiAgICBTZXR0aW5nc0NhdGVnb3J5LnByb3RvdHlwZS5jcmVhdGVDYXRlZ29yeUhlYWRsaW5lID0gZnVuY3Rpb24gKHZhbHVlKSB7CiAgICAgICAgdmFyIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJINCIpOwogICAgICAgIGVsZW1lbnQuaW5uZXJIVE1MID0gdmFsdWU7CiAgICAgICAgcmV0dXJuIGVsZW1lbnQ7CiAgICB9OwogICAgU2V0dGluZ3NDYXRlZ29yeS5wcm90b3R5cGUuY3JlYXRlSFIgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgdmFyIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJIUiIpOwogICAgICAgIHJldHVybiBlbGVtZW50OwogICAgfTsKICAgIFNldHRpbmdzQ2F0ZWdvcnkucHJvdG90eXBlLmNyZWF0ZVNldHRpbmdzID0gZnVuY3Rpb24gKHNldHRpbmdzS2V5KSB7CiAgICAgICAgdmFyIHNldHRpbmcgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJUUiIpOwogICAgICAgIHZhciBjb250ZW50ID0gbmV3IFBvcHVwQ29udGVudCgpOwogICAgICAgIHZhciBkYXRhID0gU0VSVkVSWyJzZXR0aW5ncyJdW3NldHRpbmdzS2V5XTsKICAgICAgICBzd2l0Y2ggKHNldHRpbmdzS2V5KSB7CiAgICAgICAgICAgIC8vIFRleHRlaW5nYWJlbgogICAgICAgICAgICBjYXNlICJ1cGRhdGUiOgogICAgICAgICAgICAgICAgdmFyIHRkTGVmdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB0ZExlZnQuaW5uZXJIVE1MID0gInt7LnNldHRpbmdzLnVwZGF0ZS50aXRsZX19IiArICI6IjsKICAgICAgICAgICAgICAgIHZhciB0ZFJpZ2h0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCAidXBkYXRlIiwgZGF0YS50b1N0cmluZygpKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAie3suc2V0dGluZ3MudXBkYXRlLnBsYWNlaG9sZGVyfX0iKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jaGFuZ2UiLCAiamF2YXNjcmlwdDogdGhpcy5jbGFzc05hbWUgPSAnY2hhbmdlZCciKTsKICAgICAgICAgICAgICAgIHRkUmlnaHQuYXBwZW5kQ2hpbGQoaW5wdXQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZExlZnQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZFJpZ2h0KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJiYWNrdXAucGF0aCI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuYmFja3VwUGF0aC50aXRsZX19IiArICI6IjsKICAgICAgICAgICAgICAgIHZhciB0ZFJpZ2h0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCAiYmFja3VwLnBhdGgiLCBkYXRhKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgicGxhY2Vob2xkZXIiLCAie3suc2V0dGluZ3MuYmFja3VwUGF0aC5wbGFjZWhvbGRlcn19Iik7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2hhbmdlIiwgImphdmFzY3JpcHQ6IHRoaXMuY2xhc3NOYW1lID0gJ2NoYW5nZWQnIik7CiAgICAgICAgICAgICAgICB0ZFJpZ2h0LmFwcGVuZENoaWxkKGlucHV0KTsKICAgICAgICAgICAgICAgIHNldHRpbmcuYXBwZW5kQ2hpbGQodGRMZWZ0KTsKICAgICAgICAgICAgICAgIHNldHRpbmcuYXBwZW5kQ2hpbGQodGRSaWdodCk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAidGVtcC5wYXRoIjoKICAgICAgICAgICAgICAgIHZhciB0ZExlZnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJURCIpOwogICAgICAgICAgICAgICAgdGRMZWZ0LmlubmVySFRNTCA9ICJ7ey5zZXR0aW5ncy50ZW1wUGF0aC50aXRsZX19IiArICI6IjsKICAgICAgICAgICAgICAgIHZhciB0ZFJpZ2h0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCAidGVtcC5wYXRoIiwgZGF0YSk7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoInBsYWNlaG9sZGVyIiwgInt7LnNldHRpbmdzLnRtcFBhdGgucGxhY2Vob2xkZXJ9fSIpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkTGVmdCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgInVzZXIuYWdlbnQiOgogICAgICAgICAgICAgICAgdmFyIHRkTGVmdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB0ZExlZnQuaW5uZXJIVE1MID0gInt7LnNldHRpbmdzLnVzZXJBZ2VudC50aXRsZX19IiArICI6IjsKICAgICAgICAgICAgICAgIHZhciB0ZFJpZ2h0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlSW5wdXQoInRleHQiLCAidXNlci5hZ2VudCIsIGRhdGEpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJwbGFjZWhvbGRlciIsICJ7ey5zZXR0aW5ncy51c2VyQWdlbnQucGxhY2Vob2xkZXJ9fSIpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkTGVmdCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImJ1ZmZlci50aW1lb3V0IjoKICAgICAgICAgICAgICAgIHZhciB0ZExlZnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJURCIpOwogICAgICAgICAgICAgICAgdGRMZWZ0LmlubmVySFRNTCA9ICJ7ey5zZXR0aW5ncy5idWZmZXJUaW1lb3V0LnRpdGxlfX0iICsgIjoiOwogICAgICAgICAgICAgICAgdmFyIHRkUmlnaHQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJURCIpOwogICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVJbnB1dCgidGV4dCIsICJidWZmZXIudGltZW91dCIsIGRhdGEpOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJwbGFjZWhvbGRlciIsICJ7ey5zZXR0aW5ncy5idWZmZXJUaW1lb3V0LnBsYWNlaG9sZGVyfX0iKTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jaGFuZ2UiLCAiamF2YXNjcmlwdDogdGhpcy5jbGFzc05hbWUgPSAnY2hhbmdlZCciKTsKICAgICAgICAgICAgICAgIHRkUmlnaHQuYXBwZW5kQ2hpbGQoaW5wdXQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZExlZnQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZFJpZ2h0KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAvLyBDaGVja2JveGVuCiAgICAgICAgICAgIGNhc2UgImF1dGhlbnRpY2F0aW9uLndlYiI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuYXV0aGVudGljYXRpb25XRUIudGl0bGV9fSIgKyAiOiI7CiAgICAgICAgICAgICAgICB2YXIgdGRSaWdodCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUNoZWNrYm94KHNldHRpbmdzS2V5KTsKICAgICAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkTGVmdCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImF1dGhlbnRpY2F0aW9uLnBtcyI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuYXV0aGVudGljYXRpb25QTVMudGl0bGV9fSIgKyAiOiI7CiAgICAgICAgICAgICAgICB2YXIgdGRSaWdodCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUNoZWNrYm94KHNldHRpbmdzS2V5KTsKICAgICAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkTGVmdCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImF1dGhlbnRpY2F0aW9uLm0zdSI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuYXV0aGVudGljYXRpb25NM1UudGl0bGV9fSIgKyAiOiI7CiAgICAgICAgICAgICAgICB2YXIgdGRSaWdodCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUNoZWNrYm94KHNldHRpbmdzS2V5KTsKICAgICAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkTGVmdCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImF1dGhlbnRpY2F0aW9uLnhtbCI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuYXV0aGVudGljYXRpb25YTUwudGl0bGV9fSIgKyAiOiI7CiAgICAgICAgICAgICAgICB2YXIgdGRSaWdodCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUNoZWNrYm94KHNldHRpbmdzS2V5KTsKICAgICAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkTGVmdCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImF1dGhlbnRpY2F0aW9uLmFwaSI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuYXV0aGVudGljYXRpb25BUEkudGl0bGV9fSIgKyAiOiI7CiAgICAgICAgICAgICAgICB2YXIgdGRSaWdodCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUNoZWNrYm94KHNldHRpbmdzS2V5KTsKICAgICAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkTGVmdCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImZpbGVzLnVwZGF0ZSI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuZmlsZXNVcGRhdGUudGl0bGV9fSIgKyAiOiI7CiAgICAgICAgICAgICAgICB2YXIgdGRSaWdodCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUNoZWNrYm94KHNldHRpbmdzS2V5KTsKICAgICAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkTGVmdCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImNhY2hlLmltYWdlcyI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuY2FjaGVJbWFnZXMudGl0bGV9fSIgKyAiOiI7CiAgICAgICAgICAgICAgICB2YXIgdGRSaWdodCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUNoZWNrYm94KHNldHRpbmdzS2V5KTsKICAgICAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkTGVmdCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgInhlcGcucmVwbGFjZS5taXNzaW5nLmltYWdlcyI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MucmVwbGFjZUVtcHR5SW1hZ2VzLnRpdGxlfX0iICsgIjoiOwogICAgICAgICAgICAgICAgdmFyIHRkUmlnaHQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJURCIpOwogICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVDaGVja2JveChzZXR0aW5nc0tleSk7CiAgICAgICAgICAgICAgICBpbnB1dC5jaGVja2VkID0gZGF0YTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jaGFuZ2UiLCAiamF2YXNjcmlwdDogdGhpcy5jbGFzc05hbWUgPSAnY2hhbmdlZCciKTsKICAgICAgICAgICAgICAgIHRkUmlnaHQuYXBwZW5kQ2hpbGQoaW5wdXQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZExlZnQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZFJpZ2h0KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJ4dGV2ZUF1dG9VcGRhdGUiOgogICAgICAgICAgICAgICAgdmFyIHRkTGVmdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB0ZExlZnQuaW5uZXJIVE1MID0gInt7LnNldHRpbmdzLnh0ZXZlQXV0b1VwZGF0ZS50aXRsZX19IiArICI6IjsKICAgICAgICAgICAgICAgIHZhciB0ZFJpZ2h0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHZhciBpbnB1dCA9IGNvbnRlbnQuY3JlYXRlQ2hlY2tib3goc2V0dGluZ3NLZXkpOwogICAgICAgICAgICAgICAgaW5wdXQuY2hlY2tlZCA9IGRhdGE7CiAgICAgICAgICAgICAgICBpbnB1dC5zZXRBdHRyaWJ1dGUoIm9uY2hhbmdlIiwgImphdmFzY3JpcHQ6IHRoaXMuY2xhc3NOYW1lID0gJ2NoYW5nZWQnIik7CiAgICAgICAgICAgICAgICB0ZFJpZ2h0LmFwcGVuZENoaWxkKGlucHV0KTsKICAgICAgICAgICAgICAgIHNldHRpbmcuYXBwZW5kQ2hpbGQodGRMZWZ0KTsKICAgICAgICAgICAgICAgIHNldHRpbmcuYXBwZW5kQ2hpbGQodGRSaWdodCk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAiYnVmZmVyIjoKICAgICAgICAgICAgICAgIHZhciB0ZExlZnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJURCIpOwogICAgICAgICAgICAgICAgdGRMZWZ0LmlubmVySFRNTCA9ICJ7ey5zZXR0aW5ncy5zdHJlYW1CdWZmZXJpbmcudGl0bGV9fSIgKyAiOiI7CiAgICAgICAgICAgICAgICB2YXIgdGRSaWdodCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB2YXIgaW5wdXQgPSBjb250ZW50LmNyZWF0ZUNoZWNrYm94KHNldHRpbmdzS2V5KTsKICAgICAgICAgICAgICAgIGlucHV0LmNoZWNrZWQgPSBkYXRhOwogICAgICAgICAgICAgICAgaW5wdXQuc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChpbnB1dCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkTGVmdCk7CiAgICAgICAgICAgICAgICBzZXR0aW5nLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImFwaSI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuYXBpLnRpdGxlfX0iICsgIjoiOwogICAgICAgICAgICAgICAgdmFyIHRkUmlnaHQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJURCIpOwogICAgICAgICAgICAgICAgdmFyIGlucHV0ID0gY29udGVudC5jcmVhdGVDaGVja2JveChzZXR0aW5nc0tleSk7CiAgICAgICAgICAgICAgICBpbnB1dC5jaGVja2VkID0gZGF0YTsKICAgICAgICAgICAgICAgIGlucHV0LnNldEF0dHJpYnV0ZSgib25jaGFuZ2UiLCAiamF2YXNjcmlwdDogdGhpcy5jbGFzc05hbWUgPSAnY2hhbmdlZCciKTsKICAgICAgICAgICAgICAgIHRkUmlnaHQuYXBwZW5kQ2hpbGQoaW5wdXQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZExlZnQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZFJpZ2h0KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAvLyBTZWxlY3QKICAgICAgICAgICAgY2FzZSAidHVuZXIiOgogICAgICAgICAgICAgICAgdmFyIHRkTGVmdCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB0ZExlZnQuaW5uZXJIVE1MID0gInt7LnNldHRpbmdzLnR1bmVyLnRpdGxlfX0iICsgIjoiOwogICAgICAgICAgICAgICAgdmFyIHRkUmlnaHQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJURCIpOwogICAgICAgICAgICAgICAgdmFyIHRleHQgPSBuZXcgQXJyYXkoKTsKICAgICAgICAgICAgICAgIHZhciB2YWx1ZXMgPSBuZXcgQXJyYXkoKTsKICAgICAgICAgICAgICAgIGZvciAodmFyIGkgPSAxOyBpIDw9IDEwMDsgaSsrKSB7CiAgICAgICAgICAgICAgICAgICAgdGV4dC5wdXNoKGkpOwogICAgICAgICAgICAgICAgICAgIHZhbHVlcy5wdXNoKGkpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgdmFyIHNlbGVjdCA9IGNvbnRlbnQuY3JlYXRlU2VsZWN0KHRleHQsIHZhbHVlcywgZGF0YSwgc2V0dGluZ3NLZXkpOwogICAgICAgICAgICAgICAgc2VsZWN0LnNldEF0dHJpYnV0ZSgib25jaGFuZ2UiLCAiamF2YXNjcmlwdDogdGhpcy5jbGFzc05hbWUgPSAnY2hhbmdlZCciKTsKICAgICAgICAgICAgICAgIHRkUmlnaHQuYXBwZW5kQ2hpbGQoc2VsZWN0KTsKICAgICAgICAgICAgICAgIHNldHRpbmcuYXBwZW5kQ2hpbGQodGRMZWZ0KTsKICAgICAgICAgICAgICAgIHNldHRpbmcuYXBwZW5kQ2hpbGQodGRSaWdodCk7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAiZXBnU291cmNlIjoKICAgICAgICAgICAgICAgIHZhciB0ZExlZnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJURCIpOwogICAgICAgICAgICAgICAgdGRMZWZ0LmlubmVySFRNTCA9ICJ7ey5zZXR0aW5ncy5lcGdTb3VyY2UudGl0bGV9fSIgKyAiOiI7CiAgICAgICAgICAgICAgICB2YXIgdGRSaWdodCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgICAgICAgICB2YXIgdGV4dCA9IFsiUE1TIiwgIlhFUEciXTsKICAgICAgICAgICAgICAgIHZhciB2YWx1ZXMgPSBbIlBNUyIsICJYRVBHIl07CiAgICAgICAgICAgICAgICB2YXIgc2VsZWN0ID0gY29udGVudC5jcmVhdGVTZWxlY3QodGV4dCwgdmFsdWVzLCBkYXRhLCBzZXR0aW5nc0tleSk7CiAgICAgICAgICAgICAgICBzZWxlY3Quc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChzZWxlY3QpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZExlZnQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZFJpZ2h0KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJiYWNrdXAua2VlcCI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuYmFja3VwS2VlcC50aXRsZX19IiArICI6IjsKICAgICAgICAgICAgICAgIHZhciB0ZFJpZ2h0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHZhciB0ZXh0ID0gWyI1IiwgIjEwIiwgIjIwIiwgIjMwIiwgIjQwIiwgIjUwIl07CiAgICAgICAgICAgICAgICB2YXIgdmFsdWVzID0gWyI1IiwgIjEwIiwgIjIwIiwgIjMwIiwgIjQwIiwgIjUwIl07CiAgICAgICAgICAgICAgICB2YXIgc2VsZWN0ID0gY29udGVudC5jcmVhdGVTZWxlY3QodGV4dCwgdmFsdWVzLCBkYXRhLCBzZXR0aW5nc0tleSk7CiAgICAgICAgICAgICAgICBzZWxlY3Quc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChzZWxlY3QpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZExlZnQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZFJpZ2h0KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJidWZmZXIuc2l6ZS5rYiI6CiAgICAgICAgICAgICAgICB2YXIgdGRMZWZ0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAie3suc2V0dGluZ3MuYnVmZmVyU2l6ZS50aXRsZX19IiArICI6IjsKICAgICAgICAgICAgICAgIHZhciB0ZFJpZ2h0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiVEQiKTsKICAgICAgICAgICAgICAgIHZhciB0ZXh0ID0gWyIwLjUgTUIiLCAiMSBNQiIsICIyIE1CIiwgIjMgTUIiLCAiNCBNQiIsICI1IE1CIiwgIjYgTUIiLCAiNyBNQiIsICI4IE1CIl07CiAgICAgICAgICAgICAgICB2YXIgdmFsdWVzID0gWyI1MTIiLCAiMTAyNCIsICIyMDQ4IiwgIjMwNzIiLCAiNDA5NiIsICI1MTIwIiwgIjYxNDQiLCAiNzE2OCIsICI4MTkyIl07CiAgICAgICAgICAgICAgICB2YXIgc2VsZWN0ID0gY29udGVudC5jcmVhdGVTZWxlY3QodGV4dCwgdmFsdWVzLCBkYXRhLCBzZXR0aW5nc0tleSk7CiAgICAgICAgICAgICAgICBzZWxlY3Quc2V0QXR0cmlidXRlKCJvbmNoYW5nZSIsICJqYXZhc2NyaXB0OiB0aGlzLmNsYXNzTmFtZSA9ICdjaGFuZ2VkJyIpOwogICAgICAgICAgICAgICAgdGRSaWdodC5hcHBlbmRDaGlsZChzZWxlY3QpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZExlZnQpOwogICAgICAgICAgICAgICAgc2V0dGluZy5hcHBlbmRDaGlsZCh0ZFJpZ2h0KTsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgIH0KICAgICAgICByZXR1cm4gc2V0dGluZzsKICAgIH07CiAgICBTZXR0aW5nc0NhdGVnb3J5LnByb3RvdHlwZS5jcmVhdGVEZXNjcmlwdGlvbiA9IGZ1bmN0aW9uIChzZXR0aW5nc0tleSkgewogICAgICAgIHZhciBkZXNjcmlwdGlvbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlRSIik7CiAgICAgICAgdmFyIHRleHQ7CiAgICAgICAgc3dpdGNoIChzZXR0aW5nc0tleSkgewogICAgICAgICAgICBjYXNlICJhdXRoZW50aWNhdGlvbi53ZWIiOgogICAgICAgICAgICAgICAgdGV4dCA9ICJ7ey5zZXR0aW5ncy5hdXRoZW50aWNhdGlvbldFQi5kZXNjcmlwdGlvbn19IjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJhdXRoZW50aWNhdGlvbi5tM3UiOgogICAgICAgICAgICAgICAgdGV4dCA9ICJ7ey5zZXR0aW5ncy5hdXRoZW50aWNhdGlvbk0zVS5kZXNjcmlwdGlvbn19IjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJhdXRoZW50aWNhdGlvbi5wbXMiOgogICAgICAgICAgICAgICAgdGV4dCA9ICJ7ey5zZXR0aW5ncy5hdXRoZW50aWNhdGlvblBNUy5kZXNjcmlwdGlvbn19IjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJhdXRoZW50aWNhdGlvbi54bWwiOgogICAgICAgICAgICAgICAgdGV4dCA9ICJ7ey5zZXR0aW5ncy5hdXRoZW50aWNhdGlvblhNTC5kZXNjcmlwdGlvbn19IjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJhdXRoZW50aWNhdGlvbi5hcGkiOgogICAgICAgICAgICAgICAgaWYgKFNFUlZFUlsic2V0dGluZ3MiXVsiYXV0aGVudGljYXRpb24ud2ViIl0gPT0gdHJ1ZSkgewogICAgICAgICAgICAgICAgICAgIHRleHQgPSAie3suc2V0dGluZ3MuYXV0aGVudGljYXRpb25BUEkuZGVzY3JpcHRpb259fSI7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAieHRldmVBdXRvVXBkYXRlIjoKICAgICAgICAgICAgICAgIHRleHQgPSAie3suc2V0dGluZ3MueHRldmVBdXRvVXBkYXRlLmRlc2NyaXB0aW9ufX0iOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImJhY2t1cC5rZWVwIjoKICAgICAgICAgICAgICAgIHRleHQgPSAie3suc2V0dGluZ3MuYmFja3VwS2VlcC5kZXNjcmlwdGlvbn19IjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJiYWNrdXAucGF0aCI6CiAgICAgICAgICAgICAgICB0ZXh0ID0gInt7LnNldHRpbmdzLmJhY2t1cFBhdGguZGVzY3JpcHRpb259fSI7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAidGVtcC5wYXRoIjoKICAgICAgICAgICAgICAgIHRleHQgPSAie3suc2V0dGluZ3MudGVtcFBhdGguZGVzY3JpcHRpb259fSI7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAiYnVmZmVyIjoKICAgICAgICAgICAgICAgIHRleHQgPSAie3suc2V0dGluZ3Muc3RyZWFtQnVmZmVyaW5nLmRlc2NyaXB0aW9ufX0iOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImJ1ZmZlci5zaXplLmtiIjoKICAgICAgICAgICAgICAgIHRleHQgPSAie3suc2V0dGluZ3MuYnVmZmVyU2l6ZS5kZXNjcmlwdGlvbn19IjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJidWZmZXIudGltZW91dCI6CiAgICAgICAgICAgICAgICB0ZXh0ID0gInt7LnNldHRpbmdzLmJ1ZmZlclRpbWVvdXQuZGVzY3JpcHRpb259fSI7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAidXNlci5hZ2VudCI6CiAgICAgICAgICAgICAgICB0ZXh0ID0gInt7LnNldHRpbmdzLnVzZXJBZ2VudC5kZXNjcmlwdGlvbn19IjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJlcGdTb3VyY2UiOgogICAgICAgICAgICAgICAgdGV4dCA9ICJ7ey5zZXR0aW5ncy5lcGdTb3VyY2UuZGVzY3JpcHRpb259fSI7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAidHVuZXIiOgogICAgICAgICAgICAgICAgdGV4dCA9ICJ7ey5zZXR0aW5ncy50dW5lci5kZXNjcmlwdGlvbn19IjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBjYXNlICJ1cGRhdGUiOgogICAgICAgICAgICAgICAgdGV4dCA9ICJ7ey5zZXR0aW5ncy51cGRhdGUuZGVzY3JpcHRpb259fSI7CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAiYXBpIjoKICAgICAgICAgICAgICAgIHRleHQgPSAie3suc2V0dGluZ3MuYXBpLmRlc2NyaXB0aW9ufX0iOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImZpbGVzLnVwZGF0ZSI6CiAgICAgICAgICAgICAgICB0ZXh0ID0gInt7LnNldHRpbmdzLmZpbGVzVXBkYXRlLmRlc2NyaXB0aW9ufX0iOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImNhY2hlLmltYWdlcyI6CiAgICAgICAgICAgICAgICB0ZXh0ID0gInt7LnNldHRpbmdzLmNhY2hlSW1hZ2VzLmRlc2NyaXB0aW9ufX0iOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgInhlcGcucmVwbGFjZS5taXNzaW5nLmltYWdlcyI6CiAgICAgICAgICAgICAgICB0ZXh0ID0gInt7LnNldHRpbmdzLnJlcGxhY2VFbXB0eUltYWdlcy5kZXNjcmlwdGlvbn19IjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICBkZWZhdWx0OgogICAgICAgICAgICAgICAgdGV4dCA9ICIiOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgfQogICAgICAgIHZhciB0ZExlZnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJURCIpOwogICAgICAgIHRkTGVmdC5pbm5lckhUTUwgPSAiIjsKICAgICAgICB2YXIgdGRSaWdodCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlREIik7CiAgICAgICAgdmFyIHByZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlBSRSIpOwogICAgICAgIHByZS5pbm5lckhUTUwgPSB0ZXh0OwogICAgICAgIHRkUmlnaHQuYXBwZW5kQ2hpbGQocHJlKTsKICAgICAgICBkZXNjcmlwdGlvbi5hcHBlbmRDaGlsZCh0ZExlZnQpOwogICAgICAgIGRlc2NyaXB0aW9uLmFwcGVuZENoaWxkKHRkUmlnaHQpOwogICAgICAgIHJldHVybiBkZXNjcmlwdGlvbjsKICAgIH07CiAgICByZXR1cm4gU2V0dGluZ3NDYXRlZ29yeTsKfSgpKTsKdmFyIFNldHRpbmdzQ2F0ZWdvcnlJdGVtID0gLyoqIEBjbGFzcyAqLyAoZnVuY3Rpb24gKF9zdXBlcikgewogICAgX19leHRlbmRzKFNldHRpbmdzQ2F0ZWdvcnlJdGVtLCBfc3VwZXIpOwogICAgZnVuY3Rpb24gU2V0dGluZ3NDYXRlZ29yeUl0ZW0oaGVhZGxpbmUsIHNldHRpbmdzS2V5cykgewogICAgICAgIHZhciBfdGhpcyA9IF9zdXBlci5jYWxsKHRoaXMpIHx8IHRoaXM7CiAgICAgICAgX3RoaXMuaGVhZGxpbmUgPSBoZWFkbGluZTsKICAgICAgICBfdGhpcy5zZXR0aW5nc0tleXMgPSBzZXR0aW5nc0tleXM7CiAgICAgICAgcmV0dXJuIF90aGlzOwogICAgfQogICAgU2V0dGluZ3NDYXRlZ29yeUl0ZW0ucHJvdG90eXBlLmNyZWF0ZUNhdGVnb3J5ID0gZnVuY3Rpb24gKCkgewogICAgICAgIHZhciBfdGhpcyA9IHRoaXM7CiAgICAgICAgdmFyIGhlYWRsaW5lID0gdGhpcy5jcmVhdGVDYXRlZ29yeUhlYWRsaW5lKHRoaXMuaGVhZGxpbmUpOwogICAgICAgIHZhciBzZXR0aW5nc0tleXMgPSB0aGlzLnNldHRpbmdzS2V5czsKICAgICAgICB2YXIgZG9jID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQodGhpcy5Eb2N1bWVudElEKTsKICAgICAgICBkb2MuYXBwZW5kQ2hpbGQoaGVhZGxpbmUpOwogICAgICAgIC8vIFRhYmVsbGUgZsO8ciBkaWUgS2F0ZWdvcmllIGVyc3RlbGxlbgogICAgICAgIHZhciB0YWJsZSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoIlRBQkxFIik7CiAgICAgICAgdmFyIGtleXMgPSBzZXR0aW5nc0tleXMuc3BsaXQoIiwiKTsKICAgICAgICBrZXlzLmZvckVhY2goZnVuY3Rpb24gKHNldHRpbmdzS2V5KSB7CiAgICAgICAgICAgIHN3aXRjaCAoc2V0dGluZ3NLZXkpIHsKICAgICAgICAgICAgICAgIGNhc2UgImF1dGhlbnRpY2F0aW9uLnBtcyI6CiAgICAgICAgICAgICAgICBjYXNlICJhdXRoZW50aWNhdGlvbi5tM3UiOgogICAgICAgICAgICAgICAgY2FzZSAiYXV0aGVudGljYXRpb24ueG1sIjoKICAgICAgICAgICAgICAgIGNhc2UgImF1dGhlbnRpY2F0aW9uLmFwaSI6CiAgICAgICAgICAgICAgICAgICAgaWYgKFNFUlZFUlsic2V0dGluZ3MiXVsiYXV0aGVudGljYXRpb24ud2ViIl0gPT0gZmFsc2UpIHsKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgZGVmYXVsdDoKICAgICAgICAgICAgICAgICAgICB2YXIgaXRlbSA9IF90aGlzLmNyZWF0ZVNldHRpbmdzKHNldHRpbmdzS2V5KTsKICAgICAgICAgICAgICAgICAgICB2YXIgZGVzY3JpcHRpb24gPSBfdGhpcy5jcmVhdGVEZXNjcmlwdGlvbihzZXR0aW5nc0tleSk7CiAgICAgICAgICAgICAgICAgICAgdGFibGUuYXBwZW5kQ2hpbGQoaXRlbSk7CiAgICAgICAgICAgICAgICAgICAgdGFibGUuYXBwZW5kQ2hpbGQoZGVzY3JpcHRpb24pOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICB9CiAgICAgICAgfSk7CiAgICAgICAgZG9jLmFwcGVuZENoaWxkKHRhYmxlKTsKICAgICAgICBkb2MuYXBwZW5kQ2hpbGQodGhpcy5jcmVhdGVIUigpKTsKICAgIH07CiAgICByZXR1cm4gU2V0dGluZ3NDYXRlZ29yeUl0ZW07Cn0oU2V0dGluZ3NDYXRlZ29yeSkpOwpmdW5jdGlvbiBzaG93U2V0dGluZ3MoKSB7CiAgICBjb25zb2xlLmxvZygiU0VUVElOR1MiKTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgc2V0dGluZ3NDYXRlZ29yeS5sZW5ndGg7IGkrKykgewogICAgICAgIHNldHRpbmdzQ2F0ZWdvcnlbaV0uY3JlYXRlQ2F0ZWdvcnkoKTsKICAgIH0KfQpmdW5jdGlvbiBzYXZlU2V0dGluZ3MoKSB7CiAgICBjb25zb2xlLmxvZygiU2F2ZSBTZXR0aW5ncyIpOwogICAgdmFyIGNtZCA9ICJzYXZlU2V0dGluZ3MiOwogICAgdmFyIGRpdiA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJjb250ZW50X3NldHRpbmdzIik7CiAgICB2YXIgc2V0dGluZ3MgPSBkaXYuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgiY2hhbmdlZCIpOwogICAgdmFyIG5ld1NldHRpbmdzID0gbmV3IE9iamVjdCgpOwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBzZXR0aW5ncy5sZW5ndGg7IGkrKykgewogICAgICAgIHZhciBuYW1lOwogICAgICAgIHZhciB2YWx1ZTsKICAgICAgICBzd2l0Y2ggKHNldHRpbmdzW2ldLnRhZ05hbWUpIHsKICAgICAgICAgICAgY2FzZSAiSU5QVVQiOgogICAgICAgICAgICAgICAgc3dpdGNoIChzZXR0aW5nc1tpXS50eXBlKSB7CiAgICAgICAgICAgICAgICAgICAgY2FzZSAiY2hlY2tib3giOgogICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gc2V0dGluZ3NbaV0ubmFtZTsKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBzZXR0aW5nc1tpXS5jaGVja2VkOwogICAgICAgICAgICAgICAgICAgICAgICBuZXdTZXR0aW5nc1tuYW1lXSA9IHZhbHVlOwogICAgICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgICAgICBjYXNlICJ0ZXh0IjoKICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9IHNldHRpbmdzW2ldLm5hbWU7CiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gc2V0dGluZ3NbaV0udmFsdWU7CiAgICAgICAgICAgICAgICAgICAgICAgIHN3aXRjaCAobmFtZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FzZSAidXBkYXRlIjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IHZhbHVlLnNwbGl0KCIsIik7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSB2YWx1ZS5maWx0ZXIoZnVuY3Rpb24gKGUpIHsgcmV0dXJuIGU7IH0pOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FzZSAiYnVmZmVyLnRpbWVvdXQiOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gcGFyc2VGbG9hdCh2YWx1ZSk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgbmV3U2V0dGluZ3NbbmFtZV0gPSB2YWx1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgY2FzZSAiU0VMRUNUIjoKICAgICAgICAgICAgICAgIG5hbWUgPSBzZXR0aW5nc1tpXS5uYW1lOwogICAgICAgICAgICAgICAgdmFsdWUgPSBzZXR0aW5nc1tpXS52YWx1ZTsKICAgICAgICAgICAgICAgIC8vIFdlbm4gZGVyIFdlcnQgZWluZSBaYWhsIGlzdCwgd2lyZCBkaWVzZXIgYWxzIFphaGwgZ2VzcGVpY2hlcnQKICAgICAgICAgICAgICAgIGlmIChpc05hTih2YWx1ZSkpIHsKICAgICAgICAgICAgICAgICAgICBuZXdTZXR0aW5nc1tuYW1lXSA9IHZhbHVlOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgbmV3U2V0dGluZ3NbbmFtZV0gPSBwYXJzZUludCh2YWx1ZSk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICB9CiAgICB9CiAgICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKICAgIGRhdGFbInNldHRpbmdzIl0gPSBuZXdTZXR0aW5nczsKICAgIHZhciBzZXJ2ZXIgPSBuZXcgU2VydmVyKGNtZCk7CiAgICBzZXJ2ZXIucmVxdWVzdChkYXRhKTsKfQo=" + webUI["html/js/base.js"] = "dmFyIGNvbmZpZyAgICAgICAgPSBuZXcgT2JqZWN0KCk7CnZhciBtZW51ICAgICAgICAgID0gbmV3IE9iamVjdCgpOwp2YXIgc3ViTWVudSAgICAgICA9IG5ldyBPYmplY3QoKTsKdmFyIGFjdGl2ZVN0cmVhbXMgPSBuZXcgT2JqZWN0KCk7CnZhciB4RVBHICAgICAgICAgID0gbmV3IE9iamVjdCgpOwp2YXIgdXNlcnMgICAgICAgICA9IG5ldyBPYmplY3QoKTsKdmFyIGxvZyAgICAgICAgICAgPSBuZXcgT2JqZWN0KCk7CnZhciB1bmRvICAgICAgICAgID0gbmV3IE9iamVjdCgpOwp2YXIgd2ViU29ja2V0cyAgICA9IHRydWU7CnZhciBjbG9zZUxvZywgdmVyc2lvbiwgYWN0aXZlTWVudTsKdmFyIGNvbHVtblRvU29ydCAgPSAwCgoKaWYgKHdpbmRvdy5XZWJTb2NrZXQgPT09IHVuZGVmaW5lZCkgewogIGFsZXJ0KCJZb3VyIGJyb3dzZXIgZG9lcyBub3Qgc3VwcG9ydCBXZWJTb2NrZXRzIik7CiAgd2ViU29ja2V0cyA9IGZhbHNlOwp9IAoKZnVuY3Rpb24gcGFnZVJlYWR5KCkgewogIHZhciBkYXRhID0gbmV3IE9iamVjdCgpOwogIGRhdGFbImNtZCJdID0gImdldFNlcnZlckNvbmZpZyI7CiAgeFRlVmUoZGF0YSk7CiAgLy9zaG93TG9hZGluZ1NjcmVlbihmYWxzZSk7CgogIHZhciByZXNpemVIYW5kbGUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgib3BlblN0cmVhbXMiKTsKICB2YXIgYm94ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIm15U3RyZWFtc0JveCIpOwogIHJlc2l6ZUhhbmRsZS5hZGRFdmVudExpc3RlbmVyKCJtb3VzZWRvd24iLCBpbml0aWFsaXNlUmVzaXplLCBmYWxzZSk7CgogIGZ1bmN0aW9uIGluaXRpYWxpc2VSZXNpemUoZSkgewogICAgd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoIm1vdXNlbW92ZSIsIHN0YXJ0UmVzaXppbmcsIGZhbHNlKTsKICAgIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCJtb3VzZXVwIiwgc3RvcFJlc2l6aW5nLCBmYWxzZSk7CiAgfQoKICBmdW5jdGlvbiBzdGFydFJlc2l6aW5nKGUpIHsKICAgIGJveC5zdHlsZS5oZWlnaHQgPSAoZS5jbGllbnRZIC0gYm94Lm9mZnNldFRvcCkgKyAicHgiOwogICAgICAKICAgIHZhciBlbG0gPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYWxsU3RyZWFtcyIpOwogICAgaWYgKGUuY2xpZW50WSA+IDEyMCkgewogICAgICBlbG0uY2xhc3NOYW1lID0gInZpc2libGUiOyAKICAgIH0gZWxzZSB7CiAgICAgIGVsbS5jbGFzc05hbWUgPSAibm90VmlzaWJsZSI7IAogICAgfQoKICAgIGNhbGN1bGF0ZVdyYXBwZXJIZWlnaHQoKTsKCiAgfQogIGZ1bmN0aW9uIHN0b3BSZXNpemluZyhlKSB7CiAgICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCdtb3VzZW1vdmUnLCBzdGFydFJlc2l6aW5nLCBmYWxzZSk7CiAgICAgIHdpbmRvdy5yZW1vdmVFdmVudExpc3RlbmVyKCdtb3VzZXVwJywgc3RvcFJlc2l6aW5nLCBmYWxzZSk7CiAgICAgIGNhbGN1bGF0ZVdyYXBwZXJIZWlnaHQoKTsKICB9CgogIHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCJyZXNpemUiLCBmdW5jdGlvbigpewogICAgY2FsY3VsYXRlV3JhcHBlckhlaWdodCgpOwogIH0sIHRydWUpOwp9CgoKZnVuY3Rpb24gZ2V0T2JqS2V5cyhvYmopIHsKICB2YXIga2V5cyA9IG5ldyBBcnJheSgpOwoKICBmb3IgKHZhciBpIGluIG9iaikgewogICAgaWYgKG9iai5oYXNPd25Qcm9wZXJ0eShpKSkgewogICAgICBrZXlzLnB1c2goaSk7CiAgICB9CiAgfQoKICByZXR1cm4ga2V5czsKfQoKCmZ1bmN0aW9uIGNyZWF0ZUVsZW1lbnQoaXRlbSkgewogIC8vY29uc29sZS5sb2coaXRlbSk7CiAgdmFyIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KGl0ZW1bIl9lbGVtZW50Il0pOwogIGlmIChpdGVtLmhhc093blByb3BlcnR5KCJfdGV4dCIpKSB7CiAgICAvL2VsZW1lbnQuaW5uZXJIVE1MID0gIjxwPiIgKyBpdGVtWyJfdGV4dCJdICsgIjwvcD4iOwogICAgZWxlbWVudC5pbm5lckhUTUwgPSBpdGVtWyJfdGV4dCJdOwogIH0KCiAgdmFyIGtleXMgPSBnZXRPYmpLZXlzKGl0ZW0pOwogIGZvciAodmFyIGkgPSAwOyBpIDwga2V5cy5sZW5ndGg7IGkrKykgewogICAgaWYgKGtleXNbaV0uY2hhckF0KDApICE9ICJfIikgewogICAgICAvL2NvbnNvbGUubG9nKGtleXNbaV0sIGl0ZW1ba2V5c1tpXV0pOwogICAgICBlbGVtZW50LnNldEF0dHJpYnV0ZShrZXlzW2ldLCBpdGVtW2tleXNbaV1dKTsKICAgIH0KICB9CgogIC8vY29uc29sZS5sb2coZWxlbWVudCk7CiAgcmV0dXJuIGVsZW1lbnQ7Cn0KCmZ1bmN0aW9uIG1vZGlmeU9wdGlvbihpZCwgb3B0aW9ucywgdmFsdWVzKSB7CiAgdmFyIHNlbGVjdCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlkKTsKICBzZWxlY3QuaW5uZXJIVE1MID0gIiI7CgogIGZvciAodmFyIGkgPSAwOyBpIDwgb3B0aW9ucy5sZW5ndGg7IGkrKykgewogIAogICAgdmFyIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJPUFRJT04iKQogIAogICAgZWxlbWVudC52YWx1ZSA9IHZhbHVlc1tpXTsKICAgIGVsZW1lbnQuaW5uZXJIVE1MID0gb3B0aW9uc1tpXTsKCiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuYXBwZW5kQ2hpbGQoZWxlbWVudCk7CiAgCiAgfQoKfQoKCmZ1bmN0aW9uIHN0YXJ0V2ViU29ja2V0KCkgewogIGlmICh3ZWJTb2NrZXRzID09IGZhbHNlKSB7CiAgICByZXR1cm47CiAgfQoKICAvL3dzLnNlbmQoJ3siY21kIjogImdldFNlcnZlckNvbmZpZzEifScpOwoKfQoKZnVuY3Rpb24gY2hlY2tFcnIob2JqKSB7CiAgLy9hbGVydChvYmpbImVyciJdKQogIC8vc2NyZWVuTG9nKG9ialsiZXJyIl0sICJlcnJvciIpCiAgY29uc29sZS5sb2cob2JqKTsKICB2YXIgbmV3T2JqID0gbmV3IE9iamVjdCgpOwogIHZhciBuZXdFcnIgPSBuZXcgT2JqZWN0KCk7CiAgbmV3RXJyWyJrZXkiXSAgID0gIkVycm9yIjsKICBuZXdFcnJbInZhbHVlIl0gPSBvYmpbImVyciJdOwogIG5ld0VyclsidHlwZSJdICA9ICJlcnJvciI7CgogIG5ld09ialswXSA9IG5ld0VycgogIHNob3dMb2cobmV3T2JqKTsKICByZXR1cm4KfQoKZnVuY3Rpb24gc2NyZWVuTG9nKG1zZywgbXNnVHlwZSwgc2hvdykgewogIHJldHVybgogIGNsZWFyVGltZW91dChjbG9zZUxvZykKICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNjcmVlbkxvZyIpOwogIHZhciBuZXdNc2cgPSBuZXcgT2JqZWN0KCk7CiAgCiAgbmV3TXNnWyJfZWxlbWVudCJdID0gIlAiOwogIAogIHN3aXRjaChtc2dUeXBlKSB7CiAgICBjYXNlICJlcnJvciI6ICAgbmV3TXNnWyJjbGFzcyJdID0gImVycm9yTXNnIjsgYnJlYWs7CiAgICBjYXNlICJ3YXJuaW5nIjogbmV3TXNnWyJjbGFzcyJdID0gIndhcm5pbmdNc2ciOyBicmVhazsKICAgIC8vZGVmYXVsdDogICAgICBuZXdNc2dbImNsYXNzIl0gPSAiaW5mb01zZyIKICB9CgogIG5ld01zZ1siX3RleHQiXSA9IG1zZzsKCiAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3TXNnKSk7CgogIGRpdi5zY3JvbGxUb3AgPSBkaXYuc2Nyb2xsSGVpZ2h0OwoKICBpZiAoc2hvdyA9PSBmYWxzZSkgewogICAgcmV0dXJuOwogIH0KCiAgZGl2LmNsYXNzTmFtZSA9ICIiCiAgY2xvc2VMb2cgPSBzZXRUaW1lb3V0KGNsb3NlU2NyZWVuTG9nLCAxMDAwMCk7Cn0KCgpmdW5jdGlvbiBjbG9zZVNjcmVlbkxvZygpIHsKICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNjcmVlbkxvZyIpOwogIGRpdi5jbGFzc05hbWUgPSAic2NyZWVuTG9nSGlkZGVuIgp9CgpmdW5jdGlvbiBzaG93U2NyZWVuTG9nKCkgewogIGNsZWFyVGltZW91dChjbG9zZUxvZykKICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNjcmVlbkxvZyIpOwogIHZhciBjdXJyZW50Q2xhc3MgPSBkaXYuY2xhc3NOYW1lOwogIGRpdi5jbGFzc05hbWUgPSAic2NyZWVuTG9nSGlkZGVuIgoKICBzd2l0Y2goY3VycmVudENsYXNzKSB7CiAgICBjYXNlICJzY3JlZW5Mb2dIaWRkZW4iOiAgZGl2LmNsYXNzTmFtZSA9ICIiOyBicmVhazsgCiAgICBjYXNlICIiOiBkaXYuY2xhc3NOYW1lID0gInNjcmVlbkxvZ0hpZGRlbiI7IGJyZWFrOwogIH0KfQoKZnVuY3Rpb24gc2hvd0xvYWRpbmdTY3JlZW4oZWxtKSB7CiAgdmFyIGRpdiA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJsb2FkaW5nIik7CiAgc3dpdGNoKGVsbSkgewogICAgY2FzZSB0cnVlOiBkaXYuY2xhc3NOYW1lID0gImJsb2NrIjsgYnJlYWs7CiAgICBjYXNlIGZhbHNlOiBkaXYuY2xhc3NOYW1lID0gIm5vbmUiOyBicmVhazsKCiAgICAvKgogICAgY2FzZSB0cnVlOiBkaXYuc3R5bGUuZGlzcGxheSA9ICJibG9jayI7IGJyZWFrOwogICAgY2FzZSBmYWxzZTogZGl2LnN0eWxlLmRpc3BsYXkgPSAibm9uZSI7IGJyZWFrOwogICAgKi8KICB9Cn0KCmZ1bmN0aW9uIGNyZWF0ZUNsaW50SW5mbyhvYmopIHsKICAvL2NvbnNvbGUubG9nKG9iaik7CiAgdmFyIGtleXMgPSBnZXRPYmpLZXlzKG9iaik7CiAgZm9yICh2YXIgaSA9IDA7IGkgPCBrZXlzLmxlbmd0aDsgaSsrKSB7CiAgICBpZihkb2N1bWVudC5nZXRFbGVtZW50QnlJZChrZXlzW2ldKSl7CiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGtleXNbaV0pLmlubmVySFRNTCA9IG9ialtrZXlzW2ldXTsKICAgIH0KICB9CiAgLy9kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiY2xpZW50SW5mbyIpLmNsYXNzTmFtZSA9ICJ2aXNpYmxlIjsKfQoKZnVuY3Rpb24gc2hvd0VsZW1lbnQoZWxtSUQsIHR5cGUpIHsKICBzd2l0Y2godHlwZSkgewogICAgY2FzZSB0cnVlOiAgY3NzQ2xhc3MgPSAiYmxvY2siOyBicmVhazsKICAgIGNhc2UgZmFsc2U6IGNzc0NsYXNzID0gIm5vbmUiOyBicmVhazsKICB9CgogIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGVsbUlEKS5jbGFzc05hbWUgPSBjc3NDbGFzczsKfSAKCmZ1bmN0aW9uIHNob3dQb3BVcEVsZW1lbnQoZWxtKSB7CiAgdmFyIGFsbEVsZW1lbnRzID0gbmV3IEFycmF5KCJkZWxldGVVc2VyRGV0YWlsIiwgIm1hcHBpbmctZGV0YWlsIiwgInVzZXItZGV0YWlsIiwgImZpbGUtZGV0YWlsIik7CgogIGZvciAodmFyIGkgPSAwOyBpIDwgYWxsRWxlbWVudHMubGVuZ3RoOyBpKyspIHsKICAgIHNob3dFbGVtZW50KGFsbEVsZW1lbnRzW2ldLCBmYWxzZSkKICB9CgogIHNob3dFbGVtZW50KGVsbSwgdHJ1ZSkKCiAgc2V0VGltZW91dChmdW5jdGlvbigpeyAKICAgIHNob3dFbGVtZW50KCJwb3B1cCIsIHRydWUpOwogIH0sIDEwKTsKfQoKICAvLyBib2R5Li4uCgpmdW5jdGlvbiBzaG93U3RyZWFtcyhmb3JjZSkgewoKICB2YXIgZWxtQm94ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIm15U3RyZWFtc0JveCIpOwogIHZhciBlbG0gPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYWxsU3RyZWFtcyIpOwogIC8vY29uc29sZS5sb2coZWxtKTsKICBzaG93ID0gZWxtLmNsYXNzTmFtZTsKCiAgc3dpdGNoKGZvcmNlKSB7CiAgICBjYXNlIHRydWU6IHNob3cgPSAibm90VmlzaWJsZSI7IGJyZWFrOwogICAgY2FzZSBmYWxzZTogc2hvdyA9ICJ2aXNpYmxlIjsgYnJlYWs7CiAgfQoKICBzd2l0Y2goc2hvdykgewogICAgY2FzZSAibm90VmlzaWJsZSI6IAogICAgICBlbG0uY2xhc3NOYW1lID0gInZpc2libGUiOyAKICAgICAgZWxtQm94LnN0eWxlLmhlaWdodCA9ICIxMDBweCI7CiAgICAgIGJyZWFrOwoKICAgIGRlZmF1bHQ6IAogICAgICBlbG0uY2xhc3NOYW1lID0gIm5vdFZpc2libGUiOyAKICAgICAgZWxtQm94LnN0eWxlLmhlaWdodCA9ICIyMHB4IjsKICAgICAgYnJlYWs7CiAgfQoKICB2YXIgc2hvdyA9IGVsbS5zdHlsZS5kaXNwbGF5OyB7CiAgICAvL2NvbnNvbGUubG9nKGVsbS5zdHlsZS5kaXNwbGF5KTsKICB9CiAgCiAgY2FsY3VsYXRlV3JhcHBlckhlaWdodCgpOwp9CgpmdW5jdGlvbiB4dGV2ZUJhY2t1cCgpIHsKICBjb25zb2xlLmxvZygieHRldmVCYWNrdXAiKTsKICB2YXIgZGF0YSA9IG5ldyBPYmplY3QoKTsKICBkYXRhWyJjbWQiXSA9ICJ4dGV2ZUJhY2t1cCI7CgogIHhUZVZlKGRhdGEpOwp9CgpmdW5jdGlvbiB4dGV2ZVJlc3RvcmUoZWxtKSB7CiAgdmFyIHJlc3RvcmUgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCJJTlBVVCIpOwogIHJlc3RvcmUuc2V0QXR0cmlidXRlKCJ0eXBlIiwgImZpbGUiKTsKICByZXN0b3JlLnNldEF0dHJpYnV0ZSgiY2xhc3MiLCAibm90VmlzaWJsZSIpOwogIHJlc3RvcmUuc2V0QXR0cmlidXRlKCJuYW1lIiwgIiIpOwogIHJlc3RvcmUuaWQgPSAidXBsb2FkIjsKCiAgZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChyZXN0b3JlKTsKICByZXN0b3JlLmNsaWNrKCk7CgogIHJlc3RvcmUub25jaGFuZ2UgPSBmdW5jdGlvbigpIHsKICAgIHZhciBmaWxlbmFtZSA9IHJlc3RvcmUuZmlsZXNbMF0ubmFtZQogICAgLy9jb25zb2xlLmxvZyhyZXN0b3JlLnNyY0VsZW1lbnQuZmlsZXNbMF0pOwogICAgdmFyIGNoZWNrID0gY29uZmlybSgiRmlsZTogIiArIGZpbGVuYW1lICsgIlxuQWxsIGRhdGEgd2lsbCBiZSByZXBsYWNlZCB3aXRoIHRob3NlIGZyb20gdGhlIGJhY2t1cC5cblNob3VsZCB0aGUgZmlsZXMgYmUgcmVzdG9yZWQ/Iik7IAogICAgaWYgKGNoZWNrID09IHRydWUpIHsKICAgICAgdmFyIHJlYWRlciAgPSBuZXcgRmlsZVJlYWRlcigpOwogICAgICB2YXIgZmlsZSA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJ2lucHV0W3R5cGU9ZmlsZV0nKS5maWxlc1swXTsKICAgICAgaWYgKGZpbGUpIHsKICAgICAgICByZWFkZXIucmVhZEFzRGF0YVVSTChmaWxlKTsKICAgICAgICByZWFkZXIub25sb2FkID0gZnVuY3Rpb24oKSB7CiAgICAgICAgICBjb25zb2xlLmxvZyhyZWFkZXIucmVzdWx0KTsKICAgICAgICAgIHZhciBkYXRhID0gbmV3IE9iamVjdCgpOwogICAgICAgICAgZGF0YVsiY21kIl0gICAgID0gInh0ZXZlUmVzdG9yZSIKICAgICAgICAgIGRhdGFbImJhc2U2NCJdICA9IHJlYWRlci5yZXN1bHQKCiAgICAgICAgICB4VGVWZShkYXRhKTsKICAgICAgICAgIHJldHVybgogICAgICAgIH07CiAgICAgIH0gZWxzZSB7CiAgICAgICAgYWxlcnQoIkZpbGUgY291bGQgbm90IGJlIGxvYWRlZCIpCiAgICAgIH0KICAgIH0KICB9Owp9CgpmdW5jdGlvbiBnZXRCYXNlNjQoZmlsZSkgewogICB2YXIgcmVhZGVyID0gbmV3IEZpbGVSZWFkZXIoKTsKICAgcmVhZGVyLnJlYWRBc0RhdGFVUkwoZmlsZSk7CiAgIHJlYWRlci5vbmxvYWQgPSBmdW5jdGlvbigpIHsKICAgICBjb25zb2xlLmxvZyhyZWFkZXIucmVzdWx0KTsKICAgfTsKICAgcmVhZGVyLm9uZXJyb3IgPSBmdW5jdGlvbihlcnJvcikgewogICAgIGNvbnNvbGUubG9nKCdFcnJvcjogJywgZXJyb3IpOwogICB9Owp9CgpmdW5jdGlvbiBsb2dvdXQoKSB7CiAgZG9jdW1lbnQuY29va2llLnNwbGl0KCc7JykuZm9yRWFjaChmdW5jdGlvbihjKSB7CiAgICBkb2N1bWVudC5jb29raWUgPSBjLnRyaW0oKS5zcGxpdCgnPScpWzBdICsgJz07JyArICdleHBpcmVzPVRodSwgMDEgSmFuIDE5NzAgMDA6MDA6MDAgVVRDOyc7CiAgfSk7CiAgbG9jYXRpb24ucmVsb2FkKCk7Cn0KCmZ1bmN0aW9uIGdldENvb2tpZShuYW1lKSB7CiAgdmFyIHZhbHVlID0gIjsgIiArIGRvY3VtZW50LmNvb2tpZTsKICB2YXIgcGFydHMgPSB2YWx1ZS5zcGxpdCgiOyAiICsgbmFtZSArICI9Iik7CiAgaWYgKHBhcnRzLmxlbmd0aCA9PSAyKSByZXR1cm4gcGFydHMucG9wKCkuc3BsaXQoIjsiKS5zaGlmdCgpOwp9CgpmdW5jdGlvbiBzZXRDb29raWUodG9rZW4pIHsKICAvL2NvbnNvbGUubG9nKHRva2VuKTsKICBkb2N1bWVudC5jb29raWUgPSAiVG9rZW49IiArIHRva2VuCn0KCg==" + webUI["html/js/classes_ts.js"] = "dmFyIF9fZXh0ZW5kcyA9ICh0aGlzICYmIHRoaXMuX19leHRlbmRzKSB8fCAoZnVuY3Rpb24gKCkgewogICAgdmFyIGV4dGVuZFN0YXRpY3MgPSBmdW5jdGlvbiAoZCwgYikgewogICAgICAgIGV4dGVuZFN0YXRpY3MgPSBPYmplY3Quc2V0UHJvdG90eXBlT2YgfHwKICAgICAgICAgICAgKHsgX19wcm90b19fOiBbXSB9IGluc3RhbmNlb2YgQXJyYXkgJiYgZnVuY3Rpb24gKGQsIGIpIHsgZC5fX3Byb3RvX18gPSBiOyB9KSB8fAogICAgICAgICAgICBmdW5jdGlvbiAoZCwgYikgeyBmb3IgKHZhciBwIGluIGIpIGlmIChiLmhhc093blByb3BlcnR5KHApKSBkW3BdID0gYltwXTsgfTsKICAgICAgICByZXR1cm4gZXh0ZW5kU3RhdGljcyhkLCBiKTsKICAgIH07CiAgICByZXR1cm4gZnVuY3Rpb24gKGQsIGIpIHsKICAgICAgICBleHRlbmRTdGF0aWNzKGQsIGIpOwogICAgICAgIGZ1bmN0aW9uIF9fKCkgeyB0aGlzLmNvbnN0cnVjdG9yID0gZDsgfQogICAgICAgIGQucHJvdG90eXBlID0gYiA9PT0gbnVsbCA/IE9iamVjdC5jcmVhdGUoYikgOiAoX18ucHJvdG90eXBlID0gYi5wcm90b3R5cGUsIG5ldyBfXygpKTsKICAgIH07Cn0pKCk7CnZhciBNYWluTWVudSA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uICgpIHsKICAgIGZ1bmN0aW9uIE1haW5NZW51KCkgewogICAgICAgIHRoaXMuRG9jdW1lbnRJRCA9ICJtYWluLW1lbnUiOwogICAgICAgIHRoaXMuSFRNTFRhZyA9ICJMSSI7CiAgICB9CiAgICBNYWluTWVudS5wcm90b3R5cGUuY3JlYXRlID0gZnVuY3Rpb24gKCkgewogICAgICAgIGNvbnNvbGUubG9nKHRoaXMuRG9jdW1lbnRJRCk7CiAgICB9OwogICAgcmV0dXJuIE1haW5NZW51Owp9KCkpOwp2YXIgTWFpbk1lbnVJdGVtID0gLyoqIEBjbGFzcyAqLyAoZnVuY3Rpb24gKF9zdXBlcikgewogICAgX19leHRlbmRzKE1haW5NZW51SXRlbSwgX3N1cGVyKTsKICAgIGZ1bmN0aW9uIE1haW5NZW51SXRlbSgpIHsKICAgICAgICByZXR1cm4gX3N1cGVyICE9PSBudWxsICYmIF9zdXBlci5hcHBseSh0aGlzLCBhcmd1bWVudHMpIHx8IHRoaXM7CiAgICB9CiAgICBNYWluTWVudUl0ZW0ucHJvdG90eXBlLmNyZWF0ZTIgPSBmdW5jdGlvbiAoKSB7CiAgICAgICAgdmFyIGVsZW1lbnQgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KHRoaXMuSFRNTFRhZyk7CiAgICAgICAgZWxlbWVudC5pbm5lclRleHQgPSB0aGlzLlZhbHVlOwogICAgICAgIGNvbnNvbGUubG9nKGVsZW1lbnQpOwogICAgfTsKICAgIHJldHVybiBNYWluTWVudUl0ZW07Cn0oTWFpbk1lbnUpKTsKZnVuY3Rpb24gcGFnZVJlYWR5KCkgewogICAgdmFyIGl0ZW0gPSBuZXcgTWFpbk1lbnVJdGVtKCk7CiAgICBpdGVtLlZhbHVlID0gIlRlc3QiOwogICAgaXRlbS5jcmVhdGUyKCk7Cn0K" + webUI["html/js/mapping-editor.js"] = "dmFyIG1hcHBpbmdFcnJvciAgPSBmYWxzZTsKdmFyIGJ1bGsgICAgICAgICAgPSBmYWxzZTsKdmFyIGJ1bGtFZGl0QWxsICAgPSBmYWxzZTsgCnZhciBzZWxlY3RPYmogICAgID0gbmV3IE9iamVjdCgpOwp2YXIgc2VhcmNoT2JqICAgICA9IG5ldyBPYmplY3QoKTsKCnZhciBidWxrSURzICAgICAgID0gbmV3IEFycmF5KCk7CnZhciBidWxrQ2hhbmdlT2JqID0gbmV3IE9iamVjdCgpOwoKZnVuY3Rpb24gY2hlY2tVbmRvKGtleSwgZWxtKSB7CiAgdmFyIHRtcCA9IG5ldyBPYmplY3QoKTsKICB0bXAgPSBlbG0KICBjb25zb2xlLmxvZygiLS0iKTsKICBpZiAodW5kby5oYXNPd25Qcm9wZXJ0eSgiZXBnTWFwcGluZyIpKSB7CiAgICB4RVBHWyJlcGdNYXBwaW5nIl0gPSBKU09OLnBhcnNlKEpTT04uc3RyaW5naWZ5KHVuZG9bImVwZ01hcHBpbmciXSkpOzsKICB9IGVsc2UgewogICAgdW5kb1siZXBnTWFwcGluZyJdID0gSlNPTi5wYXJzZShKU09OLnN0cmluZ2lmeShlbG0pKTsKICB9Cn0KCi8vdmFyIHBsZXhDYXRlZ29yaWVzID0gbmV3IEFycmF5KCItIiwgIkFjdGlvbiBzcG9ydHMiLCAiQWN0aW9uIiwgIkFkdWx0cyBvbmx5IiwgIkFkdmVudHVyZSIsICJBZXJvYmljcyIsICJBbmltYWxzIiwgIkFuaW1hdGVkIiwgIkFuaW1lIiwgIkFudGhvbG9neSIsICJBcmNoZXJ5IiwgIkFydCIsICJBcnRzL2NyYWZ0cyIsICJBdWN0aW9uIiwgIkF1dG8gcmFjaW5nIiwgIkF1dG8iLCAiQXZpYXRpb24iLCAiQXdhcmRzIiwgIkJhbGxldCIsICJCYXNlYmFsbCIsICJCYXNrZXRiYWxsIiwgIkJpY3ljbGUgcmFjaW5nIiwgIkJpY3ljbGUiLCAiQmlsbGlhcmRzIiwgIkJpb2dyYXBoeSIsICJCb2F0IHJhY2luZyIsICJCb2F0IiwgIkJvd2xpbmciLCAiQm94aW5nIiwgIkJ1cy4vZmluYW5jaWFsIiwgIkNoaWxkcmVuIiwgIkNvbGxlY3RpYmxlcyIsICJDb21lZHkgZHJhbWEiLCAiQ29tZWR5IiwgIkNvbW11bml0eSIsICJDb21wdXRlcnMiLCAiQ29uc3VtZXIiLCAiQ29va2luZyIsICJDcmltZSBkcmFtYSIsICJDcmltZSIsICJEYW5jZSIsICJEYXJrIGNvbWVkeSIsICJEZWJhdGUiLCAiRGl2aW5nIiwgIkRvY3VkcmFtYSIsICJEb2N1bWVudGFyeSIsICJEcmFtYSIsICJFZHVjYXRpb25hbCIsICJFbnRlcnRhaW5tZW50IiwgIkVudmlyb25tZW50IiwgIkVxdWVzdHJpYW4iLCAiRXJvdGljIiwgIkV2ZW50IiwgIkZhbnRhc3kiLCAiRmFzaGlvbiIsICJGZWF0dXJlIEZpbG0iLCAiRmlzaGluZyIsICJGb290YmFsbCIsICJHYW1lIHNob3ciLCAiR2FtaW5nIiwgIkdheS9sZXNiaWFuIiwgIkdvbGYiLCAiSGFuZGJhbGwiLCAiSGVhbHRoIiwgIkhpc3RvcmljYWwgZHJhbWEiLCAiSGlzdG9yeSIsICJIb2NrZXkiLCAiSG9saWRheSIsICJIb21lIGltcHJvdmVtZW50IiwgIkhvcnJvciIsICJIb3JzZSIsICJIb3VzZS9nYXJkZW4iLCAiSG93LXRvIiwgIkludGVydmlldyIsICJJbnRsIHNvY2NlciIsICJMYXciLCAiTWFydGlhbCBhcnRzIiwgIk1lZGljYWwiLCAiTWlsaXRhcnkiLCAiTWluaXNlcmllcyIsICJNaXhlZCBtYXJ0aWFsIGFydHMiLCAiTW90b3JjeWNsZSByYWNpbmciLCAiTW90b3JjeWNsZSIsICJNb3RvcnNwb3J0cyIsICJNb3VudGFpbiBiaWtpbmciLCAiTXVzaWMiLCAiTXVzaWNhbCBjb21lZHkiLCAiTXVzaWNhbCIsICJNeXN0ZXJ5IiwgIk5hdHVyZSIsICJOZXdzIiwgIk5ld3NtYWdhemluZSIsICJPbHltcGljcyIsICJPcGVyYSIsICJPdXRkb29ycyIsICJQYXJhZGUiLCAiUGFyYW5vcm1hbCIsICJQYXJlbnRpbmciLCAiUGVyZm9ybWluZyBhcnRzIiwgIlBsYXlvZmYgc3BvcnRzIiwgIlBva2VyIiwgIlBvbGl0aWNzIiwgIlBybyB3cmVzdGxpbmciLCAiUHVibGljIGFmZmFpcnMiLCAiUmVhbGl0eSIsICJSZWxpZ2lvdXMiLCAiUm9kZW8iLCAiUm9sbGVyIGRlcmJ5IiwgIlJvbWFuY2UiLCAiUm9tYW50aWMgY29tZWR5IiwgIlJ1Z2J5IiwgIlJ1bm5pbmciLCAiU2FpbGluZyIsICJTY2llbmNlIGZpY3Rpb24iLCAiU2NpZW5jZSIsICJTZWxmIGltcHJvdmVtZW50IiwgIlNlcmllcyIsICJTaG9vdGluZyIsICJTaG9wcGluZyIsICJTaG9ydCBGaWxtIiwgIlNpdGNvbSIsICJTa2lpbmciLCAiU25vb2tlciIsICJTb2FwIiwgIlNvY2NlciIsICJTcGVjaWFsIiwgIlNwb3J0cyIsICJzcG9ydHMiLCAiU3BvcnRzIGV2ZW50IiwgIlNwb3J0cyBub24tZXZlbnQiLCAiU3BvcnRzIHRhbGsiLCAiU3RhbmR1cCIsICJTdXJmaW5nIiwgIlN1c3BlbnNlIiwgIlRWIE1vdmllIiwgIlRhbGsiLCAiVGVjaG5vbG9neSIsICJUZW5uaXMiLCAiVGhlYXRlciIsICJUaHJpbGxlciIsICJUcmFjay9maWVsZCIsICJUcmF2ZWwiLCAiVHJpYXRobG9uIiwgIlZhcmlldHkiLCAiVm9sbGV5YmFsbCIsICJXYXIiLCAiV2F0ZXJzcG9ydHMiLCAiV2VhdGhlciIsICJXZXN0ZXJuIiwgIldyZXN0bGluZyIsICJZYWNodCByYWNpbmciLCAibW92aWUiLCAic2VyaWVzIiwgInNwb3J0cyIsICJ0dnNob3ciKTsKdmFyIHBsZXhDYXRlZ29yaWVzVmFsdWVzID0gbmV3IEFycmF5KCItIiwgIktpZHMiLCAiTmV3cyIsICJNb3ZpZSIsICJTZXJpZXMiLCAiU3BvcnRzIikKdmFyIHBsZXhDYXRlZ29yaWVzT3B0aW9uID0gbmV3IEFycmF5KCItIiwgIktpZHMgKEVtYnkgb25seSkiLCAiTmV3cyIsICJNb3ZpZSIsICJTZXJpZXMiLCAiU3BvcnRzIikKCgpmdW5jdGlvbiBvcGVuTWFwcGluZ0VkaXRvcihlbG0pIHsKICB2YXIgY29sdW1uVG9Tb3J0ICA9IDEKCiAgY2hlY2tVbmRvKCJlcGdNYXBwaW5nIiwgeEVQR1siZXBnTWFwcGluZyJdKQoKICB2YXIgbmV3RGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNldHRpbmdzIik7CiAgCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICA9ICJIUiI7CiAgbmV3RGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICAgID0gIklOUFVUIjsKICBuZXdFbnRyeVsidHlwZSJdICAgICAgICA9ICJidXR0b24iOwogIG5ld0VudHJ5WyJjbGFzcyJdICAgICAgID0gImJ1dHRvbiI7CiAgbmV3RW50cnlbInZhbHVlIl0gICAgICAgPSAiU2F2ZSI7CiAgbmV3RW50cnlbIm9uY2xpY2siXSAgICAgPSAic2F2ZVhFUEcoKSI7CiAgbmV3RGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKCiAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdCgpOwogIG5ld0VudHJ5WyJfZWxlbWVudCJdICAgID0gIklOUFVUIjsKICBuZXdFbnRyeVsidHlwZSJdICAgICAgICA9ICJidXR0b24iOwogIG5ld0VudHJ5WyJjbGFzcyJdICAgICAgID0gImJ1dHRvbiI7CiAgbmV3RW50cnlbInZhbHVlIl0gICAgICAgPSAiQnVsayBFZGl0IjsKICBuZXdFbnRyeVsib25jbGljayJdICAgICA9ICJidWxrRWRpdCgpIjsKICBuZXdEaXYuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdFbnRyeSkpOwoKICB2YXIgbmV3RW50cnkgPSBuZXcgT2JqZWN0KCk7CiAgbmV3RW50cnlbIl9lbGVtZW50Il0gICAgPSAiSU5QVVQiOwogIG5ld0VudHJ5WyJ0eXBlIl0gICAgICAgID0gImJ1dHRvbiI7CiAgbmV3RW50cnlbImNsYXNzIl0gICAgICAgPSAiYnV0dG9uIjsKICBuZXdFbnRyeVsidmFsdWUiXSAgICAgICA9ICJTaG93IFhFUEciOwogIG5ld0VudHJ5WyJvbmNsaWNrIl0gICAgID0gInNob3dYRVBHKCkiOwogIG5ld0Rpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0VudHJ5KSk7CgogIHZhciBuZXdFbnRyeSA9IG5ldyBPYmplY3QoKTsKICBuZXdFbnRyeVsiX2VsZW1lbnQiXSAgICA9ICJJTlBVVCI7CiAgbmV3RW50cnlbImNsYXNzIl0gICAgICAgPSAic2VhcmNoIjsKICBuZXdFbnRyeVsiaWQiXSAgICAgICAgICA9ICJzZWFyY2hNYXBwaW5nIjsKICBuZXdFbnRyeVsidHlwZSJdICAgICAgICA9ICJzZWFyY2giOwogIG5ld0VudHJ5WyJwbGFjZWhvbGRlciJdID0gIlNlYXJjaCI7CiAgbmV3RW50cnlbIm9uY2hhbmdlIl0gICAgPSAic2VhcmNoSW5NYXBwaW5nKCkiOwogIG5ld0Rpdi5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0VudHJ5KSk7CgogIHZhciBkaXYgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgic2V0dGluZ3MiKTsKICAvL3NjcmVlbkxvZygiRHVwbGljYXRlIElEIiwgImVycm9yIiwgdHJ1ZSkKICAKCiAgLy8gQnVpbGQgdGFibGUKCiAgdmFyIG5ld1dyYXBwZXIgPSBuZXcgT2JqZWN0KCk7CiAgbmV3V3JhcHBlclsiX2VsZW1lbnQiXSAgPSAiRElWIjsKICBuZXdXcmFwcGVyWyJpZCJdICAgICAgICA9ICJib3gtd3JhcHBlciI7CiAgZGl2LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3V3JhcHBlcikpOwoKCiAgdmFyIG5ld1RhYmxlID0gbmV3IE9iamVjdCgpOwogIG5ld1RhYmxlWyJfZWxlbWVudCJdICA9ICJUQUJMRSI7CiAgbmV3VGFibGVbImlkIl0gICAgICAgID0gImlkX21hcHBpbmciOwogIG5ld1RhYmxlWyJjbGFzcyJdICAgICA9ICJ0YWJsZS1tYXBwaW5nIjsKICBkaXYubGFzdENoaWxkLmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3VGFibGUpKTsKICBzaG93TG9hZGluZ1NjcmVlbih0cnVlKTsKCiAgc2V0VGltZW91dChmdW5jdGlvbigpeyAKICAgIGNyZWF0ZU1hcHBpbmdUYWJsZSgpOyAKICB9LCAxMCk7Cgp9CgpmdW5jdGlvbiBjcmVhdGVTZWFyY2hPYmooKSB7CiAgc2VhcmNoT2JqID0gbmV3IE9iamVjdCgpOwogIHZhciBJRHMgPSBnZXRPYmpLZXlzKHhFUEdbImVwZ01hcHBpbmciXSkKICBmb3IgKHZhciBpID0gSURzLmxlbmd0aCAtIDE7IGkgPj0gMDsgaS0tKSB7CiAgICB2YXIgaXRlbSA9IHhFUEdbImVwZ01hcHBpbmciXVtJRHNbaV1dOwogICAgdmFyIHNlYXJjaElEID0gaXRlbVsieC1lcGciXTsKICAgIHZhciBzZWFyY2hWYWx1ZSA9ICIiOyAKICAgIHNlYXJjaFZhbHVlID0gc2VhcmNoVmFsdWUgKyBpdGVtWyJ4LWNoYW5uZWxJRCJdICsgIiAiOwogICAgc2VhcmNoVmFsdWUgPSBzZWFyY2hWYWx1ZSArIGl0ZW1bIngtY2F0ZWdvcnkiXSArICIgIjsKICAgIHNlYXJjaFZhbHVlID0gc2VhcmNoVmFsdWUgKyBpdGVtWyJ4LW5hbWUiXSArICIgIjsKICAgIHNlYXJjaFZhbHVlID0gc2VhcmNoVmFsdWUgKyBpdGVtWyJ4LWdyb3VwLXRpdGxlIl0gKyAiICI7CiAgICBzZWFyY2hWYWx1ZSA9IHNlYXJjaFZhbHVlICsgaXRlbVsieC14bWx0di1maWxlIl0gKyAiICI7CiAgICBzZWFyY2hWYWx1ZSA9IHNlYXJjaFZhbHVlICsgaXRlbVsiX2ZpbGUubTN1Lm5hbWUiXSArICIgIjsKCiAgICBzd2l0Y2goaXRlbVsieC1hY3RpdmUiXSkgewogICAgICBjYXNlIHRydWU6ICBzZWFyY2hWYWx1ZSA9IHNlYXJjaFZhbHVlICsgIm9ubGluZSI7IGJyZWFrOwogICAgICBjYXNlIGZhbHNlOiBzZWFyY2hWYWx1ZSA9IHNlYXJjaFZhbHVlICsgIm9mZmxpbmUiOyBicmVhazsKICAgIH0KCiAgICBzZWFyY2hPYmpbc2VhcmNoVmFsdWVdID0gc2VhcmNoSUQ7CgogIH0KfQoKCmZ1bmN0aW9uIGNhbGN1bGF0ZVdyYXBwZXJIZWlnaHQoKSB7CgogIGlmIChkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiYm94LXdyYXBwZXIiKSl7CgogICAgdmFyIGVsbSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJib3gtd3JhcHBlciIpOwogICAgCiAgICB2YXIgZGl2cyA9IG5ldyBBcnJheSgibXlTdHJlYW1zQm94IiwgImNsaWVudEluZm8iLCAic2V0dGluZ3MiKTsKICAgIHZhciBlbGVtZW50c0hlaWdodCA9IDAgLSBlbG0ub2Zmc2V0SGVpZ2h0OwogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBkaXZzLmxlbmd0aDsgaSsrKSB7CiAgICAgIGVsZW1lbnRzSGVpZ2h0ID0gZWxlbWVudHNIZWlnaHQgKyBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChkaXZzW2ldKS5vZmZzZXRIZWlnaHQ7CiAgICB9CgogICAgZWxtLnN0eWxlLmhlaWdodCA9IHdpbmRvdy5pbm5lckhlaWdodCAtIGVsZW1lbnRzSGVpZ2h0ICsgInB4IjsKCiAgfQoKICBpZiAoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIm1lbnUtd3JhcHBlciIpKXsKCiAgICB2YXIgZWxtID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIm1lbnUtd3JhcHBlciIpOwogICAgCiAgICB2YXIgb2ZmZXN0ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNldHRpbmdzIikub2Zmc2V0SGVpZ2h0ICsgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIm15U3RyZWFtc0JveCIpLm9mZnNldEhlaWdodCArIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJjbGllbnRJbmZvIikub2Zmc2V0SGVpZ2h0OwogICAgCiAgICBpZiAod2luZG93LmlubmVySGVpZ2h0ID4gb2ZmZXN0KSB7CiAgICAgIGVsbS5zdHlsZS5oZWlnaHQgPSB3aW5kb3cuaW5uZXJIZWlnaHQgKyAicHgiCiAgICB9IGVsc2UgewogICAgICBlbG0uc3R5bGUuaGVpZ2h0ID0gb2ZmZXN0ICsgInB4IgogICAgfQogICAgCgogIH0KCgp9CgpmdW5jdGlvbiBjcmVhdGVNYXBwaW5nVGFibGUoKSB7CiAgY29sdW1uVG9Tb3J0ID0gMTsKICBjcmVhdGVTZWFyY2hPYmooKTsKCiAgLy8gQ3JlYXRlIHRhYmxlIChIZWFkZXIpCiAgdmFyIHRhYmxlID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImlkX21hcHBpbmciKTsKICB0YWJsZS5pbm5lckhUTUwgPSAiIjsKICB2YXIgbmV3VFIgPSBuZXcgT2JqZWN0KCk7CiAgbmV3VFJbIl9lbGVtZW50Il0gPSAiVFIiOwogIG5ld1RSWyJjbGFzcyJdICAgID0gInRhYmxlLW1hcHBpbmctaGVhZGVyIjsKICB0YWJsZS5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld1RSKSk7CgogIHZhciB0ciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJpZF9tYXBwaW5nIikubGFzdENoaWxkOwogIHZhciB0ckhlYWRsaW5lcyA9IG5ldyBBcnJheSgiQnVsayIsICJDaC4gTm8uIiwgIkxvZ28iLCAiQ2hhbm5lbCBOYW1lIiwgIlBsYXlsaXN0IiwgIkdyb3VwIFRpdGxlIiwgIlhNTFRWIEZpbGUiLCAiWE1MVFYgSUQiKQoKICBmb3IgKHZhciBpID0gMDsgaSA8IHRySGVhZGxpbmVzLmxlbmd0aDsgaSsrKSB7CiAgICB2YXIgbmV3VEQgPSBuZXcgT2JqZWN0KCk7CgogICAgbmV3VERbIl9lbGVtZW50Il0gPSAiVEQiOwogICAgbmV3VERbIl90ZXh0Il0gICAgPSB0ckhlYWRsaW5lc1tpXTsKCiAgICAKCiAgICB2YXIgd2lkdGggPSAiIjsKICAgIHN3aXRjaCh0ckhlYWRsaW5lc1tpXSkgewoKICAgICAgY2FzZSAiQnVsayI6ICAKICAgICAgICAKICAgICAgICBtYXhXaWR0aCA9ICIzMnB4IjsgCiAgICAgICAgbWluV2lkdGggPSAiMzJweCI7IAogICAgCiAgICAgICAgLy8gQ3JlYXRlIGJ1bGsgVEQKICAgICAgICB2YXIgbmV3Q2hlY2tib3ggPSBuZXcgT2JqZWN0KCk7CiAgICAgICAgbmV3Q2hlY2tib3hbIl9lbGVtZW50Il0gPSAiSU5QVVQiOwogICAgICAgIG5ld0NoZWNrYm94WyJ0eXBlIl0gICAgID0gImNoZWNrYm94IjsKICAgICAgICBuZXdDaGVja2JveFsiY2xhc3MiXSAgICA9ICJidWxrIGhpZGVCdWxrIjsKICAgICAgICBuZXdDaGVja2JveFsib25tb3VzZW91dCJdID0gImphdmFzY3JpcHQ6IHRoaXMuYmx1cigpIgogICAgICAgIG5ld0NoZWNrYm94WyJvbmNsaWNrIl0gICAgPSAiamF2YXNjcmlwdDogYnVsa0VkaXRBbGxDaGFubmVscygpIgoKICAgIAogICAgICAgIC8vbmV3VEQuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdDaGVja2JveCkpOwoKICAgICAgICBicmVhazsKCiAgICAgIGNhc2UgIkNoLiBOby4iOiAKICAgICAgICBtYXhXaWR0aCA9ICI4MHB4IjsgCiAgICAgICAgbWluV2lkdGggPSAiNzBweCI7IAogICAgICAgIG5ld1REWyJvbmNsaWNrIl0gID0gImphdnNjcmlwdDogc29ydFRhYmxlKCIgKyBpICsgIik7IjsKICAgICAgICBuZXdURFsiY2xhc3MiXSAgICA9ICJwb2ludGVyIjsKICAgICAgICBicmVhazsKICAgICAgCiAgICAgIGNhc2UgIkxvZ28iOiAgbWF4V2lkdGggPSAiMTIwcHgiOyBtaW5XaWR0aCA9ICI2MHB4IjsgYnJlYWs7CiAgICAgIAogICAgICBjYXNlICJDaGFubmVsIE5hbWUiOiAgCiAgICAgICAgbWF4V2lkdGggPSAiNTAlIjsgCiAgICAgICAgbWluV2lkdGggPSAiMjAwcHgiOyAKICAgICAgICBuZXdURFsib25jbGljayJdICA9ICJqYXZzY3JpcHQ6IHNvcnRUYWJsZSgiICsgaSArICIpOyI7CiAgICAgICAgbmV3VERbImNsYXNzIl0gPSAicG9pbnRlciI7CiAgICAgICAgYnJlYWs7CgogICAgICBjYXNlICJQbGF5bGlzdCI6ICAgICAgCiAgICAgICAgbWF4V2lkdGggPSAiMTUwcHgiOyAKICAgICAgICBtaW5XaWR0aCA9ICIxMDBweCI7IAogICAgICAgIG5ld1REWyJvbmNsaWNrIl0gID0gImphdnNjcmlwdDogc29ydFRhYmxlKCIgKyBpICsgIik7IjsKICAgICAgICBuZXdURFsiY2xhc3MiXSAgICA9ICJwb2ludGVyIjsKICAgICAgICBicmVhazsKICAgICAgCiAgICAgIGNhc2UgIkdyb3VwIFRpdGxlIjogICAKICAgICAgICBtYXhXaWR0aCA9ICIxNTBweCI7IAogICAgICAgIG1pbldpZHRoID0gIjEwMHB4IjsgCiAgICAgICAgbmV3VERbIm9uY2xpY2siXSAgPSAiamF2c2NyaXB0OiBzb3J0VGFibGUoIiArIGkgKyAiKTsiOwogICAgICAgIG5ld1REWyJjbGFzcyJdICAgID0gInBvaW50ZXIiOwogICAgICAgIGJyZWFrOwogICAgICAKICAgICAgY2FzZSAiWE1MVFYgRmlsZSI6ICAgIAogICAgICAgIG1heFdpZHRoID0gIjE1MHB4IjsgCiAgICAgICAgbWluV2lkdGggPSAiMTAwcHgiOyAKICAgICAgICAvL25ld1REWyJvbmNsaWNrIl0gID0gImphdnNjcmlwdDogc29ydFRhYmxlKCIgKyBpICsgIik7IjsKICAgICAgICBuZXdURFsiY2xhc3MiXSAgICA9ICIiOwogICAgICAgIGJyZWFrOwogICAgICAKCiAgICAgIGNhc2UgIlhNTFRWIElEIjogICAgICBtYXhXaWR0aCA9ICIxNTBweCI7IG1pbldpZHRoID0gIjEwMHB4IjsgYnJlYWs7CgogICAgICBkZWZhdWx0OiAKICAgICAgICBuZXdURFsiY2xhc3MiXSAgICA9ICIiOwogICAgICAgIGJyZWFrOwogICAgfQoKICAgIHRyLmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3VEQpKTsKICAgIGlmICh0ckhlYWRsaW5lc1tpXSA9PSAiQnVsayIpIHsKICAgICAgdHIubGFzdENoaWxkLmlubmVySFRNTCA9ICIiOwogICAgICB0ci5sYXN0Q2hpbGQuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdDaGVja2JveCkpOwogICAgICAKICAgIH0KICAgIAogICAgdmFyIGVsbSA9IHRyLmxhc3RDaGlsZDsKICAgIGVsbS5zdHlsZS53aWR0aCA9IG1heFdpZHRoOwogICAgZWxtLnN0eWxlLm1heFdpZHRoID0gbWF4V2lkdGg7CiAgICBlbG0uc3R5bGUubWluV2lkdGggPSBtaW5XaWR0aDsKCiAgfQogIGNhbGN1bGF0ZVdyYXBwZXJIZWlnaHQoKTsKICB2YXIgSURzID0gZ2V0T2JqS2V5cyh4RVBHWyJlcGdNYXBwaW5nIl0pCgogIHZhciBhbGxYbWx0dkZpbGVzID0gZ2V0T2JqS2V5cyh4RVBHWyJ4bWx0dk1hcCJdKTsKCiAgaWYgKGFsbFhtbHR2RmlsZXMgPT0gMCkgewogICAgc2hvd0xvYWRpbmdTY3JlZW4oZmFsc2UpOwogICAgcmV0dXJuOwogIH0KCiAgLy8gU29ydCBJRHMKICB2YXIgcG9zT2JqID0gbmV3IE9iamVjdCgpOwogIGZvciAodmFyIGkgPSAwOyBpIDwgSURzLmxlbmd0aDsgaSsrKSB7CiAgICB2YXIgaXRlbSAgPSB4RVBHWyJlcGdNYXBwaW5nIl1bSURzW2ldXTsKICAgIHZhciBwb3MKICAgIHN3aXRjaChpc05hTih4RVBHWyJlcGdNYXBwaW5nIl1bSURzW2ldXVsieC1jaGFubmVsSUQiXSkpIHsKICAgICAgY2FzZSBmYWxzZTogcG9zID0gcGFyc2VGbG9hdCh4RVBHWyJlcGdNYXBwaW5nIl1bSURzW2ldXVsieC1jaGFubmVsSUQiXSkgOyBicmVhazsKICAgIH0KICAgIHBvc09ialtwb3NdID0gaXRlbTsKICB9CiAgcG9zRmxvYXQgPSBnZXRPYmpLZXlzKHBvc09iaikKICBmdW5jdGlvbiBzb3J0RmxvYXQoYSxiKSB7IHJldHVybiBhIC0gYjsgfQogIHBvc0Zsb2F0LnNvcnQoc29ydEZsb2F0KQoKICAvL2NvbnNvbGUubG9nKHBvc0Zsb2F0KTsKCiAgLy8gLS0tCgogIGlmIChJRHMubGVuZ3RoID4gMjAwKSB7CiAgICBzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7IAogICAgICBzaG93TG9hZGluZ1NjcmVlbih0cnVlKTsKICAgIH0sIDEpOwoKICB9CgoKICAvLyB0YWJsZSBmb3IgaW50IGNoYW5uZWwgSUQncwogIGZvciAodmFyIGkgPSAwOyBpIDwgcG9zRmxvYXQubGVuZ3RoOyBpKyspIHsKCiAgICB2YXIgdGFibGUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiaWRfbWFwcGluZyIpOwogICAgdmFyIGl0ZW0gID0gcG9zT2JqW3Bvc0Zsb2F0W2ldXTsKICAgIC8vdmFyIGl0ZW0gID0geEVQR1siZXBnTWFwcGluZyJdW0lEc1tpXV07CiAgICAvL2NvbnNvbGUubG9nKGl0ZW0pOwogICAgdmFyIG5ld1RSID0gbmV3IE9iamVjdCgpOwogICAgbmV3VFJbIl9lbGVtZW50Il0gICAgICAgPSAiVFIiOwogICAgbmV3VFJbImNsYXNzIl0gICAgICAgICAgPSAiIjsKICAgIG5ld1RSWyJpZCJdICAgICAgICAgICAgID0gaXRlbVsieC1lcGciXTsKICAgIG5ld1RSWyJvbmNvbnRleHRtZW51Il0gID0gJ2phdmFzY3JpcHQ6IHN3aXRjaENoYW5uZWxTdGF0dXMoIicgKyBpdGVtWyJ4LWVwZyJdICsgJyIpOyByZXR1cm4gZmFsc2U7JzsKICAgIHRhYmxlLmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3VFIpKTsKCiAgICB2YXIgdHIgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiaWRfbWFwcGluZyIpLmxhc3RDaGlsZDsKICAgIAogICAgLy8gQ3JlYXRlIGJ1bGsgVEQKICAgIHZhciBuZXdURCA9IG5ldyBPYmplY3QoKTsKICAgIG5ld1REWyJfZWxlbWVudCJdICAgPSAiSU5QVVQiOwogICAgbmV3VERbInR5cGUiXSAgICAgICA9ICJjaGVja2JveCI7CiAgICBuZXdURFsiY2xhc3MiXSAgICAgID0gImJ1bGsgaGlkZUJ1bGsiOwogICAgbmV3VERbIm9ubW91c2VvdXQiXSA9ICJqYXZhc2NyaXB0OiB0aGlzLmJsdXIoKSIKICAgIAogICAgY3JlYXRlTmV3VEQobmV3VEQsIHRyKTsKICAgIAoKICAgIC8vIENyZWF0ZSBJRCBURAogICAgdmFyIG5ld1REID0gbmV3IE9iamVjdCgpOwogICAgbmV3VERbIl9lbGVtZW50Il0gPSAiSU5QVVQiOwogICAgbmV3VERbInR5cGUiXSAgICAgPSAidGV4dCIKICAgIG5ld1REWyJjbGFzcyJdICAgID0gInc0MHB4IjsKICAgIG5ld1REWyJ2YWx1ZSJdICAgID0gaXRlbVsieC1jaGFubmVsSUQiXTsKICAgIG5ld1REWyJvbmZvY3Vzb3V0Il0gPSAiamF2YXNjcmlwdDogYXJyYW5nZVRhYmxlKHRoaXMpOyIKICAgIGNyZWF0ZU5ld1REKG5ld1RELCB0cik7CgogICAgLy8gQ3JlYXRlIElNRyBURAogICAgdmFyIG5ld1REID0gbmV3IE9iamVjdCgpOwogICAgbmV3VERbIl9lbGVtZW50Il0gPSAiSU1HIjsKICAgIG5ld1REWyJvbmNsaWNrIl0gID0gJ2phdmFzY3JpcHQ6IG1hcHBpbmdEZXRhaWwoIicgKyBpdGVtWyJ4LWVwZyJdICsgJyIpOyc7CiAgICBpZiAoaXRlbVsidHZnLWxvZ28iXSAhPSB1bmRlZmluZWQpIHsKICAgICAgbmV3VERbInNyYyJdICAgICAgPSBpdGVtWyJ0dmctbG9nbyJdOwogICAgfSBlbHNlIHsKICAgICAgaXRlbVsidHZnLWxvZ28iXSA9ICIiOwogICAgICBuZXdURFsic3JjIl0gPSAiIjsKICAgIH0KICAgIGNyZWF0ZU5ld1REKG5ld1RELCB0cik7CiAgICB0ci5sYXN0Q2hpbGQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IG1hcHBpbmdEZXRhaWwoIicgKyBpdGVtWyJ4LWVwZyJdICsgJyIpOycpCgogICAgLy8gQ3JlYXRlIFAgVEQgKGNoYW5uZWwgbmFtZSkKICAgIHZhciBuZXdURCA9IG5ldyBPYmplY3QoKTsKICAgIG5ld1REWyJfZWxlbWVudCJdID0gIlAiOwogICAgbmV3VERbIl90ZXh0Il0gICAgPSBpdGVtWyJ4LW5hbWUiXTsKICAgIG5ld1REWyJjbGFzcyJdICAgICA9IGl0ZW1bIngtY2F0ZWdvcnkiXTsKCiAgICBjcmVhdGVOZXdURChuZXdURCwgdHIpOwogICAgdHIubGFzdENoaWxkLnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiBtYXBwaW5nRGV0YWlsKCInICsgaXRlbVsieC1lcGciXSArICciKTsnKQogICAgdHIubGFzdENoaWxkLmxhc3RDaGlsZC5zdHlsZS5wYWRkaW5nID0gIjVweCAxMHB4IjsKCiAgICAvLyBDcmVhdGUgUCBURCAoUGxheWxpc3QgTmFtZSkKICAgIHZhciBuZXdURCA9IG5ldyBPYmplY3QoKTsKICAgIG5ld1REWyJfZWxlbWVudCJdID0gIlAiOwogICAgbmV3VERbIl90ZXh0Il0gICAgPSBpdGVtWyJfZmlsZS5tM3UubmFtZSJdOwogICAgbmV3VERbImNsYXNzIl0gICAgID0gaXRlbVsidGFibGVFbGxpcHNpcyJdOwogICAgCiAgICBjcmVhdGVOZXdURChuZXdURCwgdHIpOwogICAgdHIubGFzdENoaWxkLnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiBtYXBwaW5nRGV0YWlsKCInICsgaXRlbVsieC1lcGciXSArICciKTsnKQogICAgCiAgICAvLyBDcmVhdGUgUCBURCAoR3JvdXAgVGl0bGUpCiAgICB2YXIgbmV3VEQgPSBuZXcgT2JqZWN0KCk7CiAgICBuZXdURFsiX2VsZW1lbnQiXSA9ICJQIjsKICAgIG5ld1REWyJfdGV4dCJdICAgID0gaXRlbVsieC1ncm91cC10aXRsZSJdOwogICAgbmV3VERbImNsYXNzIl0gICAgID0gaXRlbVsidGFibGVFbGxpcHNpcyJdOwogICAgCiAgICBjcmVhdGVOZXdURChuZXdURCwgdHIpOwogICAgdHIubGFzdENoaWxkLnNldEF0dHJpYnV0ZSgib25jbGljayIsICdqYXZhc2NyaXB0OiBtYXBwaW5nRGV0YWlsKCInICsgaXRlbVsieC1lcGciXSArICciKTsnKQoKICAgIAogICAgLy8gQ3JlYXRlIFAgVEQgKFhNTFRWIGZpbGUpCiAgICB2YXIgbmV3VEQgPSBuZXcgT2JqZWN0KCk7CiAgICBuZXdURFsiX2VsZW1lbnQiXSAgICAgICA9ICJQIjsKICAgIG5ld1REWyJjbGFzcyJdICAgID0gInRhYmxlRWxsaXBzaXMiOwogICAgbmV3VERbIl90ZXh0Il0gPSAiLSIKCiAgICBpZiAoYWxsWG1sdHZGaWxlcy5pbmRleE9mKGl0ZW1bIngteG1sdHYtZmlsZSJdKSAhPSAtMSkgewogICAgICB2YXIgeFhtbHR2RmlsZSA9IGl0ZW1bIngteG1sdHYtZmlsZSJdOwogICAgICBzd2l0Y2goeFhtbHR2RmlsZSkgewogICAgICAgIGNhc2UgIi0iOiAgICAgICAgICAgbmV3VERbIl90ZXh0Il0gID0geFhtbHR2RmlsZTsgYnJlYWs7CiAgICAgICAgY2FzZSAieFRlVmUgRHVtbXkiOiBuZXdURFsiX3RleHQiXSAgPSB4WG1sdHZGaWxlOyBicmVhazsKICAgICAgICBkZWZhdWx0OiAgICAgICAgICAgIG5ld1REWyJfdGV4dCJdICA9IGdldFZhbHVlRnJvbVByb3ZpZGVyRmlsZSh4WG1sdHZGaWxlLCAieG1sdHYiLCAibmFtZSIpOyBicmVhazsKICAgICAgICAKICAgICAgfQogICAgICAvL2NvbnNvbGUubG9nKG5ld1REKTsKCiAgICAgIC8vbmV3VERbIl90ZXh0Il0gICAgPSBpdGVtWyJ4LXhtbHR2LWZpbGUiXTsKICAgIH0gZWxzZSB7CiAgICAgIC8vbmV3VERbIl90ZXh0Il0gPSAiLSIKICAgIH0KICAgIGNyZWF0ZU5ld1REKG5ld1RELCB0cik7CiAgICB0ci5sYXN0Q2hpbGQuc2V0QXR0cmlidXRlKCJvbmNsaWNrIiwgJ2phdmFzY3JpcHQ6IG1hcHBpbmdEZXRhaWwoIicgKyBpdGVtWyJ4LWVwZyJdICsgJyIpOycpCgogICAgLy8gQ3JlYXRyIFAgVEQgKFhNTFRWIGNoYW5uZWwgSUQpCiAgICBuZXdURFsiX2VsZW1lbnQiXSA9ICJQIjsKICAgIG5ld1REWyJjbGFzcyJdICAgID0gInRhYmxlRWxsaXBzaXMiOwoKICAgIGlmIChpdGVtWyJ4LW1hcHBpbmciXSAhPSB1bmRlZmluZWQpIHsKICAgICAgbmV3VERbIl90ZXh0Il0gICAgPSBpdGVtWyJ4LW1hcHBpbmciXTsKICAgIH0KICAgIAogICAgY3JlYXRlTmV3VEQobmV3VEQsIHRyKTsKICAgIHRyLmxhc3RDaGlsZC5zZXRBdHRyaWJ1dGUoIm9uY2xpY2siLCAnamF2YXNjcmlwdDogbWFwcGluZ0RldGFpbCgiJyArIGl0ZW1bIngtZXBnIl0gKyAnIik7JykKCgogICAgdmFyIHhYbWx0dkZpbGUgID0gaXRlbVsieC14bWx0di1maWxlIl07CiAgICB2YXIgeE1hcHBpbmcgICAgPSBpdGVtWyJ4LW1hcHBpbmciXTsKICAgIHZhciB0dmdJRCAgICAgICA9IGl0ZW1bInR2Zy1pZCJdOwogICAgCiAgICAvL2NvbnNvbGUubG9nKGl0ZW1bIngtZXBnIl0pOwogICAgLy9jb25zb2xlLmxvZyhpdGVtKTsKCiAgICBpZiAoaXRlbVsieC1hY3RpdmUiXSA9PSB0cnVlKSB7CiAgICAgIHRyLmNsYXNzTmFtZSA9ICJhY3RpdmVFUEciOwogICAgfSBlbHNlIHsKICAgICAgdHIuY2xhc3NOYW1lID0gIm5vdEFjdGl2ZUVQRyI7CiAgICB9CiAgICAKICB9CgogIHNvcnRUYWJsZSgxKTsKCiAgc2V0VGltZW91dChmdW5jdGlvbigpeyAKICAgIHNob3dMb2FkaW5nU2NyZWVuKGZhbHNlKTsKICB9LCA1KTsKfQoKZnVuY3Rpb24gc2VhcmNoSW5NYXBwaW5nKGVsbSkgewoKICB2YXIgc2VhcmNoID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInNlYXJjaE1hcHBpbmciKS52YWx1ZTsKICB2YXIgdmFsdWVzID0gZ2V0T2JqS2V5cyhzZWFyY2hPYmopCiAgCiAgZm9yICh2YXIgaSA9IHZhbHVlcy5sZW5ndGggLSAxOyBpID49IDA7IGktLSkgewogICAgdmFyIGlkID0gc2VhcmNoT2JqW3ZhbHVlc1tpXV07CiAgICB2YXIgYm9vbCA9IHZhbHVlc1tpXS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKHNlYXJjaC50b0xvd2VyQ2FzZSgpKTsKICAgIHN3aXRjaChib29sKSB7CiAgICAgIGNhc2UgdHJ1ZTogIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlkKS5zdHlsZS5kaXNwbGF5ID0gIiI7IGJyZWFrOwogICAgICBjYXNlIGZhbHNlOiBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuc3R5bGUuZGlzcGxheSA9ICJub25lIjsgYnJlYWs7CiAgICB9CiAgfQoKfQoKZnVuY3Rpb24gbWFwcGluZ0RldGFpbCh4ZXBnKSB7CiAgCiAgYnVsa0lEcyAgID0gbmV3IEFycmF5KCk7CiAgdmFyIGFjdGl2ZUVsZW1lbnQgPSBkb2N1bWVudC5hY3RpdmVFbGVtZW50OwogIC8vIElmIGlucHV0IGlkLCByZXR1cm4KICBpZiAoYWN0aXZlRWxlbWVudC50YWdOYW1lID09ICJJTlBVVCIpIHsKICAgIHJldHVybgogIH0KCiAgaWYgKGJ1bGsgPT0gdHJ1ZSkgewogICAgdmFyIGVsbSA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoImJ1bGsiKTsKICAgIGZvciAodmFyIGkgPSAxOyBpIDwgZWxtLmxlbmd0aDsgaSsrKSB7CiAgICAgIGlmIChlbG1baV0uY2hlY2tlZCA9PSB0cnVlKSB7CiAgICAgICAgdmFyIGlkID0gZWxtW2ldLnBhcmVudEVsZW1lbnQucGFyZW50RWxlbWVudC5pZDsKICAgICAgICBidWxrSURzLnB1c2goaWQpCiAgICAgIH0KICAgICAgCiAgICB9CgogICAgaWYgKGJ1bGtJRHMubGVuZ3RoID09IDApIHsKICAgICAgc2hvd0VsZW1lbnQoJ3BvcHVwJywgZmFsc2UpCiAgICAgIGFsZXJ0KCJObyBjaGFubmVscyBzZWxlY3RlZCBmb3IgZWRpdGluZyIpCiAgICAgIHJldHVybgogICAgfQoKICAgIHhlcGcgPSBidWxrSURzWzBdCiAgfQoKCiAgY3JlYXRlU2VhcmNoT2JqKCk7CiAgCiAgc2hvd1BvcFVwRWxlbWVudCgnbWFwcGluZy1kZXRhaWwnKTsKCiAgdmFyIHRoaXNDaGFubmVsID0geEVQR1siZXBnTWFwcGluZyJdW3hlcGddOwogIC8vY29uc29sZS5sb2codGhpc0NoYW5uZWwpOwogIHZhciB4WG1sdHZGaWxlICA9IHRoaXNDaGFubmVsWyJ4LXhtbHR2LWZpbGUiXTsKICB2YXIgeE1hcHBpbmcgICAgPSB0aGlzQ2hhbm5lbFsieC1tYXBwaW5nIl07CiAgdmFyIHhDYXRlZ29yeSAgID0gdGhpc0NoYW5uZWxbIngtY2F0ZWdvcnkiXTsKCiAgaWYgKHhYbWx0dkZpbGUgPT0gdW5kZWZpbmVkKSB7CiAgICB0aGlzQ2hhbm5lbFsieC14bWx0di1maWxlIl0gPSAiLSI7CiAgICB4WG1sdHZGaWxlID0gIi0iOwogIH0KCiAgaWYgKHhNYXBwaW5nID09IHVuZGVmaW5lZCkgewogICAgdGhpc0NoYW5uZWxbIngtbWFwcGluZyJdID0gIi0iOwogICAgeE1hcHBpbmcgPSAiLSI7CiAgfQoKICAvKgogIGNvbnNvbGUubG9nKCJJRDoiLCB4ZXBnKTsKICBjb25zb2xlLmxvZygiWE1MVFYgRmlsZToiLCB4WG1sdHZGaWxlKTsKICBjb25zb2xlLmxvZygiTWFwcGluZzoiLCB4TWFwcGluZyk7CiAgKi8KCiAgdmFyIGtleXMgPSBnZXRPYmpLZXlzKHRoaXNDaGFubmVsKTsKICBmb3IgKHZhciBpID0gMDsgaSA8IGtleXMubGVuZ3RoOyBpKyspIHsKICAgIGlmKGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGtleXNbaV0pKXsKICAgICAgdmFyIHRkID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoa2V5c1tpXSkKICAgIH0gZWxzZSB7CiAgICAgIHZhciB0ZCA9IHVuZGVmaW5lZDsKICAgIH0KICAgIAogICAgdmFyIG5ld0l0ZW0gPSBuZXcgT2JqZWN0KCk7CiAgICB2YXIgdmFsdWVzLCB0ZXh0ID0gbmV3IEFycmF5KCk7CiAgICBzd2l0Y2goa2V5c1tpXSkgewogICAgICBjYXNlICJ4LXhtbHR2LWZpbGUiOiAKICAgICAgICB2YXIgZmlsZUlEcyA9IGdldE9iaktleXMoeEVQR1sieG1sdHZNYXAiXSk7CiAgICAgICAgdmFyIHZhbHVlID0gbmV3IEFycmF5KCItIik7CiAgICAgICAgdmFyIHRleHQgID0gbmV3IEFycmF5KCItIik7CgogICAgICAgIGZvciAodmFyIGogPSBmaWxlSURzLmxlbmd0aCAtIDE7IGogPj0gMDsgai0tKSB7CiAgICAgICAgICBpZiAoZmlsZUlEc1tqXSAhPSAieFRlVmUgRHVtbXkiKSB7CiAgICAgICAgICAgIHZhbHVlLnB1c2goZ2V0VmFsdWVGcm9tUHJvdmlkZXJGaWxlKGZpbGVJRHNbal0sICJ4bWx0diIsICJmaWxlLnh0ZXZlIikpCiAgICAgICAgICAgIHRleHQucHVzaChnZXRWYWx1ZUZyb21Qcm92aWRlckZpbGUoZmlsZUlEc1tqXSwgInhtbHR2IiwgIm5hbWUiKSkKICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIHZhbHVlLnB1c2goZmlsZUlEc1tqXSkKICAgICAgICAgICAgdGV4dC5wdXNoKGZpbGVJRHNbal0pCiAgICAgICAgICB9CiAgICAgICAgICAKICAgICAgICB9CiAgICAgICAgCiAgICAgICAgbmV3SXRlbVsiX2VsZW1lbnQiXSAgICAgICA9ICJTRUxFQ1QiOwogICAgICAgIG5ld0l0ZW1bIl9vcHRpb25WYWx1ZXMiXSAgPSB2YWx1ZTsKICAgICAgICBuZXdJdGVtWyJfb3B0aW9uVGV4dCJdICAgID0gdGV4dAogICAgICAgIG5ld0l0ZW1bInZhbHVlIl0gICAgICAgICAgPSB4WG1sdHZGaWxlOwogICAgICAgIG5ld0l0ZW1bIm9uY2hhbmdlIl0gICAgICAgPSAnamF2YXNjcmlwdDogY2hhbmdlWG1sdHZGaWxlKCInICsgeGVwZyArICciLHRoaXMpOyc7CgogICAgICAgIGJyZWFrOwoKICAgICAgY2FzZSAieC1tYXBwaW5nIjogCgogICAgICAgIHZhciB2YWx1ZXMgPSBnZXRPYmpLZXlzKHhFUEdbInhtbHR2TWFwIl1beFhtbHR2RmlsZV0pOwoKICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IHZhbHVlcy5sZW5ndGg7IGorKykgewogICAgICAgICAgCiAgICAgICAgICBpZiAoeEVQR1sieG1sdHZNYXAiXVt4WG1sdHZGaWxlXVt2YWx1ZXNbal1dLmhhc093blByb3BlcnR5KCdkaXNwbGF5LW5hbWUnKSA9PSB0cnVlKSB7CiAgICAgICAgICAgIHZhciBkaXNwbGF5TmFtZSA9IHhFUEdbInhtbHR2TWFwIl1beFhtbHR2RmlsZV1bdmFsdWVzW2pdXVsiZGlzcGxheS1uYW1lIl07CiAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICB2YXIgZGlzcGxheU5hbWUgPSAiLSIKICAgICAgICAgIH0KICAgICAgICAgIAogICAgICAgICAgLy90ZXh0W2pdID0gdmFsdWVzW2pdICsgIiAoIiArIGRpc3BsYXlOYW1lICsgIikiOwogICAgICAgICAgdGV4dFtqXSA9IGRpc3BsYXlOYW1lICsgIiAoIiArIHZhbHVlc1tqXSAgKyAiKSI7CiAgICAgICAgfQoKICAgICAgICB0ZXh0LnVuc2hpZnQoIi0iKTsKICAgICAgICB2YWx1ZXMudW5zaGlmdCgiLSIpOwogICAgICAgIG5ld0l0ZW1bIl9lbGVtZW50Il0gICAgICAgPSAiU0VMRUNUIjsKICAgICAgICBuZXdJdGVtWyJfb3B0aW9uVmFsdWVzIl0gID0gdmFsdWVzOwogICAgICAgIG5ld0l0ZW1bIl9vcHRpb25UZXh0Il0gICAgPSB0ZXh0CiAgICAgICAgbmV3SXRlbVsidmFsdWUiXSAgICAgICAgICA9IHhNYXBwaW5nOwogICAgICAgIG5ld0l0ZW1bIm9uY2hhbmdlIl0gICAgICAgPSAnamF2YXNjcmlwdDogbWFwcGluZ0NoYW5uZWwoIicgKyB4ZXBnICsgJyIsdGhpcyk7JzsKICAgICAgICBicmVhazsKCiAgICAgIGNhc2UgIngtY2F0ZWdvcnkiOgogICAgICAgIC8vdmFyIHZhbHVlcyA9IHBsZXhDYXRlZ29yaWVzVmFsdWVzCiAgICAgICAgbmV3SXRlbVsiX2VsZW1lbnQiXSAgICAgICA9ICJTRUxFQ1QiOwogICAgICAgIG5ld0l0ZW1bIl9vcHRpb25WYWx1ZXMiXSAgPSBwbGV4Q2F0ZWdvcmllc1ZhbHVlczsKICAgICAgICBuZXdJdGVtWyJfb3B0aW9uVGV4dCJdICAgID0gcGxleENhdGVnb3JpZXNPcHRpb247CiAgICAgICAgbmV3SXRlbVsidmFsdWUiXSAgICAgICAgICA9IHhDYXRlZ29yeTsKICAgICAgICBuZXdJdGVtWyJvbmNoYW5nZSJdICAgICAgID0gJ3NhdmVDYXRlZ29yeSgiJyArIHhlcGcgKyAnIiknOwogICAgICAgIGJyZWFrOwoKICAgICAgY2FzZSAidHZnLWxvZ28iOgogICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJjaGFubmVsLWxvZ28iKS5zZXRBdHRyaWJ1dGUoInNyYyIsIHRoaXNDaGFubmVsWyJ0dmctbG9nbyJdKTsKICAgICAgICBuZXdJdGVtWyJfZWxlbWVudCJdICAgICAgID0gIklOUFVUIjsKICAgICAgICBuZXdJdGVtWyJ0eXBlIl0gICAgICAgICAgID0gInRleHQiOwogICAgICAgIG5ld0l0ZW1bInZhbHVlIl0gICAgICAgICAgPSB0aGlzQ2hhbm5lbFsidHZnLWxvZ28iXTsKICAgICAgICBuZXdJdGVtWyJvbmZvY3Vzb3V0Il0gICAgID0gJ3NhdmVDaGFubmVsTG9nbygiJyArIHhlcGcgKyAnIiknOwogICAgICAgIG5ld0l0ZW1bInBsYWNlaG9sZGVyIl0gICAgPSAnSW1hZ2UgVVJMJzsKICAgICAgICBicmVhazsKCiAgICAgIGNhc2UgIngtdXBkYXRlLWNoYW5uZWwtaWNvbiI6CiAgICAgICAgbmV3SXRlbVsiX2VsZW1lbnQiXSAgICAgICA9ICJJTlBVVCI7CiAgICAgICAgbmV3SXRlbVsidHlwZSJdICAgICAgICAgICA9ICJjaGVja2JveCI7CiAgICAgICAgc3dpdGNoKEpTT04ucGFyc2UodGhpc0NoYW5uZWxbIngtdXBkYXRlLWNoYW5uZWwtaWNvbiJdKSkgewogICAgICAgICAgY2FzZSB0cnVlOiBuZXdJdGVtWyJjaGVja2VkIl0gICAgICAgID0gdGhpc0NoYW5uZWxbIngtdXBkYXRlLWNoYW5uZWwtaWNvbiJdOwogICAgICAgICAgICBicmVhawogICAgICAgIH0KICAgICAgICBuZXdJdGVtWyJvbmNoYW5nZSJdICAgICA9ICdzYXZlQ2hhbm5lbEljb25VcGRhdGUoIicgKyB4ZXBnICsgJyIpJzsKICAgICAgICBicmVhazsKCiAgICAgIGNhc2UgIngtbmFtZSI6CiAgICAgICAgbmV3SXRlbVsiX2VsZW1lbnQiXSAgICAgICA9ICJJTlBVVCI7CiAgICAgICAgbmV3SXRlbVsidHlwZSJdICAgICAgICAgICA9ICJ0ZXh0IjsKICAgICAgICBuZXdJdGVtWyJ2YWx1ZSJdICAgICAgICAgID0gdGhpc0NoYW5uZWxbIngtbmFtZSJdOwogICAgICAgIG5ld0l0ZW1bIm9uZm9jdXNvdXQiXSAgICAgPSAnc2F2ZUNoYW5uZWxOYW1lKCInICsgeGVwZyArICciKSc7CiAgICAgICAgbmV3SXRlbVsicGxhY2Vob2xkZXIiXSAgICA9ICdDaGFubmVsIE5hbWUnOwogICAgICAgIGJyZWFrOwoKICAgICAgY2FzZSAieC11cGRhdGUtY2hhbm5lbC1uYW1lIjoKICAgICAgICBpZiAodGhpc0NoYW5uZWwuaGFzT3duUHJvcGVydHkoIl91dWlkLmtleSIpID09IHRydWUpIHsKICAgICAgICAgIG5ld0l0ZW1bIl9lbGVtZW50Il0gICAgICAgPSAiSU5QVVQiOwogICAgICAgICAgbmV3SXRlbVsidHlwZSJdICAgICAgICAgICA9ICJjaGVja2JveCI7CiAgICAgICAgICBzd2l0Y2goSlNPTi5wYXJzZSh0aGlzQ2hhbm5lbFsieC11cGRhdGUtY2hhbm5lbC1uYW1lIl0pKSB7CiAgICAgICAgICAgIGNhc2UgdHJ1ZTogbmV3SXRlbVsiY2hlY2tlZCJdICAgICAgICA9IHRoaXNDaGFubmVsWyJ4LXVwZGF0ZS1jaGFubmVsLW5hbWUiXTsKICAgICAgICAgICAgICBicmVhawogICAgICAgICAgfQogICAgICAgICAgbmV3SXRlbVsib25jaGFuZ2UiXSAgICAgPSAnc2F2ZUNoYW5uZWxOYW1lVXBkYXRlKCInICsgeGVwZyArICciKSc7CiAgICAgICAgICBzaG93RWxlbWVudCgic3RyZWFtSGFzQ1VJRCIsIHRydWUpCgogICAgICAgICAgYnJlYWs7CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIC8vc3RyZWFtSGFzQ1VJRAogICAgICAgICAgc2hvd0VsZW1lbnQoInN0cmVhbUhhc0NVSUQiLCBmYWxzZSkKICAgICAgICAgIGJyZWFrOwogICAgICAgIH0KICAgICAgICAKICAgICAgY2FzZSAieC1hY3RpdmUiOgogICAgICAgIG5ld0l0ZW1bIl9lbGVtZW50Il0gICAgICAgPSAiSU5QVVQiOwogICAgICAgIG5ld0l0ZW1bInR5cGUiXSAgICAgICAgICAgPSAiY2hlY2tib3giOwogICAgICAgIHN3aXRjaChKU09OLnBhcnNlKHRoaXNDaGFubmVsWyJ4LWFjdGl2ZSJdKSkgewogICAgICAgICAgY2FzZSB0cnVlOiBuZXdJdGVtWyJjaGVja2VkIl0gICAgICAgID0gdGhpc0NoYW5uZWxbIngtYWN0aXZlIl07CiAgICAgICAgICAgIGJyZWFrCiAgICAgICAgfQogICAgICAgIG5ld0l0ZW1bIm9uY2hhbmdlIl0gICAgID0gJ3NhdmVDaGFubmVsU3RhdHVzKCInICsgeGVwZyArICciKSc7CiAgICAgICAgYnJlYWs7CgogICAgICBjYXNlICJ4LWdyb3VwLXRpdGxlIjoKICAgICAgICBuZXdJdGVtWyJfZWxlbWVudCJdICAgICAgID0gIklOUFVUIjsKICAgICAgICBuZXdJdGVtWyJ0eXBlIl0gICAgICAgICAgID0gInRleHQiOwogICAgICAgIG5ld0l0ZW1bInZhbHVlIl0gICAgICAgICAgPSB0aGlzQ2hhbm5lbFsieC1ncm91cC10aXRsZSJdOwogICAgICAgIG5ld0l0ZW1bIm9uZm9jdXNvdXQiXSAgICAgPSAnc2F2ZUdyb3VwVGl0bGUoIicgKyB4ZXBnICsgJyIpJzsKICAgICAgICBuZXdJdGVtWyJwbGFjZWhvbGRlciJdICAgID0gJ0dyb3VwIFRpdGxlJzsKICAgICAgICBicmVhazsKCiAgICAgIGRlZmF1bHQ6CiAgICAgICAgbmV3SXRlbVsiX2VsZW1lbnQiXSAgICAgICA9ICJQIjsKICAgICAgICBuZXdJdGVtWyJfdGV4dCJdICAgICAgICAgID0gdGhpc0NoYW5uZWxba2V5c1tpXV07CiAgICAgICAgYnJlYWs7CiAgICAgIAogICAgfQogICAgCiAgICBpZiAodGQgIT0gdW5kZWZpbmVkKSB7CiAgICAgIHRkLmlubmVySFRNTCA9ICIiOwogICAgICB2YXIgZWxlbWVudCA9IGNyZWF0ZU5ld0VsZW1lbnQobmV3SXRlbSkKICAgICAgLy9jb25zb2xlLmxvZyhlbGVtZW50KTsKICAgICAgdGQuYXBwZW5kQ2hpbGQoZWxlbWVudCk7CiAgICB9CgogIH0KCiAgaWYgKGJ1bGsgPT0gdHJ1ZSkgewoKICAgIHZhciBlbG0gPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5Q2xhc3NOYW1lKCJub0J1bGsiKTsKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgZWxtLmxlbmd0aDsgaSsrKSB7CiAgICAgIGVsbVtpXS5sYXN0Q2hpbGQuc2V0QXR0cmlidXRlKCJyZWFkb25seSIsIHRydWUpCiAgICAgIGVsbVtpXS5sYXN0Q2hpbGQuc3R5bGUuYm9yZGVyQ29sb3IgPSAicmVkIjsKICAgIH0KCiAgICB4ZXBnID0gYnVsa0lEc1swXQogIH0KCiAgc29ydFNlbGVjdChkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgieC14bWx0di1maWxlIikubGFzdENoaWxkKTsKICBzb3J0U2VsZWN0KGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJ4LW1hcHBpbmciKS5sYXN0Q2hpbGQpOwogIAp9CgpmdW5jdGlvbiBzb3J0U2VsZWN0KGVsZW0pIHsKCiAgdmFyIHRtcEFyeSA9IFtdOwogIC8vIFJldGFpbiBzZWxlY3RlZCB2YWx1ZSBiZWZvcmUgc29ydGluZwogIHZhciBzZWxlY3RlZFZhbHVlID0gZWxlbVtlbGVtLnNlbGVjdGVkSW5kZXhdLnZhbHVlOwogIC8vIEdyYWIgYWxsIGV4aXN0aW5nIGVudHJpZXMKICBmb3IgKHZhciBpPTA7aTxlbGVtLm9wdGlvbnMubGVuZ3RoO2krKykgdG1wQXJ5LnB1c2goZWxlbS5vcHRpb25zW2ldKTsKICAvLyBTb3J0IGFycmF5IGJ5IHRleHQgYXR0cmlidXRlCiAgdG1wQXJ5LnNvcnQoZnVuY3Rpb24oYSxiKXsgcmV0dXJuIChhLnRleHQgPCBiLnRleHQpPy0xOjE7IH0pOwogIC8vIFdpcGUgb3V0IGV4aXN0aW5nIGVsZW1lbnRzCiAgd2hpbGUgKGVsZW0ub3B0aW9ucy5sZW5ndGggPiAwKSBlbGVtLm9wdGlvbnNbMF0gPSBudWxsOwogIC8vIFJlc3RvcmUgc29ydGVkIGVsZW1lbnRzCiAgdmFyIG5ld1NlbGVjdGVkSW5kZXggPSAwOwogIGZvciAodmFyIGk9MDtpPHRtcEFyeS5sZW5ndGg7aSsrKSB7CiAgICAgIGVsZW0ub3B0aW9uc1tpXSA9IHRtcEFyeVtpXTsKICAgICAgaWYoZWxlbS5vcHRpb25zW2ldLnZhbHVlID09IHNlbGVjdGVkVmFsdWUpIG5ld1NlbGVjdGVkSW5kZXggPSBpOwogIH0KICBlbGVtLnNlbGVjdGVkSW5kZXggPSBuZXdTZWxlY3RlZEluZGV4OyAvLyBTZXQgbmV3IHNlbGVjdGVkIGluZGV4IGFmdGVyIHNvcnRpbmcKICByZXR1cm47Cn0KCgpmdW5jdGlvbiBzd2l0Y2hDaGFubmVsU3RhdHVzKHhlcGcpIHsKICB2YXIgdGhpc0NoYW5uZWwgPSB4RVBHWyJlcGdNYXBwaW5nIl1beGVwZ107CiAgdmFyIHhYbWx0dkZpbGUgPSB0aGlzQ2hhbm5lbFsieC14bWx0di1maWxlIl07CgogIGlmICh4RVBHWyJ4bWx0dk1hcCJdLmhhc093blByb3BlcnR5KHhYbWx0dkZpbGUpID09IHRydWUpIHsKICAgIGlmICh0aGlzQ2hhbm5lbFsieC1tYXBwaW5nIl0gIT0gIi0iICYmIHRoaXNDaGFubmVsWyJ4LW1hcHBpbmciXSAhPSB1bmRlZmluZWQpIHsKICAgICAgdGhpc0NoYW5uZWxbIngtYWN0aXZlIl0gPSAhdGhpc0NoYW5uZWxbIngtYWN0aXZlIl07CiAgICAgIHZhciB0ciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHhlcGcpOwogICAgICBzd2l0Y2godGhpc0NoYW5uZWxbIngtYWN0aXZlIl0pIHsKICAgICAgICBjYXNlIHRydWU6IHRyLmNsYXNzTmFtZSA9ICJhY3RpdmVFUEciOyBicmVhazsKICAgICAgICBjYXNlIGZhbHNlOiB0ci5jbGFzc05hbWUgPSAibm90QWN0aXZlRVBHIjsgYnJlYWs7CiAgICAgIH0KICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImxvZ0luZm8iKS5jbGFzc05hbWUgPSAibm90VmlzaWJsZSI7CgogICAgfSBlbHNlIHsKICAgICAgdmFyIGVyciA9ICJYTUxUViBDaGFubmVsIGlzIG5vdCBzZWxlY3RlZCIKICAgICAgYWxlcnQoZXJyKQogICAgICAvKgogICAgICB2YXIgbmV3RXJyb3IgPSBuZXcgT2JqZWN0KCk7CiAgICAgIG5ld0Vycm9yWyJlcnIiXSA9ICJDaGFubmVsIGlzIG5vdCBzZWxlY3RlZCI7CiAgICAgIGNoZWNrRXJyKG5ld0Vycm9yKTsKICAgICAgKi8KICAgIH0KCiAgfSBlbHNlIHsKICAgIHZhciBlcnIgPSAiWE1MVFYgRmlsZSBpcyBub3Qgc2VsZWN0ZWQiCiAgICBhbGVydChlcnIpCiAgICAvKgogICAgdmFyIG5ld0Vycm9yID0gbmV3IE9iamVjdCgpOwogICAgbmV3RXJyb3JbImVyciJdID0gIlhNTFRWIGZpbGUgaXMgbm90IHNlbGVjdGVkIjsKICAgIGNoZWNrRXJyKG5ld0Vycm9yKTsKICAgICovCiAgfQoKICBzZWFyY2hJbk1hcHBpbmcoKTsKCn0KCmZ1bmN0aW9uIGNyZWF0ZU5ld0VsZW1lbnQobmV3SXRlbSkgewoKICB2YXIgZWxlbWVudCA9IGNyZWF0ZUVsZW1lbnQobmV3SXRlbSk7CiAgCiAgc3dpdGNoKG5ld0l0ZW1bIl9lbGVtZW50Il0pIHsKICAgIGNhc2UgIlNFTEVDVCI6CiAgICAgIC8vZWxlbWVudFtdCiAgICAgIHZhciB2YWx1ZXMgID0gbmV3SXRlbVsiX29wdGlvblZhbHVlcyJdOwogICAgICB2YXIgdGV4dCAgICA9IG5ld0l0ZW1bIl9vcHRpb25UZXh0Il07CgogICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHZhbHVlcy5sZW5ndGg7IGkrKykgewogICAgICAgIC8vY29uc29sZS5sb2coaXRlbSk7CiAgICAgICAgdmFyIG5ld0VudHJ5ID0gbmV3IE9iamVjdDsKICAgICAgICBuZXdFbnRyeVsiX2VsZW1lbnQiXSAgPSAiT1BUSU9OIjsKICAgICAgICBuZXdFbnRyeVsiX3RleHQiXSAgICAgPSB0ZXh0W2ldOwogICAgICAgIG5ld0VudHJ5WyJ2YWx1ZSJdICAgICA9IHZhbHVlc1tpXTsKICAgICAgICBlbGVtZW50LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3RW50cnkpKTsKICAgICAgfQogICAgICBlbGVtZW50LnZhbHVlID0gbmV3SXRlbVsidmFsdWUiXTsKICAgICAgYnJlYWs7CiAgICAKICAgIGRlZmF1bHQ6IAogICAgICAKICAgICAgLy9lbGVtZW50LmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3SXRlbSkpOwogICAgICBicmVhazsKICB9CiAgCiAgcmV0dXJuIGVsZW1lbnQ7Cn0KCmZ1bmN0aW9uIHNhdmVCdWxrKGtleSwgdmFsdWUpIHsKICBmb3IgKHZhciBpID0gMDsgaSA8IGJ1bGtJRHMubGVuZ3RoOyBpKyspIHsKICAgIHZhciBpZCA9IGJ1bGtJRHNbaV0KICAgIHZhciB0aGlzQ2hhbm5lbCA9IHhFUEdbImVwZ01hcHBpbmciXVtpZF07CiAgICB0aGlzQ2hhbm5lbFtrZXldID0gdmFsdWU7CgogICAgc3dpdGNoKGtleSkgewogICAgICBjYXNlICJ0dmctbG9nbyI6ICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpLmNoaWxkTm9kZXNbMl0ubGFzdENoaWxkLnNldEF0dHJpYnV0ZSgic3JjIiwgdmFsdWUpOyBicmVhazsKICAgICAgCiAgICAgIGNhc2UgIngtY2F0ZWdvcnkiOiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuY2hpbGROb2Rlc1szXS5sYXN0Q2hpbGQuY2xhc3NOYW1lID0gdmFsdWU7IGJyZWFrOwoKICAgICAgY2FzZSAieC14bWx0di1maWxlIjoKICAgICAgICB2YXIgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlkKS5jaGlsZE5vZGVzWzZdLmxhc3RDaGlsZDsKICAgICAgICBzd2l0Y2godmFsdWUpIHsKICAgICAgICAgIGNhc2UgIi0iOiAgICAgICAgICAgZWxlbWVudC5pbm5lckhUTUwgPSB2YWx1ZTsgYnJlYWs7CiAgICAgICAgICBjYXNlICJ4VGVWZSBEdW1teSI6IGVsZW1lbnQuaW5uZXJIVE1MID0gdmFsdWU7IGJyZWFrOwogICAgICAgICAgZGVmYXVsdDogICAgICAgICAgICBlbGVtZW50LmlubmVySFRNTCA9IGdldFZhbHVlRnJvbVByb3ZpZGVyRmlsZSh2YWx1ZSwgInhtbHR2IiwgIm5hbWUiKTsgYnJlYWs7CiAgICAgICAgfQoKCgogICAgICAvL2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlkKS5jaGlsZE5vZGVzWzVdLmxhc3RDaGlsZC5pbm5lckhUTUwgPSB2YWx1ZS5yZXBsYWNlKC9eLipbXFxcL10vLCAnJyk7IGJyZWFrOwogICAgICBjYXNlICJ4LW1hcHBpbmciOiAgIAogICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlkKS5jaGlsZE5vZGVzWzddLmxhc3RDaGlsZC5pbm5lckhUTUwgPSB2YWx1ZTsKICAgICAgICBpZiAodmFsdWUgPT0gIi0iKSB7CiAgICAgICAgICB0aGlzQ2hhbm5lbFsieC1hY3RpdmUiXSA9IGZhbHNlOwogICAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpLmNsYXNzTmFtZSA9ICJub3RBY3RpdmVFUEciOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICB0aGlzQ2hhbm5lbFsieC1hY3RpdmUiXSA9IHRydWU7CiAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChpZCkuY2xhc3NOYW1lID0gImFjdGl2ZUVQRyI7CiAgICAgICAgfQogICAgICAgIGJyZWFrOwoKICAgICAgY2FzZSAieC1ncm91cC10aXRsZSI6IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlkKS5jaGlsZE5vZGVzWzVdLmxhc3RDaGlsZC5pbm5lckhUTUwgPSB2YWx1ZTsgYnJlYWs7CgogICAgICBjYXNlICJ4LWFjdGl2ZSI6CiAgICAgICAgdmFyIHRyID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoaWQpOwogICAgICAgIAogICAgICAgIGlmICh0aGlzQ2hhbm5lbC5oYXNPd25Qcm9wZXJ0eSgieC14bWx0di1maWxlIikgPT0gdHJ1ZSkgewogICAgICAgICAgaWYgKHRoaXNDaGFubmVsWyJ4LW1hcHBpbmciXSAhPSAiLSIgJiYgdGhpc0NoYW5uZWxbIngtbWFwcGluZyJdICE9IHVuZGVmaW5lZCAmJiB0aGlzQ2hhbm5lbFsieC14bWx0di1maWxlIl0gIT0gIi0iICYmIHRoaXNDaGFubmVsWyJ4LXhtbHR2LWZpbGUiXSAhPSB1bmRlZmluZWQpIHsKICAgICAgICAgICAgc3dpdGNoKHRoaXNDaGFubmVsWyJ4LWFjdGl2ZSJdKSB7CiAgICAgICAgICAgICAgY2FzZSB0cnVlOiB0ci5jbGFzc05hbWUgPSAiYWN0aXZlRVBHIjsgYnJlYWs7CiAgICAgICAgICAgICAgY2FzZSBmYWxzZTogdHIuY2xhc3NOYW1lID0gIm5vdEFjdGl2ZUVQRyI7IGJyZWFrOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgfQogICAgICAgIH0KCiAgICB9CgogICAgdXBkYXRlQ2hhbm5lbExvZ28oaWQpCgogIH0KCn0KCmZ1bmN0aW9uIHVwZGF0ZUNoYW5uZWxMb2dvKHhlcGcpIHsKICB2YXIgdGhpc0NoYW5uZWwgPSB4RVBHWyJlcGdNYXBwaW5nIl1beGVwZ107CiAgaWYgKHRoaXNDaGFubmVsWyJ4LXVwZGF0ZS1jaGFubmVsLWljb24iXSA9PSB0cnVlKSB7CiAgICB2YXIgeFhtbHR2RmlsZSAgPSB0aGlzQ2hhbm5lbFsieC14bWx0di1maWxlIl07CiAgICB2YXIgeE1hcHBpbmcgICAgPSB0aGlzQ2hhbm5lbFsieC1tYXBwaW5nIl07CgogICAgaWYgKHhYbWx0dkZpbGUgIT0gIi0iICYmIHhYbWx0dkZpbGUubGVuZ3RoID4gMCAmJiB4TWFwcGluZyAhPSAiLSIgJiYgeE1hcHBpbmcubGVuZ3RoID4gMCkgewogICAgICBpZiAoeEVQR1sieG1sdHZNYXAiXVt4WG1sdHZGaWxlXVt4TWFwcGluZ10uaGFzT3duUHJvcGVydHkoImljb24iKSkgewogICAgICAgIHZhciBsb2dvVVJMID0geEVQR1sieG1sdHZNYXAiXVt4WG1sdHZGaWxlXVt4TWFwcGluZ11bImljb24iXTsKICAgICAgICB0aGlzQ2hhbm5lbFsidHZnLWxvZ28iXSA9IGxvZ29VUkw7CiAgICAgICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoeGVwZykuY2hpbGROb2Rlc1syXS5sYXN0Q2hpbGQuc2V0QXR0cmlidXRlKCJzcmMiLCBsb2dvVVJMKTsKICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiY2hhbm5lbC1sb2dvIikuc2V0QXR0cmlidXRlKCJzcmMiLCBsb2dvVVJMKTsKICAgICAgfSBlbHNlIHsKICAgICAgICBhbGVydCgiTm8gbG9nbyBVUkwgaW4gdGhlIFhNTFRWIGZpbGUgYXZhaWxhYmxlIikKICAgICAgfQogICAgICAKICAgIH0KICAgIAogICAgLyoKICAgIGlmICh4RVBHWyJ4bWx0dk1hcCJdW3hYbWx0dkZpbGVdW3hNYXBwaW5nXVsiaWNvbiJdICE9IHVuZGVmaW5lZCkgewoKICAgICAgCiAgICB9CiAgICAqLwogICAgCiAgfQp9CgpmdW5jdGlvbiBzYXZlQ2hhbm5lbExvZ28oeGVwZykgewogIGlmIChidWxrID09IGZhbHNlKSB7CiAgICB2YXIgdGhpc0NoYW5uZWwgPSB4RVBHWyJlcGdNYXBwaW5nIl1beGVwZ107CiAgICB0aGlzQ2hhbm5lbFsidHZnLWxvZ28iXSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJ0dmctbG9nbyIpLmxhc3RDaGlsZC52YWx1ZTsKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHhlcGcpLmNoaWxkTm9kZXNbMl0ubGFzdENoaWxkLnNldEF0dHJpYnV0ZSgic3JjIiwgdGhpc0NoYW5uZWxbInR2Zy1sb2dvIl0pOwogICAgbWFwcGluZ0RldGFpbCh4ZXBnKTsKICAgIHJldHVybgogIH0KCiAgaWYgKGJ1bGsgPT0gdHJ1ZSkgewogICAgdmFyIGtleSAgID0gInR2Zy1sb2dvIjsKICAgIHZhciB2YWx1ZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJ0dmctbG9nbyIpLmxhc3RDaGlsZC52YWx1ZTsKICAgIHNhdmVCdWxrKGtleSwgdmFsdWUpOwoKICAgIG1hcHBpbmdEZXRhaWwoeGVwZyk7CiAgICByZXR1cm4KICB9Cn0KCmZ1bmN0aW9uIHNhdmVDaGFubmVsSWNvblVwZGF0ZSh4ZXBnKSB7CgogIHZhciBrZXkgICA9ICJ4LXVwZGF0ZS1jaGFubmVsLWljb24iOwogIHZhciB2YWx1ZSA9IEpTT04ucGFyc2UoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIngtdXBkYXRlLWNoYW5uZWwtaWNvbiIpLmxhc3RDaGlsZC5jaGVja2VkKTsKICBpZiAoYnVsayA9PSBmYWxzZSkgewogICAgdmFyIHRoaXNDaGFubmVsID0geEVQR1siZXBnTWFwcGluZyJdW3hlcGddOwogICAgdGhpc0NoYW5uZWxba2V5XSA9IHZhbHVlCiAgICB1cGRhdGVDaGFubmVsTG9nbyh4ZXBnKQogICAgCiAgICBtYXBwaW5nRGV0YWlsKHhlcGcpOwogICAgc2VhcmNoSW5NYXBwaW5nKCk7CiAgICByZXR1cm4KICB9CgogIGlmIChidWxrID09IHRydWUpIHsKICAgIHNhdmVCdWxrKGtleSwgdmFsdWUpOwogICAgbWFwcGluZ0RldGFpbCh4ZXBnKTsKICAgIHJldHVybgogIH0KICAKfQoKZnVuY3Rpb24gc2F2ZUNoYW5uZWxOYW1lKHhlcGcpIHsKICBpZiAoYnVsayA9PSBmYWxzZSkgewogICAgdmFyIHRoaXNDaGFubmVsID0geEVQR1siZXBnTWFwcGluZyJdW3hlcGddOwogICAgdGhpc0NoYW5uZWxbIngtbmFtZSJdID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoIngtbmFtZSIpLmxhc3RDaGlsZC52YWx1ZTsKICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHhlcGcpLmNoaWxkTm9kZXNbM10ubGFzdENoaWxkLmlubmVySFRNTCA9IHRoaXNDaGFubmVsWyJ4LW5hbWUiXTsKICAgIG1hcHBpbmdEZXRhaWwoeGVwZyk7CiAgICBzZWFyY2hJbk1hcHBpbmcoKTsKICB9CiAgCn0KCmZ1bmN0aW9uIHNhdmVDaGFubmVsTmFtZVVwZGF0ZSh4ZXBnKSB7CiAgdmFyIGtleSAgID0gIngtdXBkYXRlLWNoYW5uZWwtbmFtZSI7CiAgdmFyIHZhbHVlID0gSlNPTi5wYXJzZShkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgieC11cGRhdGUtY2hhbm5lbC1uYW1lIikubGFzdENoaWxkLmNoZWNrZWQpOwoKICBpZiAoYnVsayA9PSBmYWxzZSkgewogICAgdmFyIHRoaXNDaGFubmVsID0geEVQR1siZXBnTWFwcGluZyJdW3hlcGddOwogICAgdGhpc0NoYW5uZWxba2V5XSA9IHZhbHVlCiAgICBtYXBwaW5nRGV0YWlsKHhlcGcpOwogICAgc2VhcmNoSW5NYXBwaW5nKCk7CiAgICByZXR1cm4KICB9CgogIGlmIChidWxrID09IHRydWUpIHsKICAgIHNhdmVCdWxrKGtleSwgdmFsdWUpOwogICAgbWFwcGluZ0RldGFpbCh4ZXBnKTsKICAgIHJldHVybgogIH0KCn0KCmZ1bmN0aW9uIHNhdmVDaGFubmVsU3RhdHVzKHhlcGcpIHsKICB2YXIgdGhpc0NoYW5uZWwgPSB4RVBHWyJlcGdNYXBwaW5nIl1beGVwZ107CiAgdmFyIHhYbWx0dkZpbGUgPSB0aGlzQ2hhbm5lbFsieC14bWx0di1maWxlIl07CgogIHZhciBrZXkgICA9ICJ4LWFjdGl2ZSI7CiAgdmFyIHZhbHVlID0gSlNPTi5wYXJzZShkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgieC1hY3RpdmUiKS5sYXN0Q2hpbGQuY2hlY2tlZCk7CgogIGlmICh4RVBHWyJ4bWx0dk1hcCJdLmhhc093blByb3BlcnR5KHhYbWx0dkZpbGUpID09IHRydWUpIHsKICAgIGlmICh0aGlzQ2hhbm5lbFsieC1tYXBwaW5nIl0gIT0gIi0iICYmIHRoaXNDaGFubmVsWyJ4LW1hcHBpbmciXSAhPSB1bmRlZmluZWQpIHsKICAgICAgdGhpc0NoYW5uZWxbIngtYWN0aXZlIl0gPSAhdGhpc0NoYW5uZWxbIngtYWN0aXZlIl07CiAgICAgIHZhciB0ciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHhlcGcpOwogICAgICBzd2l0Y2godGhpc0NoYW5uZWxbIngtYWN0aXZlIl0pIHsKICAgICAgICBjYXNlIHRydWU6IHRyLmNsYXNzTmFtZSA9ICJhY3RpdmVFUEciOyBicmVhazsKICAgICAgICBjYXNlIGZhbHNlOiB0ci5jbGFzc05hbWUgPSAibm90QWN0aXZlRVBHIjsgYnJlYWs7CiAgICAgIH0KICAgICAgCiAgICB9IGVsc2UgewogICAgICB2YXIgZXJyID0gIlhNTFRWIENoYW5uZWwgaXMgbm90IHNlbGVjdGVkIgogICAgICBhbGVydChlcnIpCiAgICAgIHZhbHVlID0gZmFsc2UKICAgIH0KCiAgfSBlbHNlIHsKICAgIGlmICh2YWx1ZSA9PSB0cnVlKSB7CiAgICAgIHZhciBlcnIgPSAiWE1MVFYgRmlsZSBpcyBub3Qgc2VsZWN0ZSIKICAgICAgYWxlcnQoZXJyKQogICAgICB2YWx1ZSA9IGZhbHNlCiAgICB9CiAgfQoKICAKCiAgaWYgKGJ1bGsgPT0gZmFsc2UpIHsKICAgIHZhciB0aGlzQ2hhbm5lbCA9IHhFUEdbImVwZ01hcHBpbmciXVt4ZXBnXTsKICAgIHRoaXNDaGFubmVsW2tleV0gPSB2YWx1ZQogICAgbWFwcGluZ0RldGFpbCh4ZXBnKTsKICAgIHNlYXJjaEluTWFwcGluZygpOwoKICAgIHZhciB0ciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKHhlcGcpOwogICAgc3dpdGNoKHRoaXNDaGFubmVsWyJ4LWFjdGl2ZSJdKSB7CiAgICAgIGNhc2UgdHJ1ZTogdHIuY2xhc3NOYW1lID0gImFjdGl2ZUVQRyI7IGJyZWFrOwogICAgICBjYXNlIGZhbHNlOiB0ci5jbGFzc05hbWUgPSAibm90QWN0aXZlRVBHIjsgYnJlYWs7CiAgICB9CgogICAgcmV0dXJuCiAgfQoKICBpZiAoYnVsayA9PSB0cnVlKSB7CiAgICBzYXZlQnVsayhrZXksIHZhbHVlKTsKICAgIG1hcHBpbmdEZXRhaWwoeGVwZyk7CiAgICByZXR1cm4KICB9Cgp9CgpmdW5jdGlvbiBzYXZlR3JvdXBUaXRsZSh4ZXBnKSB7CiAgdmFyIGtleSAgID0gIngtZ3JvdXAtdGl0bGUiOwogIHZhciB2YWx1ZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJ4LWdyb3VwLXRpdGxlIikubGFzdENoaWxkLnZhbHVlOwoKICBpZiAoYnVsayA9PSBmYWxzZSkgewogICAgdmFyIHRoaXNDaGFubmVsID0geEVQR1siZXBnTWFwcGluZyJdW3hlcGddOwogICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoeGVwZykuY2hpbGROb2Rlc1s1XS5sYXN0Q2hpbGQuaW5uZXJIVE1MID0gdmFsdWU7CiAgICB0aGlzQ2hhbm5lbFtrZXldID0gdmFsdWU7CiAgICBtYXBwaW5nRGV0YWlsKHhlcGcpOwogICAgc2VhcmNoSW5NYXBwaW5nKCk7CiAgfQoKICBpZiAoYnVsayA9PSB0cnVlKSB7CiAgICBzYXZlQnVsayhrZXksIHZhbHVlKTsKICAgIG1hcHBpbmdEZXRhaWwoeGVwZyk7CiAgICByZXR1cm4KICB9Cgp9CgpmdW5jdGlvbiBzYXZlQ2F0ZWdvcnkoeGVwZykgewogIHZhciBrZXkgICA9ICJ4LWNhdGVnb3J5IjsKICB2YXIgdmFsdWUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgieC1jYXRlZ29yeSIpLmxhc3RDaGlsZC52YWx1ZTsKCiAgaWYgKGJ1bGsgPT0gZmFsc2UpIHsKICAgIHZhciB0aGlzQ2hhbm5lbCA9IHhFUEdbImVwZ01hcHBpbmciXVt4ZXBnXTsKICAgIHRoaXNDaGFubmVsW2tleV0gPSB2YWx1ZQogICAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoeGVwZykuY2hpbGROb2Rlc1szXS5sYXN0Q2hpbGQuY2xhc3NOYW1lID0gdmFsdWUKICAgIG1hcHBpbmdEZXRhaWwoeGVwZyk7CiAgICBzZWFyY2hJbk1hcHBpbmcoKTsKICB9CgogIGlmIChidWxrID09IHRydWUpIHsKICAgIHNhdmVCdWxrKGtleSwgdmFsdWUpOwogICAgbWFwcGluZ0RldGFpbCh4ZXBnKTsKICAgIHJldHVybgogIH0KCn0KCmZ1bmN0aW9uIGFycmFuZ2VUYWJsZShlbG0pIHsKICB2YXIgdHIgPSBlbG0ucGFyZW50RWxlbWVudC5wYXJlbnRFbGVtZW50OwogIHZhciBuZXdQb3NpdGlvbiA9IGVsbS52YWx1ZTsKICB2YXIgeF9jaGFubmVsSUQgPSB0ci5pZDsKCiAgc3dpdGNoKGlzTmFOKG5ld1Bvc2l0aW9uKSkgewogICAgY2FzZSB0cnVlOiAKICAgICAgYWxlcnQoIkNoLiBOby4gbXVzdCBiZSBhIG51bWJlciIpOwogICAgICBtYXBwaW5nRXJyb3IgPSB0cnVlOwogICAgICBicmVhazsKICB9CgoKICAvL3ZhciBpdGVtID0geEVQR1siZXBnTWFwcGluZyJdW2lkXTsKICB2YXIga2V5cyA9IGdldE9iaktleXMoeEVQR1siZXBnTWFwcGluZyJdKQogIGZvciAodmFyIGkgPSAwOyBpIDwga2V5cy5sZW5ndGg7IGkrKykgewogICAgdmFyIGl0ZW0gPSB4RVBHWyJlcGdNYXBwaW5nIl1ba2V5c1tpXV07CiAgICBpZiAoaXRlbVsieC1lcGciXSA9PSB4X2NoYW5uZWxJRCkgewoKICAgICAgLy8gQ2hlY2sgaWYgcG9zaXRpb24gZXhpc3QKICAgICAgdmFyIG9sZFBvc2l0aW9uID0gaXRlbVsieC1jaGFubmVsSUQiXTsKCiAgICAgIGlmIChvbGRQb3NpdGlvbiAhPSBuZXdQb3NpdGlvbikgewoKICAgICAgICBjb25zb2xlLmxvZyhuZXdQb3NpdGlvbiwgbmV3UG9zaXRpb24ubGVuZ3RoKTsKICAgICAgICBpZiAobmV3UG9zaXRpb24ubGVuZ3RoID09IDApIHsKICAgICAgICAgIG1hcHBpbmdFcnJvciA9IHRydWUKICAgICAgICAgIG5ld1Bvc2l0aW9uID0gb2xkUG9zaXRpb247CiAgICAgICAgICAKICAgICAgICB9CgogICAgICAgIGlmIChtYXBwaW5nRXJyb3IgPT0gdHJ1ZSkgewogICAgICAgICAgZWxtLnZhbHVlID0gb2xkUG9zaXRpb247CiAgICAgICAgICByZXR1cm47CiAgICAgICAgfQoKICAgICAgICBmb3IgKHZhciBqID0ga2V5cy5sZW5ndGggLSAxOyBqID49IDA7IGotLSkgewogICAgICAgICAgdmFyIGNoYW5uZWwgPSB4RVBHWyJlcGdNYXBwaW5nIl1ba2V5c1tqXV07CiAgICAgICAgICBpZiAoa2V5c1tqXSAhPSB4X2NoYW5uZWxJRCkgewogICAgICAgICAgICBpZiAobmV3UG9zaXRpb24gPT0gY2hhbm5lbFsieC1jaGFubmVsSUQiXSkgeyAvLyBJZiBwb3NpdGlvbiBleGlzdCwgc2V0IG5leHQgZnJlZSBwb3NpdGlvbi4KICAgICAgICAgICAgICBuZXdQb3NpdGlvbisrOwogICAgICAgICAgICAgIGVsbS52YWx1ZSA9IG5ld1Bvc2l0aW9uOwogICAgICAgICAgICAgIGFycmFuZ2VUYWJsZShlbG0pOwogICAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgICAvKgogICAgICAgICAgICAgIHZhciBuZXdFcnJvciA9IG5ldyBPYmplY3QoKTsKICAgICAgICAgICAgICBuZXdFcnJvclsiZXJyIl0gPSAiRHVwbGljYXRlIElEIjsKICAgICAgICAgICAgICBjaGVja0VycihuZXdFcnJvcik7CiAgICAgICAgICAgICAgc29ydFRhYmxlKCk7CiAgICAgICAgICAgICAgbWFwcGluZ0Vycm9yID0gdHJ1ZTsKICAgICAgICAgICAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCh4X2NoYW5uZWxJRCkuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIklOUFVUIilbMF0uZm9jdXMoKTsKICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgICAgICAgKi8KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KCiAgICAgIH0KCiAgICAgIC8vY29uc29sZS5sb2cob2xkUG9zaXRpb24sIG5ld1Bvc2l0aW9uKTsKICAgICAgaWYgKGtleXNbaV0gPT0geF9jaGFubmVsSUQgJiYgb2xkUG9zaXRpb24gIT0gbmV3UG9zaXRpb24pIHsgIAogICAgICAgIGl0ZW1bIngtY2hhbm5lbElEIl0gPSBuZXdQb3NpdGlvbjsKICAgICAgfSAKCiAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJsb2dJbmZvIikuY2xhc3NOYW1lID0gIm5vdFZpc2libGUiOwogICAgICBpZiAoY29sdW1uVG9Tb3J0ID09IDEpIHsKICAgICAgICBzb3J0VGFibGUoY29sdW1uVG9Tb3J0KTsKICAgICAgfQogICAgICBtYXBwaW5nRXJyb3IgPSBmYWxzZTsKCiAgICB9CiAgfQp9CgpmdW5jdGlvbiBjaGFuZ2VYbWx0dkZpbGUoeGVwZywgZWxtKSB7CgogIHZhciB0aGlzQ2hhbm5lbCA9IHhFUEdbImVwZ01hcHBpbmciXVt4ZXBnXTsKICAKICB2YXIgeFhtbHR2RmlsZSAgICA9IGVsbS52YWx1ZTsKICB2YXIgY2hhbm5lbElEICAgICA9IHRoaXNDaGFubmVsWyJ0dmctaWQiXTsKICB0aGlzQ2hhbm5lbFsieC14bWx0di1maWxlIl0gPSB4WG1sdHZGaWxlOwoKICBpZiAoYnVsayA9PSBmYWxzZSkgewoKICAgIHNldFRpbWVvdXQoZnVuY3Rpb24oKXsgCgogICAgICB2YXIgeE1hcHBpbmcgPSAiLSIKCiAgICAgIC8vIEF1dG9tYXAKICAgICAgaWYgKHhYbWx0dkZpbGUgIT0gIi0iKSB7CiAgICAgICAgaWYgKHhFUEdbInhtbHR2TWFwIl1beFhtbHR2RmlsZV0uaGFzT3duUHJvcGVydHkoY2hhbm5lbElEKSA9PSB0cnVlKSB7CiAgICAgICAgICB0aGlzQ2hhbm5lbFsieC1tYXBwaW5nIl0gPSBjaGFubmVsSUQ7CiAgICAgICAgICB4TWFwcGluZyA9IGNoYW5uZWxJRAogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICB0aGlzQ2hhbm5lbFsieC1tYXBwaW5nIl0gPSB4TWFwcGluZwogICAgICAgIH0KICAgICAgfSBlbHNlIHsKICAgICAgICB0aGlzQ2hhbm5lbFsieC1tYXBwaW5nIl0gPSB4TWFwcGluZwoKICAgICAgfQogICAgICAKICAgICAgdmFyIHRyID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoeGVwZyk7CgogICAgICBpZiAoeE1hcHBpbmcgPT0gIi0iKSB7CiAgICAgICAgdGhpc0NoYW5uZWxbIngtYWN0aXZlIl0gPSBmYWxzZTsKICAgICAgICB0ci5jbGFzc05hbWUgPSAibm90QWN0aXZlRVBHIgogICAgICB9IGVsc2UgewogICAgICAgIHRoaXNDaGFubmVsWyJ4LWFjdGl2ZSJdICA9IHRydWU7CiAgICAgICAgdHIuY2xhc3NOYW1lID0gImFjdGl2ZUVQRyIKICAgICAgfQoKICAgICAgLy8gU2hvdyBkYXRhIGluIHRhYmxlCiAgICAgIHZhciB0ZCA9IHRyLmdldEVsZW1lbnRzQnlUYWdOYW1lKCJURCIpOwogICAgICB2YXIgZGF0YUZpbGUgPSB0ZFt0ZC5sZW5ndGggLSAyXS5sYXN0Q2hpbGQ7CiAgICAgIHN3aXRjaCh4WG1sdHZGaWxlKSB7CiAgICAgICAgY2FzZSAiLSI6ICAgICAgICAgICBkYXRhRmlsZS5pbm5lckhUTUwgPSB4WG1sdHZGaWxlOyBicmVhazsKICAgICAgICBjYXNlICJ4VGVWZSBEdW1teSI6IGRhdGFGaWxlLmlubmVySFRNTCA9IHhYbWx0dkZpbGU7IGJyZWFrOwogICAgICAgIGRlZmF1bHQ6ICAgICAgICAgICAgZGF0YUZpbGUuaW5uZXJIVE1MID0gZ2V0VmFsdWVGcm9tUHJvdmlkZXJGaWxlKHhYbWx0dkZpbGUsICJ4bWx0diIsICJuYW1lIik7IGJyZWFrOwogICAgICB9CgogICAgICAvL3hYbWx0dkZpbGUucmVwbGFjZSgvXi4qW1xcXC9dLywgJycpOwoKICAgICAgdmFyIGRhdGFYbWx0dklEID0gdGRbdGQubGVuZ3RoIC0gMV0ubGFzdENoaWxkOwogICAgICBkYXRhWG1sdHZJRC5pbm5lckhUTUwgPSB4TWFwcGluZzsKICAgICAgCiAgICAgIG1hcHBpbmdEZXRhaWwoeGVwZyk7CgogICAgfSwgMTApOwogIH0KCiAgaWYgKGJ1bGsgPT0gdHJ1ZSkgewogICAgdmFyIGtleSA9ICJ4LXhtbHR2LWZpbGUiCiAgICB2YXIgdmFsdWUgPSB4WG1sdHZGaWxlCiAgICBzYXZlQnVsayhrZXksIHZhbHVlKTsKCiAgICB2YXIga2V5ID0gIngtbWFwcGluZyIKICAgIHZhciB2YWx1ZSA9ICItIgogICAgc2F2ZUJ1bGsoa2V5LCB2YWx1ZSk7CiAgICBtYXBwaW5nRGV0YWlsKHhlcGcpOwogICAgcmV0dXJuCiAgfQoKICByZXR1cm4KfQoKZnVuY3Rpb24gbWFwcGluZ0NoYW5uZWwoeGVwZywgZWxtKSB7CiAgdmFyIHRoaXNDaGFubmVsID0geEVQR1siZXBnTWFwcGluZyJdW3hlcGddOwogIC8vdmFyIHhNYXBwaW5nICAgICAgPSBlbG0udmFsdWU7CiAgdmFyIHhNYXBwaW5nICAgICAgPSBlbG0ub3B0aW9uc1tlbG0uc2VsZWN0ZWRJbmRleF0udmFsdWUKICAKICBpZiAoYnVsayA9PSBmYWxzZSkgewogICAgCiAgICB0aGlzQ2hhbm5lbFsieC1tYXBwaW5nIl0gPSB4TWFwcGluZzsKCiAgICB2YXIgdHIgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCh4ZXBnKTsKCiAgICBpZiAoeE1hcHBpbmcgPT0gIi0iKSB7CiAgICAgIHRoaXNDaGFubmVsWyJ4LWFjdGl2ZSJdID0gZmFsc2U7CiAgICAgIHRyLmNsYXNzTmFtZSA9ICJub3RBY3RpdmVFUEciCiAgICB9IGVsc2UgewogICAgICB0aGlzQ2hhbm5lbFsieC1hY3RpdmUiXSAgPSB0cnVlOwogICAgICB0ci5jbGFzc05hbWUgPSAiYWN0aXZlRVBHIgogICAgfQoKICAgIC8vIFNob3cgZGF0YSBpbiB0YWJsZQogICAgdmFyIHRkID0gdHIuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIlREIik7CiAgICB2YXIgZGF0YVhtbHR2SUQgPSB0ZFt0ZC5sZW5ndGggLSAxXS5sYXN0Q2hpbGQ7CiAgICBkYXRhWG1sdHZJRC5pbm5lckhUTUwgPSB4TWFwcGluZzsKICAgIC8vY29uc29sZS5sb2codGRbdGQubGVuZ3RoIC0gMV0pOwogICAgLy9jb25zb2xlLmxvZyh4TWFwcGluZywgZWxtKTsKCiAgICBjcmVhdGVTZWFyY2hPYmooKTsKICAgIHNlYXJjaEluTWFwcGluZygpOwogICAgdXBkYXRlQ2hhbm5lbExvZ28oeGVwZykKICAgIG1hcHBpbmdEZXRhaWwoeGVwZyk7CiAgICByZXR1cm4KICB9CgogIGlmIChidWxrID09IHRydWUpIHsKCiAgICB2YXIga2V5ID0gIngtbWFwcGluZyIKICAgIHZhciB2YWx1ZSA9IHhNYXBwaW5nCiAgICBzYXZlQnVsayhrZXksIHZhbHVlKTsKCiAgICBtYXBwaW5nRGV0YWlsKHhlcGcpOwogICAgcmV0dXJuCiAgfQoKICByZXR1cm4KfQoKCmZ1bmN0aW9uIGNyZWF0ZU5ld1REKG5ld0l0ZW0sIGVsbSkgewogIHZhciBuZXdURCA9IG5ldyBPYmplY3QoKTsKICBuZXdURFsiX2VsZW1lbnQiXSA9ICJURCI7CiAgCiAgZWxtLmFwcGVuZENoaWxkKGNyZWF0ZUVsZW1lbnQobmV3VEQpKTsKICB2YXIgdGQgPSBlbG0ubGFzdENoaWxkOyAKICAKICBzd2l0Y2gobmV3SXRlbVsiX2VsZW1lbnQiXSkgewogICAgY2FzZSAiU0VMRUNUIjoKICAgICAgdGQuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdJdGVtKSk7CiAgICAgIHZhciB0ZCA9IGVsbS5sYXN0Q2hpbGQubGFzdENoaWxkOyAKICAgICAgdmFyIHZhbHVlcyA9IG5ld0l0ZW1bIl9vcHRpb25WYWx1ZXMiXTsKICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB2YWx1ZXMubGVuZ3RoOyBpKyspIHsKICAgICAgICAvL2NvbnNvbGUubG9nKGl0ZW0pOwogICAgICAgIHZhciBuZXdFbnRyeSA9IG5ldyBPYmplY3Q7CiAgICAgICAgbmV3RW50cnlbIl9lbGVtZW50Il0gID0gIk9QVElPTiI7CiAgICAgICAgbmV3RW50cnlbIl90ZXh0Il0gICAgID0gdmFsdWVzW2ldOwogICAgICAgIG5ld0VudHJ5WyJ2YWx1ZSJdICAgICA9IHZhbHVlc1tpXTsKICAgICAgICB0ZC5hcHBlbmRDaGlsZChjcmVhdGVFbGVtZW50KG5ld0VudHJ5KSk7CiAgICAgIH0KICAgICAgdGQudmFsdWUgPSBuZXdJdGVtWyJ2YWx1ZSJdOwoKICAgICAgYnJlYWs7CiAgICAKICAgIGRlZmF1bHQ6IAogICAgICAKICAgICAgdGQuYXBwZW5kQ2hpbGQoY3JlYXRlRWxlbWVudChuZXdJdGVtKSk7CiAgICAgIGJyZWFrOwogIH0KICAKfQoKZnVuY3Rpb24gc2F2ZVhFUEcoKSB7CiAgaWYgKG1hcHBpbmdFcnJvciA9PSB0cnVlKSB7CiAgICBhbGVydCgiRGF0YSBjb3VsZCBub3QgYmUgc2F2ZWQsIGVycm9ycyBpbiB0aGUgWEVQRyBkYXRhLiIpOwogICAgcmV0dXJuOwogIH0KICBzaG93TG9hZGluZ1NjcmVlbih0cnVlKTsKCiAgdmFyIGRhdGEgPSBuZXcgT2JqZWN0KCk7CiAgZGF0YVsiZXBnTWFwcGluZyJdID0geEVQR1siZXBnTWFwcGluZyJdOwogIGRhdGFbImNtZCJdID0gInNhdmVFcGdNYXBwaW5nIjsKICAvL2NvbnNvbGUubG9nKGRhdGEpOwogIHhUZVZlKGRhdGEpOwp9CgpmdW5jdGlvbiBidWxrRWRpdCgpIHsKICBidWxrID0gIWJ1bGs7CiAgdmFyIGNsYXNzTmFtZTsKCiAgdmFyIGVsbSA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoImJ1bGsiKTsKCiAgc3dpdGNoKGJ1bGspIHsKICAgIGNhc2UgdHJ1ZTogCiAgICAgIGNsYXNzTmFtZSA9ICJidWxrIHNob3dCdWxrIjsKICAgICAgYnJlYWs7CgogICAgY2FzZSBmYWxzZTogCiAgICAgIGNsYXNzTmFtZSA9ICJidWxrIGhpZGVCdWxrIjsKICAgICAgYnVsa0VkaXRBbGwgPSBmYWxzZTsKICAgICAgYnJlYWs7CiAgfQoKICBmb3IgKHZhciBpID0gMDsgaSA8IGVsbS5sZW5ndGg7IGkrKykgewogICAgZWxtW2ldLmNsYXNzTmFtZSA9IGNsYXNzTmFtZTsKICAgIGVsbVtpXS5jaGVja2VkID0gZmFsc2U7CiAgfQoKfQoKZnVuY3Rpb24gYnVsa0VkaXRBbGxDaGFubmVscygpIHsKCiAgdmFyIGFsbFRSID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImlkX21hcHBpbmciKS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiVFIiKTsKCiAgZm9yICh2YXIgaSA9IDE7IGkgPCBhbGxUUi5sZW5ndGg7IGkrKykgewogICAgaWYgKGFsbFRSW2ldLnN0eWxlLmRpc3BsYXkgIT0gIm5vbmUiKSB7CiAgICAgIHN3aXRjaChidWxrRWRpdEFsbCkgewogICAgICAgIGNhc2UgZmFsc2U6IGFsbFRSW2ldLmZpcnN0Q2hpbGQuZmlyc3RDaGlsZC5jaGVja2VkID0gdHJ1ZTsgYnJlYWs7CiAgICAgICAgY2FzZSB0cnVlOiBhbGxUUltpXS5maXJzdENoaWxkLmZpcnN0Q2hpbGQuY2hlY2tlZCA9IGZhbHNlOyBicmVhazsgCiAgICAgIH0KCiAgICB9CiAgICAKICB9CgogIGJ1bGtFZGl0QWxsID0gIWJ1bGtFZGl0QWxsOwp9CgpmdW5jdGlvbiBzb3J0VGFibGUoY29sdW1tKSB7CiAgLy9jb25zb2xlLmxvZyhjb2x1bW0pOwogIGlmIChjb2x1bW0gPT0gY29sdW1uVG9Tb3J0KSB7CiAgICAvL3JldHVybjsKICB9CgogIHZhciB0YWJsZSAgICAgICA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJpZF9tYXBwaW5nIik7CiAgdmFyIHRhYmxlSGVhZCAgID0gdGFibGUuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIlRSIilbMF07CiAgdmFyIHRhYmxlSXRlbXMgID0gdGFibGVIZWFkLmdldEVsZW1lbnRzQnlUYWdOYW1lKCJURCIpOwogIAogIHZhciBzb3J0T2JqID0gbmV3IE9iamVjdCgpOwogIHZhciB4LCB4VmFsdWU7CiAgdmFyIHRhYmxlSGVhZGVyCiAgdmFyIHNvcnRCeVN0cmluZyA9IGZhbHNlCgogIGlmIChjb2x1bW0gPiAwICYmIGNvbHVtblRvU29ydCA+IDApICB7CiAgICB0YWJsZUl0ZW1zW2NvbHVtblRvU29ydF0uY2xhc3NOYW1lID0gInBvaW50ZXIiOwogICAgdGFibGVJdGVtc1tjb2x1bW1dLmNsYXNzTmFtZSA9ICJzb3J0VGhpcyI7CiAgfQoKICBjb2x1bW5Ub1NvcnQgPSBjb2x1bW07CgogIHZhciByb3dzID0gdGFibGUucm93czsKCiAgaWYgKHJvd3NbMV0gIT0gdW5kZWZpbmVkKSB7CiAgICB0YWJsZUhlYWRlciA9IHJvd3NbMF0KCiAgICB4ID0gcm93c1sxXS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiVEQiKVtjb2x1bW1dOwogICAgCiAgICBmb3IgKGkgPSAxOyBpIDwgcm93cy5sZW5ndGg7IGkrKykgewoKICAgICAgeCA9IHJvd3NbaV0uZ2V0RWxlbWVudHNCeVRhZ05hbWUoIlREIilbY29sdW1tXTsKCiAgICAgIHN3aXRjaCh4LmNoaWxkTm9kZXNbMF0udGFnTmFtZS50b0xvd2VyQ2FzZSgpKSB7CiAgICAgICAgY2FzZSAiaW5wdXQiOgogICAgICAgICAgeFZhbHVlID0geC5nZXRFbGVtZW50c0J5VGFnTmFtZSgiSU5QVVQiKVswXS52YWx1ZS50b0xvd2VyQ2FzZSgpOwogICAgICAgICAgYnJlYWs7CgogICAgICAgIGNhc2UgInAiOgogICAgICAgICAgeFZhbHVlID0geC5nZXRFbGVtZW50c0J5VGFnTmFtZSgiUCIpWzBdLmlubmVyVGV4dC50b0xvd2VyQ2FzZSgpOwogICAgICAgICAgYnJlYWs7CiAgICAgICAgCiAgICAgICAgZGVmYXVsdDogY29uc29sZS5sb2coeC5jaGlsZE5vZGVzWzBdLnRhZ05hbWUpOwogICAgICB9CgogICAgICBpZiAoeFZhbHVlID09ICIiIHx8IHhWYWx1ZSA9PSBOYU4pIHsKICAgICAgICB4VmFsdWUgPSBpCiAgICAgICAgc29ydE9ialtpXSA9IHJvd3NbaV07CiAgICAgIAogICAgICB9IGVsc2UgewoKICAgICAgICBzd2l0Y2goaXNOYU4oeFZhbHVlKSkgewogICAgICAgICAgY2FzZSBmYWxzZTogCgogICAgICAgICAgICB4VmFsdWUgPSBwYXJzZUZsb2F0KHhWYWx1ZSk7CiAgICAgICAgICAgIHNvcnRPYmpbeFZhbHVlXSA9IHJvd3NbaV0KICAgICAgICAgICAgYnJlYWs7CgogICAgICAgICAgY2FzZSB0cnVlOgoKICAgICAgICAgICAgc29ydEJ5U3RyaW5nID0gdHJ1ZQogICAgICAgICAgICBzb3J0T2JqW3hWYWx1ZS50b0xvd2VyQ2FzZSgpICsgaV0gPSByb3dzW2ldCiAgICAgICAgICAgIGJyZWFrOwoKICAgICAgICB9CgogICAgICB9CiAgICAKICAgIH0KCiAgICB3aGlsZSAodGFibGUuZmlyc3RDaGlsZCkgewogICAgICB0YWJsZS5yZW1vdmVDaGlsZCh0YWJsZS5maXJzdENoaWxkKTsKICAgIH0KICAgIAogICAgdmFyIHNvcnRWYWx1ZXMgPSBnZXRPYmpLZXlzKHNvcnRPYmopCiAgICBpZiAoc29ydEJ5U3RyaW5nID09IHRydWUpIHsKICAgICAgc29ydFZhbHVlcy5zb3J0KCkKICAgIH0gZWxzZSB7CiAgICAgIGZ1bmN0aW9uIHNvcnRGbG9hdChhLCBiKSB7IAogICAgICAgIHJldHVybiBhIC0gYjsgCiAgICAgIH0KICAgICAgc29ydFZhbHVlcy5zb3J0KHNvcnRGbG9hdCk7CiAgICB9CgogICAgdGFibGUuYXBwZW5kQ2hpbGQodGFibGVIZWFkZXIpCiAgICAKICAgIGZvciAodmFyIGkgPSAwOyBpIDwgc29ydFZhbHVlcy5sZW5ndGg7IGkrKykgewogICAgIAogICAgICB0YWJsZS5hcHBlbmRDaGlsZChzb3J0T2JqW3NvcnRWYWx1ZXNbaV1dKQoKICAgIH0KICAgIAogIH0KCn0KCgpmdW5jdGlvbiBzb3J0VGFibGVfb2xkKGNvbHVtbSkgewogIHNob3dMb2FkaW5nU2NyZWVuKHRydWUpOwogIAogIHNldFRpbWVvdXQoZnVuY3Rpb24oKXsgCgogICAgdmFyIHRhYmxlLCByb3dzLCBzd2l0Y2hpbmcsIGksIHgsIHksIHNob3VsZFN3aXRjaDsKICAgIHRhYmxlID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImlkX21hcHBpbmciKTsKCiAgICB2YXIgdGFibGVIZWFkID0gdGFibGUuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIlRSIilbMF07CiAgICB2YXIgdGFibGVJdGVtcyA9IHRhYmxlSGVhZC5nZXRFbGVtZW50c0J5VGFnTmFtZSgiVEQiKTsKCiAgICBpZiAoY29sdW1tID4gMCkgIHsKICAgICAgdGFibGVJdGVtc1tjb2x1bW5Ub1NvcnRdLmNsYXNzTmFtZSA9ICJwb2ludGVyIjsKICAgICAgdGFibGVJdGVtc1tjb2x1bW1dLmNsYXNzTmFtZSA9ICJzb3J0VGhpcyI7CiAgICB9CiAgICAKICAgIGNvbHVtblRvU29ydCA9IGNvbHVtbTsKCiAgICAvKgogICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0YWJsZUl0ZW1zLmxlbmd0aDsgaSsrKSB7CiAgICAgIGlmICh0YWJsZUl0ZW1zW2ldLmNsYXNzTmFtZSAhPSB1bmRlZmluZWQpIHsKICAgICAgICB0YWJsZUl0ZW1zW2ldLmNsYXNzTmFtZSA9ICJwb2ludGVyIgogICAgICB9CgogICAgfQogICAgKi8KCiAgICAKCiAgICBjb25zb2xlLmxvZyh0YWJsZUl0ZW1zKTsgCgogICAgc3dpdGNoaW5nID0gdHJ1ZTsKICAgIHdoaWxlIChzd2l0Y2hpbmcpIHsKICAgICAgc3dpdGNoaW5nID0gZmFsc2U7CiAgICAgIHJvd3MgPSB0YWJsZS5yb3dzOwogICAgICBmb3IgKGkgPSAxOyBpIDwgKHJvd3MubGVuZ3RoIC0gMSk7IGkrKykgewogICAgICAgIHNob3VsZFN3aXRjaCA9IGZhbHNlOwoKICAgICAgICB4ID0gcm93c1tpXS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiVEQiKVtjb2x1bW1dOwogICAgICAgIHkgPSByb3dzW2kgKyAxXS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiVEQiKVtjb2x1bW1dOwoKICAgICAgICBzd2l0Y2goeC5jaGlsZE5vZGVzWzBdLnRhZ05hbWUudG9Mb3dlckNhc2UoKSkgewogICAgICAgICAgY2FzZSAiaW5wdXQiOgogICAgICAgICAgICB4VmFsdWUgPSB4LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJJTlBVVCIpWzBdLnZhbHVlLnRvTG93ZXJDYXNlKCk7CiAgICAgICAgICAgIHlWYWx1ZSA9IHkuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIklOUFVUIilbMF0udmFsdWUudG9Mb3dlckNhc2UoKTsKICAgICAgICAgICAgYnJlYWs7CgogICAgICAgICAgY2FzZSAicCI6CiAgICAgICAgICAgIHhWYWx1ZSA9IHguZ2V0RWxlbWVudHNCeVRhZ05hbWUoIlAiKVswXS5pbm5lclRleHQudG9Mb3dlckNhc2UoKTsKICAgICAgICAgICAgeVZhbHVlID0geS5nZXRFbGVtZW50c0J5VGFnTmFtZSgiUCIpWzBdLmlubmVyVGV4dC50b0xvd2VyQ2FzZSgpOwogICAgICAgICAgICBicmVhazsKICAgICAgICAgIAogICAgICAgICAgZGVmYXVsdDogY29uc29sZS5sb2coeC5jaGlsZE5vZGVzWzBdLnRhZ05hbWUpOwogICAgICAgIH0KCiAgICAgICAgCiAgICAgICAgc3dpdGNoKGlzTmFOKHhWYWx1ZSkpIHsKICAgICAgICAgIGNhc2UgZmFsc2U6IHhWYWx1ZSA9IHBhcnNlRmxvYXQoeFZhbHVlKSA7IGJyZWFrOwogICAgICAgIH0KCiAgICAgICAgc3dpdGNoKGlzTmFOKHlWYWx1ZSkpIHsKICAgICAgICAgIGNhc2UgZmFsc2U6IHlWYWx1ZSA9IHBhcnNlRmxvYXQoeVZhbHVlKSA7IGJyZWFrOwogICAgICAgIH0KICAgICAgICAKCiAgICAgICAgaWYgKHhWYWx1ZSA+IHlWYWx1ZSkgewogICAgICAgICAgc2hvdWxkU3dpdGNoID0gdHJ1ZTsKICAgICAgICAgIGJyZWFrOwogICAgICAgIH0KCiAgICAgIH0KICAgICAgaWYgKHNob3VsZFN3aXRjaCkgewogICAgICAgIHJvd3NbaV0ucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUocm93c1tpICsgMV0sIHJvd3NbaV0pOwogICAgICAgIHN3aXRjaGluZyA9IHRydWU7CiAgICAgIH0KICAgIH0KICAgIGNyZWF0ZVNlYXJjaE9iaigpCiAgICAKICAgIHNob3dMb2FkaW5nU2NyZWVuKGZhbHNlKTsKICB9LCAyMCk7Cgp9CgpmdW5jdGlvbiBzaG93WEVQRygpIHsKICB2YXIgdXJsID0gbG9jYXRpb24ucHJvdG9jb2wgKyAiLy8iICsgbG9jYXRpb24uaG9zdG5hbWUgKyAiOiIgKyBsb2NhdGlvbi5wb3J0ICsgIi94bWx0di94dGV2ZS54bWwiCiAgdmFyIHdpbiA9IHdpbmRvdy5vcGVuKHVybCwgJ19ibGFuaycpOwogIHdpbi5mb2N1cygpOwp9" + webUI["html/js/network_ts.js"] = "dmFyIFNlcnZlciA9IC8qKiBAY2xhc3MgKi8gKGZ1bmN0aW9uICgpIHsKICAgIGZ1bmN0aW9uIFNlcnZlcihjbWQpIHsKICAgICAgICB0aGlzLmNtZCA9IGNtZDsKICAgIH0KICAgIFNlcnZlci5wcm90b3R5cGUucmVxdWVzdCA9IGZ1bmN0aW9uIChkYXRhKSB7CiAgICAgICAgaWYgKFNFUlZFUl9DT05ORUNUSU9OID09IHRydWUpIHsKICAgICAgICAgICAgcmV0dXJuOwogICAgICAgIH0KICAgICAgICBTRVJWRVJfQ09OTkVDVElPTiA9IHRydWU7CiAgICAgICAgY29uc29sZS5sb2coZGF0YSk7CiAgICAgICAgaWYgKHRoaXMuY21kICE9ICJ1cGRhdGVMb2ciKSB7CiAgICAgICAgICAgIHNob3dFbGVtZW50KCJsb2FkaW5nIiwgdHJ1ZSk7CiAgICAgICAgICAgIFVORE8gPSBuZXcgT2JqZWN0KCk7CiAgICAgICAgfQogICAgICAgIHN3aXRjaCAod2luZG93LmxvY2F0aW9uLnByb3RvY29sKSB7CiAgICAgICAgICAgIGNhc2UgImh0dHA6IjoKICAgICAgICAgICAgICAgIHRoaXMucHJvdG9jb2wgPSAid3M6Ly8iOwogICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgIGNhc2UgImh0dHBzOi8vIjoKICAgICAgICAgICAgICAgIHRoaXMucHJvdG9jb2wgPSAid3NzOi8vIjsKICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgIH0KICAgICAgICB2YXIgdXJsID0gdGhpcy5wcm90b2NvbCArIHdpbmRvdy5sb2NhdGlvbi5ob3N0bmFtZSArICI6IiArIHdpbmRvdy5sb2NhdGlvbi5wb3J0ICsgIi9kYXRhLyIgKyAiP1Rva2VuPSIgKyBnZXRDb29raWUoIlRva2VuIik7CiAgICAgICAgZGF0YVsiY21kIl0gPSB0aGlzLmNtZDsKICAgICAgICB2YXIgd3MgPSBuZXcgV2ViU29ja2V0KHVybCk7CiAgICAgICAgd3Mub25vcGVuID0gZnVuY3Rpb24gKCkgewogICAgICAgICAgICBXU19BVkFJTEFCTEUgPSB0cnVlOwogICAgICAgICAgICBjb25zb2xlLmxvZygiUkVRVUVTVCAoSlMpOiIpOwogICAgICAgICAgICBjb25zb2xlLmxvZyhkYXRhKTsKICAgICAgICAgICAgY29uc29sZS5sb2coIlJFUVVFU1Q6IChKU09OKSIpOwogICAgICAgICAgICBjb25zb2xlLmxvZyhKU09OLnN0cmluZ2lmeShkYXRhKSk7CiAgICAgICAgICAgIHRoaXMuc2VuZChKU09OLnN0cmluZ2lmeShkYXRhKSk7CiAgICAgICAgfTsKICAgICAgICB3cy5vbmVycm9yID0gZnVuY3Rpb24gKGUpIHsKICAgICAgICAgICAgY29uc29sZS5sb2coIk5vIHdlYnNvY2tldCBjb25uZWN0aW9uIHRvIHhUZVZlIGNvdWxkIGJlIGVzdGFibGlzaGVkLiBDaGVjayB5b3VyIG5ldHdvcmsgY29uZmlndXJhdGlvbi4iKTsKICAgICAgICAgICAgU0VSVkVSX0NPTk5FQ1RJT04gPSBmYWxzZTsKICAgICAgICAgICAgaWYgKFdTX0FWQUlMQUJMRSA9PSBmYWxzZSkgewogICAgICAgICAgICAgICAgYWxlcnQoIk5vIHdlYnNvY2tldCBjb25uZWN0aW9uIHRvIHhUZVZlIGNvdWxkIGJlIGVzdGFibGlzaGVkLiBDaGVjayB5b3VyIG5ldHdvcmsgY29uZmlndXJhdGlvbi4iKTsKICAgICAgICAgICAgfQogICAgICAgIH07CiAgICAgICAgd3Mub25tZXNzYWdlID0gZnVuY3Rpb24gKGUpIHsKICAgICAgICAgICAgU0VSVkVSX0NPTk5FQ1RJT04gPSBmYWxzZTsKICAgICAgICAgICAgc2hvd0VsZW1lbnQoImxvYWRpbmciLCBmYWxzZSk7CiAgICAgICAgICAgIGNvbnNvbGUubG9nKCJSRVNQT05TRToiKTsKICAgICAgICAgICAgdmFyIHJlc3BvbnNlID0gSlNPTi5wYXJzZShlLmRhdGEpOwogICAgICAgICAgICBjb25zb2xlLmxvZyhyZXNwb25zZSk7CiAgICAgICAgICAgIGlmIChyZXNwb25zZS5oYXNPd25Qcm9wZXJ0eSgidG9rZW4iKSkgewogICAgICAgICAgICAgICAgZG9jdW1lbnQuY29va2llID0gIlRva2VuPSIgKyByZXNwb25zZVsidG9rZW4iXTsKICAgICAgICAgICAgfQogICAgICAgICAgICBpZiAocmVzcG9uc2VbInN0YXR1cyJdID09IGZhbHNlKSB7CiAgICAgICAgICAgICAgICBhbGVydChyZXNwb25zZVsiZXJyIl0pOwogICAgICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhhc093blByb3BlcnR5KCJyZWxvYWQiKSkgewogICAgICAgICAgICAgICAgICAgIGxvY2F0aW9uLnJlbG9hZCgpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgcmV0dXJuOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmIChyZXNwb25zZS5oYXNPd25Qcm9wZXJ0eSgibG9nb1VSTCIpKSB7CiAgICAgICAgICAgICAgICB2YXIgZGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImNoYW5uZWwtaWNvbiIpOwogICAgICAgICAgICAgICAgZGl2LnZhbHVlID0gcmVzcG9uc2VbImxvZ29VUkwiXTsKICAgICAgICAgICAgICAgIGRpdi5jbGFzc05hbWUgPSAiY2hhbmdlZCI7CiAgICAgICAgICAgICAgICByZXR1cm47CiAgICAgICAgICAgIH0KICAgICAgICAgICAgc3dpdGNoIChkYXRhWyJjbWQiXSkgewogICAgICAgICAgICAgICAgY2FzZSAidXBkYXRlTG9nIjoKICAgICAgICAgICAgICAgICAgICBTRVJWRVJbImxvZyJdID0gcmVzcG9uc2VbImxvZyJdOwogICAgICAgICAgICAgICAgICAgIGlmIChkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgiY29udGVudF9sb2ciKSkgewogICAgICAgICAgICAgICAgICAgICAgICBzaG93TG9ncyhmYWxzZSk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgICAgICAgICBicmVhazsKICAgICAgICAgICAgICAgIGRlZmF1bHQ6CiAgICAgICAgICAgICAgICAgICAgU0VSVkVSID0gbmV3IE9iamVjdCgpOwogICAgICAgICAgICAgICAgICAgIFNFUlZFUiA9IHJlc3BvbnNlOwogICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmIChyZXNwb25zZS5oYXNPd25Qcm9wZXJ0eSgib3Blbk1lbnUiKSkgewogICAgICAgICAgICAgICAgdmFyIG1lbnUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChyZXNwb25zZVsib3Blbk1lbnUiXSk7CiAgICAgICAgICAgICAgICBtZW51LmNsaWNrKCk7CiAgICAgICAgICAgICAgICBzaG93RWxlbWVudCgicG9wdXAiLCBmYWxzZSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgaWYgKHJlc3BvbnNlLmhhc093blByb3BlcnR5KCJvcGVuTGluayIpKSB7CiAgICAgICAgICAgICAgICB3aW5kb3cubG9jYXRpb24gPSByZXNwb25zZVsib3BlbkxpbmsiXTsKICAgICAgICAgICAgfQogICAgICAgICAgICBpZiAocmVzcG9uc2UuaGFzT3duUHJvcGVydHkoImFsZXJ0IikpIHsKICAgICAgICAgICAgICAgIGFsZXJ0KHJlc3BvbnNlWyJhbGVydCJdKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBpZiAocmVzcG9uc2UuaGFzT3duUHJvcGVydHkoInJlbG9hZCIpKSB7CiAgICAgICAgICAgICAgICBsb2NhdGlvbi5yZWxvYWQoKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBpZiAocmVzcG9uc2UuaGFzT3duUHJvcGVydHkoIndpemFyZCIpKSB7CiAgICAgICAgICAgICAgICBjcmVhdGVMYXlvdXQoKTsKICAgICAgICAgICAgICAgIGNvbmZpZ3VyYXRpb25XaXphcmRbcmVzcG9uc2VbIndpemFyZCJdXS5jcmVhdGVXaXphcmQoKTsKICAgICAgICAgICAgICAgIHJldHVybjsKICAgICAgICAgICAgfQogICAgICAgICAgICBjcmVhdGVMYXlvdXQoKTsKICAgICAgICB9OwogICAgfTsKICAgIHJldHVybiBTZXJ2ZXI7Cn0oKSk7CmZ1bmN0aW9uIGdldENvb2tpZShuYW1lKSB7CiAgICB2YXIgdmFsdWUgPSAiOyAiICsgZG9jdW1lbnQuY29va2llOwogICAgdmFyIHBhcnRzID0gdmFsdWUuc3BsaXQoIjsgIiArIG5hbWUgKyAiPSIpOwogICAgaWYgKHBhcnRzLmxlbmd0aCA9PSAyKQogICAgICAgIHJldHVybiBwYXJ0cy5wb3AoKS5zcGxpdCgiOyIpLnNoaWZ0KCk7Cn0K" + +} + diff --git a/src/webserver.go b/src/webserver.go new file mode 100644 index 0000000..841132d --- /dev/null +++ b/src/webserver.go @@ -0,0 +1,1050 @@ +package src + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "strconv" + "strings" + + "../src/internal/authentication" + + "github.com/gorilla/websocket" +) + +// StartWebserver : Startet den Webserver +func StartWebserver() (err error) { + + var port = Settings.Port + + http.HandleFunc("/", Index) + http.HandleFunc("/stream/", Stream) + http.HandleFunc("/xmltv/", xTeVe) + http.HandleFunc("/m3u/", xTeVe) + http.HandleFunc("/data/", WS) + http.HandleFunc("/web/", Web) + http.HandleFunc("/download/", Download) + http.HandleFunc("/api/", API) + http.HandleFunc("/images/", Images) + http.HandleFunc("/data_images/", DataImages) + //http.HandleFunc("/auto/", Auto) + + showInfo("DVR IP:" + System.IPAddress + ":" + Settings.Port) + + showHighlight(fmt.Sprintf("Web Interface:%s://%s:%s/web/", System.ServerProtocol.WEB, System.IPAddress, Settings.Port)) + + if err = http.ListenAndServe(":"+port, nil); err != nil { + ShowError(err, 1001) + return + } + + return +} + +// Index : Web Server / +func Index(w http.ResponseWriter, r *http.Request) { + + var err error + var response []byte + var path = r.URL.Path + var debug string + + setGlobalDomain(r.Host) + + debug = fmt.Sprintf("Web Server Request:Path: %s", path) + showDebug(debug, 2) + + switch path { + + case "/discover.json": + response, err = getDiscover() + w.Header().Set("Content-Type", "application/json") + + case "/lineup_status.json": + response, err = getLineupStatus() + w.Header().Set("Content-Type", "application/json") + + case "/lineup.json": + if Settings.AuthenticationPMS == true { + + _, err := basicAuth(r, "authentication.pms") + if err != nil { + ShowError(err, 000) + httpStatusError(w, r, 403) + return + } + + } + + response, err = getLineup() + w.Header().Set("Content-Type", "application/json") + + case "/device.xml", "/capability": + response, err = getCapability() + w.Header().Set("Content-Type", "application/xml") + + default: + response, err = getCapability() + w.Header().Set("Content-Type", "application/xml") + } + + if err == nil { + + w.WriteHeader(200) + w.Write(response) + return + + } + + httpStatusError(w, r, 500) + + return +} + +// Stream : Web Server /stream/ +func Stream(w http.ResponseWriter, r *http.Request) { + + var path = strings.Replace(r.RequestURI, "/stream/", "", 1) + //var stream = strings.SplitN(path, "-", 2) + + streamInfo, err := getStreamInfo(path) + if err != nil { + ShowError(err, 1203) + httpStatusError(w, r, 404) + return + } + + if strings.Index(streamInfo.URL, "rtsp://") != -1 || strings.Index(streamInfo.URL, "rtp://") != -1 { + err = errors.New("RTSP and RTP streams are not supported") + ShowError(err, 2004) + + showInfo("Streaming URL:" + streamInfo.URL) + http.Redirect(w, r, streamInfo.URL, 302) + + showInfo("Streaming Info:URL was passed to the client") + return + } + + showInfo(fmt.Sprintf("Buffer:%t", Settings.Buffer)) + + if Settings.Buffer == true { + showInfo(fmt.Sprintf("Buffer Size:%d KB", Settings.BufferSize)) + } + + showInfo(fmt.Sprintf("Channel Name:%s", streamInfo.Name)) + showInfo(fmt.Sprintf("Client User-Agent:%s", r.Header.Get("User-Agent"))) + + // Prüfen ob der Buffer verwendet werden soll + switch Settings.Buffer { + + case true: + bufferingStream(streamInfo.PlaylistID, streamInfo.URL, streamInfo.Name, w, r) + + case false: + showInfo("Streaming URL:" + streamInfo.URL) + http.Redirect(w, r, streamInfo.URL, 302) + + showInfo("Streaming Info:URL was passed to the client") + + } + + return +} + +// Auto : HDHR routing (wird derzeit nicht benutzt) +func Auto(w http.ResponseWriter, r *http.Request) { + + var channelID = strings.Replace(r.RequestURI, "/auto/v", "", 1) + fmt.Println(channelID) + + /* + switch Settings.Buffer { + + case true: + var playlistID, streamURL, err = getStreamByChannelID(channelID) + if err == nil { + bufferingStream(playlistID, streamURL, w, r) + } else { + httpStatusError(w, r, 404) + } + + case false: + httpStatusError(w, r, 423) + } + */ + + return +} + +// xTeVe : Web Server /xmltv/ und /m3u/ +func xTeVe(w http.ResponseWriter, r *http.Request) { + + var requestType, groupTitle, file, content string + var err error + var path = strings.TrimPrefix(r.URL.Path, "/") + var groups = []string{} + + setGlobalDomain(r.Host) + + // XMLTV Datei + if strings.Contains(path, "xmltv/") { + + requestType = "xml" + + file = System.Folder.Data + getFilenameFromPath(path) + + content, err = readStringFromFile(file) + if err != nil { + httpStatusError(w, r, 404) + return + } + + } + + // M3U Datei + if strings.Contains(path, "m3u/") { + + requestType = "m3u" + groupTitle = r.URL.Query().Get("group-title") + + if System.Dev == false { + // false: Dateiname wird im Header gesetzt + // true: M3U wird direkt im Browser angezeigt + w.Header().Set("Content-Disposition", "attachment; filename="+getFilenameFromPath(path)) + } + + if len(groupTitle) > 0 { + groups = strings.Split(groupTitle, ",") + } + + content, err = buildM3U(groups) + if err != nil { + ShowError(err, 000) + } + + } + + // Authentifizierung überprüfen + err = urlAuth(r, requestType) + if err != nil { + ShowError(err, 000) + httpStatusError(w, r, 403) + return + } + + if err == nil { + w.Write([]byte(content)) + } + + return +} + +// Images : Image Cache /images/ +func Images(w http.ResponseWriter, r *http.Request) { + + var path = strings.TrimPrefix(r.URL.Path, "/") + var filePath = System.Folder.ImagesCache + getFilenameFromPath(path) + + content, err := readByteFromFile(filePath) + if err != nil { + httpStatusError(w, r, 404) + return + } + + w.Header().Add("Content-Type", getContentType(filePath)) + w.Header().Add("Content-Length", fmt.Sprintf("%d", len(content))) + w.WriteHeader(200) + w.Write(content) + + return +} + +// DataImages : Image Pfad für Logos / Bilder die hochgeladen wurden /data_images/ +func DataImages(w http.ResponseWriter, r *http.Request) { + + var path = strings.TrimPrefix(r.URL.Path, "/") + var filePath = System.Folder.ImagesUpload + getFilenameFromPath(path) + + content, err := readByteFromFile(filePath) + if err != nil { + httpStatusError(w, r, 404) + return + } + + w.Header().Add("Content-Type", getContentType(filePath)) + w.Header().Add("Content-Length", fmt.Sprintf("%d", len(content))) + w.WriteHeader(200) + w.Write(content) + + return +} + +// WS : Web Sockets /ws/ +func WS(w http.ResponseWriter, r *http.Request) { + + var request RequestStruct + var response ResponseStruct + response.Status = true + + var newToken string + + if r.Header.Get("Origin") != "http://"+r.Host { + httpStatusError(w, r, 403) + return + } + + conn, err := websocket.Upgrade(w, r, w.Header(), 1024, 1024) + if err != nil { + ShowError(err, 0) + http.Error(w, "Could not open websocket connection", http.StatusBadRequest) + } + + for { + + err = conn.ReadJSON(&request) + + if err != nil { + return + } + + if System.ConfigurationWizard == false { + + switch Settings.AuthenticationWEB { + + // Token Authentication + case true: + + var token string + tokens, ok := r.URL.Query()["Token"] + + if !ok || len(tokens[0]) < 1 { + token = "-" + } else { + token = tokens[0] + } + + newToken, err = tokenAuthentication(token) + if err != nil { + + response.Status = false + response.Reload = true + response.Error = err.Error() + request.Cmd = "-" + + if err = conn.WriteJSON(response); err != nil { + ShowError(err, 1102) + } + + return + } + + response.Token = newToken + response.Users, _ = authentication.GetAllUserData() + + } + + } + + switch request.Cmd { + // Daten lesen + case "getServerConfig": + //response.Config = Settings + + case "updateLog": + response = setDefaultResponseData(response, false) + if err = conn.WriteJSON(response); err != nil { + ShowError(err, 1022) + } else { + return + break + } + return + + case "loadFiles": + //response.Response = Settings.Files + + // Daten schreiben + case "saveSettings": + var authenticationUpdate = Settings.AuthenticationWEB + response.Settings, err = updateServerSettings(request) + if err == nil { + + response.OpenMenu = strconv.Itoa(indexOfString("settings", System.WEB.Menu)) + + if Settings.AuthenticationWEB == true && authenticationUpdate == false { + response.Reload = true + } + + } + + case "saveFilesM3U": + err = saveFiles(request, "m3u") + if err == nil { + response.OpenMenu = strconv.Itoa(indexOfString("playlist", System.WEB.Menu)) + } + + case "updateFileM3U": + err = updateFile(request, "m3u") + if err == nil { + response.OpenMenu = strconv.Itoa(indexOfString("playlist", System.WEB.Menu)) + } + + case "saveFilesHDHR": + err = saveFiles(request, "hdhr") + if err == nil { + response.OpenMenu = strconv.Itoa(indexOfString("playlist", System.WEB.Menu)) + } + + case "updateFileHDHR": + err = updateFile(request, "hdhr") + if err == nil { + response.OpenMenu = strconv.Itoa(indexOfString("playlist", System.WEB.Menu)) + } + + case "saveFilesXMLTV": + err = saveFiles(request, "xmltv") + if err == nil { + response.OpenMenu = strconv.Itoa(indexOfString("xmltv", System.WEB.Menu)) + } + + case "updateFileXMLTV": + err = updateFile(request, "xmltv") + if err == nil { + response.OpenMenu = strconv.Itoa(indexOfString("xmltv", System.WEB.Menu)) + } + + case "saveFilter": + response.Settings, err = saveFilter(request) + if err == nil { + response.OpenMenu = strconv.Itoa(indexOfString("filter", System.WEB.Menu)) + } + + case "saveEpgMapping": + err = saveXEpgMapping(request) + + case "saveUserData": + err = saveUserData(request) + if err == nil { + response.OpenMenu = strconv.Itoa(indexOfString("users", System.WEB.Menu)) + } + + case "saveNewUser": + err = saveNewUser(request) + if err == nil { + response.OpenMenu = strconv.Itoa(indexOfString("users", System.WEB.Menu)) + } + + case "resetLogs": + WebScreenLog.Log = make([]string, 0) + WebScreenLog.Errors = 0 + WebScreenLog.Warnings = 0 + response.OpenMenu = strconv.Itoa(indexOfString("log", System.WEB.Menu)) + + case "xteveBackup": + file, errNew := xteveBackup() + err = errNew + if err == nil { + response.OpenLink = System.URLBase + "/download/" + file + } + + case "xteveRestore": + WebScreenLog.Log = make([]string, 0) + WebScreenLog.Errors = 0 + WebScreenLog.Warnings = 0 + + if len(request.Base64) > 0 { + + newWebURL, err := xteveRestore(request.Base64) + if err != nil { + ShowError(err, 000) + } + + if err == nil { + + if len(newWebURL) > 0 { + response.Alert = "Backup was successfully restored.\nThe port of the sTeVe URL has changed, you have to restart xTeVe.\nAfter a restart, xTeVe can be reached again at the following URL:\n" + newWebURL + } else { + response.Alert = "Backup was successfully restored." + response.Reload = true + } + showInfo("xTeVe:" + "Backup successfully restored.") + } + + } + + case "uploadLogo": + if len(request.Base64) > 0 { + response.LogoURL, err = uploadLogo(request.Base64, request.Filename) + + if err == nil { + + if err = conn.WriteJSON(response); err != nil { + ShowError(err, 1022) + } else { + return + } + + } + + } + + case "saveWizard": + nextStep, errNew := saveWizard(request) + + err = errNew + if err == nil { + + if nextStep == 10 { + System.ConfigurationWizard = false + response.Reload = true + } else { + response.Wizard = nextStep + } + + } + + /* + case "wizardCompleted": + System.ConfigurationWizard = false + response.Reload = true + */ + default: + fmt.Println("+ + + + + + + + + + +", request.Cmd) + + var requestMap = make(map[string]interface{}) // Debug + _ = requestMap + if System.Dev == true { + fmt.Println(mapToJSON(requestMap)) + } + + } + + if err != nil { + response.Status = false + response.Error = err.Error() + response.Settings = Settings + } + + setGlobalDomain(r.Host) + response = setDefaultResponseData(response, true) + if System.ConfigurationWizard == true { + response.ConfigurationWizard = System.ConfigurationWizard + } + + if err = conn.WriteJSON(response); err != nil { + ShowError(err, 1022) + } else { + break + } + + } + + return +} + +// Web : Web Server /web/ +func Web(w http.ResponseWriter, r *http.Request) { + + var lang = make(map[string]interface{}) + var err error + + var requestFile = strings.Replace(r.URL.Path, "/web", "html", -1) + var content, contentType, file string + + var language LanguageUI + + if System.Dev == true { + + lang, err = loadJSONFileToMap(fmt.Sprintf("html/lang/%s.json", Settings.Language)) + if err != nil { + ShowError(err, 000) + } + + } else { + + var languageFile = "html/lang/en.json" + + if value, ok := webUI[languageFile].(string); ok { + content = GetHTMLString(value) + lang = jsonToMap(content) + } + + } + + err = json.Unmarshal([]byte(mapToJSON(lang)), &language) + if err != nil { + ShowError(err, 000) + return + } + + if getFilenameFromPath(requestFile) == "html" { + + if len(Data.Streams.All) == 0 && System.ScanInProgress == 0 { + System.ConfigurationWizard = true + } + + switch System.ConfigurationWizard { + + case true: + file = requestFile + "configuration.html" + Settings.AuthenticationWEB = false + + case false: + file = requestFile + "index.html" + + } + + if System.ScanInProgress == 1 { + file = requestFile + "maintenance.html" + } + + switch Settings.AuthenticationWEB { + case true: + + var username, password, confirm string + switch r.Method { + case "POST": + var allUsers, _ = authentication.GetAllUserData() + + username = r.FormValue("username") + password = r.FormValue("password") + + if len(allUsers) == 0 { + confirm = r.FormValue("confirm") + } + + // Erster Benutzer wird angelegt (Passwortbestätigung ist vorhanden) + if len(confirm) > 0 { + + var token, err = createFirstUserForAuthentication(username, password) + if err != nil { + httpStatusError(w, r, 429) + return + } + // Redirect, damit die Daten aus dem Browser gelöscht werden. + w = authentication.SetCookieToken(w, token) + http.Redirect(w, r, "/web", 301) + return + + } + + // Benutzername und Passwort vorhanden, wird jetzt überprüft + if len(username) > 0 && len(password) > 0 { + + var token, err = authentication.UserAuthentication(username, password) + if err != nil { + file = requestFile + "login.html" + lang["authenticationErr"] = language.Login.Failed + break + } + + w = authentication.SetCookieToken(w, token) + http.Redirect(w, r, "/web", 301) // Redirect, damit die Daten aus dem Browser gelöscht werden. + + } else { + w = authentication.SetCookieToken(w, "-") + http.Redirect(w, r, "/web", 301) // Redirect, damit die Daten aus dem Browser gelöscht werden. + } + + return + + case "GET": + lang["authenticationErr"] = "" + _, token, err := authentication.CheckTheValidityOfTheTokenFromHTTPHeader(w, r) + + if err != nil { + file = requestFile + "login.html" + break + } + + err = checkAuthorizationLevel(token, "authentication.web") + if err != nil { + file = requestFile + "login.html" + break + } + + } + + allUserData, err := authentication.GetAllUserData() + if err != nil { + ShowError(err, 000) + httpStatusError(w, r, 403) + return + } + + if len(allUserData) == 0 && Settings.AuthenticationWEB == true { + file = requestFile + "create-first-user.html" + } + + } + + requestFile = file + + if value, ok := webUI[requestFile]; ok { + + content = GetHTMLString(value.(string)) + + if contentType == "text/plain" { + w.Header().Set("Content-Disposition", "attachment; filename="+getFilenameFromPath(requestFile)) + } + + } else { + + httpStatusError(w, r, 404) + return + } + + } + + if value, ok := webUI[requestFile].(string); ok { + + content = GetHTMLString(value) + contentType = getContentType(requestFile) + + if contentType == "text/plain" { + w.Header().Set("Content-Disposition", "attachment; filename="+getFilenameFromPath(requestFile)) + } + + } else { + httpStatusError(w, r, 404) + return + } + + contentType = getContentType(requestFile) + + if System.Dev == true { + // Lokale Webserver Dateien werden geladen, nur für die Entwicklung + content, _ = readStringFromFile(requestFile) + } + + w.Header().Add("Content-Type", contentType) + w.WriteHeader(200) + + if contentType == "text/html" || contentType == "application/javascript" { + content = parseTemplate(content, lang) + } + + w.Write([]byte(content)) +} + +// API : API request /api/ +func API(w http.ResponseWriter, r *http.Request) { + + /* + API Bedingungen (ohne Authentifizierung): + - API muss in den Einstellungen aktiviert sein + + Beispiel API Request mit curl + Status: + curl -X POST -H "Content-Type: application/json" -d '{"cmd":"status"}' http://localhost:34400/api/ + + - - - - - + + API Bedingungen (mit Authentifizierung): + - API muss in den Einstellungen aktiviert sein + - API muss bei den Authentifizierungseinstellungen aktiviert sein + - Benutzer muss die Berechtigung API haben + + Nach jeder API Anfrage wird ein Token generiert, dieser ist einmal in 60 Minuten gültig. + In jeder Antwort ist ein neuer Token enthalten + + Beispiel API Request mit curl + Login: + curl -X POST -H "Content-Type: application/json" -d '{"cmd":"login","username":"plex","password":"123"}' http://localhost:34400/api/ + + Antwort: + { + "status": true, + "token": "U0T-NTSaigh-RlbkqERsHvUpgvaaY2dyRGuwIIvv" + } + + Status mit Verwendung eines Tokens: + curl -X POST -H "Content-Type: application/json" -d '{"cmd":"status","token":"U0T-NTSaigh-RlbkqERsHvUpgvaaY2dyRGuwIIvv"}' http://localhost:4400/api/ + + Antwort: + { + "epg.source": "XEPG", + "status": true, + "streams.active": 7, + "streams.all": 63, + "streams.xepg": 2, + "token": "mXiG1NE1MrTXDtyh7PxRHK5z8iPI_LzxsQmY-LFn", + "url.dvr": "localhost:34400", + "url.m3u": "http://localhost:34400/m3u/xteve.m3u", + "url.xepg": "http://localhost:34400/xmltv/xteve.xml", + "version.api": "1.1.0", + "version.xteve": "1.3.0" + } + */ + + setGlobalDomain(r.Host) + var request APIRequestStruct + var response APIResponseStruct + + var responseAPIError = func(err error) { + + var response APIResponseStruct + + response.Status = false + response.Error = err.Error() + w.Write([]byte(mapToJSON(response))) + return + + } + + response.Status = true + + if Settings.API == false { + httpStatusError(w, r, 423) + return + } + + if r.Method == "GET" { + httpStatusError(w, r, 404) + return + } + + b, err := ioutil.ReadAll(r.Body) + defer r.Body.Close() + if err != nil { + httpStatusError(w, r, 400) + return + + } + + err = json.Unmarshal(b, &request) + if err != nil { + httpStatusError(w, r, 400) + return + } + + w.Header().Set("content-type", "application/json") + + if Settings.AuthenticationAPI == true { + var token string + switch len(request.Token) { + case 0: + if request.Cmd == "login" { + token, err = authentication.UserAuthentication(request.Username, request.Password) + if err != nil { + responseAPIError(err) + return + } + + } else { + err = errors.New("Login incorrect") + if err != nil { + responseAPIError(err) + return + } + + } + + default: + token, err = tokenAuthentication(request.Token) + fmt.Println(err) + if err != nil { + responseAPIError(err) + return + } + + } + err = checkAuthorizationLevel(token, "authentication.api") + if err != nil { + responseAPIError(err) + return + } + + response.Token = token + + } + + switch request.Cmd { + case "login": // Muss nichts übergeben werden + + case "status": + + fmt.Println("-----------------------------") + os.Exit(0) + response.VersionXteve = System.Version + response.VersionAPI = System.APIVersion + response.StreamsActive = int64(len(Data.Streams.Active)) + response.StreamsAll = int64(len(Data.Streams.All)) + response.StreamsXepg = int64(Data.XEPG.XEPGCount) + response.EpgSource = Settings.EpgSource + response.URLDvr = System.Domain + response.URLM3U = System.ServerProtocol.M3U + "://" + System.Domain + "/m3u/xteve.m3u" + response.URLXepg = System.ServerProtocol.XML + "://" + System.Domain + "/xmltv/xteve.xml" + + case "update.m3u": + err = getProviderData("m3u", "") + if err != nil { + break + } + + err = buildDatabaseDVR() + if err != nil { + break + } + + case "update.hdhr": + + err = getProviderData("hdhr", "") + if err != nil { + break + } + + err = buildDatabaseDVR() + if err != nil { + break + } + + case "update.xmltv": + err = getProviderData("xmltv", "") + if err != nil { + break + } + + case "update.xepg": + buildXEPG(false) + + default: + err = errors.New(getErrMsg(5000)) + + } + + if err != nil { + responseAPIError(err) + } + + w.Write([]byte(mapToJSON(response))) + + return +} + +// Download : Datei Download +func Download(w http.ResponseWriter, r *http.Request) { + + var path = r.URL.Path + var file = System.Folder.Temp + getFilenameFromPath(path) + w.Header().Set("Content-Disposition", "attachment; filename="+getFilenameFromPath(file)) + + content, err := readStringFromFile(file) + if err != nil { + w.WriteHeader(404) + return + } + + os.RemoveAll(System.Folder.Temp + getFilenameFromPath(path)) + w.Write([]byte(content)) + return +} + +func setDefaultResponseData(response ResponseStruct, data bool) (defaults ResponseStruct) { + + defaults = response + + // Folgende Daten immer an den Client übergeben + defaults.ClientInfo.ARCH = System.ARCH + defaults.ClientInfo.EpgSource = Settings.EpgSource + defaults.ClientInfo.DVR = System.Addresses.DVR + defaults.ClientInfo.M3U = System.Addresses.M3U + defaults.ClientInfo.XML = System.Addresses.XML + defaults.ClientInfo.OS = System.OS + defaults.ClientInfo.Streams = fmt.Sprintf("%d / %d", len(Data.Streams.Active), len(Data.Streams.All)) + defaults.ClientInfo.UUID = Settings.UUID + defaults.ClientInfo.Errors = WebScreenLog.Errors + defaults.ClientInfo.Warnings = WebScreenLog.Warnings + defaults.Notification = System.Notification + defaults.Log = WebScreenLog + + switch System.Branch { + + case "master": + defaults.ClientInfo.Version = fmt.Sprintf("%s", System.Version) + + default: + defaults.ClientInfo.Version = fmt.Sprintf("%s (%s)", System.Version, System.Build) + defaults.ClientInfo.Branch = System.Branch + + } + + if data == true { + + defaults.Users, _ = authentication.GetAllUserData() + //defaults.DVR = System.DVRAddress + + if Settings.EpgSource == "XEPG" { + + defaults.ClientInfo.XEPGCount = Data.XEPG.XEPGCount + + var XEPG = make(map[string]interface{}) + + if len(Data.Streams.Active) > 0 { + + XEPG["epgMapping"] = Data.XEPG.Channels + XEPG["xmltvMap"] = Data.XMLTV.Mapping + + } else { + + XEPG["epgMapping"] = make(map[string]interface{}) + XEPG["xmltvMap"] = make(map[string]interface{}) + + } + + defaults.XEPG = XEPG + + } + + defaults.Settings = Settings + + defaults.Data.Playlist.M3U.Groups.Text = Data.Playlist.M3U.Groups.Text + defaults.Data.Playlist.M3U.Groups.Value = Data.Playlist.M3U.Groups.Value + defaults.Data.StreamPreviewUI.Active = Data.StreamPreviewUI.Active + defaults.Data.StreamPreviewUI.Inactive = Data.StreamPreviewUI.Inactive + + } + + return +} + +func httpStatusError(w http.ResponseWriter, r *http.Request, httpStatusCode int) { + http.Error(w, fmt.Sprintf("%s [%d]", http.StatusText(httpStatusCode), httpStatusCode), httpStatusCode) + return +} + +func getContentType(filename string) (contentType string) { + + if strings.HasSuffix(filename, ".html") { + contentType = "text/html" + } else if strings.HasSuffix(filename, ".css") { + contentType = "text/css" + } else if strings.HasSuffix(filename, ".js") { + contentType = "application/javascript" + } else if strings.HasSuffix(filename, ".png") { + contentType = "image/png" + } else if strings.HasSuffix(filename, ".jpg") { + contentType = "image/jpeg" + } else if strings.HasSuffix(filename, ".gif") { + contentType = "image/gif" + } else if strings.HasSuffix(filename, ".svg") { + contentType = "image/svg+xml" + } else if strings.HasSuffix(filename, ".mp4") { + contentType = "video/mp4" + } else if strings.HasSuffix(filename, ".webm") { + contentType = "video/webm" + } else if strings.HasSuffix(filename, ".ogg") { + contentType = "video/ogg" + } else if strings.HasSuffix(filename, ".mp3") { + contentType = "audio/mp3" + } else if strings.HasSuffix(filename, ".wav") { + contentType = "audio/wav" + } else { + contentType = "text/plain" + } + + return +} diff --git a/src/xepg.go b/src/xepg.go new file mode 100644 index 0000000..5db87df --- /dev/null +++ b/src/xepg.go @@ -0,0 +1,967 @@ +package src + +import ( + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io/ioutil" + "path" + "runtime" + + "strconv" + "strings" + "time" +) + +// Provider XMLTV Datei überprüfen +func checkXMLCompatibility(id string, body []byte) (err error) { + + var xmltv XMLTV + var compatibility = make(map[string]int) + + err = xml.Unmarshal(body, &xmltv) + if err != nil { + return + } + + compatibility["xmltv.channels"] = len(xmltv.Channel) + compatibility["xmltv.programs"] = len(xmltv.Program) + + setProviderCompatibility(id, "xmltv", compatibility) + + return +} + +// XEPG Daten erstellen +func buildXEPG(background bool) { + + if System.ScanInProgress == 1 { + return + } + + System.ScanInProgress = 1 + + if Settings.EpgSource == "XEPG" { + + switch background { + + case true: + + go func() { + + createXEPGMapping() + createXEPGDatabase() + mapping() + cleanupXEPG() + createXMLTVFile() + createM3UFile() + go cachingImages() + + showInfo("XEPG:" + fmt.Sprintf("Ready to use")) + + System.ScanInProgress = 0 + + // Cache löschen + /* + Data.Cache.XMLTV = make(map[string]XMLTV) + Data.Cache.XMLTV = nil + */ + runtime.GC() + + }() + + case false: + + createXEPGMapping() + createXEPGDatabase() + mapping() + cleanupXEPG() + + go func() { + + createXMLTVFile() + createM3UFile() + go cachingImages() + showInfo("XEPG:" + fmt.Sprintf("Ready to use")) + + System.ScanInProgress = 0 + + // Cache löschen + //Data.Cache.XMLTV = make(map[string]XMLTV) + //Data.Cache.XMLTV = nil + runtime.GC() + + }() + + } + + } else { + + getLineup() + System.ScanInProgress = 0 + + } + +} + +// XEPG Daten aktualisieren +func updateXEPG(background bool) { + + if System.ScanInProgress == 1 { + return + } + + System.ScanInProgress = 1 + + if Settings.EpgSource == "XEPG" { + + switch background { + + case false: + + createXEPGDatabase() + mapping() + cleanupXEPG() + + go func() { + + createXMLTVFile() + createM3UFile() + showInfo("XEPG:" + fmt.Sprintf("Ready to use")) + + System.ScanInProgress = 0 + + }() + + case true: + System.ScanInProgress = 0 + + } + + } else { + + System.ScanInProgress = 0 + + } + + // Cache löschen + //Data.Cache.XMLTV = nil //make(map[string]XMLTV) + //Data.Cache.XMLTV = make(map[string]XMLTV) + + return +} + +// Mapping Menü für die XMLTV Dateien erstellen +func createXEPGMapping() { + + Data.XMLTV.Files = getLocalProviderFiles("xmltv") + Data.XMLTV.Mapping = make(map[string]interface{}) + + var tmpMap = make(map[string]interface{}) + + var friendlyDisplayName = func(channel Channel) (displayName string) { + var dn = channel.DisplayName + displayName = dn[0].Value + + switch len(dn) { + case 1: + displayName = dn[0].Value + default: + displayName = fmt.Sprintf("%s (%s)", dn[1].Value, dn[0].Value) + } + + return + } + + if len(Data.XMLTV.Files) == 0 { + return + } + + for i := len(Data.XMLTV.Files) - 1; i >= 0; i-- { + + var file = Data.XMLTV.Files[i] + + var err error + var fileID = strings.TrimSuffix(getFilenameFromPath(file), path.Ext(getFilenameFromPath(file))) + showInfo("XEPG:" + "Parse XMLTV file: " + getProviderParameter(fileID, "xmltv", "name")) + + //xmltv, err = getLocalXMLTV(file) + var xmltv XMLTV + + err = getLocalXMLTV(file, &xmltv) + if err != nil { + Data.XMLTV.Files = append(Data.XMLTV.Files, Data.XMLTV.Files[i+1:]...) + var errMsg = err.Error() + err = errors.New(getProviderParameter(fileID, "xmltv", "name") + ": " + errMsg) + ShowError(err, 000) + } + + // XML Parsen (Provider Datei) + if err == nil { + + // Daten aus der XML Datei in eine temporäre Map schreiben + var xmltvMap = make(map[string]interface{}) + + for _, c := range xmltv.Channel { + var channel = make(map[string]interface{}) + + channel["id"] = c.ID + channel["display-name"] = friendlyDisplayName(*c) + channel["icon"] = c.Icon.Src + + xmltvMap[c.ID] = channel + + } + + tmpMap[getFilenameFromPath(file)] = xmltvMap + Data.XMLTV.Mapping[getFilenameFromPath(file)] = xmltvMap + + } + + } + + // Auswahl für den Dummy erstellen + var dummy = make(map[string]interface{}) + var times = []string{"30", "60", "90", "120"} + + for _, i := range times { + + var dummyChannel = make(map[string]string) + dummyChannel["display-name"] = i + " Minutes" + dummyChannel["id"] = i + "_Minutes" + dummyChannel["icon"] = "" + + dummy[dummyChannel["id"]] = dummyChannel + + } + + Data.XMLTV.Mapping = tmpMap + Data.XMLTV.Mapping["xTeVe Dummy"] = dummy + + tmpMap = make(map[string]interface{}) + + return +} + +// XEPG Datenbank erstellen / aktualisieren +func createXEPGDatabase() (err error) { + + var allChannelNumbers []float64 + Data.Cache.Streams.Active = []string{} + + Data.XEPG.Channels, err = loadJSONFileToMap(System.File.XEPG) + if err != nil { + ShowError(err, 1004) + return err + } + + var createNewID = func() (xepg string) { + + var firstID = 0 //len(Data.XEPG.Channels) + + newXEPGID: + + if _, ok := Data.XEPG.Channels["x-ID."+strconv.FormatInt(int64(firstID), 10)]; ok { + firstID++ + goto newXEPGID + } + + xepg = "x-ID." + strconv.FormatInt(int64(firstID), 10) + return + } + + var getFreeChannelNumber = func() (xChannelID string) { + + var firstFreeNumber float64 = Settings.MappingFirstChannel + + newNumber: + + if indexOfFloat64(firstFreeNumber, allChannelNumbers) == -1 { + xChannelID = fmt.Sprintf("%g", firstFreeNumber) + allChannelNumbers = append(allChannelNumbers, firstFreeNumber) + } else { + firstFreeNumber++ + goto newNumber + } + + return + } + + showInfo("XEPG:" + "Update database") + + // Kanal mit fehlenden Kanalnummern löschen + for id, dxc := range Data.XEPG.Channels { + + var xepgChannel XEPGChannelStruct + err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel) + if err != nil { + return + } + + if len(xepgChannel.XChannelID) == 0 { + fmt.Println(mapToJSON(xepgChannel)) + delete(Data.XEPG.Channels, id) + } + + if xChannelID, err := strconv.ParseFloat(xepgChannel.XChannelID, 64); err == nil { + allChannelNumbers = append(allChannelNumbers, xChannelID) + } + + } + + for _, dsa := range Data.Streams.Active { + + var channelExists = false // Entscheidet ob ein Kanal neu zu Datenbank hinzugefügt werden soll. + var channelHasUUID = false // Überprüft, ob der Kanal (Stream) eindeutige ID's besitzt + var currentXEPGID string // Aktuelle Datenbank ID (XEPG). Wird verwendet, um den Kanal in der Datenbank mit dem Stream der M3u zu aktualisieren + var m3uChannel M3UChannelStructXEPG + + err = json.Unmarshal([]byte(mapToJSON(dsa)), &m3uChannel) + if err != nil { + return + } + + Data.Cache.Streams.Active = append(Data.Cache.Streams.Active, m3uChannel.Name) + + // XEPG Datenbank durchlaufen um nach dem Kanal zu suchen. + for xepg, dxc := range Data.XEPG.Channels { + + var xepgChannel XEPGChannelStruct + err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel) + if err != nil { + return + } + + // Vergleichen des Streams anhand einer UUID in der M3U mit dem Kanal in der Databank + if len(xepgChannel.UUIDValue) > 0 && len(m3uChannel.UUIDValue) > 0 { + + if xepgChannel.UUIDValue == m3uChannel.UUIDValue && xepgChannel.UUIDKey == m3uChannel.UUIDKey { + + channelExists = true + channelHasUUID = true + currentXEPGID = xepg + break + + } + + } else { + // Vergleichen des Streams mit dem Kanal in der Databank anhand des Kanalnamens + //fmt.Println(xepgChannel.Name, xepgChannel.UUIDKey, xepgChannel.UUIDValue) + if xepgChannel.Name == m3uChannel.Name { + channelExists = true + currentXEPGID = xepg + break + } + + } + + } + + //os.Exit(0) + + switch channelExists { + case true: + // Bereits vorhandener Kanal + var xepgChannel XEPGChannelStruct + err = json.Unmarshal([]byte(mapToJSON(Data.XEPG.Channels[currentXEPGID])), &xepgChannel) + if err != nil { + return + } + + // Streaming URL aktualisieren + xepgChannel.URL = m3uChannel.URL + + // Name aktualisieren, anhand des Names wird überprüft ob der Kanal noch in einer Playlist verhanden. Funktion: cleanupXEPG + xepgChannel.Name = m3uChannel.Name + + // Kanalname aktualisieren, nur mit Kanal ID's möglich + if channelHasUUID == true { + if xepgChannel.XUpdateChannelName == true { + xepgChannel.XName = m3uChannel.Name + } + } + + // Kanallogo aktualisieren. Wird bei vorhandenem Logo in der XMLTV Datei wieder überschrieben + if xepgChannel.XUpdateChannelIcon == true { + xepgChannel.TvgLogo = m3uChannel.TvgLogo + } + + Data.XEPG.Channels[currentXEPGID] = xepgChannel + + case false: + // Neuer Kanal + var xepg = createNewID() + var xChannelID = getFreeChannelNumber() + + var newChannel XEPGChannelStruct + newChannel.FileM3UID = m3uChannel.FileM3UID + newChannel.FileM3UName = m3uChannel.FileM3UName + newChannel.FileM3UPath = m3uChannel.FileM3UPath + newChannel.Values = m3uChannel.Values + newChannel.GroupTitle = m3uChannel.GroupTitle + newChannel.Name = m3uChannel.Name + newChannel.TvgID = m3uChannel.TvgID + newChannel.TvgLogo = m3uChannel.TvgLogo + newChannel.TvgName = m3uChannel.TvgName + newChannel.URL = m3uChannel.URL + newChannel.XmltvFile = "" + newChannel.XMapping = "" + + if len(m3uChannel.UUIDKey) > 0 { + newChannel.UUIDKey = m3uChannel.UUIDKey + newChannel.UUIDValue = m3uChannel.UUIDValue + } + + newChannel.XName = m3uChannel.Name + newChannel.XGroupTitle = m3uChannel.GroupTitle + newChannel.XEPG = xepg + newChannel.XChannelID = xChannelID + + Data.XEPG.Channels[xepg] = newChannel + + } + + } + + err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels) + if err != nil { + return + } + + return +} + +// Kanäle automatisch zuordnen und das Mapping überprüfen +func mapping() (err error) { + showInfo("XEPG:" + "Map channels") + + for xepg, dxc := range Data.XEPG.Channels { + + var xepgChannel XEPGChannelStruct + err = json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel) + if err != nil { + return + } + + // Automatische Mapping für neue Kanäle. Wird nur ausgeführt, wenn der Kanal deaktiviert ist und keine XMLTV Datei und kein XMLTV Kanal zugeordnet ist. + if xepgChannel.XActive == false { + + // Werte kann "-" sein, deswegen len < 1 + if len(xepgChannel.XmltvFile) < 1 && len(xepgChannel.XmltvFile) < 1 { + + var tvgID = xepgChannel.TvgID + + // Default für neuen Kanal setzen + xepgChannel.XmltvFile = "-" + xepgChannel.XMapping = "-" + + Data.XEPG.Channels[xepg] = xepgChannel + + for file, xmltvChannels := range Data.XMLTV.Mapping { + + if channel, ok := xmltvChannels.(map[string]interface{})[tvgID]; ok { + + if channelID, ok := channel.(map[string]interface{})["id"].(string); ok { + + xepgChannel.XmltvFile = file + xepgChannel.XMapping = channelID + xepgChannel.XActive = true + + // Falls in der XMLTV Datei ein Logo existiert, wird dieses verwendet. Falls nicht, dann das Logo aus der M3U Datei + if icon, ok := channel.(map[string]interface{})["icon"].(string); ok { + if len(icon) > 0 { + xepgChannel.TvgLogo = icon + } + } + + Data.XEPG.Channels[xepg] = xepgChannel + break + + } + + } + + } + + } + + } + + // Überprüfen, ob die zugeordneten XMLTV Dateien und Kanäle noch existieren. + if xepgChannel.XActive == true { + + var mapping = xepgChannel.XMapping + var file = xepgChannel.XmltvFile + + if file != "xTeVe Dummy" { + + if value, ok := Data.XMLTV.Mapping[file].(map[string]interface{}); ok { + + if channel, ok := value[mapping].(map[string]interface{}); ok { + + // Kanallogo aktualisieren + if logo, ok := channel["icon"].(string); ok { + + if xepgChannel.XUpdateChannelIcon == true && len(logo) > 0 { + xepgChannel.TvgLogo = logo + } + + } + + } else { + + ShowError(fmt.Errorf(fmt.Sprintf("Missing EPG data: %s", xepgChannel.Name)), 0) + showWarning(2302) + xepgChannel.XActive = false + + } + + } else { + + var fileID = strings.TrimSuffix(getFilenameFromPath(file), path.Ext(getFilenameFromPath(file))) + + ShowError(fmt.Errorf("Missing XMLTV file: %s", getProviderParameter(fileID, "xmltv", "name")), 0) + showWarning(2301) + xepgChannel.XActive = false + + } + + } + + if len(xepgChannel.XmltvFile) == 0 { + xepgChannel.XmltvFile = "-" + xepgChannel.XActive = false + } + + if len(xepgChannel.XMapping) == 0 { + xepgChannel.XMapping = "-" + xepgChannel.XActive = false + } + + Data.XEPG.Channels[xepg] = xepgChannel + + } + + } + + err = saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels) + if err != nil { + return + } + + return +} + +// XMLTV Datei erstellen +func createXMLTVFile() (err error) { + + Data.Cache.ImagesFiles = []string{} + Data.Cache.ImagesURLS = []string{} + Data.Cache.ImagesCache = []string{} + + files, err := ioutil.ReadDir(System.Folder.ImagesCache) + if err == nil { + + for _, file := range files { + + if indexOfString(file.Name(), Data.Cache.ImagesCache) == -1 { + Data.Cache.ImagesCache = append(Data.Cache.ImagesCache, file.Name()) + } + + } + + } + + if len(Data.XMLTV.Files) == 0 && len(Data.Streams.Active) == 0 { + Data.XEPG.Channels = make(map[string]interface{}) + return + } + + showInfo("XEPG:" + fmt.Sprintf("Create XMLTV file (%s)", System.File.XML)) + + var xepgXML XMLTV + + xepgXML.Generator = System.Name + xepgXML.Source = fmt.Sprintf("%s - %s", System.Name, System.Version) + + var tmpProgram = &XMLTV{} + + for _, dxc := range Data.XEPG.Channels { + + var xepgChannel XEPGChannelStruct + err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel) + if err == nil { + + if xepgChannel.XActive == true { + + // Kanäle + var channel Channel + channel.ID = xepgChannel.XChannelID + channel.Icon = Icon{Src: getCacheImageURL(xepgChannel.TvgLogo)} + channel.DisplayName = append(channel.DisplayName, DisplayName{Value: xepgChannel.XName}) + + xepgXML.Channel = append(xepgXML.Channel, &channel) + + // Programme + + *tmpProgram, err = getProgramData(xepgChannel) + if err == nil { + + for _, program := range tmpProgram.Program { + xepgXML.Program = append(xepgXML.Program, program) + } + + } + + } + + } + + } + + var content, _ = xml.MarshalIndent(xepgXML, " ", " ") + var xmlOutput = []byte(xml.Header + string(content)) + writeByteToFile(System.File.XML, xmlOutput) + + xepgXML = XMLTV{} + + //saveMapToJSONFile(System.File.Images, Data.Cache.ImageCache) + + return +} + +// Programmdaten erstellen (createXMLTVFile) +func getProgramData(xepgChannel XEPGChannelStruct) (xepgXML XMLTV, err error) { + + var xmltvFile = System.Folder.Data + xepgChannel.XmltvFile + var channelID = xepgChannel.XMapping + + var xmltv XMLTV + + if xmltvFile == System.Folder.Data+"xTeVe Dummy" { + xmltv = createDummyProgram(xepgChannel) + } else { + + err = getLocalXMLTV(xmltvFile, &xmltv) + if err != nil { + return + } + + } + + for _, xmltvProgram := range xmltv.Program { + + if xmltvProgram.Channel == channelID { + //fmt.Println(&channelID) + var program = &Program{} + + // Channel ID + program.Channel = xepgChannel.XChannelID + program.Start = xmltvProgram.Start + program.Stop = xmltvProgram.Stop + + // Title + program.Title = xmltvProgram.Title + + // Sub title (Untertitel) + program.SubTitle = xmltvProgram.SubTitle + + // Description (Beschreibung) + program.Desc = xmltvProgram.Desc + + // Category (Kategorie) + getCategory(program, xmltvProgram, xepgChannel) + + // Country (Länder) + program.Country = xmltvProgram.Country + + // Program icon (Poster / Cover) + getPoster(program, xmltvProgram, xepgChannel) + + // Language (Sprache) + program.Language = xmltvProgram.Language + + // Episodes numbers (Episodennummern) + getEpisodeNum(program, xmltvProgram, xepgChannel) + + // Video (Videoparameter) + getVideo(program, xmltvProgram, xepgChannel) + + // Date (Datum) + program.Date = xmltvProgram.Date + + // Previously shown (Wiederholung) + program.PreviouslyShown = xmltvProgram.PreviouslyShown + + // New (Neu) + program.New = xmltvProgram.New + + // Live + program.Live = xmltvProgram.Live + + xepgXML.Program = append(xepgXML.Program, program) + + } + + } + + return +} + +// Dummy Daten erstellen (createXMLTVFile) +func createDummyProgram(xepgChannel XEPGChannelStruct) (dummyXMLTV XMLTV) { + + var currentTime = time.Now() + var dateArray = strings.Fields(currentTime.String()) + var offset = " " + dateArray[2] + var currentDay = currentTime.Format("20060102") + var startTime, _ = time.Parse("20060102150405", currentDay+"000000") + + showInfo("Create Dummy Guide:" + "Time offset" + offset + " - " + xepgChannel.XName) + + var dl = strings.Split(xepgChannel.XMapping, "_") + dummyLength, err := strconv.Atoi(dl[0]) + if err != nil { + ShowError(err, 000) + return + } + + for d := 0; d < 4; d++ { + + var epgStartTime = startTime.Add(time.Hour * time.Duration(d*24)) + + for t := dummyLength; t <= 1440; t = t + dummyLength { + + var epgStopTime = epgStartTime.Add(time.Minute * time.Duration(dummyLength)) + + var epg Program + poster := Poster{} + + epg.Channel = xepgChannel.XMapping + epg.Start = epgStartTime.Format("20060102150405") + offset + epg.Stop = epgStopTime.Format("20060102150405") + offset + epg.Title = append(epg.Title, &Title{Value: xepgChannel.XName + " (" + epgStartTime.Weekday().String()[0:2] + ". " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04") + ")", Lang: "en"}) + epg.Desc = append(epg.Desc, &Desc{Value: "xTeVe: (" + strconv.Itoa(dummyLength) + " Minutes) " + epgStartTime.Weekday().String() + " " + epgStartTime.Format("15:04") + " - " + epgStopTime.Format("15:04"), Lang: "en"}) + + if Settings.XepgReplaceMissingImages == true { + poster.Src = getCacheImageURL(xepgChannel.TvgLogo) + epg.Poster = append(epg.Poster, poster) + } + + epg.EpisodeNum = append(epg.EpisodeNum, &EpisodeNum{Value: epgStartTime.Format("2006-01-02 15:04:05"), System: "original-air-date"}) + + epg.New = &New{Value: ""} + + dummyXMLTV.Program = append(dummyXMLTV.Program, &epg) + epgStartTime = epgStopTime + + } + + } + + return +} + +// Kategorien erweitern (createXMLTVFile) +func getCategory(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) { + + for _, i := range xmltvProgram.Category { + + category := &Category{} + category.Value = i.Value + category.Lang = i.Lang + program.Category = append(program.Category, category) + + } + + if len(xepgChannel.XCategory) > 0 { + + category := &Category{} + category.Value = xepgChannel.XCategory + category.Lang = "en" + program.Category = append(program.Category, category) + + } + + return +} + +// Programm Poster Cover aus der XMLTV Datei laden +func getPoster(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) { + + for _, poster := range xmltvProgram.Poster { + poster.Src = getCacheImageURL(poster.Src) + program.Poster = append(program.Poster, poster) + } + + if Settings.XepgReplaceMissingImages == true { + + if len(xmltvProgram.Poster) == 0 { + var poster Poster + poster.Src = getCacheImageURL(xepgChannel.TvgLogo) + program.Poster = append(program.Poster, poster) + } + + } + +} + +// Episodensystem übernehmen, falls keins vorhanden ist und eine Kategorie im Mapping eingestellt wurden, wird eine Episode erstellt +func getEpisodeNum(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) { + + program.EpisodeNum = xmltvProgram.EpisodeNum + + if len(xepgChannel.XCategory) > 0 { + + if len(xmltvProgram.EpisodeNum) == 0 { + program.EpisodeNum = append(program.EpisodeNum, &EpisodeNum{Value: time.Now().Format("2006-01-02"), System: "original-air-date"}) + } + + } + + return +} + +// Videoparameter erstellen (createXMLTVFile) +func getVideo(program *Program, xmltvProgram *Program, xepgChannel XEPGChannelStruct) { + + var video Video + video.Present = xmltvProgram.Video.Present + video.Colour = xmltvProgram.Video.Colour + video.Aspect = xmltvProgram.Video.Aspect + video.Quality = xmltvProgram.Video.Quality + + if len(xmltvProgram.Video.Quality) == 0 { + + if strings.Contains(strings.ToUpper(xepgChannel.XName), " HD") || strings.Contains(strings.ToUpper(xepgChannel.XName), " FHD") { + video.Quality = "HDTV" + } + + if strings.Contains(strings.ToUpper(xepgChannel.XName), " UHD") || strings.Contains(strings.ToUpper(xepgChannel.XName), " 4K") { + video.Quality = "UHDTV" + } + + } + + program.Video = video + + return +} + +// Lokale Provider XMLTV Datei laden +func getLocalXMLTV(file string, xmltv *XMLTV) (err error) { + + if _, ok := Data.Cache.XMLTV[file]; !ok { + + // Cache initialisieren + if len(Data.Cache.XMLTV) == 0 { + Data.Cache.XMLTV = make(map[string]XMLTV) + } + + // XML Daten lesen + content, err := readByteFromFile(file) + + // Lokale XML Datei existiert nicht im Ordner: data + if err != nil { + ShowError(err, 1004) + err = errors.New("Local copy of the file no longer exists") + return err + } + + // XML Datei parsen + err = xml.Unmarshal(content, &xmltv) + if err != nil { + return err + } + + Data.Cache.XMLTV[file] = *xmltv + + } else { + *xmltv = Data.Cache.XMLTV[file] + } + + return +} + +// M3U Datei erstellen +func createM3UFile() { + + showInfo("XEPG:" + fmt.Sprintf("Create M3U file (%s)", System.File.M3U)) + _, err := buildM3U([]string{}) + if err != nil { + ShowError(err, 000) + } + + saveMapToJSONFile(System.File.URLS, Data.Cache.StreamingURLS) + + return +} + +// XEPG Datenbank bereinigen +func cleanupXEPG() { + + showInfo("XEPG:" + fmt.Sprintf("Cleanup database")) + Data.XEPG.XEPGCount = 0 + + for id, dxc := range Data.XEPG.Channels { + + var xepgChannel XEPGChannelStruct + err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel) + if err == nil { + + if indexOfString(xepgChannel.Name, Data.Cache.Streams.Active) == -1 { + delete(Data.XEPG.Channels, id) + } else { + if xepgChannel.XActive == true { + Data.XEPG.XEPGCount++ + } + } + + } + + } + + err := saveMapToJSONFile(System.File.XEPG, Data.XEPG.Channels) + if err != nil { + ShowError(err, 000) + return + } + + showInfo("XEPG Channels:" + fmt.Sprintf("%d", Data.XEPG.XEPGCount)) + + if len(Data.Streams.Active) > 0 && Data.XEPG.XEPGCount == 0 { + showWarning(2005) + } + + return +} + +// Streaming URL für die Channels App generieren +func getStreamByChannelID(channelID string) (playlistID, streamURL string, err error) { + + err = errors.New("Channel not found") + + for _, dxc := range Data.XEPG.Channels { + + var xepgChannel XEPGChannelStruct + err := json.Unmarshal([]byte(mapToJSON(dxc)), &xepgChannel) + + fmt.Println(xepgChannel.XChannelID) + + if err == nil { + + if channelID == xepgChannel.XChannelID { + + playlistID = xepgChannel.FileM3UID + streamURL = xepgChannel.URL + + return playlistID, streamURL, nil + } + + } + + } + + return +} diff --git a/ts/authentication_ts.ts b/ts/authentication_ts.ts new file mode 100644 index 0000000..133b71a --- /dev/null +++ b/ts/authentication_ts.ts @@ -0,0 +1,47 @@ +function login() { + var err:Boolean = false + var data = new Object() + var div:any = document.getElementById("content") + var form:any = document.getElementById("authentication") + + var inputs:any = div.getElementsByTagName("INPUT") + + console.log(inputs) + + for (var i = inputs.length - 1; i >= 0; i--) { + + var key:string = (inputs[i] as HTMLInputElement).name + var value:string = (inputs[i] as HTMLInputElement).value + + if (value.length == 0) { + inputs[i].style.borderColor = "red" + err = true + } + + data[key] = value + + } + + if (err == true) { + data = new Object() + return + } + + if (data.hasOwnProperty("confirm")) { + + if (data["confirm"] != data["password"]) { + alert("sdafsd") + document.getElementById('password').style.borderColor = "red" + document.getElementById('confirm').style.borderColor = "red" + + document.getElementById("err").innerHTML = "{{.account.failed}}" + return + } + + } + + console.log(data) + + form.submit(); + +} \ No newline at end of file diff --git a/ts/base_ts.ts b/ts/base_ts.ts new file mode 100644 index 0000000..b215839 --- /dev/null +++ b/ts/base_ts.ts @@ -0,0 +1,663 @@ +var SERVER = new Object() +var BULK_EDIT:Boolean = false +var COLUMN_TO_SORT:number +var SEARCH_MAPPING = new Object() +var UNDO = new Object() +var SERVER_CONNECTION = false +var WS_AVAILABLE = false + + +// Menü +var menuItems = new Array() +menuItems.push(new MainMenuItem("playlist", "{{.mainMenu.item.playlist}}", "m3u.png", "{{.mainMenu.headline.playlist}}")) +//menuItems.push(new MainMenuItem("pmsID", "{{.mainMenu.item.pmsID}}", "number.png", "{{.mainMenu.headline.pmsID}}")) +menuItems.push(new MainMenuItem("filter", "{{.mainMenu.item.filter}}", "filter.png", "{{.mainMenu.headline.filter}}")) +menuItems.push(new MainMenuItem("xmltv", "{{.mainMenu.item.xmltv}}", "xmltv.png", "{{.mainMenu.headline.xmltv}}")) +menuItems.push(new MainMenuItem("mapping", "{{.mainMenu.item.mapping}}", "mapping.png", "{{.mainMenu.headline.mapping}}")) +menuItems.push(new MainMenuItem("users", "{{.mainMenu.item.users}}", "users.png", "{{.mainMenu.headline.users}}")) +menuItems.push(new MainMenuItem("settings", "{{.mainMenu.item.settings}}", "settings.png", "{{.mainMenu.headline.settings}}")) +menuItems.push(new MainMenuItem("log", "{{.mainMenu.item.log}}", "log.png", "{{.mainMenu.headline.log}}")) +menuItems.push(new MainMenuItem("logout", "{{.mainMenu.item.logout}}", "logout.png", "{{.mainMenu.headline.logout}}")) + +// Kategorien für die Einstellungen +var settingsCategory = new Array() +settingsCategory.push(new SettingsCategoryItem("{{.settings.category.general}}", "xteveAutoUpdate,tuner,epgSource,api")) +settingsCategory.push(new SettingsCategoryItem("{{.settings.category.files}}", "update,files.update,temp.path,cache.images,xepg.replace.missing.images")) +settingsCategory.push(new SettingsCategoryItem("{{.settings.category.streaming}}", "buffer,buffer.size.kb,buffer.timeout,user.agent")) +settingsCategory.push(new SettingsCategoryItem("{{.settings.category.backup}}", "backup.path,backup.keep")) +settingsCategory.push(new SettingsCategoryItem("{{.settings.category.authentication}}", "authentication.web,authentication.pms,authentication.m3u,authentication.xml,authentication.api")) + +function showPopUpElement(elm) { + + var allElements = new Array("popup-custom"); + + for (var i = 0; i < allElements.length; i++) { + showElement(allElements[i], false) + } + + showElement(elm, true) + + setTimeout(function(){ + showElement("popup", true); + }, 10); + + return +} + +function showElement(elmID, type) { + + var cssClass:string + switch(type) { + case true: cssClass = "block"; break; + case false: cssClass = "none"; break; + } + + document.getElementById(elmID).className = cssClass; +} + +function changeButtonAction(element, buttonID, attribute) { + var value = element.options[element.selectedIndex].value; + document.getElementById(buttonID).setAttribute(attribute, value) +} + +function getLocalData(dataType, id):object { + var data = new Object() + switch(dataType) { + case "m3u": + data = SERVER["settings"]["files"][dataType][id] + break + + case "hdhr": + data = SERVER["settings"]["files"][dataType][id] + break + + case "filter": + case "custom-filter": + case "group-title": + if (id == -1) { + data["active"] = true + data["caseSensitive"] = false + data["description"] = "" + data["exclude"] = "" + data["filter"] = "" + data["include"] = "" + data["name"] = "" + data["type"] = "group-title" + SERVER["settings"]["filter"][id] = data + } + data = SERVER["settings"]["filter"][id] + break + + case "xmltv": + data = SERVER["settings"]["files"][dataType][id] + break + + case "users": + data = SERVER["users"][id]["data"] + break + + case "mapping": + data = SERVER["xepg"]["epgMapping"][id] + break + + case "m3uGroups": + data = SERVER["data"]["playlist"]["m3u"]["groups"] + break + } + + return data +} + +function getObjKeys(obj) { + var keys = new Array(); + + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + keys.push(i); + } + } + + return keys; +} + +function getAllSelectedChannels():string[] { + + var channels:string[] = new Array() + + if (BULK_EDIT == false) { + return channels + } + + var trs = document.getElementById("content_table").getElementsByTagName("TR") + + for (var i = 1; i < trs.length; i++) { + + if ((trs[i] as HTMLElement).style.display != "none") { + + if ((trs[i].firstChild.firstChild as HTMLInputElement).checked == true) { + channels.push(trs[i].id) + } + + } + + } + + return channels +} + +function selectAllChannels() { + + var bulk:Boolean = false + var trs = document.getElementById("content_table").getElementsByTagName("TR") + + if ((trs[0].firstChild.firstChild as HTMLInputElement).checked == true) { + bulk = true + } + + for (var i = 1; i < trs.length; i++) { + + if ((trs[i] as HTMLElement).style.display != "none") { + + switch (bulk) { + + case true: + (trs[i].firstChild.firstChild as HTMLInputElement).checked = true + break + + case false: + (trs[i].firstChild.firstChild as HTMLInputElement).checked = false + break + + } + + } + + } + + return +} + +function bulkEdit() { + + BULK_EDIT = !BULK_EDIT + var className:string + var rows = document.getElementsByClassName("bulk"); + + switch (BULK_EDIT) { + case true: + className = "bulk showBulk" + break; + + case false: + className = "bulk hideBulk" + break; + } + + for (var i = 0; i < rows.length; i++) { + rows[i].className = className; + (rows[i] as HTMLInputElement).checked = false + } + + return +} + +function sortTable(column) { + //console.log(columm); + + if (column == COLUMN_TO_SORT) { + return; + } + + + var table = document.getElementById("content_table"); + var tableHead = table.getElementsByTagName("TR")[0]; + var tableItems = tableHead.getElementsByTagName("TD"); + + var sortObj = new Object(); + var x, xValue; + var tableHeader + var sortByString = false + + if (column > 0 && COLUMN_TO_SORT > 0) { + tableItems[COLUMN_TO_SORT].className = "pointer"; + tableItems[column].className = "sortThis"; + } + + COLUMN_TO_SORT = column; + + + var rows = (table as HTMLTableElement).rows; + + if (rows[1] != undefined) { + tableHeader = rows[0] + + x = rows[1].getElementsByTagName("TD")[column]; + + for (i = 1; i < rows.length; i++) { + + x = rows[i].getElementsByTagName("TD")[column]; + + switch(x.childNodes[0].tagName.toLowerCase()) { + case "input": + xValue = x.getElementsByTagName("INPUT")[0].value.toLowerCase(); + break; + + case "p": + xValue = x.getElementsByTagName("P")[0].innerText.toLowerCase(); + break; + + default: console.log(x.childNodes[0].tagName); + } + + if (xValue == "" || xValue == NaN) { + + xValue = i + sortObj[i] = rows[i]; + + } else { + + switch(isNaN(xValue)) { + case false: + + xValue = parseFloat(xValue); + sortObj[xValue] = rows[i] + break; + + case true: + + sortByString = true + sortObj[xValue.toLowerCase() + i] = rows[i] + break; + + } + + } + + } + + while (table.firstChild) { + table.removeChild(table.firstChild); + } + + var sortValues = getObjKeys(sortObj) + + if (sortByString == true) { + sortValues.sort() + console.log(sortValues); + } else { + function sortFloat(a, b) { + return a - b; + } + sortValues.sort(sortFloat); + } + + table.appendChild(tableHeader) + + for (var i = 0; i < sortValues.length; i++) { + + table.appendChild(sortObj[sortValues[i]]) + + } + + } + + return +} + +function createSearchObj() { + + SEARCH_MAPPING = new Object() + var data = SERVER["xepg"]["epgMapping"] + var channels = getObjKeys(data) + + var channelKeys:string[] = ["x-active", "x-channelID", "x-name", "_file.m3u.name", "x-group-title"] + + channels.forEach(id => { + + channelKeys.forEach(key => { + + if (key == "x-active") { + + switch (data[id][key]) { + case true: + SEARCH_MAPPING[id] = "online " + break; + + case false: + SEARCH_MAPPING[id] = "offline " + break; + + } + + } else { + + SEARCH_MAPPING[id] = SEARCH_MAPPING[id] + data[id][key] + " " + + } + + }) + + }) + + return +} + +function searchInMapping() { + + var searchValue = (document.getElementById("searchMapping") as HTMLInputElement).value; + var trs = document.getElementById("content_table").getElementsByTagName("TR") + + for (var i = 1; i < trs.length; ++i) { + + var id = trs[i].getAttribute("id") + var element = SEARCH_MAPPING[id] + + switch (element.toLowerCase().includes(searchValue.toLowerCase())) { + case true: + document.getElementById(id).style.display = "" + break; + + case false: + document.getElementById(id).style.display = "none" + break; + } + + + } + + return +} + +function calculateWrapperHeight() { + + if (document.getElementById("box-wrapper")){ + + var elm = document.getElementById("box-wrapper"); + + var divs = new Array("myStreamsBox", "clientInfo", "content"); + var elementsHeight = 0 - elm.offsetHeight; + for (var i = 0; i < divs.length; i++) { + elementsHeight = elementsHeight + document.getElementById(divs[i]).offsetHeight; + } + + elm.style.height = window.innerHeight - elementsHeight + "px"; + + } + + return +} + +function changeChannelNumber(element) { + + var dbID = element.parentNode.parentNode.id + + var newNumber:number = parseFloat(element.value) + var channelNumbers:number[] = [] + var data = SERVER["xepg"]["epgMapping"] + var channels = getObjKeys(data) + + if (isNaN(newNumber)) { + alert("{{.alert.invalidChannelNumber}}") + return + } + + channels.forEach(id => { + + var channelNumber = parseFloat(data[id]["x-channelID"]) + channelNumbers.push(channelNumber) + + }) + + for (var i = 0; i < channelNumbers.length; i++) { + + if (channelNumbers.indexOf(newNumber) == -1) { + break + } + + if (Math.floor(newNumber) == newNumber) { + newNumber = newNumber + 1 + } else { + newNumber = newNumber + 0.1; + newNumber.toFixed(1) + newNumber = Math.round(newNumber * 10) / 10 + } + + } + + data[dbID]["x-channelID"] = newNumber.toString() + element.value = newNumber + + console.log(data[dbID]["x-channelID"]) + + if (COLUMN_TO_SORT == 1) { + COLUMN_TO_SORT = -1 + sortTable(1) + } + + return +} + +function backup() { + + var data = new Object() + console.log("Backup data") + + var cmd = "xteveBackup" + + console.log("SEND TO SERVER"); + console.log(data) + + var server:Server = new Server(cmd) + server.request(data) + + return +} + +function toggleChannelStatus(id:string) { + + var element:any + var status:boolean + + if(document.getElementById("active")) { + var checkbox = (document.getElementById("active") as HTMLInputElement) + status = (checkbox).checked + } + + + var ids:string[] = getAllSelectedChannels() + if (ids.length == 0) { + ids.push(id) + } + + ids.forEach(id => { + + var channel = SERVER["xepg"]["epgMapping"][id] + + channel["x-active"] = status + + switch (channel["x-active"]) { + case true: + if (channel["x-xmltv-file"] == "-" || channel["x-mapping"] == "-") { + + if (BULK_EDIT == false) { + alert(channel["x-name"] + ": Missing XMLTV file / channel") + checkbox.checked = false + } + + channel["x-active"] = false + + } + + break + + case false: + // code... + break; + } + + if (channel["x-active"] == false) { + document.getElementById(id).className = "notActiveEPG" + } else { + document.getElementById(id).className = "activeEPG" + } + + }); + +} + +function restore() { + + if (document.getElementById('upload')) { + document.getElementById('upload').remove() + } + + var restore = document.createElement("INPUT"); + restore.setAttribute("type", "file"); + restore.setAttribute("class", "notVisible"); + restore.setAttribute("name", ""); + restore.id = "upload"; + + document.body.appendChild(restore); + restore.click(); + + restore.onchange = function() { + + var filename = (restore as HTMLInputElement).files[0].name + var check = confirm("File: " + filename + "\n{{.confirm.restore}}"); + + if (check == true) { + + var reader = new FileReader(); + var file = (document.querySelector('input[type=file]') as HTMLInputElement).files[0]; + + if (file) { + + reader.readAsDataURL(file); + reader.onload = function() { + console.log(reader.result); + var data = new Object(); + var cmd = "xteveRestore" + data["base64"] = reader.result + + var server:Server = new Server(cmd) + server.request(data) + + }; + + } else { + alert("File could not be loaded") + } + + restore.remove() + return + } + + } + + return +} + +function uploadLogo() { + + if (document.getElementById('upload')) { + document.getElementById('upload').remove() + } + + var upload = document.createElement("INPUT"); + upload.setAttribute("type", "file"); + upload.setAttribute("class", "notVisible"); + upload.setAttribute("name", ""); + upload.id = "upload"; + + document.body.appendChild(upload); + upload.click(); + + upload.onblur = function() { + alert() + } + + upload.onchange = function() { + + var filename = (upload as HTMLInputElement).files[0].name + + var reader = new FileReader(); + var file = (document.querySelector('input[type=file]') as HTMLInputElement).files[0]; + + if (file) { + + reader.readAsDataURL(file); + reader.onload = function() { + console.log(reader.result); + var data = new Object(); + var cmd = "uploadLogo" + data["base64"] = reader.result + data["filename"] = file.name + + var server:Server = new Server(cmd) + server.request(data) + + var updateLogo = (document.getElementById('update-icon') as HTMLInputElement) + updateLogo.checked = false + updateLogo.className = "changed" + + }; + + } else { + alert("File could not be loaded") + } + + upload.remove() + return + } + +} + +function checkUndo(key:string) { + + switch (key) { + case "epgMapping": + if (UNDO.hasOwnProperty(key)) { + SERVER["xepg"][key] = JSON.parse(JSON.stringify(UNDO[key])) + } else { + UNDO[key] = JSON.parse(JSON.stringify(SERVER["xepg"][key])); + } + break; + + default: + + break; + } + + return +} + +function sortSelect(elem) { + + var tmpAry = []; + var selectedValue = elem[elem.selectedIndex].value; + + for (var i=0;i 0) elem.options[0] = null; + + var newSelectedIndex = 0; + + for (var i=0;i { + + var entry = log.createLog(logs[logID]) + + div.append(entry) + + }); + + setTimeout(function(){ + + if (bottom == true) { + + var wrapper = document.getElementById("box-wrapper"); + wrapper.scrollTop = wrapper.scrollHeight; + + } + + }, 10); + +} + +function resetLogs() { + + var cmd = "resetLogs" + var data = new Object() + var server:Server = new Server(cmd) + server.request(data) + +} \ No newline at end of file diff --git a/ts/menu_ts.ts b/ts/menu_ts.ts new file mode 100644 index 0000000..486ac15 --- /dev/null +++ b/ts/menu_ts.ts @@ -0,0 +1,2198 @@ + +class MainMenu { + DocumentID:string = "main-menu" + HTMLTag:string = "LI" + ImagePath:string = "img/" + + createIMG(src):any { + var element = document.createElement("IMG") + element.setAttribute("src", this.ImagePath + src) + return element + } + + createValue(value):any { + var element = document.createElement("P") + element.innerHTML = value + return element + } +} + +class MainMenuItem extends MainMenu { + menuKey:string + value:string + imgSrc:string + headline:string + id:string + tableHeader:string[] + + constructor(menuKey:string, value:string, image:string, headline:string) { + super() + this.menuKey = menuKey + this.value = value + this.imgSrc = image + this.headline = headline + } + + createItem():void { + var item = document.createElement("LI") + item.setAttribute("onclick", "javascript: openThisMenu(this)") + item.setAttribute("id", this.id) + var img = this.createIMG(this.imgSrc) + var value = this.createValue(this.value) + + item.appendChild(img) + item.appendChild(value) + + var doc = document.getElementById(this.DocumentID) + doc.appendChild(item) + + switch(this.menuKey) { + case "playlist": + this.tableHeader = ["{{.playlist.table.playlist}}", "{{.playlist.table.tuner}}", "{{.playlist.table.lastUpdate}}", "{{.playlist.table.availability}} %", "{{.playlist.table.type}}", "{{.playlist.table.streams}}", "{{.playlist.table.groupTitle}} %", "{{.playlist.table.tvgID}} %", "{{.playlist.table.uniqueID}} %"] + break + + case "xmltv": + this.tableHeader = ["{{.xmltv.table.guide}}", "{{.xmltv.table.lastUpdate}}", "{{.xmltv.table.availability}} %", "{{.xmltv.table.channels}}", "{{.xmltv.table.programs}}"] + break + + case "filter": + this.tableHeader = ["{{.filter.table.name}}", "{{.filter.table.type}}", "{{.filter.table.filter}}"] + break + + case "users": + this.tableHeader = ["{{.users.table.username}}", "{{.users.table.password}}", "{{.users.table.web}}", "{{.users.table.pms}}", "{{.users.table.m3u}}", "{{.users.table.xml}}", "{{.users.table.api}}"] + break + + case "mapping": + this.tableHeader = ["BULK", "{{.mapping.table.chNo}}", "{{.mapping.table.logo}}", "{{.mapping.table.channelName}}", "{{.mapping.table.playlist}}", "{{.mapping.table.groupTitle}}", "{{.mapping.table.xmltvFile}}", "{{.mapping.table.xmltvID}}"] + break + + } + + //console.log(this.menuKey, this.tableHeader); + + } +} + +class Content { + + DocumentID:string = "content" + TableID:string = "content_table" + DivID:string + headerClass:string = "content_table_header" + interactionID:string = "content-interaction" + + createHeadline(value):any { + var element = document.createElement("H3") + element.innerHTML = value + return element + } + + createHR():any { + var element = document.createElement("HR") + return element + } + + createInteraction():any { + var element = document.createElement("DIV") + element.setAttribute("id", this.interactionID) + return element + } + + createDIV():any { + var element = document.createElement("DIV") + element.id = this.DivID + return element + } + + createTABLE():any { + var element = document.createElement("TABLE") + element.id = this.TableID + return element + } + + createTableRow():any { + var element = document.createElement("TR") + element.className = this.headerClass + return element + } + + createTableContent(menuKey:string):string[] { + + var data = new Object() + var rows = new Array() + + switch(menuKey) { + case "playlist": + var fileTypes = new Array("m3u", "hdhr") + + fileTypes.forEach(fileType => { + + data = SERVER["settings"]["files"][fileType] + + var keys = getObjKeys(data) + + keys.forEach(key => { + var tr = document.createElement("TR") + tr.id = key + + tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)') + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["name"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + if (SERVER["settings"]["buffer"] == true) { + cell.value = data[key]["tuner"] + } else { + cell.value = "-" + } + + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["last.update"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["provider.availability"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["type"].toUpperCase(); + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["compatibility"]["streams"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["compatibility"]["group.title"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["compatibility"]["tvg.id"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["compatibility"]["stream.id"] + tr.appendChild(cell.createCell()) + + rows.push(tr) + }); + + }); + break + + case "filter": + delete SERVER["settings"]["filter"][-1] + data = SERVER["settings"]["filter"] + var keys = getObjKeys(data) + keys.forEach(key => { + var tr = document.createElement("TR") + tr.id = key + + tr.setAttribute('onclick', 'javascript: openPopUp("' + data[key]["type"] + '", this)') + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["name"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + switch (data[key]["type"]) { + case "custom-filter": + cell.value = "{{.filter.custom}}" + break; + + case "group-title": + cell.value = "{{.filter.group}}" + break; + + default: + break; + } + + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["filter"] + tr.appendChild(cell.createCell()) + + rows.push(tr) + + }); + break + + case "xmltv": + var fileTypes = new Array("xmltv") + + fileTypes.forEach(fileType => { + + data = SERVER["settings"]["files"][fileType] + + var keys = getObjKeys(data) + + keys.forEach(key => { + var tr = document.createElement("TR") + + tr.id = key + tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)') + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["name"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["last.update"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["provider.availability"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["compatibility"]["xmltv.channels"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["compatibility"]["xmltv.programs"] + tr.appendChild(cell.createCell()) + + rows.push(tr) + }); + + }); + break + + case "users": + var fileTypes = new Array("users") + + fileTypes.forEach(fileType => { + data = SERVER[fileType] + + var keys = getObjKeys(data) + + keys.forEach(key => { + var tr = document.createElement("TR") + tr.id = key + tr.setAttribute('onclick', 'javascript: openPopUp("' + fileType + '", this)') + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["data"]["username"] + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = "******" + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + if (data[key]["data"]["authentication.web"] == true) { + cell.value = "✓" + } else { + cell.value = "-" + } + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + if (data[key]["data"]["authentication.pms"] == true) { + cell.value = "✓" + } else { + cell.value = "-" + } + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + if (data[key]["data"]["authentication.m3u"] == true) { + cell.value = "✓" + } else { + cell.value = "-" + } + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + if (data[key]["data"]["authentication.xml"] == true) { + cell.value = "✓" + } else { + cell.value = "-" + } + tr.appendChild(cell.createCell()) + + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + if (data[key]["data"]["authentication.api"] == true) { + cell.value = "✓" + } else { + cell.value = "-" + } + tr.appendChild(cell.createCell()) + + rows.push(tr) + }); + + }); + break + + case "mapping": + BULK_EDIT = false + createSearchObj() + checkUndo("epgMapping") + console.log("MAPPING") + data = SERVER["xepg"]["epgMapping"] + + var keys = getObjKeys(data) + keys.forEach(key => { + var tr = document.createElement("TR") + tr.id = key + + //tr.setAttribute('oncontextmenu', 'javascript: rightClick(this)') + + switch (data[key]["x-active"]) { + case true: + tr.className = "activeEPG" + break; + + case false: + tr.className = "notActiveEPG" + break; + } + + // Bulk + var cell:Cell = new Cell() + cell.child = true + cell.childType = "BULK" + cell.value = false + tr.appendChild(cell.createCell()) + + // Kanalnummer + var cell:Cell = new Cell() + cell.child = true + cell.childType = "INPUTCHANNEL" + cell.value = data[key]["x-channelID"] + //td.setAttribute('onclick', 'javascript: changeChannelNumber("' + key + '", this)') + tr.appendChild(cell.createCell()) + + // Logo + var cell:Cell = new Cell() + cell.child = true + cell.childType = "IMG" + cell.imageURL = data[key]["tvg-logo"] + var td = cell.createCell() + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)') + td.id = key + + tr.appendChild(td) + + // Kanalname + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.className = data[key]["x-category"] + cell.value = data[key]["x-name"] + var td = cell.createCell() + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)') + td.id = key + tr.appendChild(td) + + + // Playlist + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + //cell.value = data[key]["_file.m3u.name"] + cell.value = getValueFromProviderFile(data[key]["_file.m3u.id"], "m3u", "name") + var td = cell.createCell() + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)') + td.id = key + tr.appendChild(td) + + + // Gruppe (group-title) + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = data[key]["x-group-title"] + var td = cell.createCell() + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)') + td.id = key + tr.appendChild(td) + + // XMLTV Datei + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + + if (data[key]["x-xmltv-file"] != "-") { + cell.value = getValueFromProviderFile(data[key]["x-xmltv-file"], "xmltv", "name") + } else { + cell.value = data[key]["x-xmltv-file"] + } + + var td = cell.createCell() + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)') + td.id = key + tr.appendChild(td) + + // XMLTV Kanal + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + //var value = str.substring(1, 4); + var value = data[key]["x-mapping"] + if (value.length > 20) { + value = data[key]["x-mapping"].substring(0, 20) + "..." + } + cell.value = value + var td = cell.createCell() + td.setAttribute('onclick', 'javascript: openPopUp("mapping", this)') + td.id = key + + tr.appendChild(td) + + rows.push(tr) + }); + + break + + case "settings": + alert() + break + + default: + console.log("Table content (menuKey):", menuKey); + + break + + } + + return rows + + } + + return +} + +class Cell { + child:Boolean + childType:string + value:any + className:string + tdClassName:string + imageURL:string + onclick:boolean + onclickFunktion:string + + createCell():any { + var td = document.createElement("TD") + + + if (this.child == true) { + var element:any + + switch(this.childType){ + case "P": + element = document.createElement(this.childType); + element.innerHTML = this.value + element.className = this.className + break + + case "INPUT": + element = document.createElement(this.childType); + (element as HTMLInputElement).value = this.value; + (element as HTMLInputElement).type = "text"; + break + + case "INPUTCHANNEL": + element = document.createElement("INPUT"); + (element as HTMLInputElement).setAttribute("onchange", "javscript: changeChannelNumber(this)"); + (element as HTMLInputElement).value = this.value; + (element as HTMLInputElement).type = "text"; + break + + case "BULK": + element = document.createElement("INPUT"); + (element as HTMLInputElement).checked = this.value; + (element as HTMLInputElement).type = "checkbox"; + (element as HTMLInputElement).className = "bulk hideBulk"; + break + + case "BULK_HEAD": + element = document.createElement("INPUT"); + (element as HTMLInputElement).checked = this.value; + (element as HTMLInputElement).type = "checkbox"; + (element as HTMLInputElement).className = "bulk hideBulk"; + (element as HTMLInputElement).setAttribute("onclick", "javascript: selectAllChannels()") + break + + case "IMG": + element = document.createElement(this.childType); + element.setAttribute("src", this.imageURL) + if (this.imageURL != "") { + element.setAttribute("onerror", "javascript: this.onerror=null;this.src=''" ) + //onerror="this.onerror=null;this.src='missing.gif';" + } + } + + td.appendChild(element) + + } else { + td.innerHTML = this.value + } + + if (this.onclick == true) { + td.setAttribute("onclick", this.onclickFunktion) + td.className = "pointer" + } + + if (this.tdClassName != undefined) { + td.className = this.tdClassName + } + + return td + } + + return +} + +class ShowContent extends Content { + menuID:number + + constructor(menuID:number) { + super() + this.menuID = menuID + } + + createInput(type:string, name:string, value:string,):any { + + var input = document.createElement("INPUT") + input.setAttribute("type", type) + input.setAttribute("name", name) + input.setAttribute("value", value) + return input + } + + show():void { + COLUMN_TO_SORT = -1 + // Alten Inhalt löschen + var doc = document.getElementById(this.DocumentID) + doc.innerHTML = "" + showPreview(false) + + // Überschrift + var headline:string[] = menuItems[this.menuID].headline + + var menuKey = menuItems[this.menuID].menuKey + var h = this.createHeadline(headline) + doc.appendChild(h) + + var hr = this.createHR() + doc.appendChild(hr) + + // Interaktion + var div =this.createInteraction() + doc.appendChild(div) + var interaction = document.getElementById(this.interactionID) + switch (menuKey) { + case "playlist": + var input = this.createInput("button", menuKey, "{{.button.new}}") + input.setAttribute("id", "-") + input.setAttribute("onclick", 'javascript: openPopUp("playlist")') + interaction.appendChild(input) + break; + + case "filter": + var input = this.createInput("button", menuKey, "{{.button.new}}") + input.setAttribute("id", -1) + input.setAttribute("onclick", 'javascript: openPopUp("filter", this)') + interaction.appendChild(input) + break; + + + case "xmltv": + var input = this.createInput("button", menuKey, "{{.button.new}}") + input.setAttribute("id", "xmltv") + input.setAttribute("onclick", 'javascript: openPopUp("xmltv")') + interaction.appendChild(input) + break; + + case "users": + var input = this.createInput("button", menuKey, "{{.button.new}}") + input.setAttribute("id", "users") + input.setAttribute("onclick", 'javascript: openPopUp("users")') + interaction.appendChild(input) + break; + + case "mapping": + showElement("loading", true) + var input = this.createInput("button", menuKey, "{{.button.save}}") + input.setAttribute("onclick", 'javascript: savePopupData("mapping", "", "")') + interaction.appendChild(input) + + var input = this.createInput("button", menuKey, "{{.button.bulkEdit}}") + input.setAttribute("onclick", 'javascript: bulkEdit()') + interaction.appendChild(input) + + var input = this.createInput("search", "search", "") + input.setAttribute("id", "searchMapping") + input.setAttribute("placeholder", "{{.button.search}}") + input.className = "search" + input.setAttribute("onchange", 'javascript: searchInMapping()') + interaction.appendChild(input) + break; + + case "settings": + var input = this.createInput("button", menuKey, "{{.button.save}}") + input.setAttribute("onclick", 'javascript: saveSettings();') + interaction.appendChild(input) + + var input = this.createInput("button", menuKey, "{{.button.backup}}") + input.setAttribute("onclick", 'javascript: backup();') + interaction.appendChild(input) + + var input = this.createInput("button", menuKey, "{{.button.restore}}") + input.setAttribute("onclick", 'javascript: restore();') + interaction.appendChild(input) + + var wrapper = document.createElement("DIV") + wrapper.setAttribute("id", "box-wrapper") + doc.appendChild(wrapper) + + this.DivID = "content_settings" + var settings = this.createDIV() + wrapper.appendChild(settings) + + showSettings() + + return + break + + case "log": + var input = this.createInput("button", menuKey, "{{.button.resetlogs}}") + input.setAttribute("onclick", 'javascript: resetLogs();') + interaction.appendChild(input) + + var wrapper = document.createElement("DIV") + wrapper.setAttribute("id", "box-wrapper") + doc.appendChild(wrapper) + + this.DivID = "content_log" + var logs = this.createDIV() + wrapper.appendChild(logs) + + showLogs(true) + + return + break + + case "logout": + location.reload() + document.cookie = "Token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT" + break + + default: + console.log("Show content (menuKey):", menuKey); + break; + } + + // Tabelle erstellen (falls benötigt) + var tableHeader:string[] = menuItems[this.menuID].tableHeader + if (tableHeader.length > 0) { + var wrapper = document.createElement("DIV") + doc.appendChild(wrapper) + wrapper.setAttribute("id", "box-wrapper") + + var table = this.createTABLE() + wrapper.appendChild(table) + + var header = this.createTableRow() + table.appendChild(header) + + // Kopfzeile der Tablle + tableHeader.forEach(element => { + var cell:Cell = new Cell() + cell.child = true + cell.childType = "P" + cell.value = element + if (element == "BULK") { + cell.childType = "BULK_HEAD"; + cell.value = false + } + + if (menuKey == "mapping") { + + if (element == "{{.mapping.table.chNo}}") { + cell.onclick = true + cell.onclickFunktion = "javascript: sortTable(1);" + cell.tdClassName = "sortThis" + } + + if (element == "{{.mapping.table.channelName}}") { + cell.onclick = true + cell.onclickFunktion = "javascript: sortTable(3);" + } + + if (element == "{{.mapping.table.playlist}}") { + cell.onclick = true + cell.onclickFunktion = "javascript: sortTable(4);" + } + + if (element == "{{.mapping.table.groupTitle}}") { + cell.onclick = true + cell.onclickFunktion = "javascript: sortTable(5);" + } + + } + + header.appendChild(cell.createCell()) + }); + + table.appendChild(header) + + // Inhalt der Tabelle + var rows:any = this.createTableContent(menuKey) + rows.forEach(tr => { + table.appendChild(tr) + }); + + } + + switch (menuKey) { + case "mapping": + sortTable(1) + break; + + case "filter": + showPreview(true) + sortTable(0) + break + + default: + COLUMN_TO_SORT = -1 + sortTable(0) + break; + } + + showElement("loading", false) + } + +} + +function PageReady() { + + var server:Server = new Server("getServerConfig") + server.request(new Object()) + + window.addEventListener("resize", function(){ + calculateWrapperHeight(); + }, true); + + setInterval(function(){ + updateLog() + }, 10000); + + + return +} + +function createLayout() { + + // Client Info + var obj = SERVER["clientInfo"] + var keys = getObjKeys(obj); + for (var i = 0; i < keys.length; i++) { + + if (document.getElementById(keys[i])) { + document.getElementById(keys[i]).innerHTML = obj[keys[i]]; + } + + } + + if (!document.getElementById("main-menu")) { + return + } + + + + // Menü erstellen + document.getElementById("main-menu").innerHTML = "" + for (let i = 0; i < menuItems.length; i++) { + + menuItems[i].id = i + + switch (menuItems[i]["menuKey"]) { + + case "users": + case "logout": + if (SERVER["settings"]["authentication.web"] == true) { + menuItems[i].createItem() + } + break + + case "mapping": + case "xmltv": + if (SERVER["clientInfo"]["epgSource"] == "XEPG") { + menuItems[i].createItem() + } + break + + default: + menuItems[i].createItem() + break + } + + } + + return +} + +function openThisMenu(element) { + var id = element.id + var content:ShowContent = new ShowContent(id) + content.show() + calculateWrapperHeight() + + return +} + +class PopupWindow { + DocumentID:string = "popup-custom" + InteractionID:string = "interaction" + doc = document.getElementById(this.DocumentID) + + createTitle(title:string):any { + var td = document.createElement("TD") + td.className = "left" + td.innerHTML = title + ":" + return td + } + + createContent(element):any { + var td = document.createElement("TD") + td.appendChild(element) + return td + } + + createInteraction():any { + var div = document.createElement("div") + div.setAttribute("id", "popup-interaction") + div.className = "interaction" + this.doc.appendChild(div) + } +} + +class PopupContent extends PopupWindow{ + + table = document.createElement("TABLE") + + createHeadline(headline):void { + this.doc.innerHTML = "" + var element = document.createElement("H3") + element.innerHTML = headline.toUpperCase() + this.doc.appendChild(element) + + // Tabelle erstellen + this.table = document.createElement("TABLE") + this.doc.appendChild(this.table) + } + + appendRow(title:string, element:any):void { + var tr = document.createElement("TR") + + // Bezeichnung + if (title.length != 0) { + tr.appendChild(this.createTitle(title)) + } + + + // Content + tr.appendChild(this.createContent(element)) + this.table.appendChild(tr) + } + + + createInput(type:string, name:string, value:string):any { + + var input = document.createElement("INPUT") + if (value == undefined) { + value = "" + } + + input.setAttribute("type", type) + input.setAttribute("name", name) + input.setAttribute("value", value) + return input + } + + createCheckbox(name:string):any { + var input = document.createElement("INPUT") + + input.setAttribute("type", "checkbox") + input.setAttribute("name", name) + return input + } + + createSelect(text:string[], values:string[], set:string, dbKey:string):any { + var select = document.createElement("SELECT") + select.setAttribute("name", dbKey) + for (let i = 0; i < text.length; i++) { + var option = document.createElement("OPTION") + option.setAttribute("value", values[i]) + option.innerText = text[i] + select.appendChild(option) + } + if(set != "") { + (select as HTMLSelectElement).value = set + } + + if (set == undefined) { + (select as HTMLSelectElement).value = values[0] + } + + return select + } + + selectOption(select:any, value:string):any { + //select.selectedOptions = value + var s:HTMLSelectElement = (select as HTMLSelectElement) + s.options[s.selectedIndex].value = value + return select + } + + description(value:string):any { + var tr = document.createElement("TR") + var td = document.createElement("TD") + var span = document.createElement("PRE") + + span.innerHTML = value + + tr.appendChild(td) + + tr.appendChild(this.createContent(span)) + + this.table.appendChild(tr) + } + + // Interaktion + addInteraction(element:any) { + var interaction = document.getElementById("popup-interaction") + interaction.appendChild(element) + } +} + +function openPopUp(dataType, element) { + + var data:object = new Object(); + var id:any + switch (element) { + case undefined: + + switch (dataType) { + case "group-title": + if (id == undefined) { + id = -1 + } + data = getLocalData("filter", id) + data["type"] = "group-title" + break; + + case "custom-filter": + if (id == undefined) { + id = -1 + } + data = getLocalData("filter", id) + data["type"] = "custom-filter" + break; + + default: + data["id.provider"] = "-" + data["type"] = dataType + id = "-" + break; + } + + break + + default: + id = element.id + data = getLocalData(dataType, id) + break; + } + + var content:PopupContent = new PopupContent() + + switch (dataType) { + case "playlist": + content.createHeadline("{{.playlist.playlistType.title}}") + // Type + var text:string[] = ["M3U", "HDHomeRun"] + var values:string[] = ["javascript: openPopUp('m3u')", "javascript: openPopUp('hdhr')"] + var select = content.createSelect(text, values, "", "type") + select.setAttribute("id", "type") + select.setAttribute("onchange", 'javascript: changeButtonAction(this, "next", "onclick")') // changeButtonAction + content.appendRow("{{.playlist.type.title}}", select) + + // Interaktion + content.createInteraction() + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}") + input.setAttribute("onclick", 'javascript: showElement("popup", false);') + content.addInteraction(input) + + // Weiter + var input = content.createInput("button", "next", "{{.button.next}}") + input.setAttribute("onclick", 'javascript: openPopUp("m3u")') + input.setAttribute("id", 'next') + content.addInteraction(input) + break + + case "m3u": + content.createHeadline(dataType) + // Name + var dbKey:string = "name" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.playlist.name.placeholder}}") + content.appendRow("{{.playlist.name.title}}", input) + + // Beschreibung + var dbKey:string = "description" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.playlist.description.placeholder}}") + content.appendRow("{{.playlist.description.title}}", input) + + // URL + var dbKey:string = "file.source" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.playlist.fileM3U.placeholder}}") + content.appendRow("{{.playlist.fileM3U.title}}", input) + + // Tuner + if (SERVER["settings"]["buffer"] == true) { + var text:string[] = new Array() + var values:string[] = new Array() + + for (var i = 1; i <= 100; i++) { + text.push(i.toString()) + values.push(i.toString()) + } + + var dbKey:string = "tuner" + var select = content.createSelect(text, values, data[dbKey], dbKey) + select.setAttribute("onfocus", "javascript: return;") + content.appendRow("{{.playlist.tuner.title}}", select) + } else { + var dbKey:string = "tuner" + if (data[dbKey] == undefined) { + data[dbKey] = 1 + } + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("readonly", "true") + input.className = "notAvailable" + content.appendRow("{{.playlist.tuner.title}}", input) + } + + content.description("{{.playlist.tuner.description}}") + + // Interaktion + content.createInteraction() + // Löschen + if (data["id.provider"]!= "-") { + var input = content.createInput("button", "delete", "{{.button.delete}}") + input.className = "delete" + input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", true, 0)') + content.addInteraction(input) + } else { + var input = content.createInput("button", "back", "{{.button.back}}") + input.setAttribute("onclick", 'javascript: openPopUp("playlist")') + content.addInteraction(input) + } + + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}") + input.setAttribute("onclick", 'javascript: showElement("popup", false);') + content.addInteraction(input) + + // Aktualisieren + if (data["id.provider"]!= "-") { + var input = content.createInput("button", "update", "{{.button.update}}") + input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", false, 1)') + content.addInteraction(input) + } + + // Speichern + var input = content.createInput("button", "save", "{{.button.save}}") + input.setAttribute('onclick', 'javascript: savePopupData("m3u", "' + id + '", false, 0)') + content.addInteraction(input) + break + + case "hdhr": + content.createHeadline(dataType) + // Name + var dbKey:string = "name" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.playlist.name.placeholder}}") + content.appendRow("{{.playlist.name.title}}", input) + + // Beschreibung + var dbKey:string = "description" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.playlist.description.placeholder}}") + content.appendRow("{{.playlist.description.placeholder}}", input) + + // URL + var dbKey:string = "file.source" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.playlist.fileHDHR.placeholder}}") + content.appendRow("{{.playlist.fileHDHR.title}}", input) + + // Tuner + if (SERVER["settings"]["buffer"] == true) { + var text:string[] = new Array() + var values:string[] = new Array() + + for (var i = 1; i <= 100; i++) { + text.push(i.toString()) + values.push(i.toString()) + } + + var dbKey:string = "tuner" + var select = content.createSelect(text, values, data[dbKey], dbKey) + select.setAttribute("onfocus", "javascript: return;") + content.appendRow("{{.playlist.tuner.title}}", select) + } else { + var dbKey:string = "tuner" + if (data[dbKey] == undefined) { + data[dbKey] = 1 + } + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("readonly", "true") + input.className = "notAvailable" + content.appendRow("{{.playlist.tuner.title}}", input) + } + + content.description("{{.playlist.tuner.description}}") + + // Interaktion + content.createInteraction() + // Löschen + if (data["id.provider"]!= "-") { + var input = content.createInput("button", "delete", "{{.button.delete}}") + input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", true, 0)') + input.className = "delete" + content.addInteraction(input) + } else { + var input = content.createInput("button", "back", "{{.button.back}}") + input.setAttribute("onclick", 'javascript: openPopUp("playlist")') + content.addInteraction(input) + } + + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}") + input.setAttribute("onclick", 'javascript: showElement("popup", false);') + content.addInteraction(input) + + // Aktualisieren + if (data["id.provider"]!= "-") { + var input = content.createInput("button", "update", "{{.button.update}}") + input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", false, 1)') + content.addInteraction(input) + } + + // Speichern + var input = content.createInput("button", "save", "{{.button.save}}") + input.setAttribute('onclick', 'javascript: savePopupData("hdhr", "' + id + '", false, 0)') + content.addInteraction(input) + break + + case "filter": + content.createHeadline(dataType) + + // Type + var dbKey:string = "type" + var text:string[] = ["M3U: " + "{{.filter.type.groupTitle}}", "xTeVe: " + "{{.filter.type.customFilter}}"] + var values:string[] = ["javascript: openPopUp('group-title')", "javascript: openPopUp('custom-filter')"] + var select = content.createSelect(text, values, "javascript: openPopUp('group-title')", dbKey) + select.setAttribute("id", id) + select.setAttribute("onchange", 'javascript: changeButtonAction(this, "next", "onclick");') // changeButtonAction + content.appendRow("{{.filter.type.title}}", select) + + // Interaktion + content.createInteraction() + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}") + input.setAttribute("onclick", 'javascript: showElement("popup", false);') + content.addInteraction(input) + + // Weiter + var input = content.createInput("button", "next", "{{.button.next}}") + input.setAttribute("onclick", 'javascript: openPopUp("group-title")') + input.setAttribute("id", 'next') + content.addInteraction(input) + break + + case "custom-filter": + case "group-title": + + switch (dataType) { + case "custom-filter": + content.createHeadline("{{.filter.custom}}") + break; + + case "group-title": + content.createHeadline("{{.filter.group}}") + break; + } + + // Name + var dbKey:string = "name" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.filter.name.placeholder}}") + content.appendRow("{{.filter.name.title}}", input) + + // Beschreibung + var dbKey:string = "description" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.filter.description.placeholder}}") + content.appendRow("{{.filter.description.title}}", input) + + // Typ + var dbKey:string = "type" + var input = content.createInput("hidden", dbKey, data[dbKey]) + content.appendRow("", input) + + var filterType = data[dbKey] + + switch (filterType) { + + case "custom-filter": + // Groß- Kleinschreibung beachten + var dbKey:string = "caseSensitive" + var input = content.createCheckbox(dbKey) + input.checked = data[dbKey] + content.appendRow("{{.filter.caseSensitive.title}}", input) + + // Filterregel (Benutzerdefiniert) + var dbKey:string = "filter" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.filter.filterRule.placeholder}}") + content.appendRow("{{.filter.filterRule.title}}", input) + + break; + + case "group-title": + //alert(dbKey + " " + filterType) + // Filter basierend auf den Gruppen in der M3U + var dbKey:string = "filter" + var groupsM3U = getLocalData("m3uGroups", "") + var text:string[] = groupsM3U["text"] + var values:string[] = groupsM3U["value"] + + var select = content.createSelect(text, values, data[dbKey], dbKey) + select.setAttribute("onchange", "javascript: this.className = 'changed'") + content.appendRow("{{.filter.filterGroup.title}}", select) + content.description("{{.filter.filterGroup.description}}") + + // Groß- Kleinschreibung beachten + var dbKey:string = "caseSensitive" + var input = content.createCheckbox(dbKey) + input.checked = data[dbKey] + content.appendRow("{{.filter.caseSensitive.title}}", input) + + + var dbKey:string = "include" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.filter.include.placeholder}}") + + content.appendRow("{{.filter.include.title}}", input) + content.description("{{.filter.include.description}}") + + var dbKey:string = "exclude" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.filter.exclude.placeholder}}") + content.appendRow("{{.filter.exclude.title}}", input) + content.description("{{.filter.exclude.description}}") + + break + + default: + break; + } + + // Interaktion + content.createInteraction() + + // Löschen + var input = content.createInput("button", "delete", "{{.button.delete}}") + input.setAttribute('onclick', 'javascript: savePopupData("filter", "' + id + '", true, 0)') + input.className = "delete" + content.addInteraction(input) + + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}") + input.setAttribute("onclick", 'javascript: showElement("popup", false);') + content.addInteraction(input) + + // Speichern + var input = content.createInput("button", "save", "{{.button.save}}") + input.setAttribute('onclick', 'javascript: savePopupData("filter", "' + id + '", false, 0)') + content.addInteraction(input) + + break + + case "xmltv": + content.createHeadline(dataType) + // Name + var dbKey:string = "name" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.xmltv.name.placeholder}}") + content.appendRow("{{.xmltv.name.title}}", input) + + // Beschreibung + var dbKey:string = "description" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.xmltv.description.placeholder}}") + content.appendRow("{{.xmltv.description.title}}", input) + + // URL + var dbKey:string = "file.source" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.xmltv.fileXMLTV.placeholder}}") + content.appendRow("{{.xmltv.fileXMLTV.title}}", input) + + // Interaktion + content.createInteraction() + // Löschen + if (data["id.provider"]!= "-") { + var input = content.createInput("button", "delete", "{{.button.delete}}") + input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", true, 0)') + input.className = "delete" + content.addInteraction(input) + } + + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}") + input.setAttribute("onclick", 'javascript: showElement("popup", false);') + content.addInteraction(input) + + // Aktualisieren + if (data["id.provider"]!= "-") { + var input = content.createInput("button", "update", "{{.button.update}}") + input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", false, 1)') + content.addInteraction(input) + } + + // Speichern + var input = content.createInput("button", "save", "{{.button.save}}") + input.setAttribute('onclick', 'javascript: savePopupData("xmltv", "' + id + '", false, 0)') + content.addInteraction(input) + break + + case "users": + content.createHeadline("{{.mainMenu.item.users}}") + // Benutzername + var dbKey:string = "username" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("placeholder", "{{.users.username.placeholder}}") + content.appendRow("{{.users.username.title}}", input) + + // Neues Passwort + var dbKey:string = "password" + var input = content.createInput("password", dbKey, "") + input.setAttribute("placeholder", "{{.users.password.placeholder}}") + content.appendRow("{{.users.password.title}}", input) + + // Bestätigung + var dbKey:string = "confirm" + var input = content.createInput("password", dbKey, "") + input.setAttribute("placeholder", "{{.users.confirm.placeholder}}") + content.appendRow("{{.users.confirm.title}}", input) + + // Berechtigung WEB + var dbKey:string = "authentication.web" + var input = content.createCheckbox(dbKey) + input.checked = data[dbKey] + if (data["defaultUser"] == true) { + input.setAttribute("onclick", "javascript: return false") + } + content.appendRow("{{.users.web.title}}", input) + + // Berechtigung PMS + var dbKey:string = "authentication.pms" + var input = content.createCheckbox(dbKey) + input.checked = data[dbKey] + content.appendRow("{{.users.pms.title}}", input) + + // Berechtigung M3U + var dbKey:string = "authentication.m3u" + var input = content.createCheckbox(dbKey) + input.checked = data[dbKey] + content.appendRow("{{.users.m3u.title}}", input) + + // Berechtigung XML + var dbKey:string = "authentication.xml" + var input = content.createCheckbox(dbKey) + input.checked = data[dbKey] + content.appendRow("{{.users.xml.title}}", input) + + // Berechtigung API + var dbKey:string = "authentication.api" + var input = content.createCheckbox(dbKey) + input.checked = data[dbKey] + content.appendRow("{{.users.api.title}}", input) + + // Interaktion + content.createInteraction() + + // Löschen + if (data["defaultUser"]!= true && id != "-") { + var input = content.createInput("button", "delete", "{{.button.delete}}") + input.className = "delete" + input.setAttribute('onclick', 'javascript: savePopupData("' + dataType + '", "' + id + '", true, 0)') + content.addInteraction(input) + } + + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}") + input.setAttribute("onclick", 'javascript: showElement("popup", false);') + content.addInteraction(input) + + // Speichern + var input = content.createInput("button", "save", "{{.button.save}}") + input.setAttribute("onclick", 'javascript: savePopupData("' + dataType + '", "' + id + '", "false");') + content.addInteraction(input) + + break + + case "mapping": + content.createHeadline("{{.mainMenu.item.mapping}}") + // Aktiv + var dbKey:string = "x-active" + var input = content.createCheckbox(dbKey) + input.checked = data[dbKey] + input.id = "active" + //input.setAttribute("onchange", "javascript: this.className = 'changed'") + input.setAttribute("onchange", "javascript: toggleChannelStatus('" + id + "', this)") + content.appendRow("{{.mapping.active.title}}", input) + + // Kanalname + var dbKey:string = "x-name" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("onchange", "javascript: this.className = 'changed'") + if (BULK_EDIT == true) { + input.style.border = "solid 1px red" + input.setAttribute("readonly", "true") + } + content.appendRow("{{.mapping.channelName.title}}", input) + + // Aktualisierung des Kanalnamens + if (data.hasOwnProperty("_uuid.key")) { + if (data["_uuid.key"] != "") { + var dbKey:string = "x-update-channel-name" + var input = content.createCheckbox(dbKey) + input.setAttribute("onchange", "javascript: this.className = 'changed'") + input.checked = data[dbKey] + content.appendRow("{{.mapping.updateChannelName.title}}", input) + } + } + + // Logo URL (Kanal) + var dbKey:string = "tvg-logo" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("onchange", "javascript: this.className = 'changed'") + input.setAttribute("id", "channel-icon") + content.appendRow("{{.mapping.channelLogo.title}}", input) + + // Aktualisierung des Kanallogos + var dbKey:string = "x-update-channel-icon" + var input = content.createCheckbox(dbKey) + input.checked = data[dbKey] + input.setAttribute("id", "update-icon") + input.setAttribute("onchange", "javascript: this.className = 'changed'; changeChannelLogo('" + id + "');") + content.appendRow("{{.mapping.updateChannelLogo.title}}", input) + + // Erweitern der EPG Kategorie + var dbKey:string = "x-category" + var text:string[] = ["-", "Kids (Emby only)", "News", "Movie", "Series", "Sports"] + var values:string[] = ["-", "Kids", "News", "Movie", "Series", "Sports"] + var select = content.createSelect(text, values, data[dbKey], dbKey) + select.setAttribute("onchange", "javascript: this.className = 'changed'") + content.appendRow("{{.mapping.epgCategory.title}}", select) + + // M3U Gruppentitel + var dbKey:string = "x-group-title" + var input = content.createInput("text", dbKey, data[dbKey]) + input.setAttribute("onchange", "javascript: this.className = 'changed'") + content.appendRow("{{.mapping.m3uGroupTitle.title}}", input) + + // XMLTV Datei + var dbKey:string = "x-xmltv-file" + var xmlFile = data[dbKey] + var xmltv:XMLTVFile = new XMLTVFile() + var select = xmltv.getFiles(data[dbKey]) + select.setAttribute("name", dbKey) + select.setAttribute("id", "popup-xmltv") + select.setAttribute("onchange", "javascript: this.className = 'changed'; setXmltvChannel('" + id + "',this);") + content.appendRow("{{.mapping.xmltvFile.title}}", select) + var file = data[dbKey] + + // XMLTV Mapping + var dbKey:string = "x-mapping" + var xmltv:XMLTVFile = new XMLTVFile() + var select = xmltv.getPrograms(file, data[dbKey]) + select.setAttribute("name", dbKey) + select.setAttribute("id", "popup-mapping") + select.setAttribute("onchange", "javascript: this.className = 'changed'; checkXmltvChannel('" + id + "',this,'" + xmlFile + "');") + + sortSelect(select) + content.appendRow("{{.mapping.xmltvChannel.title}}", select) + + // Interaktion + content.createInteraction() + + // Logo hochladen + var input = content.createInput("button", "cancel", "{{.button.uploadLogo}}") + input.setAttribute("onclick", 'javascript: uploadLogo();') + content.addInteraction(input) + + // Abbrechen + var input = content.createInput("button", "cancel", "{{.button.cancel}}") + input.setAttribute("onclick", 'javascript: showElement("popup", false);') + content.addInteraction(input) + + // Fertig + var ids:string[] = new Array() + ids = getAllSelectedChannels() + if (ids.length == 0) { + ids.push(id) + } + + var input = content.createInput("button", "save", "{{.button.done}}") + input.setAttribute("onclick", 'javascript: donePopupData("' + dataType + '", "' + ids + '", "false");') + content.addInteraction(input) + break + + default: + break; + } + + showPopUpElement('popup-custom'); +} + +class XMLTVFile { + File:string + + getFiles(set:string):any { + var fileIDs:string[] = getObjKeys(SERVER["xepg"]["xmltvMap"]) + var values = new Array("-"); + var text = new Array("-"); + + for (let i = 0; i < fileIDs.length; i++) { + if (fileIDs[i] != "xTeVe Dummy") { + values.push(getValueFromProviderFile(fileIDs[i], "xmltv", "file.xteve")) + text.push(getValueFromProviderFile(fileIDs[i], "xmltv", "name")) + } else { + values.push(fileIDs[i]) + text.push(fileIDs[i]) + } + + } + + var select = document.createElement("SELECT") + for (let i = 0; i < text.length; i++) { + var option = document.createElement("OPTION") + option.setAttribute("value", values[i]) + option.innerText = text[i] + select.appendChild(option) + } + + if(set != "") { + (select as HTMLSelectElement).value = set + } + + return select + } + + getPrograms(file:string, set:string):any { + //var fileIDs:string[] = getObjKeys(SERVER["xepg"]["xmltvMap"]) + var values = getObjKeys(SERVER["xepg"]["xmltvMap"][file]); + var text = new Array() + var displayName:string + + for (let i = 0; i < values.length; i++) { + if (SERVER["xepg"]["xmltvMap"][file][values[i]].hasOwnProperty('display-name') == true) { + displayName = SERVER["xepg"]["xmltvMap"][file][values[i]]["display-name"]; + } else { + displayName = "-" + } + + text[i] = displayName + " (" + values[i] + ")"; + } + + text.unshift("-"); + values.unshift("-"); + + var select = document.createElement("SELECT") + for (let i = 0; i < text.length; i++) { + var option = document.createElement("OPTION") + option.setAttribute("value", values[i]) + option.innerText = text[i] + select.appendChild(option) + } + + if(set != "") { + (select as HTMLSelectElement).value = set + } + + if ((select as HTMLSelectElement).value != set) { + (select as HTMLSelectElement).value = "-" + } + + return select + } + + return +} + +function getValueFromProviderFile(file:string, fileType, key) { + + if (file == "xTeVe Dummy") { + return file + } + + var fileID:string + var indicator = file.charAt(0) + + switch (indicator) { + case "M": + fileType = "m3u" + fileID = file + break; + + case "H": + fileType = "hdhr" + fileID = file + break; + + case "X": + fileType = "xmltv" + fileID = file.substring(0, file.lastIndexOf('.')) + break; + + } + + if (SERVER["settings"]["files"][fileType].hasOwnProperty(fileID) == true) { + var data = SERVER["settings"]["files"][fileType][fileID]; + return data[key] + } + + return + +} + +function setXmltvChannel(id, element) { + + var xmltv:XMLTVFile = new XMLTVFile() + var xmlFile = element.value + + var tvgId:string = SERVER["xepg"]["epgMapping"][id]["tvg-id"] + var td = document.getElementById("popup-mapping").parentElement + td.innerHTML = "" + + var select = xmltv.getPrograms(element.value, tvgId) + select.setAttribute("name", "x-mapping") + select.setAttribute("id", "popup-mapping") + select.setAttribute("onchange", "javascript: this.className = 'changed'; checkXmltvChannel('" + id + "',this,'" + xmlFile + "');") + select.className = "changed" + sortSelect(select) + td.appendChild(select); + + checkXmltvChannel(id, select, xmlFile) +} + +function checkXmltvChannel(id:string, element:any, xmlFile) { + + var value = (element as HTMLSelectElement).value + var bool:boolean + var checkbox = document.getElementById('active') + var channel:any = SERVER["xepg"]["epgMapping"][id] + var updateLogo:boolean + + + if (value == "-") { + bool = false + } else { + bool = true + } + + (checkbox as HTMLInputElement).checked = bool + checkbox.className = "changed" + console.log(xmlFile); + + // Kanallogo aktualisieren + /* + updateLogo = (document.getElementById("update-icon") as HTMLInputElement).checked + console.log(updateLogo); + */ + + if(xmlFile != "xTeVe Dummy" && bool == true) { + + //(document.getElementById("update-icon") as HTMLInputElement).checked = true; + //(document.getElementById("update-icon") as HTMLInputElement).className = "changed"; + + console.log("ID", id) + changeChannelLogo(id) + + return + } + + if (xmlFile == "xTeVe Dummy") { + (document.getElementById("update-icon") as HTMLInputElement).checked = false; + (document.getElementById("update-icon") as HTMLInputElement).className = "changed"; + } + + return +} + +function changeChannelLogo(id:string) { + + var updateLogo:boolean + var channel:any = SERVER["xepg"]["epgMapping"][id] + + var f = (document.getElementById("popup-xmltv") as HTMLSelectElement); + var xmltvFile = f.options[f.selectedIndex].value; + + var m = (document.getElementById("popup-mapping") as HTMLSelectElement); + var xMapping = m.options[m.selectedIndex].value; + + var xmltvLogo = SERVER["xepg"]["xmltvMap"][xmltvFile][xMapping]["icon"] + updateLogo = (document.getElementById("update-icon") as HTMLInputElement).checked + + if (updateLogo == true && xmltvFile != "xTeVe Dummy") { + + if (SERVER["xepg"]["xmltvMap"][xmltvFile].hasOwnProperty(xMapping)) { + var logo = xmltvLogo + } else { + logo = channel["tvg-logo"] + } + + var logoInput = (document.getElementById("channel-icon") as HTMLInputElement); + logoInput.value = logo + if (BULK_EDIT == false) { + logoInput.className = "changed" + } + + } + +} + +function savePopupData(dataType:string, id:string, remove:Boolean, option:number) { + + if (dataType == "mapping") { + + var data = new Object() + console.log("Save mapping data") + + cmd = "saveEpgMapping" + data["epgMapping"] = SERVER["xepg"]["epgMapping"] + + console.log("SEND TO SERVER"); + + var server:Server = new Server(cmd) + server.request(data) + + delete UNDO["epgMapping"] + + return + } + + console.log("Save popup data") + var div = document.getElementById("popup-custom") + + var inputs = div.getElementsByTagName("TABLE")[0].getElementsByTagName("INPUT"); + var selects = div.getElementsByTagName("TABLE")[0].getElementsByTagName("SELECT"); + + var input = new Object(); + var confirmMsg: string + + for (let i = 0; i < selects.length; i++) { + + var name:string + name = (selects[i] as HTMLSelectElement).name + var value = (selects[i] as HTMLSelectElement).value + + switch (name) { + case "tuner": + input[name] = parseInt(value) + break; + + default: + input[name] = value + break; + } + + } + + for (let i = 0; i < inputs.length; i++) { + + switch ((inputs[i] as HTMLInputElement).type) { + + case "checkbox": + name = (inputs[i] as HTMLInputElement).name + input[name] = (inputs[i] as HTMLInputElement).checked + break + + case "text": + case "hidden": + case "password": + + name = (inputs[i] as HTMLInputElement).name + + switch (name) { + case "tuner": + input[name] = parseInt((inputs[i] as HTMLInputElement).value) + break; + + default: + input[name] = (inputs[i] as HTMLInputElement).value + break; + } + + break + + } + + } + + var data = new Object() + + var cmd:string + + if (remove == true) { + input["delete"] = true + } + + switch (dataType) { + case "users": + + confirmMsg = "Delete this user?" + if (id == "-") { + cmd = "saveNewUser" + data["userData"] = input + } else { + cmd = "saveUserData" + var d = new Object() + d[id] = input + data["userData"] = d + } + + break; + + case "m3u": + + confirmMsg = "Delete this playlist?" + switch (option) { + // Popup: Save + case 0: + cmd = "saveFilesM3U" + break + + // Popup: Update + case 1: + cmd = "updateFileM3U" + break + + } + + data["files"] = new Object + data["files"][dataType] = new Object + data["files"][dataType][id] = input + + break + + case "hdhr": + + confirmMsg = "Delete this HDHomeRun tuner?" + switch (option) { + // Popup: Save + case 0: + cmd = "saveFilesHDHR" + break + + // Popup: Update + case 1: + cmd = "updateFileHDHR" + break + + } + + data["files"] = new Object + data["files"][dataType] = new Object + data["files"][dataType][id] = input + + break + + case "xmltv": + + confirmMsg = "Delete this XMLTV file?" + switch (option) { + // Popup: Save + case 0: + cmd = "saveFilesXMLTV" + break + + // Popup: Update + case 1: + cmd = "updateFileXMLTV" + break + + } + + data["files"] = new Object + data["files"][dataType] = new Object + data["files"][dataType][id] = input + + break + + case "filter": + + confirmMsg = "Delete this filter?" + cmd = "saveFilter" + data["filter"] = new Object + data["filter"][id] = input + break + + default: + console.log(dataType, id); + return + break; + + } + + if (remove == true) { + + if (!confirm(confirmMsg)) { + showElement("popup", false) + return + } + + } + + console.log("SEND TO SERVER"); + + console.log(data); + + var server:Server = new Server(cmd) + server.request(data) + +} + +function donePopupData(dataType:string, idsStr:string) { + + var ids:string[] = idsStr.split(','); + var div = document.getElementById("popup-custom") + var inputs = div.getElementsByClassName("changed") + + ids.forEach(id => { + var input = new Object(); + input = SERVER["xepg"]["epgMapping"][id] + + console.log(input); + + for (let i = 0; i < inputs.length; i++) { + + var name:string + var value:any + + switch (inputs[i].tagName) { + + case "INPUT": + switch ((inputs[i] as HTMLInputElement).type) { + case "checkbox": + name = (inputs[i] as HTMLInputElement).name + value = (inputs[i] as HTMLInputElement).checked + input[name] = value + break + + case "text": + name = (inputs[i] as HTMLInputElement).name + value = (inputs[i] as HTMLInputElement).value + input[name] = value + break + + } + + break + + case "SELECT": + name = (inputs[i] as HTMLSelectElement).name + value = (inputs[i] as HTMLSelectElement).value + input[name] = value + break + + } + + switch (name) { + + + case "tvg-logo": + //(document.getElementById(id).childNodes[2].firstChild as HTMLElement).setAttribute("src", value) + break + + case "x-name": + (document.getElementById(id).childNodes[3].firstChild as HTMLElement).innerHTML = value + break + + case "x-category": + (document.getElementById(id).childNodes[3].firstChild as HTMLElement).className = value + break + + case "x-group-title": + (document.getElementById(id).childNodes[5].firstChild as HTMLElement).innerHTML = value + break + + case "x-xmltv-file": + if (value != "xTeVe Dummy" && value != "-") { + value = getValueFromProviderFile(value, "xmltv", "name") + } + + if (value == "-") { + input["x-active"] = false + } + + (document.getElementById(id).childNodes[6].firstChild as HTMLElement).innerHTML = value + break + + case "x-mapping": + if (value == "-") { + input["x-active"] = false + } + + (document.getElementById(id).childNodes[7].firstChild as HTMLElement).innerHTML = value + + break + + default: + + } + + createSearchObj() + searchInMapping() + + } + + if (input["x-active"] == false) { + document.getElementById(id).className = "notActiveEPG" + } else { + document.getElementById(id).className = "activeEPG" + } + + console.log(input["tvg-logo"]); + (document.getElementById(id).childNodes[2].firstChild as HTMLElement).setAttribute("src", input["tvg-logo"]) + + + }); + + showElement("popup", false); + + return +} + +function showPreview(element:boolean) { + + var div = document.getElementById("myStreamsBox") + switch (element) { + + case false: + div.className = "notVisible" + return + break; + } + + var streams:string[] = ["activeStreams", "inactiveStreams"] + + streams.forEach(preview => { + + var table = document.getElementById(preview) + table.innerHTML = "" + var obj:string[] = SERVER["data"]["StreamPreviewUI"][preview] + + obj.forEach(channel => { + + var tr = document.createElement("TR") + var tdKey = document.createElement("TD") + var tdVal = document.createElement("TD") + + tdKey.className = "tdKey" + tdVal.className = "tdVal" + + switch (preview) { + case "activeStreams": + tdKey.innerText = "Channel: (+)" + break; + + case "inactiveStreams": + tdKey.innerText = "Channel: (-)" + break; + } + + tdVal.innerText = channel + tr.appendChild(tdKey) + tr.appendChild(tdVal) + + table.appendChild(tr) + + }); + + }); + + showElement("loading", false) + div.className = "visible" + + return +} diff --git a/ts/network_ts.ts b/ts/network_ts.ts new file mode 100644 index 0000000..cb62e05 --- /dev/null +++ b/ts/network_ts.ts @@ -0,0 +1,147 @@ +class Server { + protocol:string + cmd:string + + constructor(cmd:string) { + this.cmd = cmd + } + + request(data:Object):any { + + if (SERVER_CONNECTION == true) { + return + } + + SERVER_CONNECTION = true + + console.log(data) + if (this.cmd != "updateLog") { + showElement("loading", true) + UNDO = new Object() + } + + switch(window.location.protocol) { + case "http:": + this.protocol = "ws://" + break + case "https://": + this.protocol = "wss://" + break + } + + var url = this.protocol + window.location.hostname + ":" + window.location.port + "/data/" + "?Token=" + getCookie("Token") + + data["cmd"] = this.cmd + var ws = new WebSocket(url) + ws.onopen = function() { + + WS_AVAILABLE = true + + console.log("REQUEST (JS):"); + console.log(data) + + console.log("REQUEST: (JSON)"); + console.log(JSON.stringify(data)) + + this.send(JSON.stringify(data)); + + } + + ws.onerror = function(e) { + + console.log("No websocket connection to xTeVe could be established. Check your network configuration.") + SERVER_CONNECTION = false + + if (WS_AVAILABLE == false) { + alert("No websocket connection to xTeVe could be established. Check your network configuration.") + } + + } + + + ws.onmessage = function (e) { + + SERVER_CONNECTION = false + showElement("loading", false) + + console.log("RESPONSE:"); + var response = JSON.parse(e.data); + + console.log(response); + + if (response.hasOwnProperty("token")) { + document.cookie = "Token=" + response["token"] + } + + if (response["status"] == false) { + + alert(response["err"]) + + if (response.hasOwnProperty("reload")) { + location.reload() + } + + return + } + + + if (response.hasOwnProperty("logoURL")) { + var div = (document.getElementById("channel-icon") as HTMLInputElement) + div.value = response["logoURL"] + div.className = "changed" + return + } + + switch (data["cmd"]) { + case "updateLog": + SERVER["log"] = response["log"] + if (document.getElementById("content_log")) { + showLogs(false) + } + return + break; + + default: + SERVER = new Object() + SERVER = response + break; + } + + if (response.hasOwnProperty("openMenu")) { + var menu = document.getElementById(response["openMenu"]) + menu.click() + showElement("popup", false) + } + + if (response.hasOwnProperty("openLink")) { + window.location = response["openLink"] + } + + if (response.hasOwnProperty("alert")) { + alert(response["alert"]) + } + + if (response.hasOwnProperty("reload")) { + location.reload() + } + + + if (response.hasOwnProperty("wizard")) { + createLayout() + configurationWizard[response["wizard"]].createWizard() + return + } + + createLayout() + + } + + } + +} + +function getCookie(name) { + var value = "; " + document.cookie; + var parts = value.split("; " + name + "="); + if (parts.length == 2) return parts.pop().split(";").shift(); +} \ No newline at end of file diff --git a/ts/settings_ts.ts b/ts/settings_ts.ts new file mode 100644 index 0000000..25ce2e0 --- /dev/null +++ b/ts/settings_ts.ts @@ -0,0 +1,564 @@ +class SettingsCategory { + DocumentID:string = "content_settings" + createCategoryHeadline(value:string):any { + var element = document.createElement("H4") + element.innerHTML = value + return element + } + + createHR():any { + var element = document.createElement("HR") + return element + } + + createSettings(settingsKey:string):any { + var setting = document.createElement("TR") + var content:PopupContent = new PopupContent() + var data = SERVER["settings"][settingsKey] + + switch (settingsKey) { + + // Texteingaben + case "update": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.update.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createInput("text", "update", data.toString()) + input.setAttribute("placeholder", "{{.settings.update.placeholder}}") + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "backup.path": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.backupPath.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createInput("text", "backup.path", data) + input.setAttribute("placeholder", "{{.settings.backupPath.placeholder}}") + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "temp.path": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.tempPath.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createInput("text", "temp.path", data) + input.setAttribute("placeholder", "{{.settings.tmpPath.placeholder}}") + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "user.agent": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.userAgent.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createInput("text", "user.agent", data) + input.setAttribute("placeholder", "{{.settings.userAgent.placeholder}}") + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "buffer.timeout": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.bufferTimeout.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createInput("text", "buffer.timeout", data) + input.setAttribute("placeholder", "{{.settings.bufferTimeout.placeholder}}") + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + // Checkboxen + case "authentication.web": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.authenticationWEB.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "authentication.pms": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.authenticationPMS.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "authentication.m3u": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.authenticationM3U.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "authentication.xml": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.authenticationXML.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "authentication.api": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.authenticationAPI.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "files.update": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.filesUpdate.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "cache.images": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.cacheImages.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "xepg.replace.missing.images": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.replaceEmptyImages.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "xteveAutoUpdate": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.xteveAutoUpdate.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "buffer": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.streamBuffering.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "api": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.api.title}}" + ":" + + var tdRight = document.createElement("TD") + var input = content.createCheckbox(settingsKey) + input.checked = data + input.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(input) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + // Select + case "tuner": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.tuner.title}}" + ":" + + var tdRight = document.createElement("TD") + var text = new Array() + var values = new Array() + + for (var i = 1; i <= 100; i++) { + text.push(i) + values.push(i) + } + + var select = content.createSelect(text, values, data, settingsKey) + select.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(select) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "epgSource": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.epgSource.title}}" + ":" + + var tdRight = document.createElement("TD") + var text:any[] = ["PMS", "XEPG"] + var values:any[] = ["PMS", "XEPG"] + + var select = content.createSelect(text, values, data, settingsKey) + select.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(select) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "backup.keep": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.backupKeep.title}}" + ":" + + var tdRight = document.createElement("TD") + var text:any[] = ["5", "10", "20", "30", "40", "50"] + var values:any[] = ["5", "10", "20", "30", "40", "50"] + + var select = content.createSelect(text, values, data, settingsKey) + select.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(select) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + case "buffer.size.kb": + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "{{.settings.bufferSize.title}}" + ":" + + var tdRight = document.createElement("TD") + var text:any[] = ["0.5 MB", "1 MB", "2 MB", "3 MB", "4 MB", "5 MB", "6 MB", "7 MB", "8 MB"] + var values:any[] = ["512", "1024", "2048", "3072", "4096", "5120", "6144", "7168", "8192"] + + var select = content.createSelect(text, values, data, settingsKey) + select.setAttribute("onchange", "javascript: this.className = 'changed'") + tdRight.appendChild(select) + + setting.appendChild(tdLeft) + setting.appendChild(tdRight) + break + + } + + return setting + + } + + + createDescription(settingsKey:string):any { + + var description = document.createElement("TR") + var text:string + switch (settingsKey) { + + case "authentication.web": + text = "{{.settings.authenticationWEB.description}}" + break + + case "authentication.m3u": + text = "{{.settings.authenticationM3U.description}}" + break + + case "authentication.pms": + text = "{{.settings.authenticationPMS.description}}" + break + + case "authentication.xml": + text = "{{.settings.authenticationXML.description}}" + break + + case "authentication.api": + if (SERVER["settings"]["authentication.web"] == true) { + text = "{{.settings.authenticationAPI.description}}" + } + break + + case "xteveAutoUpdate": + text = "{{.settings.xteveAutoUpdate.description}}" + break + + case "backup.keep": + text = "{{.settings.backupKeep.description}}" + break + + case "backup.path": + text = "{{.settings.backupPath.description}}" + break + + case "temp.path": + text = "{{.settings.tempPath.description}}" + break + + case "buffer": + text = "{{.settings.streamBuffering.description}}" + break + + case "buffer.size.kb": + text = "{{.settings.bufferSize.description}}" + break + + case "buffer.timeout": + text = "{{.settings.bufferTimeout.description}}" + break + + case "user.agent": + text = "{{.settings.userAgent.description}}" + break + + case "epgSource": + text = "{{.settings.epgSource.description}}" + break + + case "tuner": + text = "{{.settings.tuner.description}}" + break + + case "update": + text = "{{.settings.update.description}}" + break + + case "api": + text = "{{.settings.api.description}}" + break + + case "files.update": + text = "{{.settings.filesUpdate.description}}" + break + + case "cache.images": + text = "{{.settings.cacheImages.description}}" + break + + case "xepg.replace.missing.images": + text = "{{.settings.replaceEmptyImages.description}}" + break + + default: + text = "" + break + + } + + var tdLeft = document.createElement("TD") + tdLeft.innerHTML = "" + + var tdRight = document.createElement("TD") + var pre = document.createElement("PRE") + pre.innerHTML = text + tdRight.appendChild(pre) + + description.appendChild(tdLeft) + description.appendChild(tdRight) + + return description + + } + +} + +class SettingsCategoryItem extends SettingsCategory { + headline:string + settingsKeys:string + + constructor(headline:string, settingsKeys:string) { + super() + this.headline = headline + this.settingsKeys = settingsKeys + } + + createCategory():void { + var headline = this.createCategoryHeadline(this.headline) + var settingsKeys = this.settingsKeys + + var doc = document.getElementById(this.DocumentID) + doc.appendChild(headline) + + // Tabelle für die Kategorie erstellen + + var table = document.createElement("TABLE") + + var keys = settingsKeys.split(",") + + keys.forEach(settingsKey => { + + switch (settingsKey) { + + case "authentication.pms": + case "authentication.m3u": + case "authentication.xml": + case "authentication.api": + if (SERVER["settings"]["authentication.web"] == false) { + break + } + + default: + var item = this.createSettings(settingsKey) + var description = this.createDescription(settingsKey) + + table.appendChild(item) + table.appendChild(description) + break + + } + + }); + + doc.appendChild(table) + doc.appendChild(this.createHR()) + } + +} + +function showSettings() { + console.log("SETTINGS"); + + for (let i = 0; i < settingsCategory.length; i++) { + settingsCategory[i].createCategory() + } + +} + +function saveSettings() { + console.log("Save Settings"); + + var cmd = "saveSettings" + var div = document.getElementById("content_settings") + var settings = div.getElementsByClassName("changed") + + var newSettings = new Object(); + + for (let i = 0; i < settings.length; i++) { + + var name:string + var value:any + + switch (settings[i].tagName) { + case "INPUT": + + switch ((settings[i] as HTMLInputElement).type) { + case "checkbox": + name = (settings[i] as HTMLInputElement).name + value = (settings[i] as HTMLInputElement).checked + newSettings[name] = value + break + + case "text": + name = (settings[i] as HTMLInputElement).name + value = (settings[i] as HTMLInputElement).value + + switch (name) { + case "update": + value = value.split(",") + value = value.filter(function(e:any) { return e}) + break + + case "buffer.timeout": + value = parseFloat(value) + + } + + newSettings[name] = value + break + } + + break + + case "SELECT": + name = (settings[i] as HTMLSelectElement).name + value = (settings[i] as HTMLSelectElement).value + + // Wenn der Wert eine Zahl ist, wird dieser als Zahl gespeichert + if(isNaN(value)){ + newSettings[name] = value + } else { + newSettings[name] = parseInt(value) + } + + break + + } + + } + + var data = new Object() + data["settings"] = newSettings + + var server:Server = new Server(cmd) + server.request(data) +} diff --git a/xteve.go b/xteve.go new file mode 100644 index 0000000..d57711f --- /dev/null +++ b/xteve.go @@ -0,0 +1,171 @@ +// Copyright 2019 marmei. All rights reserved. +// Use of this source code is governed by a MIT license that can be found in the +// LICENSE file. +// GitHub: https://github.com/xteve-project/xTeVe + +package main + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "./src" +) + +// GitHubStruct : GitHub Account. Über diesen Account werden die Updates veröffentlicht +type GitHubStruct struct { + Branch string + Repo string + Update bool + User string +} + +// GitHub : GitHub Account +// If you want to fork this project, enter your Github account here. This prevents a newer version of xTeVe from updating your version. +var GitHub = GitHubStruct{Branch: "master", User: "xteve-project", Repo: "xTeVe-Downloads", Update: true} + +/* + Branch: GitHub Branch + User: GitHub Username + Repo: GitHub Repository + Update: Automatic updates from the GitHub repository [true|false] +*/ + +// Name : Programname +const Name = "xTeVe" + +// Version : Version, die Build Nummer wird in der main func geparst. +const Version = "2.0.0.0000" + +// APIVersion : API Version +const APIVersion = "1.1.0" + +// Dev : Aktiviert den Entwicklungsmodus. Für den Webserver werden dann die lokalen Dateien verwendet. +const Dev = false + +var homeDirectory = fmt.Sprintf("%s%s.%s%s", src.GetUserHomeDirectory(), string(os.PathSeparator), strings.ToLower(Name), string(os.PathSeparator)) +var samplePath = fmt.Sprintf("%spath%sto%sxteve%s", string(os.PathSeparator), string(os.PathSeparator), string(os.PathSeparator), string(os.PathSeparator)) + +var configFolder = flag.String("config", "", ": Config Folder ["+samplePath+"] (default: "+homeDirectory+")") +var port = flag.String("port", "", ": Server port [34400] (default: 34400)") + +var gitBranch = flag.String("branch", "", ": Git Branch [master|beta] (default: master)") +var debug = flag.Int("debug", 0, ": Debug level [0 - 3] (default: 0)") +var h = flag.Bool("h", false, ": Show help") + +func main() { + + // Build-Nummer von der Versionsnummer trennen + var build = strings.Split(Version, ".") + + var system = &src.System + system.APIVersion = APIVersion + system.Branch = GitHub.Branch + system.Build = build[len(build)-1:][0] + system.Dev = Dev + system.GitHub = GitHub + system.Name = Name + system.Version = strings.Join(build[0:len(build)-1], ".") + + // Panic !!! + defer func() { + + if r := recover(); r != nil { + + fmt.Println() + fmt.Println("* * * * * FATAL ERROR * * * * *") + fmt.Println("OS: ", runtime.GOOS) + fmt.Println("Arch:", runtime.GOARCH) + fmt.Println("Err: ", r) + fmt.Println() + + pc := make([]uintptr, 20) + runtime.Callers(2, pc) + + for i := range pc { + + if runtime.FuncForPC(pc[i]) != nil { + + f := runtime.FuncForPC(pc[i]) + file, line := f.FileLine(pc[i]) + + if string(file)[0:1] != "?" { + fmt.Printf("%s:%d %s\n", filepath.Base(file), line, f.Name()) + } + + } + + } + + fmt.Println() + fmt.Println("* * * * * * * * * * * * * * * *") + + } + + }() + + flag.Parse() + + if *h { + flag.Usage() + return + } + + // Webserver Port + if len(*port) > 0 { + system.Flag.Port = *port + } + + // Branch + system.Flag.Branch = *gitBranch + if len(system.Flag.Branch) > 0 { + fmt.Println("Git Branch is now:", system.Flag.Branch) + } + + // Debug Level + system.Flag.Debug = *debug + if system.Flag.Debug > 3 { + flag.Usage() + return + } + + // Speicherort für die Konfigurationsdateien + if len(*configFolder) > 0 { + system.Folder.Config = *configFolder + } + + err := src.Init() + if err != nil { + src.ShowError(err, 0) + os.Exit(0) + } + + err = src.BinaryUpdate() + if err != nil { + src.ShowError(err, 0) + os.Exit(0) + } + + err = src.StartSystem(false) + if err != nil { + src.ShowError(err, 0) + os.Exit(0) + } + + err = src.InitMaintenance() + if err != nil { + src.ShowError(err, 0) + os.Exit(0) + } + + err = src.StartWebserver() + if err != nil { + src.ShowError(err, 0) + os.Exit(0) + } + +}