@opengis/bi 1.2.0 → 1.2.2

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.
Files changed (74) hide show
  1. package/dist/bi.js +1 -1
  2. package/dist/bi.umd.cjs +42 -42
  3. package/dist/import-file-D8jh74Dz.js +3543 -0
  4. package/dist/{vs-funnel-bar-C_TceUrc.js → vs-funnel-bar-T330oJNS.js} +3 -3
  5. package/dist/{vs-list-DyhLUIPb.js → vs-list-DeHF_Oaf.js} +109 -109
  6. package/dist/{vs-map-BtQJNN4L.js → vs-map-Skt608pM.js} +8 -8
  7. package/dist/{vs-map-cluster-BbPUosvt.js → vs-map-cluster-BRUiY_90.js} +21 -21
  8. package/dist/{vs-number-D2GkU586.js → vs-number-Dd_21nn-.js} +3 -3
  9. package/dist/{vs-table-D_Yn9QqB.js → vs-table-BwC29Zyc.js} +6 -6
  10. package/dist/{vs-text-BivVd6cY.js → vs-text-DEJjWxDu.js} +32 -39
  11. package/package.json +77 -76
  12. package/plugin.js +22 -0
  13. package/server/helpers/mdToHTML.js +17 -0
  14. package/server/migrations/bi.dataset.sql +46 -0
  15. package/server/migrations/bi.sql +112 -0
  16. package/server/plugins/docs.js +48 -0
  17. package/server/plugins/hook.js +89 -0
  18. package/server/plugins/vite.js +69 -0
  19. package/server/routes/dashboard/controllers/dashboard.import.js +103 -0
  20. package/server/routes/dashboard/controllers/dashboard.js +157 -0
  21. package/server/routes/dashboard/controllers/dashboard.list.js +40 -0
  22. package/server/routes/dashboard/controllers/utils/yaml.js +11 -0
  23. package/server/routes/dashboard/index.mjs +26 -0
  24. package/server/routes/data/controllers/data.js +230 -0
  25. package/server/routes/data/controllers/util/chartSQL.js +49 -0
  26. package/server/routes/data/controllers/util/normalizeData.js +65 -0
  27. package/server/routes/data/index.mjs +32 -0
  28. package/server/routes/dataset/controllers/bi.dataset.list.js +29 -0
  29. package/server/routes/dataset/controllers/bi.db.list.js +19 -0
  30. package/server/routes/dataset/controllers/comment.js +55 -0
  31. package/server/routes/dataset/controllers/createDatasetPost.js +134 -0
  32. package/server/routes/dataset/controllers/data.js +149 -0
  33. package/server/routes/dataset/controllers/dbTablePreview.js +58 -0
  34. package/server/routes/dataset/controllers/dbTables.js +34 -0
  35. package/server/routes/dataset/controllers/delete.js +40 -0
  36. package/server/routes/dataset/controllers/deleteDataset.js +52 -0
  37. package/server/routes/dataset/controllers/editDataset.js +90 -0
  38. package/server/routes/dataset/controllers/export.js +214 -0
  39. package/server/routes/dataset/controllers/form.js +99 -0
  40. package/server/routes/dataset/controllers/format.js +46 -0
  41. package/server/routes/dataset/controllers/insert.js +47 -0
  42. package/server/routes/dataset/controllers/table.js +68 -0
  43. package/server/routes/dataset/controllers/update.js +43 -0
  44. package/server/routes/dataset/index.mjs +132 -0
  45. package/server/routes/dataset/utils/convertJSONToCSV.js +17 -0
  46. package/server/routes/dataset/utils/convertJSONToXls.js +47 -0
  47. package/server/routes/dataset/utils/createTableQuery.js +59 -0
  48. package/server/routes/dataset/utils/datasetForms.js +1 -0
  49. package/server/routes/dataset/utils/descriptionList.js +46 -0
  50. package/server/routes/dataset/utils/downloadRemoteFile.js +58 -0
  51. package/server/routes/dataset/utils/executeQuery.js +46 -0
  52. package/server/routes/dataset/utils/getLayersData.js +107 -0
  53. package/server/routes/dataset/utils/getTableData.js +47 -0
  54. package/server/routes/dataset/utils/insertDataQuery.js +12 -0
  55. package/server/routes/dataset/utils/metaFormat.js +24 -0
  56. package/server/routes/edit/controllers/dashboard.add.js +36 -0
  57. package/server/routes/edit/controllers/dashboard.delete.js +39 -0
  58. package/server/routes/edit/controllers/dashboard.edit.js +61 -0
  59. package/server/routes/edit/controllers/widget.add.js +78 -0
  60. package/server/routes/edit/controllers/widget.del.js +58 -0
  61. package/server/routes/edit/controllers/widget.edit.js +106 -0
  62. package/server/routes/edit/index.mjs +33 -0
  63. package/server/routes/map/controllers/cluster.js +125 -0
  64. package/server/routes/map/controllers/clusterVtile.js +166 -0
  65. package/server/routes/map/controllers/geojson.js +127 -0
  66. package/server/routes/map/controllers/heatmap.js +118 -0
  67. package/server/routes/map/controllers/map.js +69 -0
  68. package/server/routes/map/controllers/utils/downloadClusterData.js +45 -0
  69. package/server/routes/map/controllers/vtile.js +183 -0
  70. package/server/routes/map/index.mjs +32 -0
  71. package/server/templates/page/login.html +59 -0
  72. package/server/utils/getWidget.js +117 -0
  73. package/utils.js +12 -0
  74. package/dist/import-file-Bx4xpxVb.js +0 -3493
