diff --git a/tests/test_skills_scan.py b/tests/test_skills_scan.py index e0420aee3..dfb398e5c 100644 --- a/tests/test_skills_scan.py +++ b/tests/test_skills_scan.py @@ -126,6 +126,12 @@ def test_settings_skills_scan_section_and_prompt_assets_are_present(): assert "section-skills-scan" in settings_store assert "settings/skills/scan.html" in skills_settings assert "settings/skills/import.html" in skills_settings + assert settings_store.index("section-skills-import") < settings_store.index( + "section-skills-scan" + ) + assert skills_settings.index("section-skills-import") < skills_settings.index( + "section-skills-scan" + ) assert "scanSelectedFile()" in import_template assert "snyk-agent-scan@latest --json --no-bootstrap" in scan_prompt assert "E004" in scan_checks_text @@ -133,3 +139,77 @@ def test_settings_skills_scan_section_and_prompt_assets_are_present(): assert "W008" in scan_checks_text assert "W011" in scan_checks_text assert "W012" in scan_checks_text + + +def test_list_and_import_skills_filters_are_project_only(): + list_template = ( + PROJECT_ROOT + / "webui" + / "components" + / "settings" + / "skills" + / "list.html" + ).read_text(encoding="utf-8") + list_store = ( + PROJECT_ROOT + / "webui" + / "components" + / "settings" + / "skills" + / "skills-list-store.js" + ).read_text(encoding="utf-8") + import_template = ( + PROJECT_ROOT + / "webui" + / "components" + / "settings" + / "skills" + / "import.html" + ).read_text(encoding="utf-8") + import_store = ( + PROJECT_ROOT + / "webui" + / "components" + / "settings" + / "skills" + / "skills-import-store.js" + ).read_text(encoding="utf-8") + + assert "Agent profile" not in list_template + assert "agentProfile" not in list_store + assert "agent_profile" not in list_store + assert "Project" in list_template + assert "project_name" in list_store + assert "Limit to agent profile" not in import_template + assert "agentProfile" not in import_store + assert "agent_profile" not in import_store + assert "Limit to project" in import_template + assert "project_name" in import_store + + +def test_list_skills_has_mcp_style_search(): + list_template = ( + PROJECT_ROOT + / "webui" + / "components" + / "settings" + / "skills" + / "list.html" + ).read_text(encoding="utf-8") + list_store = ( + PROJECT_ROOT + / "webui" + / "components" + / "settings" + / "skills" + / "skills-list-store.js" + ).read_text(encoding="utf-8") + + assert 'type="search"' in list_template + assert "placeholder=\"Search skills\"" in list_template + assert "skills-search-clear" in list_template + assert "filteredSkills" in list_template + assert "No skills match this search." in list_template + assert "skillSearch" in list_store + assert "matchesSearchQuery" in list_store + assert "clearSkillSearch()" in list_store diff --git a/webui/components/settings/settings-store.js b/webui/components/settings/settings-store.js index 24f09ac25..94a865504 100644 --- a/webui/components/settings/settings-store.js +++ b/webui/components/settings/settings-store.js @@ -34,8 +34,8 @@ const TAB_ITEMS = Object.freeze([ icon: "school", sections: [ { id: "section-skills-list", label: "List Skills", icon: "view_list" }, - { id: "section-skills-scan", label: "Scan Skills", icon: "radar" }, { id: "section-skills-import", label: "Import Skills", icon: "upload_file" }, + { id: "section-skills-scan", label: "Scan Skills", icon: "radar" }, ], }, { diff --git a/webui/components/settings/skills/import.html b/webui/components/settings/skills/import.html index abc104382..8b1e1cb20 100644 --- a/webui/components/settings/skills/import.html +++ b/webui/components/settings/skills/import.html @@ -38,17 +38,6 @@ - - Limit to agent profile: - - All - - - - - - Namespace: - - Agent profile - - All - - - - + + search + + + close + @@ -62,8 +63,13 @@ No skills found. + + No skills match this search. + + - + @@ -130,6 +136,50 @@ flex: 0 0 auto; } + .skills-search { + display: inline-flex; + align-items: center; + gap: 0.35rem; + min-height: 2.1rem; + flex: 1 1 18rem; + min-width: min(18rem, 100%); + padding: 0.25rem 0.45rem; + border: 1px solid var(--color-border); + border-radius: 7px; + background: var(--color-input); + color: var(--color-text-muted); + } + + .skills-search input { + width: 100%; + min-width: 8rem; + border: 0; + background: transparent; + color: var(--color-text); + outline: none; + padding: 0.2rem; + } + + .skills-search input::-webkit-search-cancel-button, + .skills-search input::-webkit-search-decoration { + -webkit-appearance: none; + appearance: none; + display: none; + } + + .skills-search-clear { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.45rem; + height: 1.45rem; + border: 0; + border-radius: 5px; + background: transparent; + color: var(--color-text-muted); + cursor: pointer; + } + @media (max-width: 640px) { .skills-toolbar-row { gap: 0.5rem; @@ -140,6 +190,11 @@ min-width: 0; } + .skills-search { + flex-basis: 100%; + min-width: 0; + } + .skills-toolbar-actions { width: 100%; margin-left: 0; diff --git a/webui/components/settings/skills/skills-import-store.js b/webui/components/settings/skills/skills-import-store.js index 8cfd2b747..9d7933686 100644 --- a/webui/components/settings/skills/skills-import-store.js +++ b/webui/components/settings/skills/skills-import-store.js @@ -19,9 +19,7 @@ const model = { namespace: "", conflict: "skip", // skip|overwrite|rename projectKey: "", // selected project key, empty means All - agentProfileKey: "", // selected agent profile key, empty means All projects: [], // available projects options [{key,label}] - agentProfiles: [], // available agent profile options [{key,label}] preview: null, result: null, @@ -29,7 +27,6 @@ const model = { init() { this.resetState(); this.loadProjects(); - this.loadAgentProfiles(); }, resetState() { @@ -46,7 +43,6 @@ const model = { this.namespace = ""; this.conflict = "skip"; this.projectKey = ""; - this.agentProfileKey = ""; }, async loadProjects() { @@ -59,16 +55,6 @@ const model = { } }, - async loadAgentProfiles() { - try { - const data = await api.callJsonApi("/agents", { action: "list" }); - this.agentProfiles = data.ok ? (data.data || []) : []; - } catch (e) { - console.error("Failed to load agent profiles:", e); - this.agentProfiles = []; - } - }, - async handleFileUpload(event) { const file = event.target.files[0]; if (!file) return; @@ -100,9 +86,6 @@ const model = { formData.append("project_name", this.projectKey); } - if (this.agentProfileKey) { - formData.append("agent_profile", this.agentProfileKey); - } return formData; }, diff --git a/webui/components/settings/skills/skills-list-store.js b/webui/components/settings/skills/skills-list-store.js index 4ba4dfa1c..11fca8353 100644 --- a/webui/components/settings/skills/skills-list-store.js +++ b/webui/components/settings/skills/skills-list-store.js @@ -3,18 +3,23 @@ import { store as fileBrowserStore } from "/components/modals/file-browser/file- const fetchApi = globalThis.fetchApi; +function matchesSearchQuery(query, values) { + const normalized = String(query || "").trim().toLowerCase(); + if (!normalized) return true; + return values.some((value) => String(value ?? "").toLowerCase().includes(normalized)); +} + const model = { loading: false, error: "", skills: [], projects: [], projectName: "", - agentProfiles: [], - agentProfileKey: "", + skillSearch: "", async init() { this.resetState(); - await Promise.all([this.loadProjects(), this.loadAgentProfiles()]); + await this.loadProjects(); await this.loadSkills(); }, @@ -24,29 +29,13 @@ const model = { this.skills = []; this.projects = []; this.projectName = ""; - this.agentProfiles = []; - this.agentProfileKey = ""; + this.skillSearch = ""; }, onClose() { this.resetState(); }, - async loadAgentProfiles() { - try { - const response = await fetchApi("/agents", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ action: "list" }), - }); - const data = await response.json().catch(() => ({})); - this.agentProfiles = data.ok ? (data.data || []) : []; - } catch (e) { - console.error("Failed to load agent profiles:", e); - this.agentProfiles = []; - } - }, - async loadProjects() { try { const response = await fetchApi("/projects", { @@ -72,7 +61,6 @@ const model = { body: JSON.stringify({ action: "list", project_name: this.projectName || null, - agent_profile: this.agentProfileKey || null, }), }); const result = await response.json().catch(() => ({})); @@ -90,6 +78,24 @@ const model = { } }, + get filteredSkills() { + return this.skills.filter((skill) => matchesSearchQuery(this.skillSearch, [ + skill.name, + skill.description, + skill.path, + skill.scope, + skill.project_name, + ])); + }, + + get skillSearchActive() { + return !!String(this.skillSearch || "").trim(); + }, + + clearSkillSearch() { + this.skillSearch = ""; + }, + async deleteSkill(skill) { if (!skill) return; try { diff --git a/webui/components/settings/skills/skills-settings.html b/webui/components/settings/skills/skills-settings.html index 1a5542178..f67001d2d 100644 --- a/webui/components/settings/skills/skills-settings.html +++ b/webui/components/settings/skills/skills-settings.html @@ -15,18 +15,18 @@ List Skills - - - radar - Scan Skills - - Import Skills + + + radar + Scan Skills + + @@ -34,13 +34,13 @@ - - - - + + + +