mirror of
https://github.com/mudler/LocalAI.git
synced 2025-05-12 13:33:06 +00:00
feat(ui): paginate model gallery (#4886)
Some checks are pending
Explorer deployment / build-linux (push) Waiting to run
GPU tests / ubuntu-latest (1.21.x) (push) Waiting to run
generate and publish intel docker caches / generate_caches (intel/oneapi-basekit:2025.0.0-0-devel-ubuntu22.04, linux/amd64, ubuntu-latest) (push) Waiting to run
build container images / hipblas-jobs (-aio-gpu-hipblas, rocm/dev-ubuntu-22.04:6.1, hipblas, true, ubuntu:22.04, extras, latest-gpu-hipblas, latest-aio-gpu-hipblas, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -hipblas-ffmpeg) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas-core) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, false, ubuntu:22.04, extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-intel-f16, quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, true, ubuntu:22.04, extras, latest-gpu-intel-f16, latest-aio-gpu-intel-f16, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -sycl-f16-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-intel-f32, quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, true, ubuntu:22.04, extras, latest-gpu-intel-f32, latest-aio-gpu-intel-f32, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -sycl-f32-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-nvidia-cuda-11, ubuntu:22.04, cublas, 11, 7, true, extras, latest-gpu-nvidia-cuda-11, latest-aio-gpu-nvidia-cuda-11, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -cublas-cuda11-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-nvidia-cuda-12, ubuntu:22.04, cublas, 12, 0, true, extras, latest-gpu-nvidia-cuda-12, latest-aio-gpu-nvidia-cuda-12, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -cublas-cuda12-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f16-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f16-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f32-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f32-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, , , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, ) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, , true, extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, cublas, 11, 7, , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -cublas-cuda11) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, cublas, 12, 0, , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -cublas-cuda12) (push) Waiting to run
build container images / core-image-build (-aio-cpu, ubuntu:22.04, , true, core, latest-cpu, latest-aio-cpu, --jobs=4 --output-sync=target, linux/amd64,linux/arm64, arc-runner-set, false, auto, -ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 11, 7, , core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda11-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 11, 7, true, core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda11-ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 12, 0, , core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda12-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 12, 0, true, core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda12-ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, vulkan, true, core, latest-vulkan-ffmpeg-core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -vulkan-ffmpeg-core) (push) Waiting to run
build container images / gh-runner (nvcr.io/nvidia/l4t-jetpack:r36.4.0, cublas, 12, 0, true, core, latest-nvidia-l4t-arm64-core, --jobs=4 --output-sync=target, linux/arm64, ubuntu-24.04-arm, true, false, -nvidia-l4t-arm64-core) (push) Waiting to run
Security Scan / tests (push) Waiting to run
Tests extras backends / tests-transformers (push) Waiting to run
Tests extras backends / tests-rerankers (push) Waiting to run
Tests extras backends / tests-diffusers (push) Waiting to run
Tests extras backends / tests-coqui (push) Waiting to run
tests / tests-linux (1.21.x) (push) Waiting to run
tests / tests-aio-container (push) Waiting to run
tests / tests-apple (1.21.x) (push) Waiting to run
Some checks are pending
Explorer deployment / build-linux (push) Waiting to run
GPU tests / ubuntu-latest (1.21.x) (push) Waiting to run
generate and publish intel docker caches / generate_caches (intel/oneapi-basekit:2025.0.0-0-devel-ubuntu22.04, linux/amd64, ubuntu-latest) (push) Waiting to run
build container images / hipblas-jobs (-aio-gpu-hipblas, rocm/dev-ubuntu-22.04:6.1, hipblas, true, ubuntu:22.04, extras, latest-gpu-hipblas, latest-aio-gpu-hipblas, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -hipblas-ffmpeg) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas-core) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, false, ubuntu:22.04, extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-intel-f16, quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, true, ubuntu:22.04, extras, latest-gpu-intel-f16, latest-aio-gpu-intel-f16, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -sycl-f16-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-intel-f32, quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, true, ubuntu:22.04, extras, latest-gpu-intel-f32, latest-aio-gpu-intel-f32, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -sycl-f32-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-nvidia-cuda-11, ubuntu:22.04, cublas, 11, 7, true, extras, latest-gpu-nvidia-cuda-11, latest-aio-gpu-nvidia-cuda-11, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -cublas-cuda11-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-nvidia-cuda-12, ubuntu:22.04, cublas, 12, 0, true, extras, latest-gpu-nvidia-cuda-12, latest-aio-gpu-nvidia-cuda-12, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -cublas-cuda12-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f16-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f16-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f32-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f32-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, , , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, ) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, , true, extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, cublas, 11, 7, , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -cublas-cuda11) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, cublas, 12, 0, , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -cublas-cuda12) (push) Waiting to run
build container images / core-image-build (-aio-cpu, ubuntu:22.04, , true, core, latest-cpu, latest-aio-cpu, --jobs=4 --output-sync=target, linux/amd64,linux/arm64, arc-runner-set, false, auto, -ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 11, 7, , core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda11-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 11, 7, true, core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda11-ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 12, 0, , core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda12-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 12, 0, true, core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda12-ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, vulkan, true, core, latest-vulkan-ffmpeg-core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -vulkan-ffmpeg-core) (push) Waiting to run
build container images / gh-runner (nvcr.io/nvidia/l4t-jetpack:r36.4.0, cublas, 12, 0, true, core, latest-nvidia-l4t-arm64-core, --jobs=4 --output-sync=target, linux/arm64, ubuntu-24.04-arm, true, false, -nvidia-l4t-arm64-core) (push) Waiting to run
Security Scan / tests (push) Waiting to run
Tests extras backends / tests-transformers (push) Waiting to run
Tests extras backends / tests-rerankers (push) Waiting to run
Tests extras backends / tests-diffusers (push) Waiting to run
Tests extras backends / tests-coqui (push) Waiting to run
tests / tests-linux (1.21.x) (push) Waiting to run
tests / tests-aio-container (push) Waiting to run
tests / tests-apple (1.21.x) (push) Waiting to run
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
parent
5b59b5e0c1
commit
e9971b168a
@ -114,7 +114,7 @@ func FindModel(models []*GalleryModel, name string, basePath string) *GalleryMod
|
|||||||
// List available models
|
// List available models
|
||||||
// Models galleries are a list of yaml files that are hosted on a remote server (for example github).
|
// Models galleries are a list of yaml files that are hosted on a remote server (for example github).
|
||||||
// Each yaml file contains a list of models that can be downloaded and optionally overrides to define a new model setting.
|
// Each yaml file contains a list of models that can be downloaded and optionally overrides to define a new model setting.
|
||||||
func AvailableGalleryModels(galleries []config.Gallery, basePath string) ([]*GalleryModel, error) {
|
func AvailableGalleryModels(galleries []config.Gallery, basePath string) (GalleryModels, error) {
|
||||||
var models []*GalleryModel
|
var models []*GalleryModel
|
||||||
|
|
||||||
// Get models from galleries
|
// Get models from galleries
|
||||||
|
@ -62,3 +62,15 @@ func (gm GalleryModels) FindByName(name string) *GalleryModel {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gm GalleryModels) Paginate(pageNum int, itemsNum int) GalleryModels {
|
||||||
|
start := (pageNum - 1) * itemsNum
|
||||||
|
end := start + itemsNum
|
||||||
|
if start > len(gm) {
|
||||||
|
start = len(gm)
|
||||||
|
}
|
||||||
|
if end > len(gm) {
|
||||||
|
end = len(gm)
|
||||||
|
}
|
||||||
|
return gm[start:end]
|
||||||
|
}
|
||||||
|
@ -3,7 +3,9 @@ package routes
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mudler/LocalAI/core/config"
|
"github.com/mudler/LocalAI/core/config"
|
||||||
@ -126,6 +128,8 @@ func RegisterUIRoutes(app *fiber.App,
|
|||||||
// Show the Models page (all models)
|
// Show the Models page (all models)
|
||||||
app.Get("/browse", func(c *fiber.Ctx) error {
|
app.Get("/browse", func(c *fiber.Ctx) error {
|
||||||
term := c.Query("term")
|
term := c.Query("term")
|
||||||
|
page := c.Query("page")
|
||||||
|
items := c.Query("items")
|
||||||
|
|
||||||
models, _ := gallery.AvailableGalleryModels(appConfig.Galleries, appConfig.ModelPath)
|
models, _ := gallery.AvailableGalleryModels(appConfig.Galleries, appConfig.ModelPath)
|
||||||
|
|
||||||
@ -164,6 +168,54 @@ func RegisterUIRoutes(app *fiber.App,
|
|||||||
// "ApplicationConfig": appConfig,
|
// "ApplicationConfig": appConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if page == "" {
|
||||||
|
page = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if page != "" {
|
||||||
|
log.Debug().Msgf("page : %+v\n", page)
|
||||||
|
// return a subset of the models
|
||||||
|
pageNum, err := strconv.Atoi(page)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString("Invalid page number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pageNum == 0 {
|
||||||
|
return c.Render("views/models", summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsNum, err := strconv.Atoi(items)
|
||||||
|
if err != nil {
|
||||||
|
itemsNum = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPages := int(math.Ceil(float64(len(models)) / float64(itemsNum)))
|
||||||
|
|
||||||
|
models = models.Paginate(pageNum, itemsNum)
|
||||||
|
|
||||||
|
log.Debug().Msgf("number of models : %+v\n", len(models))
|
||||||
|
prevPage := pageNum - 1
|
||||||
|
nextPage := pageNum + 1
|
||||||
|
if prevPage < 1 {
|
||||||
|
prevPage = 1
|
||||||
|
}
|
||||||
|
if nextPage > totalPages {
|
||||||
|
nextPage = totalPages
|
||||||
|
}
|
||||||
|
if prevPage != pageNum {
|
||||||
|
summary["PrevPage"] = prevPage
|
||||||
|
}
|
||||||
|
summary["NextPage"] = nextPage
|
||||||
|
summary["TotalPages"] = totalPages
|
||||||
|
summary["CurrentPage"] = pageNum
|
||||||
|
summary["Models"] = template.HTML(elements.ListModels(models, processingModels, galleryService))
|
||||||
|
|
||||||
|
log.Debug().Msgf("totalPages : %+v\n", totalPages)
|
||||||
|
log.Debug().Msgf("prevPage : %+v\n", prevPage)
|
||||||
|
log.Debug().Msgf("nextPage : %+v\n", nextPage)
|
||||||
|
log.Debug().Msgf("CurrentPage : %+v\n", pageNum)
|
||||||
|
}
|
||||||
|
|
||||||
// Render index
|
// Render index
|
||||||
return c.Render("views/models", summary)
|
return c.Render("views/models", summary)
|
||||||
})
|
})
|
||||||
@ -171,6 +223,9 @@ func RegisterUIRoutes(app *fiber.App,
|
|||||||
// Show the models, filtered from the user input
|
// Show the models, filtered from the user input
|
||||||
// https://htmx.org/examples/active-search/
|
// https://htmx.org/examples/active-search/
|
||||||
app.Post("/browse/search/models", func(c *fiber.Ctx) error {
|
app.Post("/browse/search/models", func(c *fiber.Ctx) error {
|
||||||
|
page := c.Query("page")
|
||||||
|
items := c.Query("items")
|
||||||
|
|
||||||
form := struct {
|
form := struct {
|
||||||
Search string `form:"search"`
|
Search string `form:"search"`
|
||||||
}{}
|
}{}
|
||||||
@ -180,7 +235,26 @@ func RegisterUIRoutes(app *fiber.App,
|
|||||||
|
|
||||||
models, _ := gallery.AvailableGalleryModels(appConfig.Galleries, appConfig.ModelPath)
|
models, _ := gallery.AvailableGalleryModels(appConfig.Galleries, appConfig.ModelPath)
|
||||||
|
|
||||||
return c.SendString(elements.ListModels(gallery.GalleryModels(models).Search(form.Search), processingModels, galleryService))
|
if page != "" {
|
||||||
|
// return a subset of the models
|
||||||
|
pageNum, err := strconv.Atoi(page)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).SendString("Invalid page number")
|
||||||
|
}
|
||||||
|
|
||||||
|
itemsNum, err := strconv.Atoi(items)
|
||||||
|
if err != nil {
|
||||||
|
itemsNum = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
models = models.Paginate(pageNum, itemsNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.Search != "" {
|
||||||
|
models = models.Search(form.Search)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.SendString(elements.ListModels(models, processingModels, galleryService))
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -75,9 +75,37 @@
|
|||||||
hx-indicator=".htmx-indicator">
|
hx-indicator=".htmx-indicator">
|
||||||
|
|
||||||
<div id="search-results">{{.Models}}</div>
|
<div id="search-results">{{.Models}}</div>
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="flex justify-center mt-5">
|
||||||
|
<div class="flex items
|
||||||
|
-center">
|
||||||
|
<button onclick="window.location.href='browse?page={{.PrevPage}}'" class="bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-gray-200 px-3 py-1 rounded-l-md" {{if not .PrevPage}}disabled{{end}}
|
||||||
|
><i class="fas fa-arrow-left"></i></button>
|
||||||
|
<button onclick="window.location.href='browse?page={{.NextPage}}'" class="bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-gray-200 px-3 py-1 rounded-r-md" {{if not .NextPage}}disabled{{end}}
|
||||||
|
><i class="fas fa-arrow-right"></i></button>
|
||||||
|
<!--
|
||||||
|
TODO: do not refresh the page, but use htmx.
|
||||||
|
This however requires the page calculation to be here instead that in the golang backend.
|
||||||
|
<button class="bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-gray-200 px-3 py-1 rounded-l-md"
|
||||||
|
hx-post="browse/search/models?page={{.PrevPage}}"
|
||||||
|
hx-target="#search-results"
|
||||||
|
hx-indicator=".htmx-indicator"
|
||||||
|
{{if not .PrevPage}}disabled{{end}}
|
||||||
|
>
|
||||||
|
<i class="fas fa-arrow-left"></i>
|
||||||
|
</button>
|
||||||
|
<button class="bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-gray-200 px-3 py-1 rounded-r-md"
|
||||||
|
hx-post="browse/search/models?page={{.NextPage}}"
|
||||||
|
hx-target="#search-results"
|
||||||
|
hx-indicator=".htmx-indicator"
|
||||||
|
{{if not .NextPage}}disabled{{end}}
|
||||||
|
>
|
||||||
|
<i class="fas fa-arrow-right"></i>
|
||||||
|
</button>
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{template "views/partials/footer" .}}
|
{{template "views/partials/footer" .}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user