daisy-hook: accept a full "Name: value" header setting, redact the key from logs, retry once; start: brand icons on link and service tiles (trimmed simple-icons + saved favicons, monogram fallback)
All checks were successful
Deploy production / deploy (push) Successful in 1m7s
16
scripts/link-icons.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Trims the simple-icons marks used by src/lib/link-icons.ts into src/lib/link-icons.json,
|
||||||
|
// so /start ships a few KB of SVG paths instead of the whole icon set. Run after editing RULES.
|
||||||
|
import { writeFileSync } from 'node:fs';
|
||||||
|
import all from '@iconify-json/simple-icons/icons.json' with { type: 'json' };
|
||||||
|
import { RULES } from '../src/lib/link-icons.ts';
|
||||||
|
|
||||||
|
const icons = (all as { icons: Record<string, { body: string }> }).icons;
|
||||||
|
const out: Record<string, string> = {};
|
||||||
|
for (const r of RULES) {
|
||||||
|
if (!r.si) continue;
|
||||||
|
const icon = icons[r.si];
|
||||||
|
if (!icon) throw new Error(`simple-icons has no "${r.si}"`);
|
||||||
|
out[r.si] = icon.body;
|
||||||
|
}
|
||||||
|
writeFileSync('src/lib/link-icons.json', JSON.stringify(out, null, '\t') + '\n');
|
||||||
|
console.log(`wrote ${Object.keys(out).length} marks`);
|
||||||
41
src/lib/link-icons.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"gmail": "<path fill=\"currentColor\" d=\"M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64L12 9.548l6.545-4.91l1.528-1.145C21.69 2.28 24 3.434 24 5.457\"/>",
|
||||||
|
"googlecalendar": "<path fill=\"currentColor\" d=\"M18.316 5.684H24v12.632h-5.684zM5.684 24h12.632v-5.684H5.684zM18.316 5.684V0H1.895A1.894 1.894 0 0 0 0 1.895v16.421h5.684V5.684zm-7.207 6.25v-.065q.407-.216.687-.617c.28-.401.279-.595.279-.982q0-.568-.3-1.025a2.05 2.05 0 0 0-.832-.714a2.7 2.7 0 0 0-1.197-.257q-.9 0-1.481.467q-.579.467-.793 1.078l1.085.452q.13-.374.413-.633q.284-.258.767-.257q.495 0 .816.264a.86.86 0 0 1 .322.703q0 .495-.36.778t-.886.284h-.567v1.085h.633q.611 0 1.02.327q.407.327.407.843q0 .505-.387.832c-.387.327-.565.327-.924.327q-.527 0-.897-.311q-.372-.312-.521-.881l-1.096.452q.268.923.977 1.401q.707.479 1.538.477a2.84 2.84 0 0 0 1.293-.291q.574-.29.902-.794q.327-.505.327-1.149q0-.643-.344-1.105a2.07 2.07 0 0 0-.881-.689m2.093-1.931l.602.913L15 10.045v5.744h1.187V8.446h-.827zM22.105 0h-3.289v5.184H24V1.895A1.894 1.894 0 0 0 22.105 0m-3.289 23.5l4.684-4.684h-4.684zM0 22.105C0 23.152.848 24 1.895 24h3.289v-5.184H0z\"/>",
|
||||||
|
"googledrive": "<path fill=\"currentColor\" d=\"M12.01 1.485c-2.082 0-3.754.02-3.743.047c.01.02 1.708 3.001 3.774 6.62l3.76 6.574h3.76c2.081 0 3.753-.02 3.742-.047c-.005-.02-1.708-3.001-3.775-6.62l-3.76-6.574zm-4.76 1.73a789.828 789.861 0 0 0-3.63 6.319L0 15.868l1.89 3.298l1.885 3.297l3.62-6.335l3.618-6.33l-1.88-3.287C8.1 4.704 7.255 3.22 7.25 3.214zm2.259 12.653l-.203.348c-.114.198-.96 1.672-1.88 3.287a423.93 423.948 0 0 1-1.698 2.97c-.01.026 3.24.042 7.222.042h7.244l1.796-3.157c.992-1.734 1.85-3.23 1.906-3.323l.104-.167h-7.249z\"/>",
|
||||||
|
"googlephotos": "<path fill=\"currentColor\" d=\"M12.678 16.672c0 2.175.002 4.565-.001 6.494c-.001.576-.244.814-.817.833c-7.045.078-8.927-7.871-4.468-11.334c-1.95.016-4.019.007-5.986.007c-1.351 0-1.414-.01-1.405-1.351c.258-6.583 7.946-8.275 11.323-3.936L11.308.928c-.001-.695.212-.906.906-.925c6.409-.187 9.16 7.308 4.426 11.326l6.131.002c1.097 0 1.241.105 1.228 1.217c-.223 6.723-7.802 8.376-11.321 4.124m.002-15.284l-.003 9.972c6.56-.465 6.598-9.532.003-9.972m-1.36 21.224l-.001-9.97c-6.927.598-6.29 9.726.002 9.97zM1.4 11.315l9.95.008c-.527-6.829-9.762-6.367-9.95-.008m11.238 1.365c.682 6.875 9.67 6.284 9.977.01z\"/>",
|
||||||
|
"googlemaps": "<path fill=\"currentColor\" d=\"M19.527 4.799c1.212 2.608.937 5.678-.405 8.173c-1.101 2.047-2.744 3.74-4.098 5.614c-.619.858-1.244 1.75-1.669 2.727c-.141.325-.263.658-.383.992c-.121.333-.224.673-.34 1.008c-.109.314-.236.684-.627.687h-.007c-.466-.001-.579-.53-.695-.887c-.284-.874-.581-1.713-1.019-2.525c-.51-.944-1.145-1.817-1.79-2.671zM8.545 7.705l-3.959 4.707c.724 1.54 1.821 2.863 2.871 4.18q.371.465.737.936l4.984-5.925l-.029.01c-1.741.601-3.691-.291-4.392-1.987a3.4 3.4 0 0 1-.209-.716c-.063-.437-.077-.761-.004-1.198zM5.492 3.149l-.003.004c-1.947 2.466-2.281 5.88-1.117 8.77l4.785-5.689l-.058-.05zM14.661.436l-3.838 4.563l.027-.01c1.6-.551 3.403.15 4.22 1.626c.176.319.323.683.377 1.045c.068.446.085.773.012 1.22l-.003.016l3.836-4.561A8.38 8.38 0 0 0 14.67.439zM9.466 5.868L14.162.285l-.047-.012A8.3 8.3 0 0 0 11.986 0a8.44 8.44 0 0 0-6.169 2.766l-.016.018z\"/>",
|
||||||
|
"whatsapp": "<path fill=\"currentColor\" d=\"M17.472 14.382c-.297-.149-1.758-.867-2.03-.967c-.273-.099-.471-.148-.67.15c-.197.297-.767.966-.94 1.164c-.173.199-.347.223-.644.075c-.297-.15-1.255-.463-2.39-1.475c-.883-.788-1.48-1.761-1.653-2.059c-.173-.297-.018-.458.13-.606c.134-.133.298-.347.446-.52s.198-.298.298-.497c.099-.198.05-.371-.025-.52s-.669-1.612-.916-2.207c-.242-.579-.487-.5-.669-.51a13 13 0 0 0-.57-.01c-.198 0-.52.074-.792.372c-.272.297-1.04 1.016-1.04 2.479c0 1.462 1.065 2.875 1.213 3.074s2.096 3.2 5.077 4.487c.709.306 1.262.489 1.694.625c.712.227 1.36.195 1.871.118c.571-.085 1.758-.719 2.006-1.413s.248-1.289.173-1.413c-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 0 1-5.031-1.378l-.361-.214l-3.741.982l.998-3.648l-.235-.374a9.86 9.86 0 0 1-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884c2.64 0 5.122 1.03 6.988 2.898a9.82 9.82 0 0 1 2.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.82 11.82 0 0 0 12.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.9 11.9 0 0 0 5.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.82 11.82 0 0 0-3.48-8.413\"/>",
|
||||||
|
"discord": "<path fill=\"currentColor\" d=\"M20.317 4.37a19.8 19.8 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.3 18.3 0 0 0-5.487 0a13 13 0 0 0-.617-1.25a.08.08 0 0 0-.079-.037A19.7 19.7 0 0 0 3.677 4.37a.1.1 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.08.08 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.08.08 0 0 0 .084-.028a14 14 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13 13 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10 10 0 0 0 .372-.292a.07.07 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.07.07 0 0 1 .078.01q.181.149.373.292a.077.077 0 0 1-.006.127a12.3 12.3 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.08.08 0 0 0 .084.028a19.8 19.8 0 0 0 6.002-3.03a.08.08 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.06.06 0 0 0-.031-.03M8.02 15.33c-1.182 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418m7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418\"/>",
|
||||||
|
"x": "<path fill=\"currentColor\" d=\"M14.234 10.162L22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299l-.929-1.329L3.076 1.56h3.182l5.965 8.532l.929 1.329l7.754 11.09h-3.182z\"/>",
|
||||||
|
"ticktick": "<path fill=\"currentColor\" d=\"M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12s12-5.383 12-12h-2.7c0 5.128-4.172 9.3-9.3 9.3S2.7 17.128 2.7 12S6.872 2.7 12 2.7zm7.4 2.583l-7.505 9.371L8.388 9.08l-2.002 2.436l4.741 3.888a1.573 1.573 0 0 0 2.231-.233l8.504-10.617z\"/>",
|
||||||
|
"spotify": "<path fill=\"currentColor\" d=\"M12 0C5.4 0 0 5.4 0 12s5.4 12 12 12s12-5.4 12-12S18.66 0 12 0m5.521 17.34c-.24.359-.66.48-1.021.24c-2.82-1.74-6.36-2.101-10.561-1.141c-.418.122-.779-.179-.899-.539c-.12-.421.18-.78.54-.9c4.56-1.021 8.52-.6 11.64 1.32c.42.18.479.659.301 1.02m1.44-3.3c-.301.42-.841.6-1.262.3c-3.239-1.98-8.159-2.58-11.939-1.38c-.479.12-1.02-.12-1.14-.6s.12-1.021.6-1.141C9.6 9.9 15 10.561 18.72 12.84c.361.181.54.78.241 1.2m.12-3.36C15.24 8.4 8.82 8.16 5.16 9.301c-.6.179-1.2-.181-1.38-.721c-.18-.601.18-1.2.72-1.381c4.26-1.26 11.28-1.02 15.721 1.621c.539.3.719 1.02.419 1.56c-.299.421-1.02.599-1.559.3\"/>",
|
||||||
|
"lastdotfm": "<path fill=\"currentColor\" d=\"m10.584 17.21l-.88-2.392s-1.43 1.594-3.573 1.594c-1.897 0-3.244-1.649-3.244-4.288c0-3.382 1.704-4.591 3.381-4.591c2.42 0 3.189 1.567 3.849 3.574l.88 2.749c.88 2.666 2.529 4.81 7.285 4.81c3.409 0 5.718-1.044 5.718-3.793c0-2.227-1.265-3.381-3.63-3.931l-1.758-.385c-1.21-.275-1.567-.77-1.567-1.595c0-.934.742-1.484 1.952-1.484c1.32 0 2.034.495 2.144 1.677l2.749-.33c-.22-2.474-1.924-3.492-4.729-3.492c-2.474 0-4.893.935-4.893 3.932c0 1.87.907 3.051 3.189 3.601l1.87.44c1.402.33 1.869.907 1.869 1.704c0 1.017-.99 1.43-2.86 1.43c-2.776 0-3.93-1.457-4.59-3.464l-.907-2.75c-1.155-3.573-2.997-4.893-6.653-4.893C2.144 5.333 0 7.89 0 12.233c0 4.18 2.144 6.434 5.993 6.434c3.106 0 4.591-1.457 4.591-1.457\"/>",
|
||||||
|
"youtube": "<path fill=\"currentColor\" d=\"M23.498 6.186a3.02 3.02 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.02 3.02 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.02 3.02 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.02 3.02 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814M9.545 15.568V8.432L15.818 12z\"/>",
|
||||||
|
"twitch": "<path fill=\"currentColor\" d=\"M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714Z\"/>",
|
||||||
|
"osu": "<path fill=\"currentColor\" d=\"M7.698 10.362a1.94 1.94 0 0 0-.7-.516q-.421-.189-.988-.189c-.567 0-.704.063-.982.189s-.51.298-.692.516q-.273.328-.413.777q-.139.448-.139.96q0 .511.14.952q.139.44.412.767q.274.329.692.512t.982.184c.565 0 .707-.062.988-.184q.422-.184.7-.512q.279-.327.413-.767q.135-.44.135-.952a3.3 3.3 0 0 0-.135-.96a2.1 2.1 0 0 0-.413-.777m-.965 2.81q-.22.372-.723.372q-.494 0-.713-.372q-.22-.373-.22-1.073c0-.7.073-.824.22-1.073q.22-.372.713-.372q.503 0 .723.372q.22.373.22 1.073t-.22 1.073m11.89-.83l-.09-4.39a4.5 4.5 0 0 1 .69-.054q.351 0 .701.054l-.09 4.39q-.315.053-.601.053a3.5 3.5 0 0 1-.61-.054m1.319 1.4q0 .332-.054.664a4 4 0 0 1-.655.054a4 4 0 0 1-.664-.054a4 4 0 0 1-.054-.655q0-.323.054-.665a4 4 0 0 1 .655-.054q.323 0 .664.054q.054.341.054.656m-3.223-4.03q.315 0 .638.053v4.461q-.288.099-.759.193a5.3 5.3 0 0 1-1.863.023a1.7 1.7 0 0 1-.74-.305q-.32-.234-.507-.683q-.189-.449-.189-1.193V9.765a4 4 0 0 1 .638-.054q.313 0 .637.054v2.46q0 .367.058.606a.9.9 0 0 0 .18.377a.66.66 0 0 0 .3.197q.18.058.422.058q.332 0 .557-.062V9.765a4 4 0 0 1 .628-.054m-4.362 2.683q.08.225.08.548a1.4 1.4 0 0 1-.542 1.117q-.265.212-.642.333q-.378.12-.853.12a5 5 0 0 1-.395-.013a3 3 0 0 1-.346-.045a4 4 0 0 1-.327-.076a4 4 0 0 1-.35-.116a2.6 2.6 0 0 1 .085-.49a3 3 0 0 1 .175-.48q.296.117.561.175q.265.06.552.059q.126 0 .274-.023a1 1 0 0 0 .274-.08a.7.7 0 0 0 .21-.153a.35.35 0 0 0 .086-.247q0-.216-.13-.31a1.3 1.3 0 0 0-.364-.166l-.556-.162q-.503-.143-.786-.426q-.282-.283-.283-.848q0-.682.49-1.068q.489-.386 1.332-.386q.35 0 .692.062q.341.063.691.189a2.5 2.5 0 0 1-.09.485a2.3 2.3 0 0 1-.17.44a4 4 0 0 0-.476-.158a2.2 2.2 0 0 0-.548-.067q-.305 0-.476.094a.32.32 0 0 0-.17.301q0 .197.121.278t.346.153l.511.153q.252.072.454.175t.345.255t.225.377M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12c6.628 0 12-5.373 12-12S18.628 0 12 0m0 22.8C6.035 22.8 1.2 17.965 1.2 12S6.035 1.2 12 1.2S22.8 6.035 22.8 12S17.965 22.8 12 22.8\"/>",
|
||||||
|
"steam": "<path fill=\"currentColor\" d=\"M11.979 0C5.678 0 .511 4.86.022 11.037l6.432 2.658a3.4 3.4 0 0 1 1.912-.59q.094.001.188.006l2.861-4.142V8.91a4.53 4.53 0 0 1 4.524-4.524c2.494 0 4.524 2.031 4.524 4.527s-2.03 4.525-4.524 4.525h-.105l-4.076 2.911l.004.159a3.39 3.39 0 0 1-3.39 3.396a3.41 3.41 0 0 1-3.331-2.727L.436 15.27C1.862 20.307 6.486 24 11.979 24c6.627 0 11.999-5.373 11.999-12S18.605 0 11.979 0M7.54 18.21l-1.473-.61c.262.543.714.999 1.314 1.25a2.551 2.551 0 0 0 3.337-3.324a2.547 2.547 0 0 0-3.255-1.413l1.523.63a1.878 1.878 0 0 1-1.445 3.467zm11.415-9.303a3.02 3.02 0 0 0-3.015-3.015a3.015 3.015 0 1 0 3.015 3.015m-5.273-.005a2.264 2.264 0 1 1 4.531 0a2.267 2.267 0 0 1-2.266 2.265a2.264 2.264 0 0 1-2.265-2.265\"/>",
|
||||||
|
"reddit": "<path fill=\"currentColor\" d=\"M12 0C5.373 0 0 5.373 0 12c0 3.314 1.343 6.314 3.515 8.485l-2.286 2.286A.72.72 0 0 0 1.738 24H12c6.627 0 12-5.373 12-12S18.627 0 12 0m4.388 3.199a1.999 1.999 0 1 1-1.947 2.46v.002a2.37 2.37 0 0 0-2.032 2.341v.007c1.776.067 3.4.567 4.686 1.363a2.802 2.802 0 1 1 2.908 4.753c-.088 3.256-3.637 5.876-7.997 5.876c-4.361 0-7.905-2.617-7.998-5.87a2.8 2.8 0 0 1 1.189-5.34c.645 0 1.239.218 1.712.585c1.275-.79 2.881-1.291 4.64-1.365v-.01a3.23 3.23 0 0 1 2.88-3.207a2 2 0 0 1 1.959-1.595m-8.085 8.376c-.784 0-1.459.78-1.506 1.797s.64 1.429 1.426 1.429s1.371-.369 1.418-1.385s-.553-1.841-1.338-1.841m7.406 0c-.786 0-1.385.824-1.338 1.841s.634 1.385 1.418 1.385c.785 0 1.473-.413 1.426-1.429c-.046-1.017-.721-1.797-1.506-1.797m-3.703 4.013c-.974 0-1.907.048-2.77.135a.222.222 0 0 0-.183.305a3.2 3.2 0 0 0 2.953 1.964a3.2 3.2 0 0 0 2.953-1.964a.222.222 0 0 0-.184-.305a28 28 0 0 0-2.769-.135\"/>",
|
||||||
|
"linkedin": "<path fill=\"currentColor\" d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037c-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85c3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.06 2.06 0 0 1-2.063-2.065a2.064 2.064 0 1 1 2.063 2.065m1.782 13.019H3.555V9h3.564zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0z\"/>",
|
||||||
|
"claude": "<path fill=\"currentColor\" d=\"m4.714 15.956l4.718-2.648l.079-.23l-.08-.128h-.23l-.79-.048l-2.695-.073l-2.337-.097l-2.265-.122l-.57-.121l-.535-.704l.055-.353l.48-.321l.685.06l1.518.104l2.277.157l1.651.098l2.447.255h.389l.054-.158l-.133-.097l-.103-.098l-2.356-1.596l-2.55-1.688l-1.336-.972l-.722-.491L2 6.223l-.158-1.008l.656-.722l.88.06l.224.061l.893.686l1.906 1.476l2.49 1.833l.364.304l.146-.104l.018-.072l-.164-.274l-1.354-2.446l-1.445-2.49l-.644-1.032l-.17-.619a3 3 0 0 1-.103-.729L6.287.133L6.7 0l.995.134l.42.364l.619 1.415L9.735 4.14l1.555 3.03l.455.898l.243.832l.09.255h.159V9.01l.127-1.706l.237-2.095l.23-2.695l.08-.76l.376-.91l.747-.492l.583.28l.48.685l-.067.444l-.286 1.851l-.558 2.903l-.365 1.942h.213l.243-.242l.983-1.306l1.652-2.064l.728-.82l.85-.904l.547-.431h1.032l.759 1.129l-.34 1.166l-1.063 1.347l-.88 1.142l-1.263 1.7l-.79 1.36l.074.11l.188-.02l2.853-.606l1.542-.28l1.84-.315l.832.388l.09.395l-.327.807l-1.967.486l-2.307.462l-3.436.813l-.043.03l.049.061l1.548.146l.662.036h1.62l3.018.225l.79.522l.473.638l-.08.485l-1.213.62l-1.64-.389l-3.825-.91l-1.31-.329h-.183v.11l1.093 1.068l2.003 1.81l2.508 2.33l.127.578l-.321.455l-.34-.049l-2.204-1.657l-.85-.747l-1.925-1.62h-.127v.17l.443.649l2.343 3.521l.122 1.08l-.17.353l-.607.213l-.668-.122l-1.372-1.924l-1.415-2.168l-1.141-1.943l-.14.08l-.674 7.254l-.316.37l-.728.28l-.607-.461l-.322-.747l.322-1.476l.388-1.924l.316-1.53l.285-1.9l.17-.632l-.012-.042l-.14.018l-1.432 1.967l-2.18 2.945l-1.724 1.845l-.413.164l-.716-.37l.066-.662l.401-.589l2.386-3.036l1.439-1.882l.929-1.086l-.006-.158h-.055L4.138 18.56l-1.13.146l-.485-.456l.06-.746l.231-.243l1.907-1.312Z\"/>",
|
||||||
|
"revolut": "<path fill=\"currentColor\" d=\"M20.913 6.957C20.913 3.12 17.79 0 13.95 0H2.424v3.86h10.978c1.738 0 3.177 1.366 3.209 3.044a3.08 3.08 0 0 1-.888 2.232a3.08 3.08 0 0 1-2.214.93H9.232a.276.276 0 0 0-.275.275v3.431q0 .09.052.162L16.265 24h5.311l-7.273-10.094c3.663-.184 6.61-3.261 6.61-6.95M6.894 5.923h-4.47V24h4.47z\"/>",
|
||||||
|
"gitea": "<path fill=\"currentColor\" d=\"M4.209 4.603c-.247 0-.525.02-.84.088c-.333.07-1.28.283-2.054 1.027C-.403 7.25.035 9.685.089 10.052c.065.446.263 1.687 1.21 2.768c1.749 2.141 5.513 2.092 5.513 2.092s.462 1.103 1.168 2.119c.955 1.263 1.936 2.248 2.89 2.367c2.406 0 7.212-.004 7.212-.004s.458.004 1.08-.394c.535-.324 1.013-.893 1.013-.893s.492-.527 1.18-1.73c.21-.37.385-.729.538-1.068c0 0 2.107-4.471 2.107-8.823c-.042-1.318-.367-1.55-.443-1.627c-.156-.156-.366-.153-.366-.153s-4.475.252-6.792.306c-.508.011-1.012.023-1.512.027v4.474l-.634-.301c0-1.39-.004-4.17-.004-4.17c-1.107.016-3.405-.084-3.405-.084s-5.399-.27-5.987-.324c-.187-.011-.401-.032-.648-.032zm.354 1.832h.111s.271 2.269.6 3.597C5.549 11.147 6.22 13 6.22 13s-.996-.119-1.641-.348c-.99-.324-1.409-.714-1.409-.714s-.73-.511-1.096-1.52C1.444 8.73 2.021 7.7 2.021 7.7s.32-.859 1.47-1.145c.395-.106.863-.12 1.072-.12m8.33 2.554c.26.003.509.127.509.127l.868.422l-.529 1.075a.69.69 0 0 0-.614.359a.69.69 0 0 0 .072.756l-.939 1.924a.69.69 0 0 0-.66.527a.69.69 0 0 0 .347.763a.686.686 0 0 0 .867-.206a.69.69 0 0 0-.069-.882l.916-1.874a.7.7 0 0 0 .237-.02a.66.66 0 0 0 .271-.137a9 9 0 0 1 1.016.512a.76.76 0 0 1 .286.282c.073.21-.073.569-.073.569c-.087.29-.702 1.55-.702 1.55a.69.69 0 0 0-.676.477a.681.681 0 1 0 1.157-.252c.073-.141.141-.282.214-.431c.19-.397.515-1.16.515-1.16c.035-.066.218-.394.103-.814c-.095-.435-.48-.638-.48-.638c-.467-.301-1.116-.58-1.116-.58s0-.156-.042-.27a.7.7 0 0 0-.148-.241l.516-1.062l2.89 1.401s.48.218.583.619c.073.282-.019.534-.069.657c-.24.587-2.1 4.317-2.1 4.317s-.232.554-.748.588a1.1 1.1 0 0 1-.393-.045l-.202-.08l-4.31-2.1s-.417-.218-.49-.596c-.083-.31.104-.691.104-.691l2.073-4.272s.183-.37.466-.497a.9.9 0 0 1 .35-.077\"/>",
|
||||||
|
"immich": "<path fill=\"currentColor\" d=\"M11.986.27C9.577.27 6.78 1.36 6.78 4.164v.152c1.343.597 2.935 1.663 4.412 2.971c1.572 1.391 2.838 2.882 3.653 4.287c1.4-2.503 2.336-5.478 2.347-7.373v-.037c0-2.803-2.796-3.895-5.205-3.895m7.512 4.49a3.6 3.6 0 0 0-1.191.186l-.145.047c-.152 1.461-.675 3.304-1.463 5.113c-.837 1.925-1.862 3.59-2.947 4.8c2.813.557 5.93.527 7.736-.048l.035-.01c2.667-.866 2.84-3.863 2.096-6.154c-.628-1.933-2.08-3.89-4.12-3.933m-14.997.039C2.462 4.842 1.01 6.797.381 8.73c-.744 2.291-.57 5.288 2.096 6.155l.144.047c.982-1.093 2.487-2.277 4.188-3.278c1.808-1.064 3.619-1.808 5.207-2.148c-1.949-2.105-4.489-3.913-6.287-4.51l-.036-.012A3.6 3.6 0 0 0 4.502 4.8m4.672 6.758c-2.604 1.202-5.109 3.06-6.233 4.586l-.021.029c-1.648 2.268-.027 4.795 1.922 6.21c1.948 1.417 4.852 2.177 6.5-.091l.09-.121c-.736-1.272-1.396-3.072-1.823-4.998c-.453-2.05-.602-4-.435-5.615m1.072 3.338c.339 2.847 1.332 5.803 2.436 7.343l.021.03c1.648 2.268 4.551 1.507 6.5.091s3.57-3.943 1.922-6.21l-.088-.124c-1.437.307-3.352.38-5.316.19c-2.09-.202-3.99-.663-5.475-1.32\"/>",
|
||||||
|
"umami": "<path fill=\"currentColor\" d=\"M2.203 8.611H.857a.845.845 0 0 0-.841.841v.858a13 13 0 0 0-.016.6c0 6.627 5.373 12 12 12c6.527 0 11.837-5.212 11.996-11.701c0-.025.004-.05.004-.075V9.452a.845.845 0 0 0-.841-.841h-1.346c-1.159-4.329-5.112-7.521-9.805-7.521S3.363 4.282 2.203 8.611m18.444 0H3.37c1.127-3.702 4.57-6.399 8.638-6.399c4.069 0 7.512 2.697 8.639 6.399\"/>",
|
||||||
|
"postgresql": "<path fill=\"currentColor\" d=\"M23.56 14.723a.5.5 0 0 0-.057-.12q-.21-.395-1.007-.231c-1.654.34-2.294.13-2.526-.02c1.342-2.048 2.445-4.522 3.041-6.83c.272-1.05.798-3.523.122-4.73a1.6 1.6 0 0 0-.15-.236C21.693.91 19.8.025 17.51.001c-1.495-.016-2.77.346-3.116.479a10 10 0 0 0-.516-.082a8 8 0 0 0-1.312-.127c-1.182-.019-2.203.264-3.05.84C8.66.79 4.729-.534 2.296 1.19C.935 2.153.309 3.873.43 6.304c.041.818.507 3.334 1.243 5.744q.69 2.26 1.433 3.582q.83 1.493 1.714 1.79c.448.148 1.133.143 1.858-.729a56 56 0 0 1 1.945-2.206c.435.235.906.362 1.39.377v.004a11 11 0 0 0-.247.305c-.339.43-.41.52-1.5.745c-.31.064-1.134.233-1.146.811a.6.6 0 0 0 .091.327c.227.423.922.61 1.015.633c1.335.333 2.505.092 3.372-.679c-.017 2.231.077 4.418.345 5.088c.221.553.762 1.904 2.47 1.904q.375.001.829-.094c1.782-.382 2.556-1.17 2.855-2.906c.15-.87.402-2.875.539-4.101c.017-.07.036-.12.057-.136c0 0 .07-.048.427.03l.044.007l.254.022l.015.001c.847.039 1.911-.142 2.531-.43c.644-.3 1.806-1.033 1.595-1.67M2.37 11.876c-.744-2.435-1.178-4.885-1.212-5.571c-.109-2.172.417-3.683 1.562-4.493c1.837-1.299 4.84-.54 6.108-.13l-.01.01C6.795 3.734 6.843 7.226 6.85 7.44c0 .082.006.199.016.36c.034.586.1 1.68-.074 2.918c-.16 1.15.194 2.276.973 3.089q.12.126.252.237c-.347.371-1.1 1.193-1.903 2.158c-.568.682-.96.551-1.088.508c-.392-.13-.813-.587-1.239-1.322c-.48-.839-.963-2.032-1.415-3.512m6.007 5.088a1.6 1.6 0 0 1-.432-.178c.089-.039.237-.09.483-.14c1.284-.265 1.482-.451 1.915-1a8 8 0 0 1 .367-.443a.4.4 0 0 0 .074-.13c.17-.151.272-.11.436-.042c.156.065.308.26.37.475c.03.102.062.295-.045.445c-.904 1.266-2.222 1.25-3.168 1.013m2.094-3.988l-.052.14c-.133.357-.257.689-.334 1.004c-.667-.002-1.317-.288-1.81-.803c-.628-.655-.913-1.566-.783-2.5c.183-1.308.116-2.447.08-3.059l-.013-.22c.296-.262 1.666-.996 2.643-.772c.446.102.718.406.83.928c.585 2.704.078 3.83-.33 4.736a9 9 0 0 0-.23.546m7.364 4.572q-.024.266-.062.596l-.146.438a.4.4 0 0 0-.018.108c-.006.475-.054.649-.115.87a4.8 4.8 0 0 0-.18 1.057c-.11 1.414-.878 2.227-2.417 2.556c-1.515.325-1.784-.496-2.02-1.221a7 7 0 0 0-.078-.227c-.215-.586-.19-1.412-.157-2.555c.016-.561-.025-1.901-.33-2.646q.006-.44.019-.892a.4.4 0 0 0-.016-.113a2 2 0 0 0-.044-.208c-.122-.428-.42-.786-.78-.935c-.142-.059-.403-.167-.717-.087c.067-.276.183-.587.309-.925l.053-.142c.06-.16.134-.325.213-.5c.426-.948 1.01-2.246.376-5.178c-.237-1.098-1.03-1.634-2.232-1.51c-.72.075-1.38.366-1.709.532a6 6 0 0 0-.196.104c.092-1.106.439-3.174 1.736-4.482a4 4 0 0 1 .303-.276a.35.35 0 0 0 .145-.064c.752-.57 1.695-.85 2.802-.833q.616.01 1.174.081c1.94.355 3.244 1.447 4.036 2.383c.814.962 1.255 1.931 1.431 2.454c-1.323-.134-2.223.127-2.68.78c-.992 1.418.544 4.172 1.282 5.496c.135.242.252.452.289.54c.24.583.551.972.778 1.256c.07.087.138.171.189.245c-.4.116-1.12.383-1.055 1.717a35 35 0 0 1-.084.815c-.046.208-.07.46-.1.766m.89-1.621c-.04-.832.27-.919.597-1.01l.135-.041a1 1 0 0 0 .134.103c.57.376 1.583.421 3.007.134c-.202.177-.519.4-.953.601c-.41.19-1.096.333-1.747.364c-.72.034-1.086-.08-1.173-.151m.57-9.271a7 7 0 0 1-.105 1.001c-.055.358-.112.728-.127 1.177c-.014.436.04.89.093 1.33c.107.887.216 1.8-.207 2.701a4 4 0 0 1-.188-.385a8 8 0 0 0-.325-.617c-.616-1.104-2.057-3.69-1.32-4.744c.38-.543 1.342-.566 2.179-.463m.228 7.013l-.085-.107l-.035-.044c.726-1.2.584-2.387.457-3.439c-.052-.432-.1-.84-.088-1.222c.013-.407.066-.755.118-1.092c.064-.415.13-.844.111-1.35a.6.6 0 0 0 .012-.19c-.046-.486-.6-1.938-1.73-3.253a7.8 7.8 0 0 0-2.688-2.04A9.3 9.3 0 0 1 17.62.746c2.052.046 3.675.814 4.824 2.283a1 1 0 0 1 .067.1c.723 1.356-.276 6.275-2.987 10.54m-8.816-6.116c-.025.18-.31.423-.621.423l-.081-.006a.8.8 0 0 1-.506-.315c-.046-.06-.12-.178-.106-.285a.22.22 0 0 1 .093-.149c.118-.089.352-.122.61-.086c.316.044.642.193.61.418m7.93-.411c.011.08-.049.2-.153.31a.72.72 0 0 1-.408.223l-.075.005c-.293 0-.541-.234-.56-.371c-.024-.177.264-.31.56-.352c.298-.042.612.009.636.185\"/>",
|
||||||
|
"docker": "<path fill=\"currentColor\" d=\"M13.983 11.078h2.119a.186.186 0 0 0 .186-.185V9.006a.186.186 0 0 0-.186-.186h-2.119a.185.185 0 0 0-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 0 0 .186-.186V3.574a.186.186 0 0 0-.186-.185h-2.118a.185.185 0 0 0-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 0 0 .186-.186V6.29a.186.186 0 0 0-.186-.185h-2.118a.185.185 0 0 0-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 0 0 .184-.186V6.29a.185.185 0 0 0-.185-.185H8.1a.185.185 0 0 0-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 0 0 .185-.186V6.29a.185.185 0 0 0-.185-.185H5.136a.186.186 0 0 0-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 0 0 .186-.185V9.006a.186.186 0 0 0-.186-.186h-2.118a.185.185 0 0 0-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 0 0 .184-.185V9.006a.185.185 0 0 0-.184-.186h-2.12a.185.185 0 0 0-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 0 0 .185-.185V9.006a.185.185 0 0 0-.184-.186h-2.12a.186.186 0 0 0-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 0 0 .184-.185V9.006a.185.185 0 0 0-.184-.186h-2.12a.185.185 0 0 0-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51q-.508.001-1.01.087c-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199l-.226.327c-.284.438-.49.922-.612 1.43c-.23.97-.09 1.882.403 2.661c-.595.332-1.55.413-1.744.42H.751a.75.75 0 0 0-.75.748a11.4 11.4 0 0 0 .692 4.062c.545 1.428 1.355 2.48 2.41 3.124c1.18.723 3.1 1.137 5.275 1.137a15.7 15.7 0 0 0 2.93-.266a12.3 12.3 0 0 0 3.823-1.389a10.5 10.5 0 0 0 2.61-2.136c1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009c.309-.293.55-.65.707-1.046l.098-.288Z\"/>",
|
||||||
|
"svelte": "<path fill=\"currentColor\" d=\"M10.354 21.125a4.44 4.44 0 0 1-4.765-1.767a4.1 4.1 0 0 1-.703-3.107a4 4 0 0 1 .134-.522l.105-.321l.287.21a7.2 7.2 0 0 0 2.186 1.092l.208.063l-.02.208a1.25 1.25 0 0 0 .226.83a1.34 1.34 0 0 0 1.435.533a1.2 1.2 0 0 0 .343-.15l5.59-3.562a1.16 1.16 0 0 0 .524-.778a1.24 1.24 0 0 0-.211-.937a1.34 1.34 0 0 0-1.435-.533a1.2 1.2 0 0 0-.343.15l-2.133 1.36a4 4 0 0 1-1.135.499a4.44 4.44 0 0 1-4.765-1.766a4.1 4.1 0 0 1-.702-3.108a3.86 3.86 0 0 1 1.742-2.582l5.589-3.563a4 4 0 0 1 1.135-.499a4.44 4.44 0 0 1 4.765 1.767a4.1 4.1 0 0 1 .703 3.107a4 4 0 0 1-.134.522l-.105.321l-.286-.21a7.2 7.2 0 0 0-2.187-1.093l-.208-.063l.02-.207a1.25 1.25 0 0 0-.226-.831a1.34 1.34 0 0 0-1.435-.532a1.2 1.2 0 0 0-.343.15L8.62 9.368a1.16 1.16 0 0 0-.524.778a1.24 1.24 0 0 0 .211.937a1.34 1.34 0 0 0 1.435.533a1.2 1.2 0 0 0 .344-.151l2.132-1.36a4 4 0 0 1 1.135-.498a4.44 4.44 0 0 1 4.765 1.766a4.1 4.1 0 0 1 .702 3.108a3.86 3.86 0 0 1-1.742 2.583l-5.589 3.562a4 4 0 0 1-1.135.499m10.358-17.95C18.484-.015 14.082-.96 10.9 1.068L5.31 4.63a6.4 6.4 0 0 0-2.896 4.295a6.75 6.75 0 0 0 .666 4.336a6.4 6.4 0 0 0-.96 2.396a6.83 6.83 0 0 0 1.168 5.167c2.229 3.19 6.63 4.135 9.812 2.108l5.59-3.562a6.4 6.4 0 0 0 2.896-4.295a6.76 6.76 0 0 0-.665-4.336a6.4 6.4 0 0 0 .958-2.396a6.83 6.83 0 0 0-1.167-5.168\"/>",
|
||||||
|
"netdata": "<path fill=\"currentColor\" d=\"M14.764 21.827H9.922L0 2.173h14.084c5.476.01 9.913 4.565 9.916 10.183c-.009 5.235-4.14 9.47-9.238 9.47z\"/>",
|
||||||
|
"portainer": "<path fill=\"currentColor\" d=\"M12.504 0v1.023l-.01-.015l-6.106 3.526H3.417v.751h5.359v3.638h1.942V5.284h1.786V15.7c.027 0 .54-.01.751.091V5.285h.531v10.608c.293.147.55.312.751.54V5.286h6.046v-.75h-1.267l-6.061-3.5V0zm0 1.87v2.664H7.889zm.751.031l4.56 2.633h-4.56zM9.142 5.285h1.21v1.686h-1.21zm-4.736 2.73v1.951h1.942v-1.95zm2.19 0v1.951h1.941v-1.95zm-2.19 2.171v1.951h1.942v-1.95zm2.19 0v1.951h1.941v-1.95zm2.18 0v1.951h1.942v-1.95zM4.36 12.43a3.73 3.73 0 0 0-.494 1.851c0 1.227.604 2.308 1.52 2.986c.239-.064.477-.1.724-.11c.1 0 .165.01.266.019c.284-1.191 1.383-1.988 2.665-1.988c.724 0 1.438.201 1.924.668c.229-.476.302-1.007.302-1.575c0-.65-.165-1.292-.494-1.85zm4.828 3.16c-1.21 0-2.226.844-2.492 1.97a1 1 0 0 0-.275-.009a2.56 2.56 0 0 0-2.564 2.556a2.565 2.565 0 0 0 3.096 2.5A2.58 2.58 0 0 0 9.233 24c.862 0 1.622-.43 2.09-1.081a2.557 2.557 0 0 0 4.186-1.97c0-.567-.193-1.099-.504-1.52a2.557 2.557 0 0 0-3.866-2.94a2.57 2.57 0 0 0-1.951-.898z\"/>",
|
||||||
|
"ntfy": "<path fill=\"currentColor\" d=\"M12.597 13.693v2.156h6.205v-2.156ZM5.183 6.549v2.363l3.591 1.901l.023.01l-.023.009l-3.591 1.901v2.35l.386-.211l5.456-2.969V9.729ZM3.659 2.037C1.915 2.037.42 3.41.42 5.154v.002L.438 18.73L0 21.963l5.956-1.583h14.806c1.744 0 3.238-1.374 3.238-3.118V5.154c0-1.744-1.493-3.116-3.237-3.117h-.001zm0 2.2h17.104c.613.001 1.037.447 1.037.917v12.108c0 .47-.424.916-1.038.916H5.633l-3.026.915l.031-.179l-.017-13.76c0-.47.424-.917 1.038-.917\"/>",
|
||||||
|
"github": "<path fill=\"currentColor\" d=\"M12 .297c-6.63 0-12 5.373-12 12c0 5.303 3.438 9.8 8.205 11.385c.6.113.82-.258.82-.577c0-.285-.01-1.04-.015-2.04c-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729c1.205.084 1.838 1.236 1.838 1.236c1.07 1.835 2.809 1.305 3.495.998c.108-.776.417-1.305.76-1.605c-2.665-.3-5.466-1.332-5.466-5.93c0-1.31.465-2.38 1.235-3.22c-.135-.303-.54-1.523.105-3.176c0 0 1.005-.322 3.3 1.23c.96-.267 1.98-.399 3-.405c1.02.006 2.04.138 3 .405c2.28-1.552 3.285-1.23 3.285-1.23c.645 1.653.24 2.873.12 3.176c.765.84 1.23 1.91 1.23 3.22c0 4.61-2.805 5.625-5.475 5.92c.42.36.81 1.096.81 2.22c0 1.606-.015 2.896-.015 3.286c0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\"/>",
|
||||||
|
"cursor": "<path fill=\"currentColor\" d=\"M11.503.131L1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23\"/>",
|
||||||
|
"wix": "<path fill=\"currentColor\" d=\"m0 7.354l2.113 9.292h.801a1.54 1.54 0 0 0 1.506-1.218l1.351-6.34a.17.17 0 0 1 .167-.137c.08 0 .15.058.167.137l1.352 6.34a1.54 1.54 0 0 0 1.506 1.218h.805l2.113-9.292h-.565c-.62 0-1.159.43-1.296 1.035l-1.26 5.545l-1.106-5.176a1.76 1.76 0 0 0-2.19-1.324c-.639.176-1.113.716-1.251 1.365l-1.094 5.127l-1.26-5.537A1.33 1.33 0 0 0 .563 7.354zm13.992 0a.95.95 0 0 0-.951.95v8.342h.635a.95.95 0 0 0 .951-.95V7.353h-.635zm1.778 0l3.158 4.66l-3.14 4.632h1.325c.368 0 .712-.181.918-.486l1.756-2.59a.12.12 0 0 1 .197 0l1.754 2.59c.206.305.55.486.918.486h1.326l-3.14-4.632L24 7.354h-1.326c-.368 0-.712.181-.918.486l-1.772 2.617a.12.12 0 0 1-.197 0L18.014 7.84a1.11 1.11 0 0 0-.918-.486z\"/>",
|
||||||
|
"drizzle": "<path fill=\"currentColor\" d=\"M5.353 11.823a1.036 1.036 0 0 0-.395-1.422a1.063 1.063 0 0 0-1.437.399L.138 16.702a1.035 1.035 0 0 0 .395 1.422a1.063 1.063 0 0 0 1.437-.398zm11.216 0a1.036 1.036 0 0 0-.394-1.422a1.064 1.064 0 0 0-1.438.399l-3.382 5.902a1.036 1.036 0 0 0 .394 1.422c.506.283 1.15.104 1.438-.398zm7.293-4.525a1.036 1.036 0 0 0-.395-1.422a1.06 1.06 0 0 0-1.437.399l-3.383 5.902a1.036 1.036 0 0 0 .395 1.422a1.063 1.063 0 0 0 1.437-.399zm-11.219 0a1.035 1.035 0 0 0-.394-1.422a1.064 1.064 0 0 0-1.438.398l-3.382 5.903a1.036 1.036 0 0 0 .394 1.422c.506.282 1.15.104 1.438-.399z\"/>",
|
||||||
|
"cloudflare": "<path fill=\"currentColor\" d=\"M16.509 16.845c.147-.507.09-.971-.155-1.316c-.225-.316-.605-.499-1.062-.52l-8.66-.113a.16.16 0 0 1-.133-.07a.2.2 0 0 1-.02-.156a.24.24 0 0 1 .203-.156l8.736-.113c1.035-.049 2.16-.886 2.554-1.913l.499-1.302a.27.27 0 0 0 .014-.168a5.689 5.689 0 0 0-10.937-.584a2.58 2.58 0 0 0-1.794-.498a2.56 2.56 0 0 0-2.223 3.18A3.634 3.634 0 0 0 0 16.751q.002.264.035.527a.174.174 0 0 0 .17.148h15.98a.22.22 0 0 0 .204-.155zm2.757-5.564c-.077 0-.161 0-.239.011c-.056 0-.105.042-.127.098l-.337 1.174c-.148.507-.092.971.154 1.317c.225.316.605.498 1.062.52l1.844.113c.056 0 .105.026.133.07a.2.2 0 0 1 .021.156a.24.24 0 0 1-.204.156l-1.92.112c-1.042.049-2.159.887-2.553 1.914l-.141.358c-.028.072.021.142.099.142h6.597a.174.174 0 0 0 .17-.126a5 5 0 0 0 .175-1.28a4.74 4.74 0 0 0-4.734-4.727\"/>",
|
||||||
|
"ovh": "<path fill=\"currentColor\" d=\"m19.881 10.095l2.563-4.45A11.93 11.93 0 0 1 24 11.555c0 2.88-1.017 5.523-2.71 7.594h-6.62l2.04-3.541h-2.696l3.176-5.513zm-2.32-5.243L9.333 19.14l.003.009H2.709A11.95 11.95 0 0 1 0 11.555c0-2.152.57-4.17 1.561-5.918L5.855 13.1L10.6 4.852z\"/>",
|
||||||
|
"hetzner": "<path fill=\"currentColor\" d=\"M0 0v24h24V0zm4.602 4.025h2.244c.509 0 .716.215.716.717v5.64h8.883v-5.64c0-.509.215-.717.717-.717h2.229c.5 0 .71.23.724.717v14.516c0 .509-.215.717-.717.717h-2.23c-.51 0-.717-.215-.717-.717v-5.735H7.562v5.735c0 .516-.215.717-.716.717H4.602c-.51 0-.717-.208-.717-.717V4.742c0-.509.207-.717.717-.717\"/>",
|
||||||
|
"tailscale": "<path fill=\"currentColor\" d=\"M24 12a3 3 0 1 1-6 0a3 3 0 0 1 6 0m-9 9a3 3 0 1 1-6 0a3 3 0 0 1 6 0m0-9a3 3 0 1 1-6 0a3 3 0 0 1 6 0m6-6a3 3 0 1 1 0-6a3 3 0 0 1 0 6m0-.5a2.5 2.5 0 1 0 0-5a2.5 2.5 0 0 0 0 5M3 24a3 3 0 1 1 0-6a3 3 0 0 1 0 6m0-.5a2.5 2.5 0 1 0 0-5a2.5 2.5 0 0 0 0 5m18 .5a3 3 0 1 1 0-6a3 3 0 0 1 0 6m0-.5a2.5 2.5 0 1 0 0-5a2.5 2.5 0 0 0 0 5M6 12a3 3 0 1 1-6 0a3 3 0 0 1 6 0m9-9a3 3 0 1 1-6 0a3 3 0 0 1 6 0m-3 2.5a2.5 2.5 0 1 0 0-5a2.5 2.5 0 0 0 0 5M6 3a3 3 0 1 1-6 0a3 3 0 0 1 6 0M3 5.5a2.5 2.5 0 1 0 0-5a2.5 2.5 0 0 0 0 5\"/>",
|
||||||
|
"backblaze": "<path fill=\"currentColor\" d=\"M9.31 0c.653 1.35 1.567 4.082-1.388 7.174c-1.81 1.88-3.078 3.849-2.35 6.065c.365 1.103 1.187 2.507 2.887 2.785c.61.1 1.343 0 1.74-.14c2.454-.855 2.098-3.415 1.555-5.048c-.07-.213-.191-.733-.236-.924c-.373-1.602.776-2.656 1.129-3.804q.043-.138.07-.272q.062-.315.078-.638c0-1.827-.988-2.63-1.775-3.6C10.18.564 9.31 0 9.31 0m6.276 6.018s-.709.336-1.219.883c-.445.482-.863.879-1.294 1.859q-.041.21-.075.438c-.232 1.641 1.148 3.144.719 5.189c-.112.535-.355.712-.781 1.637c-.51 1.106-.383 2.588.36 3.529c.672.849 1.878 1.232 3.052.95c2.106-.505 3.065-2.283 2.896-4.286c-.131-1.58-.815-2.753-2.754-4.96c-.96-1.093-1.607-2.41-1.562-3.407c.137-1.207.658-1.832.658-1.832M4.893 15.194c-.022.014-.044.061-.059.16l-.006.02v.01c-.114.54-.165 1.822.116 2.968c.353 1.443 1.417 3.902 4.412 5.129c2.518 1.034 5.718.541 7.85-1.627c.529-.543.407-.49-.489-.201v-.002c-1.112.356-3.518.546-4.768-1c-1.523-1.885-.43-3.363-1.357-3.15c-3.616.834-5.267-1.466-5.547-2.102c-.002-.002-.086-.249-.152-.205\"/>",
|
||||||
|
"1password": "<path fill=\"currentColor\" d=\"M12 0c6.627 0 12 5.373 12 12c0 6.628-5.373 12-12 12S0 18.628 0 12C0 5.373 5.373 0 12 0m-.893 4.86c-.485 0-.727.001-.913.095a.87.87 0 0 0-.378.379c-.094.185-.095.428-.095.912v2.747c0 .12 0 .182.016.238q.02.075.065.138a1 1 0 0 0 .175.162l.695.564c.113.092.17.139.19.194a.22.22 0 0 1 0 .15c-.02.056-.077.102-.19.194l-.695.564a1 1 0 0 0-.175.162a.4.4 0 0 0-.065.138a1 1 0 0 0-.016.238v6.019c0 .485 0 .728.095.913a.87.87 0 0 0 .378.378c.186.094.428.094.913.094h1.786c.485 0 .727 0 .913-.094a.87.87 0 0 0 .378-.378c.095-.185.095-.428.095-.913v-2.747c0-.12 0-.182-.016-.238a.4.4 0 0 0-.065-.138a1 1 0 0 0-.175-.162l-.695-.564c-.113-.092-.17-.138-.191-.193a.22.22 0 0 1 0-.152c.02-.055.078-.1.19-.193l.696-.564a1 1 0 0 0 .175-.162a.4.4 0 0 0 .065-.138a1 1 0 0 0 .016-.238V6.246c0-.484 0-.727-.095-.912a.87.87 0 0 0-.378-.379c-.186-.094-.428-.094-.913-.094Z\"/>",
|
||||||
|
"squarespace": "<path fill=\"currentColor\" d=\"M22.655 8.719c-1.802-1.801-4.726-1.801-6.564 0l-7.351 7.35c-.45.45-.45 1.2 0 1.65s1.2.449 1.65 0l7.351-7.351a2.31 2.31 0 0 1 3.264 0c.9.9.9 2.364 0 3.264l-7.239 7.239c.9.899 2.362.899 3.263 0l5.589-5.589c1.836-1.838 1.836-4.763.037-6.563m-2.475 2.437a1.177 1.177 0 0 0-1.65 0l-7.354 7.389a2.31 2.31 0 0 1-3.262 0c-.45-.45-1.2-.45-1.65 0s-.45 1.2 0 1.649c1.801 1.801 4.726 1.801 6.564 0l7.351-7.35c.449-.487.449-1.239.001-1.688m-2.439-7.35c-1.801-1.801-4.726-1.801-6.564 0l-7.351 7.351c-.45.449-.45 1.199 0 1.649s1.2.45 1.65 0l7.395-7.351a2.32 2.32 0 0 1 3.27 0c.451.45 1.201.45 1.65 0c.421-.487.421-1.199-.029-1.649zm-2.475 2.437c-.45-.45-1.2-.45-1.65 0l-7.351 7.389c-.899.9-2.363.9-3.265 0a2.31 2.31 0 0 1 0-3.264l7.239-7.239c-.9-.9-2.362-.9-3.263 0L1.35 8.719c-1.8 1.8-1.8 4.725 0 6.563c1.801 1.801 4.725 1.801 6.564 0l7.35-7.351c.451-.488.451-1.238 0-1.688z\"/>"
|
||||||
|
}
|
||||||
41
src/lib/link-icons.test.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { existsSync } from 'node:fs';
|
||||||
|
import marks from './link-icons.json';
|
||||||
|
import { RULES, iconFile, linkIcon } from './link-icons';
|
||||||
|
|
||||||
|
describe('link icons', () => {
|
||||||
|
it('picks brand marks by host, port and path', () => {
|
||||||
|
expect(linkIcon('https://mail.google.com/mail/u/0/')).toMatchObject({
|
||||||
|
kind: 'svg',
|
||||||
|
color: '#EA4335'
|
||||||
|
});
|
||||||
|
expect(linkIcon('http://stan-ovh.tail665403.ts.net:19999')).toMatchObject({ color: '#00AB44' });
|
||||||
|
expect(linkIcon('https://stan-ovh.tail665403.ts.net:9443')).toMatchObject({ color: '#13BEF9' });
|
||||||
|
expect(linkIcon('https://www.google.com/maps')).toMatchObject({ color: '#34A853' });
|
||||||
|
expect(linkIcon('https://github.com/')).toMatchObject({ kind: 'svg', color: 'currentColor' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses saved favicons and exact hosts', () => {
|
||||||
|
expect(linkIcon('https://www.funda.nl/')).toEqual({
|
||||||
|
kind: 'img',
|
||||||
|
src: '/link-icons/funda.png'
|
||||||
|
});
|
||||||
|
expect(linkIcon('https://stanrunge.dev')).toEqual({ kind: 'img', src: '/favicon-32x32.png' });
|
||||||
|
expect(linkIcon('https://notstanrunge.dev').kind).toBe('letter');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to a monogram', () => {
|
||||||
|
expect(linkIcon('https://example.org', 'example')).toMatchObject({
|
||||||
|
kind: 'letter',
|
||||||
|
letter: 'E'
|
||||||
|
});
|
||||||
|
expect(linkIcon('not a url', 'x').kind).toBe('letter');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has every mark and favicon a rule points at', () => {
|
||||||
|
for (const r of RULES) {
|
||||||
|
if (r.si) expect(marks, r.si).toHaveProperty(r.si);
|
||||||
|
if (r.img) expect(existsSync(`static${iconFile(r.img)}`), r.img).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
126
src/lib/link-icons.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// Icons for /start link and service tiles, picked from the URL (no hotlinking):
|
||||||
|
// - brand marks from simple-icons, trimmed into link-icons.json by scripts/link-icons.ts
|
||||||
|
// (run `bun scripts/link-icons.ts` after adding a `si` rule below),
|
||||||
|
// - favicons saved under static/link-icons/ for brands simple-icons doesn't have,
|
||||||
|
// - otherwise a coloured monogram.
|
||||||
|
import marks from './link-icons.json';
|
||||||
|
|
||||||
|
export type LinkIcon =
|
||||||
|
| { kind: 'svg'; body: string; color: string }
|
||||||
|
| { kind: 'img'; src: string }
|
||||||
|
| { kind: 'letter'; letter: string; color: string };
|
||||||
|
|
||||||
|
type Rule = {
|
||||||
|
/** Host suffix ("google.com" matches mail.google.com), or exact host with a leading "=". */
|
||||||
|
host: string;
|
||||||
|
port?: string;
|
||||||
|
path?: string;
|
||||||
|
si?: string;
|
||||||
|
color?: string; // brand colour; omitted = currentColor (black/white marks)
|
||||||
|
img?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// First match wins, so specific rules go before broad ones.
|
||||||
|
export const RULES: Rule[] = [
|
||||||
|
{ host: 'mail.google.com', si: 'gmail', color: '#EA4335' },
|
||||||
|
{ host: 'calendar.google.com', si: 'googlecalendar', color: '#4285F4' },
|
||||||
|
{ host: 'drive.google.com', si: 'googledrive', color: '#1FA463' },
|
||||||
|
{ host: 'photos.google.com', si: 'googlephotos', color: '#FBBC04' },
|
||||||
|
{ host: 'maps.google.com', si: 'googlemaps', color: '#34A853' },
|
||||||
|
{ host: 'google.com', path: '/maps', si: 'googlemaps', color: '#34A853' },
|
||||||
|
{ host: 'whatsapp.com', si: 'whatsapp', color: '#25D366' },
|
||||||
|
{ host: 'discord.com', si: 'discord', color: '#5865F2' },
|
||||||
|
{ host: 'x.com', si: 'x' },
|
||||||
|
{ host: 'twitter.com', si: 'x' },
|
||||||
|
{ host: 'grok.com', img: 'grok.png' },
|
||||||
|
{ host: 'ticktick.com', si: 'ticktick', color: '#4772FA' },
|
||||||
|
{ host: 'spotify.com', si: 'spotify', color: '#1ED760' },
|
||||||
|
{ host: 'last.fm', si: 'lastdotfm', color: '#D51007' },
|
||||||
|
{ host: 'youtube.com', si: 'youtube', color: '#FF0000' },
|
||||||
|
{ host: 'twitch.tv', si: 'twitch', color: '#9146FF' },
|
||||||
|
{ host: 'osu.ppy.sh', si: 'osu', color: '#FF66AA' },
|
||||||
|
{ host: 'steampowered.com', si: 'steam' },
|
||||||
|
{ host: 'steamcommunity.com', si: 'steam' },
|
||||||
|
{ host: 'reddit.com', si: 'reddit', color: '#FF4500' },
|
||||||
|
{ host: 'linkedin.com', si: 'linkedin', color: '#0A66C2' },
|
||||||
|
{ host: 'claude.ai', si: 'claude', color: '#D97757' },
|
||||||
|
{ host: 'revolut.com', si: 'revolut' },
|
||||||
|
{ host: 'moneybird.com', img: 'moneybird.png' },
|
||||||
|
{ host: 'belastingdienst.nl', img: 'belastingdienst.png' },
|
||||||
|
{ host: 'megekko.nl', img: 'megekko.png' },
|
||||||
|
{ host: 'tweakers.net', img: 'tweakers.png' },
|
||||||
|
{ host: 'funda.nl', img: 'funda.png' },
|
||||||
|
{ host: 'pararius.nl', img: 'pararius.png' },
|
||||||
|
{ host: 'mijndak.nl', img: 'mijndak.png' },
|
||||||
|
{ host: 'ns.nl', img: 'ns.png' },
|
||||||
|
{ host: 'buienradar.nl', img: 'buienradar.png' },
|
||||||
|
// Self-hosted on stan-ovh
|
||||||
|
{ host: 'git.stanrunge.dev', si: 'gitea', color: '#609926' },
|
||||||
|
{ host: 'photos.stanrunge.dev', si: 'immich', color: '#4250AF' },
|
||||||
|
{ host: 'analytics.stanrunge.dev', si: 'umami' },
|
||||||
|
{ host: 'pgadmin.stanrunge.dev', si: 'postgresql', color: '#4169E1' },
|
||||||
|
{ host: 'logs.vash.software', si: 'docker', color: '#2496ED' },
|
||||||
|
{ host: 'dev.stanrunge.dev', si: 'svelte', color: '#FF3E00' },
|
||||||
|
{ host: 'stuhee.stanrunge.dev', img: 'stuhee.svg' },
|
||||||
|
{ host: 'vash.software', img: 'vash.ico' },
|
||||||
|
{ host: '=stanrunge.dev', img: '/favicon-32x32.png' },
|
||||||
|
{ host: 'ts.net', port: '19999', si: 'netdata', color: '#00AB44' },
|
||||||
|
{ host: 'ts.net', port: '9443', si: 'portainer', color: '#13BEF9' },
|
||||||
|
{ host: 'ntfy.sh', si: 'ntfy', color: '#3CB4A0' },
|
||||||
|
// Dev + infra
|
||||||
|
{ host: 'github.com', si: 'github' },
|
||||||
|
{ host: 'cursor.com', si: 'cursor' },
|
||||||
|
{ host: 'wix.com', si: 'wix' },
|
||||||
|
{ host: 'svelte.dev', si: 'svelte', color: '#FF3E00' },
|
||||||
|
{ host: 'drizzle.team', si: 'drizzle', color: '#C5F74F' },
|
||||||
|
{ host: 'cloudflare.com', si: 'cloudflare', color: '#F38020' },
|
||||||
|
{ host: 'ovh.com', si: 'ovh', color: '#4F8CF7' },
|
||||||
|
{ host: 'ovhcloud.com', si: 'ovh', color: '#4F8CF7' },
|
||||||
|
{ host: 'hetzner.com', si: 'hetzner', color: '#D50C2D' },
|
||||||
|
{ host: 'hetzner.cloud', si: 'hetzner', color: '#D50C2D' },
|
||||||
|
{ host: 'tailscale.com', si: 'tailscale' },
|
||||||
|
{ host: 'backblaze.com', si: 'backblaze', color: '#E21E29' },
|
||||||
|
{ host: '1password.com', si: '1password', color: '#3B66BC' },
|
||||||
|
{ host: 'squarespace.com', si: 'squarespace' }
|
||||||
|
];
|
||||||
|
|
||||||
|
export const iconFile = (img: string) => (img.startsWith('/') ? img : `/link-icons/${img}`);
|
||||||
|
|
||||||
|
function parse(url: string) {
|
||||||
|
try {
|
||||||
|
const u = new URL(url);
|
||||||
|
return { host: u.hostname.replace(/^www\./, '').toLowerCase(), port: u.port, path: u.pathname };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ruleFor(url: string): Rule | null {
|
||||||
|
const u = parse(url);
|
||||||
|
if (!u) return null;
|
||||||
|
return (
|
||||||
|
RULES.find((r) => {
|
||||||
|
const hostOk = r.host.startsWith('=')
|
||||||
|
? u.host === r.host.slice(1)
|
||||||
|
: u.host === r.host || u.host.endsWith(`.${r.host}`);
|
||||||
|
return hostOk && (!r.port || r.port === u.port) && (!r.path || u.path.startsWith(r.path));
|
||||||
|
}) ?? null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const marksBySlug = marks as Record<string, string>;
|
||||||
|
|
||||||
|
export function linkIcon(url: string, title = ''): LinkIcon {
|
||||||
|
const r = ruleFor(url);
|
||||||
|
if (r?.si && marksBySlug[r.si])
|
||||||
|
return { kind: 'svg', body: marksBySlug[r.si], color: r.color ?? 'currentColor' };
|
||||||
|
if (r?.img) return { kind: 'img', src: iconFile(r.img) };
|
||||||
|
const host = parse(url)?.host ?? url;
|
||||||
|
let h = 0;
|
||||||
|
for (const c of host) h = (h * 31 + c.charCodeAt(0)) >>> 0;
|
||||||
|
return {
|
||||||
|
kind: 'letter',
|
||||||
|
letter: (title.trim()[0] ?? host[0] ?? '?').toUpperCase(),
|
||||||
|
color: `hsl(${h % 360} 70% 55%)`
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import { daisyHookConfig, notifyDaisy } from './daisy-hook';
|
import { daisyHookConfig, notifyDaisy, redact } from './daisy-hook';
|
||||||
|
|
||||||
describe('daisy task webhook', () => {
|
describe('daisy task webhook', () => {
|
||||||
it('is off until both URL and key are set', () => {
|
it('is off until both URL and key are set', () => {
|
||||||
@@ -49,3 +49,35 @@ describe('daisy task webhook', () => {
|
|||||||
expect(bad?.headers.Authorization).toBe('Bearer k1');
|
expect(bad?.headers.Authorization).toBe('Bearer k1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('daisy task webhook hardening', () => {
|
||||||
|
it('accepts a full "Authorization: Bearer <key>" line as the header setting', () => {
|
||||||
|
const config = daisyHookConfig({
|
||||||
|
DAISY_TASK_WEBHOOK_URL: 'https://x/hook',
|
||||||
|
DAISY_TASK_WEBHOOK_KEY: 'k9',
|
||||||
|
DAISY_TASK_WEBHOOK_HEADER: 'Authorization: Bearer k9'
|
||||||
|
});
|
||||||
|
expect(Object.keys(config?.headers ?? {})).toEqual(['content-type', 'Authorization']);
|
||||||
|
expect(config?.headers.Authorization).toBe('Bearer k9');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('never logs the key', () => {
|
||||||
|
expect(redact("Invalid header 'Authorization: Bearer crsr_abc123'", ['k'])).not.toContain(
|
||||||
|
'crsr_abc123'
|
||||||
|
);
|
||||||
|
expect(redact('failed for supersecret', ['supersecret'])).toBe('failed for [redacted]');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('retries once after a failure', async () => {
|
||||||
|
const fetcher = vi
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValueOnce(new Response(null, { status: 502 }))
|
||||||
|
.mockResolvedValueOnce(new Response(null, { status: 200 }));
|
||||||
|
const config = daisyHookConfig({
|
||||||
|
DAISY_TASK_WEBHOOK_URL: 'https://x',
|
||||||
|
DAISY_TASK_WEBHOOK_KEY: 'k'
|
||||||
|
});
|
||||||
|
notifyDaisy({ id: 1, title: 't' }, config, fetcher as unknown as typeof fetch, 0);
|
||||||
|
await vi.waitFor(() => expect(fetcher).toHaveBeenCalledTimes(2));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,45 +3,76 @@
|
|||||||
// DAISY_TASK_WEBHOOK_URL where to POST {task_id, title}
|
// DAISY_TASK_WEBHOOK_URL where to POST {task_id, title}
|
||||||
// DAISY_TASK_WEBHOOK_KEY secret sent with it
|
// DAISY_TASK_WEBHOOK_KEY secret sent with it
|
||||||
// DAISY_TASK_WEBHOOK_HEADER header name (default Authorization, sent as "Bearer <key>";
|
// DAISY_TASK_WEBHOOK_HEADER header name (default Authorization, sent as "Bearer <key>";
|
||||||
// any other header gets the bare key)
|
// any other header gets the bare key). A full "Name: value"
|
||||||
|
// line is also accepted (only the name is used).
|
||||||
// Unset URL or key: nothing is sent and the task is still saved.
|
// Unset URL or key: nothing is sent and the task is still saved.
|
||||||
|
|
||||||
export type DaisyHookConfig = { url: string; headers: Record<string, string> };
|
export type DaisyHookConfig = { url: string; headers: Record<string, string>; secrets: string[] };
|
||||||
|
|
||||||
|
const HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
||||||
|
|
||||||
export function daisyHookConfig(env: Record<string, string | undefined> = process.env) {
|
export function daisyHookConfig(env: Record<string, string | undefined> = process.env) {
|
||||||
const url = env.DAISY_TASK_WEBHOOK_URL?.trim();
|
const url = env.DAISY_TASK_WEBHOOK_URL?.trim();
|
||||||
const key = env.DAISY_TASK_WEBHOOK_KEY?.trim();
|
const key = env.DAISY_TASK_WEBHOOK_KEY?.trim();
|
||||||
if (!url || !key) return null;
|
if (!url || !key) return null;
|
||||||
// Header name only. Tolerate a full "Name: value" line (the value part is ignored; the key
|
// The header once got set to a whole "Authorization: Bearer <key>" line: keep only the name.
|
||||||
// comes from DAISY_TASK_WEBHOOK_KEY) and fall back to Authorization for anything invalid.
|
let header = (env.DAISY_TASK_WEBHOOK_HEADER ?? '').split(':')[0].trim() || 'Authorization';
|
||||||
const rawHeader = env.DAISY_TASK_WEBHOOK_HEADER?.split(':')[0].trim() ?? '';
|
if (!HEADER_NAME.test(header)) {
|
||||||
const header = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(rawHeader) ? rawHeader : 'Authorization';
|
console.error(
|
||||||
|
'[daisy-hook] DAISY_TASK_WEBHOOK_HEADER is not a valid header name, using Authorization'
|
||||||
|
);
|
||||||
|
header = 'Authorization';
|
||||||
|
}
|
||||||
const value = header.toLowerCase() === 'authorization' ? `Bearer ${key}` : key;
|
const value = header.toLowerCase() === 'authorization' ? `Bearer ${key}` : key;
|
||||||
return { url, headers: { 'content-type': 'application/json', [header]: value } };
|
return {
|
||||||
|
url,
|
||||||
|
headers: { 'content-type': 'application/json', [header]: value },
|
||||||
|
secrets: [key, url]
|
||||||
|
} satisfies DaisyHookConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Fire and forget: never awaited by the request, 3 s timeout, failures only logged. */
|
/** Strip the key and URL (it may carry a token) from anything that gets logged. */
|
||||||
|
export function redact(message: string, secrets: string[] = []) {
|
||||||
|
let out = message;
|
||||||
|
for (const s of secrets) if (s) out = out.split(s).join('[redacted]');
|
||||||
|
return out
|
||||||
|
.replace(/crsr_[A-Za-z0-9]+/g, '[redacted]')
|
||||||
|
.replace(/Bearer\s+[^\s'"]+/gi, 'Bearer [redacted]');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function post(config: DaisyHookConfig, body: string, fetcher: typeof fetch) {
|
||||||
|
try {
|
||||||
|
const res = await fetcher(config.url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: config.headers,
|
||||||
|
body,
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
});
|
||||||
|
return res.ok ? null : `HTTP ${res.status}`;
|
||||||
|
} catch (err: unknown) {
|
||||||
|
// Name/code only: some fetch errors echo request headers (and so the key).
|
||||||
|
const e = err as { name?: string; cause?: { code?: string } };
|
||||||
|
return `${e?.name ?? 'Error'} ${e?.cause?.code ?? ''}`.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fire and forget: never awaited by the request; one retry after 2 s; failures logged redacted. */
|
||||||
export function notifyDaisy(
|
export function notifyDaisy(
|
||||||
task: { id: number; title: string },
|
task: { id: number; title: string },
|
||||||
config: DaisyHookConfig | null = daisyHookConfig(),
|
config: DaisyHookConfig | null = daisyHookConfig(),
|
||||||
fetcher: typeof fetch = fetch
|
fetcher: typeof fetch = fetch,
|
||||||
|
retryDelayMs = 2000
|
||||||
): 'sent' | 'not-configured' {
|
): 'sent' | 'not-configured' {
|
||||||
if (!config) return 'not-configured';
|
if (!config) return 'not-configured';
|
||||||
fetcher(config.url, {
|
const body = JSON.stringify({ task_id: task.id, title: task.title });
|
||||||
method: 'POST',
|
void (async () => {
|
||||||
headers: config.headers,
|
let failure = await post(config, body, fetcher);
|
||||||
body: JSON.stringify({ task_id: task.id, title: task.title }),
|
if (failure) {
|
||||||
signal: AbortSignal.timeout(3000)
|
await new Promise((r) => setTimeout(r, retryDelayMs));
|
||||||
})
|
failure = await post(config, body, fetcher);
|
||||||
.then((res) => {
|
}
|
||||||
if (!res.ok) console.error(`[daisy-hook] task ${task.id}: HTTP ${res.status}`);
|
if (failure) console.error(`[daisy-hook] task ${task.id}: ${redact(failure, config.secrets)}`);
|
||||||
})
|
else console.log(`[daisy-hook] task ${task.id}: delivered`);
|
||||||
.catch((err: unknown) => {
|
})();
|
||||||
// Name/code only: some fetch errors echo request headers (and so the key).
|
|
||||||
const e = err as { name?: string; cause?: { code?: string } };
|
|
||||||
console.error(
|
|
||||||
`[daisy-hook] task ${task.id}: ${e?.name ?? 'Error'} ${e?.cause?.code ?? ''}`.trim()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return 'sent';
|
return 'sent';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { invalidate } from '$app/navigation';
|
import { invalidate } from '$app/navigation';
|
||||||
import { ago, bytes, duration, timerStatus, type Container } from '#lib/home.js';
|
import { ago, bytes, duration, timerStatus, type Container } from '#lib/home.js';
|
||||||
|
import { linkIcon } from '#lib/link-icons.js';
|
||||||
|
|
||||||
let { data, form } = $props();
|
let { data, form } = $props();
|
||||||
|
|
||||||
@@ -358,7 +359,8 @@
|
|||||||
href={s.url}
|
href={s.url}
|
||||||
title="{host_(s.url)} · {s.code ?? '?'}{s.ms != null ? ` · ${s.ms} ms` : ''}"
|
title="{host_(s.url)} · {s.code ?? '?'}{s.ms != null ? ` · ${s.ms} ms` : ''}"
|
||||||
>
|
>
|
||||||
<i class="dot {s.ok === false ? 'bad' : s.ok ? 'ok' : 'off'}"></i>{s.name}
|
{@render icon(s.url, s.name)}<span class="label">{s.name}</span>
|
||||||
|
<i class="dot {s.ok === false ? 'bad' : s.ok ? 'ok' : 'off'}"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -383,7 +385,7 @@
|
|||||||
{#if form?.message}<p class="bad sub" role="alert">{form.message}</p>{/if}
|
{#if form?.message}<p class="bad sub" role="alert">{form.message}</p>{/if}
|
||||||
{#each sections as [section, links] (section)}
|
{#each sections as [section, links] (section)}
|
||||||
{#if sections.length > 1}<h3>{section}</h3>{/if}
|
{#if sections.length > 1}<h3>{section}</h3>{/if}
|
||||||
<ul class={editing ? 'list' : 'tiles'}>
|
<ul class={editing ? 'list' : 'tiles icons'}>
|
||||||
{#each links as l (l.id)}
|
{#each links as l (l.id)}
|
||||||
<li>
|
<li>
|
||||||
{#if editing && editId === l.id}
|
{#if editing && editId === l.id}
|
||||||
@@ -406,7 +408,9 @@
|
|||||||
<button class="ghost bad" aria-label="Delete {l.title}">✕</button>
|
<button class="ghost bad" aria-label="Delete {l.title}">✕</button>
|
||||||
</form>
|
</form>
|
||||||
{:else}
|
{:else}
|
||||||
<a href={l.url} title={l.url}>{l.title}</a>
|
<a href={l.url} title={l.url}
|
||||||
|
>{@render icon(l.url, l.title)}<span class="label">{l.title}</span></a
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -434,6 +438,20 @@
|
|||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
|
{#snippet icon(url: string, title: string)}
|
||||||
|
{@const ic = linkIcon(url, title)}
|
||||||
|
{#if ic.kind === 'svg'}
|
||||||
|
<span class="ico" style:color={ic.color} aria-hidden="true"
|
||||||
|
><!-- eslint-disable-next-line svelte/no-at-html-tags -- trimmed simple-icons paths from link-icons.json -->
|
||||||
|
{@html `<svg viewBox="0 0 24 24">${ic.body}</svg>`}</span
|
||||||
|
>
|
||||||
|
{:else if ic.kind === 'img'}
|
||||||
|
<img class="ico" src={ic.src} alt="" width="16" height="16" loading="lazy" decoding="async" />
|
||||||
|
{:else}
|
||||||
|
<span class="ico letter" style:background={ic.color} aria-hidden="true">{ic.letter}</span>
|
||||||
|
{/if}
|
||||||
|
{/snippet}
|
||||||
|
|
||||||
{#snippet linkForm(l: { id: number; title: string; url: string; section: string } | null)}
|
{#snippet linkForm(l: { id: number; title: string; url: string; section: string } | null)}
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
@@ -797,6 +815,50 @@
|
|||||||
background: var(--site-hover);
|
background: var(--site-hover);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
/* Icon tiles (links + services): brand-coloured mark on the black/white card, lift on hover. */
|
||||||
|
.tiles a:has(.ico) {
|
||||||
|
gap: 7px;
|
||||||
|
transition:
|
||||||
|
transform 120ms ease,
|
||||||
|
border-color 120ms ease,
|
||||||
|
background 120ms ease;
|
||||||
|
}
|
||||||
|
.tiles a:has(.ico):hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
border-color: var(--site-muted);
|
||||||
|
}
|
||||||
|
.tiles.icons {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
|
||||||
|
}
|
||||||
|
.tiles .label {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.tiles a .dot {
|
||||||
|
margin: 0 0 0 auto;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
.ico {
|
||||||
|
flex: none;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
.ico :global(svg) {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.ico.letter {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
button {
|
button {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
color: var(--site-text);
|
color: var(--site-text);
|
||||||
|
|||||||
BIN
static/link-icons/belastingdienst.png
Normal file
|
After Width: | Height: | Size: 488 B |
BIN
static/link-icons/buienradar.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
static/link-icons/funda.png
Normal file
|
After Width: | Height: | Size: 599 B |
BIN
static/link-icons/grok.png
Normal file
|
After Width: | Height: | Size: 1006 B |
BIN
static/link-icons/megekko.png
Normal file
|
After Width: | Height: | Size: 672 B |
BIN
static/link-icons/mijndak.png
Normal file
|
After Width: | Height: | Size: 598 B |
BIN
static/link-icons/moneybird.png
Normal file
|
After Width: | Height: | Size: 552 B |
BIN
static/link-icons/ns.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
static/link-icons/pararius.png
Normal file
|
After Width: | Height: | Size: 831 B |
1
static/link-icons/stuhee.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><defs><linearGradient id='g' x1='0' y1='0' x2='1' y2='1'><stop offset='0' stop-color='#f472b6'/><stop offset='1' stop-color='#db2777'/></linearGradient></defs><rect width='32' height='32' rx='9' fill='url(#g)'/><g transform='translate(4 4.5)'><path d='M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5' fill='#fff'/></g></svg>
|
||||||
|
After Width: | Height: | Size: 486 B |
BIN
static/link-icons/tweakers.png
Normal file
|
After Width: | Height: | Size: 968 B |
BIN
static/link-icons/vash.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |