jskelet 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,12 +14,10 @@
14
14
  <div class="brand">
15
15
  <img src="logo.png" alt="" width="30" height="30" />
16
16
  <span class="wordmark">JSkelet</span>
17
+ <span id="language"></span>
17
18
  </div>
18
- <h1>Cache panel</h1>
19
- <p>
20
- This run's password is printed in the server log
21
- (<code>[cache-panel]</code>).
22
- </p>
19
+ <h1 data-i18n="login.title">Cache panel</h1>
20
+ <p data-i18n-html="login.lead"></p>
23
21
  <input
24
22
  id="password"
25
23
  type="password"
@@ -27,21 +25,29 @@
27
25
  maxlength="64"
28
26
  spellcheck="false"
29
27
  autocomplete="off"
30
- placeholder="32-character password"
28
+ data-i18n-ph="login.placeholder"
31
29
  autofocus
32
30
  />
33
- <button class="primary" type="submit">Unlock</button>
31
+ <button class="primary" type="submit" data-i18n="login.submit">Unlock</button>
34
32
  <p class="error" id="error"></p>
35
- <p class="hint m0">
36
- Three failed attempts block this address for 24 hours.
37
- </p>
33
+ <p class="hint m0" data-i18n="login.hint"></p>
38
34
  </form>
39
35
 
40
36
  <script type="module">
37
+ import { applyTranslations, languageSelect, t } from "./i18n.js";
38
+
41
39
  const form = document.getElementById("form");
42
40
  const input = document.getElementById("password");
43
41
  const error = document.getElementById("error");
44
42
 
43
+ // Giriş sayfasında da seçici var: panele girmeden önce dilini seçen biri
44
+ // içeride aynı dili bulmalı, tercih `localStorage`'da taşınıyor.
45
+ document
46
+ .getElementById("language")
47
+ .append(languageSelect(() => applyTranslations()));
48
+
49
+ applyTranslations();
50
+
45
51
  form.addEventListener("submit", async (event) => {
46
52
  event.preventDefault();
47
53
  error.textContent = "";
@@ -59,10 +65,7 @@
59
65
 
60
66
  // Yasaklandıysa sunucu artık 404 dönüyor; kalan deneme sayısı
61
67
  // bilinçli olarak bildirilmiyor.
62
- error.textContent =
63
- response.status === 404
64
- ? "Too many attempts."
65
- : "Invalid password.";
68
+ error.textContent = t(response.status === 404 ? "login.tooMany" : "login.invalid");
66
69
  input.value = "";
67
70
  input.focus();
68
71
  });
@@ -547,6 +547,22 @@ label.check {
547
547
  font-size: 11.5px;
548
548
  }
549
549
 
