waibu-db 2.21.3 → 2.23.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.
@@ -20,25 +20,25 @@ async function table () {
20
20
  }
21
21
 
22
22
  build = async () => {
23
- const { req } = this.component
23
+ const { req, buildTag, buildSentence, buildUrl, locals = {} } = this.component
24
24
  const { escape, attrToArray } = this.app.waibu
25
25
  const { groupAttrs } = this.app.waibuMpa
26
26
  const { isHtmlLink } = this.app.bajoExtra
27
27
  const { getTruncated } = this.app.bajoTemplate
28
- const { get, omit, set, find, isEmpty, without, merge, intersection } = this.app.lib._
28
+ const { get, omit, set, find, isEmpty, without, merge, intersection, isPlainObject } = this.app.lib._
29
29
  const { isSet } = this.app.lib.aneka
30
30
  const group = groupAttrs(this.params.attr, ['body', 'head', 'foot'])
31
31
  this.params.attr = group._
32
32
  const prettyUrl = this.params.attr.prettyUrl
33
33
 
34
- const schema = get(this, 'component.locals.schema', {})
35
- const data = get(this, 'component.locals.list.data', [])
36
- const filter = get(this, 'component.locals.list.filter', {})
37
- const count = get(this, 'component.locals.list.count', 0)
34
+ const schema = get(locals, 'schema', {})
35
+ const data = get(locals, 'list.data', [])
36
+ const filter = get(locals, 'list.filter', {})
37
+ const count = get(locals, 'list.count', 0)
38
38
  if (count === 0 || data.length === 0) {
39
39
  const alert = '<c:alert color="warning" t:content="noRecordFound" margin="top-4"/>'
40
40
  this.params.noTag = true
41
- this.params.html = await this.component.buildSentence(alert)
41
+ this.params.html = await buildSentence(alert)
42
42
  return
43
43
  }
44
44
  const disableds = get(schema, 'view.disabled', [])
@@ -47,12 +47,12 @@ async function table () {
47
47
  return
48
48
  }
49
49
  const qsKey = this.app.waibu.config.qsKey
50
- let fields = without(get(this, `component.locals._meta.query.${qsKey.fields}`, '').split(','), '')
50
+ let fields = without(get(locals, `_meta.query.${qsKey.fields}`, '').split(','), '')
51
51
  if (isEmpty(fields)) fields = without(schema.view.fields, 'id')
52
52
  if (data.length > 0) {
53
53
  fields = intersection(fields, Object.keys(data[0]))
54
54
  }
55
- let sort = this.params.attr.sort ? attrToArray(this.params.attr.sort) : get(this, `component.locals._meta.query.${qsKey.sort}`, '')
55
+ let sort = this.params.attr.sort ? attrToArray(this.params.attr.sort) : get(locals, `_meta.query.${qsKey.sort}`, '')
56
56
  if (isEmpty(sort) && filter.sort) {
57
57
  const keys = Object.keys(filter.sort)
58
58
  if (keys.length > 0) sort = `${keys[0]}:${filter.sort[keys[0]]}`
@@ -89,34 +89,34 @@ async function table () {
89
89
  icon = sortDir === '1' ? (this.params.attr.sortUpIcon ?? 'caretUp') : (this.params.attr.sortDownIcon ?? 'caretDown')
90
90
  }
91
91
  const item = set({ page: 1 }, qsKey.sort, sortItem)
92
- const href = this.component.buildUrl({ params: item })
92
+ const href = buildUrl({ params: item })
93
93
  const attr = this.isRightAligned(f, schema) ? { text: 'align:end' } : {}
94
94
  const content = [
95
- await this.component.buildTag({ tag: 'div', attr, html: head }),
96
- await this.component.buildTag({ tag: 'a', attr: { icon, href, noIconLink: true }, prepend: '<div class="ms-1">', append: '</div>' })
95
+ await buildTag({ tag: 'div', attr, html: head }),
96
+ await buildTag({ tag: 'a', attr: { icon, href, noIconLink: true }, prepend: '<div class="ms-1">', append: '</div>' })
97
97
  ]
98
- head = await this.component.buildTag({ tag: 'div', attr: { flex: 'justify-content:between align-items:end' }, html: content.join('\n') })
98
+ head = await buildTag({ tag: 'div', attr: { flex: 'justify-content:between align-items:end' }, html: content.join('\n') })
99
99
  }
100
100
  let text = this.params.attr.headerNowrap ? '' : 'nowrap'
101
101
  if (text === '' && this.isNoWrap(f, schema, group.body.nowrap)) text = 'nowrap'
102
102
  if (this.isRightAligned(f, schema)) text += ' align:end'
103
103
  const attr = { dataKey: f, dataType: prop.type, text }
104
- items.push(await this.component.buildTag({ tag: 'th', attr, html: head }))
104
+ items.push(await buildTag({ tag: 'th', attr, html: head }))
105
105
  }
