waibu-db 2.22.0 → 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.
- package/extend/waibuBootstrap/theme/component/widget/data-table.js +32 -24
- package/extend/waibuBootstrap/theme/component/widget/form.js +4 -3
- package/extend/waibuMpa/extend/waibuAdmin/route/@model/@action.js +3 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/index.js +11 -0
- package/index.js +12 -6
- package/lib/method/get-schema-ext.js +9 -7
- package/package.json +1 -1
- package/wiki/CHANGES.md +9 -0
|
@@ -20,7 +20,7 @@ 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
|
|
@@ -31,14 +31,14 @@ async function table () {
|
|
|
31
31
|
this.params.attr = group._
|
|
32
32
|
const prettyUrl = this.params.attr.prettyUrl
|
|
33
33
|
|
|
34
|
-
const schema = get(
|
|
35
|
-
const data = get(
|
|
36
|
-
const filter = get(
|
|
37
|
-
const count = get(
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
92
|
+
const href = buildUrl({ params: item })
|
|
93
93
|
const attr = this.isRightAligned(f, schema) ? { text: 'align:end' } : {}
|
|
94
94
|
const content = [
|
|
95
|
-
await
|
|
96
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
114
|
+
item = await buildTag({ tag: 'icon', attr, prepend: '<th>', append: '</th>' })
|
|
115
115
|
}
|
|
116
116
|
items.unshift(item)
|
|
117
117
|
}
|
|
118
|
-
const header = await
|
|
119
|
-
html.push(await
|
|
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
|
|
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
|
|
@@ -148,23 +148,31 @@ async function table () {
|
|
|
148
148
|
const format = get(schema, `view.format.${f}`)
|
|
149
149
|
if (format) {
|
|
150
150
|
const formatted = await format.call(this, value, d, { params: this.params, req })
|
|
151
|
-
|
|
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
|
|
152
160
|
}
|
|
153
161
|
if (['object', 'array'].includes(prop.type) && !isHtmlLink(value)) value = getTruncated(value, 20) // TODO: should be handle by css instead
|
|
154
162
|
if (!get(schema, 'view.noEscape', []).includes(f) && !isHtmlLink(value)) value = escape(value)
|
|
155
|
-
const line = await
|
|
163
|
+
const line = await buildTag({ tag: 'td', attr, html: value })
|
|
156
164
|
lines.push(line)
|
|
157
165
|
}
|
|
158
166
|
const attr = { id: `rec-${d.id}` }
|
|
159
167
|
if (!disableds.includes('update') || !disableds.includes('remove') || !disableds.includes('get')) attr['@click'] = `toggle('${d.id}')`
|
|
160
168
|
if (!disableds.includes('get')) attr['@dblclick'] = `goDetails('${d.id}')`
|
|
161
|
-
items.push(await
|
|
169
|
+
items.push(await buildTag({ tag: 'tr', attr, html: lines.join('\n') }))
|
|
162
170
|
}
|
|
163
|
-
html.push(await
|
|
171
|
+
html.push(await buildTag({ tag: 'tbody', attr: group.body, html: items.join('\n') }))
|
|
164
172
|
this.params.attr = omit(this.params.attr, ['sortUpIcon', 'sortDownIcon', 'noSort', 'selection', 'headerNowrap'])
|
|
165
173
|
let xData = `
|
|
166
174
|
goDetails (id) {
|
|
167
|
-
let url = '${this.params.attr.detailsHref ??
|
|
175
|
+
let url = '${this.params.attr.detailsHref ?? buildUrl({ base: 'details', prettyUrl })}'
|
|
168
176
|
if (url === '#') return
|
|
169
177
|
if (url.indexOf('/:id') > -1) url = url.replace('/:id', '/' + id)
|
|
170
178
|
else url += '&id=' + id
|
|
@@ -220,7 +228,7 @@ async function table () {
|
|
|
220
228
|
this.params.attr['x-data'] = xData
|
|
221
229
|
this.params.attr['x-init'] = xInit
|
|
222
230
|
this.params.attr.responsive = true
|
|
223
|
-
this.params.html = await
|
|
231
|
+
this.params.html = await buildTag({ tag: 'table', attr: this.params.attr, html: html.join('\n') })
|
|
224
232
|
}
|
|
225
233
|
}
|
|
226
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
|
|
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
|
-
|
|
104
|
-
const {
|
|
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
|
-
|
|
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
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
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
|