-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathterminal.js
More file actions
154 lines (136 loc) · 4.48 KB
/
terminal.js
File metadata and controls
154 lines (136 loc) · 4.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
const Field = require("@saltcorn/data/models/field");
const Table = require("@saltcorn/data/models/table");
const Form = require("@saltcorn/data/models/form");
const View = require("@saltcorn/data/models/view");
const Trigger = require("@saltcorn/data/models/trigger");
const { findType } = require("@saltcorn/data/models/discovery");
const { save_menu_items } = require("@saltcorn/data/models/config");
const db = require("@saltcorn/data/db");
const Workflow = require("@saltcorn/data/models/workflow");
const { renderForm } = require("@saltcorn/markup");
const { div, script, domReady, pre, code } = require("@saltcorn/markup/tags");
const { getState } = require("@saltcorn/data/db/state");
const { mkTable } = require("@saltcorn/markup");
const get_state_fields = () => [];
const getForm = async (viewname) => {
const fields = [
{
name: "sql",
label: "SQL",
input_type: "code",
attributes: { mode: "text/x-sql" },
},
];
const form = new Form({
action: `/view/${viewname}`,
fields,
submitLabel: "Run query",
additionalButtons: [
{
label: "Show current activity",
onclick: "sql_terminal_show_activity(this)",
class: "btn btn-outline-secondary",
afterSave: true,
},
],
});
return form;
};
const activityScript = script(`
const sql_all_queries = {}
function sql_terminal_show_activity() {
view_post("SQL Terminal", "get_activity", {}, render_table )
}
function arrayToHTMLTable(data) {
if (!data || data.length === 0) return '<table></table>';
const headers = Object.keys(data[0]);
const headerRow = '<tr>' + headers.map(function(h) {
return '<th>' + h + '</th>';
}).join('') + '</tr>';
const bodyRows = data.map(function(obj) {
return '<tr>' + headers.map(function(h) {
return '<td>' + (obj[h] != null ? obj[h] : '') + '</td>';
}).join('') + '</tr>';
}).join('');
return '<table class="table">' + headerRow + bodyRows + '</table>';
}
function render_table(data) {
const rows = data.rows
const active_keys = new Set()
for(const row of rows) {
const key = row.query_start + row.query
sql_all_queries[key] = row
active_keys.add(key)
}
const past_queries = []
Object.keys(sql_all_queries).forEach(k=>{
if(!active_keys.has(k)) past_queries.push(sql_all_queries[k])
})
const linkout = '<a class="btn btn-primary" href="/view/SQL%20Terminal">Done</a>'
const current_table = rows.length ? "<h5>Active queries</h5>"+arrayToHTMLTable(rows) : "<h5>Active queries</h5><p>No active queries</p>"
const past_table = past_queries.length ? "<h5>Previous queries</h5>"+arrayToHTMLTable(past_queries)+"<i>Runtime is largest observed and not indicative of total duration</i><br>":""
$('[data-sc-embed-viewname="SQL Terminal"]').html(current_table+past_table+linkout)
setTimeout(sql_terminal_show_activity, 400)
}
`,
);
const run = async (table_id, viewname, cfg, state, { res, req }) => {
const form = await getForm(viewname);
return activityScript + renderForm(form, req.csrfToken());
};
const runPost = async (
table_id,
viewname,
config,
state,
body,
{ req, res },
) => {
const form = await getForm(viewname);
form.validate(body);
const is_sqlite = db.isSQLite;
const client = is_sqlite ? db : await db.getClient();
await client.query(`BEGIN;`);
if (!is_sqlite) {
await client.query(`SET LOCAL search_path TO "${db.getTenantSchema()}";`);
}
let sqlResult;
try {
const qres = await client.query(form.values.sql, []);
await client.query(`COMMIT;`);
if (!is_sqlite) client.release(true);
sqlResult = mkTable(
qres.fields.map((field) => ({ label: field.name, key: field.name })),
qres.rows,
);
} catch (error) {
sqlResult = error.message;
}
res.sendWrap("SQL Terminal", [
activityScript,
renderForm(form, req.csrfToken()),
sqlResult,
]);
};
const get_activity = async (table_id, viewname, config, body, { req, res }) => {
const { rows } = await db.query(`SELECT pid,
state,
wait_event,
query_start::time,
EXTRACT(epoch FROM now() - query_start) AS runtime,
query
FROM pg_stat_activity
WHERE state = 'active' AND NOT query LIKE '%pg_stat_activity%'
ORDER BY runtime DESC;`);
return { json: { rows } };
};
module.exports = {
name: "SQL Terminal",
display_state_form: false,
tableless: true,
singleton: true,
get_state_fields,
run,
runPost,
routes: { get_activity },
};