Commit
Author: Krystian ChachuĊ‚a [krystian@krystianch.com]
Committer: Drew DeVault [sir@cmpwn.com] Mon, 13 Dec 2021 19:19:43 +0000
Hash: c543333cc0734f16b3fa5660f6024db559906432
Timestamp: Mon, 13 Dec 2021 19:19:43 +0000 (3 years ago)

+804 -0 +/-3 browse
Add chart URL tool
Add chart URL tool

Implements: https://todo.sr.ht/~sircmpwn/chartsrv/3
1diff --git a/main.go b/main.go
2index 6d90470..1c08906 100644
3--- a/main.go
4+++ b/main.go
5 @@ -10,6 +10,7 @@ import (
6 "net/http"
7 "net/url"
8 "os"
9+ "path/filepath"
10 "sort"
11 "strconv"
12 "strings"
13 @@ -300,6 +301,17 @@ func main() {
14 registerExtension(router, "svg", "image/svg+xml")
15 registerExtension(router, "png", "image/png")
16
17+ staticDir := os.Getenv("CHARTSRV_STATICDIR")
18+ if staticDir == "" {
19+ staticDir = "static"
20+ }
21+
22+ router.Get("/*", func(w http.ResponseWriter, r *http.Request) {
23+ workDir, _ := os.Getwd()
24+ fs := http.FileServer(http.Dir(filepath.Join(workDir, staticDir)))
25+ fs.ServeHTTP(w, r)
26+ })
27+
28 addr := ":8142"
29 if len(os.Args) > 2 {
30 addr = os.Args[2]
31 diff --git a/static/base.css b/static/base.css
32new file mode 100644
33index 0000000..b964f4d
34--- /dev/null
35+++ b/static/base.css
36 @@ -0,0 +1,540 @@
37+ * {
38+ box-sizing: border-box; }
39+
40+ html {
41+ line-height: 1.15;
42+ -webkit-text-size-adjust: 100%; }
43+
44+ body {
45+ margin: 0; }
46+
47+ main {
48+ display: block; }
49+
50+ h1 {
51+ font-size: 2em;
52+ margin: 0.67em 0; }
53+
54+ hr {
55+ box-sizing: content-box;
56+ height: 0;
57+ overflow: visible; }
58+
59+ pre {
60+ font-family: monospace, monospace;
61+ font-size: 1em; }
62+
63+ a {
64+ background-color: transparent; }
65+
66+ abbr[title] {
67+ border-bottom: none;
68+ text-decoration: underline;
69+ text-decoration: underline dotted; }
70+
71+ b,
72+ strong {
73+ font-weight: bolder; }
74+
75+ code,
76+ kbd,
77+ samp {
78+ font-family: monospace, monospace;
79+ font-size: 1em; }
80+
81+ small {
82+ font-size: 80%; }
83+
84+ sub,
85+ sup {
86+ font-size: 75%;
87+ line-height: 0;
88+ position: relative;
89+ vertical-align: baseline; }
90+
91+ sub {
92+ bottom: -0.25em; }
93+
94+ sup {
95+ top: -0.5em; }
96+
97+ img {
98+ border-style: none; }
99+
100+ button,
101+ input,
102+ optgroup,
103+ select,
104+ textarea {
105+ font-family: inherit;
106+ font-size: 100%;
107+ line-height: 1.15;
108+ margin: 0; }
109+
110+ button,
111+ input {
112+ overflow: visible; }
113+
114+ button,
115+ select {
116+ text-transform: none; }
117+
118+ button,
119+ [type="button"],
120+ [type="reset"],
121+ [type="submit"] {
122+ -webkit-appearance: button; }
123+
124+ button::-moz-focus-inner,
125+ [type="button"]::-moz-focus-inner,
126+ [type="reset"]::-moz-focus-inner,
127+ [type="submit"]::-moz-focus-inner {
128+ border-style: none;
129+ padding: 0; }
130+
131+ button:-moz-focusring,
132+ [type="button"]:-moz-focusring,
133+ [type="reset"]:-moz-focusring,
134+ [type="submit"]:-moz-focusring {
135+ outline: 1px dotted ButtonText; }
136+
137+ fieldset {
138+ padding: 0.35em 0.75em 0.625em; }
139+
140+ legend {
141+ box-sizing: border-box;
142+ color: inherit;
143+ display: table;
144+ max-width: 100%;
145+ padding: 0;
146+ white-space: normal; }
147+
148+ progress {
149+ vertical-align: baseline; }
150+
151+ textarea {
152+ overflow: auto; }
153+
154+ [type="checkbox"],
155+ [type="radio"] {
156+ box-sizing: border-box;
157+ padding: 0; }
158+
159+ [type="number"]::-webkit-inner-spin-button,
160+ [type="number"]::-webkit-outer-spin-button {
161+ height: auto; }
162+
163+ [type="search"] {
164+ -webkit-appearance: textfield;
165+ outline-offset: -2px; }
166+
167+ [type="search"]::-webkit-search-decoration {
168+ -webkit-appearance: none; }
169+
170+ ::-webkit-file-upload-button {
171+ -webkit-appearance: button;
172+ font: inherit; }
173+
174+ details {
175+ display: block; }
176+
177+ summary {
178+ display: list-item; }
179+
180+ template {
181+ display: none; }
182+
183+ [hidden] {
184+ display: none; }
185+
186+ @media (prefers-color-scheme: dark) {
187+ html {
188+ background: #212529;
189+ color: #fff; } }
190+
191+ body {
192+ font-family: sans-serif;
193+ padding-bottom: 1rem; }
194+
195+ main, .main {
196+ max-width: 1140px;
197+ margin: 0 auto; }
198+
199+ table.grid {
200+ width: 100%;
201+ border-collapse: collapse;
202+ margin: 0 -1rem; }
203+ table.grid td {
204+ vertical-align: top;
205+ padding: 0 1rem;
206+ width: 8.3333333333%; }
207+ table.grid td[colspan="1"] {
208+ width: 8.3333333333%; }
209+ table.grid td[colspan="2"] {
210+ width: 16.6666666667%; }
211+ table.grid td[colspan="3"] {
212+ width: 25%; }
213+ table.grid td[colspan="4"] {
214+ width: 33.3333333333%; }
215+ table.grid td[colspan="5"] {
216+ width: 41.6666666667%; }
217+ table.grid td[colspan="6"] {
218+ width: 50%; }
219+ table.grid td[colspan="7"] {
220+ width: 58.3333333333%; }
221+ table.grid td[colspan="8"] {
222+ width: 66.6666666667%; }
223+ table.grid td[colspan="9"] {
224+ width: 75%; }
225+ table.grid td[colspan="10"] {
226+ width: 83.3333333333%; }
227+ table.grid td[colspan="11"] {
228+ width: 91.6666666667%; }
229+ table.grid td[colspan="12"] {
230+ width: 100%; }
231+ @supports (display: flex) {
232+ table.grid {
233+ display: flex; }
234+ table.grid tbody {
235+ display: flex;
236+ flex-grow: 1;
237+ flex-direction: column; }
238+ table.grid tr {
239+ display: flex; }
240+ table.grid td {
241+ display: block; } }
242+
243+ .dl, article dl {
244+ text-align: left;
245+ margin-left: 0; }
246+
247+ .dt, article dt {
248+ font-weight: normal;
249+ padding: 0; }
250+
251+ .blockquote, article blockquote {
252+ margin-left: -1rem;
253+ margin-left: calc(-4px - 1rem);
254+ padding-left: 1rem;
255+ border-left: solid 4px #ced4da; }
256+ @media (prefers-color-scheme: dark) {
257+ .blockquote, article blockquote {
258+ border-left: solid 4px #6c757d; } }
259+ .figure, article figure {
260+ margin: 0; }
261+ .figure img, article figure img {
262+ display: block;
263+ max-width: 80%;
264+ margin: 0 auto; }
265+ .figure figcaption, article figure figcaption {
266+ display: block;
267+ text-align: center;
268+ margin: 0 auto;
269+ font-size: 0.9rem;
270+ max-width: 70%; }
271+
272+ .aside, article aside {
273+ float: right;
274+ max-width: 40%;
275+ padding-left: 1rem;
276+ margin-left: 1rem;
277+ border-left: solid 4px #ced4da; }
278+ @media (prefers-color-scheme: dark) {
279+ .aside, article aside {
280+ border-left: solid 4px #6c757d; } }
281+ .pre, article pre {
282+ background: #e9ecef;
283+ margin: 0 -1rem;
284+ padding: 1rem; }
285+ @media (prefers-color-scheme: dark) {
286+ .pre, article pre {
287+ background: #131618; } }
288+ .table, article table {
289+ width: 100%;
290+ border-collapse: collapse; }
291+ .table th, article table th {
292+ text-align: left;
293+ border-bottom: solid 1px #131618; }
294+ @media (prefers-color-scheme: dark) {
295+ .table th, article table th {
296+ border-bottom: solid 1px #fff; } }
297+ a, .link, .tabs h1 a, .tabs h2 a, .tabs h3 a, .tabs h4 a, .tabs h5 a {
298+ color: #007bff; }
299+ a:hover, .link:hover, .tabs h1 a:hover, .tabs h2 a:hover, .tabs h3 a:hover, .tabs h4 a:hover, .tabs h5 a:hover {
300+ text-decoration: none; }
301+ a:active, .link:active, .tabs h1 a:active, .tabs h2 a:active, .tabs h3 a:active, .tabs h4 a:active, .tabs h5 a:active {
302+ color: #0062cc; }
303+ a:visited, .link:visited, .tabs h1 a:visited, .tabs h2 a:visited, .tabs h3 a:visited, .tabs h4 a:visited, .tabs h5 a:visited {
304+ color: #004a99; }
305+ @media (prefers-color-scheme: dark) {
306+ a, .link, .tabs h1 a, .tabs h2 a, .tabs h3 a, .tabs h4 a, .tabs h5 a {
307+ color: #3395ff; }
308+ a:active, .link:active, .tabs h1 a:active, .tabs h2 a:active, .tabs h3 a:active, .tabs h4 a:active, .tabs h5 a:active {
309+ color: #006fe6; }
310+ a:visited, .link:visited, .tabs h1 a:visited, .tabs h2 a:visited, .tabs h3 a:visited, .tabs h4 a:visited, .tabs h5 a:visited {
311+ color: #006fe6; } }
312+ h1 small {
313+ font-size: 1.2rem; }
314+
315+ del {
316+ color: inherit; }
317+
318+ hr {
319+ border: #ced4da solid 1px; }
320+ @media (prefers-color-scheme: dark) {
321+ hr {
322+ border: #6c757d solid 1px; } }
323+ .align-center {
324+ text-align: center; }
325+
326+ .align-left {
327+ text-align: left; }
328+
329+ .align-right {
330+ text-align: right; }
331+
332+ .block {
333+ display: block !important; }
334+
335+ .inline {
336+ display: inline !important; }
337+
338+ .float-left {
339+ float: left; }
340+
341+ .float-right {
342+ float: right; }
343+
344+ .text-info {
345+ color: #17a2b8; }
346+
347+ .text-success {
348+ color: #28a745; }
349+
350+ .text-danger {
351+ color: #dc3545; }
352+
353+ .text-muted, form .help, input[disabled] + label {
354+ color: #343a40; }
355+ @media (prefers-color-scheme: dark) {
356+ .text-muted, form .help, input[disabled] + label {
357+ color: #adb5bd; } }
358+ .alert {
359+ padding: 0.5rem;
360+ border: 1px solid transparent;
361+ margin-bottom: 1rem; }
362+
363+ .alert-danger {
364+ background: #f8d7da;
365+ color: #842029;
366+ border-color: #f5c6cb; }
367+
368+ .btn, button {
369+ display: inline-block;
370+ padding: .1rem .75rem;
371+ background: #e9ecef;
372+ border: #343a40 1px solid;
373+ font-size: 0.9rem;
374+ font-weight: 400;
375+ line-height: 1.5;
376+ cursor: pointer;
377+ color: #131618;
378+ border-radius: 0;
379+ transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; }
380+ .btn:hover, button:hover {
381+ text-decoration: none;
382+ background: #f8f9fa;
383+ color: #131618; }
384+ @media (prefers-color-scheme: dark) {
385+ .btn, button {
386+ background: #212529;
387+ color: #fff;
388+ border: #495057 1px solid; }
389+ .btn:hover, button:hover {
390+ background: #131618;
391+ color: #fff; } }
392+ .btn-primary {
393+ border: #001933 1px solid;
394+ background: #007bff;
395+ color: #fff; }
396+ .btn-primary:hover {
397+ background: #0069d9;
398+ color: #fff; }
399+ @media (prefers-color-scheme: dark) {
400+ .btn-primary {
401+ background: #0062cc;
402+ color: #fff;
403+ border: #001933 1px solid; }
404+ .btn-primary:hover {
405+ background: #0069d9;
406+ color: #fff; } }
407+ a.btn {
408+ text-decoration: none;
409+ color: #131618; }
410+ @media (prefers-color-scheme: dark) {
411+ a.btn {
412+ color: #fff; } }
413+ a.btn-primary {
414+ color: #fff; }
415+ @media (prefers-color-scheme: dark) {
416+ a.btn-primary {
417+ color: #fff; } }
418+ .btn.block, button.block {
419+ margin-bottom: 0.5rem; }
420+
421+ .form-field, .form-checkbox {
422+ margin-top: 1rem; }
423+
424+ label {
425+ display: block; }
426+
427+ input, textarea, select {
428+ display: block;
429+ width: 100%;
430+ border: 1px solid #888;
431+ padding: .375rem;
432+ font-size: 1rem;
433+ line-height: 1.5;
434+ background-color: #fff;
435+ background-clip: padding-box;
436+ transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
437+ border-radius: 0; }
438+ input:focus, textarea:focus, select:focus {
439+ outline: 0;
440+ border-color: #80bdff;
441+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); }
442+ @media (prefers-color-scheme: dark) {
443+ input, textarea, select {
444+ background: #131618;
445+ color: #fff;
446+ border-color: #6c757d; }
447+ input:active, input:focus, textarea:active, textarea:focus, select:active, select:focus {
448+ background: #212529;
449+ color: #fff; }
450+ input[disabled], input[readonly], textarea[disabled], textarea[readonly], select[disabled], select[readonly] {
451+ background: #212529;
452+ color: #ced4da; } }
453+ .has-error input, .has-error textarea, .has-error select {
454+ border-color: #dc3545; }
455+
456+ .has-error .error {
457+ color: #dc3545; }
458+
459+ tr:first-child td .form-field:first-child {
460+ margin-top: 0; }
461+
462+ button {
463+ display: block; }
464+
465+ button.block {
466+ width: 100%; }
467+
468+ .form-checkbox input[type="checkbox"],
469+ .form-checkbox input[type="radio"] {
470+ float: left;
471+ width: 1em;
472+ height: 1em;
473+ margin-top: 0.1rem;
474+ margin-right: 0.25rem;
475+ vertical-align: top; }
476+
477+ .form-checkbox label {
478+ display: inline-block; }
479+
480+ .form-checkbox.inline {
481+ display: inline-block !important;
482+ margin-right: 1rem; }
483+
484+ fieldset {
485+ border: none;
486+ padding: 0;
487+ margin-top: 1rem; }
488+
489+ .inset, .infobox {
490+ background: #f8f9fa;
491+ padding: 1rem; }
492+ @media (prefers-color-scheme: dark) {
493+ .inset, .infobox {
494+ background: #131618; } }
495+ .infobox header {
496+ margin-bottom: 1rem; }
497+
498+ .infobox :not(header) {
499+ margin: 0; }
500+
501+ nav, .nav {
502+ max-width: 1140px;
503+ margin: 1rem auto; }
504+ nav ul, .nav ul {
505+ display: inline;
506+ margin: 0 -0.5rem;
507+ padding-left: 0; }
508+ nav li, .nav li {
509+ display: inline;
510+ margin: 0 0.5rem; }
511+ nav a, nav a:visited, .nav a, .nav a:visited {
512+ color: #131618; }
513+ @media (prefers-color-scheme: dark) {
514+ nav a, nav a:visited, .nav a, .nav a:visited {
515+ color: #fff; } }
516+ nav .active a, nav .active a:visited, .nav .active a, .nav .active a:visited {
517+ color: #007bff; }
518+ @media (prefers-color-scheme: dark) {
519+ nav .active a, nav .active a:visited, .nav .active a, .nav .active a:visited {
520+ color: #3395ff; } }
521+ nav .brand a, .nav .brand a {
522+ text-decoration: none; }
523+ nav .right, .nav .right {
524+ float: right; }
525+
526+ .tabs:not(.tabs-aside) {
527+ border-bottom: solid 3px #ced4da; }
528+ @media (prefers-color-scheme: dark) {
529+ .tabs:not(.tabs-aside) {
530+ border-bottom: solid 3px #131618; } }
531+ .tabs nav {
532+ margin: 0 auto;
533+ position: relative; }
534+
535+ .tabs h1, .tabs h2, .tabs h3, .tabs h4, .tabs h5 {
536+ display: inline;
537+ margin-right: 1rem;
538+ font-weight: normal; }
539+
540+ .tabs ul {
541+ display: inline;
542+ margin: 0 -0.5rem;
543+ padding-left: 0;
544+ position: absolute;
545+ bottom: 0; }
546+
547+ .tabs li {
548+ display: inline;
549+ margin: 0; }
550+ .tabs li a {
551+ padding: 0 1rem; }
552+ .tabs li.active, .tabs li:hover {
553+ background: #ced4da; }
554+ @media (prefers-color-scheme: dark) {
555+ .tabs li.active, .tabs li:hover {
556+ background: #131618; } }
557+ .tabs li.active a, .tabs li:hover a {
558+ color: #131618; }
559+ @media (prefers-color-scheme: dark) {
560+ .tabs li.active a, .tabs li:hover a {
561+ color: #fff; } }
562+ .tabs a {
563+ text-decoration: none; }
564+
565+ .tabs aside {
566+ background: #ced4da;
567+ padding: 0.2rem 0; }
568+ @media (prefers-color-scheme: dark) {
569+ .tabs aside {
570+ background: #131618; } }
571+ .tabs aside p {
572+ margin: 0 auto;
573+ max-width: 1140px; }
574+
575+ header + main {
576+ margin-top: 1rem; }
577 diff --git a/static/index.html b/static/index.html
578new file mode 100644
579index 0000000..e1e6bcf
580--- /dev/null
581+++ b/static/index.html
582 @@ -0,0 +1,252 @@
583+ <!doctype html>
584+ <html lang="en">
585+ <meta charset="utf-8" />
586+ <title>Chartsrv</title>
587+ <link rel="stylesheet" href="base.css" />
588+ <style>
589+ #preview, #alert { max-width: 100%; margin-top: 0.25em; }
590+ </style>
591+ <main>
592+ <form>
593+ <table class="grid">
594+ <tr>
595+ <td colspan="2"></td>
596+ <td colspan="10">
597+ <h1>Chartsrv</h1>
598+ <p>
599+ Web service which renders SVG plots from Prometheus data.
600+ <a href="https://sr.ht/~sircmpwn/chartsrv/">See on SourceHut.</a>
601+ </p>
602+ <noscript>
603+ <p class="text-danger">Please enable JavaScript to use this tool.</p>
604+ </noscript>
605+ </td>
606+ <td colspan="2"></td>
607+ </tr>
608+ <tr>
609+ <td colspan="3"></td>
610+ <td colspan="4">
611+ <div class="form-field">
612+ <label for="instance">
613+ Chartsrv instance <span class="text-danger">*</span>
614+ </label>
615+ <input type="text" id="instance" placeholder="metrics.sr.ht:8142" />
616+ </div>
617+ </td>
618+ <td colspan="2">
619+ <div class="form-field">
620+ <label for="format">Format</label>
621+ <select id="format">
622+ <option>svg</option>
623+ <option>png</option>
624+ </select>
625+ </div>
626+ </td>
627+ <td colspan="3"></td>
628+ </tr>
629+ <tr>
630+ <td colspan="3"></td>
631+ <td colspan="6">
632+ <div class="form-field">
633+ <label for="query">Query <span class="text-danger">*</span></label>
634+ <input type="text" id="query"
635+ value="avg_over_time(node_load15[1h])" />
636+ </div>
637+ <div class="form-checkbox">
638+ <input type="checkbox" id="stacked" />
639+ <label for="stacked" class="checkbox">Stacked</label>
640+ </div>
641+ </td>
642+ <td colspan="3"></td>
643+ </tr>
644+ <tr>
645+ <td colspan="3"></td>
646+ <td colspan="2">
647+ <div class="form-field">
648+ <label for="since">Time from</label>
649+ <input type="text" id="since" placeholder="24h" />
650+ </div>
651+ </td>
652+ <td colspan="2">
653+ <div class="form-field">
654+ <label for="until">Time to</label>
655+ <input type="text" id="until" />
656+ </div>
657+ </td>
658+ <td colspan="2">
659+ <div class="form-field">
660+ <label for="step">Step</label>
661+ <input type="number" id="step" />
662+ </div>
663+ </td>
664+ <td colspan="3"></td>
665+ </tr>
666+ <tr>
667+ <td colspan="3"></td>
668+ <td colspan="6">
669+ <span class="help">
670+ Time fields are relative to present and use
671+ <a href="https://godocs.io/time#ParseDuration">duration strings</a>.
672+ <em>Step</em> is in seconds.
673+ </span>
674+ </td>
675+ <td colspan="3"></td>
676+ </tr>
677+ <tr>
678+ <td colspan="3"></td>
679+ <td colspan="3">
680+ <div class="form-field">
681+ <label for="min">Y min</label>
682+ <input type="number" id="min" />
683+ </div>
684+ </td>
685+ <td colspan="3">
686+ <div class="form-field">
687+ <label for="max">Y max</label>
688+ <input type="number" id="max" />
689+ </div>
690+ </td>
691+ <td colspan="3"></td>
692+ </tr>
693+ <tr>
694+ <td colspan="3"></td>
695+ <td colspan="3">
696+ <div class="form-field">
697+ <label for="title">Title</label>
698+ <input type="text" id="title" />
699+ </div>
700+ </td>
701+ <td colspan="3">
702+ <div class="form-field">
703+ <label for="label">Label</label>
704+ <input type="text" id="label" placeholder="{{.instance}}"
705+ value="{{.instance}}" />
706+ </div>
707+ </td>
708+ <td colspan="3"></td>
709+ </tr>
710+ <tr>
711+ <td colspan="3"></td>
712+ <td colspan="6">
713+ <span class="help">
714+ <em>Label</em> accepts a
715+ <a href="https://godocs.io/text/template">template</a>.
716+ All Prometheus labels are available.
717+ </span>
718+ </td>
719+ <td colspan="3"></td>
720+ </tr>
721+ <tr>
722+ <td colspan="3"></td>
723+ <td colspan="3">
724+ <div class="form-field">
725+ <label for="width">Width</label>
726+ <input type="number" id="width" min="0" placeholder="12" />
727+ </div>
728+ </td>
729+ <td colspan="3">
730+ <div class="form-field">
731+ <label for="height">Height</label>
732+ <input type="number" id="height" min="0" placeholder="6" />
733+ </div>
734+ </td>
735+ <td colspan="3"></td>
736+ </tr>
737+ <tr>
738+ <td colspan="3"></td>
739+ <td colspan="6">
740+ <span class="help">
741+ Dimensions are in inches. DPI is fixed at 96.
742+ </span>
743+ </td>
744+ <td colspan="3"></td>
745+ </tr>
746+ <tr>
747+ <td colspan="3"></td>
748+ <td colspan="6">
749+ <div class="form-field">
750+ <label for="url">Chart URL</label>
751+ <input type="text" id="url" disabled />
752+ </div>
753+ </td>
754+ <td colspan="3"></td>
755+ </tr>
756+ <tr>
757+ <td colspan="3"></td>
758+ <td colspan="6">
759+ <div class="form-field">
760+ <label for="preview">Chart preview</label>
761+ <div id="alert" class="alert alert-danger" style="display: none">
762+ Loading preview failed. Are the parameters correct?
763+ </div>
764+ <img id="preview" alt="" />
765+ </div>
766+ </td>
767+ <td colspan="3"></td>
768+ </tr>
769+ </table>
770+ </form>
771+ </main>
772+
773+ <script>
774+ function getValue(id) {
775+ var el = document.getElementById(id);
776+
777+ if (el.type === "checkbox") {
778+ return el.checked ? id : "";
779+ }
780+
781+ return el.value;
782+ }
783+
784+ function updateChart() {
785+ var src = getValue("instance");
786+
787+ if (!/^[a-z]+\:\/{2}/.test(src)) {
788+ src = "http://" + src;
789+ }
790+ if (src[src.length - 1] !== "/") { src += "/"; }
791+
792+ src += "chart." + getValue("format") + "?";
793+
794+ var params = ["query", "title", "stacked", "since", "until", "width",
795+ "height", "step", "min", "max", "label"];
796+ var first = true;
797+ for (var i = 0; i < params.length; ++i) {
798+ var value = getValue(params[i]);
799+ if (value !== "") {
800+ if (!first) { src += "&"; }
801+ first = false;
802+
803+ src += params[i] + "=" + encodeURIComponent(getValue(params[i]));
804+ }
805+ }
806+
807+ document.getElementById("url").value = src;
808+ document.getElementById("preview").src = src;
809+ }
810+
811+ function showAlert() {
812+ document.getElementById("alert").style.display = "";
813+ }
814+
815+ function hideAlert() {
816+ document.getElementById("alert").style.display = "none";
817+ }
818+
819+ var instInp = document.getElementById("instance");
820+ if (instInp.value === "") {
821+ instInp.value = window.location.protocol + "//" + window.location.host;
822+ }
823+
824+ var inputs = document.getElementsByTagName("input");
825+ for (var i = 0; i < inputs.length; ++i) {
826+ inputs[i].onchange = updateChart;
827+ }
828+ document.getElementById("format").onchange = updateChart;
829+
830+ document.getElementById("preview").onerror = showAlert;
831+ document.getElementById("preview").onload = hideAlert;
832+
833+ updateChart();
834+ </script>