versions.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. package views
  2. import (
  3. "fmt"
  4. "reflect"
  5. "strconv"
  6. "strings"
  7. "github.com/qor5/admin/presets"
  8. "github.com/qor5/admin/presets/actions"
  9. "github.com/qor5/admin/publish"
  10. "github.com/qor5/admin/utils"
  11. . "github.com/qor5/ui/vuetify"
  12. "github.com/qor5/web"
  13. "github.com/qor5/x/i18n"
  14. "github.com/sunfmin/reflectutils"
  15. h "github.com/theplant/htmlgo"
  16. "gorm.io/gorm"
  17. )
  18. func sidePanel(db *gorm.DB, mb *presets.ModelBuilder) presets.ComponentFunc {
  19. return func(ctx *web.EventContext) h.HTMLComponent {
  20. var (
  21. msgr = i18n.MustGetModuleMessages(ctx.R, I18nPublishKey, Messages_en_US).(*Messages)
  22. activeClass = "primary white--text"
  23. selected = ctx.R.FormValue("selected")
  24. selectVersionsEvent = web.Plaid().EventFunc(selectVersionsEvent).Query(presets.ParamID, ctx.R.FormValue(presets.ParamID)).Query("selected", web.Var("$event")).Go()
  25. selectItems = []map[string]string{
  26. {"text": msgr.AllVersions, "value": "all-versions"},
  27. {"text": msgr.NamedVersions, "value": "named-versions"},
  28. }
  29. )
  30. table, currentVersion, err := versionListTable(db, mb, msgr, ctx)
  31. if err != nil || table == nil {
  32. return nil
  33. }
  34. if selected == "" {
  35. selected = "all-versions"
  36. }
  37. var onlineVersionComp h.HTMLComponent
  38. if currentVersion != nil {
  39. onlineVersionComp = VSimpleTable(h.Tbody(h.Tr(h.Td(h.Text(currentVersion.VersionName)), h.Td(h.Text(currentVersion.Status))).Class(activeClass)))
  40. }
  41. return h.Div(
  42. VCard(
  43. VCardTitle(h.Text(msgr.OnlineVersion)),
  44. onlineVersionComp,
  45. ),
  46. h.Br(),
  47. VCard(
  48. VCardTitle(
  49. h.Text(msgr.VersionsList),
  50. ).Attr("style", "padding-bottom: 0px;"),
  51. VCardText(
  52. VSelect().
  53. Items(selectItems).
  54. Value(selected).
  55. On("change", selectVersionsEvent),
  56. ).Attr("style", "padding-bottom: 0px;"),
  57. web.Portal(
  58. table,
  59. ).Name("versions-list"),
  60. ),
  61. )
  62. }
  63. }
  64. func findVersionItems(db *gorm.DB, mb *presets.ModelBuilder, ctx *web.EventContext, paramId string) (list interface{}, err error) {
  65. list = mb.NewModelSlice()
  66. primaryKeys, err := utils.GetPrimaryKeys(mb.NewModel(), db)
  67. if err != nil {
  68. return
  69. }
  70. err = utils.PrimarySluggerWhere(db.Session(&gorm.Session{NewDB: true}).Select(strings.Join(primaryKeys, ",")), mb.NewModel(), paramId, "version").
  71. Order("version DESC").
  72. Find(list).
  73. Error
  74. return list, err
  75. }
  76. type versionListTableItem struct {
  77. ID string
  78. Version string
  79. VersionName string
  80. Status string
  81. ItemClass string
  82. ParamID string
  83. }
  84. func versionListTable(db *gorm.DB, mb *presets.ModelBuilder, msgr *Messages, ctx *web.EventContext) (table h.HTMLComponent, currentVersion *versionListTableItem, err error) {
  85. var obj = mb.NewModel()
  86. slugger := obj.(presets.SlugDecoder)
  87. paramID := ctx.R.FormValue(presets.ParamID)
  88. if paramID == "" {
  89. return nil, nil, nil
  90. }
  91. cs := slugger.PrimaryColumnValuesBySlug(paramID)
  92. id, currentVersionName := cs["id"], cs["version"]
  93. if id == "" || currentVersionName == "" {
  94. return nil, nil, fmt.Errorf("invalid version id: %s", paramID)
  95. }
  96. var (
  97. versions []*versionListTableItem
  98. namedVersions []*versionListTableItem
  99. activeClass = "vx-list-item--active primary--text"
  100. selected = ctx.R.FormValue("selected")
  101. page = ctx.R.FormValue("page")
  102. currentPage = 1
  103. )
  104. if page != "" {
  105. if p, err := strconv.Atoi(page); err == nil {
  106. currentPage = p
  107. }
  108. }
  109. var results = mb.NewModelSlice()
  110. primaryKeys, err := utils.GetPrimaryKeys(mb.NewModel(), db)
  111. if err != nil {
  112. return
  113. }
  114. err = utils.PrimarySluggerWhere(db.Session(&gorm.Session{NewDB: true}).Select(strings.Join(append(primaryKeys, "version_name", "status"), ",")), mb.NewModel(), paramID, "version").
  115. Order("version DESC").
  116. Find(results).Error
  117. if err != nil {
  118. panic(err)
  119. }
  120. vO := reflect.ValueOf(results).Elem()
  121. for i := 0; i < vO.Len(); i++ {
  122. v := vO.Index(i).Interface()
  123. version := &versionListTableItem{}
  124. ID, _ := reflectutils.Get(v, "ID")
  125. version.ID = fmt.Sprintf("%v", ID)
  126. version.Version = v.(publish.VersionInterface).GetVersion()
  127. version.VersionName = v.(publish.VersionInterface).GetVersionName()
  128. version.Status = v.(publish.StatusInterface).GetStatus()
  129. if version.Status == publish.StatusOnline {
  130. currentVersion = version
  131. }
  132. version.Status = GetStatusText(version.Status, msgr)
  133. if version.Version == currentVersionName {
  134. version.ItemClass = activeClass
  135. }
  136. if version.VersionName == "" {
  137. version.VersionName = version.Version
  138. }
  139. if version.VersionName != version.Version {
  140. namedVersions = append(namedVersions, version)
  141. }
  142. version.ParamID = v.(presets.SlugEncoder).PrimarySlug()
  143. versions = append(versions, version)
  144. }
  145. if selected == "named-versions" {
  146. versions = namedVersions
  147. }
  148. var (
  149. swithVersionEvent = web.Plaid().EventFunc(switchVersionEvent).Query(presets.ParamID, web.Var(`$event.ParamID`)).Query("selected", selected).Query("page", web.Var("locals.versionPage")).Go()
  150. deleteVersionEvent = web.Plaid().EventFunc(actions.DeleteConfirmation).Query(presets.ParamID, web.Var(`props.item.ParamID`)).
  151. Query(presets.ParamAfterDeleteEvent, afterDeleteVersionEvent).
  152. Query("current_selected_id", ctx.R.FormValue(presets.ParamID)).
  153. Query("selected", selected).
  154. Query("page", web.Var("locals.versionPage")).
  155. Go() + ";event.stopPropagation();"
  156. renameVersionEvent = web.Plaid().EventFunc(renameVersionEvent).Query(presets.ParamID, web.Var(`props.item.ParamID`)).Query("name", web.Var("props.item.VersionName")).Go()
  157. )
  158. table = web.Scope(
  159. VDataTable(
  160. web.Slot(
  161. VEditDialog(
  162. VIcon("edit").Small(true).Class("mr-2").Attr(":class", "props.item.ItemClass"),
  163. VIcon("delete").Small(true).Class("mr-2").Attr("@click", deleteVersionEvent).Attr(":class", "props.item.ItemClass"),
  164. web.Slot(
  165. VTextField().Attr("v-model", "props.item.VersionName").Label(msgr.RenameVersion),
  166. ).Name("input"),
  167. ).Bind("return-value.sync", "props.item.VersionName").On("save", renameVersionEvent).Large(true).Transition("slide-x-reverse-transition"),
  168. ).Name("item.Actions").Scope("props"),
  169. ).
  170. Items(versions).
  171. Headers(
  172. []map[string]interface{}{
  173. {"text": "VersionName", "value": "VersionName"},
  174. {"text": "Status", "value": "Status"},
  175. {"text": "Actions", "value": "Actions"},
  176. }).
  177. HideDefaultHeader(true).
  178. HideDefaultFooter(len(versions) <= 10).
  179. On("click:row", swithVersionEvent).
  180. On("pagination", "locals.versionPage = $event.page").
  181. ItemClass("ItemClass").
  182. FooterProps(
  183. map[string]interface{}{
  184. "items-per-page-options": []int{5, 10, 20},
  185. "show-first-last-page": true,
  186. "items-per-page-text": "",
  187. "page-text": "",
  188. },
  189. ).
  190. Page(currentPage),
  191. ).Init(fmt.Sprintf(`{versionPage: %d}`, currentPage)).
  192. VSlot("{ locals }")
  193. return table, currentVersion, nil
  194. }
  195. func switchVersionAction(db *gorm.DB, mb *presets.ModelBuilder, publisher *publish.Builder) web.EventFunc {
  196. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  197. paramId := ctx.R.FormValue(presets.ParamID)
  198. eb := mb.Editing()
  199. obj := mb.NewModel()
  200. obj, err = eb.Fetcher(obj, paramId, ctx)
  201. eb.UpdateOverlayContent(ctx, &r, obj, "", err)
  202. if ctx.Queries().Get("no_msg") == "true" {
  203. return
  204. }
  205. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPublishKey, Messages_en_US).(*Messages)
  206. presets.ShowMessage(&r, msgr.SwitchedToNewVersion, "")
  207. return
  208. }
  209. }
  210. func saveNewVersionAction(db *gorm.DB, mb *presets.ModelBuilder, publisher *publish.Builder) web.EventFunc {
  211. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  212. var toObj = mb.NewModel()
  213. slugger := toObj.(presets.SlugDecoder)
  214. currentVersionName := slugger.PrimaryColumnValuesBySlug(ctx.R.FormValue(presets.ParamID))["version"]
  215. paramID := ctx.R.FormValue(presets.ParamID)
  216. me := mb.Editing()
  217. vErr := me.RunSetterFunc(ctx, false, toObj)
  218. if vErr.HaveErrors() {
  219. me.UpdateOverlayContent(ctx, &r, toObj, "", &vErr)
  220. return
  221. }
  222. var fromObj = mb.NewModel()
  223. utils.PrimarySluggerWhere(db, mb.NewModel(), paramID).First(fromObj)
  224. if err = utils.SetPrimaryKeys(fromObj, toObj, db, paramID); err != nil {
  225. return
  226. }
  227. if err = reflectutils.Set(toObj, "Version.ParentVersion", currentVersionName); err != nil {
  228. return
  229. }
  230. if me.Validator != nil {
  231. if vErr := me.Validator(toObj, ctx); vErr.HaveErrors() {
  232. me.UpdateOverlayContent(ctx, &r, toObj, "", &vErr)
  233. return
  234. }
  235. }
  236. if err = me.Saver(toObj, paramID, ctx); err != nil {
  237. me.UpdateOverlayContent(ctx, &r, toObj, "", err)
  238. return
  239. }
  240. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPublishKey, Messages_en_US).(*Messages)
  241. presets.ShowMessage(&r, msgr.SuccessfullyCreated, "")
  242. if ctx.R.URL.Query().Get(presets.ParamInDialog) == "true" {
  243. web.AppendVarsScripts(&r,
  244. "vars.presetsDialog = false",
  245. web.Plaid().
  246. URL(ctx.R.RequestURI).
  247. EventFunc(actions.UpdateListingDialog).
  248. StringQuery(ctx.R.URL.Query().Get(presets.ParamListingQueries)).
  249. Go(),
  250. )
  251. } else {
  252. r.Reload = true
  253. }
  254. return
  255. }
  256. }
  257. func duplicateVersionAction(db *gorm.DB, mb *presets.ModelBuilder, publisher *publish.Builder) web.EventFunc {
  258. return func(ctx *web.EventContext) (r web.EventResponse, err error) {
  259. var toObj = mb.NewModel()
  260. slugger := toObj.(presets.SlugDecoder)
  261. currentVersionName := slugger.PrimaryColumnValuesBySlug(ctx.R.FormValue(presets.ParamID))["version"]
  262. paramID := ctx.R.FormValue(presets.ParamID)
  263. me := mb.Editing()
  264. var fromObj = mb.NewModel()
  265. if err = utils.PrimarySluggerWhere(db, mb.NewModel(), paramID).First(fromObj).Error; err != nil {
  266. return
  267. }
  268. if err = utils.SetPrimaryKeys(fromObj, toObj, db, paramID); err != nil {
  269. presets.ShowMessage(&r, err.Error(), "error")
  270. return
  271. }
  272. if vErr := me.SetObjectFields(fromObj, toObj, &presets.FieldContext{
  273. ModelInfo: mb.Info(),
  274. }, false, presets.ContextModifiedIndexesBuilder(ctx).FromHidden(ctx.R), ctx); vErr.HaveErrors() {
  275. presets.ShowMessage(&r, vErr.Error(), "error")
  276. return
  277. }
  278. if err = reflectutils.Set(toObj, "Version.ParentVersion", currentVersionName); err != nil {
  279. presets.ShowMessage(&r, err.Error(), "error")
  280. return
  281. }
  282. if me.Validator != nil {
  283. if vErr := me.Validator(toObj, ctx); vErr.HaveErrors() {
  284. presets.ShowMessage(&r, vErr.Error(), "error")
  285. return
  286. }
  287. }
  288. if err = me.Saver(toObj, paramID, ctx); err != nil {
  289. presets.ShowMessage(&r, err.Error(), "error")
  290. return
  291. }
  292. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPublishKey, Messages_en_US).(*Messages)
  293. presets.ShowMessage(&r, msgr.SuccessfullyCreated, "")
  294. se := toObj.(presets.SlugEncoder)
  295. newQueries := ctx.Queries()
  296. newQueries.Del(presets.ParamID)
  297. r.PushState = web.Location(newQueries).URL(mb.Info().DetailingHref(se.PrimarySlug()))
  298. return
  299. }
  300. }
  301. func searcher(db *gorm.DB, mb *presets.ModelBuilder) presets.SearchFunc {
  302. return func(obj interface{}, params *presets.SearchParams, ctx *web.EventContext) (r interface{}, totalCount int, err error) {
  303. ilike := "ILIKE"
  304. if db.Dialector.Name() == "sqlite" {
  305. ilike = "LIKE"
  306. }
  307. wh := db.Model(obj)
  308. if len(params.KeywordColumns) > 0 && len(params.Keyword) > 0 {
  309. var segs []string
  310. var args []interface{}
  311. for _, c := range params.KeywordColumns {
  312. segs = append(segs, fmt.Sprintf("%s %s ?", c, ilike))
  313. args = append(args, fmt.Sprintf("%%%s%%", params.Keyword))
  314. }
  315. wh = wh.Where(strings.Join(segs, " OR "), args...)
  316. }
  317. for _, cond := range params.SQLConditions {
  318. wh = wh.Where(strings.Replace(cond.Query, " ILIKE ", " "+ilike+" ", -1), cond.Args...)
  319. }
  320. stmt := &gorm.Statement{DB: db}
  321. stmt.Parse(mb.NewModel())
  322. tn := stmt.Schema.Table
  323. var pks []string
  324. condition := ""
  325. var c int64
  326. for _, f := range stmt.Schema.Fields {
  327. if f.Name == "DeletedAt" {
  328. condition = "WHERE deleted_at IS NULL"
  329. }
  330. }
  331. for _, f := range stmt.Schema.PrimaryFields {
  332. if f.Name != "Version" {
  333. pks = append(pks, f.DBName)
  334. }
  335. }
  336. pkc := strings.Join(pks, ",")
  337. sql := fmt.Sprintf("(%v,version) IN (SELECT %v, MAX(version) FROM %v %v GROUP BY %v)", pkc, pkc, tn, condition, pkc)
  338. if err = wh.Where(sql).Count(&c).Error; err != nil {
  339. return
  340. }
  341. totalCount = int(c)
  342. if params.PerPage > 0 {
  343. wh = wh.Limit(int(params.PerPage))
  344. page := params.Page
  345. if page == 0 {
  346. page = 1
  347. }
  348. offset := (page - 1) * params.PerPage
  349. wh = wh.Offset(int(offset))
  350. }
  351. orderBy := params.OrderBy
  352. if len(orderBy) > 0 {
  353. wh = wh.Order(orderBy)
  354. }
  355. if err = wh.Find(obj).Error; err != nil {
  356. return
  357. }
  358. r = reflect.ValueOf(obj).Elem().Interface()
  359. return
  360. }
  361. }
  362. func versionActionsFunc(m *presets.ModelBuilder) presets.ObjectComponentFunc {
  363. return func(obj interface{}, ctx *web.EventContext) h.HTMLComponent {
  364. gmsgr := presets.MustGetMessages(ctx.R)
  365. var buttonLabel = gmsgr.Create
  366. m.RightDrawerWidth("800")
  367. var disableUpdateBtn bool
  368. var isCreateBtn = true
  369. if ctx.R.FormValue(presets.ParamID) != "" {
  370. isCreateBtn = false
  371. buttonLabel = gmsgr.Update
  372. m.RightDrawerWidth("1200")
  373. disableUpdateBtn = m.Info().Verifier().Do(presets.PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil
  374. }
  375. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPublishKey, Messages_en_US).(*Messages)
  376. updateBtn := VBtn(buttonLabel).
  377. Color("primary").
  378. Attr("@click", web.Plaid().
  379. EventFunc(actions.Update).
  380. Queries(ctx.Queries()).
  381. Query(presets.ParamID, ctx.R.FormValue(presets.ParamID)).
  382. URL(m.Info().ListingHref()).
  383. Go(),
  384. )
  385. if disableUpdateBtn {
  386. updateBtn = updateBtn.Disabled(disableUpdateBtn)
  387. } else {
  388. updateBtn = updateBtn.Attr(":disabled", "isFetching").Attr(":loading", "isFetching")
  389. }
  390. if isCreateBtn {
  391. return h.Components(
  392. VSpacer(),
  393. updateBtn,
  394. )
  395. }
  396. saveNewVersionBtn := VBtn(msgr.SaveAsNewVersion).
  397. Color("secondary").
  398. Attr("@click", web.Plaid().
  399. EventFunc(SaveNewVersionEvent).
  400. Queries(ctx.Queries()).
  401. Query(presets.ParamID, ctx.R.FormValue(presets.ParamID)).
  402. URL(m.Info().ListingHref()).
  403. Go(),
  404. )
  405. if disableUpdateBtn {
  406. saveNewVersionBtn = saveNewVersionBtn.Disabled(disableUpdateBtn)
  407. } else {
  408. saveNewVersionBtn = saveNewVersionBtn.Attr(":disabled", "isFetching").Attr(":loading", "isFetching")
  409. }
  410. return h.Components(
  411. VSpacer(),
  412. saveNewVersionBtn,
  413. updateBtn,
  414. )
  415. }
  416. }