Skip to content

Commit 5edffca

Browse files
author
FTMahringer
committed
feat(dashboard): marketplace UI v2.5.6-dev
- Enhanced StoreView with search, filters, tags, card grid, detail modal - Added tag cloud filter, source/type tabs, community install confirmation - New CSS: store cards, tags, modal overlay, validation display - Backend: GET /api/store/{id}, StoreRegistryService.findById() with cache - Bumped core and dashboard versions to 2.5.6-dev
1 parent 14d653b commit 5edffca

9 files changed

Lines changed: 1174 additions & 422 deletions

File tree

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
---
1111

12+
## [v2.5.6-dev] - 2026-05-12
13+
14+
**Dashboard — Marketplace UI**
15+
16+
### Added
17+
- Enhanced `StoreView.vue` with full marketplace browsing experience:
18+
- Search bar filtering by name, description, and tags
19+
- Source filter tabs: All / Official / Community
20+
- Type filter tabs: All / Plugins / Bundles
21+
- Tag cloud filter with active state toggle
22+
- Card-based grid layout with hover effects
23+
- Detail modal with full metadata (author, license, description, tags, min Synapse version)
24+
- Bundle validation button with inline result display
25+
- Community source install confirmation flow with checkbox
26+
- New CSS styles: `.store-grid`, `.store-card`, `.store-search`, `.tag-chip`, `.modal-overlay`, `.modal-card`, `.modal-confirm`, `.modal-validation`
27+
- Backend `GET /api/store/{id}` endpoint for single entry lookup
28+
- `StoreRegistryService.findById()` with caching support
29+
30+
### Changed
31+
- `StoreEntry` frontend type extended with `license` and `minSynapse` fields
32+
- Dashboard version bumped to `2.5.6-dev`
33+
- Core version bumped to `2.5.6-dev`
34+
35+
---
36+
1237
## [v2.5.5-dev] - 2026-05-12
1338

1439
**Plugin Ecosystem — CLI Tooling**

packages/core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
<groupId>dev.synapse</groupId>
1717
<artifactId>synapse-core</artifactId>
18-
<version>2.3.8-hotfix</version>
18+
<version>2.5.6-dev</version>
1919
<name>synapse-core</name>
2020
<description>SYNAPSE Spring Boot backend</description>
2121

packages/core/src/main/java/dev/synapse/plugins/StoreController.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package dev.synapse.plugins;
22

33
import dev.synapse.core.common.domain.StoreEntry;
4-
import org.springframework.web.bind.annotation.*;
5-
64
import java.util.List;
5+
import org.springframework.web.bind.annotation.*;
76

