kempo 4.8.0 → 4.9.0
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/README.md +7 -3
- package/bin/cli.js +23 -1
- package/dist/admin/components/Updates.js +117 -0
- package/dist/admin/components/Updates.js.map +1 -0
- package/dist/admin/nav.fragment.html +6 -0
- package/dist/admin/updates/index.page.html +14 -0
- package/dist/kempo/api/updates/GET.js +2 -0
- package/dist/kempo/api/updates/GET.js.map +1 -0
- package/dist/kempo/api/updates/apply/POST.js +2 -0
- package/dist/kempo/api/updates/apply/POST.js.map +1 -0
- package/dist/kempo/api/updates/check/POST.js +2 -0
- package/dist/kempo/api/updates/check/POST.js.map +1 -0
- package/dist/kempo/api/updates/discard/POST.js +2 -0
- package/dist/kempo/api/updates/discard/POST.js.map +1 -0
- package/dist/kempo/api/updates/prepare/POST.js +2 -0
- package/dist/kempo/api/updates/prepare/POST.js.map +1 -0
- package/dist/kempo/api/updates/rollback/POST.js +2 -0
- package/dist/kempo/api/updates/rollback/POST.js.map +1 -0
- package/dist/kempo/fetch.js +1 -1
- package/dist/kempo/fetch.js.map +1 -1
- package/dist/kempo/sdk.js +1 -1
- package/dist/kempo/sdk.js.map +1 -1
- package/docker/.dockerignore +9 -0
- package/docker/Dockerfile +25 -0
- package/docker/docker-compose.prod.yml +39 -0
- package/docker/entrypoint.mjs +96 -0
- package/middleware/kempo.js +53 -3
- package/package.json +3 -1
- package/server/db/schema.js +12 -0
- package/server/db/upgrades/README.md +20 -0
- package/server/sdk.js +3 -0
- package/server/utils/extensions/checkExtensionUpdate.js +9 -36
- package/server/utils/extensions/reconcileExtensions.js +90 -0
- package/server/utils/extensions/updateExtension.js +14 -153
- package/server/utils/extensions/upgradeExtension.js +117 -0
- package/server/utils/extensions/writeSiteFile.js +66 -0
- package/server/utils/settings/systemSettings.js +1 -0
- package/server/utils/setup/runUpgrades.js +70 -0
- package/server/utils/setup/seedDatabase.js +1 -1
- package/server/utils/setup/seedIfNew.js +21 -0
- package/server/utils/setup/syncCoreSchema.js +130 -0
- package/server/utils/setup/systemPermissions.js +2 -0
- package/server/utils/updates/applyStagedUpdate.js +95 -0
- package/server/utils/updates/applyUpdate.js +76 -0
- package/server/utils/updates/backupDatabase.js +35 -0
- package/server/utils/updates/discardUpdate.js +11 -0
- package/server/utils/updates/extensionTargets.js +101 -0
- package/server/utils/updates/fetchManifest.js +26 -0
- package/server/utils/updates/getExtensionUpdates.js +70 -0
- package/server/utils/updates/getInstallInfo.js +25 -0
- package/server/utils/updates/getUpdateStatus.js +57 -0
- package/server/utils/updates/helpers.js +30 -0
- package/server/utils/updates/notifySchemaAttention.js +35 -0
- package/server/utils/updates/npm.js +41 -0
- package/server/utils/updates/prepareUpdate.js +109 -0
- package/server/utils/updates/releaseStatus.js +23 -0
- package/server/utils/updates/restart.js +64 -0
- package/server/utils/updates/rollbackUpdate.js +56 -0
- package/server/utils/updates/runUpdateCheck.js +52 -0
- package/server/utils/updates/runtime.js +15 -0
- package/server/utils/updates/siteLayout.js +200 -0
- package/server/utils/updates/starterFiles.js +118 -0
package/README.md
CHANGED
|
@@ -360,15 +360,19 @@ Extensions can push live updates to browsers: declare a channel, publish to it f
|
|
|
360
360
|
|
|
361
361
|
## Admin Dashboard
|
|
362
362
|
|
|
363
|
-
The admin home page is a grid of widgets one to four blocks wide and one to three tall. Kempo provides widgets for pages, content, users, notifications and extensions; extensions add their own with `widgets` in `kempo-config.json` or `registerDashboardWidget`. Each person chooses which are shown, their order and size, kept in their browser's localStorage, and a widget only loads its script and data once it is shown. Existing sites
|
|
363
|
+
The admin home page is a grid of widgets one to four blocks wide and one to three tall. Kempo provides widgets for pages, content, users, notifications and extensions; extensions add their own with `widgets` in `kempo-config.json` or `registerDashboardWidget`. Each person chooses which are shown, their order and size, kept in their browser's localStorage, and a widget only loads its script and data once it is shown. Existing sites get the `dashboardWidget` table when the server starts. See [docs/dashboard.md](docs/dashboard.md).
|
|
364
364
|
|
|
365
365
|
## Notifications
|
|
366
366
|
|
|
367
|
-
Core and extensions can notify users with `createNotification`: to specific users, everyone holding a permission, or a group. Each person has their own read state and history, a notification can carry buttons that link somewhere or call an API as the signed-in user, and a `dedupeKey` keeps a repeating event to one notification. The admin has a bell with an unread count and a Notifications page; a public site can use `<k-notification-bell>`. Existing sites
|
|
367
|
+
Core and extensions can notify users with `createNotification`: to specific users, everyone holding a permission, or a group. Each person has their own read state and history, a notification can carry buttons that link somewhere or call an API as the signed-in user, and a `dedupeKey` keeps a repeating event to one notification. The admin has a bell with an unread count and a Notifications page; a public site can use `<k-notification-bell>`. Existing sites get the two tables when the server starts. See [docs/notifications.md](docs/notifications.md).
|
|
368
|
+
|
|
369
|
+
## Updates
|
|
370
|
+
|
|
371
|
+
Kempo checks npm every 12 hours (`system:update_check_enabled` turns it off) for itself and for every installed extension, notifies the people who hold `system:updates:manage`, and **Admin → Updates** lists what is newer with a box each and installs what is ticked, restarts the server, waits for it to return and reloads with a success message, with nothing typed on the server. `npx kempo init` offers a `Dockerfile` and `docker-compose.prod.yml` that keep the whole site (pages, templates, uploads and the code) on a volume: there an update is downloaded beside the running site, shows what it will change (starter files you never edited update, files you edited stay, security fixes ask first), switches when you choose, and a version that does not work is put back by itself. A major extension version is treated as one that may change data (warned, backed up, confirmed), and each extension's tables and `update.js` are brought up to date when the server starts. A linked or `file:` install is shown the update but never replaced. Kempo's own database tables are brought up to date every time the server starts, additively (new tables, columns and indexes; nothing is dropped or changed), a new database sets itself up, and a release that must move data ships a migration that is backed up first, so an update needs no `drizzle-kit push`. See [docs/updates.md](docs/updates.md).
|
|
368
372
|
|
|
369
373
|
## Scheduled Jobs, Aliases and Email Templates for Extensions
|
|
370
374
|
|
|
371
|
-
Extensions can declare `jobs` in `kempo-config.json` and kempo runs them on a timer (several processes sharing a database run each once per interval), `aliases` to answer on a short address such as `/cart`, and ship their own email templates as `owner/name`. Existing sites
|
|
375
|
+
Extensions can declare `jobs` in `kempo-config.json` and kempo runs them on a timer (several processes sharing a database run each once per interval), `aliases` to answer on a short address such as `/cart`, and ship their own email templates as `owner/name`. Existing sites get the `scheduledJob` table when the server starts. See [docs/extensions/creating-extensions.md](docs/extensions/creating-extensions.md).
|
|
372
376
|
|
|
373
377
|
## Email
|
|
374
378
|
|
package/bin/cli.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync, cpSync, mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
3
|
+
import { existsSync, cpSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'fs';
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
5
5
|
import { join, dirname, basename } from 'path';
|
|
6
6
|
import { execSync, spawnSync } from 'child_process';
|
|
7
7
|
import crypto from 'crypto';
|
|
8
8
|
import inquirer from 'inquirer';
|
|
9
|
+
import { recordShipped } from '../server/utils/updates/starterFiles.js';
|
|
9
10
|
|
|
10
11
|
const args = process.argv.slice(2);
|
|
11
12
|
const command = args[0];
|
|
@@ -283,6 +284,17 @@ if(!useDefaults){
|
|
|
283
284
|
emailFrom = fromEmail;
|
|
284
285
|
}
|
|
285
286
|
|
|
287
|
+
/*
|
|
288
|
+
The Docker files run the site and its database on a server, with everything the admin does kept on a volume so that
|
|
289
|
+
installing and updating Kempo from the admin survives replacing the container.
|
|
290
|
+
*/
|
|
291
|
+
const addDockerFiles = useDefaults ? true : (await inquirer.prompt([{
|
|
292
|
+
type: 'confirm',
|
|
293
|
+
name: 'addDocker',
|
|
294
|
+
message: 'Add a Dockerfile and docker-compose.prod.yml for running this site on a server?',
|
|
295
|
+
default: true
|
|
296
|
+
}])).addDocker;
|
|
297
|
+
|
|
286
298
|
/*
|
|
287
299
|
Step 3: Scaffold project files
|
|
288
300
|
*/
|
|
@@ -333,6 +345,16 @@ if(!existsSync(templatesDir) && existsSync(templatesSource)){
|
|
|
333
345
|
console.log('Scaffolded templates/');
|
|
334
346
|
}
|
|
335
347
|
|
|
348
|
+
/* Remembers what was copied, so an update later can tell a starter file nobody edited from one that was. */
|
|
349
|
+
recordShipped({ cwd: projectDir, packageRoot: moduleRoot, version: JSON.parse(readFileSync(join(moduleRoot, 'package.json'), 'utf8')).version });
|
|
350
|
+
|
|
351
|
+
if(addDockerFiles){
|
|
352
|
+
for(const [source, target] of [['Dockerfile', 'Dockerfile'], ['docker-compose.prod.yml', 'docker-compose.prod.yml'], ['.dockerignore', '.dockerignore']]){
|
|
353
|
+
if(!existsSync(join(projectDir, target))) cpSync(join(moduleRoot, 'docker', source), join(projectDir, target));
|
|
354
|
+
}
|
|
355
|
+
console.log('Added Dockerfile, docker-compose.prod.yml and .dockerignore');
|
|
356
|
+
}
|
|
357
|
+
|
|
336
358
|
const drizzleConfigPath = join(projectDir, 'drizzle.config.js');
|
|
337
359
|
const scaffoldDrizzle = join(moduleRoot, 'app-drizzle.config.js');
|
|
338
360
|
if(!existsSync(drizzleConfigPath) && existsSync(scaffoldDrizzle)){
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import e from"/kempo-ui/components/ShadowComponent.js";import{html as t,nothing as s}from"/kempo-ui/lit-all.min.js";import"/kempo-ui/components/Spinner.js";import"/kempo-ui/components/Dialog.js";import"/kempo-ui/components/Icon.js";import a from"/kempo-ui/components/Toast.js";import{getUpdateStatus as n,checkForUpdate as i,prepareUpdate as r,applyUpdate as o,rollbackUpdate as c,discardUpdate as l}from"/kempo/sdk.js";const h="kempo-after-update",d=e=>e?new Date(e).toLocaleString():"never",p=e=>new Promise(t=>setTimeout(t,e)),m=(e,t,s)=>`${e} ${1===e?t:s}`,g=(e,t)=>e.split(".").map(Number).reduce((e,s,a)=>e||s-Number(t.split(".")[a]),0)<0;export default class u extends e{static properties={status:{state:!0},error:{state:!0},checking:{state:!0},phase:{state:!0},stalled:{state:!0},action:{state:!0},chosen:{state:!0}};constructor(){super(),this.status=null,this.error="",this.checking=!1,this.phase="idle",this.stalled=!1,this.action="update",this.chosen=null}connectedCallback(){super.connectedCallback(),this.load()}load=async()=>{const[e,t]=await n();e?this.error=e.msg||"The update status could not be loaded":(this.error="",this.status=t,this.selectNew(t),this.reportOutcome())};updatable=e=>[...e.updateAvailable?[{key:"kempo",name:"Kempo",from:e.runningVersion,to:e.latestVersion,major:e.major}]:[],...(e.extensionUpdates?.updates??[]).map(e=>({key:e.name,name:e.name,from:e.installedVersion,to:e.latestVersion,major:e.major,needs:e.kempoRequired}))];selectNew=e=>{this.seen??=new Set;const t=new Set(this.chosen??[]);for(const{key:s}of this.updatable(e))this.seen.has(s)||(this.seen.add(s),t.add(s));this.chosen=t};blocked=(e,t)=>Boolean(e.needs)&&g(t.runningVersion,e.needs)&&!(this.chosen.has("kempo")&&t.updateAvailable&&!g(t.latestVersion,e.needs));chosenItems=()=>this.updatable(this.status).filter(e=>this.chosen.has(e.key)&&!this.blocked(e,this.status));toggle=e=>{const t=new Set(this.chosen);t.has(e)?t.delete(e):t.add(e),this.chosen=t};reportOutcome=()=>{try{const e=JSON.parse(sessionStorage.getItem(h)||"null");if(!e)return;sessionStorage.removeItem(h);this.status.lastUpdate&&!this.status.lastUpdate.ok&&this.status.lastUpdate.at>e.at?a.error("The update did not work, so the site went back to how it was"):a.success(e.message)}catch{}};check=async()=>{this.checking=!0;const[e,t]=await i();if(this.checking=!1,e)return void a.error(e.msg);this.status=t,this.selectNew(t);const s=t.checkError||t.extensionUpdates?.checkError;s?a.error(s):this.updatable(t).length||t.staged||a.success("Everything is up to date")};selection=()=>{const e=this.chosenItems();return{version:e.some(e=>"kempo"===e.key)?this.status.latestVersion:null,extensions:e.filter(e=>"kempo"!==e.key).map(e=>e.key)}};prepare=async()=>{this.phase="preparing";const[e]=await r(this.selection());this.phase="idle",e?a.error(e.msg):await this.load()};discard=async()=>{const[e]=await l();e?a.error(e.msg):await this.load()};openConfirm=e=>{this.action=e,this.renderRoot.querySelector("#confirmDialog").open()};confirmed=async()=>{const{staged:e,bootId:t,mode:s,lastUpdate:n}=this.status;let i,r,l;if(this.stalled=!1,"back"===this.action)this.phase="going-back",[i,r]=await c({data:Boolean(n?.breaking)}),l="The site is back on what it ran before the last update";else if("release"===s)this.phase="switching",[i,r]=await o({confirm:{forced:e.plan.files.forced.length>0,breaking:e.plan.breaking}}),l=`Updated: ${e.packages.join(", ")}`;else{this.phase="installing";const{version:e,extensions:t}=this.selection();[i,r]=await o({version:e,extensions:t}),l=`Updated: ${this.chosenItems().map(e=>`${e.name} ${e.to}`).join(", ")}`}if(i)return this.phase="idle",void a.error(i.msg);if(this.phase="restarting",await this.waitForServer(t)){try{sessionStorage.setItem(h,JSON.stringify({message:l,at:new Date(Date.now()-5e3).toISOString(),to:r.to}))}catch{}window.location.reload()}else this.stalled=!0};waitForServer=async e=>{const t=Date.now();let s=!1;for(;Date.now()-t<6e5;){await p(2e3);try{const t=await fetch("/kempo/api/updates?offline=true",{credentials:"same-origin",cache:"no-store"});if(t.ok){if((await t.json()).bootId!==e)return!0}else if(t.status>=500)s=!0;else if(s)return!0}catch{s=!0}}return!1};renderBusy(){const[e,s]={preparing:["Downloading the updates…","Your site keeps working while this happens. It can take a few minutes."],installing:["Installing the updates…","This can take a minute."],switching:["Switching to the updates…","This can take a minute."],"going-back":["Going back…","This can take a minute."],restarting:["Restarting the server…","This page reloads by itself when the site is back, and updated extensions are brought up to date first."]}[this.phase];return t`
|
|
2
|
+
<div class="b r p mb" role="status">
|
|
3
|
+
<p class="d-f mb"><k-spinner class="mr"></k-spinner><strong>${e}</strong></p>
|
|
4
|
+
<p class="tc-muted">${s} Please keep this page open.</p>
|
|
5
|
+
</div>
|
|
6
|
+
`}renderStalled(){return t`
|
|
7
|
+
<div class="b r p mb" role="alert">
|
|
8
|
+
<p class="tc-danger mb"><strong>The server did not come back within ten minutes.</strong></p>
|
|
9
|
+
<p class="mb">Check that the server process is running; if it is managed by pm2, systemd or Docker, check that it restarts when it exits.</p>
|
|
10
|
+
<button class="primary" @click=${()=>window.location.reload()}>Reload this page</button>
|
|
11
|
+
</div>
|
|
12
|
+
`}renderRestartNote(e){return"respawn"===e.restart?t`The site is unavailable for a few seconds while the server restarts.`:t`The server restarts, which takes a few seconds, and the site is unavailable until it is back.`}renderPlan(e){const{update:a,add:n,edited:i,forced:r}=e.files,o=e.upgrades.filter(e=>e.breaking),c=e.extensions.filter(e=>e.breaking),l=!(a.length||n.length||i.length||r.length||e.upgrades.length||e.extensions.length),h={permissions:["permission","permissions"],settings:["setting","settings"],groups:["group","groups"],widgets:["dashboard widget","dashboard widgets"],jobs:["scheduled job","scheduled jobs"]},d=e=>Object.entries(e.adds).filter(([,e])=>e).map(([e,t])=>m(t,...h[e])).join(", ");return t`
|
|
13
|
+
<ul class="mb">
|
|
14
|
+
${l?t`<li>Nothing changes except the code.</li>`:s}
|
|
15
|
+
${e.extensions.map(e=>t`<li>${e.name} ${e.from} <k-icon name="arrow" direction="right"></k-icon> ${e.to}${d(e)?t` <span class="tc-muted">adds ${d(e)}</span>`:s}</li>`)}
|
|
16
|
+
${a.length?t`<li>${m(a.length,"starter file","starter files")} you have not edited will be updated.</li>`:s}
|
|
17
|
+
${n.length?t`<li>${m(n.length,"new file","new files")} will be added.</li>`:s}
|
|
18
|
+
${i.length?t`<li>${m(i.length,"file","files")} you have edited will stay as you made ${1===i.length?"it":"them"}, even though Kempo has a newer version: <span class="tc-muted">${i.join(", ")}</span></li>`:s}
|
|
19
|
+
${r.length?t`<li class="tc-warning"><k-icon name="warning"></k-icon> ${m(r.length,"file","files")} you have edited must be replaced for security reasons. Your version is saved first: <span class="tc-muted">${r.join(", ")}</span></li>`:s}
|
|
20
|
+
${o.length?t`<li class="tc-warning"><k-icon name="warning"></k-icon> Kempo changes your data${o.some(e=>e.description)?t` (${o.map(e=>e.description).filter(Boolean).join("; ")})`:s}.</li>`:s}
|
|
21
|
+
${c.length?t`<li class="tc-warning"><k-icon name="warning"></k-icon> ${c.map(e=>e.name).join(", ")} ${1===c.length?"is a breaking update that":"are breaking updates that"} may change your data.</li>`:s}
|
|
22
|
+
${e.breaking?t`<li class="tc-warning"><k-icon name="warning"></k-icon> A backup of the database is taken first. Going back later restores it, which loses anything entered after the update.</li>`:s}
|
|
23
|
+
</ul>
|
|
24
|
+
`}renderStaged(e){const{staged:s}=e;return t`
|
|
25
|
+
<div class="b r p mb">
|
|
26
|
+
<h2 class="h4 mb">${m(s.packages.length,"update is","updates are")} ready</h2>
|
|
27
|
+
<p class="tc-muted mb">${s.packages.join(", ")}. ${1===s.packages.length?"It is":"They are"} downloaded next to your running site, which has not changed. Switching will:</p>
|
|
28
|
+
${this.renderPlan(s.plan)}
|
|
29
|
+
<p class="tc-muted mb">${this.renderRestartNote(e)}</p>
|
|
30
|
+
<button class="primary mr" @click=${()=>this.openConfirm("update")}>Switch now</button>
|
|
31
|
+
<button @click=${this.discard}>Not now</button>
|
|
32
|
+
</div>
|
|
33
|
+
`}renderRow(e,a){const n=this.blocked(e,a);return t`
|
|
34
|
+
<label class="d-f mb">
|
|
35
|
+
<input type="checkbox" class="mr" ?checked=${this.chosen.has(e.key)&&!n} ?disabled=${n||"idle"!==this.phase} @change=${()=>this.toggle(e.key)}>
|
|
36
|
+
<span><strong>${e.name}</strong> ${e.from} <k-icon name="arrow" direction="right"></k-icon> ${e.to}${e.major?t` <span class="tc-warning">breaking release</span>`:s}
|
|
37
|
+
${n?t`<small class="d-b tc-muted">Needs Kempo ${e.needs} or newer: tick Kempo as well.</small>`:s}</span>
|
|
38
|
+
</label>
|
|
39
|
+
`}renderAvailable(e){const a=this.updatable(e),n=this.chosenItems(),i=n.filter(e=>e.major);return t`
|
|
40
|
+
<div class="b r p mb">
|
|
41
|
+
<h2 class="h4 mb">${m(a.length,"update is","updates are")} available</h2>
|
|
42
|
+
<div class="mb">${a.map(t=>this.renderRow(t,e))}</div>
|
|
43
|
+
${i.length?t`<p class="tc-warning mb" role="alert"><k-icon name="warning"></k-icon> ${i.map(e=>e.name).join(", ")} ${1===i.length?"is a breaking release":"are breaking releases"} and may change how things work. Read ${1===i.length?"its":"their"} release notes before updating.</p>`:s}
|
|
44
|
+
${e.canUpdate?"release"===e.mode?t`
|
|
45
|
+
<p class="tc-muted mb">Getting them ready downloads them without touching your site. You choose when to switch.</p>
|
|
46
|
+
<button class="primary" ?disabled=${!n.length} @click=${this.prepare}>Get ${m(n.length,"update","updates")} ready</button>
|
|
47
|
+
`:t`
|
|
48
|
+
<p class="tc-muted mb">${this.renderRestartNote(e)}</p>
|
|
49
|
+
<button class="primary" ?disabled=${!n.length} @click=${()=>this.openConfirm("update")}>Update ${m(n.length,"item","items")}</button>
|
|
50
|
+
`:t`<p class="mb">${e.reason} Update where it is installed, then restart the server.</p>`}
|
|
51
|
+
</div>
|
|
52
|
+
`}renderCurrent(){return t`
|
|
53
|
+
<div class="b r p mb">
|
|
54
|
+
<h2 class="h4 mb"><k-icon name="check" class="tc-success"></k-icon> Everything is up to date</h2>
|
|
55
|
+
<p class="tc-muted">This site runs Kempo ${this.status.runningVersion}${this.status.latestVersion?", the newest version":""}, and its extensions are on their newest versions.</p>
|
|
56
|
+
</div>
|
|
57
|
+
`}renderLast(e){const a=e.lastUpdate;return!a||a.ok?s:t`
|
|
58
|
+
<div class="b r p mb" role="alert">
|
|
59
|
+
<h2 class="h5 mb tc-danger">The last update did not work</h2>
|
|
60
|
+
<p>The update of ${a.packages?.length?a.packages.join(", "):"Kempo"} on ${d(a.at)} did not stay running (${a.reason}), so the site went back to how it was.${a.breaking?" Your data was restored from the backup taken before it.":""}</p>
|
|
61
|
+
</div>
|
|
62
|
+
`}renderGoBack(e){if(!e.canGoBack)return s;const a=e.lastUpdate;return t`<p class="mb"><button @click=${()=>this.openConfirm("back")}>Go back</button> <span class="tc-muted">Puts the site back on what it ran before the last update${a?.ok&&a.packages?.length?` (${a.packages.join(", ")})`:""}.</span></p>`}renderDatabase(e){const{applied:a=[],failed:n=[]}=e.database??{},{upgraded:i=[],failed:r=[]}=e.extensionsRun??{};return t`
|
|
63
|
+
${n.length?t`
|
|
64
|
+
<div class="b r p mb" role="alert">
|
|
65
|
+
<h2 class="h5 mb tc-danger">The database needs attention</h2>
|
|
66
|
+
<p class="mb">Kempo started, but these changes to its tables were not applied. Anything that depends on them may not work until they are.</p>
|
|
67
|
+
<ul class="mb">
|
|
68
|
+
${n.map(({change:e,reason:s})=>t`<li>${e}: <span class="tc-danger">${s}</span></li>`)}
|
|
69
|
+
</ul>
|
|
70
|
+
<p class="tc-muted">Kempo only ever adds to its tables on its own. It never drops or changes anything that holds data, so these need someone who can look at the database.</p>
|
|
71
|
+
</div>
|
|
72
|
+
`:s}
|
|
73
|
+
${r.length?t`
|
|
74
|
+
<div class="b r p mb" role="alert">
|
|
75
|
+
<h2 class="h5 mb tc-danger">An extension could not be brought up to date</h2>
|
|
76
|
+
<ul class="mb">${r.map(({name:e,from:s,to:a,reason:n})=>t`<li>${e} ${s} <k-icon name="arrow" direction="right"></k-icon> ${a}: <span class="tc-danger">${n}</span></li>`)}</ul>
|
|
77
|
+
<p class="tc-muted">It is tried again the next time the server starts.</p>
|
|
78
|
+
</div>
|
|
79
|
+
`:s}
|
|
80
|
+
${a.length||i.length?t`
|
|
81
|
+
<div class="b r p mb">
|
|
82
|
+
<h2 class="h5 mb">Brought up to date when the server last started</h2>
|
|
83
|
+
<ul>
|
|
84
|
+
${i.map(({name:e,from:s,to:a})=>t`<li>${e} ${s} <k-icon name="arrow" direction="right"></k-icon> ${a}</li>`)}
|
|
85
|
+
${a.map(e=>t`<li>${e}</li>`)}
|
|
86
|
+
</ul>
|
|
87
|
+
</div>
|
|
88
|
+
`:s}
|
|
89
|
+
`}renderDialog(e){const a="back"===this.action,n=e.staged?.plan.files.forced??[],i=!a&&(e.staged?.plan.breaking??!1),r="release"===e.mode?e.staged?.packages??[]:this.chosen?this.chosenItems().map(e=>`${e.name} ${e.to}`):[];return t`
|
|
90
|
+
<k-dialog id="confirmDialog" confirm-text=${a?"Go back and restart":"Update and restart"} confirm-classes="primary ml" cancel-text="Cancel" .confirmAction=${this.confirmed}>
|
|
91
|
+
<h2 slot="title" class="h5 pyh px m0">${a?"Go back":"Update"}</h2>
|
|
92
|
+
<div class="p">
|
|
93
|
+
<p class="mb">${a?"This puts the site back on what it ran before the last update.":`This updates ${r.join(", ")}.`} ${this.renderRestartNote(e)}</p>
|
|
94
|
+
${n.length?t`<p class="tc-warning mb"><k-icon name="warning"></k-icon> Your edits to ${n.join(", ")} will be replaced. Your version is saved first.</p>`:s}
|
|
95
|
+
${i?t`<p class="tc-warning mb"><k-icon name="warning"></k-icon> This update changes your data. A backup is taken first, and going back later restores it, which loses anything entered after the update.</p>`:s}
|
|
96
|
+
${a&&e.lastUpdate?.breaking?t`<p class="tc-warning mb"><k-icon name="warning"></k-icon> The update changed your data, so going back restores the database from before it. Anything entered since is lost.</p>`:s}
|
|
97
|
+
<p class="tc-muted">Anyone using the site meanwhile will not be able to reach it. This page reloads when the server is back.</p>
|
|
98
|
+
</div>
|
|
99
|
+
</k-dialog>
|
|
100
|
+
`}render(){const e=this.status;if(this.error)return t`<p class="tc-danger" role="alert">${this.error}</p>`;if(!e)return t`<k-spinner></k-spinner>`;const a=this.updatable(e);return t`
|
|
101
|
+
${"idle"!==this.phase?this.renderBusy():s}
|
|
102
|
+
${this.stalled?this.renderStalled():s}
|
|
103
|
+
${"idle"===this.phase?t`
|
|
104
|
+
${this.renderLast(e)}
|
|
105
|
+
${this.renderDatabase(e)}
|
|
106
|
+
${e.staged?this.renderStaged(e):a.length?this.renderAvailable(e):this.renderCurrent()}
|
|
107
|
+
${e.checkError||e.extensionUpdates?.checkError?t`<p class="tc-danger mb" role="alert">The last check failed: ${e.checkError||e.extensionUpdates.checkError}</p>`:s}
|
|
108
|
+
<p class="d-f mb">
|
|
109
|
+
<button ?disabled=${this.checking} @click=${this.check}>${this.checking?"Checking…":"Check now"}</button>
|
|
110
|
+
<span class="tc-muted ph">Last checked ${d(e.checkedAt)}</span>
|
|
111
|
+
</p>
|
|
112
|
+
${this.renderGoBack(e)}
|
|
113
|
+
<p class="tc-muted">Automatic checks can be turned off with <strong>update_check_enabled</strong> on the System tab of <a href="/admin/settings">Settings</a>. Installing or removing an extension is done from <a href="/admin/extensions">Extensions</a>.</p>
|
|
114
|
+
`:s}
|
|
115
|
+
${this.renderDialog(e)}
|
|
116
|
+
`}}customElements.define("k-admin-updates",u);
|
|
117
|
+
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\admin\components\Updates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src\\admin\\components\\Updates.js","names":["ShadowComponent","html","nothing","Toast","getUpdateStatus","checkForUpdate","prepareUpdate","applyUpdate","rollbackUpdate","discardUpdate","AFTER_RELOAD_KEY","when","value","Date","toLocaleString","sleep","ms","Promise","resolve","setTimeout","count","n","one","many","older","a","b","split","map","Number","reduce","result","part","index","Updates","static","status","state","error","checking","phase","stalled","action","chosen","constructor","super","this","connectedCallback","load","async","msg","selectNew","reportOutcome","updatable","updateAvailable","key","name","from","runningVersion","to","latestVersion","major","extensionUpdates","updates","update","installedVersion","needs","kempoRequired","seen","Set","has","add","blocked","item","Boolean","chosenItems","filter","toggle","next","delete","expected","JSON","parse","sessionStorage","getItem","removeItem","lastUpdate","ok","at","success","message","check","problem","checkError","length","staged","selection","items","version","some","extensions","prepare","discard","openConfirm","renderRoot","querySelector","open","confirmed","bootId","mode","data","breaking","confirm","forced","plan","files","packages","join","waitForServer","setItem","stringify","now","toISOString","window","location","reload","started","wasDown","res","fetch","credentials","cache","json","oldBootId","renderBusy","title","note","preparing","installing","switching","restarting","renderStalled","renderRestartNote","restart","renderPlan","edited","breakingUpgrades","upgrades","upgrade","breakingExtensions","extension","noChanges","labels","permissions","settings","groups","widgets","jobs","adds","Object","entries","what","description","renderStaged","renderRow","renderAvailable","selected","majors","canUpdate","reason","renderCurrent","renderLast","last","renderGoBack","canGoBack","renderDatabase","applied","failed","database","upgraded","extensionFailed","extensionsRun","change","renderDialog","back","render","checkedAt","customElements","define"],"sources":["0"],"sourcesContent":["import ShadowComponent from '/kempo-ui/components/ShadowComponent.js';\nimport { html, nothing } from '/kempo-ui/lit-all.min.js';\nimport '/kempo-ui/components/Spinner.js';\nimport '/kempo-ui/components/Dialog.js';\nimport '/kempo-ui/components/Icon.js';\nimport Toast from '/kempo-ui/components/Toast.js';\nimport { getUpdateStatus, checkForUpdate, prepareUpdate, applyUpdate, rollbackUpdate, discardUpdate } from '/kempo/sdk.js';\n\n/*\n The update screen, served at /admin/components/Updates.js:\n\n <k-admin-updates></k-admin-updates>\n\n Lists what is newer on npm than what the site runs: Kempo itself and each installed extension, which the server looks up\n every 12 hours (and when \"Check now\" is pressed). Each has a box; what is ticked is updated together.\n\n A site that runs from a Docker volume updates in two steps. \"Get ready\" downloads the new versions next to the running site,\n which keeps working, and shows what switching to them would change: starter files that will update, files you edited that\n stay, files that must be replaced anyway, what each extension adds, and any change to your data. \"Switch\" asks for your word\n where that is needed, then the server restarts on the new versions. A version that does not work puts the old ones back by\n itself, and \"Go back\" does it by hand. Any other site installs the update in place and restarts.\n\n While the server is gone this screen asks it, every couple of seconds, whether it is back, then reloads the page and says how\n it went. A server that does not come back in ten minutes is reported rather than waited on forever.\n*/\nconst POLL_MS = 2000;\nconst GIVE_UP_MS = 10 * 60 * 1000;\nconst AFTER_RELOAD_KEY = 'kempo-after-update';\n\nconst when = value => (value ? new Date(value).toLocaleString() : 'never');\n\nconst sleep = ms => new Promise(resolve => setTimeout(resolve, ms));\n\nconst count = (n, one, many) => `${n} ${n === 1 ? one : many}`;\n\nconst older = (a, b) => a.split('.').map(Number).reduce((result, part, index) => result || (part - Number(b.split('.')[index])), 0) < 0;\n\nexport default class Updates extends ShadowComponent {\n static properties = {\n status: { state: true },\n error: { state: true },\n checking: { state: true },\n phase: { state: true },\n stalled: { state: true },\n action: { state: true },\n chosen: { state: true },\n };\n\n constructor(){\n super();\n this.status = null;\n this.error = '';\n this.checking = false;\n this.phase = 'idle';\n this.stalled = false;\n this.action = 'update';\n this.chosen = null;\n }\n\n /*\n Lifecycle\n */\n connectedCallback(){\n super.connectedCallback();\n this.load();\n }\n\n /*\n Loading\n */\n load = async () => {\n const [error, status] = await getUpdateStatus();\n if(error){\n this.error = error.msg || 'The update status could not be loaded';\n return;\n }\n this.error = '';\n this.status = status;\n this.selectNew(status);\n this.reportOutcome();\n };\n\n /* Everything that can be updated, as { key, name, from, to, major, needs }: Kempo first, then the extensions. */\n updatable = status => [\n ...(status.updateAvailable ? [{ key: 'kempo', name: 'Kempo', from: status.runningVersion, to: status.latestVersion, major: status.major }] : []),\n ...(status.extensionUpdates?.updates ?? []).map(update => ({ key: update.name, name: update.name, from: update.installedVersion, to: update.latestVersion, major: update.major, needs: update.kempoRequired })),\n ];\n\n /* Everything that has just become available is ticked, once; what the person unticks stays unticked. */\n selectNew = status => {\n this.seen ??= new Set();\n const chosen = new Set(this.chosen ?? []);\n for(const { key } of this.updatable(status)){\n if(!this.seen.has(key)){\n this.seen.add(key);\n chosen.add(key);\n }\n }\n this.chosen = chosen;\n };\n\n /* An extension that needs a newer Kempo than the one running can only be chosen together with the Kempo update that provides it. */\n blocked = (item, status) => Boolean(item.needs) && older(status.runningVersion, item.needs) && !(this.chosen.has('kempo') && status.updateAvailable && !older(status.latestVersion, item.needs));\n\n chosenItems = () => this.updatable(this.status).filter(item => this.chosen.has(item.key) && !this.blocked(item, this.status));\n\n toggle = key => {\n const next = new Set(this.chosen);\n if(next.has(key)) next.delete(key);\n else next.add(key);\n this.chosen = next;\n };\n\n /* After the page reloads from an update: say whether the version that was asked for is the one now running. */\n reportOutcome = () => {\n try {\n const expected = JSON.parse(sessionStorage.getItem(AFTER_RELOAD_KEY) || 'null');\n if(!expected) return;\n sessionStorage.removeItem(AFTER_RELOAD_KEY);\n const failed = this.status.lastUpdate && !this.status.lastUpdate.ok && this.status.lastUpdate.at > expected.at;\n if(failed) Toast.error(`The update did not work, so the site went back to how it was`);\n else Toast.success(expected.message);\n } catch {\n /* storage can be blocked; the page has still reloaded */\n }\n };\n\n check = async () => {\n this.checking = true;\n const [error, status] = await checkForUpdate();\n this.checking = false;\n if(error){\n Toast.error(error.msg);\n return;\n }\n this.status = status;\n this.selectNew(status);\n const problem = status.checkError || status.extensionUpdates?.checkError;\n if(problem) Toast.error(problem);\n else if(!this.updatable(status).length && !status.staged) Toast.success('Everything is up to date');\n };\n\n /*\n Getting an update ready (Docker volume sites), or throwing it away\n */\n selection = () => {\n const items = this.chosenItems();\n return { version: items.some(item => item.key === 'kempo') ? this.status.latestVersion : null, extensions: items.filter(item => item.key !== 'kempo').map(item => item.key) };\n };\n\n prepare = async () => {\n this.phase = 'preparing';\n const [error] = await prepareUpdate(this.selection());\n this.phase = 'idle';\n if(error){\n Toast.error(error.msg);\n return;\n }\n await this.load();\n };\n\n discard = async () => {\n const [error] = await discardUpdate();\n if(error){\n Toast.error(error.msg);\n return;\n }\n await this.load();\n };\n\n /*\n Switching, updating in place and going back\n */\n openConfirm = action => {\n this.action = action;\n this.renderRoot.querySelector('#confirmDialog').open();\n };\n\n confirmed = async () => {\n const { staged, bootId, mode, lastUpdate } = this.status;\n this.stalled = false;\n let error;\n let result;\n let message;\n\n if(this.action === 'back'){\n this.phase = 'going-back';\n [error, result] = await rollbackUpdate({ data: Boolean(lastUpdate?.breaking) });\n message = 'The site is back on what it ran before the last update';\n } else if(mode === 'release'){\n this.phase = 'switching';\n [error, result] = await applyUpdate({ confirm: { forced: staged.plan.files.forced.length > 0, breaking: staged.plan.breaking } });\n message = `Updated: ${staged.packages.join(', ')}`;\n } else {\n this.phase = 'installing';\n const { version, extensions } = this.selection();\n [error, result] = await applyUpdate({ version, extensions });\n message = `Updated: ${this.chosenItems().map(item => `${item.name} ${item.to}`).join(', ')}`;\n }\n\n if(error){\n this.phase = 'idle';\n Toast.error(error.msg);\n return;\n }\n this.phase = 'restarting';\n if(await this.waitForServer(bootId)){\n try {\n sessionStorage.setItem(AFTER_RELOAD_KEY, JSON.stringify({ message, at: new Date(Date.now() - 5000).toISOString(), to: result.to }));\n } catch {\n /* the reload still shows what is running */\n }\n window.location.reload();\n return;\n }\n this.stalled = true;\n };\n\n /*\n True once a server that is not the one asked about answers. The old one may still answer for a moment after\n the request that restarts it, so a reply only counts if it carries a different boot id, or if the server\n has been seen gone first (any reply short of a server error then comes from the new one, even a sign-in\n redirect that has no boot id).\n */\n waitForServer = async oldBootId => {\n const started = Date.now();\n let wasDown = false;\n while(Date.now() - started < GIVE_UP_MS){\n await sleep(POLL_MS);\n try {\n const res = await fetch('/kempo/api/updates?offline=true', { credentials: 'same-origin', cache: 'no-store' });\n if(res.ok){\n if((await res.json()).bootId !== oldBootId) return true;\n } else if(res.status >= 500){\n wasDown = true;\n } else if(wasDown){\n return true;\n }\n } catch {\n wasDown = true;\n }\n }\n return false;\n };\n\n /*\n Rendering\n */\n renderBusy(){\n const [title, note] = {\n preparing: ['Downloading the updates…', 'Your site keeps working while this happens. It can take a few minutes.'],\n installing: ['Installing the updates…', 'This can take a minute.'],\n switching: ['Switching to the updates…', 'This can take a minute.'],\n 'going-back': ['Going back…', 'This can take a minute.'],\n restarting: ['Restarting the server…', 'This page reloads by itself when the site is back, and updated extensions are brought up to date first.'],\n }[this.phase];\n return html`\n <div class=\"b r p mb\" role=\"status\">\n <p class=\"d-f mb\"><k-spinner class=\"mr\"></k-spinner><strong>${title}</strong></p>\n <p class=\"tc-muted\">${note} Please keep this page open.</p>\n </div>\n `;\n }\n\n renderStalled(){\n return html`\n <div class=\"b r p mb\" role=\"alert\">\n <p class=\"tc-danger mb\"><strong>The server did not come back within ten minutes.</strong></p>\n <p class=\"mb\">Check that the server process is running; if it is managed by pm2, systemd or Docker, check that it restarts when it exits.</p>\n <button class=\"primary\" @click=${() => window.location.reload()}>Reload this page</button>\n </div>\n `;\n }\n\n renderRestartNote(status){\n return status.restart === 'respawn'\n ? html`The site is unavailable for a few seconds while the server restarts.`\n : html`The server restarts, which takes a few seconds, and the site is unavailable until it is back.`;\n }\n\n /* What switching to the prepared versions would change. */\n renderPlan(plan){\n const { update, add, edited, forced } = plan.files;\n const breakingUpgrades = plan.upgrades.filter(upgrade => upgrade.breaking);\n const breakingExtensions = plan.extensions.filter(extension => extension.breaking);\n const noChanges = !update.length && !add.length && !edited.length && !forced.length && !plan.upgrades.length && !plan.extensions.length;\n const labels = { permissions: ['permission', 'permissions'], settings: ['setting', 'settings'], groups: ['group', 'groups'], widgets: ['dashboard widget', 'dashboard widgets'], jobs: ['scheduled job', 'scheduled jobs'] };\n const adds = extension => Object.entries(extension.adds).filter(([, n]) => n).map(([what, n]) => count(n, ...labels[what])).join(', ');\n return html`\n <ul class=\"mb\">\n ${noChanges ? html`<li>Nothing changes except the code.</li>` : nothing}\n ${plan.extensions.map(extension => html`<li>${extension.name} ${extension.from} <k-icon name=\"arrow\" direction=\"right\"></k-icon> ${extension.to}${adds(extension) ? html` <span class=\"tc-muted\">adds ${adds(extension)}</span>` : nothing}</li>`)}\n ${update.length ? html`<li>${count(update.length, 'starter file', 'starter files')} you have not edited will be updated.</li>` : nothing}\n ${add.length ? html`<li>${count(add.length, 'new file', 'new files')} will be added.</li>` : nothing}\n ${edited.length ? html`<li>${count(edited.length, 'file', 'files')} you have edited will stay as you made ${edited.length === 1 ? 'it' : 'them'}, even though Kempo has a newer version: <span class=\"tc-muted\">${edited.join(', ')}</span></li>` : nothing}\n ${forced.length ? html`<li class=\"tc-warning\"><k-icon name=\"warning\"></k-icon> ${count(forced.length, 'file', 'files')} you have edited must be replaced for security reasons. Your version is saved first: <span class=\"tc-muted\">${forced.join(', ')}</span></li>` : nothing}\n ${breakingUpgrades.length ? html`<li class=\"tc-warning\"><k-icon name=\"warning\"></k-icon> Kempo changes your data${breakingUpgrades.some(upgrade => upgrade.description) ? html` (${breakingUpgrades.map(upgrade => upgrade.description).filter(Boolean).join('; ')})` : nothing}.</li>` : nothing}\n ${breakingExtensions.length ? html`<li class=\"tc-warning\"><k-icon name=\"warning\"></k-icon> ${breakingExtensions.map(extension => extension.name).join(', ')} ${breakingExtensions.length === 1 ? 'is a breaking update that' : 'are breaking updates that'} may change your data.</li>` : nothing}\n ${plan.breaking ? html`<li class=\"tc-warning\"><k-icon name=\"warning\"></k-icon> A backup of the database is taken first. Going back later restores it, which loses anything entered after the update.</li>` : nothing}\n </ul>\n `;\n }\n\n renderStaged(status){\n const { staged } = status;\n return html`\n <div class=\"b r p mb\">\n <h2 class=\"h4 mb\">${count(staged.packages.length, 'update is', 'updates are')} ready</h2>\n <p class=\"tc-muted mb\">${staged.packages.join(', ')}. ${staged.packages.length === 1 ? 'It is' : 'They are'} downloaded next to your running site, which has not changed. Switching will:</p>\n ${this.renderPlan(staged.plan)}\n <p class=\"tc-muted mb\">${this.renderRestartNote(status)}</p>\n <button class=\"primary mr\" @click=${() => this.openConfirm('update')}>Switch now</button>\n <button @click=${this.discard}>Not now</button>\n </div>\n `;\n }\n\n renderRow(item, status){\n const blocked = this.blocked(item, status);\n return html`\n <label class=\"d-f mb\">\n <input type=\"checkbox\" class=\"mr\" ?checked=${this.chosen.has(item.key) && !blocked} ?disabled=${blocked || this.phase !== 'idle'} @change=${() => this.toggle(item.key)}>\n <span><strong>${item.name}</strong> ${item.from} <k-icon name=\"arrow\" direction=\"right\"></k-icon> ${item.to}${item.major ? html` <span class=\"tc-warning\">breaking release</span>` : nothing}\n ${blocked ? html`<small class=\"d-b tc-muted\">Needs Kempo ${item.needs} or newer: tick Kempo as well.</small>` : nothing}</span>\n </label>\n `;\n }\n\n renderAvailable(status){\n const items = this.updatable(status);\n const selected = this.chosenItems();\n const majors = selected.filter(item => item.major);\n return html`\n <div class=\"b r p mb\">\n <h2 class=\"h4 mb\">${count(items.length, 'update is', 'updates are')} available</h2>\n <div class=\"mb\">${items.map(item => this.renderRow(item, status))}</div>\n ${majors.length ? html`<p class=\"tc-warning mb\" role=\"alert\"><k-icon name=\"warning\"></k-icon> ${majors.map(item => item.name).join(', ')} ${majors.length === 1 ? 'is a breaking release' : 'are breaking releases'} and may change how things work. Read ${majors.length === 1 ? 'its' : 'their'} release notes before updating.</p>` : nothing}\n ${status.canUpdate ? (status.mode === 'release' ? html`\n <p class=\"tc-muted mb\">Getting them ready downloads them without touching your site. You choose when to switch.</p>\n <button class=\"primary\" ?disabled=${!selected.length} @click=${this.prepare}>Get ${count(selected.length, 'update', 'updates')} ready</button>\n ` : html`\n <p class=\"tc-muted mb\">${this.renderRestartNote(status)}</p>\n <button class=\"primary\" ?disabled=${!selected.length} @click=${() => this.openConfirm('update')}>Update ${count(selected.length, 'item', 'items')}</button>\n `) : html`<p class=\"mb\">${status.reason} Update where it is installed, then restart the server.</p>`}\n </div>\n `;\n }\n\n renderCurrent(){\n return html`\n <div class=\"b r p mb\">\n <h2 class=\"h4 mb\"><k-icon name=\"check\" class=\"tc-success\"></k-icon> Everything is up to date</h2>\n <p class=\"tc-muted\">This site runs Kempo ${this.status.runningVersion}${this.status.latestVersion ? ', the newest version' : ''}, and its extensions are on their newest versions.</p>\n </div>\n `;\n }\n\n renderLast(status){\n const last = status.lastUpdate;\n if(!last || last.ok) return nothing;\n return html`\n <div class=\"b r p mb\" role=\"alert\">\n <h2 class=\"h5 mb tc-danger\">The last update did not work</h2>\n <p>The update of ${last.packages?.length ? last.packages.join(', ') : 'Kempo'} on ${when(last.at)} did not stay running (${last.reason}), so the site went back to how it was.${last.breaking ? ' Your data was restored from the backup taken before it.' : ''}</p>\n </div>\n `;\n }\n\n renderGoBack(status){\n if(!status.canGoBack) return nothing;\n const last = status.lastUpdate;\n return html`<p class=\"mb\"><button @click=${() => this.openConfirm('back')}>Go back</button> <span class=\"tc-muted\">Puts the site back on what it ran before the last update${last?.ok && last.packages?.length ? ` (${last.packages.join(', ')})` : ''}.</span></p>`;\n }\n\n renderDatabase(status){\n const { applied = [], failed = [] } = status.database ?? {};\n const { upgraded = [], failed: extensionFailed = [] } = status.extensionsRun ?? {};\n return html`\n ${failed.length ? html`\n <div class=\"b r p mb\" role=\"alert\">\n <h2 class=\"h5 mb tc-danger\">The database needs attention</h2>\n <p class=\"mb\">Kempo started, but these changes to its tables were not applied. Anything that depends on them may not work until they are.</p>\n <ul class=\"mb\">\n ${failed.map(({ change, reason }) => html`<li>${change}: <span class=\"tc-danger\">${reason}</span></li>`)}\n </ul>\n <p class=\"tc-muted\">Kempo only ever adds to its tables on its own. It never drops or changes anything that holds data, so these need someone who can look at the database.</p>\n </div>\n ` : nothing}\n ${extensionFailed.length ? html`\n <div class=\"b r p mb\" role=\"alert\">\n <h2 class=\"h5 mb tc-danger\">An extension could not be brought up to date</h2>\n <ul class=\"mb\">${extensionFailed.map(({ name, from, to, reason }) => html`<li>${name} ${from} <k-icon name=\"arrow\" direction=\"right\"></k-icon> ${to}: <span class=\"tc-danger\">${reason}</span></li>`)}</ul>\n <p class=\"tc-muted\">It is tried again the next time the server starts.</p>\n </div>\n ` : nothing}\n ${applied.length || upgraded.length ? html`\n <div class=\"b r p mb\">\n <h2 class=\"h5 mb\">Brought up to date when the server last started</h2>\n <ul>\n ${upgraded.map(({ name, from, to }) => html`<li>${name} ${from} <k-icon name=\"arrow\" direction=\"right\"></k-icon> ${to}</li>`)}\n ${applied.map(change => html`<li>${change}</li>`)}\n </ul>\n </div>\n ` : nothing}\n `;\n }\n\n renderDialog(status){\n const back = this.action === 'back';\n const forced = status.staged?.plan.files.forced ?? [];\n const breaking = !back && (status.staged?.plan.breaking ?? false);\n const items = status.mode === 'release' ? (status.staged?.packages ?? []) : this.chosen ? this.chosenItems().map(item => `${item.name} ${item.to}`) : [];\n return html`\n <k-dialog id=\"confirmDialog\" confirm-text=${back ? 'Go back and restart' : 'Update and restart'} confirm-classes=\"primary ml\" cancel-text=\"Cancel\" .confirmAction=${this.confirmed}>\n <h2 slot=\"title\" class=\"h5 pyh px m0\">${back ? 'Go back' : 'Update'}</h2>\n <div class=\"p\">\n <p class=\"mb\">${back ? 'This puts the site back on what it ran before the last update.' : `This updates ${items.join(', ')}.`} ${this.renderRestartNote(status)}</p>\n ${forced.length ? html`<p class=\"tc-warning mb\"><k-icon name=\"warning\"></k-icon> Your edits to ${forced.join(', ')} will be replaced. Your version is saved first.</p>` : nothing}\n ${breaking ? html`<p class=\"tc-warning mb\"><k-icon name=\"warning\"></k-icon> This update changes your data. A backup is taken first, and going back later restores it, which loses anything entered after the update.</p>` : nothing}\n ${back && status.lastUpdate?.breaking ? html`<p class=\"tc-warning mb\"><k-icon name=\"warning\"></k-icon> The update changed your data, so going back restores the database from before it. Anything entered since is lost.</p>` : nothing}\n <p class=\"tc-muted\">Anyone using the site meanwhile will not be able to reach it. This page reloads when the server is back.</p>\n </div>\n </k-dialog>\n `;\n }\n\n render(){\n const status = this.status;\n if(this.error) return html`<p class=\"tc-danger\" role=\"alert\">${this.error}</p>`;\n if(!status) return html`<k-spinner></k-spinner>`;\n const items = this.updatable(status);\n return html`\n ${this.phase !== 'idle' ? this.renderBusy() : nothing}\n ${this.stalled ? this.renderStalled() : nothing}\n ${this.phase === 'idle' ? html`\n ${this.renderLast(status)}\n ${this.renderDatabase(status)}\n ${status.staged ? this.renderStaged(status) : (items.length ? this.renderAvailable(status) : this.renderCurrent())}\n ${status.checkError || status.extensionUpdates?.checkError ? html`<p class=\"tc-danger mb\" role=\"alert\">The last check failed: ${status.checkError || status.extensionUpdates.checkError}</p>` : nothing}\n <p class=\"d-f mb\">\n <button ?disabled=${this.checking} @click=${this.check}>${this.checking ? 'Checking…' : 'Check now'}</button>\n <span class=\"tc-muted ph\">Last checked ${when(status.checkedAt)}</span>\n </p>\n ${this.renderGoBack(status)}\n <p class=\"tc-muted\">Automatic checks can be turned off with <strong>update_check_enabled</strong> on the System tab of <a href=\"/admin/settings\">Settings</a>. Installing or removing an extension is done from <a href=\"/admin/extensions\">Extensions</a>.</p>\n ` : nothing}\n ${this.renderDialog(status)}\n `;\n }\n}\n\ncustomElements.define('k-admin-updates', Updates);\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -90,6 +90,12 @@
|
|
|
90
90
|
group="Settings"
|
|
91
91
|
no-expand
|
|
92
92
|
>Emails</k-aside-item>
|
|
93
|
+
<k-aside-item
|
|
94
|
+
icon="deployed_code_update"
|
|
95
|
+
href="/admin/updates"
|
|
96
|
+
group="Settings"
|
|
97
|
+
no-expand
|
|
98
|
+
>Updates</k-aside-item>
|
|
93
99
|
<location name="admin-nav-extensions"></location>
|
|
94
100
|
<k-aside-item
|
|
95
101
|
icon="sensors"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
locked: true
|
|
3
|
+
-->
|
|
4
|
+
<page title="Updates - Admin">
|
|
5
|
+
<content>
|
|
6
|
+
<script type="module" src="/admin/components/Updates.js"></script>
|
|
7
|
+
|
|
8
|
+
<h1>Updates</h1>
|
|
9
|
+
<p class="tc-muted mb">Kempo checks npm for a newer version and tells you here and in your notifications. Installing one downloads it and restarts the server.</p>
|
|
10
|
+
|
|
11
|
+
<k-admin-updates></k-admin-updates>
|
|
12
|
+
|
|
13
|
+
</content>
|
|
14
|
+
</page>
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import s from"../../../../server/utils/permissions/currentUserHasPermission.js";import e from"../../../../server/utils/updates/getUpdateStatus.js";export default async(r,t)=>{const[o,i]=await s(r.cookies.session_token,"system:updates:manage");if(o)return t.status(o.code).json({error:o.msg});if(!i)return t.status(403).json({error:"Insufficient permissions"});const[n,a]=await e({offline:"true"===r.query.offline});if(n)return t.status(n.code).json({error:n.msg});t.json(a)};
|
|
2
|
+
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\kempo\api\updates\GET.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src\\kempo\\api\\updates\\GET.js","names":["currentUserHasPermission","getUpdateStatus","async","request","response","permError","hasPermission","cookies","session_token","status","code","json","error","msg","data","offline","query"],"sources":["0"],"sourcesContent":["import currentUserHasPermission from '../../../../server/utils/permissions/currentUserHasPermission.js';\nimport getUpdateStatus from '../../../../server/utils/updates/getUpdateStatus.js';\n\n/* Where the site stands: what is running, the newest version on npm and whether it can be installed. `?offline=true` never asks npm, which is what the screen polls with while the server restarts. */\nexport default async (request, response) => {\n\tconst [permError, hasPermission] = await currentUserHasPermission(request.cookies.session_token, 'system:updates:manage');\n\tif(permError) return response.status(permError.code).json({ error: permError.msg });\n\tif(!hasPermission) return response.status(403).json({ error: 'Insufficient permissions' });\n\n\tconst [error, data] = await getUpdateStatus({ offline: request.query.offline === 'true' });\n\tif(error) return response.status(error.code).json({ error: error.msg });\n\n\tresponse.json(data);\n};\n"],"mappings":"OAAOA,MAA8B,0EAC9BC,MAAqB,qEAGbC,MAAOC,EAASC,KAC9B,MAAOC,EAAWC,SAAuBN,EAAyBG,EAAQI,QAAQC,cAAe,yBACjG,GAAGH,EAAW,OAAOD,EAASK,OAAOJ,EAAUK,MAAMC,KAAK,CAAEC,MAAOP,EAAUQ,MAC7E,IAAIP,EAAe,OAAOF,EAASK,OAAO,KAAKE,KAAK,CAAEC,MAAO,6BAE7D,MAAOA,EAAOE,SAAcb,EAAgB,CAAEc,QAAmC,SAA1BZ,EAAQa,MAAMD,UACrE,GAAGH,EAAO,OAAOR,EAASK,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,MAEjET,EAASO,KAAKG","ignoreList":[]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import s from"../../../../../server/utils/permissions/currentUserHasPermission.js";import e from"../../../../../server/utils/updates/applyUpdate.js";import r from"../../../../../server/utils/updates/applyStagedUpdate.js";export default async(t,o)=>{const[i,n]=await s(t.cookies.session_token,"system:updates:manage");if(i)return o.status(i.code).json({error:i.msg});if(!n)return o.status(403).json({error:"Insufficient permissions"});const[a,d]=process.env.KEMPO_SITE_ROOT?await r({confirm:t.body?.confirm||{}}):await e({version:null===t.body?.version?null:t.body?.version||"latest",extensions:Array.isArray(t.body?.extensions)?t.body.extensions:[]});if(a)return o.status(a.code).json({error:a.msg,...a.details?{details:a.details}:{}});o.json(d)};
|
|
2
|
+
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\kempo\api\updates\apply\POST.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src\\kempo\\api\\updates\\apply\\POST.js","names":["currentUserHasPermission","applyUpdate","applyStagedUpdate","async","request","response","permError","hasPermission","cookies","session_token","status","code","json","error","msg","data","process","env","KEMPO_SITE_ROOT","confirm","body","version","extensions","Array","isArray","details"],"sources":["0"],"sourcesContent":["import currentUserHasPermission from '../../../../../server/utils/permissions/currentUserHasPermission.js';\nimport applyUpdate from '../../../../../server/utils/updates/applyUpdate.js';\nimport applyStagedUpdate from '../../../../../server/utils/updates/applyStagedUpdate.js';\n\n/*\n Updates kempo and restarts the server a second after this answers. A site that runs from a Docker volume switches to the update\n that was prepared (`{ confirm: { forced: true, breaking: true } }` where it needs the owner's word, see applyStagedUpdate); any\n other site installs `{ version, extensions }` (kempo's version, the newest by default, null to leave it; a list of extension names) in place.\n*/\nexport default async (request, response) => {\n\tconst [permError, hasPermission] = await currentUserHasPermission(request.cookies.session_token, 'system:updates:manage');\n\tif(permError) return response.status(permError.code).json({ error: permError.msg });\n\tif(!hasPermission) return response.status(403).json({ error: 'Insufficient permissions' });\n\n\tconst [error, data] = process.env.KEMPO_SITE_ROOT\n\t\t? await applyStagedUpdate({ confirm: request.body?.confirm || {} })\n\t\t: await applyUpdate({ version: request.body?.version === null ? null : (request.body?.version || 'latest'), extensions: Array.isArray(request.body?.extensions) ? request.body.extensions : [] });\n\tif(error) return response.status(error.code).json({ error: error.msg, ...(error.details ? { details: error.details } : {}) });\n\n\tresponse.json(data);\n};\n"],"mappings":"OAAOA,MAA8B,6EAC9BC,MAAiB,4DACjBC,MAAuB,0EAOfC,MAAOC,EAASC,KAC9B,MAAOC,EAAWC,SAAuBP,EAAyBI,EAAQI,QAAQC,cAAe,yBACjG,GAAGH,EAAW,OAAOD,EAASK,OAAOJ,EAAUK,MAAMC,KAAK,CAAEC,MAAOP,EAAUQ,MAC7E,IAAIP,EAAe,OAAOF,EAASK,OAAO,KAAKE,KAAK,CAAEC,MAAO,6BAE7D,MAAOA,EAAOE,GAAQC,QAAQC,IAAIC,sBACzBhB,EAAkB,CAAEiB,QAASf,EAAQgB,MAAMD,SAAW,CAAC,UACvDlB,EAAY,CAAEoB,QAAmC,OAA1BjB,EAAQgB,MAAMC,QAAmB,KAAQjB,EAAQgB,MAAMC,SAAW,SAAWC,WAAYC,MAAMC,QAAQpB,EAAQgB,MAAME,YAAclB,EAAQgB,KAAKE,WAAa,KAC7L,GAAGT,EAAO,OAAOR,EAASK,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,OAASD,EAAMY,QAAU,CAAEA,QAASZ,EAAMY,SAAY,CAAC,IAExHpB,EAASO,KAAKG","ignoreList":[]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import s from"../../../../../server/utils/permissions/currentUserHasPermission.js";import r from"../../../../../server/utils/updates/getUpdateStatus.js";export default async(e,t)=>{const[o,i]=await s(e.cookies.session_token,"system:updates:manage");if(o)return t.status(o.code).json({error:o.msg});if(!i)return t.status(403).json({error:"Insufficient permissions"});const[n,a]=await r({force:!0});if(n)return t.status(n.code).json({error:n.msg});t.json(a)};
|
|
2
|
+
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\kempo\api\updates\check\POST.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src\\kempo\\api\\updates\\check\\POST.js","names":["currentUserHasPermission","getUpdateStatus","async","request","response","permError","hasPermission","cookies","session_token","status","code","json","error","msg","data","force"],"sources":["0"],"sourcesContent":["import currentUserHasPermission from '../../../../../server/utils/permissions/currentUserHasPermission.js';\nimport getUpdateStatus from '../../../../../server/utils/updates/getUpdateStatus.js';\n\n/* Asks npm for the newest version now, whatever was found earlier. */\nexport default async (request, response) => {\n\tconst [permError, hasPermission] = await currentUserHasPermission(request.cookies.session_token, 'system:updates:manage');\n\tif(permError) return response.status(permError.code).json({ error: permError.msg });\n\tif(!hasPermission) return response.status(403).json({ error: 'Insufficient permissions' });\n\n\tconst [error, data] = await getUpdateStatus({ force: true });\n\tif(error) return response.status(error.code).json({ error: error.msg });\n\n\tresponse.json(data);\n};\n"],"mappings":"OAAOA,MAA8B,6EAC9BC,MAAqB,wEAGbC,MAAOC,EAASC,KAC9B,MAAOC,EAAWC,SAAuBN,EAAyBG,EAAQI,QAAQC,cAAe,yBACjG,GAAGH,EAAW,OAAOD,EAASK,OAAOJ,EAAUK,MAAMC,KAAK,CAAEC,MAAOP,EAAUQ,MAC7E,IAAIP,EAAe,OAAOF,EAASK,OAAO,KAAKE,KAAK,CAAEC,MAAO,6BAE7D,MAAOA,EAAOE,SAAcb,EAAgB,CAAEc,OAAO,IACrD,GAAGH,EAAO,OAAOR,EAASK,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,MAEjET,EAASO,KAAKG","ignoreList":[]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import s from"../../../../../server/utils/permissions/currentUserHasPermission.js";import e from"../../../../../server/utils/updates/discardUpdate.js";export default async(r,t)=>{const[i,o]=await s(r.cookies.session_token,"system:updates:manage");if(i)return t.status(i.code).json({error:i.msg});if(!o)return t.status(403).json({error:"Insufficient permissions"});const[a,n]=await e();if(a)return t.status(a.code).json({error:a.msg,...a.details?{details:a.details}:{}});t.json(n)};
|
|
2
|
+
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\kempo\api\updates\discard\POST.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src\\kempo\\api\\updates\\discard\\POST.js","names":["currentUserHasPermission","discardUpdate","async","request","response","permError","hasPermission","cookies","session_token","status","code","json","error","msg","data","details"],"sources":["0"],"sourcesContent":["import currentUserHasPermission from '../../../../../server/utils/permissions/currentUserHasPermission.js';\nimport discardUpdate from '../../../../../server/utils/updates/discardUpdate.js';\n\n/* Throws away a prepared update that will not be switched to. */\nexport default async (request, response) => {\n\tconst [permError, hasPermission] = await currentUserHasPermission(request.cookies.session_token, 'system:updates:manage');\n\tif(permError) return response.status(permError.code).json({ error: permError.msg });\n\tif(!hasPermission) return response.status(403).json({ error: 'Insufficient permissions' });\n\n\tconst [error, data] = await discardUpdate();\n\tif(error) return response.status(error.code).json({ error: error.msg, ...(error.details ? { details: error.details } : {}) });\n\n\tresponse.json(data);\n};\n"],"mappings":"OAAOA,MAA8B,6EAC9BC,MAAmB,sEAGXC,MAAOC,EAASC,KAC9B,MAAOC,EAAWC,SAAuBN,EAAyBG,EAAQI,QAAQC,cAAe,yBACjG,GAAGH,EAAW,OAAOD,EAASK,OAAOJ,EAAUK,MAAMC,KAAK,CAAEC,MAAOP,EAAUQ,MAC7E,IAAIP,EAAe,OAAOF,EAASK,OAAO,KAAKE,KAAK,CAAEC,MAAO,6BAE7D,MAAOA,EAAOE,SAAcb,IAC5B,GAAGW,EAAO,OAAOR,EAASK,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,OAASD,EAAMG,QAAU,CAAEA,QAASH,EAAMG,SAAY,CAAC,IAExHX,EAASO,KAAKG","ignoreList":[]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import s from"../../../../../server/utils/permissions/currentUserHasPermission.js";import e from"../../../../../server/utils/updates/prepareUpdate.js";export default async(r,t)=>{const[o,n]=await s(r.cookies.session_token,"system:updates:manage");if(o)return t.status(o.code).json({error:o.msg});if(!n)return t.status(403).json({error:"Insufficient permissions"});const[i,a]=await e({version:null===r.body?.version?null:r.body?.version||"latest",extensions:Array.isArray(r.body?.extensions)?r.body.extensions:[]});if(i)return t.status(i.code).json({error:i.msg,...i.details?{details:i.details}:{}});t.json(a)};
|
|
2
|
+
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\kempo\api\updates\prepare\POST.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src\\kempo\\api\\updates\\prepare\\POST.js","names":["currentUserHasPermission","prepareUpdate","async","request","response","permError","hasPermission","cookies","session_token","status","code","json","error","msg","data","version","body","extensions","Array","isArray","details"],"sources":["0"],"sourcesContent":["import currentUserHasPermission from '../../../../../server/utils/permissions/currentUserHasPermission.js';\nimport prepareUpdate from '../../../../../server/utils/updates/prepareUpdate.js';\n\n/*\n Installs a newer kempo and/or newer versions of installed extensions next to the running site (Docker volume sites) and answers\n with what switching to them would change. The site is not touched. `{ version, extensions }`: version is the kempo version (the\n newest by default, null to leave kempo as it is), extensions a list of names.\n*/\nexport default async (request, response) => {\n\tconst [permError, hasPermission] = await currentUserHasPermission(request.cookies.session_token, 'system:updates:manage');\n\tif(permError) return response.status(permError.code).json({ error: permError.msg });\n\tif(!hasPermission) return response.status(403).json({ error: 'Insufficient permissions' });\n\n\tconst [error, data] = await prepareUpdate({ version: request.body?.version === null ? null : (request.body?.version || 'latest'), extensions: Array.isArray(request.body?.extensions) ? request.body.extensions : [] });\n\tif(error) return response.status(error.code).json({ error: error.msg, ...(error.details ? { details: error.details } : {}) });\n\n\tresponse.json(data);\n};\n"],"mappings":"OAAOA,MAA8B,6EAC9BC,MAAmB,sEAOXC,MAAOC,EAASC,KAC9B,MAAOC,EAAWC,SAAuBN,EAAyBG,EAAQI,QAAQC,cAAe,yBACjG,GAAGH,EAAW,OAAOD,EAASK,OAAOJ,EAAUK,MAAMC,KAAK,CAAEC,MAAOP,EAAUQ,MAC7E,IAAIP,EAAe,OAAOF,EAASK,OAAO,KAAKE,KAAK,CAAEC,MAAO,6BAE7D,MAAOA,EAAOE,SAAcb,EAAc,CAAEc,QAAmC,OAA1BZ,EAAQa,MAAMD,QAAmB,KAAQZ,EAAQa,MAAMD,SAAW,SAAWE,WAAYC,MAAMC,QAAQhB,EAAQa,MAAMC,YAAcd,EAAQa,KAAKC,WAAa,KAClN,GAAGL,EAAO,OAAOR,EAASK,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,OAASD,EAAMQ,QAAU,CAAEA,QAASR,EAAMQ,SAAY,CAAC,IAExHhB,EAASO,KAAKG","ignoreList":[]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import s from"../../../../../server/utils/permissions/currentUserHasPermission.js";import r from"../../../../../server/utils/updates/rollbackUpdate.js";export default async(e,t)=>{const[o,i]=await s(e.cookies.session_token,"system:updates:manage");if(o)return t.status(o.code).json({error:o.msg});if(!i)return t.status(403).json({error:"Insufficient permissions"});const[n,a]=await r({confirm:e.body?.confirm||{}});if(n)return t.status(n.code).json({error:n.msg,...n.details?{details:n.details}:{}});t.json(a)};
|
|
2
|
+
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\kempo\api\updates\rollback\POST.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"src\\kempo\\api\\updates\\rollback\\POST.js","names":["currentUserHasPermission","rollbackUpdate","async","request","response","permError","hasPermission","cookies","session_token","status","code","json","error","msg","data","confirm","body","details"],"sources":["0"],"sourcesContent":["import currentUserHasPermission from '../../../../../server/utils/permissions/currentUserHasPermission.js';\nimport rollbackUpdate from '../../../../../server/utils/updates/rollbackUpdate.js';\n\n/* Goes back to the version before the last update and restarts. `{ confirm: { data: true } }` when that also restores the database. */\nexport default async (request, response) => {\n\tconst [permError, hasPermission] = await currentUserHasPermission(request.cookies.session_token, 'system:updates:manage');\n\tif(permError) return response.status(permError.code).json({ error: permError.msg });\n\tif(!hasPermission) return response.status(403).json({ error: 'Insufficient permissions' });\n\n\tconst [error, data] = await rollbackUpdate({ confirm: request.body?.confirm || {} });\n\tif(error) return response.status(error.code).json({ error: error.msg, ...(error.details ? { details: error.details } : {}) });\n\n\tresponse.json(data);\n};\n"],"mappings":"OAAOA,MAA8B,6EAC9BC,MAAoB,uEAGZC,MAAOC,EAASC,KAC9B,MAAOC,EAAWC,SAAuBN,EAAyBG,EAAQI,QAAQC,cAAe,yBACjG,GAAGH,EAAW,OAAOD,EAASK,OAAOJ,EAAUK,MAAMC,KAAK,CAAEC,MAAOP,EAAUQ,MAC7E,IAAIP,EAAe,OAAOF,EAASK,OAAO,KAAKE,KAAK,CAAEC,MAAO,6BAE7D,MAAOA,EAAOE,SAAcb,EAAe,CAAEc,QAASZ,EAAQa,MAAMD,SAAW,CAAC,IAChF,GAAGH,EAAO,OAAOR,EAASK,OAAOG,EAAMF,MAAMC,KAAK,CAAEC,MAAOA,EAAMC,OAASD,EAAMK,QAAU,CAAEA,QAASL,EAAMK,SAAY,CAAC,IAExHb,EAASO,KAAKG","ignoreList":[]}
|
package/dist/kempo/fetch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=new Map,t=async(t,
|
|
1
|
+
const e=new Map,t=async(t,a)=>{const r=((e,t={})=>`${t.method||"GET"}:${e}:${t.body||""}`)(t,a);if(e.has(r)){const t=e.get(r);return new Promise((e,a)=>{t.callbacks.add({resolve:e,reject:a})}).then(async e=>{if(!e.ok){let t;try{t=await e.json()}catch(t){return[{code:e.status,msg:"Service temporarily unavailable. Please try again later."},null]}return[{code:e.status,msg:t.error||t.message||"An error occurred",...t.details?{details:t.details}:{}},null]}try{return[null,await e.json()]}catch(e){return[{code:500,msg:"Service temporarily unavailable. Please try again later."},null]}})}const n=new Set,o=fetch(t,a).then(t=>(n.forEach(({resolve:e})=>e(t.clone())),e.delete(r),t)).catch(t=>{throw n.forEach(({reject:e})=>e(t)),e.delete(r),t});e.set(r,{fetchPromise:o,callbacks:n});try{const e=await o;if(!e.ok){let t;try{t=await e.json()}catch(t){return[{code:e.status,msg:"Service temporarily unavailable. Please try again later."},null]}return[{code:e.status,msg:t.error||t.message||"An error occurred",...t.details?{details:t.details}:{}},null]}try{return[null,await e.json()]}catch(e){return[{code:500,msg:"Service temporarily unavailable. Please try again later."},null]}}catch(e){return"Service temporarily unavailable. Please try again later."===e.message||e.message.includes("Failed to fetch")?[{code:503,msg:"Service temporarily unavailable. Please try again later."},null]:[{code:500,msg:e.message||"An error occurred"},null]}};export const get=(e,a)=>{if(a){const r=Object.entries(a).filter(([,e])=>null!=e);if(r.length){const a=r.map(([e,t])=>Array.isArray(t)?`${encodeURIComponent(e)}=${t.map(e=>encodeURIComponent(e)).join(",")}`:`${encodeURIComponent(e)}=${encodeURIComponent(t)}`).join("&");return t(`${e}?${a}`,{method:"GET"})}}return t(e,{method:"GET"})};export const post=(e,a)=>t(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)});export const put=(e,a)=>t(e,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)});export const patch=(e,a)=>t(e,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)});export const del=(e,a)=>t(e,{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)});
|
|
2
2
|
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\kempo\fetch.js.map
|
package/dist/kempo/fetch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"src\\kempo\\fetch.js","names":["pendingRequests","Map","dedupFetch","async","url","options","key","method","body","createRequestKey","has","existingRequest","get","Promise","resolve","reject","callbacks","add","then","response","ok","errorData","json","e","code","status","msg","error","message","Set","fetchPromise","fetch","forEach","clone","delete","catch","set","includes","params","entries","Object","filter","v","length","queryString","map","value","Array","isArray","encodeURIComponent","join","post","data","headers","JSON","stringify","put","patch","del"],"sources":["0"],"sourcesContent":["const pendingRequests = new Map();\r\n\r\nconst createRequestKey = (url, options = {}) => {\r\n const method = options.method || 'GET';\r\n const body = options.body || '';\r\n return `${method}:${url}:${body}`;\r\n};\r\n\r\nconst dedupFetch = async (url, options) => {\r\n const key = createRequestKey(url, options);\r\n if(pendingRequests.has(key)){\r\n const existingRequest = pendingRequests.get(key);\r\n return new Promise((resolve, reject) => {\r\n existingRequest.callbacks.add({ resolve, reject });\r\n }).then(async response => {\r\n if(!response.ok) {\r\n let errorData;\r\n try {\r\n errorData = await response.json();\r\n } catch(e) {\r\n return [{ code: response.status, msg: 'Service temporarily unavailable. Please try again later.' }, null];\r\n }\r\n return [{ code: response.status, msg: errorData.error || errorData.message || 'An error occurred' }, null];\r\n }\r\n try {\r\n return [null, await response.json()];\r\n } catch(e) {\r\n return [{ code: 500, msg: 'Service temporarily unavailable. Please try again later.' }, null];\r\n }\r\n });\r\n }\r\n const callbacks = new Set();\r\n const fetchPromise = fetch(url, options)\r\n .then(response => {\r\n callbacks.forEach(({ resolve }) => resolve(response.clone()));\r\n pendingRequests.delete(key);\r\n return response;\r\n })\r\n .catch(error => {\r\n callbacks.forEach(({ reject }) => reject(error));\r\n pendingRequests.delete(key);\r\n throw error;\r\n });\r\n pendingRequests.set(key, { fetchPromise, callbacks });\r\n try {\r\n const response = await fetchPromise;\r\n if(!response.ok) {\r\n let errorData;\r\n try {\r\n errorData = await response.json();\r\n } catch(e) {\r\n return [{ code: response.status, msg: 'Service temporarily unavailable. Please try again later.' }, null];\r\n }\r\n return [{ code: response.status, msg: errorData.error || errorData.message || 'An error occurred' }, null];\r\n }\r\n try {\r\n return [null, await response.json()];\r\n } catch(e) {\r\n return [{ code: 500, msg: 'Service temporarily unavailable. Please try again later.' }, null];\r\n }\r\n } catch(error) {\r\n if(error.message === 'Service temporarily unavailable. Please try again later.' || error.message.includes('Failed to fetch')) {\r\n return [{ code: 503, msg: 'Service temporarily unavailable. Please try again later.' }, null];\r\n }\r\n return [{ code: 500, msg: error.message || 'An error occurred' }, null];\r\n }\r\n};\r\n\r\nexport const get = (url, params) => {\r\n if(params){\r\n const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== null);\r\n if(entries.length){\r\n const queryString = entries\r\n .map(([key, value]) => {\r\n if(Array.isArray(value)){\r\n return `${encodeURIComponent(key)}=${value.map(v => encodeURIComponent(v)).join(',')}`;\r\n }\r\n return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;\r\n })\r\n .join('&');\r\n return dedupFetch(`${url}?${queryString}`, { method: 'GET' });\r\n }\r\n }\r\n return dedupFetch(url, { method: 'GET' });\r\n};\r\n\r\nexport const post = (url, data) => dedupFetch(url, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data)\r\n});\r\n\r\nexport const put = (url, data) => dedupFetch(url, {\r\n method: 'PUT',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data)\r\n});\r\n\r\nexport const patch = (url, data) => dedupFetch(url, {\r\n method: 'PATCH',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data)\r\n});\r\n\r\nexport const del = (url, data) => dedupFetch(url, {\r\n method: 'DELETE',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data)\r\n});\r\n"],"mappings":"AAAA,MAAMA,EAAkB,IAAIC,IAQtBC,EAAaC,MAAOC,EAAKC,KAC7B,MAAMC,EAPiB,EAACF,EAAKC,EAAU,CAAC,IAGjC,GAFQA,EAAQE,QAAU,SAEbH,KADPC,EAAQG,MAAQ,KAKjBC,CAAiBL,EAAKC,GAClC,GAAGL,EAAgBU,IAAIJ,GAAK,CAC1B,MAAMK,EAAkBX,EAAgBY,IAAIN,GAC5C,OAAO,IAAIO,QAAQ,CAACC,EAASC,KAC3BJ,EAAgBK,UAAUC,IAAI,CAAEH,UAASC,aACxCG,KAAKf,UACN,IAAIgB,EAASC,GAAI,CACf,IAAIC,EACJ,IACEA,QAAkBF,EAASG,MAC7B,CAAE,MAAMC,GACN,MAAO,CAAC,CAAEC,KAAML,EAASM,OAAQC,IAAK,4DAA8D,KACtG,CACA,MAAO,CAAC,CAAEF,KAAML,EAASM,OAAQC,IAAKL,EAAUM,OAASN,EAAUO,SAAW,
|
|
1
|
+
{"version":3,"file":"src\\kempo\\fetch.js","names":["pendingRequests","Map","dedupFetch","async","url","options","key","method","body","createRequestKey","has","existingRequest","get","Promise","resolve","reject","callbacks","add","then","response","ok","errorData","json","e","code","status","msg","error","message","details","Set","fetchPromise","fetch","forEach","clone","delete","catch","set","includes","params","entries","Object","filter","v","length","queryString","map","value","Array","isArray","encodeURIComponent","join","post","data","headers","JSON","stringify","put","patch","del"],"sources":["0"],"sourcesContent":["const pendingRequests = new Map();\r\n\r\nconst createRequestKey = (url, options = {}) => {\r\n const method = options.method || 'GET';\r\n const body = options.body || '';\r\n return `${method}:${url}:${body}`;\r\n};\r\n\r\nconst dedupFetch = async (url, options) => {\r\n const key = createRequestKey(url, options);\r\n if(pendingRequests.has(key)){\r\n const existingRequest = pendingRequests.get(key);\r\n return new Promise((resolve, reject) => {\r\n existingRequest.callbacks.add({ resolve, reject });\r\n }).then(async response => {\r\n if(!response.ok) {\r\n let errorData;\r\n try {\r\n errorData = await response.json();\r\n } catch(e) {\r\n return [{ code: response.status, msg: 'Service temporarily unavailable. Please try again later.' }, null];\r\n }\r\n return [{ code: response.status, msg: errorData.error || errorData.message || 'An error occurred', ...(errorData.details ? { details: errorData.details } : {}) }, null];\r\n }\r\n try {\r\n return [null, await response.json()];\r\n } catch(e) {\r\n return [{ code: 500, msg: 'Service temporarily unavailable. Please try again later.' }, null];\r\n }\r\n });\r\n }\r\n const callbacks = new Set();\r\n const fetchPromise = fetch(url, options)\r\n .then(response => {\r\n callbacks.forEach(({ resolve }) => resolve(response.clone()));\r\n pendingRequests.delete(key);\r\n return response;\r\n })\r\n .catch(error => {\r\n callbacks.forEach(({ reject }) => reject(error));\r\n pendingRequests.delete(key);\r\n throw error;\r\n });\r\n pendingRequests.set(key, { fetchPromise, callbacks });\r\n try {\r\n const response = await fetchPromise;\r\n if(!response.ok) {\r\n let errorData;\r\n try {\r\n errorData = await response.json();\r\n } catch(e) {\r\n return [{ code: response.status, msg: 'Service temporarily unavailable. Please try again later.' }, null];\r\n }\r\n return [{ code: response.status, msg: errorData.error || errorData.message || 'An error occurred', ...(errorData.details ? { details: errorData.details } : {}) }, null];\r\n }\r\n try {\r\n return [null, await response.json()];\r\n } catch(e) {\r\n return [{ code: 500, msg: 'Service temporarily unavailable. Please try again later.' }, null];\r\n }\r\n } catch(error) {\r\n if(error.message === 'Service temporarily unavailable. Please try again later.' || error.message.includes('Failed to fetch')) {\r\n return [{ code: 503, msg: 'Service temporarily unavailable. Please try again later.' }, null];\r\n }\r\n return [{ code: 500, msg: error.message || 'An error occurred' }, null];\r\n }\r\n};\r\n\r\nexport const get = (url, params) => {\r\n if(params){\r\n const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== null);\r\n if(entries.length){\r\n const queryString = entries\r\n .map(([key, value]) => {\r\n if(Array.isArray(value)){\r\n return `${encodeURIComponent(key)}=${value.map(v => encodeURIComponent(v)).join(',')}`;\r\n }\r\n return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;\r\n })\r\n .join('&');\r\n return dedupFetch(`${url}?${queryString}`, { method: 'GET' });\r\n }\r\n }\r\n return dedupFetch(url, { method: 'GET' });\r\n};\r\n\r\nexport const post = (url, data) => dedupFetch(url, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data)\r\n});\r\n\r\nexport const put = (url, data) => dedupFetch(url, {\r\n method: 'PUT',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data)\r\n});\r\n\r\nexport const patch = (url, data) => dedupFetch(url, {\r\n method: 'PATCH',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data)\r\n});\r\n\r\nexport const del = (url, data) => dedupFetch(url, {\r\n method: 'DELETE',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(data)\r\n});\r\n"],"mappings":"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","ignoreList":[]}
|
package/dist/kempo/sdk.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{get as e,post as s,put as n,patch as t,del as o}from"./fetch.js";const a="/kempo/api";export const register=async({email:e,password:n,name:t})=>s(`${a}/auth/register/email`,{email:e,password:n,name:t});export const login=async({email:e,password:n})=>s(`${a}/auth/login/email`,{email:e,password:n});export const logout=async()=>s(`${a}/auth/logout`,{});export const getSession=async()=>e(`${a}/auth/session`);export const forgotPassword=async({email:e})=>s(`${a}/forgot-password`,{email:e});export const resetPassword=async({token:e,password:n,logoutAll:t=!1})=>s(`${a}/auth/reset-password`,{token:e,newPassword:n,logoutAll:t});export const changePassword=async({currentPassword:e,newPassword:n,logoutAll:t})=>s(`${a}/auth/change-password`,{currentPassword:e,newPassword:n,logoutAll:t});export const verifyEmail=async({token:e})=>s(`${a}/auth/verify-email`,{token:e});export const currentUserHasPermission=async({permission:s})=>e(`${a}/user/current/permissions`,{permission:s});export const currentUserHasAllPermissions=async({permissions:s})=>e(`${a}/user/current/permissions/all`,{permissions:s});export const currentUserHasSomePermissions=async({permissions:s})=>e(`${a}/user/current/permissions/some`,{permissions:s});export const getAllCurrentUserPermissions=async()=>e(`${a}/user/current/permissions/list`);export const userHasPermission=async({userid:s,permission:n})=>e(`${a}/user/${s}/permissions`,{permission:n});export const userHasAllPermissions=async({userid:s,permissions:n})=>e(`${a}/user/${s}/permissions/all`,{permissions:n});export const userHasSomePermissions=async({userid:s,permissions:n})=>e(`${a}/user/${s}/permissions/some`,{permissions:n});export const getAllUserPermissions=async({userid:s})=>e(`${a}/user/${s}/permissions/list`);export const getUsers=async({limit:s,offset:n}={})=>e(`${a}/user`,{limit:s,offset:n});export const searchUsers=async({q:s,limit:n,offset:t,inGroup:o,notInGroup:i}={})=>e(`${a}/user/search`,{q:s,limit:n,offset:t,inGroup:o,notInGroup:i});export const createUser=async({name:e,email:n,password:t,emailVerified:o})=>s(`${a}/user/create`,{name:e,email:n,password:t,emailVerified:o});export const deleteUsers=async e=>o(`${a}/user`,{ids:e});export const updateUser=async({id:e,name:s,email:n,emailVerified:o,createdAt:i})=>t(`${a}/user`,{id:e,name:s,email:n,emailVerified:o,createdAt:i});export const getUser=async s=>e(`${a}/user/${s}`);export const getUserSessions=async(s,{limit:n,offset:t}={})=>e(`${a}/user/${s}/sessions`,{limit:n,offset:t});export const deleteUserSession=async(e,s)=>o(`${a}/user/${e}/sessions`,{sessionToken:s});export const deleteExpiredUserSessions=async e=>o(`${a}/user/${e}/sessions/expired`);export const getUserGroups=async(s,{limit:n,offset:t}={})=>e(`${a}/user/${s}/groups`,{limit:n,offset:t});export const addUserToGroup=async(e,n)=>s(`${a}/user/${e}/groups`,{groupName:n});export const removeUserFromGroup=async(e,s)=>o(`${a}/user/${e}/groups`,{groupName:s});export const listGroups=async({limit:s,offset:n,owner:t}={})=>e(`${a}/groups`,{limit:s,offset:n,owner:t});export const createGroup=async({name:e,description:n})=>s(`${a}/groups`,{name:e,description:n});export const updateGroup=async({name:e,newName:s,description:n})=>t(`${a}/groups`,{name:e,newName:s,description:n});export const deleteGroups=async e=>o(`${a}/groups`,{names:e});export const getGroup=async s=>e(`${a}/groups/${encodeURIComponent(s)}`);export const getGroupMembers=async(s,{limit:n,offset:t}={})=>e(`${a}/groups/${encodeURIComponent(s)}/members`,{limit:n,offset:t});export const addMemberToGroup=async(e,n)=>s(`${a}/groups/${encodeURIComponent(e)}/members`,{userId:n});export const removeMemberFromGroup=async(e,s)=>o(`${a}/groups/${encodeURIComponent(e)}/members`,{userId:s});export const getGroupPermissions=async s=>e(`${a}/groups/${encodeURIComponent(s)}/permissions`);export const addPermissionToGroup=async(e,n)=>s(`${a}/groups/${encodeURIComponent(e)}/permissions`,{permissionName:n});export const removePermissionFromGroup=async(e,s)=>o(`${a}/groups/${encodeURIComponent(e)}/permissions`,{permissionName:s});export const listPermissions=async({limit:s,offset:n,owner:t}={})=>e(`${a}/permissions`,{limit:s,offset:n,owner:t});export const createPermissions=async e=>s(`${a}/permissions`,{permissions:e});export const updatePermission=async({name:e,description:s})=>t(`${a}/permissions`,{name:e,description:s});export const deletePermissions=async e=>o(`${a}/permissions`,{names:e});export const getSetting=async(s,n)=>e(`${a}/settings/${encodeURIComponent(s)}/${encodeURIComponent(n)}`);export const listSettings=async({owner:s,limit:n,offset:t}={})=>e(`${a}/settings/${encodeURIComponent(s)}`,{limit:n,offset:t});export const setSetting=async(e,n,t)=>s(`${a}/settings/${encodeURIComponent(e)}/${encodeURIComponent(n)}`,t);export const deleteSetting=async(e,s)=>o(`${a}/settings/${encodeURIComponent(e)}/${encodeURIComponent(s)}`);export const listEmails=async({status:s,type:n,search:t,limit:o,offset:i}={})=>e(`${a}/emails`,{status:s,type:n,search:t,limit:o,offset:i});export const getEmail=async s=>e(`${a}/emails/${encodeURIComponent(s)}`);export const retryEmail=async e=>s(`${a}/emails/${encodeURIComponent(e)}/retry`,{});export const sendTestEmail=async e=>s(`${a}/emails/test`,{to:e});export const listPages=async({owner:s,showHidden:n,offset:t,limit:o}={})=>e(`${a}/pages`,{owner:s,showHidden:n,offset:t,limit:o});export const rescanPages=async()=>s(`${a}/pages/rescan`,{});export const listTemplates=async()=>e(`${a}/templates`);export const listDirectories=async()=>e(`${a}/pages/directories`);export const getPage=async s=>e(`${a}/pages/file`,{file:s});export const createPage=async({directory:e,name:n,template:t})=>s(`${a}/pages`,{directory:e,name:n,template:t});export const updatePage=async({file:e,name:s,title:t,description:o,author:i,template:r,contents:c,extraMetadata:l})=>n(`${a}/pages/file`,{file:e,name:s,title:t,description:o,author:i,template:r,contents:c,extraMetadata:l});export const deletePages=async e=>o(`${a}/pages`,{files:e});export const disablePage=async e=>n(`${a}/pages/disable`,{file:e});export const enablePage=async e=>n(`${a}/pages/enable`,{file:e});export const movePage=async({file:e,newFile:s})=>t(`${a}/pages/file`,{file:e,newFile:s});export const searchPagesByMetadata=async s=>e(`${a}/pages/search`,{query:JSON.stringify(s)});export const setPageLocked=async({file:e,locked:s})=>n(`${a}/pages/lock`,{file:e,locked:s});export const getTemplate=async s=>e(`${a}/templates/file`,{file:s});export const createTemplate=async({directory:e,name:n,copyFrom:t})=>s(`${a}/templates`,{directory:e,name:n,copyFrom:t});export const updateTemplate=async({file:e,name:s,author:t,markup:o})=>n(`${a}/templates/file`,{file:e,name:s,author:t,markup:o});export const deleteTemplates=async e=>o(`${a}/templates`,{files:e});export const listEmailTemplates=async s=>e(`${a}/email-templates`,s?{owner:s}:{});export const getEmailTemplate=async s=>e(`${a}/email-templates/file`,{template:s});export const saveEmailTemplate=async({template:e,source:s})=>n(`${a}/email-templates/file`,{template:e,source:s});export const resetEmailTemplate=async e=>o(`${a}/email-templates/file`,{template:e});export const disableTemplate=async e=>n(`${a}/templates/disable`,{file:e});export const enableTemplate=async e=>n(`${a}/templates/enable`,{file:e});export const moveTemplate=async({file:e,newFile:s})=>t(`${a}/templates/file`,{file:e,newFile:s});export const setTemplateLocked=async({file:e,locked:s})=>n(`${a}/templates/lock`,{file:e,locked:s});export const listFragments=async()=>e(`${a}/fragments`);export const getFragment=async s=>e(`${a}/fragments/file`,{file:s});export const createFragment=async({directory:e,name:n})=>s(`${a}/fragments`,{directory:e,name:n});export const updateFragment=async({file:e,author:s,markup:t})=>n(`${a}/fragments/file`,{file:e,author:s,markup:t});export const deleteFragments=async e=>o(`${a}/fragments`,{files:e});export const moveFragment=async({file:e,newFile:s})=>t(`${a}/fragments/file`,{file:e,newFile:s});export const disableFragment=async e=>n(`${a}/fragments/disable`,{file:e});export const enableFragment=async e=>n(`${a}/fragments/enable`,{file:e});export const setFragmentLocked=async({file:e,locked:s})=>n(`${a}/fragments/lock`,{file:e,locked:s});export const listGlobalContent=async()=>e(`${a}/globals`);export const getGlobalContent=async s=>e(`${a}/globals/entry`,{id:s});export const createGlobalContent=async({name:e,location:n,priority:t})=>s(`${a}/globals`,{name:e,location:n,priority:t});export const updateGlobalContent=async({id:e,name:s,location:t,priority:o,markup:i})=>n(`${a}/globals/entry`,{id:e,name:s,location:t,priority:o,markup:i});export const deleteGlobalContent=async e=>o(`${a}/globals`,{ids:e});export const disableGlobalContent=async e=>n(`${a}/globals/disable`,{id:e});export const enableGlobalContent=async e=>n(`${a}/globals/enable`,{id:e});export const setGlobalContentLocked=async({id:e,locked:s})=>n(`${a}/globals/lock`,{id:e,locked:s});export const listMenus=async()=>e(`${a}/menus`);export const saveMenu=async({id:e,name:n})=>s(`${a}/menus`,{id:e,name:n});export const deleteMenu=async e=>o(`${a}/menus`,{id:e});export const assignMenuLocation=async({location:e,menuId:s})=>n(`${a}/menus/location`,{location:e,menuId:s});export const saveMenuItem=async({id:e,menuId:n,location:t,navItem:o,label:i,href:r,icon:c,newTab:l})=>s(`${a}/menus/items`,{id:e,menuId:n,location:t,navItem:o,label:i,href:r,icon:c,newTab:l});export const deleteMenuItems=async e=>o(`${a}/menus/items`,{ids:e});export const setMenuItemEnabled=async({id:e,enabled:s})=>n(`${a}/menus/items/enabled`,{id:e,enabled:s});export const reorderMenuItems=async({menuId:e,ids:s})=>n(`${a}/menus/items/order`,{menuId:e,ids:s});export const adoptMenuItem=async({id:e,menuId:s,menuName:t})=>n(`${a}/menus/adopt`,{id:e,menuId:s,menuName:t});export const listExtensions=async({limit:s,offset:n}={})=>e(`${a}/extensions`,{limit:s,offset:n});export const listAvailableExtensions=async()=>e(`${a}/extensions/available`);export const listKnownExtensions=async({search:s,sort:n,limit:t,offset:o}={})=>e(`${a}/extensions/known`,{search:s,sort:n,limit:t,offset:o});export const getKnownExtension=async s=>e(`${a}/extensions/known`,{name:s});export const rateExtension=async(e,s)=>n(`${a}/extensions/rating`,{name:e,rating:s});export const getDashboardWidgets=async()=>e(`${a}/dashboard/widgets`);export const getNotifications=async({unreadOnly:s=!1,limit:n,offset:t}={})=>e(`${a}/notifications`,{unreadOnly:s,limit:n,offset:t});export const getUnreadNotificationCount=async()=>e(`${a}/notifications/count`);export const markNotificationRead=async e=>s(`${a}/notifications/${encodeURIComponent(e)}/read`,{});export const markNotificationHandled=async e=>s(`${a}/notifications/${encodeURIComponent(e)}/handled`,{});export const markAllNotificationsRead=async()=>s(`${a}/notifications/read-all`,{});export const dismissNotification=async e=>o(`${a}/notifications/${encodeURIComponent(e)}`);export const listRealtimeConnections=async()=>e(`${a}/realtime/connections`);export const installExtension=async(e,{withDependencies:n=!1}={})=>s(`${a}/extensions`,{name:e,withDependencies:n});export const uninstallExtension=async(e,s=!1,{cascade:n=!1}={})=>o(`${a}/extensions`,{name:e,purgeData:s,cascade:n});export const enableExtension=async(e,{cascade:n=!1}={})=>s(`${a}/extensions/${encodeURIComponent(e)}/enable`,{cascade:n});export const getExtensionRequirements=async s=>e(`${a}/extensions/${encodeURIComponent(s)}/requirements`);export const disableExtension=async(e,{cascade:n=!1}={})=>s(`${a}/extensions/${encodeURIComponent(e)}/disable`,{cascade:n});export const getExtensionDependents=async(s,{includeDisabled:n=!1}={})=>e(`${a}/extensions/${encodeURIComponent(s)}/dependents`,{includeDisabled:n?"true":void 0});export const listAdminGlobalContent=async()=>e(`${a}/admin-globals`);export const getAdminGlobalContent=async s=>e(`${a}/admin-globals/${encodeURIComponent(s)}`);export const createAdminGlobalContent=async({name:e,location:n,priority:t,owner:o,markup:i})=>s(`${a}/admin-globals`,{name:e,location:n,priority:t,owner:o,markup:i});export const updateAdminGlobalContent=async(e,{name:s,location:t,priority:o,markup:i})=>n(`${a}/admin-globals/${encodeURIComponent(e)}`,{name:s,location:t,priority:o,markup:i});export const deleteAdminGlobalContent=async e=>o(`${a}/admin-globals`,{ids:e});export const enableAdminGlobalContent=async e=>n(`${a}/admin-globals/${encodeURIComponent(e)}/enable`,{});export const disableAdminGlobalContent=async e=>n(`${a}/admin-globals/${encodeURIComponent(e)}/disable`,{});
|
|
1
|
+
import{get as e,post as s,put as t,patch as n,del as o}from"./fetch.js";const a="/kempo/api";export const register=async({email:e,password:t,name:n})=>s(`${a}/auth/register/email`,{email:e,password:t,name:n});export const login=async({email:e,password:t})=>s(`${a}/auth/login/email`,{email:e,password:t});export const logout=async()=>s(`${a}/auth/logout`,{});export const getSession=async()=>e(`${a}/auth/session`);export const forgotPassword=async({email:e})=>s(`${a}/forgot-password`,{email:e});export const resetPassword=async({token:e,password:t,logoutAll:n=!1})=>s(`${a}/auth/reset-password`,{token:e,newPassword:t,logoutAll:n});export const changePassword=async({currentPassword:e,newPassword:t,logoutAll:n})=>s(`${a}/auth/change-password`,{currentPassword:e,newPassword:t,logoutAll:n});export const verifyEmail=async({token:e})=>s(`${a}/auth/verify-email`,{token:e});export const currentUserHasPermission=async({permission:s})=>e(`${a}/user/current/permissions`,{permission:s});export const currentUserHasAllPermissions=async({permissions:s})=>e(`${a}/user/current/permissions/all`,{permissions:s});export const currentUserHasSomePermissions=async({permissions:s})=>e(`${a}/user/current/permissions/some`,{permissions:s});export const getAllCurrentUserPermissions=async()=>e(`${a}/user/current/permissions/list`);export const userHasPermission=async({userid:s,permission:t})=>e(`${a}/user/${s}/permissions`,{permission:t});export const userHasAllPermissions=async({userid:s,permissions:t})=>e(`${a}/user/${s}/permissions/all`,{permissions:t});export const userHasSomePermissions=async({userid:s,permissions:t})=>e(`${a}/user/${s}/permissions/some`,{permissions:t});export const getAllUserPermissions=async({userid:s})=>e(`${a}/user/${s}/permissions/list`);export const getUsers=async({limit:s,offset:t}={})=>e(`${a}/user`,{limit:s,offset:t});export const searchUsers=async({q:s,limit:t,offset:n,inGroup:o,notInGroup:i}={})=>e(`${a}/user/search`,{q:s,limit:t,offset:n,inGroup:o,notInGroup:i});export const createUser=async({name:e,email:t,password:n,emailVerified:o})=>s(`${a}/user/create`,{name:e,email:t,password:n,emailVerified:o});export const deleteUsers=async e=>o(`${a}/user`,{ids:e});export const updateUser=async({id:e,name:s,email:t,emailVerified:o,createdAt:i})=>n(`${a}/user`,{id:e,name:s,email:t,emailVerified:o,createdAt:i});export const getUser=async s=>e(`${a}/user/${s}`);export const getUserSessions=async(s,{limit:t,offset:n}={})=>e(`${a}/user/${s}/sessions`,{limit:t,offset:n});export const deleteUserSession=async(e,s)=>o(`${a}/user/${e}/sessions`,{sessionToken:s});export const deleteExpiredUserSessions=async e=>o(`${a}/user/${e}/sessions/expired`);export const getUserGroups=async(s,{limit:t,offset:n}={})=>e(`${a}/user/${s}/groups`,{limit:t,offset:n});export const addUserToGroup=async(e,t)=>s(`${a}/user/${e}/groups`,{groupName:t});export const removeUserFromGroup=async(e,s)=>o(`${a}/user/${e}/groups`,{groupName:s});export const listGroups=async({limit:s,offset:t,owner:n}={})=>e(`${a}/groups`,{limit:s,offset:t,owner:n});export const createGroup=async({name:e,description:t})=>s(`${a}/groups`,{name:e,description:t});export const updateGroup=async({name:e,newName:s,description:t})=>n(`${a}/groups`,{name:e,newName:s,description:t});export const deleteGroups=async e=>o(`${a}/groups`,{names:e});export const getGroup=async s=>e(`${a}/groups/${encodeURIComponent(s)}`);export const getGroupMembers=async(s,{limit:t,offset:n}={})=>e(`${a}/groups/${encodeURIComponent(s)}/members`,{limit:t,offset:n});export const addMemberToGroup=async(e,t)=>s(`${a}/groups/${encodeURIComponent(e)}/members`,{userId:t});export const removeMemberFromGroup=async(e,s)=>o(`${a}/groups/${encodeURIComponent(e)}/members`,{userId:s});export const getGroupPermissions=async s=>e(`${a}/groups/${encodeURIComponent(s)}/permissions`);export const addPermissionToGroup=async(e,t)=>s(`${a}/groups/${encodeURIComponent(e)}/permissions`,{permissionName:t});export const removePermissionFromGroup=async(e,s)=>o(`${a}/groups/${encodeURIComponent(e)}/permissions`,{permissionName:s});export const listPermissions=async({limit:s,offset:t,owner:n}={})=>e(`${a}/permissions`,{limit:s,offset:t,owner:n});export const createPermissions=async e=>s(`${a}/permissions`,{permissions:e});export const updatePermission=async({name:e,description:s})=>n(`${a}/permissions`,{name:e,description:s});export const deletePermissions=async e=>o(`${a}/permissions`,{names:e});export const getSetting=async(s,t)=>e(`${a}/settings/${encodeURIComponent(s)}/${encodeURIComponent(t)}`);export const listSettings=async({owner:s,limit:t,offset:n}={})=>e(`${a}/settings/${encodeURIComponent(s)}`,{limit:t,offset:n});export const setSetting=async(e,t,n)=>s(`${a}/settings/${encodeURIComponent(e)}/${encodeURIComponent(t)}`,n);export const deleteSetting=async(e,s)=>o(`${a}/settings/${encodeURIComponent(e)}/${encodeURIComponent(s)}`);export const listEmails=async({status:s,type:t,search:n,limit:o,offset:i}={})=>e(`${a}/emails`,{status:s,type:t,search:n,limit:o,offset:i});export const getEmail=async s=>e(`${a}/emails/${encodeURIComponent(s)}`);export const retryEmail=async e=>s(`${a}/emails/${encodeURIComponent(e)}/retry`,{});export const sendTestEmail=async e=>s(`${a}/emails/test`,{to:e});export const getUpdateStatus=async()=>e(`${a}/updates`);export const checkForUpdate=async()=>s(`${a}/updates/check`,{});export const prepareUpdate=async({version:e,extensions:t}={})=>s(`${a}/updates/prepare`,{version:e,extensions:t});export const applyUpdate=async({version:e,extensions:t,confirm:n}={})=>s(`${a}/updates/apply`,{version:e,extensions:t,confirm:n});export const rollbackUpdate=async e=>s(`${a}/updates/rollback`,{confirm:e});export const discardUpdate=async()=>s(`${a}/updates/discard`,{});export const listPages=async({owner:s,showHidden:t,offset:n,limit:o}={})=>e(`${a}/pages`,{owner:s,showHidden:t,offset:n,limit:o});export const rescanPages=async()=>s(`${a}/pages/rescan`,{});export const listTemplates=async()=>e(`${a}/templates`);export const listDirectories=async()=>e(`${a}/pages/directories`);export const getPage=async s=>e(`${a}/pages/file`,{file:s});export const createPage=async({directory:e,name:t,template:n})=>s(`${a}/pages`,{directory:e,name:t,template:n});export const updatePage=async({file:e,name:s,title:n,description:o,author:i,template:r,contents:c,extraMetadata:p})=>t(`${a}/pages/file`,{file:e,name:s,title:n,description:o,author:i,template:r,contents:c,extraMetadata:p});export const deletePages=async e=>o(`${a}/pages`,{files:e});export const disablePage=async e=>t(`${a}/pages/disable`,{file:e});export const enablePage=async e=>t(`${a}/pages/enable`,{file:e});export const movePage=async({file:e,newFile:s})=>n(`${a}/pages/file`,{file:e,newFile:s});export const searchPagesByMetadata=async s=>e(`${a}/pages/search`,{query:JSON.stringify(s)});export const setPageLocked=async({file:e,locked:s})=>t(`${a}/pages/lock`,{file:e,locked:s});export const getTemplate=async s=>e(`${a}/templates/file`,{file:s});export const createTemplate=async({directory:e,name:t,copyFrom:n})=>s(`${a}/templates`,{directory:e,name:t,copyFrom:n});export const updateTemplate=async({file:e,name:s,author:n,markup:o})=>t(`${a}/templates/file`,{file:e,name:s,author:n,markup:o});export const deleteTemplates=async e=>o(`${a}/templates`,{files:e});export const listEmailTemplates=async s=>e(`${a}/email-templates`,s?{owner:s}:{});export const getEmailTemplate=async s=>e(`${a}/email-templates/file`,{template:s});export const saveEmailTemplate=async({template:e,source:s})=>t(`${a}/email-templates/file`,{template:e,source:s});export const resetEmailTemplate=async e=>o(`${a}/email-templates/file`,{template:e});export const disableTemplate=async e=>t(`${a}/templates/disable`,{file:e});export const enableTemplate=async e=>t(`${a}/templates/enable`,{file:e});export const moveTemplate=async({file:e,newFile:s})=>n(`${a}/templates/file`,{file:e,newFile:s});export const setTemplateLocked=async({file:e,locked:s})=>t(`${a}/templates/lock`,{file:e,locked:s});export const listFragments=async()=>e(`${a}/fragments`);export const getFragment=async s=>e(`${a}/fragments/file`,{file:s});export const createFragment=async({directory:e,name:t})=>s(`${a}/fragments`,{directory:e,name:t});export const updateFragment=async({file:e,author:s,markup:n})=>t(`${a}/fragments/file`,{file:e,author:s,markup:n});export const deleteFragments=async e=>o(`${a}/fragments`,{files:e});export const moveFragment=async({file:e,newFile:s})=>n(`${a}/fragments/file`,{file:e,newFile:s});export const disableFragment=async e=>t(`${a}/fragments/disable`,{file:e});export const enableFragment=async e=>t(`${a}/fragments/enable`,{file:e});export const setFragmentLocked=async({file:e,locked:s})=>t(`${a}/fragments/lock`,{file:e,locked:s});export const listGlobalContent=async()=>e(`${a}/globals`);export const getGlobalContent=async s=>e(`${a}/globals/entry`,{id:s});export const createGlobalContent=async({name:e,location:t,priority:n})=>s(`${a}/globals`,{name:e,location:t,priority:n});export const updateGlobalContent=async({id:e,name:s,location:n,priority:o,markup:i})=>t(`${a}/globals/entry`,{id:e,name:s,location:n,priority:o,markup:i});export const deleteGlobalContent=async e=>o(`${a}/globals`,{ids:e});export const disableGlobalContent=async e=>t(`${a}/globals/disable`,{id:e});export const enableGlobalContent=async e=>t(`${a}/globals/enable`,{id:e});export const setGlobalContentLocked=async({id:e,locked:s})=>t(`${a}/globals/lock`,{id:e,locked:s});export const listMenus=async()=>e(`${a}/menus`);export const saveMenu=async({id:e,name:t})=>s(`${a}/menus`,{id:e,name:t});export const deleteMenu=async e=>o(`${a}/menus`,{id:e});export const assignMenuLocation=async({location:e,menuId:s})=>t(`${a}/menus/location`,{location:e,menuId:s});export const saveMenuItem=async({id:e,menuId:t,location:n,navItem:o,label:i,href:r,icon:c,newTab:p})=>s(`${a}/menus/items`,{id:e,menuId:t,location:n,navItem:o,label:i,href:r,icon:c,newTab:p});export const deleteMenuItems=async e=>o(`${a}/menus/items`,{ids:e});export const setMenuItemEnabled=async({id:e,enabled:s})=>t(`${a}/menus/items/enabled`,{id:e,enabled:s});export const reorderMenuItems=async({menuId:e,ids:s})=>t(`${a}/menus/items/order`,{menuId:e,ids:s});export const adoptMenuItem=async({id:e,menuId:s,menuName:n})=>t(`${a}/menus/adopt`,{id:e,menuId:s,menuName:n});export const listExtensions=async({limit:s,offset:t}={})=>e(`${a}/extensions`,{limit:s,offset:t});export const listAvailableExtensions=async()=>e(`${a}/extensions/available`);export const listKnownExtensions=async({search:s,sort:t,limit:n,offset:o}={})=>e(`${a}/extensions/known`,{search:s,sort:t,limit:n,offset:o});export const getKnownExtension=async s=>e(`${a}/extensions/known`,{name:s});export const rateExtension=async(e,s)=>t(`${a}/extensions/rating`,{name:e,rating:s});export const getDashboardWidgets=async()=>e(`${a}/dashboard/widgets`);export const getNotifications=async({unreadOnly:s=!1,limit:t,offset:n}={})=>e(`${a}/notifications`,{unreadOnly:s,limit:t,offset:n});export const getUnreadNotificationCount=async()=>e(`${a}/notifications/count`);export const markNotificationRead=async e=>s(`${a}/notifications/${encodeURIComponent(e)}/read`,{});export const markNotificationHandled=async e=>s(`${a}/notifications/${encodeURIComponent(e)}/handled`,{});export const markAllNotificationsRead=async()=>s(`${a}/notifications/read-all`,{});export const dismissNotification=async e=>o(`${a}/notifications/${encodeURIComponent(e)}`);export const listRealtimeConnections=async()=>e(`${a}/realtime/connections`);export const installExtension=async(e,{withDependencies:t=!1}={})=>s(`${a}/extensions`,{name:e,withDependencies:t});export const uninstallExtension=async(e,s=!1,{cascade:t=!1}={})=>o(`${a}/extensions`,{name:e,purgeData:s,cascade:t});export const enableExtension=async(e,{cascade:t=!1}={})=>s(`${a}/extensions/${encodeURIComponent(e)}/enable`,{cascade:t});export const getExtensionRequirements=async s=>e(`${a}/extensions/${encodeURIComponent(s)}/requirements`);export const disableExtension=async(e,{cascade:t=!1}={})=>s(`${a}/extensions/${encodeURIComponent(e)}/disable`,{cascade:t});export const getExtensionDependents=async(s,{includeDisabled:t=!1}={})=>e(`${a}/extensions/${encodeURIComponent(s)}/dependents`,{includeDisabled:t?"true":void 0});export const listAdminGlobalContent=async()=>e(`${a}/admin-globals`);export const getAdminGlobalContent=async s=>e(`${a}/admin-globals/${encodeURIComponent(s)}`);export const createAdminGlobalContent=async({name:e,location:t,priority:n,owner:o,markup:i})=>s(`${a}/admin-globals`,{name:e,location:t,priority:n,owner:o,markup:i});export const updateAdminGlobalContent=async(e,{name:s,location:n,priority:o,markup:i})=>t(`${a}/admin-globals/${encodeURIComponent(e)}`,{name:s,location:n,priority:o,markup:i});export const deleteAdminGlobalContent=async e=>o(`${a}/admin-globals`,{ids:e});export const enableAdminGlobalContent=async e=>t(`${a}/admin-globals/${encodeURIComponent(e)}/enable`,{});export const disableAdminGlobalContent=async e=>t(`${a}/admin-globals/${encodeURIComponent(e)}/disable`,{});
|
|
2
2
|
//# sourceMappingURL=C:\Users\dusti\dev\kempo\dist\kempo\sdk.js.map
|