versions.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  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. vErr := me.RunSetterFunc(ctx, false, toObj)
  265. if vErr.HaveErrors() {
  266. presets.ShowMessage(&r, vErr.Error(), "error")
  267. return
  268. }
  269. var fromObj = mb.NewModel()
  270. utils.PrimarySluggerWhere(db, mb.NewModel(), paramID).First(fromObj)
  271. if err = utils.SetPrimaryKeys(fromObj, toObj, db, paramID); err != nil {
  272. presets.ShowMessage(&r, err.Error(), "error")
  273. return
  274. }
  275. if err = reflectutils.Set(toObj, "Version.ParentVersion", currentVersionName); err != nil {
  276. presets.ShowMessage(&r, err.Error(), "error")
  277. return
  278. }
  279. if me.Validator != nil {
  280. if vErr := me.Validator(toObj, ctx); vErr.HaveErrors() {
  281. presets.ShowMessage(&r, vErr.Error(), "error")
  282. return
  283. }
  284. }
  285. if err = me.Saver(toObj, paramID, ctx); err != nil {
  286. presets.ShowMessage(&r, err.Error(), "error")
  287. return
  288. }
  289. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPublishKey, Messages_en_US).(*Messages)
  290. presets.ShowMessage(&r, msgr.SuccessfullyCreated, "")
  291. se := toObj.(presets.SlugEncoder)
  292. newQueries := ctx.Queries()
  293. newQueries.Del(presets.ParamID)
  294. r.PushState = web.Location(newQueries).URL(mb.Info().DetailingHref(se.PrimarySlug()))
  295. return
  296. }
  297. }
  298. func searcher(db *gorm.DB, mb *presets.ModelBuilder) presets.SearchFunc {
  299. return func(obj interface{}, params *presets.SearchParams, ctx *web.EventContext) (r interface{}, totalCount int, err error) {
  300. ilike := "ILIKE"
  301. if db.Dialector.Name() == "sqlite" {
  302. ilike = "LIKE"
  303. }
  304. wh := db.Model(obj)
  305. if len(params.KeywordColumns) > 0 && len(params.Keyword) > 0 {
  306. var segs []string
  307. var args []interface{}
  308. for _, c := range params.KeywordColumns {
  309. segs = append(segs, fmt.Sprintf("%s %s ?", c, ilike))
  310. args = append(args, fmt.Sprintf("%%%s%%", params.Keyword))
  311. }
  312. wh = wh.Where(strings.Join(segs, " OR "), args...)
  313. }
  314. for _, cond := range params.SQLConditions {
  315. wh = wh.Where(strings.Replace(cond.Query, " ILIKE ", " "+ilike+" ", -1), cond.Args...)
  316. }
  317. stmt := &gorm.Statement{DB: db}
  318. stmt.Parse(mb.NewModel())
  319. tn := stmt.Schema.Table
  320. var pks []string
  321. condition := ""
  322. var c int64
  323. for _, f := range stmt.Schema.Fields {
  324. if f.Name == "DeletedAt" {
  325. condition = "WHERE deleted_at IS NULL"
  326. }
  327. }
  328. for _, f := range stmt.Schema.PrimaryFields {
  329. if f.Name != "Version" {
  330. pks = append(pks, f.DBName)
  331. }
  332. }
  333. pkc := strings.Join(pks, ",")
  334. sql := fmt.Sprintf("(%v,version) IN (SELECT %v, MAX(version) FROM %v %v GROUP BY %v)", pkc, pkc, tn, condition, pkc)
  335. if err = wh.Where(sql).Count(&c).Error; err != nil {
  336. return
  337. }
  338. totalCount = int(c)
  339. if params.PerPage > 0 {
  340. wh = wh.Limit(int(params.PerPage))
  341. page := params.Page
  342. if page == 0 {
  343. page = 1
  344. }
  345. offset := (page - 1) * params.PerPage
  346. wh = wh.Offset(int(offset))
  347. }
  348. orderBy := params.OrderBy
  349. if len(orderBy) > 0 {
  350. wh = wh.Order(orderBy)
  351. }
  352. if err = wh.Find(obj).Error; err != nil {
  353. return
  354. }
  355. r = reflect.ValueOf(obj).Elem().Interface()
  356. return
  357. }
  358. }
  359. func versionActionsFunc(m *presets.ModelBuilder) presets.ObjectComponentFunc {
  360. return func(obj interface{}, ctx *web.EventContext) h.HTMLComponent {
  361. gmsgr := presets.MustGetMessages(ctx.R)
  362. var buttonLabel = gmsgr.Create
  363. m.RightDrawerWidth("800")
  364. var disableUpdateBtn bool
  365. var isCreateBtn = true
  366. if ctx.R.FormValue(presets.ParamID) != "" {
  367. isCreateBtn = false
  368. buttonLabel = gmsgr.Update
  369. m.RightDrawerWidth("1200")
  370. disableUpdateBtn = m.Info().Verifier().Do(presets.PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil
  371. }
  372. msgr := i18n.MustGetModuleMessages(ctx.R, I18nPublishKey, Messages_en_US).(*Messages)
  373. updateBtn := VBtn(buttonLabel).
  374. Color("primary").
  375. Attr("@click", web.Plaid().
  376. EventFunc(actions.Update).
  377. Queries(ctx.Queries()).
  378. Query(presets.ParamID, ctx.R.FormValue(presets.ParamID)).
  379. URL(m.Info().ListingHref()).
  380. Go(),
  381. )
  382. if disableUpdateBtn {
  383. updateBtn = updateBtn.Disabled(disableUpdateBtn)
  384. } else {
  385. updateBtn = updateBtn.Attr(":disabled", "isFetching").Attr(":loading", "isFetching")
  386. }
  387. if isCreateBtn {
  388. return h.Components(
  389. VSpacer(),
  390. updateBtn,
  391. )
  392. }
  393. saveNewVersionBtn := VBtn(msgr.SaveAsNewVersion).
  394. Color("secondary").
  395. Attr("@click", web.Plaid().
  396. EventFunc(SaveNewVersionEvent).
  397. Queries(ctx.Queries()).
  398. Query(presets.ParamID, ctx.R.FormValue(presets.ParamID)).
  399. URL(m.Info().ListingHref()).
  400. Go(),
  401. )
  402. if disableUpdateBtn {
  403. saveNewVersionBtn = saveNewVersionBtn.Disabled(disableUpdateBtn)
  404. } else {
  405. saveNewVersionBtn = saveNewVersionBtn.Attr(":disabled", "isFetching").Attr(":loading", "isFetching")
  406. }
  407. return h.Components(
  408. VSpacer(),
  409. saveNewVersionBtn,
  410. updateBtn,
  411. )
  412. }
  413. }