@@ -0,0 +1,48 @@
1
+ import path, { dirname } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import fs from 'fs';
4
+
5
+ const dir = dirname(fileURLToPath(import.meta.url));
6
+ const root = `${dir}/../../`;
7
+
8
+ async function plugin(fastify, opts) {
9
+ fastify.get('/docs*', async (req, reply) => {
10
+ if (!fs.existsSync(path.join(root, 'docs/.vitepress/dist/'))) {
11
+ return reply.status(404).send('docs not exists');
12
+ }
13
+
14
+ const { params } = req;
15
+ const url = params['*'];
16
+
17
+ const filePath =
18
+ url && url[url.length - 1] !== '/'
19
+ ? path.join(root, 'docs/.vitepress/dist/', url)
20
+ : path.join(root, 'docs/.vitepress/dist/', url, 'index.html');
21
+
22
+ if (!fs.existsSync(filePath)) {
23
+ return reply.status(404).send('File not found');
24
+ }
25
+
26
+ const ext = path.extname(filePath);
27
+ const mime = {
28
+ '.js': 'text/javascript',
29
+ '.css': 'text/css',
30
+ '.woff2': 'application/font-woff',
31
+ '.png': 'image/png',
32
+ '.svg': 'image/svg+xml',
33
+ '.jpg': 'image/jpg',
34
+ '.html': 'text/html',
35
+ '.json': 'application/json',
36
+ '.pdf': 'application/pdf',
37
+ }[ext];
38
+
39
+ const stream = fs.createReadStream(filePath);
40
+ stream.on('error', (err) => {
41
+ reply.status(500).send('Error reading file');
42
+ });
43
+
44
+ return mime ? reply.type(mime).send(stream) : reply.send(stream);
45
+ });
46
+ }
47
+
48
+ export default plugin;
@@ -0,0 +1,89 @@
1
+ import fp from 'fastify-plugin';
2
+ import fs from 'fs';
3
+
4
+ // the use of fastify-plugin is required to be able
5
+ // to export the decorators to the outer scope
6
+
7
+ async function plugin(fastify) {
8
+ // preSerialization
9
+ fastify.addHook('preSerialization', async (req, reply, payload) => {
10
+ if (!req.session?.passport?.user?.uid) {
11
+ // return reply.redirect('/login');
12
+ }
13
+ if (req.url.includes('/suggest/') && !req.query.json) {
14
+ return payload?.data;
15
+ }
16
+ if (payload.redirect) {
17
+ return reply.redirect(payload.redirect);
18
+ }
19
+ if (reply.sent) {
20
+ return null;
21
+ }
22
+
23
+ if ([200, 400, 403, 409, 404, 500].includes(payload.status)) {
24
+ reply.status(payload.status);
25
+ }
26
+ /* if (payload.headers) {
27
+ reply.headers(payload.headers);
28
+ } */
29
+ if (payload.buffer) {
30
+ return payload.buffer;
31
+ }
32
+ if (payload.file) {
33
+ // const buffer = await readFile(payload.file);
34
+ // return reply.send(buffer);
35
+ const stream = fs.createReadStream(payload.file);
36
+ return stream;
37
+ // return reply.send(stream);
38
+ }
39
+
40
+ if (payload.message) {
41
+ return payload.message;
42
+ }
43
+ return payload;
44
+ });
45
+
46
+ // preValidation
47
+ fastify.addHook('preValidation', async (req) => {
48
+ const parseRawBody =
49
+ ['POST', 'PUT'].includes(req.method) &&
50
+ req.body &&
51
+ typeof req.body === 'string' &&
52
+ req.body.trim(/\r\n/g).startsWith('{') &&
53
+ req.body.trim(/\r\n/g).endsWith('}');
54
+ if (parseRawBody) {
55
+ try {
56
+ req.body = JSON.parse(req.body || '{}');
57
+ } catch (err) {
58
+ // throw new Error('invalid body');
59
+ // return { error: 'invalid body', status: 400 };
60
+ }
61
+ }
62
+ });
63
+
64
+ // allow upload file
65
+ const kIsMultipart = Symbol.for('[FastifyMultipart.isMultipart]');
66
+ fastify.addContentTypeParser('multipart', (request, _, done) => {
67
+ request[kIsMultipart] = true;
68
+ done(null);
69
+ });
70
+
71
+ // parse Body
72
+ function contentParser(req, body, done) {
73
+ const parseBody = decodeURIComponent(body.toString())
74
+ .split('&')
75
+ .reduce((acc, el) => {
76
+ const [key, val] = el.split('=');
77
+ return { ...acc, [key]: val };
78
+ }, {});
79
+ done(null, parseBody);
80
+ }
81
+
82
+ fastify.addContentTypeParser(
83
+ 'application/x-www-form-urlencoded',
84
+ { parseAs: 'buffer' },
85
+ contentParser
86
+ );
87
+ }
88
+
89
+ export default fp(plugin);
@@ -0,0 +1,69 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import config from '../../config.js';
4
+
5
+ const { disableAuth } = config;
6
+ const isProduction = process.env.NODE_ENV === 'production';
7
+
8
+ async function plugin(fastify) {
9
+ // vite server
10
+ if (!isProduction) {
11
+ const vite = await import('vite');
12
+
13
+ const viteServer = await vite.createServer({
14
+ server: {
15
+ middlewareMode: true,
16
+ },
17
+ });
18
+ // hot reload
19
+ viteServer.watcher.on('all', (d, t) => {
20
+ if (!t.includes('module') && !t.includes('templates')) return;
21
+ // console.log(d, t);
22
+ viteServer.ws.send({ type: 'full-reload' });
23
+ });
24
+
25
+ // this is middleware for vite's dev servert
26
+ fastify.addHook('onRequest', async (req, reply) => {
27
+ // const { user } = req.session?.passport || {};
28
+ const next = () => new Promise((resolve) => {
29
+ viteServer.middlewares(req.raw, reply.raw, () => resolve());
30
+ });
31
+ await next();
32
+ });
33
+ fastify.get('*', async () => {});
34
+ return;
35
+ }
36
+
37
+ // From Build
38
+ fastify.get('*', async (req, reply) => {
39
+ // console.log(disableAuth)
40
+ if (!req.user && !disableAuth) return reply.redirect('/login');
41
+ const stream = fs.createReadStream('dist/index.html');
42
+ return reply
43
+ .headers({ 'Cache-Control': 'public, no-cache' })
44
+ .type('text/html')
45
+ .send(stream);
46
+ });
47
+ fastify.get('/assets/:file', async (req, reply) => {
48
+ const stream = fs.createReadStream(`dist/assets/${req.params.file}`);
49
+ const ext = path.extname(req.params.file);
50
+ const mime = {
51
+ '.js': 'text/javascript',
52
+ '.css': 'text/css',
53
+ '.woff2': 'application/font-woff',
54
+ '.png': 'image/png',
55
+ }[ext];
56
+ // reply.cacheControl('max-age', '1d');
57
+ return mime
58
+ ? reply
59
+ .headers({
60
+ 'Cache-Control': 'public, max-age=3600',
61
+ 'Content-Encoding': 'identity',
62
+ })
63
+ .type(mime)
64
+ .send(stream)
65
+ : stream;
66
+ });
67
+ }
68
+
69
+ export default plugin;
@@ -0,0 +1,103 @@
1
+ import path from 'node:path';
2
+
3
+ import {
4
+ config, logger, pgClients, getTemplate, getTemplatePath, dataInsert, dataUpdate,
5
+ } from '@opengis/fastify-table/utils.js';
6
+
7
+ export default async function dashboardImport({ pg = pgClients.client, query = {}, user = {} }, reply) {
8
+ const time = Date.now();
9
+
10
+ if (!query?.dashboard) {
11
+ return reply.status(400).send('not enough query params: dashboard');
12
+ }
13
+
14
+ const list = getTemplatePath('dashboard').filter(el => el[0] === query.dashboard);
15
+
16
+ if (!list?.length) {
17
+ return reply.status(404).send(`dashboard not found`);
18
+ }
19
+
20
+ const client = await pg.connect();
21
+ Object.assign(client, {
22
+ options: pg.options,
23
+ pk: pg.pk,
24
+ pgType: pg.pgType,
25
+ tlist: pg.tlist,
26
+ });
27
+
28
+ const ids = [];
29
+ try {
30
+ await client.query('BEGIN');
31
+ const rows = await Promise.all(list.map(async ([filename]) => {
32
+
33
+ const obj = await getTemplate('dashboard', filename);
34
+ const index = obj?.find((el) => el[0] === 'index.yml')?.[1];
35
+ const { db = pg.options?.database, description, filters, panels, table_name, title, tags, style, grid } = index || {};
36
+
37
+ const dashboardId = await dataInsert({
38
+ pg,
39
+ table: 'bi.dashboard',
40
+ data: {
41
+ db,
42
+ name: filename,
43
+ description,
44
+ filters,
45
+ panels,
46
+ table_name,
47
+ title,
48
+ words: tags?.join(','),
49
+ style,
50
+ grid,
51
+ source: 'file',
52
+ },
53
+ uid: user?.uid,
54
+ }).then(el => el.rows?.[0]?.dashboard_id);
55
+ ids.push(dashboardId);
56
+
57
+ const widgetList = panels?.reduce((acc, curr) => {
58
+ curr.widgets?.forEach(item => acc.push(item));
59
+ if (curr.widget) acc.push(curr.widget);
60
+ return acc;
61
+ }, []).filter(el => el) || [];
62
+
63
+ const widgets = widgetList.length
64
+ ? await Promise.all(obj.filter(el => widgetList.includes(path.parse(el[0]).name)).map(async ([widget, widgetData]) => {
65
+ const { ext, name } = path.parse(widget);
66
+ const data = ext === '.yml' ? widgetData : { type: 'text', data: { text: widgetData } };
67
+ Object.assign(data, { name, x: widgetData?.data?.x, table_name: widgetData?.data?.table, dashboard_id: dashboardId });
68
+
69
+ const res = await dataInsert({
70
+ pg: client,
71
+ table: 'bi.widget',
72
+ data,
73
+ uid: user?.uid,
74
+ }).then(el => el.rows?.[0] || {});
75
+ return res;
76
+ }))
77
+ : [];
78
+ const test = await dataUpdate({
79
+ pg: client,
80
+ table: 'bi.dashboard',
81
+ id: dashboardId,
82
+ data: {
83
+ widgets,
84
+ },
85
+ uid: user?.uid,
86
+ });
87
+ return { dashboardId, name: filename, widgets };
88
+ }));
89
+
90
+ await client.query('COMMIT');
91
+ return { time: Date.now() - time, rows };
92
+ } catch (err) {
93
+ await client.query('ROLLBACK');
94
+ if (ids.length) {
95
+ const { rowCount = 0 } = await pg.query('delete from bi.dashboard where dashboard_id = any($1)', [ids]);
96
+ console.log('ROLLBACK delete', rowCount, 'dashboard');
97
+ // const { rowCount: rowCount1 = 0 } = await pg.query('delete from bi.widget where dashboard_id = any($1)', [ids]);
98
+ // console.log('delete', rowCount1, 'widget');
99
+ }
100
+ logger.file('bi/dashboardImport/error', { error: err.toString(), stack: err.stack, ids });
101
+ return reply.status(500).send(config.debug ? err.toString() : 'import error');
102
+ }
103
+ }
@@ -0,0 +1,157 @@
1
+ import yaml from 'js-yaml';
2
+ import {
3
+ pgClients,
4
+ getTemplatePath,
5
+ getTemplate,
6
+ getPGAsync,
7
+ getMeta,
8
+ } from '@opengis/fastify-table/utils.js';
9
+
10
+ export default async function dashboard({
11
+ pg: pg1 = pgClients.client, params = {},
12
+ }) {
13
+ const time = Date.now();
14
+ const { id } = params;
15
+
16
+ if (!id) {
17
+ return { message: 'not enough params: dashboard required', status: 400 };
18
+ }
19
+ const dashboards = getTemplatePath('dashboard');
20
+
21
+ const fileDashboard = dashboards.find((el) => el[0] === id);
22
+ if (!fileDashboard) {
23
+ const sql = `select title, description, table_name, panels, grid, widgets, filters, style, words, db, public
24
+ from bi.dashboard where $1 in (dashboard_id, name)`;
25
+
26
+ const data = await pg1.query(sql, [id]).then(el => el.rows?.[0] || {});
27
+
28
+ let pg = pg1;
29
+ try {
30
+ pg = data.pg || (data.db ? await getPGAsync(data.db) : null) || pg1;
31
+ } catch (err) {
32
+ data.error = err.toString();
33
+ }
34
+
35
+ data.type = 'bd';
36
+ const { table_name: table } = data;
37
+
38
+ if (!table) {
39
+ return { message: 'not enough params: table required', status: 400 };
40
+ }
41
+
42
+ const loadTemplate = pg.pk?.['admin.doc_template'] ? await pg.query(
43
+ 'select body from admin.doc_template where doc_type=5 and title=$1',
44
+ [table]
45
+ ).then(el => el.rows?.[0]?.body) : null;
46
+
47
+ const sqlList = loadTemplate?.sql ? loadTemplate?.sql
48
+ ?.filter?.(el => !el.disabled && el?.sql?.replace)
49
+ ?.map?.((el, i) => `left join lateral (${el.sql.replace(/limit 1/ig, '')}) t${i} on 1=1`)?.join?.(' ') : '';
50
+
51
+ const { fields = [] } = table && pg.pk?.[loadTemplate?.table || table] ? await pg.query(`select * from ${loadTemplate?.table || table} t ${sqlList || ''} limit 0`) : {};
52
+
53
+ data?.widgets?.forEach?.(el => {
54
+ const { style, data = {}, type, title, x, metrics } = el;
55
+ el.yml = yaml.dump({ title, type, data: { x, metrics, ...data }, style, })
56
+ // el.yml = yaml.dump({ style, data, type, title });
57
+ });
58
+
59
+ data.panels?.forEach?.(el => {
60
+ const { title, type } = data?.widgets?.find(item => item.name === el.widget) || {};
61
+ Object.assign(el, { title, type });
62
+ });
63
+
64
+ const meta = table ? await getMeta({ pg, table: loadTemplate?.table || table }) : {};
65
+ const columnIndexes = meta?.columns?.reduce((acc, curr, idx) => Object.assign(acc, { [curr.name]: idx }), {}) || [];
66
+
67
+ const columns = fields?.map(el => ({ name: el.name, title: meta?.columns[columnIndexes[el.name]]?.title || el.name, type: pg.pgType?.[el.dataTypeID] }));
68
+
69
+ return {
70
+ ...data || {},
71
+ widgets: data?.widgets?.filter?.(el => el?.widget_id) || [],
72
+ panels: data?.panels?.filter?.(el => el?.widget) || [],
73
+ geom: !meta?.geom,
74
+ error:
75
+ table && !pg.pk?.[loadTemplate?.table || table] ? `table pkey not found: ${loadTemplate?.table || table}` : undefined,
76
+ table_name: table,
77
+ templateTable: loadTemplate?.table,
78
+ time: Date.now() - time,
79
+ columns,
80
+ };
81
+ }
82
+
83
+ const fileData = await getTemplate('dashboard', id);
84
+ const index = fileData.find((el) => el[0] === 'index.yml')[1];
85
+
86
+ if (!index) {
87
+ return { message: `not found ${id}`, status: 404 };
88
+ }
89
+
90
+ const data = index;
91
+ data.type = 'file';
92
+ const { table } = data?.data || { table: data?.table_name };
93
+
94
+ let pg = pg1;
95
+ try {
96
+ pg = data.pg || (data.db ? await getPGAsync(data.db) : null) || pg1;
97
+ } catch (err) {
98
+ data.error = err.toString();
99
+ }
100
+
101
+ const { fields = [] } = table ? await pg.query(`select * from ${table} limit 0`) : {};
102
+
103
+ const meta = await getMeta({ pg: pg1, table });
104
+
105
+ const columns = meta?.columns
106
+ ?.filter(el => fields.map(field => field.name).includes(el.name))
107
+ ?.map(el => ({ name: el.name, title: el.title, type: pg.pgType?.[el.dataTypeID] }));
108
+
109
+ const checks = index?.filters?.filter((el) => el?.id && fields.map((el) => el?.name).includes(el?.id) && el?.type === 'Check');
110
+ if (checks?.length) {
111
+ await Promise.all(checks.map(async (el) => {
112
+ if (el?.data) {
113
+ const options = await getTemplate('cls', el.data);
114
+ Object.assign(el, { options });
115
+ } else if (index?.table_name || index?.table) {
116
+ const { rows = [] } = await pg.query(`select "${el.id}" as id, count(*) from ${index?.table_name || index?.table} group by "${el.id}"`);
117
+ Object.assign(el, { options: rows });
118
+ }
119
+ }));
120
+ }
121
+ const ranges = index?.filters?.filter((el) => el?.id && fields.map((el) => el?.name).includes(el?.id) && el?.type === 'Range');
122
+ if (ranges?.length) {
123
+ await Promise.all(ranges.map(async (el) => {
124
+ const rows = await pg.query(`select array[
125
+ percentile_cont(0) within group (order by ${el.id}), percentile_cont(0.25) within group (order by ${el.id}),
126
+ percentile_cont(0.5) within group (order by ${el.id}), percentile_cont(0.75) within group (order by ${el.id}),
127
+ percentile_cont(1.0) within group (order by ${el.id})
128
+ ] from ${index?.table_name || index?.table}`).then(el => el.rows?.[0]?.array || []);
129
+ Object.assign(el, { options: rows });
130
+ }));
131
+ }
132
+
133
+ // console.log(fileData)
134
+ const widgets = fileData
135
+ .filter((el) => el[0] !== 'index.yml')
136
+ .map((el) =>
137
+ el[1].data
138
+ ? {
139
+ name: el[0].split('.')[0],
140
+ type: el[1].type,
141
+ title: el[1].title,
142
+ style: el[1].style,
143
+ data: el[1].data,
144
+
145
+ }
146
+ : { name: el[0].split('.')[0], title: el[1] }
147
+ );
148
+
149
+ return {
150
+ ...data,
151
+ geom: !!meta?.geom,
152
+ table_name: table,
153
+ time: Date.now() - time,
154
+ columns,
155
+ widgets,
156
+ };
157
+ }
@@ -0,0 +1,40 @@
1
+ import { pgClients, getTemplatePath, getTemplate } from '@opengis/fastify-table/utils.js';
2
+
3
+ const q = `select dashboard_id as name, 'db' as type, title, description, table_name, words, public from bi.dashboard`;
4
+
5
+ export default async function data({
6
+ pg = pgClients.client, query = {}, user = {}
7
+ }) {
8
+ const time = Date.now();
9
+ const { type = 'file' } = query;
10
+
11
+ const data = getTemplatePath('dashboard');
12
+ const dir = type === 'file' ? await Promise.all(
13
+ data.map(async ([filename, filepath]) => {
14
+ const obj = await getTemplate('dashboard', filename);
15
+ const index = obj?.find?.((el) => el[0] === 'index.yml')?.[1];
16
+ const { table_name, description, title } = index || {};
17
+
18
+ return { name: filename, path: user?.user_type?.includes('admin') ? filepath : undefined, type: 'file', title, description, table_name };
19
+ })
20
+ ) : [];
21
+
22
+ const { rows = [] } = ['db', 'viewer'].includes(type) ? await pg.query(q) : {};
23
+
24
+ if (type === 'viewer') {
25
+ return {
26
+ time: Date.now() - time,
27
+ db: pg.options?.database,
28
+ rows: rows.filter(el => el.table_name && !el.table_name.startsWith('demo.')),
29
+ };
30
+ }
31
+
32
+ const list = dir.concat(rows);
33
+
34
+ const res = {
35
+ time: Date.now() - time,
36
+ db: pg.options?.database,
37
+ rows: list,
38
+ };
39
+ return res;
40
+ }
@@ -0,0 +1,11 @@
1
+ import yaml from 'js-yaml';
2
+
3
+ yaml.loadSafe = (yml) => {
4
+ try {
5
+ return yaml.load(yml);
6
+ } catch (err) {
7
+ return { error: err.toString() };
8
+ }
9
+ };
10
+
11
+ export default yaml;
@@ -0,0 +1,26 @@
1
+ import dashboard from './controllers/dashboard.js';
2
+ import dashboardList from './controllers/dashboard.list.js';
3
+ import dashboardImport from './controllers/dashboard.import.js';
4
+
5
+ const biSchema = {
6
+ type: 'object',
7
+ properties: {
8
+ querystring: {
9
+ widget: { type: 'string', pattern: '^([\\d\\w]+)$' },
10
+ dashboard: { type: 'string', pattern: '^([\\d\\w]+)$' },
11
+ list: { type: 'string', pattern: '^([\\d])$' },
12
+ sql: { type: 'string', pattern: '^([\\d])$' },
13
+ },
14
+ params: {
15
+ id: { type: 'string', pattern: '^([\\d\\w]+)$' },
16
+ },
17
+ },
18
+ };
19
+
20
+ const policy = ['public'];
21
+
22
+ export default async function route(fastify) {
23
+ fastify.get(`/bi-dashboard/:id`, { config: { policy }, schema: biSchema }, dashboard);
24
+ fastify.get(`/bi-dashboard`, { config: { policy } }, dashboardList);
25
+ fastify.get(`/bi-dashboard-import`, { config: { policy } }, dashboardImport);
26
+ }