550
+ /* Header'daki seçici bir form alanı gibi değil, yanındaki chip'ler gibi
551
+ görünmeli: panelin üst şeridi durum bilgisi taşıyor, kontrol değil. */
552
+ select.lang {
553
+ padding: 5px 7px;
554
+ border: 1px solid var(--line);
555
+ border-radius: 7px;
556
+ background: #0d0f14;
557
+ color: var(--muted);
558
+ font: inherit;
559
+ font-size: 11.5px;
560
+ }
561
+
562
+ select.lang:hover {
563
+ color: var(--text);
564
+ }
565
+
550
566
  select {
551
567
  padding: 8px 11px;
552
568
  border: 1px solid var(--line);
@@ -18,7 +18,7 @@
18
18
  </div>
19
19
 
20
20
  <nav class="crumbs" aria-label="Panel">
21
- <span aria-current="page">Cache</span>
21
+ <span aria-current="page" data-i18n="nav.cache">Cache</span>
22
22
  </nav>
23
23
 
24
24
  <span class="spacer"></span>
@@ -28,62 +28,67 @@
28
28
  <span class="chip" id="uptime">—</span>
29
29
  <span class="chip" id="memory">—</span>
30
30
 
31
+ <span id="language"></span>
32
+
31
33
  <label class="check">
32
34
  <input type="checkbox" id="auto" checked />
33
- live
35
+ <span data-i18n="shell.live">live</span>
34
36
  </label>
35
- <button id="refresh">Refresh</button>
36
- <button id="logout">Sign out</button>
37
+ <button id="refresh" data-i18n="shell.refresh">Refresh</button>
38
+ <button id="logout" data-i18n="shell.signOut">Sign out</button>
37
39
  </header>
38
40
 
39
41
  <main>
40
42
  <div class="cards">
41
43
  <div class="card">
42
- <div class="label">HTML entries</div>
44
+ <div class="label" data-i18n="card.htmlEntries">HTML entries</div>
43
45
  <div class="value" id="html-size">—</div>
44
46
  <div class="sub" id="html-sub">—</div>
45
47
  </div>
46
48
  <div class="card">
47
- <div class="label">HTML in memory</div>
49
+ <div class="label" data-i18n="card.htmlMemory">HTML in memory</div>
48
50
  <div class="value" id="html-bytes">—</div>
49
51
  <div class="sub" id="html-stale">—</div>
50
52
  </div>
51
53
  <div class="card">
52
- <div class="label">Data entries</div>
54
+ <div class="label" data-i18n="card.dataEntries">Data entries</div>
53
55
  <div class="value" id="data-size">—</div>
54
56
  <div class="sub" id="data-sub">—</div>
55
57
  </div>
56
58
  <div class="card">
57
- <div class="label">Shared tier</div>
59
+ <div class="label" data-i18n="card.sharedTier">Shared tier</div>
58
60
  <div class="value" id="redis-state">—</div>
59
61
  <div class="sub" id="redis-sub">—</div>
60
62
  </div>
61
63
  <div class="card">
62
- <div class="label">Prewarm</div>
64
+ <div class="label" data-i18n="card.prewarm">Prewarm</div>
63
65
  <div class="value" id="prewarm-state">—</div>
64
66
  <div class="sub" id="prewarm-sub">—</div>
65
67
  </div>
66
68
  </div>
67
69
 
68
70
  <section>
69
- <h2>Shared tier <span class="count" id="redis-heading">—</span></h2>
71
+ <h2>
72
+ <span data-i18n="redis.section">Shared tier</span>
73
+ <span class="count" id="redis-heading">—</span>
74
+ </h2>
70
75
  <div class="body">
71
76
  <dl class="kv" id="redis-kv"></dl>
72
77
 
73
78
  <div class="row" id="redis-actions" hidden>
74
- <button id="redis-inspect">Count keys in Redis</button>
75
- <button class="danger" id="redis-html">Drop shared HTML keys</button>
76
- <button class="danger" id="redis-data">Drop shared data keys</button>
79
+ <button id="redis-inspect" data-i18n="redis.inspect">Count keys in Redis</button>
80
+ <button class="danger" id="redis-html" data-i18n="redis.dropHtml">
81
+ Drop shared HTML keys
82
+ </button>
83
+ <button class="danger" id="redis-data" data-i18n="redis.dropData">
84
+ Drop shared data keys
85
+ </button>
77
86
  </div>
78
87
 
79
88
  <div class="suggest" id="redis-suggest" hidden>
80
89
  <p class="m0">
81
- <strong>No shared tier.</strong> Every entry above lives in this
82
- process only, so each replica warms up on its own and an
83
- invalidation reaches whichever replica received the request.
84
- Adding Redis keeps the in-process cache primary and only does what
85
- a single process cannot: serve HTML another replica already
86
- rendered, and broadcast purges to all of them.
90
+ <strong data-i18n="redis.suggest.title">No shared tier.</strong>
91
+ <span data-i18n="redis.suggest.body"></span>
87
92
  </p>
88
93
  <pre><code>npm install ioredis</code></pre>
89
94
  <pre><code>// jskelet.config.mjs
@@ -92,19 +97,17 @@ cache() {
92
97
  redis: { enabled: true, url: process.env.REDIS_URL },
93
98
  };
94
99
  }</code></pre>
