| 1 | // The console's icon set — every piece of interface chrome, one file. This |
| 2 | // is the server-side twin of Eleven's web/static/icons.js: static, in-code |
| 3 | // SVG strings in the Lucide idiom (24×24 viewBox, currentColor, stroke, round |
| 4 | // caps and joins), inlined into the page at render time. |
| 5 | // |
| 6 | // Vendored, never a CDN. The console has the same hard constraint as the |
| 7 | // fleet status page — no build step, no JavaScript framework, and no external |
| 8 | // request, ever. A <script src> or <link href> at some icon host would be a |
| 9 | // second origin the console cannot vouch for, cannot cache-control, and |
| 10 | // cannot serve when it is offline. Inlining the paths is the only option, so |
| 11 | // the paths live here. |
| 12 | // |
| 13 | // Icons are vendored from Lucide — https://lucide.dev — used under the ISC |
| 14 | // licence: |
| 15 | // |
| 16 | // Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as |
| 17 | // part of Feather (MIT). All other copyright (c) for Lucide are held by |
| 18 | // Lucide Contributors 2022. |
| 19 | // |
| 20 | // Permission to use, copy, modify, and/or distribute this software for any |
| 21 | // purpose with or without fee is hereby granted, provided that the above |
| 22 | // copyright notice and this permission notice appear in all copies. THE |
| 23 | // SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH |
| 24 | // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 25 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 26 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 27 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 28 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 29 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 30 | // |
| 31 | // Emoji policy, inherited from Eleven's docs/icons.md: emoji are CONTENT — a |
| 32 | // mark a person or a model chose, like the footer's factor-X disclosure — and |
| 33 | // never chrome. Interface furniture uses these icons. Adding one = paste the |
| 34 | // Lucide SVG's paths in below, keep the (Lucide: <slug>) comment, register it |
| 35 | // in the icons map, use it as {{icon "<slug>"}}. |
| 36 | // |
| 37 | // Each icon carries aria-hidden="true" because every one of them sits beside |
| 38 | // its own text label — the label is the accessible name, and a second one |
| 39 | // from the icon would only be read out twice. An icon used as the ONLY label |
| 40 | // must not use these constants bare: give its button or link an aria-label. |
| 41 | package console |
| 42 | |
| 43 | import "html/template" |
| 44 | |
| 45 | // template.HTML is used HERE AND ONLY HERE in the console, and this is why it |
| 46 | // is safe here and nowhere else: every string in this file is a compile-time |
| 47 | // constant of our own markup. No user input, no bucket object, no registry |
| 48 | // row, nothing derived from a request is ever concatenated into one. Anything |
| 49 | // from those sources must stay a plain string so html/template escapes it — |
| 50 | // a review in this repo has already flagged template.HTML on bucket-derived |
| 51 | // text as a Critical finding, and that judgement stands. |
| 52 | const ( |
| 53 | iconExternalLink template.HTML = iconOpen + `<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5"/></svg>` |
| 54 | // iconOpen is the shared opening tag: no width or height, so the icon |
| 55 | // sizes from the CSS .icon rule (1em) and tracks the text beside it. |
| 56 | iconOpen = `<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" ` + |
| 57 | `stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">` |
| 58 | |
| 59 | // The account switcher's disclosure caret (Lucide: chevron-down). |
| 60 | iconChevronDown template.HTML = iconOpen + `<path d="m6 9 6 6 6-6"/></svg>` |
| 61 | |
| 62 | // The switcher menu's mark on the account you are currently in |
| 63 | // (Lucide: check). |
| 64 | iconCheck template.HTML = iconOpen + `<path d="M20 6 9 17l-5-5"/></svg>` |
| 65 | |
| 66 | // "New account", in the same column the check sits in (Lucide: plus). |
| 67 | iconPlus template.HTML = iconOpen + `<path d="M5 12h14"/><path d="M12 5v14"/></svg>` |
| 68 | |
| 69 | // The step between two rungs of the ladder. It points DOWN because the |
| 70 | // ladder is drawn the way the spec draws it — canary at the top, stable |
| 71 | // at the foot — so the arrow follows the page, not the metaphor |
| 72 | // (Lucide: arrow-down). |
| 73 | iconArrowDown template.HTML = iconOpen + `<path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg>` |
| 74 | |
| 75 | // A route following a canary channel (Lucide: flask-conical). |
| 76 | iconFlask template.HTML = iconOpen + |
| 77 | `<path d="M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2"/>` + |
| 78 | `<path d="M6.453 15h11.094"/><path d="M8.5 2h7"/></svg>` |
| 79 | |
| 80 | // A canary that looks production-wired — the one badge on the app page |
| 81 | // that has to stop a reader (Lucide: triangle-alert). |
| 82 | iconTriangleAlert template.HTML = iconOpen + |
| 83 | `<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/>` + |
| 84 | `<path d="M12 9v4"/><path d="M12 17h.01"/></svg>` |
| 85 | |
| 86 | // A route's bound environment, beside the rung it is listed under |
| 87 | // (Lucide: layers). |
| 88 | iconLayers template.HTML = iconOpen + |
| 89 | `<path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z"/>` + |
| 90 | `<path d="m6.08 9.5-3.5 1.6a1 1 0 0 0 0 1.81l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83l-3.5-1.59"/>` + |
| 91 | `<path d="m6.08 14.5-3.5 1.6a1 1 0 0 0 0 1.81l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83l-3.5-1.59"/></svg>` |
| 92 | |
| 93 | // A canary/* route whose lease has run out (spec §5 "Canaries expire") — |
| 94 | // distinct from iconTriangleAlert's prod-wired warning, because expiry is |
| 95 | // a clock running out, not a hazard (Lucide: hourglass). |
| 96 | iconHourglass template.HTML = iconOpen + |
| 97 | `<path d="M5 22h14"/><path d="M5 2h14"/>` + |
| 98 | `<path d="M17 22v-4.172a2 2 0 0 0-.586-1.414L12 12l-4.414 4.414A2 2 0 0 0 7 17.828V22"/>` + |
| 99 | `<path d="M7 2v4.172a2 2 0 0 0 .586 1.414L12 12l4.414-4.414A2 2 0 0 0 17 6.172V2"/></svg>` |
| 100 | |
| 101 | // A pointer that arrived by hotfix — the ladder was bypassed, and the |
| 102 | // badge is permanent (Lucide: zap). |
| 103 | iconZap template.HTML = iconOpen + |
| 104 | `<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/></svg>` |
| 105 | |
| 106 | // A pointer whose bake window was waived — fast-tracked, badge equally |
| 107 | // permanent (Lucide: fast-forward). |
| 108 | iconFastForward template.HTML = iconOpen + |
| 109 | `<polygon points="13 19 22 12 13 5 13 19"/>` + |
| 110 | `<polygon points="2 19 11 12 2 5 2 19"/></svg>` |
| 111 | ) |
| 112 | |
| 113 | // icons maps each Lucide slug to its vendored markup. Slugs are the Lucide |
| 114 | // names so the mapping back to lucide.dev stays obvious. |
| 115 | var icons = map[string]template.HTML{ |
| 116 | "external-link": iconExternalLink, |
| 117 | "chevron-down": iconChevronDown, |
| 118 | "check": iconCheck, |
| 119 | "plus": iconPlus, |
| 120 | "arrow-down": iconArrowDown, |
| 121 | "flask-conical": iconFlask, |
| 122 | "triangle-alert": iconTriangleAlert, |
| 123 | "zap": iconZap, |
| 124 | "fast-forward": iconFastForward, |
| 125 | "layers": iconLayers, |
| 126 | "hourglass": iconHourglass, |
| 127 | } |
| 128 | |
| 129 | // icon renders one chrome icon by its Lucide slug: {{icon "chevron-down"}}. |
| 130 | // An unknown slug renders nothing rather than panicking a page mid-response — |
| 131 | // a typo must cost a missing caret, not a 500. TestTemplateIconSlugsAllExist |
| 132 | // is what actually catches the typo, at test time. |
| 133 | func icon(slug string) template.HTML { return icons[slug] } |
| 134 | |