|
1 |
+ |
<!doctype html> |
|
2 |
+ |
<html lang="en"> |
|
3 |
+ |
<head> |
|
4 |
+ |
<meta charset="UTF-8" /> |
|
5 |
+ |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|
6 |
+ |
<meta name="theme-color" content="#121113" /> |
|
7 |
+ |
<link rel="stylesheet" href="/static/styles.css" /> |
|
8 |
+ |
<link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon.png"> |
|
9 |
+ |
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png"> |
|
10 |
+ |
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png"> |
|
11 |
+ |
<link rel="manifest" href="/assets/site.webmanifest"> |
|
12 |
+ |
|
|
13 |
+ |
<title>Sipp - Admin</title> |
|
14 |
+ |
<meta name="description" content="Minimal Code Sharing"> |
|
15 |
+ |
|
|
16 |
+ |
<meta property="og:url" content="https://sipp.so"> |
|
17 |
+ |
<meta property="og:type" content="website"> |
|
18 |
+ |
<meta property="og:title" content="Sipps"> |
|
19 |
+ |
<meta property="og:description" content="Minimal Code Sharing"> |
|
20 |
+ |
<meta property="og:image" content="https://sipp.so/assets/og.png"> |
|
21 |
+ |
|
|
22 |
+ |
<meta name="twitter:card" content="summary_large_image"> |
|
23 |
+ |
<meta property="twitter:domain" content="sipp.so"> |
|
24 |
+ |
<meta property="twitter:url" content="https://sipp.so"> |
|
25 |
+ |
<meta name="twitter:title" content="Sipps"> |
|
26 |
+ |
<meta name="twitter:description" content="Minimal Code Sharing"> |
|
27 |
+ |
<meta name="twitter:image" content="https://sipp.so/assets/og.png"> |
|
28 |
+ |
</head> |
|
29 |
+ |
<body> |
|
30 |
+ |
|
|
31 |
+ |
<div class="nav"> |
|
32 |
+ |
<a href="/" class="header"> |
|
33 |
+ |
<h1>SIPP</h1> |
|
34 |
+ |
</a> |
|
35 |
+ |
|
|
36 |
+ |
<a class="icon" target="_blank" href="https://github.com/stevedylandev/sipp"> |
|
37 |
+ |
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> |
|
38 |
+ |
<title>GitHub</title> |
|
39 |
+ |
<path d="m21.838 11.677l-9.549-9.58c-.129-.13-.451-.13-.645 0L9 4.742l2.452 2.452c.193-.097.419-.13.645-.13c.903 0 1.58.742 1.58 1.581c0 .226-.032.452-.129.645l1.968 1.968c.194-.097.42-.129.645-.129c.904 0 1.58.742 1.58 1.58c0 .904-.741 1.581-1.58 1.581c-.903 0-1.58-.742-1.58-1.58c0-.226.032-.452.129-.646l-1.968-1.967h-.032v3.71c.58.258 1 .806 1 1.483c0 .904-.742 1.581-1.581 1.581c-.903 0-1.58-.742-1.58-1.58c0-.678.419-1.259 1-1.485v-3.612c-.581-.259-1-.807-1-1.484c0-.226.032-.452.128-.645L8.225 5.613l-6.097 6.064c-.129.13-.129.452 0 .646l9.58 9.58c.13.13.452.13.646 0l9.548-9.58a.59.59 0 0 0-.064-.646"/> |
|
40 |
+ |
</svg> |
|
41 |
+ |
</a> |
|
42 |
+ |
</div> |
|
43 |
+ |
|
|
44 |
+ |
<div id="authForm" style="display: flex; gap: 1rem; width: 100%;"> |
|
45 |
+ |
<input placeholder="API Key" type="password" id="apiKey" style="flex: 1;"> |
|
46 |
+ |
<button id="loadBtn" onclick="loadSnippets()">Load Snippets</button> |
|
47 |
+ |
</div> |
|
48 |
+ |
|
|
49 |
+ |
<div id="error" style="display: none; color: #ff6b6b;"></div> |
|
50 |
+ |
|
|
51 |
+ |
<div id="snippetList" style="display: none; width: 100%;"></div> |
|
52 |
+ |
|
|
53 |
+ |
<script> |
|
54 |
+ |
async function loadSnippets() { |
|
55 |
+ |
const apiKey = document.getElementById('apiKey').value; |
|
56 |
+ |
const errorEl = document.getElementById('error'); |
|
57 |
+ |
const listEl = document.getElementById('snippetList'); |
|
58 |
+ |
const loadBtn = document.getElementById('loadBtn'); |
|
59 |
+ |
|
|
60 |
+ |
errorEl.style.display = 'none'; |
|
61 |
+ |
listEl.style.display = 'none'; |
|
62 |
+ |
loadBtn.textContent = 'Loading...'; |
|
63 |
+ |
loadBtn.disabled = true; |
|
64 |
+ |
|
|
65 |
+ |
try { |
|
66 |
+ |
const res = await fetch('/api/snippets', { |
|
67 |
+ |
headers: { 'x-api-key': apiKey } |
|
68 |
+ |
}); |
|
69 |
+ |
|
|
70 |
+ |
if (!res.ok) { |
|
71 |
+ |
const data = await res.json(); |
|
72 |
+ |
throw new Error(data.error || 'Failed to load snippets'); |
|
73 |
+ |
} |
|
74 |
+ |
|
|
75 |
+ |
const snippets = await res.json(); |
|
76 |
+ |
|
|
77 |
+ |
if (snippets.length === 0) { |
|
78 |
+ |
listEl.innerHTML = '<p>No snippets found.</p>'; |
|
79 |
+ |
} else { |
|
80 |
+ |
listEl.innerHTML = snippets.map(s => |
|
81 |
+ |
`<a class="snippet-item" href="/s/${s.short_id}">` + |
|
82 |
+ |
`<span class="snippet-name">${s.name}</span>` + |
|
83 |
+ |
`<span class="snippet-id">/s/${s.short_id}</span>` + |
|
84 |
+ |
`</a>` |
|
85 |
+ |
).join(''); |
|
86 |
+ |
} |
|
87 |
+ |
|
|
88 |
+ |
listEl.style.display = 'flex'; |
|
89 |
+ |
} catch (err) { |
|
90 |
+ |
errorEl.textContent = err.message; |
|
91 |
+ |
errorEl.style.display = 'block'; |
|
92 |
+ |
} finally { |
|
93 |
+ |
loadBtn.textContent = 'Load Snippets'; |
|
94 |
+ |
loadBtn.disabled = false; |
|
95 |
+ |
} |
|
96 |
+ |
} |
|
97 |
+ |
|
|
98 |
+ |
document.getElementById('apiKey').addEventListener('keydown', (e) => { |
|
99 |
+ |
if (e.key === 'Enter') { |
|
100 |
+ |
e.preventDefault(); |
|
101 |
+ |
loadSnippets(); |
|
102 |
+ |
} |
|
103 |
+ |
}); |
|
104 |
+ |
</script> |
|
105 |
+ |
</body> |
|
106 |
+ |
</html> |