Blank root panel, asset form as a modal, and a dedicated Statuses page

- Main panel is now blank at the top level ("Select a folder or asset
  from the tree") instead of showing a dashboard - browsing happens in
  the tree, the panel only shows something once you click into it.
  Removed the now-dead DashboardStats/Stats() machinery it depended on.
- Add/Edit Asset no longer takes over the main panel; it opens in
  #assetModal instead; asset_form.html was trimmed down to just the form
  (no more breadcrumb/panel wrapper) since the modal chrome provides the
  title and footer buttons now. The dynamic item-type-fields and submit
  listeners moved from #main-panel's delegation to the modal element's.
- Statuses split out of the General settings page into its own
  /settings/statuses page and sub-nav entry, alongside General, Item
  Types, and Users.
This commit is contained in:
Owen
2026-08-17 15:45:38 -05:00
parent 4d7ebed68e
commit 8d9106542c
12 changed files with 254 additions and 436 deletions
+7 -40
View File
@@ -1,7 +1,6 @@
package web
import (
"context"
"net/http"
"inventree/internal/assets"
@@ -12,7 +11,6 @@ import (
type InventoryPageData struct {
User *models.User
Tree []models.TreeNode
Root *RootPanelData
ItemTypes []models.ItemType
Statuses []models.Status
}
@@ -31,11 +29,6 @@ func (a *App) handleInventoryPage(w http.ResponseWriter, r *http.Request) {
writeErr(w, http.StatusInternalServerError, err.Error())
return
}
root, err := a.rootPanelData(ctx)
if err != nil {
writeErr(w, http.StatusInternalServerError, err.Error())
return
}
itemTypes, err := a.ItemTypes.List(ctx, false)
if err != nil {
writeErr(w, http.StatusInternalServerError, err.Error())
@@ -48,7 +41,7 @@ func (a *App) handleInventoryPage(w http.ResponseWriter, r *http.Request) {
}
a.Render(w, http.StatusOK, "inventory", InventoryPageData{
User: u, Tree: tree, Root: root, ItemTypes: itemTypes, Statuses: statusList,
User: u, Tree: tree, ItemTypes: itemTypes, Statuses: statusList,
})
}
@@ -112,39 +105,13 @@ func (a *App) handleTreeCollapseAll(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
// RootPanelData backs the main panel when nothing is selected: the virtual
// "Inventory" root, showing top-level folders, unfiled assets, and the
// small dashboard.
type RootPanelData struct {
Folders []models.Folder
UnfiledAssets []models.Asset
UnfiledTotal int
Stats *assets.DashboardStats
}
func (a *App) rootPanelData(ctx context.Context) (*RootPanelData, error) {
folderList, err := a.Folders.Children(ctx, nil)
if err != nil {
return nil, err
}
unfiled, total, err := a.Assets.List(ctx, assets.ListFilter{FolderSet: true, FolderID: nil, Page: 1, PageSize: 50})
if err != nil {
return nil, err
}
stats, err := a.Assets.Stats(ctx)
if err != nil {
return nil, err
}
return &RootPanelData{Folders: folderList, UnfiledAssets: unfiled, UnfiledTotal: total, Stats: stats}, nil
}
// handlePanelRoot backs the main panel when nothing is selected. It is
// intentionally blank (no dashboard/summary) - the panel only shows
// something once a folder or asset is actually clicked; browsing happens
// in the tree. The fragment still carries a data-panel-type="root" hook so
// right-click still offers "New Folder" / "Add Asset" / import / export.
func (a *App) handlePanelRoot(w http.ResponseWriter, r *http.Request) {
data, err := a.rootPanelData(r.Context())
if err != nil {
writeErr(w, http.StatusInternalServerError, err.Error())
return
}
a.RenderFragment(w, http.StatusOK, "panel_root", data)
a.RenderFragment(w, http.StatusOK, "panel_root", nil)
}
// FolderPanelData backs the main panel when a folder is selected.