mirror of
https://github.com/mudler/LocalAI.git
synced 2024-12-18 20:27:57 +00:00
feat(ui): move model detailed info to a modal (#4086)
* feat(ui): move model detailed info to a modal Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chore: add static asset Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
parent
c4af769d4f
commit
2c041a2077
97
core/http/elements/buttons.go
Normal file
97
core/http/elements/buttons.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package elements
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/chasefleming/elem-go"
|
||||||
|
"github.com/chasefleming/elem-go/attrs"
|
||||||
|
"github.com/mudler/LocalAI/core/gallery"
|
||||||
|
)
|
||||||
|
|
||||||
|
func installButton(galleryName string) elem.Node {
|
||||||
|
return elem.Button(
|
||||||
|
attrs.Props{
|
||||||
|
"data-twe-ripple-init": "",
|
||||||
|
"data-twe-ripple-color": "light",
|
||||||
|
"class": "float-right inline-block rounded bg-primary px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong",
|
||||||
|
"hx-swap": "outerHTML",
|
||||||
|
// post the Model ID as param
|
||||||
|
"hx-post": "/browse/install/model/" + galleryName,
|
||||||
|
},
|
||||||
|
elem.I(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "fa-solid fa-download pr-2",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
elem.Text("Install"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reInstallButton(galleryName string) elem.Node {
|
||||||
|
return elem.Button(
|
||||||
|
attrs.Props{
|
||||||
|
"data-twe-ripple-init": "",
|
||||||
|
"data-twe-ripple-color": "light",
|
||||||
|
"class": "float-right inline-block rounded bg-primary ml-2 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong",
|
||||||
|
"hx-target": "#action-div-" + dropBadChars(galleryName),
|
||||||
|
"hx-swap": "outerHTML",
|
||||||
|
// post the Model ID as param
|
||||||
|
"hx-post": "/browse/install/model/" + galleryName,
|
||||||
|
},
|
||||||
|
elem.I(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "fa-solid fa-arrow-rotate-right pr-2",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
elem.Text("Reinstall"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func infoButton(m *gallery.GalleryModel) elem.Node {
|
||||||
|
return elem.Button(
|
||||||
|
attrs.Props{
|
||||||
|
"data-twe-ripple-init": "",
|
||||||
|
"data-twe-ripple-color": "light",
|
||||||
|
"class": "float-left inline-block rounded bg-primary px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong",
|
||||||
|
"data-modal-target": modalName(m),
|
||||||
|
"data-modal-toggle": modalName(m),
|
||||||
|
},
|
||||||
|
elem.P(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "flex items-center",
|
||||||
|
},
|
||||||
|
elem.I(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "fas fa-info-circle pr-2",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
elem.Text("Info"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteButton(galleryID string) elem.Node {
|
||||||
|
return elem.Button(
|
||||||
|
attrs.Props{
|
||||||
|
"data-twe-ripple-init": "",
|
||||||
|
"data-twe-ripple-color": "light",
|
||||||
|
"hx-confirm": "Are you sure you wish to delete the model?",
|
||||||
|
"class": "float-right inline-block rounded bg-red-800 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-red-accent-300 hover:shadow-red-2 focus:bg-red-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-red-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong",
|
||||||
|
"hx-target": "#action-div-" + dropBadChars(galleryID),
|
||||||
|
"hx-swap": "outerHTML",
|
||||||
|
// post the Model ID as param
|
||||||
|
"hx-post": "/browse/delete/model/" + galleryID,
|
||||||
|
},
|
||||||
|
elem.I(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "fa-solid fa-cancel pr-2",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
elem.Text("Delete"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Javascript/HTMX doesn't like weird IDs
|
||||||
|
func dropBadChars(s string) string {
|
||||||
|
return strings.ReplaceAll(s, "@", "__")
|
||||||
|
}
|
@ -2,13 +2,11 @@ package elements
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/chasefleming/elem-go"
|
"github.com/chasefleming/elem-go"
|
||||||
"github.com/chasefleming/elem-go/attrs"
|
"github.com/chasefleming/elem-go/attrs"
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
"github.com/mudler/LocalAI/core/gallery"
|
"github.com/mudler/LocalAI/core/gallery"
|
||||||
"github.com/mudler/LocalAI/core/p2p"
|
|
||||||
"github.com/mudler/LocalAI/core/services"
|
"github.com/mudler/LocalAI/core/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,231 +14,6 @@ const (
|
|||||||
noImage = "https://upload.wikimedia.org/wikipedia/commons/6/65/No-Image-Placeholder.svg"
|
noImage = "https://upload.wikimedia.org/wikipedia/commons/6/65/No-Image-Placeholder.svg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func renderElements(n []elem.Node) string {
|
|
||||||
render := ""
|
|
||||||
for _, r := range n {
|
|
||||||
render += r.Render()
|
|
||||||
}
|
|
||||||
return render
|
|
||||||
}
|
|
||||||
|
|
||||||
func DoneProgress(galleryID, text string, showDelete bool) string {
|
|
||||||
var modelName = galleryID
|
|
||||||
// Split by @ and grab the name
|
|
||||||
if strings.Contains(galleryID, "@") {
|
|
||||||
modelName = strings.Split(galleryID, "@")[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return elem.Div(
|
|
||||||
attrs.Props{
|
|
||||||
"id": "action-div-" + dropBadChars(galleryID),
|
|
||||||
},
|
|
||||||
elem.H3(
|
|
||||||
attrs.Props{
|
|
||||||
"role": "status",
|
|
||||||
"id": "pblabel",
|
|
||||||
"tabindex": "-1",
|
|
||||||
"autofocus": "",
|
|
||||||
},
|
|
||||||
elem.Text(bluemonday.StrictPolicy().Sanitize(text)),
|
|
||||||
),
|
|
||||||
elem.If(showDelete, deleteButton(galleryID, modelName), reInstallButton(galleryID)),
|
|
||||||
).Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ErrorProgress(err, galleryName string) string {
|
|
||||||
return elem.Div(
|
|
||||||
attrs.Props{},
|
|
||||||
elem.H3(
|
|
||||||
attrs.Props{
|
|
||||||
"role": "status",
|
|
||||||
"id": "pblabel",
|
|
||||||
"tabindex": "-1",
|
|
||||||
"autofocus": "",
|
|
||||||
},
|
|
||||||
elem.Text("Error "+bluemonday.StrictPolicy().Sanitize(err)),
|
|
||||||
),
|
|
||||||
installButton(galleryName),
|
|
||||||
).Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProgressBar(progress string) string {
|
|
||||||
return elem.Div(attrs.Props{
|
|
||||||
"class": "progress",
|
|
||||||
"role": "progressbar",
|
|
||||||
"aria-valuemin": "0",
|
|
||||||
"aria-valuemax": "100",
|
|
||||||
"aria-valuenow": "0",
|
|
||||||
"aria-labelledby": "pblabel",
|
|
||||||
},
|
|
||||||
elem.Div(attrs.Props{
|
|
||||||
"id": "pb",
|
|
||||||
"class": "progress-bar",
|
|
||||||
"style": "width:" + progress + "%",
|
|
||||||
}),
|
|
||||||
).Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
func P2PNodeStats(nodes []p2p.NodeData) string {
|
|
||||||
/*
|
|
||||||
<div class="bg-gray-800 p-6 rounded-lg shadow-lg text-left">
|
|
||||||
<p class="text-xl font-semibold text-gray-200">Total Workers Detected: {{ len .Nodes }}</p>
|
|
||||||
{{ $online := 0 }}
|
|
||||||
{{ range .Nodes }}
|
|
||||||
{{ if .IsOnline }}
|
|
||||||
{{ $online = add $online 1 }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
<p class="text-xl font-semibold text-gray-200">Total Online Workers: {{$online}}</p>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
|
|
||||||
online := 0
|
|
||||||
for _, n := range nodes {
|
|
||||||
if n.IsOnline() {
|
|
||||||
online++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class := "text-green-500"
|
|
||||||
if online == 0 {
|
|
||||||
class = "text-red-500"
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
<i class="fas fa-circle animate-pulse text-green-500 ml-2 mr-1"></i>
|
|
||||||
*/
|
|
||||||
circle := elem.I(attrs.Props{
|
|
||||||
"class": "fas fa-circle animate-pulse " + class + " ml-2 mr-1",
|
|
||||||
})
|
|
||||||
nodesElements := []elem.Node{
|
|
||||||
elem.Span(
|
|
||||||
attrs.Props{
|
|
||||||
"class": class,
|
|
||||||
},
|
|
||||||
circle,
|
|
||||||
elem.Text(fmt.Sprintf("%d", online)),
|
|
||||||
),
|
|
||||||
elem.Span(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "text-gray-200",
|
|
||||||
},
|
|
||||||
elem.Text(fmt.Sprintf("/%d", len(nodes))),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
return renderElements(nodesElements)
|
|
||||||
}
|
|
||||||
|
|
||||||
func P2PNodeBoxes(nodes []p2p.NodeData) string {
|
|
||||||
/*
|
|
||||||
<div class="bg-gray-800 p-4 rounded-lg shadow-lg text-left">
|
|
||||||
<div class="flex items-center mb-2">
|
|
||||||
<i class="fas fa-desktop text-gray-400 mr-2"></i>
|
|
||||||
<span class="text-gray-200 font-semibold">{{.ID}}</span>
|
|
||||||
</div>
|
|
||||||
<p class="text-sm text-gray-400 mt-2 flex items-center">
|
|
||||||
Status:
|
|
||||||
<i class="fas fa-circle {{ if .IsOnline }}text-green-500{{ else }}text-red-500{{ end }} ml-2 mr-1"></i>
|
|
||||||
<span class="{{ if .IsOnline }}text-green-400{{ else }}text-red-400{{ end }}">
|
|
||||||
{{ if .IsOnline }}Online{{ else }}Offline{{ end }}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
*/
|
|
||||||
|
|
||||||
nodesElements := []elem.Node{}
|
|
||||||
|
|
||||||
for _, n := range nodes {
|
|
||||||
|
|
||||||
nodesElements = append(nodesElements,
|
|
||||||
elem.Div(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "bg-gray-700 p-6 rounded-lg shadow-lg text-left",
|
|
||||||
},
|
|
||||||
elem.P(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "text-sm text-gray-400 mt-2 flex",
|
|
||||||
},
|
|
||||||
elem.I(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "fas fa-desktop text-gray-400 mr-2",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
elem.Text("Name: "),
|
|
||||||
elem.Span(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "text-gray-200 font-semibold ml-2 mr-1",
|
|
||||||
},
|
|
||||||
elem.Text(bluemonday.StrictPolicy().Sanitize(n.ID)),
|
|
||||||
),
|
|
||||||
elem.Text("Status: "),
|
|
||||||
elem.If(
|
|
||||||
n.IsOnline(),
|
|
||||||
elem.I(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "fas fa-circle animate-pulse text-green-500 ml-2 mr-1",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
elem.I(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "fas fa-circle animate-pulse text-red-500 ml-2 mr-1",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
elem.If(
|
|
||||||
n.IsOnline(),
|
|
||||||
elem.Span(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "text-green-400",
|
|
||||||
},
|
|
||||||
|
|
||||||
elem.Text("Online"),
|
|
||||||
),
|
|
||||||
elem.Span(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "text-red-400",
|
|
||||||
},
|
|
||||||
elem.Text("Offline"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
return renderElements(nodesElements)
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartProgressBar(uid, progress, text string) string {
|
|
||||||
if progress == "" {
|
|
||||||
progress = "0"
|
|
||||||
}
|
|
||||||
return elem.Div(
|
|
||||||
attrs.Props{
|
|
||||||
"hx-trigger": "done",
|
|
||||||
"hx-get": "/browse/job/" + uid,
|
|
||||||
"hx-swap": "outerHTML",
|
|
||||||
"hx-target": "this",
|
|
||||||
},
|
|
||||||
elem.H3(
|
|
||||||
attrs.Props{
|
|
||||||
"role": "status",
|
|
||||||
"id": "pblabel",
|
|
||||||
"tabindex": "-1",
|
|
||||||
"autofocus": "",
|
|
||||||
},
|
|
||||||
elem.Text(bluemonday.StrictPolicy().Sanitize(text)), //Perhaps overly defensive
|
|
||||||
elem.Div(attrs.Props{
|
|
||||||
"hx-get": "/browse/job/progress/" + uid,
|
|
||||||
"hx-trigger": "every 600ms",
|
|
||||||
"hx-target": "this",
|
|
||||||
"hx-swap": "innerHTML",
|
|
||||||
},
|
|
||||||
elem.Raw(ProgressBar(progress)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
func cardSpan(text, icon string) elem.Node {
|
func cardSpan(text, icon string) elem.Node {
|
||||||
return elem.Span(
|
return elem.Span(
|
||||||
attrs.Props{
|
attrs.Props{
|
||||||
@ -268,7 +41,6 @@ func searchableElement(text, icon string) elem.Node {
|
|||||||
attrs.Props{
|
attrs.Props{
|
||||||
"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2 hover:bg-gray-300 hover:shadow-gray-2",
|
"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2 hover:bg-gray-300 hover:shadow-gray-2",
|
||||||
},
|
},
|
||||||
|
|
||||||
elem.A(
|
elem.A(
|
||||||
attrs.Props{
|
attrs.Props{
|
||||||
// "name": "search",
|
// "name": "search",
|
||||||
@ -290,7 +62,8 @@ func searchableElement(text, icon string) elem.Node {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func link(text, url string) elem.Node {
|
/*
|
||||||
|
func buttonLink(text, url string) elem.Node {
|
||||||
return elem.A(
|
return elem.A(
|
||||||
attrs.Props{
|
attrs.Props{
|
||||||
"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2 hover:bg-gray-300 hover:shadow-gray-2",
|
"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2 hover:bg-gray-300 hover:shadow-gray-2",
|
||||||
@ -303,163 +76,255 @@ func link(text, url string) elem.Node {
|
|||||||
elem.Text(bluemonday.StrictPolicy().Sanitize(text)),
|
elem.Text(bluemonday.StrictPolicy().Sanitize(text)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
func installButton(galleryName string) elem.Node {
|
*/
|
||||||
return elem.Button(
|
|
||||||
attrs.Props{
|
|
||||||
"data-twe-ripple-init": "",
|
|
||||||
"data-twe-ripple-color": "light",
|
|
||||||
"class": "float-right inline-block rounded bg-primary px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong",
|
|
||||||
"hx-swap": "outerHTML",
|
|
||||||
// post the Model ID as param
|
|
||||||
"hx-post": "/browse/install/model/" + galleryName,
|
|
||||||
},
|
|
||||||
elem.I(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "fa-solid fa-download pr-2",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
elem.Text("Install"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reInstallButton(galleryName string) elem.Node {
|
func link(text, url string) elem.Node {
|
||||||
return elem.Button(
|
return elem.A(
|
||||||
attrs.Props{
|
attrs.Props{
|
||||||
"data-twe-ripple-init": "",
|
"class": "text-base leading-relaxed text-gray-500 dark:text-gray-400",
|
||||||
"data-twe-ripple-color": "light",
|
"href": url,
|
||||||
"class": "float-right inline-block rounded bg-primary ml-2 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong",
|
"target": "_blank",
|
||||||
"hx-target": "#action-div-" + dropBadChars(galleryName),
|
|
||||||
"hx-swap": "outerHTML",
|
|
||||||
// post the Model ID as param
|
|
||||||
"hx-post": "/browse/install/model/" + galleryName,
|
|
||||||
},
|
},
|
||||||
elem.I(
|
elem.I(attrs.Props{
|
||||||
attrs.Props{
|
"class": "fas fa-link pr-2",
|
||||||
"class": "fa-solid fa-arrow-rotate-right pr-2",
|
}),
|
||||||
},
|
elem.Text(bluemonday.StrictPolicy().Sanitize(text)),
|
||||||
),
|
|
||||||
elem.Text("Reinstall"),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteButton(galleryID, modelName string) elem.Node {
|
|
||||||
return elem.Button(
|
|
||||||
attrs.Props{
|
|
||||||
"data-twe-ripple-init": "",
|
|
||||||
"data-twe-ripple-color": "light",
|
|
||||||
"hx-confirm": "Are you sure you wish to delete the model?",
|
|
||||||
"class": "float-right inline-block rounded bg-red-800 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-red-accent-300 hover:shadow-red-2 focus:bg-red-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-red-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong",
|
|
||||||
"hx-target": "#action-div-" + dropBadChars(galleryID),
|
|
||||||
"hx-swap": "outerHTML",
|
|
||||||
// post the Model ID as param
|
|
||||||
"hx-post": "/browse/delete/model/" + galleryID,
|
|
||||||
},
|
|
||||||
elem.I(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "fa-solid fa-cancel pr-2",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
elem.Text("Delete"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Javascript/HTMX doesn't like weird IDs
|
|
||||||
func dropBadChars(s string) string {
|
|
||||||
return strings.ReplaceAll(s, "@", "__")
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProcessTracker interface {
|
type ProcessTracker interface {
|
||||||
Exists(string) bool
|
Exists(string) bool
|
||||||
Get(string) string
|
Get(string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, galleryService *services.GalleryService) string {
|
func modalName(m *gallery.GalleryModel) string {
|
||||||
modelsElements := []elem.Node{}
|
return m.Name + "-modal"
|
||||||
descriptionDiv := func(m *gallery.GalleryModel) elem.Node {
|
}
|
||||||
return elem.Div(
|
|
||||||
attrs.Props{
|
func modelDescription(m *gallery.GalleryModel) elem.Node {
|
||||||
"class": "p-6 text-surface dark:text-white",
|
urls := []elem.Node{}
|
||||||
},
|
for _, url := range m.URLs {
|
||||||
elem.H5(
|
urls = append(urls,
|
||||||
attrs.Props{
|
elem.Li(attrs.Props{}, link(url, url)),
|
||||||
"class": "mb-2 text-xl font-bold leading-tight",
|
|
||||||
},
|
|
||||||
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)),
|
|
||||||
),
|
|
||||||
elem.P(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "mb-4 text-sm [&:not(:hover)]:truncate text-base",
|
|
||||||
},
|
|
||||||
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
actionDiv := func(m *gallery.GalleryModel) elem.Node {
|
tagsNodes := []elem.Node{}
|
||||||
galleryID := fmt.Sprintf("%s@%s", m.Gallery.Name, m.Name)
|
for _, tag := range m.Tags {
|
||||||
currentlyProcessing := processTracker.Exists(galleryID)
|
tagsNodes = append(tagsNodes,
|
||||||
jobID := ""
|
searchableElement(tag, "fas fa-tag"),
|
||||||
isDeletionOp := false
|
|
||||||
if currentlyProcessing {
|
|
||||||
status := galleryService.GetStatus(galleryID)
|
|
||||||
if status != nil && status.Deletion {
|
|
||||||
isDeletionOp = true
|
|
||||||
}
|
|
||||||
jobID = processTracker.Get(galleryID)
|
|
||||||
// TODO:
|
|
||||||
// case not handled, if status == nil : "Waiting"
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes := []elem.Node{
|
|
||||||
cardSpan("Repository: "+m.Gallery.Name, "fa-brands fa-git-alt"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.License != "" {
|
|
||||||
nodes = append(nodes,
|
|
||||||
cardSpan("License: "+m.License, "fas fa-book"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
tagsNodes := []elem.Node{}
|
|
||||||
for _, tag := range m.Tags {
|
|
||||||
tagsNodes = append(tagsNodes,
|
|
||||||
searchableElement(tag, "fas fa-tag"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes = append(nodes,
|
|
||||||
elem.Div(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "flex flex-row flex-wrap content-center",
|
|
||||||
},
|
|
||||||
tagsNodes...,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
for i, url := range m.URLs {
|
return elem.Div(
|
||||||
nodes = append(nodes,
|
attrs.Props{
|
||||||
link("Link #"+fmt.Sprintf("%d", i+1), url),
|
"class": "p-6 text-surface dark:text-white",
|
||||||
)
|
},
|
||||||
}
|
elem.H5(
|
||||||
|
|
||||||
progressMessage := "Installation"
|
|
||||||
if isDeletionOp {
|
|
||||||
progressMessage = "Deletion"
|
|
||||||
}
|
|
||||||
|
|
||||||
return elem.Div(
|
|
||||||
attrs.Props{
|
attrs.Props{
|
||||||
"class": "px-6 pt-4 pb-2",
|
"class": "mb-2 text-xl font-bold leading-tight",
|
||||||
|
},
|
||||||
|
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)),
|
||||||
|
),
|
||||||
|
elem.Div( // small description
|
||||||
|
attrs.Props{
|
||||||
|
"class": "mb-4 text-sm truncate text-base",
|
||||||
|
},
|
||||||
|
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)),
|
||||||
|
),
|
||||||
|
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"id": modalName(m),
|
||||||
|
"tabindex": "-1",
|
||||||
|
"aria-hidden": "true",
|
||||||
|
"class": "hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full",
|
||||||
},
|
},
|
||||||
elem.P(
|
|
||||||
attrs.Props{
|
|
||||||
"class": "mb-4 text-base",
|
|
||||||
},
|
|
||||||
nodes...,
|
|
||||||
),
|
|
||||||
elem.Div(
|
elem.Div(
|
||||||
attrs.Props{
|
attrs.Props{
|
||||||
"id": "action-div-" + dropBadChars(galleryID),
|
"class": "relative p-4 w-full max-w-2xl max-h-full",
|
||||||
|
},
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "relative p-4 w-full max-w-2xl max-h-full bg-white rounded-lg shadow dark:bg-gray-700",
|
||||||
|
},
|
||||||
|
// header
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600",
|
||||||
|
},
|
||||||
|
elem.H3(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "text-xl font-semibold text-gray-900 dark:text-white",
|
||||||
|
},
|
||||||
|
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)),
|
||||||
|
),
|
||||||
|
elem.Button( // close button
|
||||||
|
attrs.Props{
|
||||||
|
"class": "text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white",
|
||||||
|
"data-modal-hide": modalName(m),
|
||||||
|
},
|
||||||
|
elem.Raw(
|
||||||
|
`<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
||||||
|
</svg>`,
|
||||||
|
),
|
||||||
|
elem.Span(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "sr-only",
|
||||||
|
},
|
||||||
|
elem.Text("Close modal"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// body
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "p-4 md:p-5 space-y-4",
|
||||||
|
},
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "flex justify-center items-center",
|
||||||
|
},
|
||||||
|
elem.Img(attrs.Props{
|
||||||
|
// "class": "rounded-t-lg object-fit object-center h-96",
|
||||||
|
"class": "lazy rounded-t-lg max-h-48 max-w-96 object-cover mt-3 entered loaded",
|
||||||
|
"src": m.Icon,
|
||||||
|
"loading": "lazy",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
elem.P(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "text-base leading-relaxed text-gray-500 dark:text-gray-400",
|
||||||
|
},
|
||||||
|
elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)),
|
||||||
|
),
|
||||||
|
elem.Hr(
|
||||||
|
attrs.Props{},
|
||||||
|
),
|
||||||
|
elem.P(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "text-sm font-semibold text-gray-900 dark:text-white",
|
||||||
|
},
|
||||||
|
elem.Text("Links"),
|
||||||
|
),
|
||||||
|
elem.Ul(
|
||||||
|
attrs.Props{},
|
||||||
|
urls...,
|
||||||
|
),
|
||||||
|
elem.If(
|
||||||
|
len(m.Tags) > 0,
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{},
|
||||||
|
elem.P(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "text-sm mb-5 font-semibold text-gray-900 dark:text-white",
|
||||||
|
},
|
||||||
|
elem.Text("Tags"),
|
||||||
|
),
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "flex flex-row flex-wrap content-center",
|
||||||
|
},
|
||||||
|
tagsNodes...,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
elem.Div(attrs.Props{}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Footer
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b dark:border-gray-600",
|
||||||
|
},
|
||||||
|
elem.Button(
|
||||||
|
attrs.Props{
|
||||||
|
"data-modal-hide": modalName(m),
|
||||||
|
"class": "py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700",
|
||||||
|
},
|
||||||
|
elem.Text("Close"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func modelActionItems(m *gallery.GalleryModel, processTracker ProcessTracker, galleryService *services.GalleryService) elem.Node {
|
||||||
|
galleryID := fmt.Sprintf("%s@%s", m.Gallery.Name, m.Name)
|
||||||
|
currentlyProcessing := processTracker.Exists(galleryID)
|
||||||
|
jobID := ""
|
||||||
|
isDeletionOp := false
|
||||||
|
if currentlyProcessing {
|
||||||
|
status := galleryService.GetStatus(galleryID)
|
||||||
|
if status != nil && status.Deletion {
|
||||||
|
isDeletionOp = true
|
||||||
|
}
|
||||||
|
jobID = processTracker.Get(galleryID)
|
||||||
|
// TODO:
|
||||||
|
// case not handled, if status == nil : "Waiting"
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes := []elem.Node{
|
||||||
|
cardSpan("Repository: "+m.Gallery.Name, "fa-brands fa-git-alt"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.License != "" {
|
||||||
|
nodes = append(nodes,
|
||||||
|
cardSpan("License: "+m.License, "fas fa-book"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
tagsNodes := []elem.Node{}
|
||||||
|
|
||||||
|
for _, tag := range m.Tags {
|
||||||
|
tagsNodes = append(tagsNodes,
|
||||||
|
searchableElement(tag, "fas fa-tag"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nodes = append(nodes,
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "flex flex-row flex-wrap content-center",
|
||||||
|
},
|
||||||
|
tagsNodes...,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, url := range m.URLs {
|
||||||
|
nodes = append(nodes,
|
||||||
|
buttonLink("Link #"+fmt.Sprintf("%d", i+1), url),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
progressMessage := "Installation"
|
||||||
|
if isDeletionOp {
|
||||||
|
progressMessage = "Deletion"
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "px-6 pt-4 pb-2",
|
||||||
|
},
|
||||||
|
elem.P(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "mb-4 text-base",
|
||||||
|
},
|
||||||
|
nodes...,
|
||||||
|
),
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"id": "action-div-" + dropBadChars(galleryID),
|
||||||
|
"class": "flow-root", // To order buttons left and right
|
||||||
|
},
|
||||||
|
infoButton(m),
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "float-right",
|
||||||
},
|
},
|
||||||
elem.If(
|
elem.If(
|
||||||
currentlyProcessing,
|
currentlyProcessing,
|
||||||
@ -470,14 +335,18 @@ func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, g
|
|||||||
elem.Node(elem.Div(
|
elem.Node(elem.Div(
|
||||||
attrs.Props{},
|
attrs.Props{},
|
||||||
reInstallButton(m.ID()),
|
reInstallButton(m.ID()),
|
||||||
deleteButton(m.ID(), m.Name),
|
deleteButton(m.ID()),
|
||||||
)),
|
)),
|
||||||
installButton(m.ID()),
|
installButton(m.ID()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, galleryService *services.GalleryService) string {
|
||||||
|
modelsElements := []elem.Node{}
|
||||||
|
|
||||||
for _, m := range models {
|
for _, m := range models {
|
||||||
elems := []elem.Node{}
|
elems := []elem.Node{}
|
||||||
@ -521,7 +390,10 @@ func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, g
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
elems = append(elems, descriptionDiv(m), actionDiv(m))
|
elems = append(elems,
|
||||||
|
modelDescription(m),
|
||||||
|
modelActionItems(m, processTracker, galleryService),
|
||||||
|
)
|
||||||
modelsElements = append(modelsElements,
|
modelsElements = append(modelsElements,
|
||||||
elem.Div(
|
elem.Div(
|
||||||
attrs.Props{
|
attrs.Props{
|
||||||
|
147
core/http/elements/p2p.go
Normal file
147
core/http/elements/p2p.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package elements
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/chasefleming/elem-go"
|
||||||
|
"github.com/chasefleming/elem-go/attrs"
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
"github.com/mudler/LocalAI/core/p2p"
|
||||||
|
)
|
||||||
|
|
||||||
|
func renderElements(n []elem.Node) string {
|
||||||
|
render := ""
|
||||||
|
for _, r := range n {
|
||||||
|
render += r.Render()
|
||||||
|
}
|
||||||
|
return render
|
||||||
|
}
|
||||||
|
|
||||||
|
func P2PNodeStats(nodes []p2p.NodeData) string {
|
||||||
|
/*
|
||||||
|
<div class="bg-gray-800 p-6 rounded-lg shadow-lg text-left">
|
||||||
|
<p class="text-xl font-semibold text-gray-200">Total Workers Detected: {{ len .Nodes }}</p>
|
||||||
|
{{ $online := 0 }}
|
||||||
|
{{ range .Nodes }}
|
||||||
|
{{ if .IsOnline }}
|
||||||
|
{{ $online = add $online 1 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
<p class="text-xl font-semibold text-gray-200">Total Online Workers: {{$online}}</p>
|
||||||
|
</div>
|
||||||
|
*/
|
||||||
|
|
||||||
|
online := 0
|
||||||
|
for _, n := range nodes {
|
||||||
|
if n.IsOnline() {
|
||||||
|
online++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class := "text-green-500"
|
||||||
|
if online == 0 {
|
||||||
|
class = "text-red-500"
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
<i class="fas fa-circle animate-pulse text-green-500 ml-2 mr-1"></i>
|
||||||
|
*/
|
||||||
|
circle := elem.I(attrs.Props{
|
||||||
|
"class": "fas fa-circle animate-pulse " + class + " ml-2 mr-1",
|
||||||
|
})
|
||||||
|
nodesElements := []elem.Node{
|
||||||
|
elem.Span(
|
||||||
|
attrs.Props{
|
||||||
|
"class": class,
|
||||||
|
},
|
||||||
|
circle,
|
||||||
|
elem.Text(fmt.Sprintf("%d", online)),
|
||||||
|
),
|
||||||
|
elem.Span(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "text-gray-200",
|
||||||
|
},
|
||||||
|
elem.Text(fmt.Sprintf("/%d", len(nodes))),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderElements(nodesElements)
|
||||||
|
}
|
||||||
|
|
||||||
|
func P2PNodeBoxes(nodes []p2p.NodeData) string {
|
||||||
|
/*
|
||||||
|
<div class="bg-gray-800 p-4 rounded-lg shadow-lg text-left">
|
||||||
|
<div class="flex items-center mb-2">
|
||||||
|
<i class="fas fa-desktop text-gray-400 mr-2"></i>
|
||||||
|
<span class="text-gray-200 font-semibold">{{.ID}}</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-400 mt-2 flex items-center">
|
||||||
|
Status:
|
||||||
|
<i class="fas fa-circle {{ if .IsOnline }}text-green-500{{ else }}text-red-500{{ end }} ml-2 mr-1"></i>
|
||||||
|
<span class="{{ if .IsOnline }}text-green-400{{ else }}text-red-400{{ end }}">
|
||||||
|
{{ if .IsOnline }}Online{{ else }}Offline{{ end }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
*/
|
||||||
|
|
||||||
|
nodesElements := []elem.Node{}
|
||||||
|
|
||||||
|
for _, n := range nodes {
|
||||||
|
|
||||||
|
nodesElements = append(nodesElements,
|
||||||
|
elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "bg-gray-700 p-6 rounded-lg shadow-lg text-left",
|
||||||
|
},
|
||||||
|
elem.P(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "text-sm text-gray-400 mt-2 flex",
|
||||||
|
},
|
||||||
|
elem.I(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "fas fa-desktop text-gray-400 mr-2",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
elem.Text("Name: "),
|
||||||
|
elem.Span(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "text-gray-200 font-semibold ml-2 mr-1",
|
||||||
|
},
|
||||||
|
elem.Text(bluemonday.StrictPolicy().Sanitize(n.ID)),
|
||||||
|
),
|
||||||
|
elem.Text("Status: "),
|
||||||
|
elem.If(
|
||||||
|
n.IsOnline(),
|
||||||
|
elem.I(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "fas fa-circle animate-pulse text-green-500 ml-2 mr-1",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
elem.I(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "fas fa-circle animate-pulse text-red-500 ml-2 mr-1",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
elem.If(
|
||||||
|
n.IsOnline(),
|
||||||
|
elem.Span(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "text-green-400",
|
||||||
|
},
|
||||||
|
|
||||||
|
elem.Text("Online"),
|
||||||
|
),
|
||||||
|
elem.Span(
|
||||||
|
attrs.Props{
|
||||||
|
"class": "text-red-400",
|
||||||
|
},
|
||||||
|
elem.Text("Offline"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderElements(nodesElements)
|
||||||
|
}
|
89
core/http/elements/progressbar.go
Normal file
89
core/http/elements/progressbar.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package elements
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/chasefleming/elem-go"
|
||||||
|
"github.com/chasefleming/elem-go/attrs"
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DoneProgress(galleryID, text string, showDelete bool) string {
|
||||||
|
return elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"id": "action-div-" + dropBadChars(galleryID),
|
||||||
|
},
|
||||||
|
elem.H3(
|
||||||
|
attrs.Props{
|
||||||
|
"role": "status",
|
||||||
|
"id": "pblabel",
|
||||||
|
"tabindex": "-1",
|
||||||
|
"autofocus": "",
|
||||||
|
},
|
||||||
|
elem.Text(bluemonday.StrictPolicy().Sanitize(text)),
|
||||||
|
),
|
||||||
|
elem.If(showDelete, deleteButton(galleryID), reInstallButton(galleryID)),
|
||||||
|
).Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrorProgress(err, galleryName string) string {
|
||||||
|
return elem.Div(
|
||||||
|
attrs.Props{},
|
||||||
|
elem.H3(
|
||||||
|
attrs.Props{
|
||||||
|
"role": "status",
|
||||||
|
"id": "pblabel",
|
||||||
|
"tabindex": "-1",
|
||||||
|
"autofocus": "",
|
||||||
|
},
|
||||||
|
elem.Text("Error "+bluemonday.StrictPolicy().Sanitize(err)),
|
||||||
|
),
|
||||||
|
installButton(galleryName),
|
||||||
|
).Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProgressBar(progress string) string {
|
||||||
|
return elem.Div(attrs.Props{
|
||||||
|
"class": "progress",
|
||||||
|
"role": "progressbar",
|
||||||
|
"aria-valuemin": "0",
|
||||||
|
"aria-valuemax": "100",
|
||||||
|
"aria-valuenow": "0",
|
||||||
|
"aria-labelledby": "pblabel",
|
||||||
|
},
|
||||||
|
elem.Div(attrs.Props{
|
||||||
|
"id": "pb",
|
||||||
|
"class": "progress-bar",
|
||||||
|
"style": "width:" + progress + "%",
|
||||||
|
}),
|
||||||
|
).Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartProgressBar(uid, progress, text string) string {
|
||||||
|
if progress == "" {
|
||||||
|
progress = "0"
|
||||||
|
}
|
||||||
|
return elem.Div(
|
||||||
|
attrs.Props{
|
||||||
|
"hx-trigger": "done",
|
||||||
|
"hx-get": "/browse/job/" + uid,
|
||||||
|
"hx-swap": "outerHTML",
|
||||||
|
"hx-target": "this",
|
||||||
|
},
|
||||||
|
elem.H3(
|
||||||
|
attrs.Props{
|
||||||
|
"role": "status",
|
||||||
|
"id": "pblabel",
|
||||||
|
"tabindex": "-1",
|
||||||
|
"autofocus": "",
|
||||||
|
},
|
||||||
|
elem.Text(bluemonday.StrictPolicy().Sanitize(text)), //Perhaps overly defensive
|
||||||
|
elem.Div(attrs.Props{
|
||||||
|
"hx-get": "/browse/job/progress/" + uid,
|
||||||
|
"hx-trigger": "every 600ms",
|
||||||
|
"hx-target": "this",
|
||||||
|
"hx-swap": "innerHTML",
|
||||||
|
},
|
||||||
|
elem.Raw(ProgressBar(progress)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).Render()
|
||||||
|
}
|
2
core/http/static/assets/flowbite.min.js
vendored
Normal file
2
core/http/static/assets/flowbite.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -57,6 +57,7 @@
|
|||||||
<link href="/static/assets/fontawesome/css/fontawesome.css" rel="stylesheet" />
|
<link href="/static/assets/fontawesome/css/fontawesome.css" rel="stylesheet" />
|
||||||
<link href="/static/assets/fontawesome/css/brands.css" rel="stylesheet" />
|
<link href="/static/assets/fontawesome/css/brands.css" rel="stylesheet" />
|
||||||
<link href="/static/assets/fontawesome/css/solid.css" rel="stylesheet" />
|
<link href="/static/assets/fontawesome/css/solid.css" rel="stylesheet" />
|
||||||
|
<script src="/static/assets/flowbite.min.js"></script>
|
||||||
<script src="/static/assets/htmx.js" crossorigin="anonymous"></script>
|
<script src="/static/assets/htmx.js" crossorigin="anonymous"></script>
|
||||||
<!-- P2P Animation START -->
|
<!-- P2P Animation START -->
|
||||||
<style>
|
<style>
|
||||||
@ -118,4 +119,11 @@
|
|||||||
100% { transform: rotate(0deg); } /* Return to center */
|
100% { transform: rotate(0deg); } /* Return to center */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- https://stackoverflow.com/questions/76051980/flowbite-component-not-working-when-loaded-via-htmx-django-project -->
|
||||||
|
<script>
|
||||||
|
htmx.onLoad(function(content) {
|
||||||
|
initFlowbite();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
@ -56,4 +56,7 @@
|
|||||||
sha: "8a9a74f4455f392ec3e7499cfda6097b536bb4b7f1e529a079c3d953c08b54ca"
|
sha: "8a9a74f4455f392ec3e7499cfda6097b536bb4b7f1e529a079c3d953c08b54ca"
|
||||||
- filename: "KFOlCnqEu92Fr1MmYUtfBBc9.ttf"
|
- filename: "KFOlCnqEu92Fr1MmYUtfBBc9.ttf"
|
||||||
url: "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfBBc9.ttf"
|
url: "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfBBc9.ttf"
|
||||||
sha: "361a50f8a6c816ba4306c5290b7e487a726e1b4dcc3d8d7e4acf1fc2dae9f551"
|
sha: "361a50f8a6c816ba4306c5290b7e487a726e1b4dcc3d8d7e4acf1fc2dae9f551"
|
||||||
|
- filename: "flowbite.min.js"
|
||||||
|
url: "https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js"
|
||||||
|
sha: "d2a1a72a4c2399e43c01412b86b9957c4df1845f2e0586607c7e55b9ae949cf8"
|
Loading…
Reference in New Issue
Block a user