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.
- package/CHANGELOG.md +7 -0
- package/docs/06-cache.md +8 -1
- package/docs/en/06-caching.md +8 -1
- package/package.json +1 -1
- package/src/client/cache-panel/i18n.js +670 -0
- package/src/client/cache-panel/login.html +17 -14
- package/src/client/cache-panel/panel.css +16 -0
- package/src/client/cache-panel/panel.html +80 -79
- package/src/client/cache-panel/panel.js +201 -94
- package/src/server/cache-panel.js +104 -83
|
@@ -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
|
-
|
|
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>
|
|
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"
|
|
76
|
-
|
|
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>
|
|
82
|
-
|
|
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
|
-
|
|
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"
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
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>
|
|
171
|
-
|
|
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>
|
|
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
|
-
|
|
217
|
+
data-i18n-ph="actions.target.placeholder"
|
|
221
218
|
/>
|
|
222
219
|
<label class="check">
|
|
223
220
|
<input type="checkbox" id="hard" />
|
|
224
|
-
hard
|
|
221
|
+
<span data-i18n="actions.hard"></span>
|
|
225
222
|
</label>
|
|
226
|
-
<button class="primary" id="invalidate">
|
|
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
|
-
|
|
234
|
+
data-i18n-ph="actions.prefix.placeholder"
|
|
236
235
|
/>
|
|
237
|
-
<button class="danger" id="clear-data"
|
|
238
|
-
|
|
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">
|
|
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
|
-
|
|
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>
|