| title | Console |
|---|---|
| nav_order | 8 |
The console is a Compose Multiplatform app that lists every device's compliance status and lets an operator edit the desired policy. The same UI code runs on desktop (JVM) and the web (JS or Wasm); iOS is wired to compile but parked as a viewer.
Source: app/shared/src/commonMain/kotlin/com/arganaemre/mdmcore/
plus the thin per-platform entry points under app/desktopApp and app/webApp.
It is a live UI, not a placeholder — it talks to the running server.
ConsoleClient.kt is
a Ktor client pointed at http://localhost:8081 (the console runs on the host, alongside the
server). It uses the shared MdmJson for content negotiation, and uses two endpoints:
| Call | Endpoint | Returns |
|---|---|---|
fetchDevices() |
GET /devices |
List<DeviceStatus> |
updatePolicy(id, PolicyUpdate) |
PUT /devices/{id}/policy |
the new DevicePolicy |
The HTTP engine is chosen per target (the client core is shared, the engine is not): CIO on the
JVM/desktop, the JS fetch engine on web (JS and Wasm). See the source sets in
app/shared/build.gradle.kts.
ConsoleViewModel.kt
is a multiplatform ViewModel exposing a StateFlow<ConsoleUiState>:
sealed interface ConsoleUiState {
data object Loading : ConsoleUiState
data class Loaded(val devices: List<DeviceStatus>) : ConsoleUiState
data class Error(val message: String) : ConsoleUiState
}- It calls
refresh()on construction to load the roster. updatePolicy(deviceId, cameraDisabled)sends aPolicyUpdate, then callsrefresh()again so the list repaints with the bumped version — which is when you'll see the device flip to STALE until the agent catches up (see the loop).- Failures surface as
ConsoleUiState.Error.
App.kt builds a Material 3 UI:
- A "MDM Console" header with a Refresh button.
Loading→ a spinner;Error→ the message;Loaded→ aLazyColumnof device cards (or "No devices enrolled yet.").- Each
DeviceCardshows the device id, the compliance state as a color-coded label (UNKNOWN gray, STALE orange, NON_COMPLIANT red, COMPLIANT green), the desired policy (camera=… (v…)), the last reported state (camera + applied version, or "No report yet"), and a camera toggleSwitchthat calls back intoupdatePolicy.
So the operator's whole workflow — see who's drifted, flip a policy, watch it propagate — is on one screen.
The console is also the canonical example of Kotlin's expect/actual. commonMain declares the
contract and each target supplies its own implementation
(Platform.kt):
interface Platform { val name: String }
expect fun getPlatform(): Platform| Target | File | name resolves to |
|---|---|---|
| Android | Platform.android.kt |
"Android ${Build.VERSION.SDK_INT}" |
| JVM/desktop | Platform.jvm.kt |
"Java ${java.version}" |
| JS | Platform.js.kt |
the browser parsed from the user-agent |
| Wasm | Platform.wasmJs.kt |
"Web with Kotlin/Wasm" |
| iOS | Platform.ios.kt |
UIDevice system name + version |
You must provide an actual for every target source set or the build fails. More on the pattern:
architecture.md.
Each platform has a tiny launcher that just hosts the shared App():
- Desktop —
app/desktopApp/src/main/kotlin/.../main.kt: Compose Desktopapplication { Window(title = "MDMCore") { App() } }. - Web —
app/webApp/src/webMain/kotlin/.../main.kt:ComposeViewport { App() }, rendering into the browser. - iOS —
MainViewController.kt: returns aComposeUIViewController { App() }for Swift to embed.
# Desktop (JVM)
./gradlew :app:desktopApp:run
./gradlew :app:desktopApp:hotRun --auto # with Compose hot reload
# Web — Wasm (faster, modern browsers)
./gradlew :app:webApp:wasmJsBrowserDevelopmentRun
# Web — JS (broader browser support)
./gradlew :app:webApp:jsBrowserDevelopmentRunEach of these is a long-lived Gradle task that holds the project lock — start the server first (or from a separate checkout). See development.md.
Shared resources live in app/shared/src/commonMain/composeResources/ and are reached through the
generated Res class (package mdmcore.app.shared.generated.resources). The console currently leans
on Material 3 built-ins; the only bundled resource is the template compose-multiplatform.xml
drawable. Generated code under app/shared/build/ is not source — never edit it.