87
@RestController
98
@RequestMapping("/api/store")
@@ -12,7 +11,10 @@ public class StoreController {
1211
private final StoreRegistryService storeRegistryService;
1312
private final BundleInstallService bundleInstallService;
1413

15-
public StoreController(StoreRegistryService storeRegistryService, BundleInstallService bundleInstallService) {
14+
public StoreController(
15+
StoreRegistryService storeRegistryService,
16+
BundleInstallService bundleInstallService
17+
) {
1618
this.storeRegistryService = storeRegistryService;
1719
this.bundleInstallService = bundleInstallService;
1820
}
@@ -25,21 +27,39 @@ public List<StoreEntry> listEntries(
2527
) {
2628
if (type != null) {
2729
try {
28-
return storeRegistryService.findByType(StoreEntry.StoreEntryType.valueOf(type.toUpperCase()), page, size);
30+
return storeRegistryService.findByType(
31+
StoreEntry.StoreEntryType.valueOf(type.toUpperCase()),
32+
page,
33+
size
34+
);
2935
} catch (IllegalArgumentException e) {
3036
return List.of();
3137
}
3238
}
3339
return storeRegistryService.findAll(page, size);
3440
}
3541

42+
@GetMapping("/{id}")
43+
public StoreEntry getEntry(@PathVariable String id) {
44+
StoreEntry entry = storeRegistryService.findById(id);
45+
if (entry == null) {
46+
throw new dev.synapse.core.infrastructure.exception.ResourceNotFoundException(
47+
"StoreEntry",
48+
id
49+
);
50+
}
51+
return entry;
52+
}
53+
3654
@PostMapping("/{id}/validate")
3755
public ValidationResult validateBundle(@PathVariable String id) {
3856
return bundleInstallService.validateBundle(id);
3957
}
4058

4159
@PostMapping("/{id}/install")
42-
public BundleInstallService.BundleInstallResult installBundle(@PathVariable String id) {
60+
public BundleInstallService.BundleInstallResult installBundle(
61+
@PathVariable String id
62+
) {
4363
return bundleInstallService.installBundle(id);
4464
}
4565
}

packages/core/src/main/java/dev/synapse/plugins/StoreRegistryService.java

Lines changed: 78 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,45 @@
11
package dev.synapse.plugins;
22

33
import dev.synapse.core.common.domain.StoreEntry;
4+
import dev.synapse.core.common.repository.StoreEntryRepository;
45
import dev.synapse.core.infrastructure.logging.LogCategory;
56
import dev.synapse.core.infrastructure.logging.LogLevel;
67
import dev.synapse.core.infrastructure.logging.SystemLogService;
7-
import dev.synapse.core.common.repository.StoreEntryRepository;
8-
import org.springframework.cache.annotation.CacheEvict;
9-
import org.springframework.cache.annotation.Cacheable;
8+
import java.io.File;
9+
import java.io.FileInputStream;
10+
import java.io.IOException;
11+
import java.io.InputStream;
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
import java.util.Map;
1015
import org.slf4j.Logger;
1116
import org.slf4j.LoggerFactory;
17+
import org.springframework.cache.annotation.CacheEvict;
18+
import org.springframework.cache.annotation.Cacheable;
1219
import org.springframework.core.io.ClassPathResource;
1320
import org.springframework.data.domain.PageRequest;
1421
import org.springframework.data.domain.Sort;
1522
import org.springframework.stereotype.Service;
1623
import org.springframework.transaction.annotation.Transactional;
1724
import org.yaml.snakeyaml.Yaml;
1825

19-
import java.io.File;
20-
import java.io.FileInputStream;
21-
import java.io.IOException;
22-
import java.io.InputStream;
23-
import java.util.ArrayList;
24-
import java.util.List;
25-
import java.util.Map;
26-
2726
/**
2827
* Syncs store entries from local store/registry.yml into the database.
2928
*/
3029
@Service
3130
public class StoreRegistryService {
3231

33-
private static final Logger log = LoggerFactory.getLogger(StoreRegistryService.class);
32+
private static final Logger log = LoggerFactory.getLogger(
33+
StoreRegistryService.class
34+
);
3435

3536
private final StoreEntryRepository storeEntryRepository;
3637
private final SystemLogService logService;
3738

38-
public StoreRegistryService(StoreEntryRepository storeEntryRepository, SystemLogService logService) {
39+
public StoreRegistryService(
40+
StoreEntryRepository storeEntryRepository,
41+
SystemLogService logService
42+
) {
3943
this.storeEntryRepository = storeEntryRepository;
4044
this.logService = logService;
4145
}
@@ -52,11 +56,15 @@ public int syncFromFile(String registryPath) {
5256

5357
storeEntryRepository.saveAll(entries);
5458

55-
logService.log(LogLevel.INFO, LogCategory.STORE,
59+
logService.log(
60+
LogLevel.INFO,
61+
LogCategory.STORE,
5662
Map.of("component", "StoreRegistryService"),
5763
"STORE_SYNCED",
5864
Map.of("count", entries.size(), "path", registryPath),
59-
null, null);
65+
null,
66+
null
67+
);
6068

6169
return entries.size();
6270
}
@@ -68,9 +76,16 @@ public List<StoreEntry> findAll() {
6876
}
6977

7078
@Transactional(readOnly = true)
71-
@Cacheable(value = "plugin-metadata", key = "'all:page:' + #page + ':' + #size")
79+
@Cacheable(
80+
value = "plugin-metadata",
81+
key = "'all:page:' + #page + ':' + #size"
82+
)
7283
public List<StoreEntry> findAll(int page, int size) {
73-
PageRequest pageRequest = PageRequest.of(page, size, Sort.by(Sort.Direction.ASC, "name"));
84+
PageRequest pageRequest = PageRequest.of(
85+
page,
86+
size,
87+
Sort.by(Sort.Direction.ASC, "name")
88+
);
7489
return storeEntryRepository.findAll(pageRequest).getContent();
7590
}
7691

@@ -81,12 +96,29 @@ public List<StoreEntry> findByType(StoreEntry.StoreEntryType type) {
8196
}
8297

8398
@Transactional(readOnly = true)
84-
@Cacheable(value = "plugin-metadata", key = "'type:' + #type.name() + ':page:' + #page + ':' + #size")
85-
public List<StoreEntry> findByType(StoreEntry.StoreEntryType type, int page, int size) {
86-
PageRequest pageRequest = PageRequest.of(page, size, Sort.by(Sort.Direction.ASC, "name"));
99+
@Cacheable(
100+
value = "plugin-metadata",
101+
key = "'type:' + #type.name() + ':page:' + #page + ':' + #size"
102+
)
103+
public List<StoreEntry> findByType(
104+
StoreEntry.StoreEntryType type,
105+
int page,
106+
int size
107+
) {
108+
PageRequest pageRequest = PageRequest.of(
109+
page,
110+
size,
111+
Sort.by(Sort.Direction.ASC, "name")
112+
);
87113
return storeEntryRepository.findByType(type, pageRequest);
88114
}
89115

116+
@Transactional(readOnly = true)
117+
@Cacheable(value = "plugin-metadata", key = "'id:' + #id")
118+
public StoreEntry findById(String id) {
119+
return storeEntryRepository.findById(id).orElse(null);
120+
}
121+
90122
@SuppressWarnings("unchecked")
91123
private List<StoreEntry> parsePlugins(Map<String, Object> registry) {
92124
Object pluginsObj = registry.get("plugins");
@@ -96,7 +128,10 @@ private List<StoreEntry> parsePlugins(Map<String, Object> registry) {
96128
for (Object item : list) {
97129
if (!(item instanceof Map<?, ?> raw)) continue;
98130
Map<String, Object> map = (Map<String, Object>) raw;
99-
StoreEntry entry = mapToEntry(map, StoreEntry.StoreEntryType.PLUGIN);
131+
StoreEntry entry = mapToEntry(
132+
map,
133+
StoreEntry.StoreEntryType.PLUGIN
134+
);
100135
if (entry != null) result.add(entry);
101136
}
102137
return result;
@@ -111,23 +146,33 @@ private List<StoreEntry> parseBundles(Map<String, Object> registry) {
111146
for (Object item : list) {
112147
if (!(item instanceof Map<?, ?> raw)) continue;
113148
Map<String, Object> map = (Map<String, Object>) raw;
114-
StoreEntry entry = mapToEntry(map, StoreEntry.StoreEntryType.BUNDLE);
149+
StoreEntry entry = mapToEntry(
150+
map,
151+
StoreEntry.StoreEntryType.BUNDLE
152+
);
115153
if (entry != null) result.add(entry);
116154
}
117155
return result;
118156
}
119157

120158
@SuppressWarnings("unchecked")
121-
private StoreEntry mapToEntry(Map<String, Object> map, StoreEntry.StoreEntryType type) {
159+
private StoreEntry mapToEntry(
160+
Map<String, Object> map,
161+
StoreEntry.StoreEntryType type
162+
) {
122163
String id = str(map, "id");
123164
if (id == null) return null;
124165

125166
StoreEntry entry = new StoreEntry();
126167
entry.setId(id);
127168
entry.setName(str(map, "name") != null ? str(map, "name") : id);
128169
entry.setType(type);
129-
entry.setSource(str(map, "source") != null ? str(map, "source") : "unknown");
130-
entry.setVersion(str(map, "version") != null ? str(map, "version") : "0.0.0");
170+
entry.setSource(
171+
str(map, "source") != null ? str(map, "source") : "unknown"
172+
);
173+
entry.setVersion(
174+
str(map, "version") != null ? str(map, "version") : "0.0.0"
175+
);
131176
entry.setAuthor(str(map, "author"));
132177
entry.setLicense(str(map, "license"));
133178
entry.setDescription(str(map, "description"));
@@ -152,14 +197,20 @@ private Map<String, Object> loadYaml(String path) {
152197
}
153198
}
154199
// fallback: classpath
155-
ClassPathResource resource = new ClassPathResource("store/registry.yml");
200+
ClassPathResource resource = new ClassPathResource(
201+
"store/registry.yml"
202+
);
156203
if (resource.exists()) {
157204
try (InputStream is = resource.getInputStream()) {
158205
return yaml.load(is);
159206
}
160207
}
161208
} catch (IOException e) {
162-
log.warn("Failed to load registry YAML from {}: {}", path, e.getMessage());
209+
log.warn(
210+
"Failed to load registry YAML from {}: {}",
211+
path,
212+
e.getMessage()
213+
);
163214
}
164215
return null;
165216
}

packages/core/src/main/resources/application.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ management:
7979

8080
synapse:
8181
system-name: ${SYSTEM_NAME:SYNAPSE}
82-
version: ${SYNAPSE_VERSION:${spring.application.version:v2.3.8-hotfix}}
82+
version: ${SYNAPSE_VERSION:${spring.application.version:v2.5.6-dev}}
8383
agents-path: ${SYNAPSE_AGENTS_PATH:agents}
8484
store:
8585
registry-path: ${SYNAPSE_STORE_REGISTRY_PATH:store/registry.yml}

packages/dashboard/frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@synapse/dashboard",
3-
"version": "1.0.0",
3+
"version": "2.5.6-dev",
44
"private": true,
55
"type": "module",
66
"scripts": {

0 commit comments

Comments
 (0)