Complete CoreState v2.0 Android-managed backup system
Created by Wiktor/overspend1 - Revolutionary enterprise backup solution: ✨ Features: - Complete Android-only management (no web dashboards) - AI-powered backup optimization and anomaly detection - Real-time WebSocket communication and CRDT sync - Hardware-accelerated encryption with KernelSU integration - Comprehensive microservices architecture - System-level file monitoring and COW snapshots 🏗️ Implementation: - Android app with complete system administration - Rust daemon with Android bridge and gRPC services - ML-powered backup prediction and scheduling optimization - KernelSU module with native kernel integration - Enterprise microservices (Kotlin, Python, Node.js, Rust) - Production-ready CI/CD with proper release packaging 📱 Management via Android: - Real-time backup monitoring and control - Service management and configuration - Device registration and security management - Performance monitoring and troubleshooting - ML analytics dashboard and insights 🔒 Enterprise Security: - End-to-end encryption with hardware acceleration - Multi-device key management and rotation - Zero-trust architecture with device authentication - Audit logging and security event monitoring Author: Wiktor (overspend1) Version: 2.0.0 License: MIT
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
package com.corestate.androidApp.data.repository
|
||||
|
||||
import com.corestate.androidApp.data.model.*
|
||||
import com.corestate.androidApp.network.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class BackupRepository @Inject constructor(
|
||||
private val apiService: CoreStateApiService,
|
||||
private val deviceManager: DeviceManager
|
||||
) {
|
||||
|
||||
suspend fun startBackup(
|
||||
paths: List<String>,
|
||||
backupType: BackupType = BackupType.INCREMENTAL,
|
||||
options: BackupOptions = BackupOptions()
|
||||
): ApiResult<BackupJobResponse> {
|
||||
return try {
|
||||
val deviceId = deviceManager.getDeviceId()
|
||||
val request = BackupRequest(
|
||||
deviceId = deviceId,
|
||||
paths = paths,
|
||||
backupType = backupType,
|
||||
options = options
|
||||
)
|
||||
apiService.startBackup(request)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to start backup")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getBackupStatus(jobId: String): ApiResult<BackupJobStatus> {
|
||||
return apiService.getBackupStatus(jobId)
|
||||
}
|
||||
|
||||
fun streamBackupProgress(jobId: String): Flow<BackupProgress> {
|
||||
return apiService.getBackupProgress(jobId)
|
||||
}
|
||||
|
||||
suspend fun pauseBackup(jobId: String): ApiResult<Unit> {
|
||||
return apiService.pauseBackup(jobId)
|
||||
}
|
||||
|
||||
suspend fun resumeBackup(jobId: String): ApiResult<Unit> {
|
||||
return apiService.resumeBackup(jobId)
|
||||
}
|
||||
|
||||
suspend fun cancelBackup(jobId: String): ApiResult<Unit> {
|
||||
return apiService.cancelBackup(jobId)
|
||||
}
|
||||
|
||||
suspend fun getBackupHistory(
|
||||
page: Int = 0,
|
||||
size: Int = 20
|
||||
): ApiResult<BackupJobListResponse> {
|
||||
return apiService.listBackups(page, size)
|
||||
}
|
||||
|
||||
suspend fun getActiveBackups(): ApiResult<List<BackupJobSummary>> {
|
||||
return when (val result = apiService.listBackups(0, 50)) {
|
||||
is ApiResult.Success -> {
|
||||
val activeJobs = result.data.jobs.filter {
|
||||
it.status in listOf(JobStatus.RUNNING, JobStatus.QUEUED, JobStatus.PAUSED)
|
||||
}
|
||||
ApiResult.Success(activeJobs)
|
||||
}
|
||||
is ApiResult.Error -> result
|
||||
ApiResult.Loading -> ApiResult.Loading
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun startRestore(
|
||||
snapshotId: String,
|
||||
files: List<String>,
|
||||
targetPath: String,
|
||||
overwriteExisting: Boolean = false
|
||||
): ApiResult<RestoreJobResponse> {
|
||||
return try {
|
||||
val deviceId = deviceManager.getDeviceId()
|
||||
val request = RestoreRequest(
|
||||
deviceId = deviceId,
|
||||
snapshotId = snapshotId,
|
||||
files = files,
|
||||
targetPath = targetPath,
|
||||
overwriteExisting = overwriteExisting
|
||||
)
|
||||
apiService.startRestore(request)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to start restore")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getRestoreStatus(jobId: String): ApiResult<RestoreJobStatus> {
|
||||
return apiService.getRestoreStatus(jobId)
|
||||
}
|
||||
|
||||
suspend fun getSnapshots(
|
||||
page: Int = 0,
|
||||
size: Int = 20
|
||||
): ApiResult<SnapshotListResponse> {
|
||||
val deviceId = deviceManager.getDeviceId()
|
||||
return apiService.listSnapshots(deviceId, page, size)
|
||||
}
|
||||
|
||||
suspend fun browseSnapshotFiles(
|
||||
snapshotId: String,
|
||||
path: String = "/"
|
||||
): ApiResult<FileListResponse> {
|
||||
return apiService.browseSnapshot(snapshotId, path)
|
||||
}
|
||||
|
||||
suspend fun getBackupPrediction(
|
||||
paths: List<String>,
|
||||
estimatedSize: Long
|
||||
): ApiResult<BackupPrediction> {
|
||||
return try {
|
||||
val deviceId = deviceManager.getDeviceId()
|
||||
val request = BackupPredictionRequest(
|
||||
deviceId = deviceId,
|
||||
filePaths = paths,
|
||||
estimatedSize = estimatedSize,
|
||||
metadata = deviceManager.getDeviceMetadata()
|
||||
)
|
||||
apiService.predictBackupPerformance(request)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to get backup prediction")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun optimizeBackupSchedule(
|
||||
backupJobs: List<BackupJobRequest>
|
||||
): ApiResult<OptimizationResult> {
|
||||
return try {
|
||||
val request = ScheduleOptimizationRequest(
|
||||
backupJobs = backupJobs,
|
||||
resourceConstraints = mapOf(
|
||||
"maxConcurrentJobs" to 3,
|
||||
"maxCpuUsage" to 80,
|
||||
"maxMemoryUsage" to 90
|
||||
),
|
||||
optimizationGoals = listOf("minimize_time", "maximize_throughput")
|
||||
)
|
||||
apiService.optimizeBackupSchedule(request)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to optimize backup schedule")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class FileRepository @Inject constructor(
|
||||
private val apiService: CoreStateApiService,
|
||||
private val deviceManager: DeviceManager
|
||||
) {
|
||||
|
||||
suspend fun listFiles(path: String): ApiResult<FileListResponse> {
|
||||
return apiService.listFiles(path)
|
||||
}
|
||||
|
||||
suspend fun getFileInfo(path: String): ApiResult<BackupFileInfo> {
|
||||
return when (val result = listFiles(path)) {
|
||||
is ApiResult.Success -> {
|
||||
val file = result.data.files.find { it.path == path }
|
||||
if (file != null) {
|
||||
ApiResult.Success(file)
|
||||
} else {
|
||||
ApiResult.Error(Exception("File not found"), "File not found: $path")
|
||||
}
|
||||
}
|
||||
is ApiResult.Error -> result
|
||||
ApiResult.Loading -> ApiResult.Loading
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun searchFiles(
|
||||
query: String,
|
||||
path: String = "/",
|
||||
fileTypes: List<String> = emptyList()
|
||||
): ApiResult<List<BackupFileInfo>> {
|
||||
return when (val result = listFiles(path)) {
|
||||
is ApiResult.Success -> {
|
||||
val filteredFiles = result.data.files.filter { file ->
|
||||
val matchesQuery = file.name.contains(query, ignoreCase = true) ||
|
||||
file.path.contains(query, ignoreCase = true)
|
||||
val matchesType = fileTypes.isEmpty() ||
|
||||
fileTypes.any { type -> file.name.endsWith(".$type", ignoreCase = true) }
|
||||
matchesQuery && matchesType
|
||||
}
|
||||
ApiResult.Success(filteredFiles)
|
||||
}
|
||||
is ApiResult.Error -> result
|
||||
ApiResult.Loading -> ApiResult.Loading
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getDirectoryTree(rootPath: String, maxDepth: Int = 3): ApiResult<DirectoryNode> {
|
||||
return buildDirectoryTree(rootPath, maxDepth, 0)
|
||||
}
|
||||
|
||||
private suspend fun buildDirectoryTree(
|
||||
path: String,
|
||||
maxDepth: Int,
|
||||
currentDepth: Int
|
||||
): ApiResult<DirectoryNode> {
|
||||
if (currentDepth >= maxDepth) {
|
||||
return ApiResult.Success(DirectoryNode(path, emptyList(), emptyList()))
|
||||
}
|
||||
|
||||
return when (val result = listFiles(path)) {
|
||||
is ApiResult.Success -> {
|
||||
val files = result.data.files.filter { !it.isDirectory }
|
||||
val directories = result.data.files.filter { it.isDirectory }
|
||||
|
||||
val childNodes = mutableListOf<DirectoryNode>()
|
||||
for (dir in directories) {
|
||||
when (val childResult = buildDirectoryTree(dir.path, maxDepth, currentDepth + 1)) {
|
||||
is ApiResult.Success -> childNodes.add(childResult.data)
|
||||
is ApiResult.Error -> continue // Skip failed directories
|
||||
ApiResult.Loading -> continue
|
||||
}
|
||||
}
|
||||
|
||||
ApiResult.Success(DirectoryNode(path, files, childNodes))
|
||||
}
|
||||
is ApiResult.Error -> result
|
||||
ApiResult.Loading -> ApiResult.Loading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class StatisticsRepository @Inject constructor(
|
||||
private val apiService: CoreStateApiService,
|
||||
private val deviceManager: DeviceManager
|
||||
) {
|
||||
|
||||
suspend fun getSystemMetrics(): ApiResult<SystemMetricsResponse> {
|
||||
return apiService.getSystemMetrics()
|
||||
}
|
||||
|
||||
suspend fun getBackupMetrics(): ApiResult<BackupMetrics> {
|
||||
return apiService.getSystemMetrics().let { result ->
|
||||
when (result) {
|
||||
is ApiResult.Success -> ApiResult.Success(result.data.backupMetrics)
|
||||
is ApiResult.Error -> result
|
||||
ApiResult.Loading -> ApiResult.Loading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getStorageUsage(): ApiResult<StorageUsageResponse> {
|
||||
val deviceId = deviceManager.getDeviceId()
|
||||
return apiService.getStorageUsage(deviceId)
|
||||
}
|
||||
|
||||
suspend fun getSystemHealth(): ApiResult<ServicesHealthResponse> {
|
||||
return apiService.getAllServicesHealth()
|
||||
}
|
||||
|
||||
suspend fun getAnomalyReport(
|
||||
timeRange: TimeRange = TimeRange.LAST_24_HOURS
|
||||
): ApiResult<AnomalyReport> {
|
||||
return try {
|
||||
val deviceId = deviceManager.getDeviceId()
|
||||
val metrics = deviceManager.getCurrentMetrics()
|
||||
|
||||
val request = AnomalyDetectionRequest(
|
||||
deviceId = deviceId,
|
||||
metrics = metrics,
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
when (val result = apiService.detectAnomalies(request)) {
|
||||
is ApiResult.Success -> {
|
||||
val report = AnomalyReport(
|
||||
deviceId = deviceId,
|
||||
timeRange = timeRange,
|
||||
anomalies = listOf(result.data),
|
||||
totalAnomalies = if (result.data.isAnomaly) 1 else 0,
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
ApiResult.Success(report)
|
||||
}
|
||||
is ApiResult.Error -> result
|
||||
ApiResult.Loading -> ApiResult.Loading
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to get anomaly report")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getPerformanceReport(
|
||||
timeRange: TimeRange = TimeRange.LAST_7_DAYS
|
||||
): ApiResult<PerformanceReport> {
|
||||
return try {
|
||||
val backupMetrics = getBackupMetrics()
|
||||
val systemMetrics = getSystemMetrics()
|
||||
|
||||
when {
|
||||
backupMetrics is ApiResult.Success && systemMetrics is ApiResult.Success -> {
|
||||
val report = PerformanceReport(
|
||||
timeRange = timeRange,
|
||||
averageBackupDuration = backupMetrics.data.averageBackupDuration,
|
||||
compressionRatio = backupMetrics.data.compressionRatio,
|
||||
deduplicationRatio = backupMetrics.data.deduplicationRatio,
|
||||
successRate = calculateSuccessRate(backupMetrics.data),
|
||||
systemUtilization = systemMetrics.data.systemUtilization,
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
ApiResult.Success(report)
|
||||
}
|
||||
backupMetrics is ApiResult.Error -> backupMetrics
|
||||
systemMetrics is ApiResult.Error -> systemMetrics
|
||||
else -> ApiResult.Loading
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to get performance report")
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateSuccessRate(metrics: BackupMetrics): Double {
|
||||
val total = metrics.totalBackupsCompleted + metrics.totalBackupsFailed
|
||||
return if (total > 0) {
|
||||
metrics.totalBackupsCompleted.toDouble() / total * 100
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class SettingsRepository @Inject constructor(
|
||||
private val apiService: CoreStateApiService,
|
||||
private val deviceManager: DeviceManager,
|
||||
private val localPreferences: LocalPreferences
|
||||
) {
|
||||
|
||||
suspend fun getConfiguration(): ApiResult<DaemonConfigResponse> {
|
||||
return apiService.getConfiguration()
|
||||
}
|
||||
|
||||
suspend fun updateConfiguration(config: DaemonConfigRequest): ApiResult<ConfigUpdateResponse> {
|
||||
return apiService.updateConfiguration(config)
|
||||
}
|
||||
|
||||
suspend fun getLocalSettings(): LocalSettings {
|
||||
return localPreferences.getSettings()
|
||||
}
|
||||
|
||||
suspend fun updateLocalSettings(settings: LocalSettings) {
|
||||
localPreferences.saveSettings(settings)
|
||||
}
|
||||
|
||||
suspend fun getNotificationSettings(): NotificationSettings {
|
||||
return localPreferences.getNotificationSettings()
|
||||
}
|
||||
|
||||
suspend fun updateNotificationSettings(settings: NotificationSettings) {
|
||||
localPreferences.saveNotificationSettings(settings)
|
||||
}
|
||||
|
||||
suspend fun getSecuritySettings(): SecuritySettings {
|
||||
return localPreferences.getSecuritySettings()
|
||||
}
|
||||
|
||||
suspend fun updateSecuritySettings(settings: SecuritySettings) {
|
||||
localPreferences.saveSecuritySettings(settings)
|
||||
}
|
||||
|
||||
suspend fun exportConfiguration(): ApiResult<String> {
|
||||
return when (val result = getConfiguration()) {
|
||||
is ApiResult.Success -> {
|
||||
try {
|
||||
val json = kotlinx.serialization.json.Json.encodeToString(result.data)
|
||||
ApiResult.Success(json)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to export configuration")
|
||||
}
|
||||
}
|
||||
is ApiResult.Error -> result
|
||||
ApiResult.Loading -> ApiResult.Loading
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun importConfiguration(configJson: String): ApiResult<ConfigUpdateResponse> {
|
||||
return try {
|
||||
val config = kotlinx.serialization.json.Json.decodeFromString<DaemonConfigRequest>(configJson)
|
||||
updateConfiguration(config)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to import configuration")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Device management repository
|
||||
@Singleton
|
||||
class DeviceRepository @Inject constructor(
|
||||
private val apiService: CoreStateApiService,
|
||||
private val deviceManager: DeviceManager
|
||||
) {
|
||||
|
||||
suspend fun registerDevice(): ApiResult<DeviceRegistrationResponse> {
|
||||
return try {
|
||||
val request = DeviceRegistrationRequest(
|
||||
deviceId = deviceManager.getDeviceId(),
|
||||
deviceInfo = deviceManager.getDeviceInfo(),
|
||||
capabilities = deviceManager.getDeviceCapabilities()
|
||||
)
|
||||
apiService.registerDevice(request)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to register device")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getRegisteredDevices(): ApiResult<RegisteredDevicesResponse> {
|
||||
return apiService.getRegisteredDevices()
|
||||
}
|
||||
|
||||
suspend fun getConnectedDevices(): ApiResult<ConnectedDevicesResponse> {
|
||||
return apiService.getConnectedDevices()
|
||||
}
|
||||
|
||||
suspend fun getCurrentDevice(): ApiResult<DeviceInfo> {
|
||||
return try {
|
||||
val deviceInfo = deviceManager.getDeviceInfo()
|
||||
ApiResult.Success(deviceInfo)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to get current device info")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateDeviceStatus(status: DeviceStatus): ApiResult<Unit> {
|
||||
return try {
|
||||
deviceManager.updateStatus(status)
|
||||
ApiResult.Success(Unit)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to update device status")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getKernelModuleStatus(): ApiResult<KernelStatusResponse> {
|
||||
return apiService.getKernelStatus()
|
||||
}
|
||||
|
||||
suspend fun loadKernelModule(): ApiResult<KernelOperationResponse> {
|
||||
return apiService.loadKernelModule()
|
||||
}
|
||||
|
||||
suspend fun unloadKernelModule(): ApiResult<KernelOperationResponse> {
|
||||
return apiService.unloadKernelModule()
|
||||
}
|
||||
}
|
||||
|
||||
// Security repository for encryption and key management
|
||||
@Singleton
|
||||
class SecurityRepository @Inject constructor(
|
||||
private val apiService: CoreStateApiService,
|
||||
private val deviceManager: DeviceManager
|
||||
) {
|
||||
|
||||
suspend fun generateDeviceKey(): ApiResult<DeviceKeyInfo> {
|
||||
val deviceId = deviceManager.getDeviceId()
|
||||
return apiService.generateDeviceKey(deviceId)
|
||||
}
|
||||
|
||||
suspend fun rotateDeviceKey(): ApiResult<DeviceKeyInfo> {
|
||||
val deviceId = deviceManager.getDeviceId()
|
||||
return apiService.rotateDeviceKey(deviceId)
|
||||
}
|
||||
|
||||
suspend fun getDeviceKeys(): ApiResult<DeviceKeysResponse> {
|
||||
val deviceId = deviceManager.getDeviceId()
|
||||
return apiService.getDeviceKeys(deviceId)
|
||||
}
|
||||
|
||||
suspend fun encryptData(data: String): ApiResult<EncryptionResult> {
|
||||
return try {
|
||||
val request = EncryptionRequest(
|
||||
data = data,
|
||||
deviceId = deviceManager.getDeviceId()
|
||||
)
|
||||
apiService.encryptData(request)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to encrypt data")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun decryptData(encryptedData: String, keyId: String? = null): ApiResult<DecryptionResult> {
|
||||
return try {
|
||||
val request = DecryptionRequest(
|
||||
encryptedData = encryptedData,
|
||||
deviceId = deviceManager.getDeviceId(),
|
||||
keyId = keyId
|
||||
)
|
||||
apiService.decryptData(request)
|
||||
} catch (e: Exception) {
|
||||
ApiResult.Error(e, "Failed to decrypt data")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
package com.corestate.androidApp.network
|
||||
|
||||
import com.corestate.androidApp.data.model.*
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
|
||||
interface BackupEngineApi {
|
||||
@POST("api/v1/backup/start")
|
||||
suspend fun startBackup(@Body request: BackupRequest): Response<BackupJobResponse>
|
||||
|
||||
@GET("api/v1/backup/job/{jobId}")
|
||||
suspend fun getJobStatus(@Path("jobId") jobId: String): Response<BackupJobStatus>
|
||||
|
||||
@GET("api/v1/backup/job/{jobId}/progress")
|
||||
suspend fun getProgress(@Path("jobId") jobId: String): Flow<BackupProgress>
|
||||
|
||||
@POST("api/v1/backup/job/{jobId}/pause")
|
||||
suspend fun pauseJob(@Path("jobId") jobId: String): Response<Unit>
|
||||
|
||||
@POST("api/v1/backup/job/{jobId}/resume")
|
||||
suspend fun resumeJob(@Path("jobId") jobId: String): Response<Unit>
|
||||
|
||||
@DELETE("api/v1/backup/job/{jobId}")
|
||||
suspend fun cancelJob(@Path("jobId") jobId: String): Response<Unit>
|
||||
|
||||
@GET("api/v1/backup/jobs")
|
||||
suspend fun listJobs(
|
||||
@Query("page") page: Int = 0,
|
||||
@Query("size") size: Int = 20,
|
||||
@Query("deviceId") deviceId: String? = null,
|
||||
@Query("status") status: String? = null
|
||||
): Response<BackupJobListResponse>
|
||||
|
||||
@POST("api/v1/backup/restore")
|
||||
suspend fun startRestore(@Body request: RestoreRequest): Response<RestoreJobResponse>
|
||||
|
||||
@GET("api/v1/backup/restore/{jobId}")
|
||||
suspend fun getRestoreStatus(@Path("jobId") jobId: String): Response<RestoreJobStatus>
|
||||
|
||||
@GET("api/v1/backup/snapshots")
|
||||
suspend fun listSnapshots(
|
||||
@Query("deviceId") deviceId: String,
|
||||
@Query("page") page: Int = 0,
|
||||
@Query("size") size: Int = 20
|
||||
): Response<SnapshotListResponse>
|
||||
|
||||
@GET("api/v1/backup/snapshot/{snapshotId}/files")
|
||||
suspend fun browseSnapshotFiles(
|
||||
@Path("snapshotId") snapshotId: String,
|
||||
@Query("path") path: String = "/"
|
||||
): Response<FileListResponse>
|
||||
|
||||
@GET("api/v1/backup/health")
|
||||
suspend fun getHealthStatus(): Response<HealthStatus>
|
||||
|
||||
@GET("api/v1/backup/metrics")
|
||||
suspend fun getMetrics(): Response<BackupMetrics>
|
||||
}
|
||||
|
||||
interface EncryptionApi {
|
||||
@POST("api/v1/encrypt")
|
||||
suspend fun encryptData(@Body request: EncryptionRequest): Response<EncryptionResult>
|
||||
|
||||
@POST("api/v1/decrypt")
|
||||
suspend fun decryptData(@Body request: DecryptionRequest): Response<DecryptionResult>
|
||||
|
||||
@POST("api/v1/keys/generate")
|
||||
suspend fun generateKey(@Body request: KeyGenerationRequest): Response<DeviceKeyInfo>
|
||||
|
||||
@POST("api/v1/keys/rotate")
|
||||
suspend fun rotateKey(@Body request: KeyRotationRequest): Response<DeviceKeyInfo>
|
||||
|
||||
@GET("api/v1/keys/{deviceId}")
|
||||
suspend fun getKeyInfo(@Path("deviceId") deviceId: String): Response<DeviceKeysResponse>
|
||||
|
||||
@GET("api/v1/health")
|
||||
suspend fun getHealthStatus(): Response<ServiceHealthStatus>
|
||||
|
||||
@GET("api/v1/metrics")
|
||||
suspend fun getMetrics(): Response<EncryptionMetrics>
|
||||
}
|
||||
|
||||
interface MLOptimizerApi {
|
||||
@POST("predict/backup")
|
||||
suspend fun predictBackup(@Body request: BackupPredictionRequest): Response<BackupPrediction>
|
||||
|
||||
@POST("detect/anomaly")
|
||||
suspend fun detectAnomaly(@Body request: AnomalyDetectionRequest): Response<AnomalyResult>
|
||||
|
||||
@POST("optimize/schedule")
|
||||
suspend fun optimizeSchedule(@Body request: ScheduleOptimizationRequest): Response<OptimizationResult>
|
||||
|
||||
@GET("models/status")
|
||||
suspend fun getModelStatus(): Response<ModelStatusResponse>
|
||||
|
||||
@GET("health")
|
||||
suspend fun getHealthStatus(): Response<ServiceHealthStatus>
|
||||
|
||||
@GET("metrics")
|
||||
suspend fun getMetrics(): Response<MLMetrics>
|
||||
}
|
||||
|
||||
interface SyncCoordinatorApi {
|
||||
@GET("health")
|
||||
suspend fun getHealthStatus(): Response<ServiceHealthStatus>
|
||||
|
||||
@GET("metrics")
|
||||
suspend fun getMetrics(): Response<SyncMetrics>
|
||||
|
||||
@GET("devices")
|
||||
suspend fun getConnectedDevices(): Response<ConnectedDevicesResponse>
|
||||
|
||||
@POST("sync/manual")
|
||||
suspend fun triggerManualSync(@Body request: ManualSyncRequest): Response<SyncResult>
|
||||
|
||||
@GET("sync/status")
|
||||
suspend fun getSyncStatus(): Response<SyncStatusResponse>
|
||||
}
|
||||
|
||||
interface StorageHalApi {
|
||||
@POST("storage/store")
|
||||
suspend fun storeChunk(@Body request: StoreChunkRequest): Response<StorageResult>
|
||||
|
||||
@GET("storage/retrieve/{chunkId}")
|
||||
suspend fun retrieveChunk(@Path("chunkId") chunkId: String): Response<ChunkData>
|
||||
|
||||
@DELETE("storage/delete/{chunkId}")
|
||||
suspend fun deleteChunk(@Path("chunkId") chunkId: String): Response<Unit>
|
||||
|
||||
@GET("storage/health")
|
||||
suspend fun getHealthStatus(): Response<StorageHealthStatus>
|
||||
|
||||
@GET("storage/metrics")
|
||||
suspend fun getMetrics(): Response<StorageMetrics>
|
||||
|
||||
@GET("storage/usage")
|
||||
suspend fun getStorageUsage(@Query("deviceId") deviceId: String): Response<StorageUsageResponse>
|
||||
}
|
||||
|
||||
interface CompressionEngineApi {
|
||||
@POST("compress")
|
||||
suspend fun compressData(@Body request: CompressionRequest): Response<CompressionResult>
|
||||
|
||||
@POST("decompress")
|
||||
suspend fun decompressData(@Body request: DecompressionRequest): Response<DecompressionResult>
|
||||
|
||||
@GET("algorithms")
|
||||
suspend fun getSupportedAlgorithms(): Response<CompressionAlgorithmsResponse>
|
||||
|
||||
@GET("health")
|
||||
suspend fun getHealthStatus(): Response<ServiceHealthStatus>
|
||||
|
||||
@GET("metrics")
|
||||
suspend fun getMetrics(): Response<CompressionMetrics>
|
||||
}
|
||||
|
||||
interface DeduplicationApi {
|
||||
@POST("deduplicate")
|
||||
suspend fun deduplicateChunks(@Body request: DeduplicationRequest): Response<DeduplicationResult>
|
||||
|
||||
@GET("stats")
|
||||
suspend fun getDeduplicationStats(@Query("deviceId") deviceId: String): Response<DeduplicationStats>
|
||||
|
||||
@GET("health")
|
||||
suspend fun getHealthStatus(): Response<ServiceHealthStatus>
|
||||
|
||||
@GET("metrics")
|
||||
suspend fun getMetrics(): Response<DeduplicationMetrics>
|
||||
}
|
||||
|
||||
interface DaemonApi {
|
||||
@GET("status")
|
||||
suspend fun getSystemStatus(): Response<SystemStatusInfo>
|
||||
|
||||
@GET("logs")
|
||||
suspend fun getLogs(
|
||||
@Query("level") level: String = "info",
|
||||
@Query("lines") lines: Int = 100
|
||||
): Response<LogDataResponse>
|
||||
|
||||
@GET("config")
|
||||
suspend fun getConfiguration(): Response<DaemonConfigResponse>
|
||||
|
||||
@PUT("config")
|
||||
suspend fun updateConfiguration(@Body config: DaemonConfigRequest): Response<ConfigUpdateResponse>
|
||||
|
||||
@GET("kernel/status")
|
||||
suspend fun getKernelStatus(): Response<KernelStatusResponse>
|
||||
|
||||
@POST("kernel/load")
|
||||
suspend fun loadKernelModule(): Response<KernelOperationResponse>
|
||||
|
||||
@POST("kernel/unload")
|
||||
suspend fun unloadKernelModule(): Response<KernelOperationResponse>
|
||||
|
||||
@GET("files")
|
||||
suspend fun listFiles(@Query("path") path: String): Response<FileListResponse>
|
||||
|
||||
@POST("backup/start")
|
||||
suspend fun startBackupViaDaemon(@Body request: DaemonBackupRequest): Response<DaemonBackupResponse>
|
||||
|
||||
@GET("devices")
|
||||
suspend fun getRegisteredDevices(): Response<RegisteredDevicesResponse>
|
||||
|
||||
@POST("devices/register")
|
||||
suspend fun registerDevice(@Body request: DeviceRegistrationRequest): Response<DeviceRegistrationResponse>
|
||||
}
|
||||
|
||||
// Aggregated API service that combines all microservices
|
||||
interface CoreStateApiService {
|
||||
// Backup operations
|
||||
suspend fun startBackup(request: BackupRequest): ApiResult<BackupJobResponse>
|
||||
suspend fun getBackupStatus(jobId: String): ApiResult<BackupJobStatus>
|
||||
suspend fun getBackupProgress(jobId: String): Flow<BackupProgress>
|
||||
suspend fun pauseBackup(jobId: String): ApiResult<Unit>
|
||||
suspend fun resumeBackup(jobId: String): ApiResult<Unit>
|
||||
suspend fun cancelBackup(jobId: String): ApiResult<Unit>
|
||||
suspend fun listBackups(page: Int = 0, size: Int = 20): ApiResult<BackupJobListResponse>
|
||||
|
||||
// Restore operations
|
||||
suspend fun startRestore(request: RestoreRequest): ApiResult<RestoreJobResponse>
|
||||
suspend fun getRestoreStatus(jobId: String): ApiResult<RestoreJobStatus>
|
||||
|
||||
// File management
|
||||
suspend fun listFiles(path: String): ApiResult<FileListResponse>
|
||||
suspend fun browseSnapshot(snapshotId: String, path: String = "/"): ApiResult<FileListResponse>
|
||||
suspend fun listSnapshots(deviceId: String, page: Int = 0, size: Int = 20): ApiResult<SnapshotListResponse>
|
||||
|
||||
// System management
|
||||
suspend fun getSystemStatus(): ApiResult<SystemStatusInfo>
|
||||
suspend fun getSystemLogs(level: String = "info", lines: Int = 100): ApiResult<LogDataResponse>
|
||||
suspend fun getConfiguration(): ApiResult<DaemonConfigResponse>
|
||||
suspend fun updateConfiguration(config: DaemonConfigRequest): ApiResult<ConfigUpdateResponse>
|
||||
|
||||
// Kernel module management
|
||||
suspend fun getKernelStatus(): ApiResult<KernelStatusResponse>
|
||||
suspend fun loadKernelModule(): ApiResult<KernelOperationResponse>
|
||||
suspend fun unloadKernelModule(): ApiResult<KernelOperationResponse>
|
||||
|
||||
// Device management
|
||||
suspend fun getRegisteredDevices(): ApiResult<RegisteredDevicesResponse>
|
||||
suspend fun registerDevice(request: DeviceRegistrationRequest): ApiResult<DeviceRegistrationResponse>
|
||||
suspend fun getConnectedDevices(): ApiResult<ConnectedDevicesResponse>
|
||||
|
||||
// Security operations
|
||||
suspend fun encryptData(request: EncryptionRequest): ApiResult<EncryptionResult>
|
||||
suspend fun decryptData(request: DecryptionRequest): ApiResult<DecryptionResult>
|
||||
suspend fun generateDeviceKey(deviceId: String): ApiResult<DeviceKeyInfo>
|
||||
suspend fun rotateDeviceKey(deviceId: String): ApiResult<DeviceKeyInfo>
|
||||
suspend fun getDeviceKeys(deviceId: String): ApiResult<DeviceKeysResponse>
|
||||
|
||||
// ML and Analytics
|
||||
suspend fun predictBackupPerformance(request: BackupPredictionRequest): ApiResult<BackupPrediction>
|
||||
suspend fun detectAnomalies(request: AnomalyDetectionRequest): ApiResult<AnomalyResult>
|
||||
suspend fun optimizeBackupSchedule(request: ScheduleOptimizationRequest): ApiResult<OptimizationResult>
|
||||
suspend fun getMLModelStatus(): ApiResult<ModelStatusResponse>
|
||||
|
||||
// Storage operations
|
||||
suspend fun getStorageUsage(deviceId: String): ApiResult<StorageUsageResponse>
|
||||
suspend fun getStorageMetrics(): ApiResult<StorageMetrics>
|
||||
|
||||
// Sync operations
|
||||
suspend fun getSyncStatus(): ApiResult<SyncStatusResponse>
|
||||
suspend fun triggerManualSync(deviceId: String): ApiResult<SyncResult>
|
||||
|
||||
// Health and metrics
|
||||
suspend fun getAllServicesHealth(): ApiResult<ServicesHealthResponse>
|
||||
suspend fun getSystemMetrics(): ApiResult<SystemMetricsResponse>
|
||||
|
||||
// Real-time updates
|
||||
fun subscribeToBackupProgress(jobId: String): Flow<BackupProgress>
|
||||
fun subscribeToSystemEvents(): Flow<SystemEvent>
|
||||
fun subscribeToSyncEvents(): Flow<SyncEvent>
|
||||
}
|
||||
|
||||
sealed class ApiResult<out T> {
|
||||
data class Success<T>(val data: T) : ApiResult<T>()
|
||||
data class Error(val exception: Throwable, val message: String? = null) : ApiResult<Nothing>()
|
||||
object Loading : ApiResult<Nothing>()
|
||||
}
|
||||
|
||||
// Extension functions for easier result handling
|
||||
inline fun <T> ApiResult<T>.onSuccess(action: (T) -> Unit): ApiResult<T> {
|
||||
if (this is ApiResult.Success) action(data)
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun <T> ApiResult<T>.onError(action: (Throwable, String?) -> Unit): ApiResult<T> {
|
||||
if (this is ApiResult.Error) action(exception, message)
|
||||
return this
|
||||
}
|
||||
|
||||
inline fun <T> ApiResult<T>.onLoading(action: () -> Unit): ApiResult<T> {
|
||||
if (this is ApiResult.Loading) action()
|
||||
return this
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
package com.corestate.androidApp.ui.screens.admin
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.corestate.androidApp.data.model.*
|
||||
import com.corestate.androidApp.ui.components.*
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SystemAdminScreen(
|
||||
viewModel: SystemAdminViewModel = hiltViewModel()
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.loadSystemStatus()
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "System Administration",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// System Status Overview
|
||||
item {
|
||||
SystemStatusCard(
|
||||
systemStatus = uiState.systemStatus,
|
||||
isLoading = uiState.isLoading
|
||||
)
|
||||
}
|
||||
|
||||
// Service Management
|
||||
item {
|
||||
ServiceManagementCard(
|
||||
services = uiState.services,
|
||||
onServiceAction = viewModel::performServiceAction
|
||||
)
|
||||
}
|
||||
|
||||
// Kernel Module Management
|
||||
item {
|
||||
KernelModuleCard(
|
||||
kernelStatus = uiState.kernelStatus,
|
||||
onLoadModule = viewModel::loadKernelModule,
|
||||
onUnloadModule = viewModel::unloadKernelModule,
|
||||
isLoading = uiState.kernelOperationInProgress
|
||||
)
|
||||
}
|
||||
|
||||
// Device Management
|
||||
item {
|
||||
DeviceManagementCard(
|
||||
devices = uiState.connectedDevices,
|
||||
onRefresh = viewModel::refreshDevices
|
||||
)
|
||||
}
|
||||
|
||||
// Configuration Management
|
||||
item {
|
||||
ConfigurationCard(
|
||||
configuration = uiState.configuration,
|
||||
onUpdateConfig = viewModel::updateConfiguration,
|
||||
onExportConfig = viewModel::exportConfiguration,
|
||||
onImportConfig = viewModel::importConfiguration
|
||||
)
|
||||
}
|
||||
|
||||
// System Logs
|
||||
item {
|
||||
SystemLogsCard(
|
||||
logs = uiState.systemLogs,
|
||||
onRefreshLogs = viewModel::refreshLogs,
|
||||
onClearLogs = viewModel::clearLogs
|
||||
)
|
||||
}
|
||||
|
||||
// Performance Monitoring
|
||||
item {
|
||||
PerformanceMonitoringCard(
|
||||
metrics = uiState.performanceMetrics,
|
||||
onRefresh = viewModel::refreshMetrics
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SystemStatusCard(
|
||||
systemStatus: SystemStatusInfo?,
|
||||
isLoading: Boolean
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "System Status",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
if (isLoading) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(20.dp))
|
||||
} else {
|
||||
systemStatus?.let { status ->
|
||||
StatusIndicator(
|
||||
isHealthy = status.daemonUptime > 0 && status.servicesStatus.values.all { it },
|
||||
text = if (status.daemonUptime > 0) "Online" else "Offline"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
systemStatus?.let { status ->
|
||||
StatusMetricRow("Daemon Uptime", formatUptime(status.daemonUptime))
|
||||
StatusMetricRow("Active Backups", status.activeBackups.toString())
|
||||
StatusMetricRow("Total Files Backed Up", formatNumber(status.totalFilesBackedUp))
|
||||
StatusMetricRow("Total Backup Size", formatBytes(status.totalBackupSize))
|
||||
StatusMetricRow("Memory Usage", formatBytes(status.memoryUsage))
|
||||
StatusMetricRow("CPU Usage", "${status.cpuUsage}%")
|
||||
StatusMetricRow("Kernel Module", if (status.kernelModuleLoaded) "Loaded" else "Not Loaded")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ServiceManagementCard(
|
||||
services: Map<String, ServiceStatus>,
|
||||
onServiceAction: (String, ServiceAction) -> Unit
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Microservices",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
services.forEach { (serviceName, status) ->
|
||||
ServiceRow(
|
||||
serviceName = serviceName,
|
||||
status = status,
|
||||
onAction = { action -> onServiceAction(serviceName, action) }
|
||||
)
|
||||
|
||||
if (serviceName != services.keys.last()) {
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ServiceRow(
|
||||
serviceName: String,
|
||||
status: ServiceStatus,
|
||||
onAction: (ServiceAction) -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = formatServiceName(serviceName),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = "Response: ${status.responseTime}ms",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
StatusIndicator(
|
||||
isHealthy = status.isHealthy,
|
||||
text = if (status.isHealthy) "Healthy" else "Error"
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = { onAction(ServiceAction.RESTART) }
|
||||
) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = "Restart Service")
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = { onAction(ServiceAction.VIEW_LOGS) }
|
||||
) {
|
||||
Icon(Icons.Default.Description, contentDescription = "View Logs")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun KernelModuleCard(
|
||||
kernelStatus: KernelStatusResponse?,
|
||||
onLoadModule: () -> Unit,
|
||||
onUnloadModule: () -> Unit,
|
||||
isLoading: Boolean
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Kernel Module",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
kernelStatus?.let { status ->
|
||||
StatusMetricRow("Status", if (status.loaded) "Loaded" else "Not Loaded")
|
||||
StatusMetricRow("Version", status.version)
|
||||
|
||||
if (status.features.isNotEmpty()) {
|
||||
Text(
|
||||
text = "Features:",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
)
|
||||
|
||||
status.features.forEach { feature ->
|
||||
Text(
|
||||
text = "• $feature",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (status.loaded) {
|
||||
Button(
|
||||
onClick = onUnloadModule,
|
||||
enabled = !isLoading,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
) {
|
||||
if (isLoading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp),
|
||||
color = MaterialTheme.colorScheme.onError
|
||||
)
|
||||
} else {
|
||||
Text("Unload Module")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Button(
|
||||
onClick = onLoadModule,
|
||||
enabled = !isLoading
|
||||
) {
|
||||
if (isLoading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
} else {
|
||||
Text("Load Module")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeviceManagementCard(
|
||||
devices: List<ConnectedDevice>,
|
||||
onRefresh: () -> Unit
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "Connected Devices",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
IconButton(onClick = onRefresh) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = "Refresh Devices")
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
if (devices.isEmpty()) {
|
||||
Text(
|
||||
text = "No devices connected",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
} else {
|
||||
devices.forEach { device ->
|
||||
DeviceRow(device)
|
||||
if (device != devices.last()) {
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeviceRow(device: ConnectedDevice) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = device.deviceName,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = device.deviceId,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = "Last seen: ${formatTimestamp(device.lastSeen)}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
StatusIndicator(
|
||||
isHealthy = device.isOnline,
|
||||
text = if (device.isOnline) "Online" else "Offline"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StatusIndicator(
|
||||
isHealthy: Boolean,
|
||||
text: String
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(8.dp)
|
||||
.background(
|
||||
color = if (isHealthy) Color.Green else Color.Red,
|
||||
shape = androidx.compose.foundation.shape.CircleShape
|
||||
)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = if (isHealthy) Color.Green else Color.Red
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StatusMetricRow(label: String, value: String) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 2.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
private fun formatUptime(uptimeSeconds: Long): String {
|
||||
val days = uptimeSeconds / 86400
|
||||
val hours = (uptimeSeconds % 86400) / 3600
|
||||
val minutes = (uptimeSeconds % 3600) / 60
|
||||
|
||||
return when {
|
||||
days > 0 -> "${days}d ${hours}h ${minutes}m"
|
||||
hours > 0 -> "${hours}h ${minutes}m"
|
||||
else -> "${minutes}m"
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatBytes(bytes: Long): String {
|
||||
val units = arrayOf("B", "KB", "MB", "GB", "TB")
|
||||
var size = bytes.toDouble()
|
||||
var unitIndex = 0
|
||||
|
||||
while (size >= 1024 && unitIndex < units.size - 1) {
|
||||
size /= 1024
|
||||
unitIndex++
|
||||
}
|
||||
|
||||
return "%.1f %s".format(size, units[unitIndex])
|
||||
}
|
||||
|
||||
private fun formatNumber(number: Long): String {
|
||||
return when {
|
||||
number >= 1_000_000 -> "%.1fM".format(number / 1_000_000.0)
|
||||
number >= 1_000 -> "%.1fK".format(number / 1_000.0)
|
||||
else -> number.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatServiceName(serviceName: String): String {
|
||||
return serviceName.split("-", "_")
|
||||
.joinToString(" ") { it.replaceFirstChar { char -> char.uppercase() } }
|
||||
}
|
||||
|
||||
private fun formatTimestamp(timestamp: Long): String {
|
||||
val now = System.currentTimeMillis()
|
||||
val diff = now - timestamp
|
||||
|
||||
return when {
|
||||
diff < 60_000 -> "Just now"
|
||||
diff < 3_600_000 -> "${diff / 60_000}m ago"
|
||||
diff < 86_400_000 -> "${diff / 3_600_000}h ago"
|
||||
else -> "${diff / 86_400_000}d ago"
|
||||
}
|
||||
}
|
||||
|
||||
enum class ServiceAction {
|
||||
RESTART,
|
||||
VIEW_LOGS,
|
||||
CONFIGURE
|
||||
}
|
||||
Reference in New Issue
Block a user