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.
@@ -338,7 +338,11 @@ async function hostStatus() {
338
338
  /* --------------------------------------------------------------- aksiyonlar */
339
339
 
340
340
  /**
341
- * Panelden gelen işlemi uygular ve kullanıcıya gösterilecek özeti döner.
341
+ * Panelden gelen işlemi uygular ve sonucu **metin değil kod** olarak döner.
342
+ *
343
+ * Cümleyi panel kuruyor (`i18n.js`): panel iki dilde konuşuyor, sunucunun
344
+ * arayüz dilini bilmesi için bir sebep yok. `params` yer tutucuları besler,
345
+ * `parts` ise eksik kalabilen parçalı cevaplar için.
342
346
  *
343
347
  * Her işlem yerel kademeyi düşürüp paylaşımlı kademeye de yayılıyor (bkz.
344
348
  * `html-cache.js`, `data-cache.js`): tek node'un önbelleğini boşaltmak,
@@ -346,7 +350,9 @@ async function hostStatus() {
346
350
  *
347
351
  * @param {Record<string, any>} body
348
352
  * @param {import('express').Request} req
349
- * @returns {Promise<{ ok: boolean, message: string }>}
353
+ * @returns {Promise<{ ok: boolean, code?: string,
354
+ * params?: Record<string, string | number>,
355
+ * parts?: ({ code: string, params?: Record<string, string | number> } | null)[] }>}
350
356
  */
351
357
  async function runAction(body, req) {
352
358
  const type = String(body?.type ?? "");
@@ -355,89 +361,88 @@ async function runAction(body, req) {
355
361
  case "html:clear": {
356
362
  const size = getHtmlCacheSize();
357
363
  clearHtmlCache();
358
- return { ok: true, message: `HTML cache cleared (${size} entries)` };
364
+ return { ok: true, code: "html.cleared", params: { count: size } };
359
365
  }
360
366
 
361
367
  case "data:clear": {
362
368
  const prefix = typeof body.prefix === "string" && body.prefix ? body.prefix : undefined;
363
369
  const removed = clearDataCache(prefix);
364
- return {
365
- ok: true,
366
- message: prefix
367
- ? `${removed} data entries dropped under \`${prefix}\``
368
- : `Data cache cleared (${removed} entries)`,
369
- };
370
+ return prefix
371
+ ? { ok: true, code: "data.clearedPrefix", params: { count: removed, prefix } }
372
+ : { ok: true, code: "data.cleared", params: { count: removed } };
370
373
  }
371
374
 
372
375
  case "html:invalidate": {
373
376
  const target = String(body.target ?? "");
374
377
  if (!target.startsWith("/")) {
375
- return { ok: false, message: "Target must start with `/`" };
378
+ return { ok: false, code: "target.invalid" };
376
379
  }
377
380
 
378
381
  const hard = body.hard === true;
379
382
  const count = invalidateHtmlCache(target, { hard });
380
383
  return {
381
384
  ok: true,
382
- message: `${count} entries ${hard ? "dropped" : "marked stale"} for \`${target}\``,
385
+ code: hard ? "html.dropped" : "html.marked",
386
+ params: { count, target },
383
387
  };
384
388
  }
385
389
 
386
390
  case "html:drop": {
387
391
  const key = String(body.key ?? "");
388
- if (!key) return { ok: false, message: "Missing key" };
392
+ if (!key) return { ok: false, code: "key.missing" };
389
393
  const existed = dropHtmlCacheKey(key);
390
- return {
391
- ok: true,
392
- message: existed ? `Dropped \`${key}\`` : `\`${key}\` was not cached`,
393
- };
394
+ return { ok: true, code: existed ? "entry.dropped" : "entry.absent", params: { key } };
394
395
  }
395
396
 
396
397
  case "data:drop": {
397
398
  const key = String(body.key ?? "");
398
- if (!key) return { ok: false, message: "Missing key" };
399
+ if (!key) return { ok: false, code: "key.missing" };
399
400
  const existed = dropDataCacheKey(key);
400
- return {
401
- ok: true,
402
- message: existed ? `Dropped \`${key}\`` : `\`${key}\` was not cached`,
403
- };
401
+ return { ok: true, code: existed ? "entry.dropped" : "entry.absent", params: { key } };
404
402
  }
405
403
 
406
404
  case "redis:inspect": {
407
405
  const result = await inspectRedis();
408
- if (!result.ok) return { ok: false, message: "Redis is not reachable" };
409
-
410
- const parts = [
411
- `${result.html} html keys`,
412
- `${result.data} data keys`,
413
- result.totalKeys !== null ? `${result.totalKeys} keys in db` : null,
414
- result.usedMemory ? `${result.usedMemory} used` : null,
415
- ].filter(Boolean);
406
+ if (!result.ok) return { ok: false, code: "redis.unreachable" };
416
407
 
417
- return { ok: true, message: parts.join(" · ") };
408
+ // Sayım tek cümle değil parçalar hâlinde dönüyor: `DBSIZE` ve
409
+ // `used_memory` komut kısıtlı bir Redis'te okunamıyor ve eksik parçayı
410
+ // cümleden düşürme kararı panelin.
411
+ return {
412
+ ok: true,
413
+ parts: [
414
+ { code: "redis.htmlKeys", params: { count: result.html } },
415
+ { code: "redis.dataKeys", params: { count: result.data } },
416
+ result.totalKeys !== null
417
+ ? { code: "redis.dbKeys", params: { count: result.totalKeys } }
418
+ : null,
419
+ result.usedMemory ? { code: "redis.memory", params: { value: result.usedMemory } } : null,
420
+ ].filter(Boolean),
421
+ };
418
422
  }
419
423
 
420
424
  case "redis:drop": {
421
425
  const kind = body.kind === "data" ? "data" : "html";
422
426
  const status = getRedisStatus();
423
427
  if (!status.connected) {
424
- return { ok: false, message: "Redis is not connected" };
428
+ return { ok: false, code: "redis.notConnected" };
425
429
  }
426
430
 
427
431
  const dropped = await redisDropMatching(kind);
428
- return { ok: true, message: `${dropped} shared ${kind} keys dropped` };
432
+ return {
433
+ ok: true,
434
+ code: kind === "data" ? "redis.droppedData" : "redis.droppedHtml",
435
+ params: { count: dropped },
436
+ };
429
437
  }
430
438
 
431
439
  /* ------------------------------------------------------- cloudflare */
432
440
 
433
441
  case "cf:purge-everything": {
434
442
  const result = await purgeCloudflare({ everything: true });
435
- return {
436
- ok: result.ok,
437
- message: result.ok
438
- ? "Cloudflare cache purged (everything)"
439
- : `Cloudflare: ${result.error}`,
440
- };
443
+ return result.ok
444
+ ? { ok: true, code: "cf.purgedEverything" }
445
+ : cloudflareFailure(result.error);
441
446
  }
442
447
 
443
448
  case "cf:purge-urls": {
@@ -445,23 +450,19 @@ async function runAction(body, req) {
445
450
  // Origin, isteğin geldiği host'tan türetilir ki tek zone'lu kurulumda
446
451
  // ayrıca ayar gerekmesin.
447
452
  const paths = Array.isArray(body.paths) ? body.paths.map(String) : [];
448
- if (!paths.length) return { ok: false, message: "No paths given" };
453
+ if (!paths.length) return { ok: false, code: "cf.noPaths" };
449
454
 
450
455
  const urls = toCloudflareUrls(paths, originOf(req));
451
- if (!urls.length) {
452
- return {
453
- ok: false,
454
- message: "Could not build absolute URLs — set cache().cloudflare.hostname",
455
- };
456
- }
456
+ if (!urls.length) return { ok: false, code: "cf.noHostname" };
457
457
 
458
458
  const result = await purgeCloudflare({ files: urls });
459
- return {
460
- ok: result.ok,
461
- message: result.ok
462
- ? `Purged ${result.purged} URLs at Cloudflare in ${result.batches} request(s)`
463
- : `Cloudflare: ${result.error}`,
464
- };
459
+ return result.ok
460
+ ? {
461
+ ok: true,
462
+ code: "cf.purgedUrls",
463
+ params: { count: result.purged, batches: result.batches },
464
+ }
465
+ : cloudflareFailure(result.error);
465
466
  }
466
467
 
467
468
  case "cf:purge-keys": {
@@ -473,51 +474,47 @@ async function runAction(body, req) {
473
474
  .split(/[\s,]+/)
474
475
  .filter(Boolean);
475
476
 
476
- if (!values.length) return { ok: false, message: "Nothing to purge" };
477
+ if (!values.length) return { ok: false, code: "cf.nothingToPurge" };
477
478
 
478
479
  const result = await purgeCloudflare({ [kind]: values });
479
- return {
480
- ok: result.ok,
481
- message: result.ok
482
- ? `Purged ${result.purged} ${kind} at Cloudflare`
483
- : `Cloudflare: ${result.error}`,
484
- };
480
+ return result.ok
481
+ ? { ok: true, code: "cf.purgedKeys", params: { count: result.purged, kind } }
482
+ : cloudflareFailure(result.error);
485
483
  }
486
484
 
487
485
  case "cf:setting": {
488
486
  const result = await setCloudflareSetting(body.id, body.value);
489
- return {
490
- ok: result.ok,
491
- message: result.ok
492
- ? `Cloudflare ${body.id} is now ${result.value}`
493
- : `Cloudflare: ${result.error}`,
494
- };
487
+ return result.ok
488
+ ? {
489
+ ok: true,
490
+ code: "cf.settingChanged",
491
+ params: { id: String(body.id), value: String(result.value) },
492
+ }
493
+ : cloudflareFailure(result.error);
495
494
  }
496
495
 
497
496
  case "cf:feature": {
498
497
  const value = body.value === "on" ? "on" : "off";
499
498
  const result = await setCloudflareFeature(body.feature, value);
500
- return {
501
- ok: result.ok,
502
- message: result.ok
503
- ? `Cloudflare ${body.feature} turned ${value}`
504
- : `Cloudflare: ${result.error}`,
505
- };
499
+ return result.ok
500
+ ? {
501
+ ok: true,
502
+ code: "cf.featureChanged",
503
+ params: { feature: String(body.feature), value },
504
+ }
505
+ : cloudflareFailure(result.error);
506
506
  }
507
507
 
508
508
  case "cf:clear-reserve": {
509
509
  const result = await clearCloudflareCacheReserve();
510
- return {
511
- ok: result.ok,
512
- message: result.ok
513
- ? "Cache Reserve clear started — it runs asynchronously at Cloudflare"
514
- : `Cloudflare: ${result.error}`,
515
- };
510
+ return result.ok
511
+ ? { ok: true, code: "cf.reserveClearing" }
512
+ : cloudflareFailure(result.error);
516
513
  }
517
514
 
518
515
  case "prewarm": {
519
516
  if (prewarmProgress.active) {
520
- return { ok: false, message: "A prewarm round is already running" };
517
+ return { ok: false, code: "prewarm.busy" };
521
518
  }
522
519
 
523
520
  const requested = Array.isArray(body.paths) ? body.paths : [];
@@ -535,17 +532,27 @@ async function runAction(body, req) {
535
532
  console.error("[cache-panel] prewarm failed", error);
536
533
  });
537
534
 
538
- return {
539
- ok: true,
540
- message: paths.length ? `Prewarming ${paths.length} paths` : "Prewarming all paths",
541
- };
535
+ return paths.length
536
+ ? { ok: true, code: "prewarm.paths", params: { count: paths.length } }
537
+ : { ok: true, code: "prewarm.all" };
542
538
  }
543
539
 
544
540
  default:
545
- return { ok: false, message: `Unknown action: ${type}` };
541
+ return { ok: false, code: "action.unknown", params: { type } };
546
542
  }
547
543
  }
548
544
 
545
+ /**
546
+ * Cloudflare hatası tek bir kodla döner: hata metni Cloudflare'den geliyor ve
547
+ * çevrilemez, ama etrafındaki cümle çevrilebilir.
548
+ *
549
+ * @param {string | undefined} error
550
+ * @returns {{ ok: false, code: string, params: { error: string } }}
551
+ */
552
+ function cloudflareFailure(error) {
553
+ return { ok: false, code: "cf.failed", params: { error: error ?? "unknown error" } };
554
+ }
555
+
549
556
  /* ------------------------------------------------------------------ router */
550
557
 
551
558
  /**
@@ -591,6 +598,12 @@ function router() {
591
598
  sendFile(res, "panel.css");
592
599
  });
593
600
 
601
+ // Sözlük giriş sayfasında da gerekiyor: dil seçimi oturumdan önce yapılıyor.
602
+ api.get("/i18n.js", (req, res) => {
603
+ res.type("application/javascript");
604
+ sendFile(res, "i18n.js");
605
+ });
606
+
594
607
  api.get("/logo.png", (req, res) => {
595
608
  res.type("image/png");
596
609
  fs.createReadStream(path.join(FRAMEWORK_ROOT, "src", "logo.png")).pipe(res);
@@ -634,7 +647,15 @@ function router() {
634
647
  api.use((req, res, next) => {
635
648
  if (authenticated(req)) return next();
636
649
 
637
- noteFailure(req, `unauthenticated ${req.method} ${req.path}`);
650
+ // Yetkisiz **okuma** sayaca yazılmaz. Süreç yeniden başladığında eski
651
+ // oturumlar ölüyor ve açık kalmış bir panel sekmesi yoklamaya devam
652
+ // ediyor: sayarsak operatör kendi IP'sini üç saniyede yasaklıyor. Okuma
653
+ // zaten `404` dönüyor, yani saymamanın bir bedeli yok — korunması gereken
654
+ // şey şifre denemesi ve durumu değiştiren istek.
655
+ if (req.method !== "GET" && req.method !== "HEAD") {
656
+ noteFailure(req, `unauthenticated ${req.method} ${req.path}`);
657
+ }
658
+
638
659
  notFound(res);
639
660
  });
640
661