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

+788 -0 +/-2 browse
Add chart URL tool
Add chart URL tool

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