106
106
  if (items.length > 0 && selection) {
107
107
  let item = '<th></th>'
108
108
  if (selection === 'multi') {
109
109
  const attr = { 'x-model': 'toggleAll', name: '_rtm', noWrapper: true, noLabel: true }
110
- item = await this.component.buildTag({ tag: 'formCheck', attr, prepend: '<th>', append: '</th>' })
110
+ item = await buildTag({ tag: 'formCheck', attr, prepend: '<th>', append: '</th>' })
111
111
  } else {
112
112
  const attr = { name: 'remove', '@click': 'selected = \'\'' }
113
113
  if (!disableds.includes('get')) attr.style = { cursor: 'pointer' }
114
- item = await this.component.buildTag({ tag: 'icon', attr, prepend: '<th>', append: '</th>' })
114
+ item = await buildTag({ tag: 'icon', attr, prepend: '<th>', append: '</th>' })
115
115
  }
116
116
  items.unshift(item)
117
117
  }
118
- const header = await this.component.buildTag({ tag: 'tr', html: items.join('\n') })
119
- html.push(await this.component.buildTag({ tag: 'thead', attr: group.head, html: header }))
118
+ const header = await buildTag({ tag: 'tr', html: items.join('\n') })
119
+ html.push(await buildTag({ tag: 'thead', attr: group.head, html: header }))
120
120
  // body
121
121
  items = []
