minimal sql console API and UI
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -13,9 +13,9 @@
|
||||
|
||||
<!-- prism -->
|
||||
<script src="./libs/prism/prism.js" data-manual></script>
|
||||
<script type="module" crossorigin src="./assets/index-1u9sxa_M.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index-BRepvmcR.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="./assets/pocketbase.es-B_4DUNUU.js">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-ePwHvxbV.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-C5MjNoft.css">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
Vendored
+1
@@ -1,3 +1,4 @@
|
||||
(function(e){let t,n=new Set,r=new Map,i=[],a,o=Symbol(),s=Symbol(),c=Symbol(),l=Symbol(),u=Symbol(),d=Symbol(),f=Symbol(),p=Symbol(),m=Symbol(),h=Symbol();function g(e,n){let d={[o]:`_`+Math.random(),[p]:e,[m]:n};return r.set(d[o],d),d.run=()=>{let e;t&&(e=t,d[s]=t[o],t[c]=t[c]||[],t[c].push(d[o])),d[l]?.forEach(e=>{e.delete(d[o])}),t=d;let n=d[p](d[h],d);d[m]&&(t=null,d[m](n,d[h],d)),d[h]=n,t=e},d.unwatch=function(){d[u]=1,i.push(d[o]),a&&clearTimeout(a),a=setTimeout(()=>{for(let e of i)_(e);i=[],a=null},50)},d.run(),d}function _(e){let t=r.get(e);if(t?.[d]?.(),t?.[c]){for(let e of t[c])_(e);t[s]=null,t[c]=null}if(t?.[l]){for(let n of t[l])n.delete(e);t[l]=null}r.delete(e)}function v(e){return y(e,new Map)}function y(e,n){let r=typeof e==`object`&&!Array.isArray(e)?Object.getOwnPropertyDescriptors(e):{};return new Proxy(e,{get(e,i,a){if(typeof i==`symbol`)return e[i];if(i==`__raw`)return e;if(r[i]?.get&&!e[s]){let n=i;if(i=`@@`+i,!t){let t=r[n].get.call(e);return r[n]._watcher&&(a[i]=t),t}let s=t[o];if(r[n]._refs=r[n]._refs||new Set,!r[n]._refs.has(s)){r[n]._refs.add(s);let e=t[d];t[d]=()=>{e?.(),r[n]._refs.delete(s),!r[n]._refs.size&&r[n]._watcher&&_(r[n]._watcher[o])}}if(!r[n]._watcher){let o=t;t=null;let s=g(r[n].get.bind(e),t=>{e.hasOwnProperty(i)?a[i]=t:Object.defineProperty(e,i,{writable:!0,enumerable:!1,value:t})});r[n]._watcher=s,s[d]=()=>{r[n]._watcher=null},t=o}}let c=e?.[i];if(typeof c==`function`)return c;if(typeof c==`object`&&c&&!c[s]&&(c.constructor?.name==`Object`||c.constructor?.name==`Array`||c.constructor?.name==null)&&(c[s]=[a,i],c=y(c,n),e[i]=c),t){let r=b(e,i),a=t[o],s=[r];t[l]=t[l]||new Set;for(let e of s){let r=n.get(e);r||(r=new Set,n.set(e,r)),r.add(a),t[l].add(r)}}return c},set(e,t,r){if(typeof t==`symbol`)return e[t]=r,!0;let i=e[t];i?.[s]&&(i[f]=!0),r?.[s]&&Array.isArray(e)&&!isNaN(t)&&(r[s][1]=t);let a=!1;return i===void 0&&!e.hasOwnProperty(t)&&(a=!0),e[t]=r,a&&x(e,`toJSON`,n),(r!==i||t===`length`)&&x(e,t,n),!0},deleteProperty(e,t){if(typeof t!=`symbol`){x(e,t,n);let r=b(e,t);for(let e of n)(e[0]==r||e[0].startsWith(r+`/`))&&n.delete(e[0])}return delete e[t]}})}function b(e,t){let n=t,r=e?.[s];for(;r;)n=r[1]+`/`+n,r=r[0][s];return n}function x(e,t,i){let a=b(e,t),o=i.get(a);if(o)for(let e of o)n.delete(e),n.add(e),n.size==1&&queueMicrotask(()=>{let e={},t;for(let i of n)if(t=r.get(i),!(!t||t[u])&&!(t[s]&&n.has(t[s]))){if(e[i]=(e[i]||0)+1,e[i]>250){console.warn(`Possible infinite loop for watcher `+i+`:`,`
|
||||
watch(`+t[p]?.toString()+(t[m]?`, `+t[m].toString():``)+`)`);continue}t.run()}n.clear()})}let S=new Proxy({},{get(e,t){return function(){return w(),O.call(void 0,t,...arguments)}}}),C=!1;function w(){if(C)return;C=!0;function e(t,n){for(let r of n)r[t]&&r[t](r),r.childNodes&&e(t,r.childNodes)}new MutationObserver(t=>{for(let n of t)e(`onmount`,n.addedNodes),e(`onunmount`,n.removedNodes)}).observe(document,{childList:!0,subtree:!0})}let T=Symbol(),E=Symbol(),D=Symbol();function O(e,t={},...n){let r=document.createElement(e);if(t)for(let e in t){let n=t[e],i=!1;e.length>5&&e.startsWith(`html-`)&&(i=!0,e=e.substring(5)),n===void 0?r.removeAttribute(e):typeof n!=`function`||e.length>2&&e.startsWith(`on`)?i?r.setAttribute(e,n):r[e]=n:(e==`rid`&&console.warn(`rid is provided as reactive function and will not have effect for `+r+`.
|
||||
Consider using a plain number, string or object/array reference instead.`),r[T]=r[T]||[],r[T].push(()=>{if(!r)return;let t=n(r,e);t===void 0?(r[e]!==void 0&&(r[e]=void 0),r.removeAttribute(e)):i?r.setAttribute(e,t):r[e]=t}))}let i=r.onmount;r.onmount=()=>{if(!r[D]){if(r[D]=!0,r[T]){r[E]=r[E]||[];for(let e of r[T])r[E].push(g(e))}i?.(r)}};let a=r.onunmount;return r.onunmount=()=>{if(r[D]){if(r[D]=!1,r[E])for(let e of r[E])e.unwatch();r[E]=null,a?.(r)}},k(r,n),r}function k(e,t){t=N(t);for(let n of t)if(typeof n==`function`)A(e,n);else{let t=P(n);Array.isArray(t)?k(e,t):t&&e.appendChild(t)}}function A(e,t){let n=document.createComment(``);e.appendChild(n);let r=[],i=new Map,a=e.moveBefore||e.insertBefore;e[T]=e[T]||[],e[T].push(()=>{if(!e)return;let o=N(t(e)),s=o.length,c=new Map;if(!r?.length){let t=document.createDocumentFragment();for(let e=0;e<s;e++){o[e]=P(o[e],!0),t.appendChild(o[e]);let n=o[e].rid;n!==void 0&&(c.has(n)?console.warn(`Duplicated rid:`,n,o[e]):c.set(n,e))}e.insertBefore(t,n),t=null,r=o,i=c;return}let l=[],u=[],d=new Set,f=[];for(let e=0;e<s;e++){o[e]=P(o[e],!0);let t=o[e].rid;if(t!==void 0){c.has(t)?console.warn(`Duplicated rid:`,t,o[e]):c.set(t,e);let n=i.get(t);if(n>=0){d.add(r[n]),o[e]=r[n],f.push(n);continue}}u.push({child:o[e],prev:o[e-1]})}let p=j(f);f.length!=p.length&&f.forEach((e,t)=>{p.has(e)||l.push({child:r[e],targetPos:t})});let m,h,g;for(let t of l)m=r.findIndex(e=>e===t.child),h=t.targetPos,m<t.targetPos&&(h=t.targetPos+1),g=r[h]||n,M(r,m,t.targetPos),a.call(e,t.child,g);for(let e of u)e.prev?e.prev.after(e.child):(r[0]||n).before(e.child);for(let e=0;e<r.length;e++)d.has(r[e])||r[e].remove?.();r=o,i=c,o=null,c=null,d=null,l=null,u=null})}function j(e){let t=[],n=[];for(let r=0;r<e.length;r++){let i=e[r],a=0,o=0,s=t.length;for(;a<s;)o=Math.floor((a+s)/2),e[t[o]]>=i?s=o:a=o+1;a>0&&(n[r]=t[a-1]),t[a]=r}let r=new Set,i=t[t.length-1];for(;i!==void 0;)r.add(e[i]),i=n[i];return r}function M(e,t,n){if(t==n)return e;let r=t>n?-1:1,i=e[t];for(let i=t;i!=n;i+=r)e[i]=e[i+r];e[n]=i}function N(e){return e==null?[]:Array.isArray(e)?e:[e]}function P(e,t=!1){return t&&e==null&&(e=``),typeof e==`string`||typeof e==`number`||typeof e==`boolean`?document.createTextNode(e):e?.__raw===void 0?e:e.__raw}function F(e,t={fallbackPath:`#/`,transition:!0}){let n=L(e),r,i=()=>{let e=window.location.hash,i=I(n,e);if(!i){if(t.fallbackPath!=e){window.location.hash=t.fallbackPath;return}console.warn(`missing route:`,e);return}let a=async()=>{try{await r?.(),r=await i.handler(i)}catch(e){console.warn(`route navigation failed:`,e)}};t.transition&&document.startViewTransition?document.startViewTransition(a):a()};return window.addEventListener(`hashchange`,i),i(),()=>{window.removeEventListener(`hashchange`,i)}}function I(e,t){for(let n of e){let e=t.match(n.regex);if(!e)continue;let r={},i=t.split(`?`)?.[1];if(i){let e=new URLSearchParams(i);for(let[t,n]of e.entries())Array.isArray(r[t])||(r[t]=r[t]?[r[t]]:[]),r[t].push(n)}return Object.assign({path:t,query:r,params:e.groups||{}},n)}}function L(e){let t=[];for(let n in e){let r=n.split(`/`);for(let e in r)r[e].length>2&&r[e].startsWith(`{`)&&r[e].endsWith(`}`)?r[e]=`(?<`+r[e].substring(1,r[e].length-1)+`>[^\\/#?]+)`:r[e]=RegExp.escape(r[e]);t.push({regex:RegExp(`^`+r.join(`\\/`)+`(?:[?#].*)?$`),pattern:n,handler:e[n]})}return t}e.router=F,e.store=v,e.t=S,e.watch=g})(this.window=this.window||{});
|
||||
//# sourceMappingURL=shablon.iife.js.map
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
(function(e){let t,n=new Set,r=new Map,i=[],a,o=Symbol(),s=Symbol(),c=Symbol(),l=Symbol(),u=Symbol(),d=Symbol(),f=Symbol(),p=Symbol(),m=Symbol(),h=Symbol();function g(e,n){let d={[o]:`_`+Math.random(),[p]:e,[m]:n};return r.set(d[o],d),d.run=()=>{let e;t&&(e=t,d[s]=t[o],t[c]=t[c]||[],t[c].push(d[o])),d[l]?.forEach(e=>{e.delete(d[o])}),t=d;let n=d[p](d[h],d);d[m]&&(t=null,d[m](n,d[h],d)),d[h]=n,t=e},d.unwatch=function(){d[u]=1,i.push(d[o]),a&&clearTimeout(a),a=setTimeout(()=>{for(let e of i)_(e);i=[],a=null},50)},d.run(),d}function _(e){let t=r.get(e);if(t?.[d]?.(),t?.[c]){for(let e of t[c])_(e);t[s]=null,t[c]=null}if(t?.[l]){for(let n of t[l])n.delete(e);t[l]=null}r.delete(e)}function v(e){return y(e,new Map)}function y(e,n){let r=typeof e==`object`&&!Array.isArray(e)?Object.getOwnPropertyDescriptors(e):{};return new Proxy(e,{get(e,i,a){if(typeof i==`symbol`)return e[i];if(i==`__raw`)return e;if(r[i]?.get&&!e[s]){let n=i;if(i=`@@`+i,!t){let t=r[n].get.call(e);return r[n]._watcher&&(a[i]=t),t}let s=t[o];if(r[n]._refs=r[n]._refs||new Set,!r[n]._refs.has(s)){r[n]._refs.add(s);let e=t[d];t[d]=()=>{e?.(),r[n]._refs.delete(s),!r[n]._refs.size&&r[n]._watcher&&_(r[n]._watcher[o])}}if(!r[n]._watcher){let o=t;t=null;let s=g(r[n].get.bind(e),t=>{e.hasOwnProperty(i)?a[i]=t:Object.defineProperty(e,i,{writable:!0,enumerable:!1,value:t})});r[n]._watcher=s,s[d]=()=>{r[n]._watcher=null},t=o}}let c=e?.[i];if(typeof c==`function`)return c;if(typeof c==`object`&&c&&!c[s]&&(c.constructor?.name==`Object`||c.constructor?.name==`Array`||c.constructor?.name==null)&&(c[s]=[a,i],c=y(c,n),e[i]=c),t){let r=b(e,i),a=t[o],s=[r];t[l]=t[l]||new Set;for(let e of s){let r=n.get(e);r||(r=new Set,n.set(e,r)),r.add(a),t[l].add(r)}}return c},set(e,t,r){if(typeof t==`symbol`)return e[t]=r,!0;let i=e[t];i?.[s]&&(i[f]=!0),r?.[s]&&Array.isArray(e)&&!isNaN(t)&&(r[s][1]=t);let a=!1;return i===void 0&&!e.hasOwnProperty(t)&&(a=!0),e[t]=r,a&&x(e,`toJSON`,n),(r!==i||t===`length`)&&x(e,t,n),!0},deleteProperty(e,t){if(typeof t!=`symbol`){x(e,t,n);let r=b(e,t);for(let e of n)(e[0]==r||e[0].startsWith(r+`/`))&&n.delete(e[0])}return delete e[t]}})}function b(e,t){let n=t,r=e?.[s];for(;r;)n=r[1]+`/`+n,r=r[0][s];return n}function x(e,t,i){let a=b(e,t),o=i.get(a);if(o)for(let e of o)n.delete(e),n.add(e),n.size==1&&queueMicrotask(()=>{let e={},t;for(let i of n)if(t=r.get(i),!(!t||t[u])&&!(t[s]&&n.has(t[s]))){if(e[i]=(e[i]||0)+1,e[i]>250){console.warn(`Possible infinite loop for watcher `+i+`:`,`
|
||||
watch(`+t[p]?.toString()+(t[m]?`, `+t[m].toString():``)+`)`);continue}t.run()}n.clear()})}let S=new Proxy({},{get(e,t){return function(){return w(),O.call(void 0,t,...arguments)}}}),C=!1;function w(){if(C)return;C=!0;function e(t,n){for(let r of n)r[t]&&r[t](r),r.childNodes&&e(t,r.childNodes)}new MutationObserver(t=>{for(let n of t)e(`onmount`,n.addedNodes),e(`onunmount`,n.removedNodes)}).observe(document,{childList:!0,subtree:!0})}let T=Symbol(),E=Symbol(),D=Symbol();function O(e,t={},...n){let r=document.createElement(e);if(t)for(let e in t){let n=t[e],i=!1;e.length>5&&e.startsWith(`html-`)&&(i=!0,e=e.substring(5)),n===void 0?r.removeAttribute(e):typeof n!=`function`||e.length>2&&e.startsWith(`on`)?i?r.setAttribute(e,n):r[e]=n:(e==`rid`&&console.warn(`rid is provided as reactive function and will not have effect for `+r+`.
|
||||
Consider using a plain number, string or object/array reference instead.`),r[T]=r[T]||[],r[T].push(()=>{if(!r)return;let t=n(r,e);t===void 0?(r[e]!==void 0&&(r[e]=void 0),r.removeAttribute(e)):i?r.setAttribute(e,t):r[e]=t}))}let i=r.onmount;r.onmount=()=>{if(!r[D]){if(r[D]=!0,r[T]){r[E]=r[E]||[];for(let e of r[T])r[E].push(g(e))}i?.(r)}};let a=r.onunmount;return r.onunmount=()=>{if(r[D]){if(r[D]=!1,r[E])for(let e of r[E])e.unwatch();r[E]=null,a?.(r)}},k(r,n),r}function k(e,t){t=N(t);for(let n of t)if(typeof n==`function`)A(e,n);else{let t=P(n);Array.isArray(t)?k(e,t):t&&e.appendChild(t)}}function A(e,t){let n=document.createComment(``);e.appendChild(n);let r=[],i=new Map,a=e.moveBefore||e.insertBefore;e[T]=e[T]||[],e[T].push(()=>{if(!e)return;let o=N(t(e)),s=o.length,c=new Map;if(!r?.length){let t=document.createDocumentFragment();for(let e=0;e<s;e++){o[e]=P(o[e],!0),t.appendChild(o[e]);let n=o[e].rid;n!==void 0&&(c.has(n)?console.warn(`Duplicated rid:`,n,o[e]):c.set(n,e))}e.insertBefore(t,n),t=null,r=o,i=c;return}let l=[],u=[],d=new Set,f=[];for(let e=0;e<s;e++){o[e]=P(o[e],!0);let t=o[e].rid;if(t!==void 0){c.has(t)?console.warn(`Duplicated rid:`,t,o[e]):c.set(t,e);let n=i.get(t);if(n>=0){d.add(r[n]),o[e]=r[n],f.push(n);continue}}u.push({child:o[e],prev:o[e-1]})}let p=j(f);f.length!=p.length&&f.forEach((e,t)=>{p.has(e)||l.push({child:r[e],targetPos:t})});let m,h,g;for(let t of l)m=r.findIndex(e=>e===t.child),h=t.targetPos,m<t.targetPos&&(h=t.targetPos+1),g=r[h]||n,M(r,m,t.targetPos),a.call(e,t.child,g);for(let e of u)e.prev?e.prev.after(e.child):(r[0]||n).before(e.child);for(let e=0;e<r.length;e++)d.has(r[e])||r[e].remove?.();r=o,i=c,o=null,c=null,d=null,l=null,u=null})}function j(e){let t=[],n=[];for(let r=0;r<e.length;r++){let i=e[r],a=0,o=0,s=t.length;for(;a<s;)o=Math.floor((a+s)/2),e[t[o]]>=i?s=o:a=o+1;a>0&&(n[r]=t[a-1]),t[a]=r}let r=new Set,i=t[t.length-1];for(;i!==void 0;)r.add(e[i]),i=n[i];return r}function M(e,t,n){if(t==n)return e;let r=t>n?-1:1,i=e[t];for(let i=t;i!=n;i+=r)e[i]=e[i+r];e[n]=i}function N(e){return e==null?[]:Array.isArray(e)?e:[e]}function P(e,t=!1){return t&&e==null&&(e=``),typeof e==`string`||typeof e==`number`||typeof e==`boolean`?document.createTextNode(e):e?.__raw===void 0?e:e.__raw}function F(e,t={fallbackPath:`#/`,transition:!0}){let n=L(e),r,i=()=>{let e=window.location.hash,i=I(n,e);if(!i){if(t.fallbackPath!=e){window.location.hash=t.fallbackPath;return}console.warn(`missing route:`,e);return}let a=async()=>{try{await r?.(),r=await i.handler(i)}catch(e){console.warn(`route navigation failed:`,e)}};t.transition&&document.startViewTransition?document.startViewTransition(a):a()};return window.addEventListener(`hashchange`,i),i(),()=>{window.removeEventListener(`hashchange`,i)}}function I(e,t){for(let n of e){let e=t.match(n.regex);if(!e)continue;let r={},i=t.split(`?`)?.[1];if(i){let e=new URLSearchParams(i);for(let[t,n]of e.entries())Array.isArray(r[t])||(r[t]=r[t]?[r[t]]:[]),r[t].push(n)}return Object.assign({path:t,query:r,params:e.groups||{}},n)}}function L(e){let t=[];for(let n in e){let r=n.split(`/`);for(let e in r)r[e].length>2&&r[e].startsWith(`{`)&&r[e].endsWith(`}`)?r[e]=`(?<`+r[e].substring(1,r[e].length-1)+`>[^\\/#?]+)`:r[e]=RegExp.escape(r[e]);t.push({regex:RegExp(`^`+r.join(`\\/`)+`(?:[?#].*)?$`),pattern:n,handler:e[n]})}return t}e.router=F,e.store=v,e.t=S,e.watch=g})(this.window=this.window||{});
|
||||
//# sourceMappingURL=shablon.iife.js.map
|
||||
|
||||
@@ -197,7 +197,7 @@ export function pageCollections(route) {
|
||||
t.div(
|
||||
{ className: "page-content full-height" },
|
||||
t.header(
|
||||
{ className: "page-header compact flex-nowrap" },
|
||||
{ className: "page-header flex-nowrap" },
|
||||
t.nav(
|
||||
{ className: "breadcrumbs" },
|
||||
t.div(null, "Collections"),
|
||||
|
||||
@@ -35,3 +35,4 @@
|
||||
@import "./collectionsOverview.css";
|
||||
@import "./erd.css";
|
||||
@import "./hideControls.css";
|
||||
@import "./sqlConsole.css";
|
||||
|
||||
@@ -919,6 +919,7 @@ hr {
|
||||
.alert {
|
||||
display: block;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
align-content: center;
|
||||
min-height: var(--lgBtnHeight);
|
||||
padding: max(5px, calc(var(--inputPadding) - 5px)) max(10px, var(--inputPadding));
|
||||
|
||||
@@ -1131,6 +1131,7 @@ button {
|
||||
&:empty::before {
|
||||
content: attr(data-placeholder);
|
||||
color: var(--surfaceTxtDisabledColor);
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-10
@@ -174,16 +174,14 @@
|
||||
column-gap: var(--smSpacing);
|
||||
row-gap: calc(var(--smSpacing) - 5px);
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
margin: -5px 0 calc(var(--spacing) - 5px);
|
||||
min-height: var(--btnHeight);
|
||||
margin: -10px 0 calc(var(--spacing) - 10px);
|
||||
.searchbar {
|
||||
width: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
&.compact {
|
||||
margin-top: -10px;
|
||||
margin-bottom: calc(var(--spacing) - 10px);
|
||||
}
|
||||
.page-header-secondary-btns,
|
||||
.page-header-primary-btns {
|
||||
display: inline-flex;
|
||||
@@ -206,10 +204,6 @@
|
||||
}
|
||||
@media (max-width: 950px) {
|
||||
gap: 5px;
|
||||
flex-grow: 1;
|
||||
.flex-nowrap & {
|
||||
flex-grow: 0;
|
||||
}
|
||||
.btn {
|
||||
flex-grow: 1;
|
||||
flex-basis: 0;
|
||||
@@ -219,7 +213,6 @@
|
||||
}
|
||||
@media (max-width: 550px) {
|
||||
gap: 5px;
|
||||
flex-grow: 0;
|
||||
.api-preview-btn {
|
||||
display: none;
|
||||
}
|
||||
@@ -228,6 +221,7 @@
|
||||
width: auto;
|
||||
aspect-ratio: 1 / 1;
|
||||
border-radius: 50%;
|
||||
min-width: 0;
|
||||
i + .txt {
|
||||
display: none;
|
||||
}
|
||||
@@ -241,6 +235,7 @@
|
||||
align-items: center;
|
||||
width: auto;
|
||||
z-index: 10;
|
||||
gap: var(--smSpacing);
|
||||
color: var(--surfaceTxtHintColor);
|
||||
font-size: var(--smFontSize);
|
||||
line-height: var(--smLineHeight);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
.field.sql-console-field {
|
||||
.input {
|
||||
resize: vertical;
|
||||
height: 250px;
|
||||
min-height: 150px;
|
||||
max-height: 400px;
|
||||
|
||||
@media (max-height: 650px) {
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sql-console-history-dropdown {
|
||||
width: 450px;
|
||||
&.no-items {
|
||||
width: 300px;
|
||||
}
|
||||
.dropdown-item {
|
||||
gap: 5px;
|
||||
padding: 5px;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.2;
|
||||
font-family: var(--monospaceFontFamily);
|
||||
.query {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -317,6 +317,22 @@ table {
|
||||
min-width: 900px;
|
||||
border-radius: 0;
|
||||
}
|
||||
table > :first-child > tr:first-child {
|
||||
> :first-child {
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
> :last-child {
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
table > :last-child > tr:last-child {
|
||||
> :first-child {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
> :last-child {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
tr {
|
||||
animation: fadeIn var(--animationSpeed);
|
||||
}
|
||||
@@ -342,6 +358,7 @@ table {
|
||||
td,
|
||||
th {
|
||||
height: 59px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
line-height: var(--smLineHeight);
|
||||
font-family: var(--baseFontFamily);
|
||||
scrollbar-width: thin;
|
||||
margin: 3px;
|
||||
padding: 3px 5px;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
|
||||
@@ -165,6 +165,8 @@ export function logsList(logsSettings) {
|
||||
return; // nothing to download
|
||||
}
|
||||
|
||||
console.log(data.bulkSelected);
|
||||
|
||||
if (selected.length == 1) {
|
||||
return app.utils.downloadJSON(
|
||||
selected[0],
|
||||
|
||||
@@ -5,6 +5,7 @@ import { pageApplicationSettings } from "@/settings/application/pageApplicationS
|
||||
import { pageBackupsSettings } from "@/settings/backups/pageBackupsSettings";
|
||||
import { pageCronsSettings } from "@/settings/crons/pageCronsSettings";
|
||||
import { pageMailSettings } from "@/settings/mail/pageMailSettings";
|
||||
import { pageSQLConsole } from "@/settings/sql/pageSQLConsole";
|
||||
import { pageStorageSettings } from "@/settings/storage/pageStorageSettings";
|
||||
import { pageExportCollections } from "@/settings/sync/pageExportCollections";
|
||||
import { pageImportCollections } from "@/settings/sync/pageImportCollections";
|
||||
@@ -170,3 +171,4 @@ app.routes.superuserOnly("#/settings/backups", pageBackupsSettings);
|
||||
app.routes.superuserOnly("#/settings/crons", pageCronsSettings);
|
||||
app.routes.superuserOnly("#/settings/export-collections", pageExportCollections);
|
||||
app.routes.superuserOnly("#/settings/import-collections", pageImportCollections);
|
||||
app.routes.superuserOnly("#/settings/sql", pageSQLConsole);
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
import { settingsSidebar } from "../settingsSidebar";
|
||||
|
||||
const SQL_HISTORY_STORAGE_KEY = "pbSQLConsoleHistory";
|
||||
|
||||
export function pageSQLConsole(route) {
|
||||
app.store.title = "SQL console";
|
||||
|
||||
const uniqueId = "sql_console_" + app.utils.randomString();
|
||||
const editorId = uniqueId + "editor";
|
||||
const requestKey = uniqueId + "executeSQL";
|
||||
const defaultMaxRows = 250;
|
||||
|
||||
const pageData = store({
|
||||
askedForConfirmationAtLeastOnce: false,
|
||||
isExecuting: false,
|
||||
maxRows: defaultMaxRows,
|
||||
query: "",
|
||||
result: {},
|
||||
sort: {},
|
||||
errorMsg: "",
|
||||
executedHistory: app.utils.getLocalHistory(SQL_HISTORY_STORAGE_KEY, []),
|
||||
|
||||
get sortedResultRows() {
|
||||
// no client-side sort
|
||||
if (typeof pageData.sort?.index == "undefined") {
|
||||
const rows = pageData.result?.rows || [];
|
||||
return pageData.maxRows >= rows.length ? rows : rows.slice(0, pageData.maxRows);
|
||||
}
|
||||
|
||||
const isAsc = !!pageData.sort?.asc;
|
||||
|
||||
const sorted = pageData.result?.rows?.toSorted((rowA, rowB) => {
|
||||
const valA = rowA[pageData.sort.index];
|
||||
const valB = rowB[pageData.sort.index];
|
||||
|
||||
if (isAsc) {
|
||||
if (valA == valB) {
|
||||
return 0;
|
||||
}
|
||||
if (valA == null) {
|
||||
return -1;
|
||||
}
|
||||
if (valB == null) {
|
||||
return 1;
|
||||
}
|
||||
return valA.localeCompare(valB);
|
||||
}
|
||||
|
||||
if (valA == valB) {
|
||||
return 0;
|
||||
}
|
||||
if (valA == null) {
|
||||
return 1;
|
||||
}
|
||||
if (valB == null) {
|
||||
return -1;
|
||||
}
|
||||
return valB.localeCompare(valA);
|
||||
}) || [];
|
||||
|
||||
return pageData.maxRows >= sorted.length ? sorted : sorted.slice(0, pageData.maxRows);
|
||||
},
|
||||
get totalRemainingRows() {
|
||||
return (pageData.result?.rows?.length << 0) - pageData.sortedResultRows.length;
|
||||
},
|
||||
});
|
||||
|
||||
function needConfirmation(query) {
|
||||
if (!query?.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the hideControls option is enabled ask for confirmation
|
||||
// at least once no matter of the query
|
||||
if (
|
||||
!pageData.askedForConfirmationAtLeastOnce
|
||||
&& !!app.store.settings?.meta?.hideControls
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
query = query?.replace(/[\s\;]/gm, " ").toUpperCase() + " ";
|
||||
|
||||
return !![
|
||||
"INSERT ",
|
||||
"CREATE ",
|
||||
"UPDATE ",
|
||||
"DELETE ",
|
||||
"DROP ",
|
||||
"DETACH ",
|
||||
"PRAGMA ",
|
||||
].find((p) => query.includes(p));
|
||||
}
|
||||
|
||||
async function executeSQLWithConfirm() {
|
||||
if (!needConfirmation(pageData.query)) {
|
||||
return executeSQL();
|
||||
}
|
||||
|
||||
pageData.askedForConfirmationAtLeastOnce = true;
|
||||
|
||||
return app.modals.confirm(
|
||||
t.div(
|
||||
{ className: "txt-center" },
|
||||
t.h6(
|
||||
null,
|
||||
"Be careful and continue only if you really know what you are doing because, depending on the query, the operation could break your application and may not be reversible.",
|
||||
),
|
||||
),
|
||||
() => executeSQL(),
|
||||
null,
|
||||
{ yesButton: "Execute", noButton: "Cancel" },
|
||||
);
|
||||
}
|
||||
|
||||
async function executeSQL() {
|
||||
pageData.isExecuting = true;
|
||||
pageData.maxRows = defaultMaxRows;
|
||||
pageData.result = {};
|
||||
pageData.sort = {};
|
||||
pageData.errorMsg = "";
|
||||
|
||||
const query = pageData.query.trim();
|
||||
if (!query) {
|
||||
app.pb.cancelRequest(requestKey);
|
||||
pageData.isExecuting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// @todo add method to JS SDK
|
||||
pageData.result = await app.pb.send("/api/sql", {
|
||||
method: "POST",
|
||||
body: { query },
|
||||
requestKey: requestKey,
|
||||
});
|
||||
|
||||
addToHistory(query);
|
||||
|
||||
pageData.isExecuting = false;
|
||||
} catch (err) {
|
||||
if (!err?.isAbort) {
|
||||
pageData.isExecuting = false;
|
||||
pageData.errorMsg = err?.response?.message || err?.message || "Failed to execute query.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromHistory(query) {
|
||||
function looseNormalize(str) {
|
||||
return str.replace(/[\s\;]/gm, "").toUpperCase();
|
||||
}
|
||||
|
||||
const normalized = looseNormalize(query);
|
||||
|
||||
for (let i = pageData.executedHistory.length - 1; i >= 0; i--) {
|
||||
if (
|
||||
pageData.executedHistory[i] == query
|
||||
|| looseNormalize(pageData.executedHistory[i]) == normalized
|
||||
) {
|
||||
pageData.executedHistory.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addToHistory(query) {
|
||||
removeFromHistory(query);
|
||||
|
||||
pageData.executedHistory.unshift(pageData.query);
|
||||
if (pageData.executedHistory.length > 10) {
|
||||
pageData.executedHistory.splice(10);
|
||||
}
|
||||
}
|
||||
|
||||
function downloadCSV() {
|
||||
if (!pageData.sortedResultRows.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = [pageData.result.columns.map((c) => c.name)].concat(pageData.sortedResultRows);
|
||||
|
||||
const name = "export_" + app.utils.toLocalDatetime(new Date()).replace(/[\-\:\. ]/g, "_") + ".csv";
|
||||
|
||||
app.utils.downloadCSV(data, name);
|
||||
}
|
||||
|
||||
function toggleSort(index) {
|
||||
if (pageData.sort?.index == index) {
|
||||
pageData.sort = {
|
||||
index: index,
|
||||
asc: !pageData.sort.asc,
|
||||
};
|
||||
} else {
|
||||
pageData.sort = {
|
||||
index: index,
|
||||
asc: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const watchers = [
|
||||
watch(() => JSON.stringify(pageData.executedHistory), (newVal, oldVal) => {
|
||||
if (typeof oldVal == "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
window.localStorage.setItem(SQL_HISTORY_STORAGE_KEY, newVal);
|
||||
}),
|
||||
];
|
||||
|
||||
return t.div(
|
||||
{
|
||||
pbEvent: "pageSQLConsole",
|
||||
className: "page",
|
||||
onunmount: () => {
|
||||
app.pb.cancelRequest(requestKey);
|
||||
watchers.forEach((w) => w?.unwatch());
|
||||
},
|
||||
},
|
||||
settingsSidebar(),
|
||||
t.div(
|
||||
{ className: "page-content full-height" },
|
||||
t.header(
|
||||
{ className: "page-header" },
|
||||
t.nav(
|
||||
{ className: "breadcrumbs" },
|
||||
t.div({ className: "breadcrumb-item" }, "Settings"),
|
||||
t.div({ className: "breadcrumb-item" }, () => app.store.title),
|
||||
),
|
||||
t.div(
|
||||
{ className: "page-header-secondary-btns" },
|
||||
t.button(
|
||||
{
|
||||
type: "button",
|
||||
className: "btn circle transparent secondary",
|
||||
ariaDescription: app.attrs.tooltip("Recently executed queries", "right"),
|
||||
"html-popovertarget": "sql-console-history-dropdown",
|
||||
},
|
||||
t.i({ className: "ri-history-line", ariaHidden: true }),
|
||||
),
|
||||
t.div(
|
||||
{
|
||||
id: "sql-console-history-dropdown",
|
||||
className: () =>
|
||||
`dropdown left sql-console-history-dropdown ${
|
||||
!pageData.executedHistory.length ? "no-items" : ""
|
||||
}`,
|
||||
popover: "auto",
|
||||
},
|
||||
() => {
|
||||
if (!pageData.executedHistory.length) {
|
||||
return t.span({ className: "txt txt-hint p-5" }, "No recently executed queries.");
|
||||
}
|
||||
|
||||
return pageData.executedHistory.map((item) => {
|
||||
return t.button(
|
||||
{
|
||||
role: "button",
|
||||
className: "dropdown-item",
|
||||
onclick: (e) => {
|
||||
e.target.closest(".dropdown").hidePopover();
|
||||
pageData.query = item;
|
||||
|
||||
document.getElementById(editorId)?.click();
|
||||
},
|
||||
},
|
||||
t.span(
|
||||
{ className: "query" },
|
||||
() => app.utils.truncate(item, 500),
|
||||
),
|
||||
t.small(
|
||||
{
|
||||
role: "button",
|
||||
className: "remove-btn link-hint m-l-auto p-l-5 p-r-5",
|
||||
title: "Clear",
|
||||
onauxclick: (e) => {
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
},
|
||||
onclick: (e) => {
|
||||
e.stopPropagation();
|
||||
removeFromHistory(item);
|
||||
return false;
|
||||
},
|
||||
},
|
||||
t.i({ className: "ri-close-line", ariaHidden: true }),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "page-header-primary-btns" },
|
||||
t.button(
|
||||
{
|
||||
type: "button",
|
||||
className: () => `btn expanded-lg ${pageData.isExecuting ? "loading" : ""}`,
|
||||
disabled: () => pageData.isExecuting,
|
||||
onclick: () => executeSQLWithConfirm(),
|
||||
},
|
||||
t.i({ className: "ri-play-large-line", ariaHidden: true }),
|
||||
t.span({ className: "txt" }, "Execute"),
|
||||
),
|
||||
),
|
||||
),
|
||||
t.div(
|
||||
{ className: "field sql-console-field" },
|
||||
app.components.codeEditor({
|
||||
id: editorId,
|
||||
language: "sql",
|
||||
required: true,
|
||||
name: "query",
|
||||
placeholder: "e.g. EXPLAIN QUERY PLAN SELECT * from users WHERE verified=true",
|
||||
value: () => pageData.query,
|
||||
oninput: (val) => pageData.query = val,
|
||||
onblur: (val) => pageData.query = val.trim(),
|
||||
}),
|
||||
),
|
||||
t.div(
|
||||
{ className: "flex field-help m-b-sm" },
|
||||
t.button(
|
||||
{
|
||||
type: "button",
|
||||
className: "link-hint m-l-auto",
|
||||
"html-popovertarget": uniqueId + "caveats_dropdown",
|
||||
},
|
||||
() => "SQL console caveats",
|
||||
),
|
||||
t.div(
|
||||
{
|
||||
id: uniqueId + "caveats_dropdown",
|
||||
className: "dropdown sm query-caveats-dropdown",
|
||||
popover: "auto",
|
||||
},
|
||||
t.ul(
|
||||
null,
|
||||
t.li(null, "The returned rows are limited up to 1000."),
|
||||
t.li(null, "The executed queries have a max timeout of 3 minutes."),
|
||||
t.li(null, "The data is returned as byte strings without any additional formatting."),
|
||||
t.li(null, "Multiple queries are supported but only the result of the last one is returned."),
|
||||
),
|
||||
),
|
||||
),
|
||||
// error alert
|
||||
t.div(
|
||||
{
|
||||
hidden: () => pageData.isExecuting || !pageData.errorMsg,
|
||||
className: "alert danger m-b-sm",
|
||||
},
|
||||
t.pre(null, () => pageData.errorMsg),
|
||||
),
|
||||
// success alert
|
||||
t.div(
|
||||
{
|
||||
hidden: () =>
|
||||
pageData.isExecuting || pageData.errorMsg || pageData.result?.columns?.length
|
||||
|| app.utils.isEmpty(pageData.result),
|
||||
className: "alert success m-b-sm",
|
||||
},
|
||||
t.p({ className: "txt-bold" }, "Query executed successfully!"),
|
||||
t.p(null, "Affected rows: ", () => pageData.result?.affectedRows || 0),
|
||||
),
|
||||
// rows
|
||||
t.div(
|
||||
{
|
||||
hidden: () => pageData.isExecuting || !pageData.result?.columns?.length,
|
||||
className: "page-table-wrapper",
|
||||
},
|
||||
t.table(
|
||||
{ className: "sql-console-table responsive-table optimize" },
|
||||
t.thead(
|
||||
{ className: "sticky" },
|
||||
t.tr(null, () => {
|
||||
return pageData.result?.columns?.map((col, i) => {
|
||||
return t.th({
|
||||
textContent: col.name,
|
||||
className: () => {
|
||||
let classes = "sort-handle";
|
||||
|
||||
if (pageData.sort?.index == i) {
|
||||
classes += pageData.sort.asc ? " asc" : " desc";
|
||||
}
|
||||
|
||||
return classes;
|
||||
},
|
||||
onclick: () => toggleSort(i),
|
||||
});
|
||||
});
|
||||
}),
|
||||
),
|
||||
t.tbody(
|
||||
null,
|
||||
() => {
|
||||
if (!pageData.sortedResultRows.length) {
|
||||
return t.tr(
|
||||
null,
|
||||
t.td(
|
||||
{ colSpan: pageData.result?.columns?.length || 1, className: "txt-center" },
|
||||
t.span({ className: "txt-hint" }, "No rows found."),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return pageData.sortedResultRows.map((rowData) => {
|
||||
return t.tr(
|
||||
null,
|
||||
() => {
|
||||
return pageData.result?.columns?.map((col, j) => {
|
||||
const val = rowData[j];
|
||||
return t.td(
|
||||
{ "html-data-name": col.name },
|
||||
val == null ? "NULL" : app.utils.truncate(val, 2000),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
// load more btn
|
||||
t.tr(
|
||||
{
|
||||
hidden: () => (
|
||||
pageData.isExecuting
|
||||
|| !pageData.result?.rows?.length
|
||||
|| pageData.result.rows.length <= pageData.sortedResultRows.length
|
||||
),
|
||||
},
|
||||
t.td(
|
||||
{ colSpan: 99 },
|
||||
t.button(
|
||||
{
|
||||
type: "button",
|
||||
className: "btn lg secondary load-more-btn",
|
||||
onclick: () => {
|
||||
pageData.maxRows = pageData.result?.rows?.length || defaultMaxRows;
|
||||
},
|
||||
},
|
||||
t.span({
|
||||
className: "txt",
|
||||
textContent: () => `Load remaining (${pageData.totalRemainingRows})`,
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
t.footer(
|
||||
{ className: "page-footer" },
|
||||
t.span(
|
||||
{
|
||||
className: () => `exec-time ${pageData.isExecuting ? "faded" : ""}`,
|
||||
},
|
||||
"Time: ",
|
||||
() => (pageData.result?.execTime || 0) + "ms",
|
||||
),
|
||||
t.span(
|
||||
{
|
||||
hidden: () => !pageData.result?.columns?.length,
|
||||
className: () => `total-count ${pageData.isExecuting ? "faded" : ""}`,
|
||||
},
|
||||
"Rows: ",
|
||||
() => pageData.result?.rows?.length || 0,
|
||||
() => {
|
||||
if (!pageData.result?.rows?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [
|
||||
" (",
|
||||
t.span({
|
||||
role: "button",
|
||||
className: "link-hint",
|
||||
textContent: "Export as CSV",
|
||||
onclick: downloadCSV,
|
||||
}),
|
||||
")",
|
||||
];
|
||||
},
|
||||
),
|
||||
app.components.credits(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
+8
-1
@@ -115,6 +115,13 @@ window.app.store = store({
|
||||
label: "Import collections",
|
||||
},
|
||||
],
|
||||
Debug: [
|
||||
{
|
||||
href: "#/settings/sql",
|
||||
icon: "ri-terminal-box-line",
|
||||
label: "SQL console",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
predefinedAccentColors: [
|
||||
@@ -205,7 +212,7 @@ window.app.store = store({
|
||||
addOrUpdateCollection(collection) {
|
||||
const index = app.store.collections.findIndex((c) => c.id == collection.id);
|
||||
if (index >= 0) {
|
||||
if (app.store.activeCollection.id == collection.id) {
|
||||
if (app.store.activeCollection?.id == collection.id) {
|
||||
app.store._activeCollectionIdOrName = collection.id;
|
||||
}
|
||||
|
||||
|
||||
@@ -844,6 +844,29 @@ const utils = {
|
||||
utils.download(url, name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Downloads a CSV file created from the provide array data.
|
||||
*
|
||||
* @param {Array} arr The JS array to serialize as CSV
|
||||
* @param {string} name The result file name.
|
||||
*/
|
||||
downloadCSV(arr, name) {
|
||||
name = name.endsWith(".csv") ? name : name + ".csv";
|
||||
|
||||
// seriaze data
|
||||
let content = arr.map((row) => {
|
||||
return row.map((v) => "\"" + ("" + v).replaceAll("\"", "\"\"") + "\"").join(",");
|
||||
}).join("\n");
|
||||
|
||||
const blob = new Blob([content], {
|
||||
type: "text/csv;charset=utf-8;",
|
||||
});
|
||||
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
utils.download(url, name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a normalized API URL address that is used in the API example docs.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user