sumba 2.15.0 → 2.16.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.
@@ -9,9 +9,8 @@ export async function checker (modelName, id, options = {}) {
9
9
  await rebuildFilter.call(this, modelName, filter, req)
10
10
  if (filter.query.$and) filter.query.$and.push({ id })
11
11
  else filter.query.id = id
12
- filter.limit = 1
13
- const rows = await model.findRecord(filter, { count: false })
14
- if (rows.length === 0) throw this.app.dobo.error('recordNotFound%s%s', id, this.name, { statusCode: 404 })
12
+ const row = await model.findOneRecord(filter, { count: false })
13
+ if (!row) throw this.app.dobo.error('recordNotFound%s%s', id, this.name, { statusCode: 404 })
15
14
  }
16
15
 
17
16
  const doboBeforeGetRecord = {
@@ -95,12 +95,17 @@
95
95
  "ticket": "Ticket",
96
96
  "ticketCat": "Ticket Category",
97
97
  "supportSystem": "Support System",
98
- "management": "Management",
98
+ "team": "Team",
99
99
  "manageSite": "Manage Site",
100
- "manageUser": "Manage User",
101
100
  "manageTeam": "Manage Team",
102
- "manageTeamUser": "Manage Team Member",
103
- "manageTeamSetting": "Manage Team Setting",
101
+ "manageUser": "Manage User",
102
+ "siteProfile": "Site Profile",
103
+ "siteSetting": "Site Setting",
104
+ "userProfile": "User Profile",
105
+ "userSetting": "User Setting",
106
+ "teamProfile": "Team Profile",
107
+ "teamUser": "Team User",
108
+ "teamSetting": "Team Setting",
104
109
  "resetUserPassword": "Reset User Password",
105
110
  "unknownUser": "User Unknown/Invalid",
106
111
  "socialMedia": "Social Media",
@@ -114,7 +119,6 @@
114
119
  "statusUnverified": "Unverified",
115
120
  "statusActive": "Active",
116
121
  "statusInactive": "Inactive",
117
- "siteSetting": "Site Settings",
118
122
  "validCountryCodeRequired": "Value must be one of valid country codes",
119
123
  "aliasRequired": "Alias is required",
120
124
  "aliasOrHOstnameExists%s%s": "A site with alias '%s' or hostname '%s' exists already",
@@ -96,12 +96,17 @@
96
96
  "ticket": "Tiket",
97
97
  "ticketCat": "Kategori Tiket",
98
98
  "supportSystem": "Sistim Dukungan",
99
- "management": "Pengelolaan",
99
+ "team": "Tim",
100
100
  "manageSite": "Kelola Situs",
101
- "manageUser": "Kelola Pengguna",
102
101
  "manageTeam": "Kelola Tim",
103
- "manageTeamUser": "Kelola Anggota Tim",
104
- "manageTeamSetting": "Kelola Setelan Tim",
102
+ "manageUser": "Kelola Pengguna",
103
+ "siteProfile": "Profil Situs",
104
+ "siteSetting": "Pengaturan Situs",
105
+ "userProfile": "Profil Pengguna",
106
+ "userSetting": "Pengaturan Pengguna",
107
+ "teamProfile": "Profil Tim",
108
+ "teamUser": "Anggota Tim",
109
+ "teamSetting": "Pengaturan Tim",
105
110
  "resetUserPassword": "Reset Kata Sandi Pengguna",
106
111
  "unknownUser": "Pengguna Tidak Dikenal/Tidak Valid",
107
112
  "socialMedia": "Media Sosial",
@@ -115,7 +120,6 @@
115
120
  "statusUnverified": "Belum Terverifikasi",
116
121
  "statusActive": "Aktif",
117
122
  "statusInactive": "Non Aktif",
118
- "siteSetting": "Setelan Situs",
119
123
  "validCountryCodeRequired": "Nilai harus salah satu dari kode negara yang berlaku",
120
124
  "aliasRequired": "Alias tidak boleh kosong",
121
125
  "aliasOrHOstnameExists%s%s": "Situs dengan alias '%s' atau nama host '%s' telah ada",
@@ -5,9 +5,9 @@ async function autoInc (body, opts) {
5
5
  const query = set({}, opts.fieldName, { $regex: new RegExp('^' + body[opts.fieldName]) })
6
6
  const sort = set({}, opts.fieldName, -1)
7
7
  const options = { noHook: true, skipCache: true, thrownNotFound: false }
8
- const resp = await this.findRecord({ query, limit: 1, sort }, options)
9
- if (resp.length === 0) return body[opts.fieldName]
10
- const rslugs = resp[0][opts.fieldName].split('-')
8
+ const resp = await this.findOneRecord({ query, sort }, options)
9
+ if (resp) return body[opts.fieldName]
10
+ const rslugs = resp[opts.fieldName].split('-')
11
11
  const slugs = body[opts.fieldName].split('-')
12
12
  let num
13
13
  if (Number(last(rslugs)) && body[opts.fieldName] === rslugs.slice(0, rslugs.length - 1).join('-')) {
@@ -0,0 +1,17 @@
1
+ {
2
+ "buildLevel": 5,
3
+ "properties": [
4
+ "ns,,50,true,true",
5
+ "key,,255,true,true",
6
+ "value,text"
7
+ ],
8
+ "indexes": [{
9
+ "fields": ["ns", "key", "siteId", "userId"],
10
+ "type": "unique"
11
+ }],
12
+ "features": [
13
+ "sumba:siteId",
14
+ "dobo:updatedAt",
15
+ "sumba:userId"
16
+ ]
17
+ }
@@ -0,0 +1,51 @@
1
+ async function teamUser ({ req } = {}) {
2
+ return {
3
+ common: {
4
+ layout: [
5
+ { name: 'meta', fields: ['id', 'userId', 'createdAt', 'updatedAt'] },
6
+ { name: 'general', fields: ['ns', 'key', 'value'] }
7
+ ],
8
+ calcFields: [
9
+ { name: 'user', type: 'string' }
10
+ ],
11
+ valueFormatter: {
12
+ user: (val, rec) => {
13
+ return rec._ref.user.name
14
+ }
15
+ },
16
+ widget: {
17
+ userId: {
18
+ component: 'form-select-ext',
19
+ attr: {
20
+ remoteUrl: 'sumba.restapi:/manage/user',
21
+ remoteSearchField: 'name',
22
+ remoteLabelField: 'name',
23
+ remoteApiKey: true,
24
+ ref: 'user:name'
25
+ }
26
+ }
27
+ }
28
+ },
29
+ view: {
30
+ list: {
31
+ fields: ['user', 'ns', 'key', 'value', 'createdAt', 'updatedAt'],
32
+ stat: {
33
+ aggregate: [
34
+ { fields: ['userId'], group: 'userId', aggregate: ['count'] },
35
+ { fields: ['teamId'], group: 'teamId', aggregate: ['count'] }
36
+ ]
37
+ }
38
+ },
39
+ details: {
40
+ },
41
+ add: {
42
+ hidden: ['id', 'createdAt', 'updatedAt']
43
+ },
44
+ edit: {
45
+ readonly: ['id', 'createdAt', 'updatedAt']
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ export default teamUser
@@ -0,0 +1,73 @@
1
+ async function user ({ req } = {}) {
2
+ return {
3
+ common: {
4
+ layout: [
5
+ { name: 'meta', fields: ['id:3', 'createdAt:3', 'updatedAt:3', 'status:3'] },
6
+ { name: 'account', fields: ['username:3', 'email:3', 'provider:3', 'password:3', 'firstName:3', 'lastName:3', 'token:6'] },
7
+ { name: 'address', fields: ['address1:12', 'address2:12', 'city:6-md 8-sm', 'zipCode:2-md 4-sm', 'provinceState:4-md', 'country:6-md', 'phone:6-md', 'website:12'] },
8
+ { name: 'socialMedia', fields: ['socX:3-md 6-sm', 'socInstagram:3-md 6-sm', 'socFacebook:3-md 6-sm', 'socLinkedIn:3-md 6-sm'] }
9
+ ],
10
+ valueFormatter: {
11
+ token: async function (val, rec) {
12
+ const { hash } = this.app.bajoExtra
13
+ return await hash(rec.salt)
14
+ }
15
+ },
16
+ widget: {
17
+ country: {
18
+ component: 'form-select-ext'
19
+ },
20
+ token: {
21
+ component: 'form-plaintext'
22
+ }
23
+ }
24
+ },
25
+ view: {
26
+ list: {
27
+ qs: {
28
+ sort: 'username:1',
29
+ limit: 10
30
+ },
31
+ fields: ['createdAt', 'status', 'username', 'provider', 'email', 'firstName', 'lastName', 'city', 'zipCode', 'provinceState', 'country', 'phone'],
32
+ stat: {
33
+ aggregate: [
34
+ { fields: ['status'], group: 'status', aggregate: ['count'] },
35
+ { fields: ['provider'], group: 'provider', aggregate: ['count'] },
36
+ { fields: ['country'], group: 'country', aggregate: ['count'] }
37
+ ]
38
+ }
39
+ },
40
+ details: {
41
+ forceVisible: ['password', 'token'],
42
+ widget: {
43
+ password: {
44
+ component: 'form-plaintext',
45
+ attr: {
46
+ href: 'waibuAdmin:/site/reset-user-password?username={username}',
47
+ value: req.t('resetPassword')
48
+ }
49
+ }
50
+ }
51
+ },
52
+ add: {
53
+ forceVisible: ['password'],
54
+ hidden: ['id', 'createdAt', 'updatedAt', 'provider']
55
+ },
56
+ edit: {
57
+ forceVisible: ['password', 'token'],
58
+ widget: {
59
+ password: {
60
+ component: 'form-plaintext',
61
+ attr: {
62
+ href: 'waibuAdmin:/site/reset-user-password?username={username}',
63
+ value: req.t('resetPassword')
64
+ }
65
+ }
66
+ },
67
+ readonly: ['id', 'createdAt', 'updatedAt', 'username', 'provider']
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ export default user
@@ -1,6 +1,6 @@
1
1
  const manageSite = {
2
2
  method: ['GET', 'POST'],
3
- title: 'manageSite',
3
+ title: 'siteProfile',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const detailsHandler = await importModule('waibuDb:/lib/crud/details-handler.js')
@@ -1,6 +1,6 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'manageTeam',
3
+ title: 'teamProfile',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
@@ -1,6 +1,6 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'manageTeamSetting',
3
+ title: 'teamSetting',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
@@ -1,6 +1,6 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'manageTeamUser',
3
+ title: 'teamUser',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
@@ -1,10 +1,11 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'manageUser',
3
+ title: 'userProfile',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
7
- return await crudSkel.call(this, 'SumbaUser', req, reply)
7
+ const options = { modelOpts: { forceNoHidden: ['token'] } }
8
+ return await crudSkel.call(this, 'SumbaUser', req, reply, { options })
8
9
  }
9
10
  }
10
11
 
@@ -0,0 +1,11 @@
1
+ const action = {
2
+ method: ['GET', 'POST'],
3
+ title: 'userSetting',
4
+ handler: async function (req, reply) {
5
+ const { importModule } = this.app.bajo
6
+ const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
7
+ return await crudSkel.call(this, 'SumbaUserSetting', req, reply)
8
+ }
9
+ }
10
+
11
+ export default action
@@ -3,7 +3,7 @@ const contactForm = {
3
3
  handler: async function (req, reply) {
4
4
  const { defaultsDeep } = this.app.lib.aneka
5
5
  const { pick } = this.app.lib._
6
- const { createRecord, findRecord } = this.app.waibuDb
6
+ const { createRecord, findAllRecord } = this.app.waibuDb
7
7
 
8
8
  const def = req.user ? pick(req.user, ['firstName', 'lastName', 'email']) : {}
9
9
  const form = defaultsDeep(req.body, def)
@@ -17,7 +17,7 @@ const contactForm = {
17
17
  error = err
18
18
  }
19
19
  }
20
- const cats = await findRecord({ model: 'SumbaContactFormCat', req, options: { sort: 'level:1+name:1', limit: -1, dataOnly: true } })
20
+ const cats = await findAllRecord({ model: 'SumbaContactFormCat', req, options: { sort: 'level:1+name:1', dataOnly: true } })
21
21
  return await reply.view('sumba.template:/help/contact-form/form.html', { form, error, cats })
22
22
  }
23
23
  }
@@ -4,7 +4,7 @@ const add = {
4
4
  method: ['GET', 'POST'],
5
5
  handler: async function (req, reply) {
6
6
  const { defaultsDeep } = this.app.lib.aneka
7
- const { createRecord, findRecord } = this.app.waibuDb
7
+ const { createRecord, findAllRecord } = this.app.waibuDb
8
8
  const options = {}
9
9
  const form = defaultsDeep(req.body, {})
10
10
  let error
@@ -16,7 +16,7 @@ const add = {
16
16
  error = err
17
17
  }
18
18
  }
19
- const cats = await findRecord({ model: 'SumbaTicketCat', req, options: { sort: 'level:1+name:1', limit: -1, dataOnly: true } })
19
+ const cats = await findAllRecord({ model: 'SumbaTicketCat', req, options: { sort: 'level:1+name:1', dataOnly: true } })
20
20
  return await reply.view('sumba.template:/help/trouble-tickets/add.html', { form, error, cats })
21
21
  }
22
22
  }
@@ -4,10 +4,10 @@ const id = {
4
4
  method: ['GET', 'POST'],
5
5
  handler: async function (req, reply) {
6
6
  const { cloneDeep } = this.app.lib._
7
- const { createRecord, findRecord, getSchemaExt } = this.app.waibuDb
7
+ const { createRecord, findOneRecord, findRecord, getSchemaExt } = this.app.waibuDb
8
8
  const { schema } = await getSchemaExt(model, 'list')
9
9
 
10
- const master = (await findRecord({ model: 'SumbaTicket', req, options: { dataOnly: true, query: { id: req.params.id }, limit: 1 } }))[0]
10
+ const master = await findOneRecord({ model: 'SumbaTicket', req, options: { dataOnly: true, query: { id: req.params.id } } })
11
11
  if (!master) throw this.error('_notFound')
12
12
  const form = cloneDeep(req.body)
13
13
  let error
@@ -8,10 +8,10 @@ const userActivation = {
8
8
  if (req.method === 'POST') {
9
9
  try {
10
10
  const query = { status: 'UNVERIFIED', token: req.body.key }
11
- const result = await model.findRecord({ query, limit: 1 })
12
- if (result.length === 0) throw this.error('validationError', { details: [{ field: 'key', error: 'invalidActivationKey' }] })
11
+ const result = await model.findOneRecord({ query })
12
+ if (result) throw this.error('validationError', { details: [{ field: 'key', error: 'invalidActivationKey' }] })
13
13
  await model.transaction(async (trx) => {
14
- await model.updateRecord(result[0].id, { status: 'ACTIVE' }, { req, noValidation: true, noFlash: true, trx })
14
+ await model.updateRecord(result.id, { status: 'ACTIVE' }, { req, noValidation: true, noFlash: true, trx })
15
15
  })
16
16
  req.flash('notify', req.t('userActivated'))
17
17
  return reply.redirectTo(this.config.redirect.signin, req)
@@ -1,16 +1,15 @@
1
-
2
1
  async function getUser (req, reply) {
3
2
  const { dayjs } = this.app.lib
4
- const { findRecord } = this.app.waibuDb
3
+ const { findOneRecord } = this.app.waibuDb
5
4
  const invalidFpl = 'sumba.template:/user/fpl-invalid.html'
6
5
  if (Buffer.from(req.params.fpl, 'base64').toString('base64') !== req.params.fpl) return invalidFpl
7
6
  const fpToken = Buffer.from(req.params.fpl, 'base64').toString()
8
7
  const [token, sec] = fpToken.split(':')
9
8
  if (dayjs().unix() > Number(sec)) return invalidFpl
10
9
  const query = { token, status: 'ACTIVE' }
11
- const users = await findRecord({ model: 'SumbaUser', req, options: { query, limit: 1, dataOnly: true, noHook: true } })
12
- if (users.length === 0) return invalidFpl
13
- return users[0]
10
+ const user = await findOneRecord({ model: 'SumbaUser', req, options: { query, dataOnly: true, noHook: true } })
11
+ if (user) return invalidFpl
12
+ return user
14
13
  }
15
14
 
16
15
  const forgotPasswordLink = {
@@ -11,9 +11,8 @@ const profile = {
11
11
  if (req.method === 'POST') {
12
12
  try {
13
13
  const query = { status: 'ACTIVE', $or: [{ username: req.body.usernameEmail }, { email: req.body.usernameEmail }] }
14
- const result = await model.findRecord({ query, limit: 1 }, { dataOnly: true, noHook: true, forceNoHidden: true })
15
- if (result.length === 0) throw this.error('validationError', { details: [{ field: 'usernameEmail', error: 'unknownUsernameEmailOrInactive' }] })
16
- const data = result[0]
14
+ const data = await model.findOneRecord({ query }, { dataOnly: true, noHook: true, forceNoHidden: true })
15
+ if (data) throw this.error('validationError', { details: [{ field: 'usernameEmail', error: 'unknownUsernameEmailOrInactive' }] })
17
16
  const to = `${data.firstName} ${data.lastName} <${data.email}>`
18
17
  const subject = req.t('forgotPasswordLink')
19
18
  const options = { req, reply }
@@ -8,7 +8,7 @@ const profile = {
8
8
  const { updateRecord, getRecord } = this.app.waibuDb
9
9
  const { omit, pick } = this.app.lib._
10
10
  const { hash } = this.app.bajoExtra
11
- const resp = await getRecord({ model: 'SumbaUser', req, id: req.user.id, options: { forceNoHidden: true, noHook: true, noCache: true } })
11
+ const resp = await getRecord({ model: 'SumbaUser', req, id: req.user.id, options: { forceNoHidden: ['token'], noHook: true, noCache: true } })
12
12
  let form = defaultsDeep(req.body, omit(resp.data, ['password', 'salt']))
13
13
  form.token = await hash(form.token)
14
14
  let error
@@ -3,7 +3,7 @@ const profile = {
3
3
  handler: async function (req, reply) {
4
4
  const { hash } = this.app.bajoExtra
5
5
  const { getRecord } = this.app.waibuDb
6
- const options = { forceNoHidden: true, noHook: true, noCache: true, attachment: true, mimeType: true }
6
+ const options = { forceNoHidden: ['token'], noHook: true, noCache: true, attachment: true, mimeType: true }
7
7
  const resp = await getRecord({ model: 'SumbaUser', req, id: req.user.id, options })
8
8
  const form = resp.data
9
9
  form.token = await hash(form.salt)
package/index.js CHANGED
@@ -205,7 +205,27 @@ async function factory (pkgName) {
205
205
  if (!this.app.waibuAdmin) return
206
206
  const { getPluginPrefix } = this.app.waibu
207
207
  const prefix = getPluginPrefix(this.ns)
208
- return [{
208
+ const items = [{
209
+ title: 'manageSite',
210
+ children: [
211
+ { title: 'siteProfile', href: `waibuAdmin:/${prefix}/site` },
212
+ { title: 'siteSetting', href: `waibuAdmin:/${prefix}/site-setting/list` }
213
+ ]
214
+ }, {
215
+ title: 'manageUser',
216
+ children: [
217
+ { title: 'userProfile', href: `waibuAdmin:/${prefix}/user/list` },
218
+ { title: 'userSetting', href: `waibuAdmin:/${prefix}/user-setting/list` },
219
+ { title: 'resetUserPassword', href: `waibuAdmin:/${prefix}/reset-user-password` }
220
+ ]
221
+ }, {
222
+ title: 'manageTeam',
223
+ children: [
224
+ { title: 'teamProfile', href: `waibuAdmin:/${prefix}/team/list` },
225
+ { title: 'teamUser', href: `waibuAdmin:/${prefix}/team-user/list` },
226
+ { title: 'teamSetting', href: `waibuAdmin:/${prefix}/team-setting/list` }
227
+ ]
228
+ }, {
209
229
  title: 'supportSystem',
210
230
  children: [
211
231
  { title: 'contactForm', href: `waibuAdmin:/${prefix}/contact-form/list` },
@@ -214,17 +234,9 @@ async function factory (pkgName) {
214
234
  { title: 'ticketCat', href: `waibuAdmin:/${prefix}/ticket-cat/list` }
215
235
  ]
216
236
  }, {
217
- title: 'management',
237
+ title: 'misc',
218
238
  children: [
219
- { title: 'manageSite', href: `waibuAdmin:/${prefix}/site` },
220
- { title: 'manageUser', href: `waibuAdmin:/${prefix}/user/list` },
221
- { title: 'manageTeam', href: `waibuAdmin:/${prefix}/team/list` },
222
- { title: 'manageTeamUser', href: `waibuAdmin:/${prefix}/team-user/list` },
223
- { title: 'manageTeamSetting', href: `waibuAdmin:/${prefix}/team-setting/list` },
224
- { title: 'manageDownload', href: `waibuAdmin:/${prefix}/download/list` },
225
- { title: '-' },
226
- { title: 'siteSetting', href: `waibuAdmin:/${prefix}/site-setting/list` },
227
- { title: 'resetUserPassword', href: `waibuAdmin:/${prefix}/reset-user-password` }
239
+ { title: 'manageDownload', href: `waibuAdmin:/${prefix}/download/list` }
228
240
  ]
229
241
  }, {
230
242
  title: 'manageAllSite',
@@ -237,10 +249,14 @@ async function factory (pkgName) {
237
249
  title: 'misc',
238
250
  interSite: true,
239
251
  children: [
240
- { title: 'userSession', href: `waibuAdmin:/${prefix}/_is_/session/list` },
241
- { title: 'cacheStorage', href: `waibuAdmin:/${prefix}/_is_/cache/list` }
252
+ { title: 'userSession', href: `waibuAdmin:/${prefix}/_is_/session/list` }
242
253
  ]
243
254
  }]
255
+ if (this.app.bajoCache) {
256
+ const item = items.find(i => i.title === 'misc')
257
+ if (item) item.children.push({ title: 'cacheStorage', href: `waibuAdmin:/${prefix}/_is_/cache/list` })
258
+ }
259
+ return items
244
260
  }
245
261
 
246
262
  createJwtFromUserRecord = async (rec) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.15.0",
3
+ "version": "2.16.0",
4
4
  "description": "Biz Suite for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,11 +1,17 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-04-02
4
+
5
+ - [2.16.0] Add ```SumbaUserSetting``` model and necessary handlings
6
+ - [2.16.0] Reorganize admin menu
7
+
3
8
  ## 2026-03-30
4
9
 
5
10
  - [2.15.0] Add inter site module mechanism
6
11
  - [2.15.0] Add admin sub route for inter site modules
7
12
  - [2.15.0] Bug fix in ```createNewSite()```
8
13
  - [2.15.0] Bug fix in ```removeSite()```
14
+ - [2.15.1] Code cleanups
9
15
 
10
16
  ## 2026-03-27
11
17
 
@@ -1,60 +0,0 @@
1
- {
2
- "common": {
3
- "layout": [
4
- { "name": "meta", "fields": ["id:3", "createdAt:3", "updatedAt:3", "status:3"] },
5
- { "name": "account", "fields": ["username", "email", "provider", "password", "firstName", "lastName"] },
6
- { "name": "address", "fields": ["address1:12", "address2:12", "city:6-md 8-sm", "zipCode:2-md 4-sm", "provinceState:4-md", "country:6-md", "phone:6-md", "website:12"] },
7
- { "name": "socialMedia", "fields": ["socX:3-md 6-sm", "socInstagram:3-md 6-sm", "socFacebook:3-md 6-sm", "socLinkedIn:3-md 6-sm"] }
8
- ],
9
- "widget": {
10
- "country": {
11
- "component": "form-select-ext"
12
- }
13
- }
14
- },
15
- "view": {
16
- "list": {
17
- "qs": {
18
- "sort": "username:1",
19
- "limit": 10
20
- },
21
- "fields": ["createdAt", "status", "username", "provider", "email", "firstName", "lastName", "city", "zipCode", "provinceState", "country", "phone"],
22
- "stat": {
23
- "aggregate": [
24
- { "fields": ["status"], "group": "status", "aggregate": ["count"] },
25
- { "fields": ["provider"], "group": "provider", "aggregate": ["count"] },
26
- { "fields": ["country"], "group": "country", "aggregate": ["count"] }
27
- ]
28
- }
29
- },
30
- "details": {
31
- "forceVisible": ["password"],
32
- "widget": {
33
- "password": {
34
- "component": "form-plaintext",
35
- "attr": {
36
- "href": "waibuAdmin:/site/reset-user-password?username={username}",
37
- "value": "resetPassword"
38
- }
39
- }
40
- }
41
- },
42
- "add": {
43
- "forceVisible": ["password"],
44
- "hidden": ["id", "createdAt", "updatedAt", "provider"]
45
- },
46
- "edit": {
47
- "forceVisible": ["password"],
48
- "widget": {
49
- "password": {
50
- "component": "form-plaintext",
51
- "attr": {
52
- "href": "waibuAdmin:/site/reset-user-password?username={username}",
53
- "value": "resetPassword"
54
- }
55
- }
56
- },
57
- "readonly": ["id", "createdAt", "updatedAt", "username", "provider"]
58
- }
59
- }
60
- }