shokupan 0.7.0 → 0.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 +53 -0
- package/dist/context.d.ts +50 -15
- package/dist/{http-server-DFhwlK8e.cjs → http-server-BEMPIs33.cjs} +4 -2
- package/dist/http-server-BEMPIs33.cjs.map +1 -0
- package/dist/{http-server-0xH174zz.js → http-server-CCeagTyU.js} +4 -2
- package/dist/http-server-CCeagTyU.js.map +1 -0
- package/dist/index.cjs +998 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +996 -135
- package/dist/index.js.map +1 -1
- package/dist/plugins/application/dashboard/metrics-collector.d.ts +12 -0
- package/dist/plugins/application/dashboard/plugin.d.ts +14 -8
- package/dist/plugins/application/dashboard/static/charts.js +328 -0
- package/dist/plugins/application/dashboard/static/failures.js +85 -0
- package/dist/plugins/application/dashboard/static/graph.mjs +523 -0
- package/dist/plugins/application/dashboard/static/poll.js +146 -0
- package/dist/plugins/application/dashboard/static/reactflow.css +18 -0
- package/dist/plugins/application/dashboard/static/registry.css +131 -0
- package/dist/plugins/application/dashboard/static/registry.js +269 -0
- package/dist/plugins/application/dashboard/static/requests.js +118 -0
- package/dist/plugins/application/dashboard/static/scrollbar.css +24 -0
- package/dist/plugins/application/dashboard/static/styles.css +175 -0
- package/dist/plugins/application/dashboard/static/tables.js +92 -0
- package/dist/plugins/application/dashboard/static/tabs.js +113 -0
- package/dist/plugins/application/dashboard/static/tabulator.css +66 -0
- package/dist/plugins/application/dashboard/template.eta +246 -0
- package/dist/plugins/application/socket-io.d.ts +14 -0
- package/dist/router.d.ts +12 -0
- package/dist/shokupan.d.ts +21 -1
- package/dist/util/datastore.d.ts +4 -3
- package/dist/util/decorators.d.ts +5 -0
- package/dist/util/http-error.d.ts +38 -0
- package/dist/util/http-status.d.ts +30 -0
- package/dist/util/request.d.ts +1 -1
- package/dist/util/symbol.d.ts +19 -0
- package/dist/util/types.d.ts +30 -1
- package/package.json +6 -3
- package/dist/http-server-0xH174zz.js.map +0 -1
- package/dist/http-server-DFhwlK8e.cjs.map +0 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg-primary: #0f172a;
|
|
3
|
+
--bg-secondary: #1e293b;
|
|
4
|
+
--text-primary: #f8fafc;
|
|
5
|
+
--text-secondary: #94a3b8;
|
|
6
|
+
--text-emphasis: #60a5fa;
|
|
7
|
+
--accent: #3b82f6;
|
|
8
|
+
--success: #22c55e;
|
|
9
|
+
--error: #ef4444;
|
|
10
|
+
--card-border: #334155;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
* {
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
margin: 0;
|
|
16
|
+
padding: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
html {
|
|
20
|
+
height: 100%;
|
|
21
|
+
width: 100%;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
body {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
28
|
+
background-color: var(--bg-primary);
|
|
29
|
+
color: var(--text-primary);
|
|
30
|
+
min-height: 100vh;
|
|
31
|
+
padding: 2rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
header {
|
|
35
|
+
margin-bottom: 2rem;
|
|
36
|
+
display: flex;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
align-items: center;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
h1 {
|
|
42
|
+
font-size: 2rem;
|
|
43
|
+
font-weight: 700;
|
|
44
|
+
background: linear-gradient(to right, var(--accent), #8b5cf6);
|
|
45
|
+
-webkit-background-clip: text;
|
|
46
|
+
background-clip: text;
|
|
47
|
+
color: transparent;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
select {
|
|
51
|
+
outline: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.container {
|
|
55
|
+
display: flex;
|
|
56
|
+
flex: 1;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
max-width: 1200px;
|
|
59
|
+
margin: 0 auto;
|
|
60
|
+
overflow: auto
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.metrics-grid {
|
|
64
|
+
display: grid;
|
|
65
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
66
|
+
gap: 1.5rem;
|
|
67
|
+
margin-bottom: 2rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.card {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
background-color: var(--bg-secondary);
|
|
74
|
+
border: 1px solid var(--card-border);
|
|
75
|
+
border-radius: 1rem;
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.card canvas {
|
|
80
|
+
padding: 0 .5rem .5rem .5rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.card-chart {
|
|
84
|
+
flex: 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.card-value {
|
|
88
|
+
padding: 1rem;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.card-title {
|
|
92
|
+
color: var(--text-secondary);
|
|
93
|
+
font-size: 0.875rem;
|
|
94
|
+
margin-bottom: 0.5rem;
|
|
95
|
+
text-transform: uppercase;
|
|
96
|
+
letter-spacing: 0.05em;
|
|
97
|
+
height: 48px;
|
|
98
|
+
line-height: 48px;
|
|
99
|
+
padding-left: 16px;
|
|
100
|
+
font-weight: 600;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.card-value {
|
|
104
|
+
font-size: 2.5rem;
|
|
105
|
+
font-weight: 700;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.text-success {
|
|
109
|
+
color: var(--success);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.text-error {
|
|
113
|
+
color: var(--error);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#chart-container,
|
|
117
|
+
#table-container {
|
|
118
|
+
background-color: var(--bg-secondary);
|
|
119
|
+
border: 1px solid var(--card-border);
|
|
120
|
+
border-radius: 1rem;
|
|
121
|
+
padding: 1.5rem;
|
|
122
|
+
margin-bottom: 2rem;
|
|
123
|
+
overflow: hidden;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Tabs */
|
|
127
|
+
.tabs {
|
|
128
|
+
display: flex;
|
|
129
|
+
gap: 1rem;
|
|
130
|
+
border-bottom: 1px solid var(--card-border);
|
|
131
|
+
margin-bottom: 2rem;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.tab-btn {
|
|
135
|
+
background: none;
|
|
136
|
+
border: none;
|
|
137
|
+
color: var(--text-secondary);
|
|
138
|
+
padding: 1rem;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
font-size: 1rem;
|
|
141
|
+
border-bottom: 2px solid transparent;
|
|
142
|
+
transition: all 0.2s;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.tab-btn:hover {
|
|
146
|
+
color: var(--text-primary);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.tab-btn.active {
|
|
150
|
+
color: var(--accent);
|
|
151
|
+
border-bottom-color: var(--accent);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.tab-content {
|
|
155
|
+
display: none;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.tab-content.active {
|
|
159
|
+
display: block;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* Graph */
|
|
163
|
+
#cy {
|
|
164
|
+
width: 100%;
|
|
165
|
+
height: 600px;
|
|
166
|
+
background-color: var(--bg-secondary);
|
|
167
|
+
border-radius: 1rem;
|
|
168
|
+
border: 1px solid var(--card-border);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.card-container {
|
|
172
|
+
display: grid;
|
|
173
|
+
gap: 1rem;
|
|
174
|
+
grid-template-columns: 1fr;
|
|
175
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
|
|
2
|
+
// --- Table Config Helper ---
|
|
3
|
+
function createTable(id, columns, placeholder = "No data available") {
|
|
4
|
+
return new Tabulator(id, {
|
|
5
|
+
layout: "fitColumns",
|
|
6
|
+
height: "300px",
|
|
7
|
+
placeholder: placeholder,
|
|
8
|
+
data: [],
|
|
9
|
+
columns: columns,
|
|
10
|
+
layoutColumnsOnNewData: true,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// --- Top Requests Table ---
|
|
15
|
+
const topRequestsTable = createTable("#top-requests-table", [
|
|
16
|
+
{ title: "Count", field: "count", align: "center", width: 100, sorter: "number" },
|
|
17
|
+
{ title: "Method", field: "method", width: 110 },
|
|
18
|
+
{ title: "URL", field: "url" },
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
// --- Top Errors Table ---
|
|
22
|
+
const topErrorsTable = createTable("#top-errors-table", [
|
|
23
|
+
{ title: "Count", field: "count", align: "center", width: 100, sorter: "number" },
|
|
24
|
+
{ title: "Error Message", field: "error" },
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
// --- Failing Requests Table ---
|
|
28
|
+
const failingRequestsTable = createTable("#failing-requests-table", [
|
|
29
|
+
{ title: "Failures", field: "count", align: "center", width: 80, sorter: "number" },
|
|
30
|
+
{ title: "Method", field: "method", width: 110 },
|
|
31
|
+
{ title: "URL", field: "url" },
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
// --- Slowest Requests Table ---
|
|
35
|
+
const slowestRequestsTable = createTable("#slowest-requests-table", [
|
|
36
|
+
{ title: "Duration (ms)", field: "duration", width: 130, sorter: "number", formatter: (cell) => printDuration(cell.getValue()) },
|
|
37
|
+
{
|
|
38
|
+
title: "URL", formatter: (cell) => {
|
|
39
|
+
const data = cell.getData() ?? {};
|
|
40
|
+
return data.method?.toUpperCase() + ": " + data.url;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: "Status", field: "status", width: 100, align: "center", formatter: function (cell) {
|
|
45
|
+
const val = cell.getValue();
|
|
46
|
+
return `<span style="color: ${val >= 400 ? 'red' : 'green'}">${val}</span>`;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
{ title: "Time", field: "timestamp", width: 150, formatter: (cell) => new Date(cell.getValue()).toLocaleTimeString() }
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async function fetchTopStats() {
|
|
54
|
+
// Get request headers from global function if available
|
|
55
|
+
const headers = typeof getRequestHeaders !== 'undefined' ? getRequestHeaders() : {};
|
|
56
|
+
|
|
57
|
+
// Determine base path for API requests
|
|
58
|
+
const basePath = window.location.pathname.endsWith('/') ? window.location.pathname.slice(0, -1) : window.location.pathname;
|
|
59
|
+
const url = basePath + '/';
|
|
60
|
+
const interval = document.getElementById('time-range-selector')?.value || '1m';
|
|
61
|
+
try {
|
|
62
|
+
// Top Requests
|
|
63
|
+
fetch(url + 'requests/top?interval=' + interval, { headers }).then(r => r.json()).then(d => {
|
|
64
|
+
if (d.top) topRequestsTable.setData(d.top);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Top Errors
|
|
68
|
+
fetch(url + 'errors/top?interval=' + interval, { headers }).then(r => r.json()).then(d => {
|
|
69
|
+
if (d.top) topErrorsTable.setData(d.top);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Failing Requests
|
|
73
|
+
fetch(url + 'requests/failing?interval=' + interval, { headers }).then(r => r.json()).then(d => {
|
|
74
|
+
if (d.top) failingRequestsTable.setData(d.top);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Slowest Requests
|
|
78
|
+
fetch(url + 'requests/slowest?interval=' + interval, { headers }).then(r => r.json()).then(d => {
|
|
79
|
+
if (d.slowest) slowestRequestsTable.setData(d.slowest);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error("Failed to fetch top stats", e);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
document.addEventListener("DOMContentLoaded", () => {
|
|
88
|
+
fetchTopStats();
|
|
89
|
+
// Refresh periodically
|
|
90
|
+
setInterval(fetchTopStats, 10000);
|
|
91
|
+
});
|
|
92
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// --- Tabs Logic ---
|
|
2
|
+
function switchTab(tabId) {
|
|
3
|
+
console.log('Switching to tab:', tabId);
|
|
4
|
+
// Update buttons
|
|
5
|
+
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
|
|
6
|
+
// Find the button that was clicked
|
|
7
|
+
const btn = Array.from(document.querySelectorAll('.tab-btn')).find(b => b.getAttribute('onclick') === `switchTab('${tabId}')`);
|
|
8
|
+
if (btn) btn.classList.add('active');
|
|
9
|
+
|
|
10
|
+
// Update content
|
|
11
|
+
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
|
|
12
|
+
document.getElementById('tab-' + tabId).classList.add('active');
|
|
13
|
+
|
|
14
|
+
if (tabId === 'overview') {
|
|
15
|
+
if (typeof fetchTopStats === 'function') fetchTopStats();
|
|
16
|
+
}
|
|
17
|
+
else if (tabId === 'graph') {
|
|
18
|
+
initGraph();
|
|
19
|
+
}
|
|
20
|
+
else if (tabId === 'requests') {
|
|
21
|
+
if (typeof fetchRequests === 'function') fetchRequests();
|
|
22
|
+
}
|
|
23
|
+
else if (tabId === 'failures') {
|
|
24
|
+
fetchFailures();
|
|
25
|
+
}
|
|
26
|
+
else if (tabId === 'middleware') {
|
|
27
|
+
fetchMiddleware();
|
|
28
|
+
}
|
|
29
|
+
else if (tabId === 'registry') {
|
|
30
|
+
if (typeof fetchRegistry === 'function') fetchRegistry();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Middleware fetch function
|
|
35
|
+
async function fetchMiddleware() {
|
|
36
|
+
try {
|
|
37
|
+
const headers = getRequestHeaders ? getRequestHeaders() : {};
|
|
38
|
+
const basePath = window.location.pathname.endsWith('/') ? '' : window.location.pathname;
|
|
39
|
+
const url = basePath + (basePath.endsWith('/') ? 'middleware' : '/middleware');
|
|
40
|
+
|
|
41
|
+
const res = await fetch(url, { headers });
|
|
42
|
+
if (!res.ok) return;
|
|
43
|
+
const data = await res.json();
|
|
44
|
+
|
|
45
|
+
// Initialize or update table
|
|
46
|
+
if (!window.middlewareTable) {
|
|
47
|
+
window.middlewareTable = new Tabulator("#middleware-table-container", {
|
|
48
|
+
layout: "fitColumns",
|
|
49
|
+
height: "500px",
|
|
50
|
+
placeholder: "No middleware executions tracked",
|
|
51
|
+
data: data.middleware || [],
|
|
52
|
+
columns: [
|
|
53
|
+
{
|
|
54
|
+
title: "Timestamp", field: "timestamp", width: 180, formatter: function (cell) {
|
|
55
|
+
return new Date(cell.getValue()).toLocaleString();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
title: "Name", field: "name", width: 200,
|
|
60
|
+
formatter: function (cell) {
|
|
61
|
+
const row = cell.getRow().getData();
|
|
62
|
+
const isBuiltin = row.metadata?.isBuiltin;
|
|
63
|
+
const pluginName = row.metadata?.pluginName;
|
|
64
|
+
let badge = '';
|
|
65
|
+
if (isBuiltin) {
|
|
66
|
+
badge = ' <span style="background: #059669; color: white; padding: 2px 6px; border-radius: 4px; font-size: 0.7em;">BUILTIN</span>';
|
|
67
|
+
}
|
|
68
|
+
const plugin = pluginName ? ` <span style="color: #6ee7b7;">[${pluginName}]</span>` : '';
|
|
69
|
+
return cell.getValue() + badge + plugin;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{ title: "Path", field: "path", headerFilter: "input" },
|
|
73
|
+
{
|
|
74
|
+
title: "Duration (ms)", field: "duration", width: 120,
|
|
75
|
+
formatter: (cell) => cell.getValue() ? cell.getValue().toFixed(2) : 'N/A'
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
title: "Status", field: "error", width: 100, formatter: function (cell) {
|
|
79
|
+
const error = cell.getValue();
|
|
80
|
+
if (error) {
|
|
81
|
+
return '<span style="color: #ef4444; font-weight: bold;">ERROR</span>';
|
|
82
|
+
}
|
|
83
|
+
return '<span style="color: #22c55e; font-weight: bold;">OK</span>';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
initialSort: [
|
|
88
|
+
{ column: "timestamp", dir: "desc" }
|
|
89
|
+
]
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
window.middlewareTable.replaceData(data.middleware || []);
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error("Failed to fetch middleware", e);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function fetchFailures() {
|
|
100
|
+
try {
|
|
101
|
+
const headers = getRequestHeaders ? getRequestHeaders() : {};
|
|
102
|
+
// Handle relative path issue
|
|
103
|
+
const basePath = window.location.pathname.endsWith('/') ? '' : window.location.pathname;
|
|
104
|
+
const url = basePath + (basePath.endsWith('/') ? 'failures' : '/failures');
|
|
105
|
+
|
|
106
|
+
const res = await fetch(url, { headers });
|
|
107
|
+
if (!res.ok) return;
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
failuresTable.replaceData(data.failures);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.error("Failed to fetch failures", e);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
.tabulator {
|
|
2
|
+
height: 100%;
|
|
3
|
+
width: 100%;
|
|
4
|
+
background-color: #0f1322;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.tabulator .tabulator-tableholder .tabulator-table {
|
|
8
|
+
color: #ccc;
|
|
9
|
+
background-color: #0f1322;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.tabulator .tabulator-header {
|
|
13
|
+
color: #ddd;
|
|
14
|
+
background-color: #0f1322;
|
|
15
|
+
border-top-color: #0b0b18;
|
|
16
|
+
border-bottom-color: #0b0b18;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.tabulator .tabulator-header .tabulator-col {
|
|
20
|
+
border-right-color: #15162f;
|
|
21
|
+
background-color: #0f1322;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter {
|
|
25
|
+
color: #0d0f20;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover {
|
|
29
|
+
background-color: #111427;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.tabulator .tabulator-row {
|
|
33
|
+
border-bottom-color: #0c0d23;
|
|
34
|
+
transition: background-color 100ms ease;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.tabulator .tabulator-row .tabulator-cell {
|
|
38
|
+
border-right-color: #11152c;
|
|
39
|
+
align-content: center;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.tabulator .tabulator-row:hover {
|
|
43
|
+
background-color: #202540 !important;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.tabulator .tabulator-row.tabulator-row-even {
|
|
47
|
+
background-color: #1c1e2c;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.tabulator .tabulator-row.tabulator-row-odd {
|
|
51
|
+
background-color: #171824;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.tabulator .tabulator-row.tabulator-selected {
|
|
55
|
+
background-color: #252a43;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input {
|
|
59
|
+
background-color: #0e1016ff;
|
|
60
|
+
color: #ccc;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input:focus {
|
|
64
|
+
background-color: #212637ff;
|
|
65
|
+
color: #fff
|
|
66
|
+
}
|