webspresso 0.0.7 → 0.0.9

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.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Dashboard Mithril.js Application
3
+ * Components and logic for the dashboard SPA
4
+ */
5
+
6
+ module.exports = `
7
+ // State
8
+ const state = {
9
+ activeTab: 'routes',
10
+ filter: 'all',
11
+ search: '',
12
+ routes: window.__DASHBOARD_DATA__.routes || [],
13
+ plugins: window.__DASHBOARD_DATA__.plugins || [],
14
+ config: window.__DASHBOARD_DATA__.config || {}
15
+ };
16
+
17
+ // Icons
18
+ const Icons = {
19
+ logo: () => m('svg', { viewBox: '0 0 24 24', fill: 'none' }, [
20
+ m('path', { d: 'M12 2L2 7L12 12L22 7L12 2Z', fill: 'currentColor', opacity: '0.3' }),
21
+ m('path', { d: 'M2 17L12 22L22 17', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' }),
22
+ m('path', { d: 'M2 12L12 17L22 12', stroke: 'currentColor', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' })
23
+ ]),
24
+ empty: () => m('svg', { viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', 'stroke-width': '1.5' }, [
25
+ m('path', { d: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z' })
26
+ ])
27
+ };
28
+
29
+ // Components
30
+ const Header = {
31
+ view: () => m('.header', [
32
+ m('h1', [
33
+ Icons.logo(),
34
+ 'Webspresso Dashboard'
35
+ ]),
36
+ m('span.dev-badge', 'development')
37
+ ])
38
+ };
39
+
40
+ const Tabs = {
41
+ view: () => m('.tabs', [
42
+ ['routes', 'Routes'],
43
+ ['plugins', 'Plugins'],
44
+ ['config', 'Config']
45
+ ].map(([id, label]) =>
46
+ m('button.tab', {
47
+ class: state.activeTab === id ? 'active' : '',
48
+ onclick: () => { state.activeTab = id; }
49
+ }, label)
50
+ ))
51
+ };
52
+
53
+ const FilterBar = {
54
+ view: () => m('.filter-bar', [
55
+ m('.filter-group', [
56
+ ['all', 'All'],
57
+ ['ssr', 'SSR'],
58
+ ['api', 'API']
59
+ ].map(([id, label]) =>
60
+ m('button.filter-btn', {
61
+ class: state.filter === id ? 'active' : '',
62
+ onclick: () => { state.filter = id; }
63
+ }, label)
64
+ )),
65
+ m('input.search-input', {
66
+ type: 'text',
67
+ placeholder: 'Search routes...',
68
+ value: state.search,
69
+ oninput: (e) => { state.search = e.target.value; }
70
+ })
71
+ ])
72
+ };
73
+
74
+ const MethodBadge = {
75
+ view: (vnode) => m('span.method-badge.method-' + vnode.attrs.method,
76
+ vnode.attrs.method.toUpperCase()
77
+ )
78
+ };
79
+
80
+ const TypeBadge = {
81
+ view: (vnode) => m('span.type-badge.type-' + vnode.attrs.type,
82
+ vnode.attrs.type.toUpperCase()
83
+ )
84
+ };
85
+
86
+ const RoutesTable = {
87
+ view: () => {
88
+ let routes = state.routes;
89
+
90
+ // Apply filter
91
+ if (state.filter !== 'all') {
92
+ routes = routes.filter(r => r.type === state.filter);
93
+ }
94
+
95
+ // Apply search
96
+ if (state.search) {
97
+ const search = state.search.toLowerCase();
98
+ routes = routes.filter(r =>
99
+ r.pattern.toLowerCase().includes(search) ||
100
+ r.file.toLowerCase().includes(search)
101
+ );
102
+ }
103
+
104
+ if (routes.length === 0) {
105
+ return m('.table-container',
106
+ m('.empty-state', [
107
+ Icons.empty(),
108
+ m('p', 'No routes found')
109
+ ])
110
+ );
111
+ }
112
+
113
+ return m('.table-container',
114
+ m('table', [
115
+ m('thead',
116
+ m('tr', [
117
+ m('th', 'Method'),
118
+ m('th', 'Path'),
119
+ m('th', 'File'),
120
+ m('th', 'Type')
121
+ ])
122
+ ),
123
+ m('tbody',
124
+ routes.map(route =>
125
+ m('tr', [
126
+ m('td', m(MethodBadge, { method: route.method })),
127
+ m('td', [
128
+ m('span.code', route.pattern),
129
+ route.isDynamic ? m('span.dynamic-indicator', '⚡ dynamic') : null
130
+ ]),
131
+ m('td', m('span.file-path', route.file)),
132
+ m('td', m(TypeBadge, { type: route.type }))
133
+ ])
134
+ )
135
+ )
136
+ ])
137
+ );
138
+ }
139
+ };
140
+
141
+ const StatsGrid = {
142
+ view: () => {
143
+ const ssrCount = state.routes.filter(r => r.type === 'ssr').length;
144
+ const apiCount = state.routes.filter(r => r.type === 'api').length;
145
+ const dynamicCount = state.routes.filter(r => r.isDynamic).length;
146
+
147
+ return m('.stats-grid', [
148
+ m('.card', [
149
+ m('.card-title', 'Total Routes'),
150
+ m('.card-value', state.routes.length)
151
+ ]),
152
+ m('.card', [
153
+ m('.card-title', 'SSR Pages'),
154
+ m('.card-value', ssrCount)
155
+ ]),
156
+ m('.card', [
157
+ m('.card-title', 'API Endpoints'),
158
+ m('.card-value', apiCount)
159
+ ]),
160
+ m('.card', [
161
+ m('.card-title', 'Dynamic Routes'),
162
+ m('.card-value', dynamicCount)
163
+ ])
164
+ ]);
165
+ }
166
+ };
167
+
168
+ const RoutesView = {
169
+ view: () => m('div', [
170
+ m(StatsGrid),
171
+ m(FilterBar),
172
+ m(RoutesTable)
173
+ ])
174
+ };
175
+
176
+ const PluginsView = {
177
+ view: () => {
178
+ if (state.plugins.length === 0) {
179
+ return m('.table-container',
180
+ m('.empty-state', [
181
+ Icons.empty(),
182
+ m('p', 'No plugins loaded')
183
+ ])
184
+ );
185
+ }
186
+
187
+ return m('.table-container',
188
+ state.plugins.map(plugin =>
189
+ m('.plugin-item', [
190
+ m('div', [
191
+ m('.plugin-name', plugin.name),
192
+ plugin.description ? m('p', { style: 'color: var(--text-secondary); font-size: 13px; margin-top: 4px;' }, plugin.description) : null
193
+ ]),
194
+ m('.plugin-version', 'v' + plugin.version)
195
+ ])
196
+ )
197
+ );
198
+ }
199
+ };
200
+
201
+ const ConfigView = {
202
+ view: () => {
203
+ const config = state.config;
204
+
205
+ return m('div', [
206
+ // Environment
207
+ m('.config-section', [
208
+ m('h3', 'Environment'),
209
+ m('.table-container',
210
+ Object.entries(config.env || {}).map(([key, value]) =>
211
+ m('.config-item', [
212
+ m('.config-key', key),
213
+ m('.config-value', String(value))
214
+ ])
215
+ )
216
+ )
217
+ ]),
218
+
219
+ // i18n
220
+ m('.config-section', [
221
+ m('h3', 'Internationalization'),
222
+ m('.table-container', [
223
+ m('.config-item', [
224
+ m('.config-key', 'Default Locale'),
225
+ m('.config-value', config.i18n?.defaultLocale || 'en')
226
+ ]),
227
+ m('.config-item', [
228
+ m('.config-key', 'Supported Locales'),
229
+ m('.config-value', (config.i18n?.supportedLocales || ['en']).join(', '))
230
+ ])
231
+ ])
232
+ ]),
233
+
234
+ // Server
235
+ m('.config-section', [
236
+ m('h3', 'Server'),
237
+ m('.table-container', [
238
+ m('.config-item', [
239
+ m('.config-key', 'Port'),
240
+ m('.config-value', config.server?.port || '3000')
241
+ ]),
242
+ m('.config-item', [
243
+ m('.config-key', 'Base URL'),
244
+ m('.config-value', config.server?.baseUrl || 'http://localhost:3000')
245
+ ])
246
+ ])
247
+ ])
248
+ ]);
249
+ }
250
+ };
251
+
252
+ const App = {
253
+ view: () => m('.dashboard', [
254
+ m(Header),
255
+ m(Tabs),
256
+ state.activeTab === 'routes' ? m(RoutesView) : null,
257
+ state.activeTab === 'plugins' ? m(PluginsView) : null,
258
+ state.activeTab === 'config' ? m(ConfigView) : null
259
+ ])
260
+ };
261
+
262
+ // Mount app
263
+ m.mount(document.getElementById('app'), App);
264
+ `;
265
+
266
+
267
+
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Webspresso Dashboard Plugin
3
+ * Development dashboard for monitoring routes, plugins, and configuration
4
+ * Uses Mithril.js for the SPA interface
5
+ */
6
+
7
+ const styles = require('./styles');
8
+ const appScript = require('./app');
9
+
10
+ /**
11
+ * Filter sensitive environment variables
12
+ */
13
+ function filterSensitiveEnv(env) {
14
+ const sensitiveKeys = ['SECRET', 'PASSWORD', 'KEY', 'TOKEN', 'CREDENTIAL', 'PRIVATE'];
15
+ const filtered = {};
16
+
17
+ for (const [key, value] of Object.entries(env)) {
18
+ const isSensitive = sensitiveKeys.some(s => key.toUpperCase().includes(s));
19
+ filtered[key] = isSensitive ? '••••••••' : value;
20
+ }
21
+
22
+ return filtered;
23
+ }
24
+
25
+ /**
26
+ * Generate the dashboard HTML
27
+ */
28
+ function generateDashboardHtml(data) {
29
+ return `<!DOCTYPE html>
30
+ <html lang="en">
31
+ <head>
32
+ <meta charset="UTF-8">
33
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
34
+ <title>Webspresso Dashboard</title>
35
+ <script src="https://unpkg.com/mithril/mithril.js"></script>
36
+ <style>${styles}</style>
37
+ </head>
38
+ <body>
39
+ <div id="app"></div>
40
+ <script>
41
+ window.__DASHBOARD_DATA__ = ${JSON.stringify(data)};
42
+ </script>
43
+ <script>${appScript}</script>
44
+ </body>
45
+ </html>`;
46
+ }
47
+
48
+ /**
49
+ * Create the dashboard plugin
50
+ * @param {Object} options - Plugin options
51
+ * @param {string} options.path - Dashboard path (default: '/_webspresso')
52
+ * @param {boolean} options.enabled - Force enable/disable (default: auto based on NODE_ENV)
53
+ */
54
+ function dashboardPlugin(options = {}) {
55
+ const {
56
+ path: dashboardPath = '/_webspresso',
57
+ enabled
58
+ } = options;
59
+
60
+ // Determine if enabled (default: only in development)
61
+ const isEnabled = enabled !== undefined
62
+ ? enabled
63
+ : process.env.NODE_ENV !== 'production';
64
+
65
+ return {
66
+ name: 'dashboard',
67
+ version: '1.0.0',
68
+ description: 'Development dashboard for monitoring routes and configuration',
69
+
70
+ /**
71
+ * Called after routes are mounted
72
+ */
73
+ onRoutesReady(ctx) {
74
+ // Skip if disabled
75
+ if (!isEnabled) {
76
+ return;
77
+ }
78
+
79
+ const { app, routes, options: appOptions } = ctx;
80
+
81
+ // Collect plugin info
82
+ const pluginManager = ctx.pluginManager || (appOptions && appOptions.pluginManager);
83
+ const plugins = [];
84
+
85
+ // Get plugins from plugin manager if available
86
+ if (pluginManager && pluginManager.plugins) {
87
+ for (const [name, plugin] of pluginManager.plugins) {
88
+ plugins.push({
89
+ name: plugin.name || name,
90
+ version: plugin.version || '0.0.0',
91
+ description: plugin.description || ''
92
+ });
93
+ }
94
+ }
95
+
96
+ // Build config data
97
+ const config = {
98
+ env: filterSensitiveEnv({
99
+ NODE_ENV: process.env.NODE_ENV || 'development',
100
+ PORT: process.env.PORT || '3000',
101
+ BASE_URL: process.env.BASE_URL || 'http://localhost:3000'
102
+ }),
103
+ i18n: {
104
+ defaultLocale: process.env.DEFAULT_LOCALE || 'en',
105
+ supportedLocales: (process.env.SUPPORTED_LOCALES || 'en').split(',')
106
+ },
107
+ server: {
108
+ port: process.env.PORT || '3000',
109
+ baseUrl: process.env.BASE_URL || 'http://localhost:3000'
110
+ }
111
+ };
112
+
113
+ // Dashboard HTML endpoint
114
+ ctx.addRoute('get', dashboardPath, (req, res) => {
115
+ const data = { routes, plugins, config };
116
+ res.type('text/html');
117
+ res.send(generateDashboardHtml(data));
118
+ });
119
+
120
+ // JSON API endpoints
121
+ ctx.addRoute('get', dashboardPath + '/api/routes', (req, res) => {
122
+ res.json(routes);
123
+ });
124
+
125
+ ctx.addRoute('get', dashboardPath + '/api/plugins', (req, res) => {
126
+ res.json(plugins);
127
+ });
128
+
129
+ ctx.addRoute('get', dashboardPath + '/api/config', (req, res) => {
130
+ res.json(config);
131
+ });
132
+
133
+ // Log dashboard URL
134
+ console.log(`\n📊 Dashboard available at: http://localhost:${process.env.PORT || 3000}${dashboardPath}\n`);
135
+ }
136
+ };
137
+ }
138
+
139
+ module.exports = dashboardPlugin;
140
+
141
+
142
+