95
- <p class="hint m0">
96
- Worth it from the second replica onwards. On a single instance the
97
- gain is close to zero, which is why it is off by default.
98
- </p>
100
+ <p class="hint m0" data-i18n="redis.suggest.hint"></p>
99
101
  </div>
100
102
  </div>
101
103
  </section>
102
104
 
103
105
  <section>
104
106
  <h2>
105
- Cloudflare <span class="count" id="cf-heading">—</span>
107
+ <span data-i18n="cf.section">Cloudflare</span>
108
+ <span class="count" id="cf-heading">—</span>
106
109
  <span class="tabs">
107
- <button id="cf-refresh">Refresh</button>
110
+ <button id="cf-refresh" data-i18n="cf.refresh">Refresh</button>
108
111
  </span>
109
112
  </h2>
110
113
  <div class="body">
@@ -112,34 +115,40 @@ cache() {
112
115
 
113
116
  <div id="cf-tools" hidden>
114
117
  <div class="row">
115
- <button class="danger" id="cf-purge-all">Purge everything</button>
116
- <button id="cf-purge-cached">Purge URLs cached here</button>
118
+ <button class="danger" id="cf-purge-all" data-i18n="cf.purgeAll">
119
+ Purge everything
120
+ </button>
121
+ <button id="cf-purge-cached" data-i18n="cf.purgeCached">
122
+ Purge URLs cached here
123
+ </button>
117
124
  <label class="check">
118
125
  <input type="checkbox" id="cf-dev" />
119
- development mode (bypass cache for 3h)
126
+ <span data-i18n="cf.devMode"></span>
120
127
  </label>
121
128
  </div>
122
129
 
123
130
  <div class="row">
124
131
  <select id="cf-kind">
125
- <option value="prefixes">prefixes</option>
126
- <option value="hosts">hosts</option>
127
- <option value="tags">cache tags</option>
132
+ <option value="prefixes" data-i18n="cf.kind.prefixes">prefixes</option>
133
+ <option value="hosts" data-i18n="cf.kind.hosts">hosts</option>
134
+ <option value="tags" data-i18n="cf.kind.tags">cache tags</option>
128
135
  </select>
129
136
  <input
130
137
  class="grow"
131
138
  type="text"
132
139
  id="cf-values"
133
140
  spellcheck="false"
134
- placeholder="example.com/news · space or comma separated, 100 per request"
141
+ data-i18n-ph="cf.values.placeholder"
135
142
  />
136
- <button class="primary" id="cf-purge-keys">Purge</button>
143
+ <button class="primary" id="cf-purge-keys" data-i18n="cf.purge">Purge</button>
137
144
  </div>
138
145
 
139
146
  <div class="row">
140
- <button id="cf-tiered">Toggle Tiered Cache</button>
141
- <button id="cf-reserve">Toggle Cache Reserve</button>
142
- <button class="danger" id="cf-reserve-clear">Clear Cache Reserve</button>
147
+ <button id="cf-tiered" data-i18n="cf.toggleTiered">Toggle Tiered Cache</button>
148
+ <button id="cf-reserve" data-i18n="cf.toggleReserve">Toggle Cache Reserve</button>
149
+ <button class="danger" id="cf-reserve-clear" data-i18n="cf.clearReserve">
150
+ Clear Cache Reserve
151
+ </button>
143
152
  </div>
144
153
 
145
154
  <div class="row">
@@ -148,49 +157,37 @@ cache() {
148
157
  type="text"
149
158
  id="cf-path"
150
159
  spellcheck="false"
151
- placeholder="/news/some-post — which edges served this path (blank = whole zone)"
160
+ data-i18n-ph="cf.path.placeholder"
152
161
  />
153
- <button id="cf-analytics">Query analytics</button>
162
+ <button id="cf-analytics" data-i18n="cf.queryAnalytics">Query analytics</button>
154
163
  </div>
155
164
 
156
165
  <div id="cf-report" class="report" hidden></div>
157
166
 
158
- <p class="hint">
159
- Cloudflare has no endpoint that lists which edges currently hold a
160
- copy of a URL, and no way to warm a chosen edge from the origin —
161
- an object only enters an edge cache through a real request routed
162
- there. What you get above is observation: which colos served the
163
- path from cache and which went to the origin. Warming is our
164
- prewarm pass plus Tiered Cache or Cache Reserve behind it.
165
- </p>
167
+ <p class="hint" data-i18n="cf.edgeHint"></p>
166
168
  </div>
167
169
 
168
170
  <div class="suggest" id="cf-suggest" hidden>
169
171
  <p class="m0">
170
- <strong>Cloudflare is not connected.</strong> The cache above is
171
- the origin cache; the copy your visitors actually get is at the
172
- edge. Connecting a zone lets a purge here reach both, and shows
173
- the cache hit ratio that decides whether the origin cache is even
174
- being asked.
172
+ <strong data-i18n="cf.suggest.title">Cloudflare is not connected.</strong>
173
+ <span data-i18n="cf.suggest.body"></span>
175
174
  </p>
176
175
  <pre><code>JSKELET_CLOUDFLARE_KEY=your_api_token
177
176
  JSKELET_CLOUDFLARE_ZONE_ID=your_zone_id</code></pre>
178
- <p class="hint m0">
179
- Token permissions: <code>Zone.Cache Purge</code> to purge,
180
- <code>Zone.Zone Settings</code> to change settings,
181
- <code>Zone.Analytics</code> (read) for the hit ratio. The token is
182
- read from the environment and never leaves the server.
183
- </p>
177
+ <p class="hint m0" data-i18n-html="cf.suggest.hint"></p>
184
178
  </div>
185
179
  </div>
186
180
  </section>
187
181
 
188
182
  <section>
189
- <h2>Host <span class="count" id="host-heading">—</span></h2>
183
+ <h2>
184
+ <span data-i18n="host.section">Host</span>
185
+ <span class="count" id="host-heading">—</span>
186
+ </h2>
190
187
  <div class="body">
191
188
  <div class="meter">
192
189
  <div class="meter-head">
193
- <span>Memory</span>
190
+ <span data-i18n="host.memory">Memory</span>
194
191
  <span class="mono" id="ram-text">—</span>
195
192
  </div>
196
193
  <div class="bar"><span id="ram-bar"></span></div>
@@ -199,7 +196,7 @@ JSKELET_CLOUDFLARE_ZONE_ID=your_zone_id</code></pre>
199
196
 
200
197
  <div class="meter">
201
198
  <div class="meter-head">
202
- <span>Disk</span>
199
+ <span data-i18n="host.disk">Disk</span>
203
200
  <span class="mono" id="disk-text">—</span>
204
201
  </div>
205
202
  <div class="bar"><span id="disk-bar"></span></div>
@@ -209,7 +206,7 @@ JSKELET_CLOUDFLARE_ZONE_ID=your_zone_id</code></pre>
209
206
  </section>
210
207
 
211
208
  <section>
212
- <h2>Actions</h2>
209
+ <h2><span data-i18n="actions.section">Actions</span></h2>
213
210
  <div class="body">
214
211
  <div class="row">
215
212
  <input
@@ -217,13 +214,15 @@ JSKELET_CLOUDFLARE_ZONE_ID=your_zone_id</code></pre>
217
214
  type="text"
218
215
  id="target"
219
216
  spellcheck="false"
220
- placeholder="/news/:slug · /news/some-post · invalidation target"
217
+ data-i18n-ph="actions.target.placeholder"
221
218
  />
222
219
  <label class="check">
223
220
  <input type="checkbox" id="hard" />
224
- hard (delete instead of marking stale)
221
+ <span data-i18n="actions.hard"></span>
225
222
  </label>
226
- <button class="primary" id="invalidate">Invalidate</button>
223
+ <button class="primary" id="invalidate" data-i18n="actions.invalidate">
224
+ Invalidate
225
+ </button>
227
226
  </div>
228
227
 
229
228
  <div class="row">
@@ -232,31 +231,33 @@ JSKELET_CLOUDFLARE_ZONE_ID=your_zone_id</code></pre>
232
231
  type="text"
233
232
  id="prefix"
234
233
  spellcheck="false"
235
- placeholder="data key prefix — empty clears every data entry"
234
+ data-i18n-ph="actions.prefix.placeholder"
236
235
  />
237
- <button class="danger" id="clear-data">Clear data cache</button>
238
- <button class="danger" id="clear-html">Clear HTML cache</button>
236
+ <button class="danger" id="clear-data" data-i18n="actions.clearData">
237
+ Clear data cache
238
+ </button>
239
+ <button class="danger" id="clear-html" data-i18n="actions.clearHtml">
240
+ Clear HTML cache
241
+ </button>
239
242
  </div>
240
243
 
241
244
  <div class="row">
242
- <button id="prewarm">Prewarm all paths</button>
245
+ <button id="prewarm" data-i18n="actions.prewarm">Prewarm all paths</button>
243
246
  </div>
244
247
 
245
- <p class="hint">
246
- Invalidation marks entries stale by default: visitors keep getting
247
- the old HTML while a single background refresh runs. Hard mode
248
- deletes them, so the next visitor waits for a cold render.
249
- </p>
248
+ <p class="hint" data-i18n="actions.hint"></p>
250
249
  </div>
251
250
  </section>
252
251
 
253
252
  <section>
254
253
  <h2>
255
- Entries
254
+ <span data-i18n="entries.section">Entries</span>
256
255
  <span class="count" id="listed">—</span>
257
256
  <span class="tabs" role="tablist">
258
257
  <button id="tab-html" role="tab" aria-selected="true">HTML</button>
259
- <button id="tab-data" role="tab" aria-selected="false">Data</button>
258
+ <button id="tab-data" role="tab" aria-selected="false" data-i18n="tab.data">
259
+ Data
260
+ </button>
260
261
  </span>
261
262
  </h2>
262
263
  <div class="body">
@@ -266,7 +267,7 @@ JSKELET_CLOUDFLARE_ZONE_ID=your_zone_id</code></pre>
266
267
  type="search"
267
268
  id="search"
268
269
  spellcheck="false"
269
- placeholder="Filter by key"
270
+ data-i18n-ph="entries.search"
270
271
  />
271
272
  </div>
272
273
  </div>
@@ -275,7 +276,7 @@ JSKELET_CLOUDFLARE_ZONE_ID=your_zone_id</code></pre>
275
276
  <thead id="thead"></thead>
276
277
  <tbody id="tbody"></tbody>
277
278
  </table>
278
- <div class="empty" id="empty" hidden>No entries.</div>
279
+ <div class="empty" id="empty" data-i18n="entries.empty" hidden>No entries.</div>
279
280
  </div>
280
281
  </section>
281
282
  </main>
@@ -292,11 +293,11 @@ JSKELET_CLOUDFLARE_ZONE_ID=your_zone_id</code></pre>
292
293
  GitHub
293
294
  </a>
294
295
  <span class="dot" aria-hidden="true">·</span>
295
- <span>Cache panel</span>
296
+ <span data-i18n="footer.panel">Cache panel</span>
296
297
  <span class="dot" aria-hidden="true">·</span>
297
298
  <span class="live">
298
299
  <span class="pulse"></span>
299
- Password rotates on restart
300
+ <span data-i18n="footer.rotates">Password rotates on restart</span>
300
301
  </span>
301
302
  </div>
302
303
  </footer>