Appmanager: implement functional search bar
reworked handlers for different app types
Change-Id: I82d3c856aa5c583dcdcf83ed6fbaf440bc4c8f87
diff --git a/core/installer/app_repository.go b/core/installer/app_repository.go
index 1befd7c..381a84f 100644
--- a/core/installer/app_repository.go
+++ b/core/installer/app_repository.go
@@ -9,6 +9,7 @@
"io"
"log"
"net/http"
+ "strings"
"github.com/go-git/go-billy/v5"
"sigs.k8s.io/yaml"
@@ -71,6 +72,7 @@
type AppRepository interface {
GetAll() ([]App, error)
Find(name string) (App, error)
+ Filter(query string) ([]App, error)
}
type InMemoryAppRepository struct {
@@ -104,6 +106,19 @@
)
}
+func (r InMemoryAppRepository) Filter(query string) ([]App, error) {
+ var filteredApps []App
+ if query == "" {
+ return r.GetAll()
+ }
+ for _, a := range r.apps {
+ if strings.Contains(strings.ToLower(a.Name()), strings.ToLower(query)) {
+ filteredApps = append(filteredApps, a)
+ }
+ }
+ return filteredApps, nil
+}
+
func CreateStoreApps() []App {
return CreateEnvApps(storeEnvAppConfigs)
}
diff --git a/core/installer/welcome/appmanager-tmpl/base.html b/core/installer/welcome/appmanager-tmpl/base.html
index fa77d97..dd0549a 100644
--- a/core/installer/welcome/appmanager-tmpl/base.html
+++ b/core/installer/welcome/appmanager-tmpl/base.html
@@ -14,9 +14,9 @@
<aside id="menu-nav">
<nav id="menu" class="is-sticky-above-lg">
<ul>
- <li><a href="/" class="{{ if (eq .CurrentPage "ALL") }}primary{{ end }}">All</a></li>
- <li><a href="/installed" class="{{ if (eq .CurrentPage "INSTALLED") }}primary{{ end }}">Installed</a></li>
- <li><a href="/not-installed" class="{{ if (eq .CurrentPage "NOT_INSTALLED") }}primary{{ end }}">Not Installed</a></li>
+ <li><a href="/" class="{{ if (eq .CurrentPage "all") }}primary{{ end }}">All</a></li>
+ <li><a href="/installed" class="{{ if (eq .CurrentPage "installed") }}primary{{ end }}">Installed</a></li>
+ <li><a href="/not-installed" class="{{ if (eq .CurrentPage "not-installed") }}primary{{ end }}">Not Installed</a></li>
<hr>
{{ block "extra_menu" . }}{{ end }}
</ul>
@@ -26,6 +26,6 @@
{{ block "content" . }}{{ end }}
</div>
</main>
- <script src="/static/app-manager.js?v=0.0.2"></script>
+ <script src="/static/app-manager.js?v=0.0.10"></script>
</body>
</html>
diff --git a/core/installer/welcome/appmanager-tmpl/index.html b/core/installer/welcome/appmanager-tmpl/index.html
index 6ba92c3..fcb613f 100644
--- a/core/installer/welcome/appmanager-tmpl/index.html
+++ b/core/installer/welcome/appmanager-tmpl/index.html
@@ -1,13 +1,14 @@
{{ define "header" }}
- <form class="search-bar">
- <input name="search" type="search" placeholder="Search" />
+ <form id="search-form" class="search-bar" method="GET" action="/{{ .SearchTarget }}">
+ <input id="search-input" name="query" type="search" placeholder="Search" value="{{ .SearchValue }}"/>
</form>
+ <input type="hidden" id="page-type" value="{{ .SearchTarget }}" />
{{ end }}
{{ define "content" }}
<aside>
<nav>
- <ul>
+ <ul id="app-list">
{{ range .Apps }}
<li class="app-card">
<a href="/app/{{ .Slug }}" class="app-link">
diff --git a/core/installer/welcome/appmanager.go b/core/installer/welcome/appmanager.go
index 2c434c6..29d19ed 100644
--- a/core/installer/welcome/appmanager.go
+++ b/core/installer/welcome/appmanager.go
@@ -100,11 +100,10 @@
r.HandleFunc("/api/instance/{slug}", s.handleInstance).Methods(http.MethodGet)
r.HandleFunc("/api/instance/{slug}/update", s.handleAppUpdate).Methods(http.MethodPost)
r.HandleFunc("/api/instance/{slug}/remove", s.handleAppRemove).Methods(http.MethodPost)
- r.HandleFunc("/", s.handleIndex).Methods(http.MethodGet)
- r.HandleFunc("/not-installed", s.handleNotInstalledApps).Methods(http.MethodGet)
- r.HandleFunc("/installed", s.handleInstalledApps).Methods(http.MethodGet)
r.HandleFunc("/app/{slug}", s.handleAppUI).Methods(http.MethodGet)
r.HandleFunc("/instance/{slug}", s.handleInstanceUI).Methods(http.MethodGet)
+ r.HandleFunc("/{pageType}", s.handleAppsList).Methods(http.MethodGet)
+ r.HandleFunc("/", s.handleAppsList).Methods(http.MethodGet)
fmt.Printf("Starting HTTP server on port: %d\n", s.port)
return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
}
@@ -317,88 +316,51 @@
}
type PageData struct {
- Apps []app
- CurrentPage string
+ Apps []app
+ CurrentPage string
+ SearchTarget string
+ SearchValue string
}
-func (s *AppManagerServer) handleIndex(w http.ResponseWriter, r *http.Request) {
- all, err := s.r.GetAll()
- if err != nil {
- log.Printf("all apps: %v", err)
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
+func (s *AppManagerServer) handleAppsList(w http.ResponseWriter, r *http.Request) {
+ pageType := mux.Vars(r)["pageType"]
+ if pageType == "" {
+ pageType = "all"
}
- resp := make([]app, 0)
- for _, a := range all {
- instances, err := s.m.FindAllAppInstances(a.Slug())
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
- }
- data := PageData{
- Apps: resp,
- CurrentPage: "ALL",
- }
- if err := s.tmpl.index.Execute(w, data); err != nil {
- log.Printf("executing template: %v", err)
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-}
-
-func (s *AppManagerServer) handleNotInstalledApps(w http.ResponseWriter, r *http.Request) {
- all, err := s.r.GetAll()
+ searchQuery := r.FormValue("query")
+ apps, err := s.r.Filter(searchQuery)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp := make([]app, 0)
- for _, a := range all {
+ for _, a := range apps {
instances, err := s.m.FindAllAppInstances(a.Slug())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- if len(instances) == 0 {
- resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil})
- }
- }
- data := PageData{
- Apps: resp,
- CurrentPage: "NOT_INSTALLED",
- }
- if err := s.tmpl.index.Execute(w, data); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-}
-
-func (s *AppManagerServer) handleInstalledApps(w http.ResponseWriter, r *http.Request) {
- all, err := s.r.GetAll()
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- resp := make([]app, 0)
- for _, a := range all {
- instances, err := s.m.FindAllAppInstances(a.Slug())
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- if len(instances) != 0 {
+ switch pageType {
+ case "installed":
+ if len(instances) != 0 {
+ resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
+ }
+ case "not-installed":
+ if len(instances) == 0 {
+ resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), nil})
+ }
+ default:
resp = append(resp, app{a.Name(), a.Icon(), a.Description(), a.Slug(), instances})
}
}
data := PageData{
- Apps: resp,
- CurrentPage: "INSTALLED",
+ Apps: resp,
+ CurrentPage: pageType,
+ SearchTarget: pageType,
+ SearchValue: searchQuery,
}
if err := s.tmpl.index.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
- return
}
}
diff --git a/core/installer/welcome/static/app-manager.js b/core/installer/welcome/static/app-manager.js
index 5cd568b..55357f3 100644
--- a/core/installer/welcome/static/app-manager.js
+++ b/core/installer/welcome/static/app-manager.js
@@ -1,4 +1,13 @@
+function delaySearch(func, wait) {
+ let timeout;
+ return function (...args) {
+ clearTimeout(timeout);
+ timeout = setTimeout(() => func.apply(this, args), wait);
+ };
+}
+
document.addEventListener("DOMContentLoaded", function () {
+ let searchRequestCount = 0;
const page = document.documentElement;
const headerHeight = parseFloat(getComputedStyle(page).getPropertyValue('--pico-header-height').replace("px", ""));
const nav = document.getElementById("menu");
@@ -7,6 +16,39 @@
const menu = document.getElementById("menu-nav");
const menuHeight = parseFloat(getComputedStyle(document.getElementById('menu-nav')).height.replace("px", "")) + 15;
menu.style.setProperty("height", `${menuHeight}px`);
+ const searchForm = document.getElementById('search-form');
+ const searchInput = document.getElementById('search-input');
+ function fetchAndUpdateAppList() {
+ searchRequestCount++;
+ const currentRequest = searchRequestCount;
+ const formData = new FormData(searchForm);
+ const query = formData.get('query');
+ const pageType = document.getElementById('page-type').value;
+ const url = `/${pageType}?query=${encodeURIComponent(query)}`;
+ fetch(url, {
+ method: 'GET'
+ })
+ .then(response => response.text())
+ .then(html => {
+ if (currentRequest !== searchRequestCount) {
+ return;
+ }
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = html;
+ const newAppListHTML = tempDiv.querySelector('#app-list').innerHTML;
+ const appListContainer = document.getElementById("app-list");
+ appListContainer.innerHTML = newAppListHTML;
+ })
+ .catch(error => console.error('Error fetching app list:', error));
+ }
+ const delayedFetchAndUpdateAppList = delaySearch(fetchAndUpdateAppList, 300);
+ searchForm.addEventListener('submit', (event) => {
+ event.preventDefault();
+ fetchAndUpdateAppList();
+ });
+ searchInput.addEventListener('input', () => {
+ delayedFetchAndUpdateAppList();
+ });
});
let prevWindowHeight = window.innerHeight;