122
122
  for (const idx in data) {
@@ -127,7 +127,7 @@ async function table () {
127
127
  const attr = { 'x-model': 'selected', name: '_rt', value: d.id, noLabel: true, noWrapper: true }
128
128
  const type = find(schema.properties, { name: 'id' }).type
129
129
  const prepend = `<td data-value="${d.id}" data-key="id" data-type="${type}">`
130
- lines.push(await this.component.buildTag({ tag, attr, prepend, append: '</td>' }))
130
+ lines.push(await buildTag({ tag, attr, prepend, append: '</td>' }))
131
131
  }
132
132
  for (const f of schema.view.fields) {
133
133
  if (!fields.includes(f)) continue
@@ -146,22 +146,33 @@ async function table () {
146
146
  else attr.text = `${noWrap}`
147
147
  if (d._immutable) attr.text += ' color:body-tertiary'
148
148
  const format = get(schema, `view.format.${f}`)
149
- if (format) value = await format.call(this, value, d, { params: this.params, req })
149
+ if (format) {
150
+ const formatted = await format.call(this, value, d, { params: this.params, req })
151
+ if (isPlainObject(formatted) && formatted.href) {
152
+ const text = await buildTag({ tag: 'div', attr: {}, html: formatted.value })
153
+ const link = await buildTag({ tag: 'a', attr: { text: 'color:white', icon: 'link', href: formatted.href, noIconLink: true } })
154
+ const badge = await buildTag({ tag: 'badge', attr: { text: 'background:primary', rounded: 'type:pill' }, html: link, prepend: '<div class="ms-2">', append: '</div>' })
155
+ const line = await buildTag({ tag: 'div', attr: { flex: 'justify-content:between align-items:end' }, html: `${text}\n${badge}` })
156
+ lines.push(await buildTag({ tag: 'td', attr, html: line }))
157
+ continue
158
+ }
159
+ value = formatted
160
+ }
150
161
  if (['object', 'array'].includes(prop.type) && !isHtmlLink(value)) value = getTruncated(value, 20) // TODO: should be handle by css instead
151
162
  if (!get(schema, 'view.noEscape', []).includes(f) && !isHtmlLink(value)) value = escape(value)
152
- const line = await this.component.buildTag({ tag: 'td', attr, html: value })
163
+ const line = await buildTag({ tag: 'td', attr, html: value })
153
164
  lines.push(line)
154
165
  }
155
166
  const attr = { id: `rec-${d.id}` }
156
167
  if (!disableds.includes('update') || !disableds.includes('remove') || !disableds.includes('get')) attr['@click'] = `toggle('${d.id}')`
157
168
  if (!disableds.includes('get')) attr['@dblclick'] = `goDetails('${d.id}')`
158
- items.push(await this.component.buildTag({ tag: 'tr', attr, html: lines.join('\n') }))
169
+ items.push(await buildTag({ tag: 'tr', attr, html: lines.join('\n') }))
159
170
  }
160
- html.push(await this.component.buildTag({ tag: 'tbody', attr: group.body, html: items.join('\n') }))
171
+ html.push(await buildTag({ tag: 'tbody', attr: group.body, html: items.join('\n') }))
161
172
  this.params.attr = omit(this.params.attr, ['sortUpIcon', 'sortDownIcon', 'noSort', 'selection', 'headerNowrap'])
162
173
  let xData = `
163
174
  goDetails (id) {
164
- let url = '${this.params.attr.detailsHref ?? this.component.buildUrl({ base: 'details', prettyUrl })}'
175
+ let url = '${this.params.attr.detailsHref ?? buildUrl({ base: 'details', prettyUrl })}'
165
176
  if (url === '#') return
166
177
  if (url.indexOf('/:id') > -1) url = url.replace('/:id', '/' + id)
167
178
  else url += '&id=' + id
@@ -217,7 +228,7 @@ async function table () {
217
228
  this.params.attr['x-data'] = xData
218
229
  this.params.attr['x-init'] = xInit
219
230
  this.params.attr.responsive = true
220
- this.params.html = await this.component.buildTag({ tag: 'table', attr: this.params.attr, html: html.join('\n') })
231
+ this.params.html = await buildTag({ tag: 'table', attr: this.params.attr, html: html.join('\n') })
221
232
  }
222
233
  }
223
234
  }
@@ -9,13 +9,14 @@ async function form () {
9
9
  }
10
10
 
11
11
  static async handleRw ({ attr = {}, prop = {}, widget = {} } = {}) {
12
- const { get, has } = this.app.lib._
13
- const { stringifyAttribs } = this.app.waibuMpa
12
+ const { get, has, camelCase } = this.app.lib._
13
+ // const { stringifyAttribs } = this.app.waibuMpa
14
14
  attr.dataType = prop.type
15
15
  if (has(attr, 'name') && !has(attr, 'value')) {
16
16
  attr.value = widget.component === 'form-plaintext' ? get(this, `oldData.${attr.name}`, attr.dataValue) : attr.dataValue
17
17
  }
18
- return `<c:${widget.component} ${stringifyAttribs(attr)} />`
18
+ return await this.component.buildTag({ tag: camelCase(widget.component), attr, addons: widget.addons, selfCosing: true, noEscape: true }, { prop, widget })
19
+ // return `<c:${widget.component} ${stringifyAttribs(attr)} />`
19
20
  }
20
21
 
21
22
  build = async () => {
@@ -3,10 +3,13 @@ const action = {
3
3
  title: 'Model Database',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
+ const { pascalCase } = this.app.lib.aneka
6
7
  const handler = await importModule('waibuDb:/lib/crud/all-handler.js')
7
8
  const { model, action } = req.params
8
9
  const template = `waibuDb.template:/crud/${action}.html`
9
10
  const params = { page: { layout: `waibuAdmin.layout:/crud/${action === 'list' ? 'wide' : 'default'}.html` } }
11
+ const models = this.app.waibuDb.getAutoModels().map(m => m.name)
12
+ if (!models.includes(pascalCase(model))) throw this.error('_notFound')
10
13
  return handler.call(this, { model, req, reply, action, params, template })
11
14
  }
12
15
  }
@@ -0,0 +1,11 @@
1
+ async function index (req, reply) {
2
+ const { kebabCase } = this.app.lib._
3
+ const plugin = this.app.waibuDb
4
+ const models = plugin.getAutoModels().map(m => m.name)
5
+ const prefix = this.app.waibu.getPluginPrefix(plugin.ns)
6
+ const params = { model: kebabCase(models[0]), action: 'list' }
7
+ if (models.length > 0) return reply.redirectTo(`waibuAdmin:/${prefix}/:model/:action`, { params })
8
+ throw this.error('_notFound')
9
+ }
10
+
11
+ export default index
package/index.js CHANGED
@@ -100,13 +100,9 @@ async function factory (pkgName) {
100
100
  }
101
101
  }
102
102
 
103
- adminMenu = async (locals, req) => {
104
- const { getPluginPrefix } = this.app.waibu
103
+ getAutoModels = () => {
104
+ const { filter, get, map, isArray } = this.app.lib._
105
105
  const { pascalCase } = this.app.lib.aneka
106
- const { getPluginTitle } = this.app.waibuMpa
107
- const { camelCase, map, groupBy, keys, kebabCase, filter, get, isArray } = this.app.lib._
108
-
109
- const prefix = getPluginPrefix(this.ns)
110
106
  const allModels = this.app.dobo.models
111
107
  const models = filter(allModels, s => {
112
108
  const byModelFind = !s.disabled.includes('find')
@@ -117,6 +113,16 @@ async function factory (pkgName) {
117
113
  const byDbDisabled = !modelDisabled.includes(s.name)
118
114
  return byModelFind && byDbDisabled
119
115
  })
116
+ return models
117
+ }
118
+
119
+ adminMenu = async (locals, req) => {
120
+ const { getPluginPrefix } = this.app.waibu
121
+ const { getPluginTitle } = this.app.waibuMpa
122
+ const { camelCase, map, groupBy, keys, kebabCase } = this.app.lib._
123
+
124
+ const prefix = getPluginPrefix(this.ns)
125
+ const models = this.getAutoModels()
120
126
  const omenu = groupBy(map(models, s => {
121
127
  const item = { name: s.name, ns: s.plugin.ns }
122
128
  item.nsTitle = getPluginTitle(s.plugin.ns, req)
@@ -83,13 +83,15 @@ function customLayout ({ action, schema, ext, layout, readonly }) {
83
83
  }
84
84
  }
85
85
 
86
- function applyLayout (action, schema, ext) {
86
+ async function applyLayout (action, schema, ext, options) {
87
87
  const { defaultsDeep } = this.app.lib.aneka
88
88
  const { set, get, isEmpty, find } = this.app.lib._
89
89
  const { fields, card } = getCommons.call(this, action, schema, ext)
90
90
  const layout = get(ext, `view.${action}.layout`, get(ext, 'common.layout', []))
91
91
  const readonly = get(ext, `view.${action}.readonly`, get(ext, 'common.readonly', defReadonly))
92
92
  const widget = {}
93
+ let { req, model } = (options.args ?? [])[0] ?? {}
94
+ if (!model) model = this.app.dobo.getModel(schema.name)
93
95
  for (const f of fields) {
94
96
  const prop = find(schema.properties, { name: f })
95
97
  if (!prop) continue
@@ -123,6 +125,7 @@ function applyLayout (action, schema, ext) {
123
125
  }
124
126
  }
125
127
  if (prop.values) {
128
+ const values = await model.buildPropValues(prop, { req })
126
129
  result.component = orgCmp ?? 'form-select'
127
130
  if (prop.type === 'array') {
128
131
  result.component = 'form-select-ext'
@@ -130,8 +133,7 @@ function applyLayout (action, schema, ext) {
130
133
  result.attr.removeBtn = true
131
134
  delete result.attr.allowCreate
132
135
  }
133
- if (typeof prop.values === 'string') result.attr.options = prop.values
134
- else result.attr.options = prop.values.map(item => `${item.value}:${item.text}`).join(';')
136
+ result.attr.options = values.map(item => `${item.value}:${item.text}`).join(';')
135
137
  if (result.attr.options.split(';').length > 8 && !orgCmp) result.component = 'form-select-ext'
136
138
  }
137
139
  if (['string', 'text'].includes(prop.type) && prop.maxLength) set(result, 'attr.maxlength', prop.maxLength)
@@ -176,16 +178,16 @@ const handler = {
176
178
  set(schema, 'view.qs.fields', qsFields.join(','))
177
179
  },
178
180
  details: async function (schema, ext, options) {
179
- applyLayout.call(this, 'details', schema, ext, options)
181
+ await applyLayout.call(this, 'details', schema, ext, options)
180
182
  },
181
183
  add: async function (schema, ext, options) {
182
- applyLayout.call(this, 'add', schema, ext, options)
184
+ await applyLayout.call(this, 'add', schema, ext, options)
183
185
  },
184
186
  edit: async function (schema, ext, options) {
185
- applyLayout.call(this, 'edit', schema, ext, options)
187
+ await applyLayout.call(this, 'edit', schema, ext, options)
186
188
  },
187
189
  delete: async function (schema, ext, options) {
188
- applyLayout.call(this, 'delete', schema, ext, options)
190
+ await applyLayout.call(this, 'delete', schema, ext, options)
189
191
  }
190
192
  }
191
193
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu-db",
3
- "version": "2.21.3",
3
+ "version": "2.23.0",
4
4
  "description": "DB Helper",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,8 +1,18 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-06-03
4
+
5
+ - [2.23.0] Add ```getAutoModels()```
6
+ - [2.23.0] Display link in ```data-table``` widget as separate link inside a ```badge```
7
+ - [2.23.0] Bug fix in ```form.js```
8
+ - [2.23.0] Add folder redirection
9
+ - [2.23.0] Bug fix in model route, now correctly works for allowed models only
10
+ - [2.23.0] Bug fix in ```get-schema-ext.js```
11
+
3
12
  ## 2026-06-01
4
13
 
5
14
  - [2.21.3] Bug fix in ```wdb-form``` widget
15
+ - [2.22.0] Now use the new ```format``` definition for ```virtual``` column in ```wdb-data-table``` widget
6
16
 
7
17
  ## 2026-05-28
8
18