Skip to content

Commit 0c860c3

Browse files
authored
Merge pull request #59 from modelix/feature/content-explorer
MODELIX-179: Browser-based explorer for model server content
2 parents 45134c3 + 344f08e commit 0c860c3

File tree

15 files changed

+755
-260
lines changed

15 files changed

+755
-260
lines changed

authorization/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ tasks.getByName<Test>("test") {
2222
useJUnitPlatform()
2323
}
2424

25+
java {
26+
toolchain {
27+
languageVersion.set(JavaLanguageVersion.of(11))
28+
}
29+
}
30+
2531
publishing {
2632
publications {
2733
create<MavenPublication>("maven") {

docker-build-model.sh

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,14 @@
33
set -e
44

55
TAG=$( ./modelix-version.sh )
6-
MODELIX_TARGET_PLATFORM="${MODELIX_TARGET_PLATFORM:=linux/amd64}"
76

87
(
98
cd model-server
109
if [ "${CI}" = "true" ]; then
11-
docker buildx build --platform linux/amd64,linux/arm64 --push --no-cache \
10+
docker buildx build --platform linux/amd64,linux/arm64 --push \
1211
-t modelix/modelix-model:latest -t "modelix/modelix-model:${TAG}" .
1312
else
14-
docker build --platform "${MODELIX_TARGET_PLATFORM}" --no-cache \
15-
-t modelix/modelix-model:latest -t "modelix/modelix-model:${TAG}" .
13+
docker build -t modelix/modelix-model:latest -t "modelix/modelix-model:${TAG}" .
1614
fi
1715
)
1816

model-server/build.gradle.kts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ description = "Model Server offering access to model storage"
1515

1616
defaultTasks.add("build")
1717

18-
/*compileJava {
19-
sourceCompatibility = '11'
20-
targetCompatibility = '11'
21-
}*/
18+
java {
19+
toolchain {
20+
languageVersion.set(JavaLanguageVersion.of(11))
21+
}
22+
}
2223

2324
val mpsExtensionsVersion: String by project
2425

model-server/src/main/kotlin/org/modelix/model/server/Main.kt

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,34 +20,25 @@ import io.ktor.serialization.kotlinx.json.*
2020
import io.ktor.server.application.*
2121
import io.ktor.server.engine.*
2222
import io.ktor.server.html.*
23+
import io.ktor.server.http.content.*
2324
import io.ktor.server.netty.*
2425
import io.ktor.server.plugins.contentnegotiation.*
2526
import io.ktor.server.plugins.cors.routing.*
2627
import io.ktor.server.plugins.forwardedheaders.*
2728
import io.ktor.server.response.*
2829
import io.ktor.server.routing.*
2930
import io.ktor.server.websocket.*
30-
import kotlinx.html.a
31-
import kotlinx.html.body
32-
import kotlinx.html.br
33-
import kotlinx.html.div
34-
import kotlinx.html.head
35-
import kotlinx.html.li
36-
import kotlinx.html.style
37-
import kotlinx.html.ul
31+
import kotlinx.html.*
3832
import org.apache.commons.io.FileUtils
3933
import org.apache.ignite.Ignition
4034
import org.modelix.authorization.KeycloakUtils
4135
import org.modelix.authorization.installAuthentication
42-
import org.modelix.model.server.handlers.HistoryHandler
43-
import org.modelix.model.server.handlers.DeprecatedLightModelServer
44-
import org.modelix.model.server.handlers.KeyValueLikeModelServer
45-
import org.modelix.model.server.handlers.ModelReplicationServer
46-
import org.modelix.model.server.handlers.RepositoriesManager
36+
import org.modelix.model.server.handlers.*
4737
import org.modelix.model.server.store.IStoreClient
4838
import org.modelix.model.server.store.IgniteStoreClient
4939
import org.modelix.model.server.store.InMemoryStoreClient
5040
import org.modelix.model.server.store.LocalModelClient
41+
import org.modelix.model.server.templates.PageWithMenuBar
5142
import org.slf4j.LoggerFactory
5243
import java.io.File
5344
import java.io.FileReader
@@ -134,7 +125,9 @@ object Main {
134125

135126
val jsonModelServer = DeprecatedLightModelServer(localModelClient)
136127
val repositoriesManager = RepositoriesManager(localModelClient)
128+
val repositoryOverview = RepositoryOverview(repositoriesManager)
137129
val historyHandler = HistoryHandler(localModelClient, repositoriesManager)
130+
val contentExplorer = ContentExplorer(localModelClient, repositoriesManager)
138131
val modelReplicationServer = ModelReplicationServer(repositoriesManager)
139132
val ktorServer: NettyApplicationEngine = embeddedServer(Netty, port = port) {
140133
install(Routing)
@@ -155,13 +148,20 @@ object Main {
155148

156149
modelServer.init(this)
157150
historyHandler.init(this)
151+
repositoryOverview.init(this)
152+
contentExplorer.init(this)
158153
jsonModelServer.init(this)
159154
modelReplicationServer.init(this)
160155
routing {
156+
static("/public") {
157+
resources("public")
158+
}
161159
get("/") {
162-
call.respondHtml {
163-
head {
160+
call.respondHtmlTemplate(PageWithMenuBar("root", ".")) {
161+
headContent {
164162
style { +"""
163+
body {
164+
font-family: sans-serif;
165165
table {
166166
border-collapse: collapse;
167167
}
@@ -171,12 +171,11 @@ object Main {
171171
}
172172
""".trimIndent() }
173173
}
174-
body {
175-
div { +"Model Server" }
176-
br {}
174+
bodyContent {
175+
h1 { +"Model Server" }
177176
ul {
178177
li {
179-
a("history/") { +"Model History" }
178+
a("repos/") { +"View Repositories on the Model Server" }
180179
}
181180
li {
182181
a("json/") { +"JSON API for JavaScript clients" }
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package org.modelix.model.server.handlers
2+
3+
import io.ktor.http.*
4+
import io.ktor.server.application.*
5+
import io.ktor.server.html.*
6+
import io.ktor.server.response.*
7+
import io.ktor.server.routing.*
8+
import kotlinx.html.*
9+
import org.modelix.model.ModelFacade
10+
import org.modelix.model.api.ITree
11+
import org.modelix.model.api.PNodeAdapter
12+
import org.modelix.model.api.TreePointer
13+
import org.modelix.model.client.IModelClient
14+
import org.modelix.model.lazy.CLVersion
15+
import org.modelix.model.server.templates.PageWithMenuBar
16+
17+
class ContentExplorer(private val client: IModelClient, private val repoManager: RepositoriesManager) {
18+
19+
private val rootNodes: List<PNodeAdapter>
20+
get() {
21+
val nodeList = mutableListOf<PNodeAdapter>()
22+
23+
for (repoId in repoManager.getRepositories()) {
24+
val branchRef = repoId.getBranchReference()
25+
val version = ModelFacade.loadCurrentVersion(client, branchRef) ?: continue
26+
val rootNode = PNodeAdapter(ITree.ROOT_ID, TreePointer(version.getTree()))
27+
nodeList.add(rootNode)
28+
}
29+
return nodeList
30+
}
31+
32+
fun init(application: Application) {
33+
application.routing {
34+
get("/content/") {
35+
call.respondRedirect("../repos/")
36+
}
37+
get("/content/{versionHash}/") {
38+
val versionHash = call.parameters["versionHash"]
39+
if (versionHash.isNullOrEmpty()) {
40+
call.respondText("version not found", status = HttpStatusCode.InternalServerError)
41+
return@get
42+
}
43+
val tree = CLVersion.loadFromHash(versionHash, client.storeCache).getTree()
44+
val rootNode = PNodeAdapter(ITree.ROOT_ID, TreePointer(tree))
45+
call.respondHtmlTemplate(PageWithMenuBar("repos/", "../..")) {
46+
headContent {
47+
title("Content Explorer")
48+
link("../../public/content-explorer.css", rel = "stylesheet")
49+
script("text/javascript", src = "../../public/content-explorer.js") {}
50+
}
51+
bodyContent {contentPageBody(rootNode, versionHash)}
52+
}
53+
}
54+
get("/content/{versionHash}/{nodeId}/") {
55+
val id = call.parameters["nodeId"]!!.toLong()
56+
var found: PNodeAdapter? = null
57+
for (node in rootNodes) {
58+
val candidate = PNodeAdapter(id, node.branch).takeIf { it.isValid }
59+
if (candidate != null) {
60+
found = candidate
61+
break
62+
}
63+
}
64+
if (found == null) {
65+
call.respondText("node id not found", status = HttpStatusCode.NotFound)
66+
} else {
67+
call.respondHtml { body { nodeInspector(found) } }
68+
}
69+
}
70+
}
71+
}
72+
73+
private fun FlowContent.contentPageBody(rootNode: PNodeAdapter, versionHash: String) {
74+
h1 {+"Model Server Content"}
75+
small {
76+
style = "color: #888; text-align: center; margin-bottom: 15px;"
77+
+versionHash
78+
}
79+
div {
80+
style = "display: flex;"
81+
button(classes="btn") {
82+
id = "expandAllBtn"
83+
+"Expand all"
84+
}
85+
button(classes="btn") {
86+
id = "collapseAllBtn"
87+
+"Collapse all"
88+
}
89+
}
90+
div {
91+
id = "treeWrapper"
92+
ul("treeRoot") {
93+
nodeItem(rootNode)
94+
}
95+
}
96+
div {
97+
id = "nodeInspector"
98+
}
99+
}
100+
101+
private fun UL.nodeItem(node: PNodeAdapter) {
102+
li("nodeItem") {
103+
if (node.allChildren.toList().isNotEmpty()) {
104+
div("expander") { unsafe { +"&#x25B6;" } }
105+
}
106+
div("nameField") {
107+
attributes["data-nodeid"] = node.nodeId.toString()
108+
b {
109+
if (node.getPropertyRoles().contains("name")) {
110+
+"${node.getPropertyValue("name")}"
111+
} else {
112+
+"Unnamed Node"
113+
}
114+
}
115+
small { +"(${node})" }
116+
br { }
117+
val conceptRef = node.getConceptReference()
118+
small {
119+
if (conceptRef != null) {
120+
+conceptRef.getUID()
121+
} else {
122+
+"No concept reference"
123+
}
124+
}
125+
126+
}
127+
div("nested") {
128+
ul("nodeTree") {
129+
for (child in node.allChildren) {
130+
nodeItem(child as PNodeAdapter)
131+
}
132+
}
133+
}
134+
135+
}
136+
}
137+
138+
private fun BODY.nodeInspector(node: PNodeAdapter) {
139+
div {
140+
h3 { +"Node Details" }
141+
}
142+
val nodeEmpty = node.getReferenceRoles().isEmpty() && node.getPropertyRoles().isEmpty()
143+
if (nodeEmpty) {
144+
div { +"No roles." }
145+
return
146+
}
147+
if (node.getPropertyRoles().isEmpty()) {
148+
div { +"No properties." }
149+
} else {
150+
table {
151+
thead {
152+
tr {
153+
th { +"PropertyRole" }
154+
th { +"Value" }
155+
}
156+
}
157+
for (propertyRole in node.getPropertyRoles()) {
158+
tr {
159+
td { +propertyRole }
160+
td { +(node.getPropertyValue(propertyRole) ?: "null") }
161+
}
162+
}
163+
}
164+
}
165+
if (node.getReferenceRoles().isEmpty()) {
166+
div { +"No references." }
167+
} else {
168+
table {
169+
thead {
170+
tr {
171+
th { +"ReferenceRole" }
172+
th { +"Value" }
173+
}
174+
}
175+
for (referenceRole in node.getReferenceRoles()) {
176+
tr {
177+
td { +referenceRole }
178+
td {
179+
// TODO MODELIX-387
180+
// +"${node.getReferenceTarget(referenceRole)}"
181+
}
182+
}
183+
}
184+
}
185+
}
186+
}
187+
}

0 commit comments

Comments
 (0)