diff --git a/.github/README.md b/.github/README.md index 8f7bb4fd4..0e371df36 100644 --- a/.github/README.md +++ b/.github/README.md @@ -56,10 +56,10 @@ For huge public repositories you can adjust memory limit and even size of used t ```bash # Launching a standalone JAR file -$ java -Xmx16M -jar reposilite-3.0.0-alpha.3.jar +$ java -Xmx16M -jar reposilite-3.0.0-alpha.4.jar # Using a Docker -$ docker pull dzikoysk/reposilite:3.0.0-alpha.3 +$ docker pull dzikoysk/reposilite:3.0.0-alpha.4 ``` Visit official guide to read more about extra parameters and configuration details. diff --git a/docker-compose.yml b/docker-compose.yml index 15382118b..b5393825e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.9" services: reposilite: - image: reposilite:3.0.0-alpha.3 + image: reposilite:3.0.0-alpha.4 build: context: . dockerfile: Dockerfile diff --git a/docs/docs/docker.html b/docs/docs/docker.html index 41a4662d8..1fa8a07be 100644 --- a/docs/docs/docker.html +++ b/docs/docs/docker.html @@ -61,7 +61,7 @@
First of all, you have to pull the image from DockerHub:
-// released builds, e.g. 3.0.0-alpha.3
+// released builds, e.g. 3.0.0-alpha.4
$ docker pull dzikoysk/reposilite:3.0.0-SNAPSHOT
// nightly builds
diff --git a/docs/docs/docker/index.html b/docs/docs/docker/index.html
index 41a4662d8..1fa8a07be 100644
--- a/docs/docs/docker/index.html
+++ b/docs/docs/docker/index.html
@@ -61,7 +61,7 @@
Installation
First of all, you have to pull the image from DockerHub:
-// released builds, e.g. 3.0.0-alpha.3
+// released builds, e.g. 3.0.0-alpha.4
$ docker pull dzikoysk/reposilite:3.0.0-SNAPSHOT
// nightly builds
diff --git a/reposilite-backend/.run/Reposilite.run.xml b/reposilite-backend/.run/Reposilite.run.xml
index f578e6e73..7fa1dd4fb 100644
--- a/reposilite-backend/.run/Reposilite.run.xml
+++ b/reposilite-backend/.run/Reposilite.run.xml
@@ -5,7 +5,7 @@
-
+
diff --git a/reposilite-backend/build.gradle.kts b/reposilite-backend/build.gradle.kts
index 5bf9a46a0..a2d3749ab 100644
--- a/reposilite-backend/build.gradle.kts
+++ b/reposilite-backend/build.gradle.kts
@@ -20,7 +20,7 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
group = "org.panda-lang"
-version = "3.0.0-alpha.3"
+version = "3.0.0-alpha.4"
plugins {
`java-library`
@@ -52,7 +52,7 @@ tasks.withType().configureEach {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect:1.5.21")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.21")
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
+ // implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
val expressible = "1.0.17"
implementation("org.panda-lang:expressible:$expressible")
@@ -84,9 +84,10 @@ dependencies {
implementation("io.javalin-rfc:javalin-openapi-plugin:$openapi")
implementation("io.javalin-rfc:javalin-swagger-plugin:$openapi")
- val javalinRfcs = "4.0.27"
+ val javalinRfcs = "4.1.0"
implementation("com.reposilite.javalin-rfcs:javalin-context:$javalinRfcs")
- implementation("com.reposilite.javalin-rfcs:javalin-reactive-routing:$javalinRfcs")
+ implementation("com.reposilite.javalin-rfcs:javalin-routing:$javalinRfcs")
+ //implementation("com.reposilite.javalin-rfcs:javalin-reactive-routing:$javalinRfcs")
// val javalin = "4.0.0.RC3"
// implementation("io.javalin:javalin:$javalin")
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/Reposilite.kt b/reposilite-backend/src/main/kotlin/com/reposilite/Reposilite.kt
index 12c81197e..086b951f8 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/Reposilite.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/Reposilite.kt
@@ -30,22 +30,19 @@ import com.reposilite.status.StatusFacade
import com.reposilite.token.AccessTokenFacade
import com.reposilite.web.JavalinWebServer
import com.reposilite.web.WebConfiguration
-import com.reposilite.web.coroutines.ExclusiveDispatcher
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
import org.jetbrains.exposed.sql.Database
import panda.utilities.console.Effect
-import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ExecutorService
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.atomic.AtomicBoolean
-const val VERSION = "3.0.0-alpha.3"
+const val VERSION = "3.0.0-alpha.4"
class Reposilite(
val journalist: ReposiliteJournalist,
val parameters: ReposiliteParameters,
val configuration: Configuration,
- val ioDispatcher: ExclusiveDispatcher?,
+ val ioService: ExecutorService,
val scheduler: ScheduledExecutorService,
val database: Database,
val webServer: JavalinWebServer,
@@ -66,7 +63,7 @@ class Reposilite(
alive.peek { shutdown() }
}
- suspend fun launch() {
+ fun launch() {
load()
start()
}
@@ -83,7 +80,7 @@ class Reposilite(
logger.info("Platform: ${System.getProperty("java.version")} (${System.getProperty("os.name")})")
logger.info("Working directory: ${parameters.workingDirectory.toAbsolutePath()}")
- logger.info("Mode: ${if (configuration.reactiveMode) "Reactive" else "Blocking"}")
+ logger.info("Threads: ${configuration.webThreadPool} WEB / ${configuration.ioThreadPool} IO")
logger.info("")
logger.info("--- Loading domain configurations")
@@ -97,7 +94,7 @@ class Reposilite(
logger.info("")
}
- private suspend fun start(): Reposilite {
+ private fun start(): Reposilite {
alive.set(true)
Thread.currentThread().name = "Reposilite | Main Thread"
@@ -111,17 +108,13 @@ class Reposilite(
logger.info("")
consoleFacade.executeCommand("help")
- val task = suspend {
+ ioService.execute {
logger.info("")
logger.info("Collecting status metrics...")
logger.info("")
consoleFacade.executeCommand("status")
logger.info("")
}
-
- ioDispatcher
- ?.also { withContext(ioDispatcher) { task() } }
- ?: CompletableFuture.runAsync { runBlocking { task() } }
} catch (exception: Exception) {
logger.error("Failed to start Reposilite")
logger.exception(exception)
@@ -137,11 +130,11 @@ class Reposilite(
alive.set(false)
logger.info("Shutting down ${parameters.hostname}::${parameters.port}...")
scheduler.shutdown()
- ioDispatcher?.prepareShutdown()
+ ioService.shutdown()
webs.forEach { it.dispose(this@Reposilite) }
webServer.stop()
scheduler.shutdownNow()
- ioDispatcher?.completeShutdown()
+ ioService.shutdownNow()
journalist.shutdown()
}
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/ReposiliteFactory.kt b/reposilite-backend/src/main/kotlin/com/reposilite/ReposiliteFactory.kt
index 8ed4e47c2..faab2b991 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/ReposiliteFactory.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/ReposiliteFactory.kt
@@ -27,18 +27,15 @@ import com.reposilite.journalist.Journalist
import com.reposilite.journalist.backend.PrintStreamLogger
import com.reposilite.maven.application.MavenWebConfiguration
import com.reposilite.shared.HttpRemoteClient
+import com.reposilite.shared.newFixedThreadPool
+import com.reposilite.shared.newSingleThreadScheduledExecutor
import com.reposilite.statistics.application.StatisticsWebConfiguration
import com.reposilite.status.application.FailureWebConfiguration
import com.reposilite.status.application.StatusWebConfiguration
import com.reposilite.token.application.AccessTokenWebConfiguration
import com.reposilite.web.JavalinWebServer
import com.reposilite.web.WebConfiguration
-import com.reposilite.web.coroutines.ExclusiveDispatcher
import com.reposilite.web.web
-import java.util.concurrent.Executors
-import java.util.concurrent.LinkedBlockingQueue
-import java.util.concurrent.ThreadPoolExecutor
-import java.util.concurrent.TimeUnit.MILLISECONDS
object ReposiliteFactory {
@@ -51,33 +48,29 @@ object ReposiliteFactory {
fun createReposilite(parameters: ReposiliteParameters, journalist: Journalist, configuration: Configuration): Reposilite {
parameters.applyLoadedConfiguration(configuration)
-
- val webServer = JavalinWebServer()
val logger = ReposiliteJournalist(journalist, configuration.cachedLogSize, parameters.testEnv)
- val scheduler = Executors.newSingleThreadScheduledExecutor()
- val database = DatabaseSourceConfiguration.createConnection(parameters.workingDirectory, configuration.database)
- val ioDispatcher =
- if (configuration.reactiveMode)
- ExclusiveDispatcher(ThreadPoolExecutor(2, configuration.ioThreadPool, 0L, MILLISECONDS, LinkedBlockingQueue()))
- else
- null
+ val scheduler = newSingleThreadScheduledExecutor("Reposilite | Scheduler")
+ val ioService = newFixedThreadPool(2, configuration.ioThreadPool, "Reposilite | IO")
+ val database = DatabaseSourceConfiguration.createConnection(parameters.workingDirectory, configuration.database)
+ val webServer = JavalinWebServer()
val webs = mutableListOf()
+
val statusFacade = web(webs, StatusWebConfiguration) { createFacade(parameters.testEnv, webServer) }
val failureFacade = web(webs, FailureWebConfiguration) { createFacade(logger) }
val consoleFacade = web(webs, ConsoleWebConfiguration) { createFacade(logger, failureFacade) }
val mavenFacade = web(webs, MavenWebConfiguration) { createFacade(logger, parameters.workingDirectory, HttpRemoteClient(logger), configuration.repositories) }
val frontendFacade = web(webs, FrontendWebConfiguration) { createFacade(configuration) }
- val statisticFacade = web(webs, StatisticsWebConfiguration) { createFacade(logger, ioDispatcher, database) }
- val accessTokenFacade = web(webs, AccessTokenWebConfiguration) { createFacade(ioDispatcher, database) }
+ val statisticFacade = web(webs, StatisticsWebConfiguration) { createFacade(logger, database) }
+ val accessTokenFacade = web(webs, AccessTokenWebConfiguration) { createFacade(database) }
val authenticationFacade = web(webs, AuthenticationWebConfiguration) { createFacade(logger, accessTokenFacade) }
return Reposilite(
journalist = logger,
parameters = parameters,
configuration = configuration,
- ioDispatcher = ioDispatcher,
+ ioService = ioService,
scheduler = scheduler,
database = database,
webServer = webServer,
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/ReposiliteLauncher.kt b/reposilite-backend/src/main/kotlin/com/reposilite/ReposiliteLauncher.kt
index 8878e1ecf..e4cda8aad 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/ReposiliteLauncher.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/ReposiliteLauncher.kt
@@ -15,7 +15,6 @@
*/
package com.reposilite
-import kotlinx.coroutines.runBlocking
import picocli.CommandLine
fun createWithParameters(vararg args: String): Reposilite? {
@@ -35,6 +34,6 @@ fun createWithParameters(vararg args: String): Reposilite? {
return ReposiliteFactory.createReposilite(parameters)
}
-fun main(args: Array): Unit = runBlocking {
+fun main(args: Array) {
createWithParameters(*args)?.launch()
}
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/auth/AuthenticationFacade.kt b/reposilite-backend/src/main/kotlin/com/reposilite/auth/AuthenticationFacade.kt
index 67e0f78d0..9d51de74c 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/auth/AuthenticationFacade.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/auth/AuthenticationFacade.kt
@@ -28,22 +28,21 @@ import com.reposilite.web.http.extractFromString
import io.javalin.http.HttpCode.UNAUTHORIZED
import panda.std.Result
import panda.std.asSuccess
-import panda.std.coroutines.rxFlatMap
class AuthenticationFacade internal constructor(
private val journalist: Journalist,
private val accessTokenFacade: AccessTokenFacade
) : Journalist {
- suspend fun authenticateByHeader(headers: Map): Result =
+ fun authenticateByHeader(headers: Map): Result =
extractFromHeaders(headers)
- .rxFlatMap { (name, secret) -> authenticateByCredentials(name, secret) }
+ .flatMap { (name, secret) -> authenticateByCredentials(name, secret) }
- suspend fun authenticateByCredentials(credentials: String): Result =
+ fun authenticateByCredentials(credentials: String): Result =
extractFromString(credentials)
- .rxFlatMap { (name, secret) -> authenticateByCredentials(name, secret) }
+ .flatMap { (name, secret) -> authenticateByCredentials(name, secret) }
- suspend fun authenticateByCredentials(name: String, secret: String): Result =
+ fun authenticateByCredentials(name: String, secret: String): Result =
accessTokenFacade.getToken(name)
?.takeIf { B_CRYPT_TOKENS_ENCODER.matches(secret, it.secret) }
?.asSuccess()
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/config/Configuration.kt b/reposilite-backend/src/main/kotlin/com/reposilite/config/Configuration.kt
index 94080df5e..d7e3935d4 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/config/Configuration.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/config/Configuration.kt
@@ -264,20 +264,21 @@ class Configuration : Serializable {
/* Performance */
- @Description(
- "",
- "# Note: It might be hard to estimate the best amount of threads for your use case,",
- "# but you can safely increase amount of threads if needed and Reposilite will create only as much as it needs.",
- "# This option might be more useful to limit available memory resources to minimum (1 thread requires around 200kb to 1MB of memory)",
- "",
- "# By default, Reposilite 3.x uses experimental reactive mode to maximize performance of each spawned thread.",
- "# If you've noticed various unresolved behaviours like freezing and deadlocking, you can switch to the standard blocking mode.",
- "# Remember: Blocking mode requires more resources (threads) to handle the same throughput. "
- )
- @JvmField
- var reactiveMode = true
+// @Description(
+// "",
+// "# Note: It might be hard to estimate the best amount of threads for your use case,",
+// "# but you can safely increase amount of threads if needed and Reposilite will create only as much as it needs.",
+// "# This option might be more useful to limit available memory resources to minimum (1 thread requires around 200kb to 1MB of memory)",
+// "",
+// "# By default, Reposilite 3.x uses experimental reactive mode to maximize performance of each spawned thread.",
+// "# If you've noticed various unresolved behaviours like freezing and deadlocking, you can switch to the standard blocking mode.",
+// "# Remember: Blocking mode requires more resources (threads) to handle the same throughput. "
+// )
+// @JvmField
+// var reactiveMode = true
@Description(
+ "",
"# Max amount of threads used by core thread pool (min: 4)",
"# The web thread pool handles first few steps of incoming http connections, as soon as possible all tasks are redirected to IO thread pool."
)
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/console/CommandExecutor.kt b/reposilite-backend/src/main/kotlin/com/reposilite/console/CommandExecutor.kt
index eaa07661b..ecf64bc33 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/console/CommandExecutor.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/console/CommandExecutor.kt
@@ -22,7 +22,6 @@ import com.reposilite.console.api.ReposiliteCommand
import com.reposilite.journalist.Journalist
import com.reposilite.journalist.Logger
import com.reposilite.status.FailureFacade
-import kotlinx.coroutines.CoroutineDispatcher
import picocli.CommandLine
import picocli.CommandLine.Command
import picocli.CommandLine.MissingParameterException
@@ -40,10 +39,10 @@ internal class CommandExecutor(
private val consoleThread = ConsoleThread(this, source, failureFacade)
private val commandExecutor = CommandLine(this)
- suspend fun execute(command: String): ExecutionResponse =
+ fun execute(command: String): ExecutionResponse =
execute(command) { logger.info(it) }
- suspend fun execute(command: String, outputConsumer: Consumer): ExecutionResponse =
+ fun execute(command: String, outputConsumer: Consumer): ExecutionResponse =
executeCommand(command).also {
it.response.forEach { message ->
message.replace(System.lineSeparator(), "\n").split("\n").toTypedArray().forEach { line ->
@@ -52,7 +51,7 @@ internal class CommandExecutor(
}
}
- private suspend fun executeCommand(command: String): ExecutionResponse {
+ private fun executeCommand(command: String): ExecutionResponse {
val processedCommand = command.trim()
if (processedCommand.isEmpty()) {
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/console/ConsoleFacade.kt b/reposilite-backend/src/main/kotlin/com/reposilite/console/ConsoleFacade.kt
index 69ea23093..3ff02c28b 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/console/ConsoleFacade.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/console/ConsoleFacade.kt
@@ -35,7 +35,7 @@ class ConsoleFacade internal constructor(
internal val commandExecutor: CommandExecutor
) : Journalist {
- suspend fun executeCommand(command: String): Result {
+ fun executeCommand(command: String): Result {
if (StringUtils.isEmpty(command)) {
return errorResponse(BAD_REQUEST, "Missing command")
}
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/console/ConsoleThread.kt b/reposilite-backend/src/main/kotlin/com/reposilite/console/ConsoleThread.kt
index 23bcc5953..64e6bca7a 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/console/ConsoleThread.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/console/ConsoleThread.kt
@@ -16,7 +16,6 @@
package com.reposilite.console
import com.reposilite.status.FailureFacade
-import kotlinx.coroutines.runBlocking
import java.io.InputStream
import java.util.Scanner
@@ -40,27 +39,6 @@ internal class ConsoleThread(
return
}
- /*
- val reader = LineReaderBuilder.builder().build();
- val prompt = "$"
-
- while (!isInterrupted && input.hasNextLine()) {
- try {
- val line = reader.readLine(prompt)
-
- runBlocking {
- console.logger.info("")
- console.execute(line)
- console.logger.info("")
- }
- } catch (e: UserInterruptException) {
- // Ignore
- } catch (e: EndOfFileException) {
- return
- }
- }
- */
-
do {
val command = input.nextLine().trim()
@@ -69,11 +47,9 @@ internal class ConsoleThread(
}
runCatching {
- runBlocking {
- commandExecutor.logger.info("")
- commandExecutor.execute(command)
- commandExecutor.logger.info("")
- }
+ commandExecutor.logger.info("")
+ commandExecutor.execute(command)
+ commandExecutor.logger.info("")
}.onFailure {
failureFacade.throwException("Command: $command", it)
}
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/console/StandardCommands.kt b/reposilite-backend/src/main/kotlin/com/reposilite/console/StandardCommands.kt
index 67b2b0289..cedcc0919 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/console/StandardCommands.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/console/StandardCommands.kt
@@ -33,7 +33,7 @@ internal class HelpCommand(private val consoleFacade: ConsoleFacade) : Reposilit
@Parameters(index = "0", paramLabel = "[]", description = ["Display usage of the given command"], defaultValue = "")
private lateinit var requestedCommand: String
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
createCommandHelp(consoleFacade.getCommands(), requestedCommand)
.peek { context.appendAll(it) }
.onError {
@@ -47,7 +47,7 @@ internal class HelpCommand(private val consoleFacade: ConsoleFacade) : Reposilit
@Command(name = "stop", aliases = ["shutdown"], description = ["Shutdown server"])
internal class StopCommand(private val reposilite: Reposilite) : ReposiliteCommand {
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
reposilite.logger.warn("The shutdown request has been sent")
reposilite.scheduler.schedule({ reposilite.shutdown() }, 1, SECONDS)
}
@@ -60,7 +60,7 @@ internal class LevelCommand(private val journalist: ReposiliteJournalist) : Repo
@Parameters(index = "0", paramLabel = "", description = ["The new threshold"], defaultValue = "info")
private lateinit var level: String
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
ofOptional(Channel.of(level))
.onEmpty {
context.status = FAILED
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/console/api/ReposiliteCommand.kt b/reposilite-backend/src/main/kotlin/com/reposilite/console/api/ReposiliteCommand.kt
index 6cb25deaf..31ecc07a6 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/console/api/ReposiliteCommand.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/console/api/ReposiliteCommand.kt
@@ -19,6 +19,6 @@ import com.reposilite.console.CommandContext
interface ReposiliteCommand {
- suspend fun execute(context: CommandContext)
+ fun execute(context: CommandContext)
}
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/console/infrastructure/ConsoleWebSocketHandler.kt b/reposilite-backend/src/main/kotlin/com/reposilite/console/infrastructure/ConsoleWebSocketHandler.kt
index cf06a61d3..d31378062 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/console/infrastructure/ConsoleWebSocketHandler.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/console/infrastructure/ConsoleWebSocketHandler.kt
@@ -24,7 +24,6 @@ import io.javalin.openapi.OpenApi
import io.javalin.websocket.WsConfig
import io.javalin.websocket.WsContext
import io.javalin.websocket.WsMessageContext
-import kotlinx.coroutines.runBlocking
import panda.std.Result
import panda.std.Result.error
import panda.std.Result.ok
@@ -47,22 +46,20 @@ internal class CliEndpoint(
override fun accept(ws: WsConfig) {
ws.onConnect { connection ->
ws.onMessage { messageContext ->
- runBlocking {
- authenticateContext(messageContext)
- .peek {
- journalist.logger.info("CLI | $it accessed remote console")
- initializeAuthenticatedContext(ws, connection, it)
- }
- .onError {
- connection.send(it)
- connection.session.disconnect()
- }
- }
+ authenticateContext(messageContext)
+ .peek {
+ journalist.logger.info("CLI | $it accessed remote console")
+ initializeAuthenticatedContext(ws, connection, it)
+ }
+ .onError {
+ connection.send(it)
+ connection.session.disconnect()
+ }
}
}
}
- private suspend fun authenticateContext(connection: WsMessageContext): Result {
+ private fun authenticateContext(connection: WsMessageContext): Result {
val authMessage = connection.message()
if (!authMessage.startsWith(AUTHORIZATION_PREFIX)) {
@@ -86,10 +83,8 @@ internal class CliEndpoint(
when(val message = it.message()) {
"keep-alive" -> connection.send("keep-alive")
else -> {
- runBlocking {
- journalist.logger.info("CLI | $session> $message")
- consoleFacade.executeCommand(message)
- }
+ journalist.logger.info("CLI | $session> $message")
+ consoleFacade.executeCommand(message)
}
}
}
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/maven/MavenFacade.kt b/reposilite-backend/src/main/kotlin/com/reposilite/maven/MavenFacade.kt
index d1a9d381c..3d716f5ec 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/maven/MavenFacade.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/maven/MavenFacade.kt
@@ -55,7 +55,7 @@ class MavenFacade internal constructor(
val REPOSITORIES: Path = Paths.get("repositories")
}
- suspend fun findFile(lookupRequest: LookupRequest): Result {
+ fun findFile(lookupRequest: LookupRequest): Result {
val repository = repositoryService.getRepository(lookupRequest.repository) ?: return notFound("Repository not found").asError()
val gav = lookupRequest.gav.toPath()
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/maven/ProxyService.kt b/reposilite-backend/src/main/kotlin/com/reposilite/maven/ProxyService.kt
index 10fe53d43..cbd03050e 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/maven/ProxyService.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/maven/ProxyService.kt
@@ -20,24 +20,22 @@ import com.reposilite.config.Configuration.RepositoryConfiguration.ProxiedHostCo
import com.reposilite.maven.api.DocumentInfo
import com.reposilite.maven.api.FileDetails
import com.reposilite.shared.RemoteClient
+import com.reposilite.shared.firstOrErrors
import com.reposilite.shared.toPath
import com.reposilite.web.http.ErrorResponse
import io.javalin.http.HttpCode.NOT_FOUND
-import kotlinx.coroutines.flow.asFlow
-import kotlinx.coroutines.flow.map
import panda.std.Result
import panda.std.Result.ok
-import panda.std.coroutines.firstOrErrors
internal class ProxyService(private val remoteClient: RemoteClient) {
- suspend fun findFile(repository: Repository, gav: String): Result =
- repository.proxiedHosts.asSequence().asFlow()
+ fun findFile(repository: Repository, gav: String): Result =
+ repository.proxiedHosts.asSequence()
.map { (host, configuration) -> findFile(repository, host, gav, configuration) }
.firstOrErrors()
.mapErr { errors -> ErrorResponse(NOT_FOUND, errors.joinToString(" -> ") { "(${it.status}: ${it.message})" }) }
- private suspend fun findFile(repository: Repository, host: String, gav: String, configuration: ProxiedHostConfiguration): Result =
+ private fun findFile(repository: Repository, host: String, gav: String, configuration: ProxiedHostConfiguration): Result =
findFile(host, configuration, gav).flatMap { document ->
if (configuration.store)
storeFile(repository, gav, document)
@@ -50,7 +48,7 @@ internal class ProxyService(private val remoteClient: RemoteClient) {
.putFile(gav.toPath(), document.content())
.flatMap { repository.getFileDetails(gav.toPath()) }
- private suspend fun findFile(host: String, configuration: ProxiedHostConfiguration, gav: String): Result =
+ private fun findFile(host: String, configuration: ProxiedHostConfiguration, gav: String): Result =
remoteClient.get("$host/$gav", configuration.authorization, configuration.connectTimeout, configuration.readTimeout)
.mapErr { error -> error.updateMessage { "$host: $it" } }
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/maven/infrastructure/MavenApiEndpoints.kt b/reposilite-backend/src/main/kotlin/com/reposilite/maven/infrastructure/MavenApiEndpoints.kt
index 2ee926385..50080f418 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/maven/infrastructure/MavenApiEndpoints.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/maven/infrastructure/MavenApiEndpoints.kt
@@ -59,7 +59,7 @@ class MavenApiEndpoints(private val mavenFacade: MavenFacade) : ReposiliteRoutes
)
]
)
- private val findFileDetails: suspend ContextDsl.() -> Unit = {
+ private val findFileDetails: ContextDsl.() -> Unit = {
accessed {
response = parameter("repository")
?.let { repository -> mavenFacade.findFile(LookupRequest(this, repository, wildcard("gav") ?: "", )) }
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/shared/ExecutorsExtensions.kt b/reposilite-backend/src/main/kotlin/com/reposilite/shared/ExecutorsExtensions.kt
new file mode 100644
index 000000000..1e35cf22d
--- /dev/null
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/shared/ExecutorsExtensions.kt
@@ -0,0 +1,31 @@
+package com.reposilite.shared
+
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.ScheduledThreadPoolExecutor
+import java.util.concurrent.ThreadFactory
+import java.util.concurrent.ThreadPoolExecutor
+import java.util.concurrent.TimeUnit.MILLISECONDS
+import java.util.concurrent.atomic.AtomicInteger
+
+class NamedThreadFactory(private val prefix: String) : ThreadFactory {
+
+ private val group: ThreadGroup = System.getSecurityManager()?.threadGroup ?: Thread.currentThread().threadGroup
+ private val threadCount = AtomicInteger(0)
+
+ override fun newThread(runnalbe: Runnable): Thread =
+ Thread(group, runnalbe, "$prefix${threadCount.getAndIncrement()}", 0)
+
+}
+
+fun newFixedThreadPool(min: Int, max: Int, prefix: String): ExecutorService =
+ ThreadPoolExecutor(
+ min, max,
+ 0L, MILLISECONDS,
+ LinkedBlockingQueue(),
+ NamedThreadFactory("$prefix ({$max}) - ")
+ )
+
+fun newSingleThreadScheduledExecutor(prefix: String): ScheduledExecutorService =
+ ScheduledThreadPoolExecutor(1, NamedThreadFactory("$prefix (1) - 1"))
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/shared/ExposedExtensions.kt b/reposilite-backend/src/main/kotlin/com/reposilite/shared/ExposedExtensions.kt
deleted file mode 100644
index 04210bae7..000000000
--- a/reposilite-backend/src/main/kotlin/com/reposilite/shared/ExposedExtensions.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (c) 2021 dzikoysk
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.reposilite.shared
-
-import kotlinx.coroutines.CoroutineDispatcher
-import org.jetbrains.exposed.sql.Database
-import org.jetbrains.exposed.sql.Transaction
-import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
-import org.jetbrains.exposed.sql.transactions.transaction
-
-fun Iterable.firstAndMap(transform: (T) -> R): R? =
- this.firstOrNull()?.let(transform)
-
-suspend fun launchTransaction(dispatcher: CoroutineDispatcher?, database: Database, statement: Transaction.() -> T): T =
- if (dispatcher != null)
- newSuspendedTransaction(dispatcher, database) { statement(this) }
- else
- transaction(database) { statement(this) }
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/shared/HttpClient.kt b/reposilite-backend/src/main/kotlin/com/reposilite/shared/HttpClient.kt
index 0e276ea73..3ffb6a217 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/shared/HttpClient.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/shared/HttpClient.kt
@@ -46,13 +46,13 @@ interface RemoteClient {
* @param connectTimeout - connection establishment timeout in seconds
* @param readTimeout - connection read timeout in seconds
*/
- suspend fun get(uri: String, credentials: String?, connectTimeout: Int, readTimeout: Int): Result
+ fun get(uri: String, credentials: String?, connectTimeout: Int, readTimeout: Int): Result
}
class HttpRemoteClient(private val journalist: Journalist) : RemoteClient {
- override suspend fun get(uri: String, credentials: String?, connectTimeout: Int, readTimeout: Int): Result =
+ override fun get(uri: String, credentials: String?, connectTimeout: Int, readTimeout: Int): Result =
Fuel.head(uri)
.authenticateWith(credentials)
.timeout(connectTimeout * 1000)
@@ -115,10 +115,10 @@ class HttpRemoteClient(private val journalist: Journalist) : RemoteClient {
}
class FakeRemoteClient(
- private val handler: suspend (String, String?, Int, Int) -> Result
+ private val handler: (String, String?, Int, Int) -> Result
) : RemoteClient {
- override suspend fun get(uri: String, credentials: String?, connectTimeout: Int, readTimeout: Int): Result =
+ override fun get(uri: String, credentials: String?, connectTimeout: Int, readTimeout: Int): Result =
handler(uri, credentials, connectTimeout, readTimeout)
}
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/shared/KotlinExtensions.kt b/reposilite-backend/src/main/kotlin/com/reposilite/shared/KotlinExtensions.kt
index bd0d4403b..76f4aef3b 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/shared/KotlinExtensions.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/shared/KotlinExtensions.kt
@@ -16,10 +16,27 @@
package com.reposilite.shared
+import panda.std.Result
import java.util.concurrent.atomic.AtomicBoolean
fun AtomicBoolean.peek(block: () -> Unit) {
if (this.get()) {
block()
}
+}
+
+fun Iterable.firstAndMap(transform: (T) -> R): R? =
+ this.firstOrNull()?.let(transform)
+
+fun Sequence>.firstSuccessOr(elseValue: () -> Result): Result =
+ this.firstOrNull { it.isOk }
+ ?.projectToValue()
+ ?: elseValue()
+
+fun Sequence>.firstOrErrors(): Result> {
+ val collection: MutableCollection = ArrayList()
+
+ return this
+ .map { result -> result.onError { collection.add(it) } }
+ .firstSuccessOr { Result.error(collection) }
}
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatisticsFacade.kt b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatisticsFacade.kt
index 3f1df49f9..e7fd9e737 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatisticsFacade.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatisticsFacade.kt
@@ -39,26 +39,26 @@ class StatisticsFacade internal constructor(
fun increaseRecord(type: RecordType, uri: String) =
recordsBulk.merge(RecordIdentifier(type, uri), 1) { cached, value -> cached + value }
- suspend fun saveRecordsBulk() =
+ fun saveRecordsBulk() =
recordsBulk.toMap().also {
recordsBulk.clear() // read doesn't lock, so there is a possibility of dropping a few records between toMap and clear. Might be improved in the future
statisticsRepository.incrementRecords(it)
logger.debug("Statistics | Saved bulk with ${it.size} records")
}
- suspend fun findRecordsByPhrase(type: String, phrase: String, limit: Int = Int.MAX_VALUE): Result =
+ fun findRecordsByPhrase(type: String, phrase: String, limit: Int = Int.MAX_VALUE): Result =
findRecordTypeByName(type)
?.let { findRecordsByPhrase(it, phrase, limit).asSuccess() }
?: errorResponse(BAD_REQUEST, "Unknown record type $type}")
- suspend fun findRecordsByPhrase(type: RecordType, phrase: String, limit: Int = Int.MAX_VALUE): RecordCountResponse =
+ fun findRecordsByPhrase(type: RecordType, phrase: String, limit: Int = Int.MAX_VALUE): RecordCountResponse =
statisticsRepository.findRecordsByPhrase(type, phrase, limit)
.let { RecordCountResponse(it.sumOf(Record::count), it) }
- suspend fun countUniqueRecords(): Long =
+ fun countUniqueRecords(): Long =
statisticsRepository.countUniqueRecords()
- suspend fun countRecords(): Long =
+ fun countRecords(): Long =
statisticsRepository.countRecords()
override fun getLogger(): Logger =
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatisticsRepository.kt b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatisticsRepository.kt
index 9120510a7..479558c20 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatisticsRepository.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatisticsRepository.kt
@@ -22,16 +22,16 @@ import com.reposilite.statistics.api.RecordType
internal interface StatisticsRepository {
- suspend fun incrementRecords(bulk: Map)
+ fun incrementRecords(bulk: Map)
- suspend fun incrementRecord(record: RecordIdentifier, count: Long)
+ fun incrementRecord(record: RecordIdentifier, count: Long)
- suspend fun findRecordByTypeAndIdentifier(record: RecordIdentifier): Record?
+ fun findRecordByTypeAndIdentifier(record: RecordIdentifier): Record?
- suspend fun findRecordsByPhrase(type: RecordType, phrase: String, limit: Int): List
+ fun findRecordsByPhrase(type: RecordType, phrase: String, limit: Int): List
- suspend fun countUniqueRecords(): Long
+ fun countUniqueRecords(): Long
- suspend fun countRecords(): Long
+ fun countRecords(): Long
}
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatsCommand.kt b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatsCommand.kt
index 7935bffad..4d501d862 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatsCommand.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/StatsCommand.kt
@@ -31,7 +31,7 @@ internal class StatsCommand(private val statisticsFacade: StatisticsFacade) : Re
@Parameters(index = "0", paramLabel = "[]", description = ["Accepts string as pattern and int as limiter"], defaultValue = "")
private lateinit var filter: String
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
context.append("Statistics: ")
context.append(" Unique requests: " + statisticsFacade.countUniqueRecords() + " (count: " + statisticsFacade.countRecords() + ")")
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/application/StatisticsWebConfiguration.kt b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/application/StatisticsWebConfiguration.kt
index 4c0c5d54f..274409b31 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/application/StatisticsWebConfiguration.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/application/StatisticsWebConfiguration.kt
@@ -25,33 +25,23 @@ import com.reposilite.statistics.infrastructure.StatisticsEndpoint
import com.reposilite.statistics.infrastructure.StatisticsHandler
import com.reposilite.web.ReposiliteRoutes
import com.reposilite.web.WebConfiguration
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
import org.jetbrains.exposed.sql.Database
import java.util.concurrent.TimeUnit.MINUTES
internal object StatisticsWebConfiguration : WebConfiguration {
- fun createFacade(journalist: Journalist, dispatcher: CoroutineDispatcher?, database: Database): StatisticsFacade =
- StatisticsFacade(journalist, SqlStatisticsRepository(dispatcher, database))
+ fun createFacade(journalist: Journalist, database: Database): StatisticsFacade =
+ StatisticsFacade(journalist, SqlStatisticsRepository(database))
override fun initialize(reposilite: Reposilite) {
- val consoleFacade = reposilite.consoleFacade
val statisticsFacade = reposilite.statisticsFacade
+ val consoleFacade = reposilite.consoleFacade
consoleFacade.registerCommand(StatsCommand(statisticsFacade))
reposilite.scheduler.scheduleWithFixedDelay({
- runBlocking {
- if (reposilite.ioDispatcher == null) {
- statisticsFacade.saveRecordsBulk()
- }
- else {
- withContext(reposilite.ioDispatcher) {
- statisticsFacade.saveRecordsBulk()
- }
- }
+ reposilite.ioService.execute {
+ statisticsFacade.saveRecordsBulk()
}
}, 1, 1, MINUTES)
}
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/infrastructure/InMemoryStatisticsRepository.kt b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/infrastructure/InMemoryStatisticsRepository.kt
index e4bb8b201..c0a775251 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/infrastructure/InMemoryStatisticsRepository.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/infrastructure/InMemoryStatisticsRepository.kt
@@ -26,13 +26,13 @@ internal class InMemoryStatisticsRepository : StatisticsRepository {
private val records: MutableMap = ConcurrentHashMap()
- override suspend fun incrementRecord(record: RecordIdentifier, count: Long) {
+ override fun incrementRecord(record: RecordIdentifier, count: Long) {
findRecordByTypeAndIdentifier(record)
?.also { records[record] = it.copy(count = it.count + count) }
?: createRecord(record, count)
}
- override suspend fun incrementRecords(bulk: Map) {
+ override fun incrementRecords(bulk: Map) {
bulk.forEach { incrementRecord(it.key, it.value) }
}
@@ -44,10 +44,10 @@ internal class InMemoryStatisticsRepository : StatisticsRepository {
)
}
- override suspend fun findRecordByTypeAndIdentifier(record: RecordIdentifier): Record? =
+ override fun findRecordByTypeAndIdentifier(record: RecordIdentifier): Record? =
records.values.firstOrNull { it.type == record.type && it.identifier == record.identifier }
- override suspend fun findRecordsByPhrase(type: RecordType, phrase: String, limit: Int): List =
+ override fun findRecordsByPhrase(type: RecordType, phrase: String, limit: Int): List =
records.values.asSequence()
.filter { it.type == type }
.filter { it.identifier.contains(phrase) }
@@ -55,12 +55,12 @@ internal class InMemoryStatisticsRepository : StatisticsRepository {
.sortedByDescending { it.count }
.toList()
- override suspend fun countRecords(): Long =
+ override fun countRecords(): Long =
records
.map { it.value.count }
.sum()
- override suspend fun countUniqueRecords(): Long =
+ override fun countUniqueRecords(): Long =
records.size.toLong()
}
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/infrastructure/SqlStatisticsRepository.kt b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/infrastructure/SqlStatisticsRepository.kt
index b070097c4..4264a3ee6 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/statistics/infrastructure/SqlStatisticsRepository.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/statistics/infrastructure/SqlStatisticsRepository.kt
@@ -17,12 +17,10 @@
package com.reposilite.statistics.infrastructure
import com.reposilite.shared.firstAndMap
-import com.reposilite.shared.launchTransaction
import com.reposilite.statistics.StatisticsRepository
import com.reposilite.statistics.api.Record
import com.reposilite.statistics.api.RecordIdentifier
import com.reposilite.statistics.api.RecordType
-import kotlinx.coroutines.CoroutineDispatcher
import net.dzikoysk.exposed.upsert.upsert
import net.dzikoysk.exposed.upsert.withUnique
import org.jetbrains.exposed.sql.Database
@@ -50,7 +48,7 @@ internal object StatisticsTable : Table("statistics") {
val statisticsTypeWithIdentifierKey = withUnique("uq_type_identifier_id", type, identifier_id)
}
-internal class SqlStatisticsRepository(private val dispatcher: CoroutineDispatcher?, private val database: Database) : StatisticsRepository {
+internal class SqlStatisticsRepository(private val database: Database) : StatisticsRepository {
init {
transaction(database) {
@@ -59,13 +57,13 @@ internal class SqlStatisticsRepository(private val dispatcher: CoroutineDispatch
}
}
- override suspend fun incrementRecord(record: RecordIdentifier, count: Long) =
- launchTransaction(dispatcher, database) {
+ override fun incrementRecord(record: RecordIdentifier, count: Long) =
+ transaction(database) {
rawIncrementRecord(record, count)
}
- override suspend fun incrementRecords(bulk: Map) =
- launchTransaction(dispatcher, database) {
+ override fun incrementRecords(bulk: Map) =
+ transaction(database) {
bulk.forEach { rawIncrementRecord(it.key, it.value) }
}
@@ -96,30 +94,30 @@ internal class SqlStatisticsRepository(private val dispatcher: CoroutineDispatch
row[StatisticsTable.count]
)
- override suspend fun findRecordByTypeAndIdentifier(record: RecordIdentifier): Record? =
- launchTransaction(dispatcher, database) {
+ override fun findRecordByTypeAndIdentifier(record: RecordIdentifier): Record? =
+ transaction(database) {
StatisticsTable.select { build { StatisticsTable.type eq record.type.name }.and { StatisticsTable.identifier eq record.identifier } }
.firstAndMap { toRecord(it) }
}
- override suspend fun findRecordsByPhrase(type: RecordType, phrase: String, limit: Int): List =
- launchTransaction(dispatcher, database) {
+ override fun findRecordsByPhrase(type: RecordType, phrase: String, limit: Int): List =
+ transaction(database) {
StatisticsTable.select { build { StatisticsTable.type eq type.name }.and { StatisticsTable.identifier like "%${phrase}%" }}
.limit(limit)
.orderBy(StatisticsTable.count, order = DESC)
.map { toRecord(it) }
}
- override suspend fun countRecords(): Long =
- launchTransaction(dispatcher, database) {
+ override fun countRecords(): Long =
+ transaction(database) {
with (StatisticsTable.count.sum()) {
StatisticsTable.slice(this).selectAll().firstAndMap { it[this] }
}
?: 0
}
- override suspend fun countUniqueRecords(): Long =
- launchTransaction(dispatcher, database) {
+ override fun countUniqueRecords(): Long =
+ transaction(database) {
StatisticsTable.selectAll().count()
}
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/status/FailuresCommand.kt b/reposilite-backend/src/main/kotlin/com/reposilite/status/FailuresCommand.kt
index 8c3fbc5fd..9837153bf 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/status/FailuresCommand.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/status/FailuresCommand.kt
@@ -22,7 +22,7 @@ import picocli.CommandLine.Command
@Command(name = "failures", description = ["Display all recorded exceptions"])
internal class FailuresCommand(private val failureFacade: FailureFacade) : ReposiliteCommand {
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
if (!failureFacade.hasFailures()) {
context.append("No exception has occurred yet")
return
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/status/StatusCommand.kt b/reposilite-backend/src/main/kotlin/com/reposilite/status/StatusCommand.kt
index 242799dd2..26ef47534 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/status/StatusCommand.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/status/StatusCommand.kt
@@ -29,7 +29,7 @@ internal class StatusCommand(
private val failureFacade: FailureFacade
) : ReposiliteCommand {
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
context.append("Reposilite $VERSION Status")
context.append(" Active: $GREEN_BOLD${statusFacade.isAlive()}$RESET")
context.append(" Uptime: ${statusFacade.uptime()}")
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/status/StatusFacade.kt b/reposilite-backend/src/main/kotlin/com/reposilite/status/StatusFacade.kt
index 597c342f1..df4e3c893 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/status/StatusFacade.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/status/StatusFacade.kt
@@ -17,7 +17,6 @@
package com.reposilite.status
import com.github.kittinunf.fuel.Fuel
-import com.github.kittinunf.fuel.coroutines.awaitStringResult
import com.reposilite.VERSION
import com.reposilite.shared.TimeUtils
import panda.utilities.console.Effect.GREEN
@@ -43,15 +42,20 @@ class StatusFacade(
internal fun threadGroupUsage(): String =
Thread.activeCount().toString()
- internal suspend fun getVersion(): String =
+ internal fun getVersion(): String =
if (testEnv)
""
else
Fuel.get(remoteVersionUrl)
- .awaitStringResult()
- .fold(
- success = { (if (VERSION == it) GREEN else RED_UNDERLINED).toString() + it + RESET },
- failure = { if (it.message?.contains("java.security.NoSuchAlgorithmException") == true) "Cannot load SSL context for HTTPS request due to the lack of available memory" else "$remoteVersionUrl is unavailable: ${it.message}" }
+ .responseString()
+ .third.fold(
+ success = { "${if (VERSION == it) GREEN else RED_UNDERLINED}$it$RESET" },
+ failure = {
+ return when (it.message?.contains("java.security.NoSuchAlgorithmException")) {
+ true -> "Cannot load SSL context for HTTPS request due to the lack of available memory"
+ else -> "$remoteVersionUrl is unavailable: ${it.message}"
+ }
+ }
)
}
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenCommands.kt b/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenCommands.kt
index ddb117fb2..6e5446a2e 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenCommands.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenCommands.kt
@@ -28,7 +28,7 @@ import picocli.CommandLine.Parameters
@Command(name = "tokens", description = ["List all generated tokens"])
internal class TokensCommand(private val accessTokenFacade: AccessTokenFacade) : ReposiliteCommand {
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
context.append("Tokens (${accessTokenFacade.count()})")
accessTokenFacade.getTokens().forEach {
@@ -61,7 +61,7 @@ internal class KeygenCommand(private val accessTokenFacade: AccessTokenFacade) :
])
private lateinit var permissions: String
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
val response = accessTokenFacade.createAccessToken(CreateAccessTokenRequest(
name,
secret,
@@ -85,7 +85,7 @@ internal class ChNameCommand(private val accessTokenFacade: AccessTokenFacade) :
@Parameters(index = "1", paramLabel = "", description = ["New token name"])
private lateinit var updatedName: String
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
accessTokenFacade.getToken(name)
?.let {
accessTokenFacade.updateToken(it.copy(name = updatedName))
@@ -108,7 +108,7 @@ internal class ChModCommand(private val accessTokenFacade: AccessTokenFacade) :
@Parameters(index = "1", paramLabel = "", description = ["New permissions"])
private lateinit var permissions: String
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
accessTokenFacade.getToken(token)
?.let {
accessTokenFacade.updateToken(it.copy(
@@ -132,7 +132,7 @@ internal class RevokeCommand(private val accessTokenFacade: AccessTokenFacade) :
@Parameters(index = "0", paramLabel = "", description = ["Name of token to revoke"])
private lateinit var name: String
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
accessTokenFacade.deleteToken(name)
context.append("Token for '$name' has been revoked")
}
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenFacade.kt b/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenFacade.kt
index d35c7e700..9ec6b5082 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenFacade.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenFacade.kt
@@ -30,13 +30,13 @@ class AccessTokenFacade internal constructor(
private val persistentRepository: AccessTokenRepository
) {
- suspend fun createTemporaryAccessToken(request: CreateAccessTokenRequest): CreateAccessTokenResponse =
+ fun createTemporaryAccessToken(request: CreateAccessTokenRequest): CreateAccessTokenResponse =
createAccessToken(temporaryRepository, TEMPORARY, request.name, request.secret ?: generateSecret(), request.permissions)
- suspend fun createAccessToken(request: CreateAccessTokenRequest): CreateAccessTokenResponse =
+ fun createAccessToken(request: CreateAccessTokenRequest): CreateAccessTokenResponse =
createAccessToken(persistentRepository, PERSISTENT, request.name, request.secret ?: generateSecret(), request.permissions)
- private suspend fun createAccessToken(
+ private fun createAccessToken(
repository: AccessTokenRepository,
type: AccessTokenType,
name: String,
@@ -49,25 +49,25 @@ class AccessTokenFacade internal constructor(
return CreateAccessTokenResponse(repository.saveAccessToken(accessToken), secret)
}
- suspend fun updateToken(accessToken: AccessToken): AccessToken =
+ fun updateToken(accessToken: AccessToken): AccessToken =
when(accessToken.type) {
PERSISTENT -> persistentRepository.saveAccessToken(accessToken)
TEMPORARY -> temporaryRepository.saveAccessToken(accessToken)
}
- suspend fun deleteToken(name: String): AccessToken? =
+ fun deleteToken(name: String): AccessToken? =
deleteToken(temporaryRepository, name) ?: deleteToken(persistentRepository, name)
- private suspend fun deleteToken(repository: AccessTokenRepository, name: String): AccessToken? =
+ private fun deleteToken(repository: AccessTokenRepository, name: String): AccessToken? =
repository.findAccessTokenByName(name)?.also { persistentRepository.deleteAccessToken(it) }
- suspend fun getToken(name: String): AccessToken? =
+ fun getToken(name: String): AccessToken? =
temporaryRepository.findAccessTokenByName(name) ?: persistentRepository.findAccessTokenByName(name)
- suspend fun getTokens(): Collection =
+ fun getTokens(): Collection =
temporaryRepository.findAll() + persistentRepository.findAll()
- suspend fun count(): Long =
+ fun count(): Long =
temporaryRepository.countAccessTokens() + persistentRepository.countAccessTokens()
}
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenRepository.kt b/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenRepository.kt
index 97c2f2ddf..ca1884d7c 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenRepository.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/token/AccessTokenRepository.kt
@@ -20,14 +20,14 @@ import com.reposilite.token.api.AccessToken
internal interface AccessTokenRepository {
- suspend fun saveAccessToken(accessToken: AccessToken): AccessToken
+ fun saveAccessToken(accessToken: AccessToken): AccessToken
- suspend fun deleteAccessToken(accessToken: AccessToken)
+ fun deleteAccessToken(accessToken: AccessToken)
- suspend fun findAccessTokenByName(name: String): AccessToken?
+ fun findAccessTokenByName(name: String): AccessToken?
- suspend fun findAll(): Collection
+ fun findAll(): Collection
- suspend fun countAccessTokens(): Long
+ fun countAccessTokens(): Long
}
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/token/RouteCommands.kt b/reposilite-backend/src/main/kotlin/com/reposilite/token/RouteCommands.kt
index fed9f3252..d3ca08bb7 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/token/RouteCommands.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/token/RouteCommands.kt
@@ -40,7 +40,7 @@ internal class RouteAdd(private val accessTokenFacade: AccessTokenFacade) : Repo
])
private lateinit var permissions: String
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
with(accessTokenFacade) {
getToken(name)
?.let {
@@ -71,7 +71,7 @@ internal class RouteRemove(private val accessTokenFacade: AccessTokenFacade) : R
@Parameters(index = "1", paramLabel = "", description = ["Path of route to remove"])
private lateinit var path: String
- override suspend fun execute(context: CommandContext) {
+ override fun execute(context: CommandContext) {
with(accessTokenFacade) {
getToken(name)
?.let {
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/token/application/AccessTokenWebConfiguration.kt b/reposilite-backend/src/main/kotlin/com/reposilite/token/application/AccessTokenWebConfiguration.kt
index 1ce22d3bb..fb6e842be 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/token/application/AccessTokenWebConfiguration.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/token/application/AccessTokenWebConfiguration.kt
@@ -28,25 +28,21 @@ import com.reposilite.token.TokensCommand
import com.reposilite.token.infrastructure.InMemoryAccessTokenRepository
import com.reposilite.token.infrastructure.SqlAccessTokenRepository
import com.reposilite.web.WebConfiguration
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.runBlocking
import org.jetbrains.exposed.sql.Database
internal object AccessTokenWebConfiguration : WebConfiguration {
- fun createFacade(dispatcher: CoroutineDispatcher?, database: Database): AccessTokenFacade =
+ fun createFacade(database: Database): AccessTokenFacade =
AccessTokenFacade(
temporaryRepository = InMemoryAccessTokenRepository(),
- persistentRepository = SqlAccessTokenRepository(dispatcher, database)
+ persistentRepository = SqlAccessTokenRepository(database)
)
override fun initialize(reposilite: Reposilite) {
val accessTokenFacade = reposilite.accessTokenFacade
reposilite.parameters.tokens.forEach {
- runBlocking {
- accessTokenFacade.createTemporaryAccessToken(it)
- }
+ accessTokenFacade.createTemporaryAccessToken(it)
}
val consoleFacade = reposilite.consoleFacade
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/token/infrastructure/InMemoryAccessTokenRepository.kt b/reposilite-backend/src/main/kotlin/com/reposilite/token/infrastructure/InMemoryAccessTokenRepository.kt
index 29e52e90e..58ed36807 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/token/infrastructure/InMemoryAccessTokenRepository.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/token/infrastructure/InMemoryAccessTokenRepository.kt
@@ -26,7 +26,7 @@ internal class InMemoryAccessTokenRepository : AccessTokenRepository {
private val tokens: MutableMap = HashMap(1)
private val id = AtomicInteger()
- override suspend fun saveAccessToken(accessToken: AccessToken): AccessToken {
+ override fun saveAccessToken(accessToken: AccessToken): AccessToken {
val initializedAccessToken = when (accessToken.id) {
UNINITIALIZED_ENTITY_ID -> accessToken.copy(id = id.incrementAndGet())
else -> accessToken
@@ -36,17 +36,17 @@ internal class InMemoryAccessTokenRepository : AccessTokenRepository {
return initializedAccessToken
}
- override suspend fun deleteAccessToken(accessToken: AccessToken) {
+ override fun deleteAccessToken(accessToken: AccessToken) {
tokens.remove(accessToken.id)
}
- override suspend fun findAccessTokenByName(name: String): AccessToken? =
+ override fun findAccessTokenByName(name: String): AccessToken? =
tokens.values.firstOrNull { it.name == name }
- override suspend fun findAll(): Collection =
+ override fun findAll(): Collection =
tokens.values
- override suspend fun countAccessTokens(): Long =
+ override fun countAccessTokens(): Long =
tokens.size.toLong()
}
\ No newline at end of file
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/token/infrastructure/SqlAccessTokenRepository.kt b/reposilite-backend/src/main/kotlin/com/reposilite/token/infrastructure/SqlAccessTokenRepository.kt
index 715cd7f35..ed10919e7 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/token/infrastructure/SqlAccessTokenRepository.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/token/infrastructure/SqlAccessTokenRepository.kt
@@ -17,7 +17,6 @@
package com.reposilite.token.infrastructure
import com.reposilite.shared.firstAndMap
-import com.reposilite.shared.launchTransaction
import com.reposilite.token.AccessTokenRepository
import com.reposilite.token.api.AccessToken
import com.reposilite.token.api.AccessTokenPermission
@@ -25,7 +24,6 @@ import com.reposilite.token.api.AccessTokenPermission.Companion.findAccessTokenP
import com.reposilite.token.api.AccessTokenType
import com.reposilite.token.api.Route
import com.reposilite.token.api.RoutePermission.Companion.findRoutePermissionByIdentifier
-import kotlinx.coroutines.CoroutineDispatcher
import net.dzikoysk.exposed.shared.UNINITIALIZED_ENTITY_ID
import net.dzikoysk.exposed.upsert.withIndex
import net.dzikoysk.exposed.upsert.withUnique
@@ -75,7 +73,7 @@ object PermissionToRouteTable : Table("permission_route") {
}
}
-internal class SqlAccessTokenRepository(private val dispatcher: CoroutineDispatcher?, private val database: Database) : AccessTokenRepository {
+internal class SqlAccessTokenRepository(private val database: Database) : AccessTokenRepository {
init {
transaction(database) {
@@ -84,8 +82,8 @@ internal class SqlAccessTokenRepository(private val dispatcher: CoroutineDispatc
}
}
- override suspend fun saveAccessToken(accessToken: AccessToken): AccessToken =
- launchTransaction(dispatcher, database) {
+ override fun saveAccessToken(accessToken: AccessToken): AccessToken =
+ transaction(database) {
when(getIdByName(accessToken.name)) {
UNINITIALIZED_ENTITY_ID -> createAccessToken(accessToken)
else -> updateAccessToken(accessToken)
@@ -153,8 +151,8 @@ internal class SqlAccessTokenRepository(private val dispatcher: CoroutineDispatc
?.value
?: UNINITIALIZED_ENTITY_ID
- override suspend fun deleteAccessToken(accessToken: AccessToken) {
- launchTransaction(dispatcher, database) {
+ override fun deleteAccessToken(accessToken: AccessToken) {
+ transaction(database) {
AccessTokenTable.deleteWhere { AccessTokenTable.id eq accessToken.id }
}
}
@@ -187,18 +185,18 @@ internal class SqlAccessTokenRepository(private val dispatcher: CoroutineDispatc
)
}
- override suspend fun findAccessTokenByName(name: String): AccessToken? =
- launchTransaction(dispatcher, database) {
+ override fun findAccessTokenByName(name: String): AccessToken? =
+ transaction(database) {
AccessTokenTable.select { AccessTokenTable.name eq name }.firstAndMap { toAccessToken(it) }
}
- override suspend fun findAll(): Collection =
- launchTransaction(dispatcher, database) {
+ override fun findAll(): Collection =
+ transaction(database) {
AccessTokenTable.selectAll().map { toAccessToken(it) }
}
- override suspend fun countAccessTokens(): Long =
- launchTransaction(dispatcher, database) {
+ override fun countAccessTokens(): Long =
+ transaction(database) {
AccessTokenTable.selectAll().count()
}
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/web/ContextDsl.kt b/reposilite-backend/src/main/kotlin/com/reposilite/web/ContextDsl.kt
index 69fd03ed7..f82cf4070 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/web/ContextDsl.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/web/ContextDsl.kt
@@ -26,7 +26,6 @@ import com.reposilite.web.http.uri
import io.javalin.http.Context
import io.javalin.http.HttpCode
import panda.std.Result
-import panda.std.coroutines.rxPeek
class ContextDsl(
val logger: Logger,
@@ -56,23 +55,23 @@ class ContextDsl(
/**
* Request was created by either anonymous user or through authenticated token
*/
- suspend fun accessed(init: suspend AccessToken?.() -> Unit) {
+ fun accessed(init: AccessToken?.() -> Unit) {
init(authenticationResult.orNull())
}
/**
* Request was created by valid access token
*/
- suspend fun authenticated(init: suspend AccessToken.() -> Unit) {
+ fun authenticated(init: AccessToken.() -> Unit) {
authenticationResult
.onError { ctx.error(it) }
- .rxPeek { init(it) }
+ .peek { init(it) }
}
/**
* Request was created by valid access token and the token has access to the requested path
*/
- suspend fun authorized(to: String = ctx.uri(), init: suspend AccessToken.() -> Unit) {
+ fun authorized(to: String = ctx.uri(), init: AccessToken.() -> Unit) {
authenticated {
if (isAuthorized(to))
init(this)
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/web/JavalinWebServer.kt b/reposilite-backend/src/main/kotlin/com/reposilite/web/JavalinWebServer.kt
index 884b07a05..9df945230 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/web/JavalinWebServer.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/web/JavalinWebServer.kt
@@ -21,8 +21,6 @@ import com.reposilite.config.Configuration
import com.reposilite.shared.TimeUtils
import io.javalin.Javalin
import io.javalin.core.JavalinConfig
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
import org.eclipse.jetty.io.EofException
import org.eclipse.jetty.util.thread.QueuedThreadPool
import org.eclipse.jetty.util.thread.ThreadPool
@@ -35,10 +33,12 @@ class JavalinWebServer {
fun start(reposilite: Reposilite) =
runWithDisabledLogging {
- this.webThreadPool = QueuedThreadPool(reposilite.configuration.webThreadPool, 2)
- webThreadPool?.start()
+ this.webThreadPool = QueuedThreadPool(reposilite.configuration.webThreadPool, 2).also {
+ it.name = "Reposilite Web (${it.maxThreads} -"
+ it.start()
+ }
- this.javalin = createJavalin(reposilite, reposilite.configuration, webThreadPool!!, reposilite.ioDispatcher ?: Dispatchers.Default)
+ this.javalin = createJavalin(reposilite, reposilite.configuration, webThreadPool!!)
.exception(EofException::class.java) { _, _ -> reposilite.logger.warn("Client closed connection") }
.events { listener ->
listener.serverStopping { reposilite.logger.info("Server stopping...") }
@@ -53,15 +53,15 @@ class JavalinWebServer {
}
}
- private fun createJavalin(reposilite: Reposilite, configuration: Configuration, webThreadPool: ThreadPool, dispatcher: CoroutineDispatcher): Javalin =
+ private fun createJavalin(reposilite: Reposilite, configuration: Configuration, webThreadPool: ThreadPool): Javalin =
if (servlet)
- Javalin.createStandalone { configureServer(reposilite, configuration, webThreadPool, dispatcher, it) }
+ Javalin.createStandalone { configureServer(reposilite, configuration, webThreadPool, it) }
else
- Javalin.create { configureServer(reposilite, configuration, webThreadPool, dispatcher, it) }
+ Javalin.create { configureServer(reposilite, configuration, webThreadPool, it) }
- private fun configureServer(reposilite: Reposilite, configuration: Configuration, webThreadPool: ThreadPool, dispatcher: CoroutineDispatcher, serverConfig: JavalinConfig) {
+ private fun configureServer(reposilite: Reposilite, configuration: Configuration, webThreadPool: ThreadPool, serverConfig: JavalinConfig) {
WebServerConfiguration.configure(reposilite, webThreadPool, configuration, serverConfig)
- serverConfig.registerPlugin(createReactiveRouting(reposilite, dispatcher))
+ serverConfig.registerPlugin(createReactiveRouting(reposilite))
}
fun stop() {
diff --git a/reposilite-backend/src/main/kotlin/com/reposilite/web/ReactiveRouting.kt b/reposilite-backend/src/main/kotlin/com/reposilite/web/ReactiveRouting.kt
index 87f414d91..aa7e6245b 100644
--- a/reposilite-backend/src/main/kotlin/com/reposilite/web/ReactiveRouting.kt
+++ b/reposilite-backend/src/main/kotlin/com/reposilite/web/ReactiveRouting.kt
@@ -20,46 +20,35 @@ import com.reposilite.Reposilite
import com.reposilite.web.http.response
import com.reposilite.web.http.uri
import com.reposilite.web.routing.AbstractRoutes
-import com.reposilite.web.routing.ReactiveRoutingPlugin
import com.reposilite.web.routing.Route
import com.reposilite.web.routing.RouteMethod
+import com.reposilite.web.routing.RoutingPlugin
import io.javalin.http.Context
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withTimeout
abstract class ReposiliteRoutes : AbstractRoutes()
class ReposiliteRoute(
path: String,
vararg methods: RouteMethod,
- handler: suspend ContextDsl.() -> Unit
+ handler: ContextDsl.() -> Unit
) : Route(path = path, methods = methods, handler = handler)
-fun createReactiveRouting(reposilite: Reposilite, dispatcher: CoroutineDispatcher): ReactiveRoutingPlugin {
+fun createReactiveRouting(reposilite: Reposilite): RoutingPlugin {
val failureFacade = reposilite.failureFacade
- val plugin = ReactiveRoutingPlugin(
- name = "reposilite-reactive-routing",
- coroutinesEnabled = reposilite.configuration.reactiveMode,
- errorConsumer = { name, error -> reposilite.logger.error("Coroutine $name failed to execute task", error) },
- dispatcher = dispatcher,
- syncHandler = { ctx, route ->
+ val plugin = RoutingPlugin(
+ handler = { ctx, route ->
try {
- handle(reposilite, ctx, route)
+ val authenticationResult = reposilite.authenticationFacade.authenticateByHeader(ctx.headerMap())
+ val dsl = ContextDsl(reposilite.logger, ctx, authenticationResult)
+ route.handler(dsl)
+ dsl.response?.also { ctx.response(it) }
} catch (throwable: Throwable) {
throwable.printStackTrace()
failureFacade.throwException(ctx.uri(), throwable)
}
- },
- asyncHandler = { ctx, route, result ->
- try {
- handle(reposilite, ctx, route)
- result.complete(Unit)
- }
- catch (throwable: Throwable) {
- throwable.printStackTrace()
- failureFacade.throwException(ctx.uri(), throwable)
- result.completeExceptionally(throwable)
- }
}
)
@@ -71,11 +60,4 @@ fun createReactiveRouting(reposilite: Reposilite, dispatcher: CoroutineDispatche
.let { plugin.registerRoutes(it) }
return plugin
-}
-
-private suspend fun handle(reposilite: Reposilite, ctx: Context, route: Route) {
- val authenticationResult = reposilite.authenticationFacade.authenticateByHeader(ctx.headerMap())
- val dsl = ContextDsl(reposilite.logger, ctx, authenticationResult)
- route.handler(dsl)
- dsl.response?.also { ctx.response(it) }
}
\ No newline at end of file
diff --git a/reposilite-backend/src/main/resources/reposilite-frontend/assets/index.87a2c378.css b/reposilite-backend/src/main/resources/reposilite-frontend/assets/index.1b9d6d6a.css
similarity index 62%
rename from reposilite-backend/src/main/resources/reposilite-frontend/assets/index.87a2c378.css
rename to reposilite-backend/src/main/resources/reposilite-frontend/assets/index.1b9d6d6a.css
index 47480657b..1a27383c3 100644
--- a/reposilite-backend/src/main/resources/reposilite-frontend/assets/index.87a2c378.css
+++ b/reposilite-backend/src/main/resources/reposilite-frontend/assets/index.1b9d6d6a.css
@@ -1 +1 @@
-@charset "UTF-8";@import"https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600&display=swap";html{--tw-bg-opacity: 1;background-color:rgba(245,245,245,var(--tw-bg-opacity))}#app{font-family:"Open Sans",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.container{padding-left:2.5rem;padding-right:2.5rem}.dark .active{--tw-border-opacity: 1;border-color:rgba(255,255,255,var(--tw-border-opacity))}.bg-default{--tw-bg-opacity: 1;background-color:rgba(245,245,245,var(--tw-bg-opacity))}.dark .bg-default{--tw-border-opacity: 1;border-color:rgba(23,23,23,var(--tw-border-opacity))}.mosha__toast{touch-action:none;display:flex;justify-content:space-between;position:fixed;min-height:64px;max-height:800px;box-sizing:border-box;overflow:hidden;padding:12px 8px;word-break:break-word;min-width:312px;max-width:480px;z-index:9999;width:-webkit-max-content;width:-moz-max-content;width:max-content;transition:top .3s ease-out .5s,bottom .3s ease-out .5s;border-radius:8px;box-shadow:0 1px 10px #0000001a,0 2px 15px #0000000d;margin:0 16px;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.mosha__toast__content-wrapper{display:flex;align-items:center}.mosha__toast__content{font-family:sans-serif;line-height:20px;display:flex;flex-direction:column;color:#fff}.mosha__toast__content__text{margin-bottom:2px;font-size:16px;font-weight:700}.mosha__toast__content__description{font-size:14px;font-weight:400}.mosha__toast__slot-wrapper{width:100%}.mosha__toast__close-icon:before{font-size:20px;cursor:pointer;content:"\d7";color:#fff;transition:color .3s;font-weight:600;margin-left:10px;position:relative;top:-12px}.mosha__toast__close-icon:hover:before{color:gray}.mosha__toast__progress{transition:all linear .2s;position:absolute;z-index:9999;height:8px;background-color:#fff9;bottom:0;margin-left:-8px}.mosha__toast.success{background-color:#06d6a0}.mosha__toast.warning{background-color:#ffc43d}.mosha__toast.info{background-color:#1b9aaa}.mosha__toast.danger{background-color:#ef476f}.mosha__toast.default{background-color:#fff}.mosha__toast.default .mosha__toast__content{color:#616161}.mosha__toast.default .mosha__toast__close-icon:before{color:#616161}.mosha__toast.default .mosha__toast__close-icon:hover:before{color:#d0d4d4}.mosha__toast.default .mosha__toast__progress{background-image:linear-gradient(-225deg,#69eacb 0,#eaccf8 48%,#6654f1 100%)}@media only screen and (max-width:475px){.mosha__toast{max-width:95.2%;left:0;right:0;margin:0 auto}}.mosha__icon{margin-right:16px}.mosha__bounceInRight-enter-active{-webkit-animation:bounceInRight .7s;animation:bounceInRight .7s}.mosha__bounceInRight-leave-active{-webkit-animation:bounceOutLeft .7s;animation:bounceOutLeft .7s}.mosha__bounceInLeft-enter-active{-webkit-animation:bounceInLeft .7s;animation:bounceInLeft .7s}.mosha__bounceInLeft-leave-active{-webkit-animation:bounceOutRight .7s;animation:bounceOutRight .7s}.mosha__bounceInDown-enter-active{-webkit-animation:bounceInDown .7s;animation:bounceInDown .7s}.mosha__bounceInDown-leave-active{-webkit-animation:bounceOutUp .7s;animation:bounceOutUp .7s}.mosha__bounceInUp-enter-active{-webkit-animation:bounceInUp .7s;animation:bounceInUp .7s}.mosha__bounceInUp-leave-active{-webkit-animation:bounceOutDown .7s;animation:bounceOutDown .7s}@-webkit-keyframes bounceInRight{60%,75%,90%,0%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate(2000px)}60%{opacity:1;transform:translate(-25px)}75%{transform:translate(10px)}90%{transform:translate(-5px)}to{transform:none}}@keyframes bounceInRight{60%,75%,90%,0%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate(2000px)}60%{opacity:1;transform:translate(-25px)}75%{transform:translate(10px)}90%{transform:translate(-5px)}to{transform:none}}@-webkit-keyframes bounceOutLeft{20%{opacity:1;transform:translate(-20px)}to{opacity:0;transform:translate(2000px)}}@keyframes bounceOutLeft{20%{opacity:1;transform:translate(-20px)}to{opacity:0;transform:translate(2000px)}}@-webkit-keyframes bounceInLeft{60%,75%,90%,0%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate(-2000px)}60%{opacity:1;transform:translate(25px)}75%{transform:translate(-10px)}90%{transform:translate(5px)}to{transform:none}}@keyframes bounceInLeft{60%,75%,90%,0%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translate(-2000px)}60%{opacity:1;transform:translate(25px)}75%{transform:translate(-10px)}90%{transform:translate(5px)}to{transform:none}}@-webkit-keyframes bounceOutRight{20%{opacity:1;transform:translate(20px)}to{opacity:0;transform:translate(-2000px)}}@keyframes bounceOutRight{20%{opacity:1;transform:translate(20px)}to{opacity:0;transform:translate(-2000px)}}@-webkit-keyframes bounceInUp{60%,75%,90%,0%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translateY(3000px)}60%{opacity:1;transform:translateY(-20px)}75%{transform:translateY(10px)}90%{transform:translateY(-5px)}to{transform:translate(0)}}@keyframes bounceInUp{60%,75%,90%,0%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translateY(3000px)}60%{opacity:1;transform:translateY(-20px)}75%{transform:translateY(10px)}90%{transform:translateY(-5px)}to{transform:translate(0)}}@-webkit-keyframes bounceOutUp{20%{transform:translateY(-10px)}40%,45%{opacity:1;transform:translateY(20px)}to{opacity:0;transform:translateY(-2000px)}}@keyframes bounceOutUp{20%{transform:translateY(-10px)}40%,45%{opacity:1;transform:translateY(20px)}to{opacity:0;transform:translateY(-2000px)}}@-webkit-keyframes bounceInDown{60%,75%,90%,0%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translateY(-3000px)}60%{opacity:1;transform:translateY(25px)}75%{transform:translateY(-10px)}90%{transform:translateY(5px)}to{transform:none}}@keyframes bounceInDown{60%,75%,90%,0%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{opacity:0;transform:translateY(-3000px)}60%{opacity:1;transform:translateY(25px)}75%{transform:translateY(-10px)}90%{transform:translateY(5px)}to{transform:none}}@-webkit-keyframes bounceOutDown{20%{transform:translateY(10px)}40%,45%{opacity:1;transform:translateY(-20px)}to{opacity:0;transform:translateY(2000px)}}@keyframes bounceOutDown{20%{transform:translateY(10px)}40%,45%{opacity:1;transform:translateY(-20px)}to{opacity:0;transform:translateY(2000px)}}.mosha__slideInRight-enter-active{-webkit-animation:slideInRight .5s;animation:slideInRight .5s}.mosha__slideInRight-leave-active{-webkit-animation:slideOutRight .5s;animation:slideOutRight .5s}.mosha__slideInLeft-enter-active{-webkit-animation:slideInLeft .5s;animation:slideInLeft .5s}.mosha__slideInLeft-leave-active{-webkit-animation:slideOutLeft .5s;animation:slideOutLeft .5s}.mosha__slideInDown-enter-active{-webkit-animation:slideInDown .5s;animation:slideInDown .5s}.mosha__slideInDown-leave-active{-webkit-animation:slideOutUp .5s;animation:slideOutUp .5s}.mosha__slideInUp-enter-active{-webkit-animation:slideInUp .5s;animation:slideInUp .5s}.mosha__slideInUp-leave-active{-webkit-animation:slideOutDown .5s;animation:slideOutDown .5s}@-webkit-keyframes slideInRight{0%{transform:translate(110%);visibility:visible}to{transform:translate(0)}}@keyframes slideInRight{0%{transform:translate(110%);visibility:visible}to{transform:translate(0)}}@-webkit-keyframes slideOutLeft{0%{transform:translate(0)}to{visibility:hidden;transform:translate(-110%)}}@keyframes slideOutLeft{0%{transform:translate(0)}to{visibility:hidden;transform:translate(-110%)}}@-webkit-keyframes slideInLeft{0%{transform:translate(-110%);visibility:visible}to{transform:translate(0)}}@keyframes slideInLeft{0%{transform:translate(-110%);visibility:visible}to{transform:translate(0)}}@-webkit-keyframes slideOutRight{0%{transform:translate(0)}to{visibility:hidden;transform:translate(110%)}}@keyframes slideOutRight{0%{transform:translate(0)}to{visibility:hidden;transform:translate(110%)}}@-webkit-keyframes slideInUp{0%{transform:translateY(110%);visibility:visible}to{transform:translate(0)}}@keyframes slideInUp{0%{transform:translateY(110%);visibility:visible}to{transform:translate(0)}}@-webkit-keyframes slideInDown{0%{opacity:0;transform:translateY(-120%)}to{opacity:1;transform:translate(0)}}@keyframes slideInDown{0%{opacity:0;transform:translateY(-120%)}to{opacity:1;transform:translate(0)}}@-webkit-keyframes slideOutDown{0%{transform:translate(0)}to{visibility:hidden;transform:translateY(500px)}}@keyframes slideOutDown{0%{transform:translate(0)}to{visibility:hidden;transform:translateY(500px)}}@-webkit-keyframes slideOutUp{0%{transform:translate(0)}to{visibility:hidden;transform:translateY(-500px)}}@keyframes slideOutUp{0%{transform:translate(0)}to{visibility:hidden;transform:translateY(-500px)}}.mosha__zoomIn-enter-active{-webkit-animation:zoomIn .5s;animation:zoomIn .5s}.mosha__zoomIn-leave-active{-webkit-animation:zoomOut .5s;animation:zoomOut .5s}@-webkit-keyframes zoomIn{0%{opacity:0;transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{opacity:0;transform:scale3d(.3,.3,.3)}50%{opacity:1}}@-webkit-keyframes zoomOut{0%{opacity:1}50%{opacity:0;transform:scale3d(.3,.3,.3)}to{opacity:0}}@keyframes zoomOut{0%{opacity:1}50%{opacity:0;transform:scale3d(.3,.3,.3)}to{opacity:0}}.mosha__fadeOutRight-leave-active{-webkit-animation:fadeOutRight .5s;animation:fadeOutRight .5s}.mosha__fadeOutLeft-leave-active{-webkit-animation:fadeOutLeft .5s;animation:fadeOutLeft .5s}@-webkit-keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;transform:translate(-100%)}}@keyframes fadeOutLeft{0%{opacity:1}to{opacity:0;transform:translate(-100%)}}@-webkit-keyframes fadeOutRight{0%{opacity:1}to{opacity:0;transform:translate(100%)}}@keyframes fadeOutRight{0%{opacity:1}to{opacity:0;transform:translate(100%)}}.input[data-v-1d9af69e]{padding:.5rem;margin-top:.25rem;margin-bottom:.25rem;--tw-bg-opacity: 1;background-color:rgba(250,250,250,var(--tw-bg-opacity));border-radius:.375rem}.dark .input[data-v-1d9af69e]{--tw-bg-opacity: 1;background-color:rgba(38,38,38,var(--tw-bg-opacity))}.prism-editor-wrapper{width:100%;height:100%;display:flex;align-items:flex-start;overflow:auto;-o-tab-size:1.5em;tab-size:1.5em;-moz-tab-size:1.5em}@media (-ms-high-contrast:active),(-ms-high-contrast:none){.prism-editor-wrapper .prism-editor__textarea{color:transparent!important}.prism-editor-wrapper .prism-editor__textarea::-moz-selection{background-color:#accef7!important;color:transparent!important}.prism-editor-wrapper .prism-editor__textarea::selection{background-color:#accef7!important;color:transparent!important}}.prism-editor-wrapper .prism-editor__container{position:relative;text-align:left;box-sizing:border-box;padding:0;overflow:hidden;width:100%}.prism-editor-wrapper .prism-editor__line-numbers{height:100%;overflow:hidden;flex-shrink:0;padding-top:4px;margin-top:0;margin-right:10px}.prism-editor-wrapper .prism-editor__line-number{text-align:right;white-space:nowrap}.prism-editor-wrapper .prism-editor__textarea{position:absolute;top:0;left:0;height:100%;width:100%;resize:none;color:inherit;overflow:hidden;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;-webkit-text-fill-color:transparent}.prism-editor-wrapper .prism-editor__editor,.prism-editor-wrapper .prism-editor__textarea{margin:0;border:0;background:none;box-sizing:inherit;display:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-variant-ligatures:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;-moz-tab-size:inherit;-o-tab-size:inherit;tab-size:inherit;text-indent:inherit;text-rendering:inherit;text-transform:inherit;white-space:pre-wrap;word-wrap:keep-all;overflow-wrap:break-word;padding:0}.prism-editor-wrapper .prism-editor__textarea--empty{-webkit-text-fill-color:inherit!important}.prism-editor-wrapper .prism-editor__editor{position:relative;pointer-events:none}code[class*=language-],pre[class*=language-]{color:#000;background:none;font-family:Consolas,Monaco,"Andale Mono","Ubuntu Mono",monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{position:relative;margin:.5em 0;overflow:visible;padding:0}pre[class*=language-]>code{position:relative;border-left:10px solid #358ccb;box-shadow:-1px 0 #358ccb,0 0 0 1px #dfdfdf;background-color:#fdfdfd;background-image:linear-gradient(transparent 50%,rgba(69,142,209,.04) 50%);background-size:3em 3em;background-origin:content-box;background-attachment:local}code[class*=language-]{max-height:inherit;height:inherit;padding:0 1em;display:block;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background-color:#fdfdfd;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin-bottom:1em}:not(pre)>code[class*=language-]{position:relative;padding:.2em;border-radius:.3em;color:#c92c2c;border:1px solid rgba(0,0,0,.1);display:inline;white-space:normal}pre[class*=language-]:before,pre[class*=language-]:after{content:"";z-index:-2;display:block;position:absolute;bottom:.75em;left:.18em;width:40%;height:20%;max-height:13em;box-shadow:0 13px 8px #979797;-webkit-transform:rotate(-2deg);-moz-transform:rotate(-2deg);-ms-transform:rotate(-2deg);-o-transform:rotate(-2deg);transform:rotate(-2deg)}pre[class*=language-]:after{right:.75em;left:auto;-webkit-transform:rotate(2deg);-moz-transform:rotate(2deg);-ms-transform:rotate(2deg);-o-transform:rotate(2deg);transform:rotate(2deg)}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#7d8b99}.token.punctuation{color:#5f6364}.token.property,.token.tag,.token.boolean,.token.number,.token.function-name,.token.constant,.token.symbol,.token.deleted{color:#c92c2c}.token.selector,.token.attr-name,.token.string,.token.char,.token.function,.token.builtin,.token.inserted{color:#2f9c0a}.token.operator,.token.entity,.token.url,.token.variable{color:#a67f59;background:rgba(255,255,255,.5)}.token.atrule,.token.attr-value,.token.keyword,.token.class-name{color:#1990b8}.token.regex,.token.important{color:#e90}.language-css .token.string,.style .token.string{color:#a67f59;background:rgba(255,255,255,.5)}.token.important{font-weight:normal}.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}.token.namespace{opacity:.7}@media screen and (max-width: 767px){pre[class*=language-]:before,pre[class*=language-]:after{bottom:14px;box-shadow:none}}pre[class*=language-].line-numbers.line-numbers{padding-left:0}pre[class*=language-].line-numbers.line-numbers code{padding-left:3.8em}pre[class*=language-].line-numbers.line-numbers .line-numbers-rows{left:0}pre[class*=language-][data-line]{padding-top:0;padding-bottom:0;padding-left:0}pre[data-line] code{position:relative;padding-left:4em}pre .line-highlight{margin-top:0}.slide-right-enter-active,.slide-right-leave-active,.slide-left-enter-active,.slide-left-leave-active{transition:opacity .1s ease,transform .1s ease}.slide-right-leave-to,.slide-left-enter-from{opacity:0;transform:translate(60px)}.slide-right-enter-from,.slide-left-leave-to{opacity:0;transform:translate(-60px)}.snippet{font-family:"Consolas","monospace"}::-webkit-scrollbar{height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background-color:#9b9b9b66;border-radius:20px;border:transparent;margin-top:10px}.prism-editor__textarea{display:none}.prism-editor-wrapper .prism-editor__editor{pointer-events:auto!important}.prism-editor-wrapper .prism-editor__container{overflow:auto;scrollbar-width:thin;scrollbar-track-color:transparent;margin-right:27px}.prism-editor-wrapper .prism-editor__editor,.prism-editor-wrapper .prism-editor__textarea{white-space:pre!important;min-height:100px}.token.tag{color:#9370db}.token.operator{background:none}.token.function{--tw-text-opacity: 1;color:rgba(0,0,0,var(--tw-text-opacity))}.dark .token.function{--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.token.string{color:#9370db}#console{white-space:pre-wrap;font-family:"Consolas","monospace";font-size:12px}.item[data-v-63b115f1]{padding-left:.25rem;padding-right:.25rem;padding-bottom:.25rem;cursor:pointer;--tw-text-opacity: 1;color:rgba(82,82,82,var(--tw-text-opacity));--tw-bg-opacity: 1;background-color:rgba(245,245,245,var(--tw-bg-opacity))}.dark .item[data-v-63b115f1]{--tw-text-opacity: 1;color:rgba(212,212,212,var(--tw-text-opacity));--tw-bg-opacity: 1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.selected[data-v-63b115f1]{border-bottom-width:2px;--tw-border-opacity: 1;border-color:rgba(0,0,0,var(--tw-border-opacity));--tw-text-opacity: 1;color:rgba(0,0,0,var(--tw-text-opacity))}.dark .selected[data-v-63b115f1]{--tw-border-opacity: 1;border-color:rgba(255,255,255,var(--tw-border-opacity));--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}*,:before,:after{-webkit-box-sizing:border-box;box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e5e5}*{--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgba(59, 130, 246, .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000}:root{-moz-tab-size:4;-o-tab-size:4;tab-size:4}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}::moz-focus-inner{border-style:none;padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}a{color:inherit;text-decoration:inherit}body{margin:0;font-family:inherit;line-height:inherit}button{text-transform:none;background-color:transparent;background-image:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,[role=button]{cursor:pointer}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";line-height:1.5}hr{height:0;color:inherit;border-top-width:1px}h1{font-size:inherit;font-weight:inherit}input,button{font-family:inherit;font-size:100%;line-height:1.15;margin:0;padding:0;line-height:inherit;color:inherit}img{border-style:solid;max-width:100%;height:auto}input::placeholder{opacity:1;color:#a3a3a3}input::webkit-input-placeholder{opacity:1;color:#a3a3a3}input::-moz-placeholder{opacity:1;color:#a3a3a3}input:-ms-input-placeholder{opacity:1;color:#a3a3a3}input::-ms-input-placeholder{opacity:1;color:#a3a3a3}img,svg{display:block;vertical-align:middle}pre{font-size:1em;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}p,hr,h1,pre{margin:0}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}.dark .dark\:bg-black{--tw-bg-opacity: 1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgba(245,245,245,var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.dark .dark\:bg-gray-900{--tw-bg-opacity: 1;background-color:rgba(23,23,23,var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgba(250,250,250,var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity: 1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.dark .dark\:bg-white{--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.dark .dark\:bg-gray-800{--tw-bg-opacity: 1;background-color:rgba(38,38,38,var(--tw-bg-opacity))}.dark .dark\:border-dark-300{--tw-border-opacity: 1;border-color:rgba(45,45,45,var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity: 1;border-color:rgba(245,245,245,var(--tw-border-opacity))}.dark .dark\:border-black{--tw-border-opacity: 1;border-color:rgba(0,0,0,var(--tw-border-opacity))}.border-transparent{border-color:transparent}.\!border-gray-800{--tw-border-opacity: 1 !important;border-color:rgba(38,38,38,var(--tw-border-opacity))!important}.dark .dark\:border-gray-800{--tw-border-opacity: 1;border-color:rgba(38,38,38,var(--tw-border-opacity))}.dark .dark\:border-gray-700{--tw-border-opacity: 1;border-color:rgba(64,64,64,var(--tw-border-opacity))}.border-black{--tw-border-opacity: 1;border-color:rgba(0,0,0,var(--tw-border-opacity))}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-2xl{border-radius:1rem}.rounded-md{border-radius:.375rem}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.border-2{border-width:2px}.border{border-width:1px}.border-b-2{border-bottom-width:2px}.cursor-pointer{cursor:pointer}.flex{display:-webkit-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.flex-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;-webkit-flex-direction:row;flex-direction:row}.flex-col{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;-webkit-flex-direction:column;flex-direction:column}.items-center{-webkit-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center}.justify-items-center{justify-items:center}.justify-center{-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center}.justify-between{-webkit-box-pack:justify;-ms-flex-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.justify-around{-ms-flex-pack:distribute;-webkit-justify-content:space-around;justify-content:space-around}.flex-grow{-webkit-box-flex:1;-ms-flex-positive:1;-webkit-flex-grow:1;flex-grow:1}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.font-bold{font-weight:700}.font-semibold{font-weight:600}.font-normal{font-weight:400}.font-medium{font-weight:500}.h-6{height:1.5rem}.h-144{height:36rem}.h-33{height:8.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xm{font-size:.625rem;line-height:.75rem}.m-w-20{margin:5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mr-5{margin-right:1.25rem}.mb-32{margin-bottom:8rem}.ml-auto{margin-left:auto}.mt-6{margin-top:1.5rem}.mr-1{margin-right:.25rem}.mb-1\.5{margin-bottom:.375rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-5{margin-top:1.25rem}.mr-1\.9{margin-right:.475rem}.mr-1\.5{margin-right:.375rem}.min-h-screen{min-height:100vh}.min-h-320px{min-height:320px}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.p-7{padding:1.75rem}.p-4{padding:1rem}.px-15{padding-left:3.75rem;padding-right:3.75rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-7{padding-left:1.75rem;padding-right:1.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.pt-10{padding-top:2.5rem}.pt-1\.9{padding-top:.475rem}.pl-2{padding-left:.5rem}.pr-4{padding-right:1rem}.pb-6{padding-bottom:1.5rem}.pt-7{padding-top:1.75rem}.pb-3{padding-bottom:.75rem}.pt-5{padding-top:1.25rem}.pt-4{padding-top:1rem}.pt-1\.75{padding-top:.4375rem}.pt-2{padding-top:.5rem}.pb-11{padding-bottom:2.75rem}.pb-4{padding-bottom:1rem}.pt-1\.1{padding-top:.275rem}.pt-1\.3{padding-top:.325rem}.\code{position:relative;border-left:10px solid #358ccb;box-shadow:-1px 0 #358ccb,0 0 0 1px #dfdfdf;background-color:#fdfdfd;background-image:linear-gradient(transparent 50%,rgba(69,142,209,.04) 50%);background-size:3em 3em;background-origin:content-box;background-attachment:local}code[class*=language-]{max-height:inherit;height:inherit;padding:0 1em;display:block;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background-color:#fdfdfd;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;margin-bottom:1em}:not(pre)>code[class*=language-]{position:relative;padding:.2em;border-radius:.3em;color:#c92c2c;border:1px solid rgba(0,0,0,.1);display:inline;white-space:normal}pre[class*=language-]:before,pre[class*=language-]:after{content:"";z-index:-2;display:block;position:absolute;bottom:.75em;left:.18em;width:40%;height:20%;max-height:13em;box-shadow:0 13px 8px #979797;-webkit-transform:rotate(-2deg);-moz-transform:rotate(-2deg);-ms-transform:rotate(-2deg);-o-transform:rotate(-2deg);transform:rotate(-2deg)}pre[class*=language-]:after{right:.75em;left:auto;-webkit-transform:rotate(2deg);-moz-transform:rotate(2deg);-ms-transform:rotate(2deg);-o-transform:rotate(2deg);transform:rotate(2deg)}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#7d8b99}.token.punctuation{color:#5f6364}.token.property,.token.tag,.token.boolean,.token.number,.token.function-name,.token.constant,.token.symbol,.token.deleted{color:#c92c2c}.token.selector,.token.attr-name,.token.string,.token.char,.token.function,.token.builtin,.token.inserted{color:#2f9c0a}.token.operator,.token.entity,.token.url,.token.variable{color:#a67f59;background:rgba(255,255,255,.5)}.token.atrule,.token.attr-value,.token.keyword,.token.class-name{color:#1990b8}.token.regex,.token.important{color:#e90}.language-css .token.string,.style .token.string{color:#a67f59;background:rgba(255,255,255,.5)}.token.important{font-weight:normal}.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}.token.namespace{opacity:.7}@media screen and (max-width: 767px){pre[class*=language-]:before,pre[class*=language-]:after{bottom:14px;box-shadow:none}}pre[class*=language-].line-numbers.line-numbers{padding-left:0}pre[class*=language-].line-numbers.line-numbers code{padding-left:3.8em}pre[class*=language-].line-numbers.line-numbers .line-numbers-rows{left:0}pre[class*=language-][data-line]{padding-top:0;padding-bottom:0;padding-left:0}pre[data-line] code{position:relative;padding-left:4em}pre .line-highlight{margin-top:0}.slide-right-enter-active,.slide-right-leave-active,.slide-left-enter-active,.slide-left-leave-active{transition:opacity .1s ease,transform .1s ease}.slide-right-leave-to,.slide-left-enter-from{opacity:0;transform:translate(60px)}.slide-right-enter-from,.slide-left-leave-to{opacity:0;transform:translate(-60px)}.snippet{font-family:"Consolas","monospace"}::-webkit-scrollbar{height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background-color:#9b9b9b66;border-radius:20px;border:transparent;margin-top:10px}.prism-editor__textarea{display:none}.prism-editor-wrapper .prism-editor__editor{pointer-events:auto!important}.prism-editor-wrapper .prism-editor__container{overflow:auto;scrollbar-width:thin;scrollbar-track-color:transparent;margin-right:27px}.prism-editor-wrapper .prism-editor__editor,.prism-editor-wrapper .prism-editor__textarea{white-space:pre!important;min-height:100px}.token.tag{color:#9370db}.token.operator{background:none}.token.function{--tw-text-opacity: 1;color:rgba(0,0,0,var(--tw-text-opacity))}.dark .token.function{--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}.token.string{color:#9370db}#console{white-space:pre-wrap;font-family:"Consolas","monospace";font-size:12px}.item[data-v-63b115f1]{padding-left:.25rem;padding-right:.25rem;padding-bottom:.25rem;cursor:pointer;--tw-text-opacity: 1;color:rgba(82,82,82,var(--tw-text-opacity));--tw-bg-opacity: 1;background-color:rgba(245,245,245,var(--tw-bg-opacity))}.dark .item[data-v-63b115f1]{--tw-text-opacity: 1;color:rgba(212,212,212,var(--tw-text-opacity));--tw-bg-opacity: 1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.selected[data-v-63b115f1]{border-bottom-width:2px;--tw-border-opacity: 1;border-color:rgba(0,0,0,var(--tw-border-opacity));--tw-text-opacity: 1;color:rgba(0,0,0,var(--tw-text-opacity))}.dark .selected[data-v-63b115f1]{--tw-border-opacity: 1;border-color:rgba(255,255,255,var(--tw-border-opacity));--tw-text-opacity: 1;color:rgba(255,255,255,var(--tw-text-opacity))}*,:before,:after{-webkit-box-sizing:border-box;box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e5e5}*{--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgba(59, 130, 246, .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000}:root{-moz-tab-size:4;-o-tab-size:4;tab-size:4}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}::moz-focus-inner{border-style:none;padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}a{color:inherit;text-decoration:inherit}body{margin:0;font-family:inherit;line-height:inherit}button{text-transform:none;background-color:transparent;background-image:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,[role=button]{cursor:pointer}html{-webkit-text-size-adjust:100%;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";line-height:1.5}hr{height:0;color:inherit;border-top-width:1px}h1{font-size:inherit;font-weight:inherit}input,button{font-family:inherit;font-size:100%;line-height:1.15;margin:0;padding:0;line-height:inherit;color:inherit}img{border-style:solid;max-width:100%;height:auto}input::placeholder{opacity:1;color:#a3a3a3}input::webkit-input-placeholder{opacity:1;color:#a3a3a3}input::-moz-placeholder{opacity:1;color:#a3a3a3}input:-ms-input-placeholder{opacity:1;color:#a3a3a3}input::-ms-input-placeholder{opacity:1;color:#a3a3a3}img,svg{display:block;vertical-align:middle}pre{font-size:1em;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}p,hr,h1,pre{margin:0}ul{list-style:none;margin:0;padding:0}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}.dark .dark\:bg-black{--tw-bg-opacity: 1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgba(245,245,245,var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.dark .dark\:bg-gray-900{--tw-bg-opacity: 1;background-color:rgba(23,23,23,var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgba(250,250,250,var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity: 1;background-color:rgba(0,0,0,var(--tw-bg-opacity))}.dark .dark\:bg-white{--tw-bg-opacity: 1;background-color:rgba(255,255,255,var(--tw-bg-opacity))}.bg-true-gray-100{--tw-bg-opacity: 1;background-color:rgba(245,245,245,var(--tw-bg-opacity))}.dark .dark\:bg-dark-600{--tw-bg-opacity: 1;background-color:rgba(28,28,30,var(--tw-bg-opacity))}.dark .dark\:bg-gray-800{--tw-bg-opacity: 1;background-color:rgba(38,38,38,var(--tw-bg-opacity))}.dark .dark\:border-dark-300{--tw-border-opacity: 1;border-color:rgba(45,45,45,var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity: 1;border-color:rgba(245,245,245,var(--tw-border-opacity))}.dark .dark\:border-black{--tw-border-opacity: 1;border-color:rgba(0,0,0,var(--tw-border-opacity))}.border-transparent{border-color:transparent}.\!border-gray-800{--tw-border-opacity: 1 !important;border-color:rgba(38,38,38,var(--tw-border-opacity))!important}.border-true-gray-200{--tw-border-opacity: 1;border-color:rgba(229,229,229,var(--tw-border-opacity))}.dark .dark\:border-gray-800{--tw-border-opacity: 1;border-color:rgba(38,38,38,var(--tw-border-opacity))}.dark .dark\:border-gray-700{--tw-border-opacity: 1;border-color:rgba(64,64,64,var(--tw-border-opacity))}.border-black{--tw-border-opacity: 1;border-color:rgba(0,0,0,var(--tw-border-opacity))}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-2xl{border-radius:1rem}.rounded-md{border-radius:.375rem}.rounded-b-lg{border-bottom-right-radius:.5rem;border-bottom-left-radius:.5rem}.border-1{border-width:1px}.border-2{border-width:2px}.border{border-width:1px}.border-b-2{border-bottom-width:2px}.box-border{-webkit-box-sizing:border-box;box-sizing:border-box}.cursor-pointer{cursor:pointer}.flex{display:-webkit-box;display:-ms-flexbox;display:-webkit-flex;display:flex}.hidden{display:none}.flex-row{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;-webkit-flex-direction:row;flex-direction:row}.flex-col{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;-webkit-flex-direction:column;flex-direction:column}.items-center{-webkit-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center}.justify-items-center{justify-items:center}.justify-center{-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center}.justify-between{-webkit-box-pack:justify;-ms-flex-pack:justify;-webkit-justify-content:space-between;justify-content:space-between}.justify-around{-ms-flex-pack:distribute;-webkit-justify-content:space-around;justify-content:space-around}.flex-grow{-webkit-box-flex:1;-ms-flex-positive:1;-webkit-flex-grow:1;flex-grow:1}.float-right{float:right}.font-sans{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}.font-bold{font-weight:700}.font-semibold{font-weight:600}.font-normal{font-weight:400}.font-medium{font-weight:500}.h-3{height:.75rem}.h-6{height:1.5rem}.h-144{height:36rem}.h-25px{height:25px}.h-33{height:8.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xm{font-size:.625rem;line-height:.75rem}.m-auto{margin:auto}.m-w-20{margin:5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mr-5{margin-right:1.25rem}.mb-1\.5{margin-bottom:.375rem}.ml-auto{margin-left:auto}.mt-24px{margin-top:24px}.mt-6{margin-top:1.5rem}.mr-1{margin-right:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-5{margin-top:1.25rem}.mr-1\.9{margin-right:.475rem}.mr-1\.5{margin-right:.375rem}.max-h-33px{max-height:33px}.min-h-320px{min-height:320px}.min-w-93px{min-width:93px}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.p-7{padding:1.75rem}.p-2{padding:.5rem}.p-4{padding:1rem}.px-15{padding-left:3.75rem;padding-right:3.75rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-7{padding-left:1.75rem;padding-right:1.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5px{padding-top:5px;padding-bottom:5px}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.pt-1\.5{padding-top:.375rem}.pb-1\.5{padding-bottom:.375rem}.pt-10{padding-top:2.5rem}.pt-1\.9{padding-top:.475rem}.pl-2{padding-left:.5rem}.pr-4{padding-right:1rem}.pb-6{padding-bottom:1.5rem}.pt-7{padding-top:1.75rem}.pb-3{padding-bottom:.75rem}.pt-5{padding-top:1.25rem}.pt-4{padding-top:1rem}.pt-1\.75{padding-top:.4375rem}.pt-2{padding-top:.5rem}.pb-11{padding-bottom:2.75rem}.pb-4{padding-bottom:1rem}.pt-1\.1{padding-top:.275rem}.pt-1\.3{padding-top:.325rem}.\t in o?Ie(o,t,{enumerable:!0,configurable:!0,writable:!0,value:a}):o[t]=a,ie=(o,t)=>{for(var a in t||(t={}))Te.call(t,a)&&le(o,a,t[a]);if(re)for(var a of re(t))Ee.call(t,a)&&le(o,a,t[a]);return o};import{r as O,a as ce,u as de,w as j,d as Ce,b as Le,c as m,o as c,e as p,f as g,g as l,t as $,h as E,n as ue,i as pe,E as Se,S as Me,j as f,k,l as Oe,m as U,v as W,p as qe,q,s as _e,x as me,y as A,z as L,A as B,P as Ae,B as Pe,C as G,D as ee,F as M,G as P,T as Re,H as Be,I as Ve,J as je,K as De,L as ge,M as He,N as Ne,O as ze,Q as Ue,R as We,U as Ge}from"./vendor.5488b9be.js";const Ke=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))e(s);new MutationObserver(s=>{for(const i of s)if(i.type==="childList")for(const n of i.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&e(n)}).observe(document,{childList:!0,subtree:!0});function a(s){const i={};return s.integrity&&(i.integrity=s.integrity),s.referrerpolicy&&(i.referrerPolicy=s.referrerpolicy),s.crossorigin==="use-credentials"?i.credentials="include":s.crossorigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function e(s){if(s.ep)return;s.ep=!0;const i=a(s);fetch(s.href,i)}};Ke();const D=O({isDark:!1}),fe="dark-theme";function he(){return{theme:D,fetchTheme:()=>{D.isDark=localStorage.getItem(fe)==="true"},toggleTheme:()=>{D.isDark=!D.isDark,localStorage.setItem(fe,D.isDark)}}}function V(){const o=!"{{REPOSILITE.BASE_PATH}}".includes("REPOSILITE.BASE_PATH"),t=o?"{{REPOSILITE.BASE_PATH}}":"/",a=o?"{{REPOSILITE.ID}}":"reposilite-repository",e=o?"{{REPOSILITE.TITLE}}":"Reposilite Repository",s=o?"{{REPOSILITE.DESCRIPTION}}":"Public Maven repository hosted through the Reposilite",i=o?"{{REPOSILITE.ORGANIZATION_WEBSITE}}":location.protocol+"//"+location.host+t;return{available:o,basePath:t,id:a,title:e,description:s,organizationWebsite:i,organizationLogo:o?"{{REPOSILITE.ORGANIZATION_LOGO}}":"https://avatars.githubusercontent.com/u/75123628?s=200&v=4",icpLicense:o?"{{REPOSILITE.ICP_LICENSE}}":"\u56FDICP\u5907000000000\u53F7"}}const{basePath:Fe}=V(),te=()=>window.location.protocol+"//"+location.host+Fe,Qe=()=>te().endsWith("/")?te().slice(0,-1):te(),K=o=>Qe()+o,oe=(o,t)=>{const a=()=>o&&t?e(o,t):{},e=(n,r)=>({headers:{Authorization:`xBasic ${btoa(`${n}:${r}`)}`}}),s=(n,r)=>(r=r||a(),ce.get(K(n),ie({},r)));return{createURL:K,client:{auth:{me(n,r){return s("/api/auth/me",e(n,r))}},console:{},maven:{content(n){return s(`/${n}`)},details(n){return s(`/api/maven/details/${n||""}`)}}}}},S="",ve="session-token-name",be="session-token-secret",Ye="access-token:manager",F=O({name:S,secret:S}),ye={id:S,name:S,createdAt:S,permissions:[],routes:[]},Q=O({details:ye});function H(){const o=(n,r)=>{localStorage.setItem(ve,n),F.name=n,localStorage.setItem(be,r),F.secret=r},t=()=>{o(S,S),Q.details=ye},a=async(n,r)=>{try{const{client:d}=oe();if(n==S)throw new Error("Missing credentials");const u=await d.auth.me(n,r);return o(n,r),Q.details=u.data,{token:F,session:Q}}catch(d){throw t(),d}};return{token:F,session:Q,login:a,logout:t,fetchSession:()=>a(localStorage.getItem(ve),localStorage.getItem(be)),isLogged:n=>(n==null?void 0:n.name)!=S,isManager:n=>{var r;return(r=n==null?void 0:n.permissions)==null?void 0:r.find(d=>d.identifier==Ye)}}}const Y=O({watchable:0,path:""});function Ze(o){const t=de();return j(()=>t.params.qualifier,a=>{Y.path=a,Y.watchable++},{immediate:!0}),j(()=>o.name,a=>Y.watchable++),{qualifier:Y}}var v=(o,t)=>{for(const[a,e]of t)o[a]=e;return o};const Je=Ce({setup(){const{title:o,description:t,organizationLogo:a,icpLicense:e}=V();Le({title:o,description:t});const{theme:s,fetchTheme:i}=he(),{fetchSession:n,token:r,session:d}=H(),{qualifier:u}=Ze(r);return i(),n().catch(h=>{}),{theme:s,qualifier:u,token:r,session:d,icpLicense:e}}}),Xe={key:0,class:"absolute bottom-4 w-full text-center text-xs"},et={href:"https://beian.miit.gov.cn",target:"_blank"};function tt(o,t,a,e,s,i){const n=m("router-view");return c(),p("div",{class:ue({dark:o.theme.isDark})},[g(n,{class:"min-h-screen dark:bg-black dark:text-white",qualifier:o.qualifier,token:o.token,session:o.session},null,8,["qualifier","token","session"]),o.icpLicense?(c(),p("div",Xe,[l("a",et,$(o.icpLicense),1)])):E("",!0)],2)}var ot=v(Je,[["render",tt]]);const nt={},st={class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},at=l("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"},null,-1),rt=[at];function lt(o,t){return c(),p("svg",st,rt)}var it=v(nt,[["render",lt]]);const ct={components:{GlobeIcon:it},setup(){const{description:o,organizationWebsite:t,organizationLogo:a}=V();return{description:o,organizationWebsite:t,organizationLogo:a}}},dt={class:"bg-gray-100 dark:bg-black"},ut={class:"container mx-auto flex flex-row"},pt={class:"w-35"},_t=["src"],mt={class:"flex flex-col justify-center px-10"},gt={class:"flex flex-row py-2"},ft=["href"];function ht(o,t,a,e,s,i){const n=m("GlobeIcon");return c(),p("div",dt,[l("div",ut,[l("div",pt,[l("img",{class:"border-2 rounded-full dark:border-gray-700",src:e.organizationLogo},null,8,_t)]),l("div",mt,[l("div",null,[l("p",null,$(e.description),1)]),l("div",gt,[g(n),l("a",{class:"px-3 text-gray-500",href:e.organizationWebsite},$(e.organizationWebsite),9,ft)])])])])}var vt=v(ct,[["render",ht]]);const bt={},yt={class:"mx-2 py-1.5 rounded-full bg-white dark:bg-gray-900 font-bold px-6 text-sm cursor-pointer"};function kt(o,t){return c(),p("div",yt,[pe(o.$slots,"default")])}var xt=v(bt,[["render",kt]]);const wt={inheritAttrs:!1,components:{VueFinalModal:Se,ModalsContainer:Me},setup(){const{login:o}=H(),t=f(!1),a=f(""),e=f(""),s=()=>t.value=!1;return{name:a,secret:e,close:s,showLogin:t,signin:(n,r)=>{o(n,r).then(d=>q(`Dashboard accessed as ${n}`,{position:"bottom-right"})).then(d=>s()).catch(d=>{console.log(d),q(`${d.response.status}: ${d.response.data.message}`,{type:"danger"})})}}}},$t=o=>(_e("data-v-1d9af69e"),o=o(),me(),o),It={class:"relative border bg-white dark:bg-gray-900 border-gray-100 dark:border-black m-w-20 py-5 px-10 rounded-2xl shadow-xl text-center"},Tt=$t(()=>l("p",{class:"font-bold text-xl pb-4"},"Login with access token",-1)),Et={class:"text-right mt-1"};function Ct(o,t,a,e,s,i){const n=m("vue-final-modal");return c(),p("div",null,[g(n,qe({modelValue:e.showLogin,"onUpdate:modelValue":t[6]||(t[6]=r=>e.showLogin=r)},o.$attrs,{classes:"flex justify-center items-center"}),{default:k(()=>[l("div",It,[Tt,l("form",{class:"flex flex-col w-96",onSubmit:t[4]||(t[4]=Oe(r=>e.signin(e.name,e.secret),["prevent"]))},[U(l("input",{placeholder:"Name","onUpdate:modelValue":t[0]||(t[0]=r=>e.name=r),type:"text",class:"input"},null,512),[[W,e.name]]),U(l("input",{placeholder:"Secret","onUpdate:modelValue":t[1]||(t[1]=r=>e.secret=r),type:"password",class:"input"},null,512),[[W,e.secret]]),l("div",Et,[l("button",{onClick:t[2]||(t[2]=r=>e.close()),class:"text-blue-400 text-xs"},"\u2190 Back to index")]),l("div",{class:"bg-gray-100 dark:bg-gray-800 py-2 my-3 rounded-md cursor-pointer",onClick:t[3]||(t[3]=r=>e.signin(e.name,e.secret))},"Sign in")],32),l("button",{class:"absolute top-0 right-0 mt-5 mr-5",onClick:t[5]||(t[5]=r=>e.close())},"\u{1F5D9}")])]),_:1},16,["modelValue"]),l("div",{onClick:t[7]||(t[7]=r=>e.showLogin=!0)},[pe(o.$slots,"button",{},void 0,!0)])])}var Lt=v(wt,[["render",Ct],["__scopeId","data-v-1d9af69e"]]);const St={},Mt={class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},Ot=l("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"},null,-1),qt=[Ot];function At(o,t){return c(),p("svg",Mt,qt)}var Pt=v(St,[["render",At]]);const Rt={},Bt={class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},Vt=l("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"},null,-1),jt=[Vt];function Dt(o,t){return c(),p("svg",Bt,jt)}var Ht=v(Rt,[["render",Dt]]);const Nt={components:{MenuButton:xt,LoginModal:Lt,MoonIcon:Pt,SunIcon:Ht},props:{token:{type:Object,required:!0}},setup(o){const{theme:t,toggleTheme:a}=he(),{title:e}=V(),{isLogged:s,logout:i}=H(),n=o.token,r=A(()=>s(n));return{token:n,title:e,logged:r,signout:()=>i(),theme:t,toggleTheme:a}}},zt={class:"flex flex-row"},Ut={key:0,class:"pt-1.1 px-2"},Wt=B(" Welcome "),Gt={class:"font-bold underline"},Kt=B(" Sign in "),Ft=B(" Logout ");function Qt(o,t,a,e,s,i){const n=m("MenuButton"),r=m("LoginModal"),d=m("SunIcon"),u=m("MoonIcon");return c(),p("nav",zt,[e.logged?(c(),p("div",Ut,[Wt,l("span",Gt,$(e.token.name),1)])):E("",!0),g(r,null,{button:k(()=>[e.logged?E("",!0):(c(),L(n,{key:0},{default:k(()=>[Kt]),_:1}))]),_:1}),e.logged?(c(),L(n,{key:1,onClick:t[0]||(t[0]=h=>e.signout())},{default:k(()=>[Ft]),_:1})):E("",!0),l("div",{class:"pl-2 pt-1.3 cursor-pointer rounded-full bg-white dark:bg-gray-900",onClick:t[1]||(t[1]=h=>e.toggleTheme())},[e.theme.isDark?(c(),L(d,{key:0,class:"mr-1.9"})):(c(),L(u,{key:1,class:"mr-1.5"}))])])}var Yt=v(Nt,[["render",Qt]]);const Zt={components:{Hero:vt,Menu:Yt},props:{token:{type:Object,required:!0}},setup(o){const t=o.token,{title:a}=V();return{token:t,title:a}}},Jt={class:"bg-gray-100 dark:bg-black dark:text-white"},Xt={class:"container mx-auto flex flex-row py-10 justify-between"},eo={class:"text-xl font-medium py-1"};function to(o,t,a,e,s,i){const n=m("router-link"),r=m("Menu"),d=m("Hero");return c(),p("header",Jt,[l("div",Xt,[l("h1",eo,[g(n,{to:"/"},{default:k(()=>[B($(e.title),1)]),_:1})]),g(r,{token:e.token,class:"mt-0.5"},null,8,["token"])]),g(d,{class:"pt-2 pb-11"})])}var oo=v(Zt,[["render",to]]);function no(){return{createSnippets:(t,a,e)=>[{name:"Maven",lang:"xml",snippet:`
-
- ${t}
- ${a}
- ${e}
- `.trim()},{name:"Gradle Groovy",lang:"xml",snippet:`implementation "${t}:${a}:${e}"`},{name:"Gradle Kotlin",lang:"kotlin",snippet:`implementation("${t}:${a}:${e}")`},{name:"SBT",lang:"scala",snippet:`"${t}" %% "${a}" %% "${e}"`}]}}function so(){const{id:o,title:t}=V();return{createRepositories:e=>{const s=A(()=>e.path.split("/")[0]),i=o+(e.path?`-${s.value}`:""),n=location.protocol+"//"+location.host+(e.path?`/${s.value}`:"/{repository}");return[{name:"Maven",lang:"xml",snippet:`
-
- ${i}
- ${t}
- ${n}
-
- `.trim()},{name:"Gradle Groovy",lang:"groovy",snippet:`maven {
- url "${n}"
- }`.trim()},{name:"Gradle Kotlin",lang:"kotlin",snippet:`maven {
- url = uri("${n}")
-}`},{name:"SBT",lang:"scala",snippet:`resolvers += "${i}" at "${n}"`}]}}}const ao=new DOMParser;function ro(){return{parseMetadata:s=>ao.parseFromString(s,"text/xml"),groupId:s=>{var i,n;return(n=(i=s==null?void 0:s.getElementsByTagName("groupId")[0])==null?void 0:i.firstChild)==null?void 0:n.nodeValue},artifactId:s=>{var i,n;return(n=(i=s==null?void 0:s.getElementsByTagName("artifactId")[0])==null?void 0:i.firstChild)==null?void 0:n.nodeValue},versions:s=>{var i,n,r;return(r=(n=Array.from((i=s==null?void 0:s.getElementsByTagName("versioning")[0])==null?void 0:i.children))==null?void 0:n.map(d=>d.firstChild.nodeValue))!=null?r:["{unknown}"]}}}const lo={},io={class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},co=l("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"},null,-1),uo=[co];function po(o,t){return c(),p("svg",io,uo)}var _o=v(lo,[["render",po]]);const mo={components:{PrismEditor:Ae,CopyIcon:_o},props:{qualifier:{type:Object,required:!0},token:{type:Object,required:!0}},setup(o){const t=o.qualifier,a=o.token,e=f(""),s=f([]),{createRepositories:i}=so(),{createSnippets:n}=no(),{parseMetadata:r,groupId:d,artifactId:u,versions:h}=ro(),{client:_}=oe(a.name,a.secret),{copy:I,isSupported:b}=Pe(),x=f(localStorage.getItem("card-tab")||"Maven");G(()=>localStorage.setItem("card-tab",x.value));const R=()=>{s.value=i(t),e.value="Repository details"},J=T=>{const y=r(T);s.value=n(d(y),u(y),h(y)[0]),e.value="Artifact details"};G(()=>{const T=t.path.split("/");if(T.length==1&&T[0]==""){R();return}_.maven.content(`${t.path}/maven-metadata.xml`).then(y=>J(y.data)).catch(y=>{y.message!=="Request failed with status code 404"&&console.log(y),R()})}),G(()=>{s.value.forEach(T=>{T.highlighter=y=>{var z;return ee.highlight(y,(z=ee.languages[T.lang])!=null?z:ee.languages.js)}})});const C=f("slide-right");return j(x,(T,y)=>{const z=s.value.findIndex(X=>X.name===T),$e=s.value.findIndex(X=>X.name===y);C.value=z-$e<0?"slide-left":"slide-right"}),{title:e,configurations:s,selectedTab:x,transitionName:C,copy:async()=>{const{snippet:T}=s.value.find(y=>y.name===x.value);return await I(T),q("Copied snippet",{type:"info"})},isCopySupported:b}}},go={class:"bg-white dark:bg-gray-900 shadow-lg p-7 rounded-xl border-gray-100 dark:border-black"},fo={class:"flex flex-row justify-between"},ho={class:"font-bold flex items-center w-full"},vo={class:"flex"},bo=["onClick"],yo=l("hr",{class:"dark:border-gray-800"},null,-1),ko={class:"overflow-hidden"};function xo(o,t,a,e,s,i){const n=m("copy-icon"),r=m("prism-editor");return c(),p("div",go,[l("div",fo,[l("h1",ho,[B($(e.title)+" ",1),e.isCopySupported?(c(),p("span",{key:0,onClick:t[0]||(t[0]=(...d)=>e.copy&&e.copy(...d)),class:"ml-auto cursor-pointer"},[g(n)])):E("",!0)])]),l("div",vo,[(c(!0),p(M,null,P(e.configurations,d=>(c(),p("div",{key:d.name,onClick:u=>e.selectedTab=d.name,class:ue(["py-4 px-7 flex-grow text-center border-b-2 cursor-pointer border-transparent",{"!border-gray-800":d.name===e.selectedTab}])},$(d.name),11,bo))),128))]),yo,l("div",ko,[g(Re,{name:e.transitionName,mode:"out-in"},{default:k(()=>[(c(),p("div",{key:e.selectedTab,class:"relative h-33 mt-6 p-4 mr-1 rounded-lg bg-gray-100 dark:bg-gray-800"},[(c(!0),p(M,null,P(e.configurations,d=>(c(),p(M,null,[d.name===e.selectedTab?(c(),L(r,{key:0,class:"snippet absolute text-sm",modelValue:d.snippet,"onUpdate:modelValue":u=>d.snippet=u,highlight:d.highlighter,readonly:"","line-numbers":""},null,8,["modelValue","onUpdate:modelValue","highlight"])):E("",!0)],64))),256))]))]),_:1},8,["name"])])])}var wo=v(mo,[["render",xo]]);const $o={props:{file:{type:Object,required:!0}},setup(o){return{file:o.file,prettyBytes:Be}}},Io={class:"flex flex-row justify-between mb-1.5 py-3 rounded-full bg-white dark:bg-gray-900 lg:max-w-2/5 xl:max-w-1/2 cursor-pointer"},To={class:"flex flex-row"},Eo={key:0,class:"text-xm px-6 pt-1.75"},Co={key:1,class:"text-xm px-6 pt-1.75"},Lo={class:"font-semibold"},So={key:0,class:"px-6"};function Mo(o,t,a,e,s,i){return c(),p("div",Io,[l("div",To,[e.file.type=="DIRECTORY"?(c(),p("div",Eo,"\u26AB")):(c(),p("div",Co,"\u26AA")),l("div",Lo,$(e.file.name),1)]),e.file.contentLength?(c(),p("div",So,$(e.prettyBytes(e.file.contentLength)),1)):E("",!0)])}var Oo=v($o,[["render",Mo]]);const qo={components:{Card:wo,Entry:Oo},props:{qualifier:{type:Object,required:!0},token:{type:Object,required:!0}},setup(o){const t=o.qualifier,a=o.token,e=f(""),s=f([]),i=f(!1),n=f(void 0),r=_=>_.type=="DIRECTORY",d=de(),u=_=>(_.endsWith("/")?_.slice(0,-1):_).split("/").slice(0,-1).join("/")||"/";j(()=>t.watchable,async _=>{const{client:I}=oe(a.name,a.secret);I.maven.details(t.path).then(b=>{s.value=b.data.files,i.value=s.value.length==0,n.value=void 0}).catch(b=>{console.log(b),q(`${b.response.status}: ${b.response.data.message}`,{type:"danger"}),n.value=b}),e.value=u(`/${t.path}`)},{immediate:!0});const h=A(()=>{const _=d.path.split("/");return _.map((I,b)=>({link:_.slice(0,b+1).join("/")||"/",name:b===_.length-1?I:I+"/"}))});return{qualifier:t,token:a,parentPath:e,files:s,isEmpty:i,isErrored:n,isDirectory:r,createURL:K,breadcrumbs:h}}},Ao={class:"bg-gray-100"},Po={class:"bg-gray-100 dark:bg-black"},Ro={class:"container mx-auto"},Bo={class:"pt-7 pb-3 pl-2 font-semibold"},Vo=l("span",{class:"select-none"},"Index of ",-1),jo={class:"select-text"},Do=l("span",{class:"font-normal text-xl text-gray-500 select-none"}," \u2934 ",-1),Ho={class:"dark:bg-black"},No={class:"container mx-auto relative min-h-320px mb-32"},zo={class:"lg:absolute pt-5 -top-5 right-8"},Uo={class:"pt-4"},Wo=["href"],Go={key:0},Ko=l("p",null,"Directory is empty",-1),Fo=[Ko],Qo={key:1},Yo=l("p",null,"Directory not found",-1),Zo=[Yo];function Jo(o,t,a,e,s,i){const n=m("router-link"),r=m("Card"),d=m("Entry");return c(),p("div",Ao,[l("div",Po,[l("div",Ro,[l("p",Bo,[Vo,l("span",jo,[(c(!0),p(M,null,P(e.breadcrumbs,u=>(c(),L(n,{key:u.link,to:u.link},{default:k(()=>[B($(u.name),1)]),_:2},1032,["to"]))),128))]),g(n,{to:e.parentPath},{default:k(()=>[Do]),_:1},8,["to"])])])]),l("div",Ho,[l("div",No,[l("div",zo,[g(r,{qualifier:e.qualifier,token:e.token},null,8,["qualifier","token"])]),l("div",Uo,[(c(!0),p(M,null,P(e.files,u=>(c(),p("div",{key:u},[e.isDirectory(u)?(c(),L(n,{key:0,to:o.append(o.$route.path,u.name)},{default:k(()=>[g(d,{file:u},null,8,["file"])]),_:2},1032,["to"])):(c(),p("a",{key:1,href:e.createURL(o.$route.path+"/"+u.name),target:"_blank"},[g(d,{file:u},null,8,["file"])],8,Wo))]))),128)),e.isEmpty?(c(),p("div",Go,Fo)):E("",!0),e.isErrored?(c(),p("div",Qo,Zo)):E("",!0)])])])])}var Xo=v(qo,[["render",Jo]]);const en={},tn={class:"container mx-auto pt-10 px-15"},on=l("i",null,"Endpoints :: soon\u2122",-1),nn=[on];function sn(o,t){return c(),p("div",tn,nn)}var an=v(en,[["render",sn]]);const ke=["Other","Trace","Debug","Info","Warn","Error"],ne=O({}),xe=f(""),rn=f(0),Z=O([]),ln=new Ve,cn=o=>{var t;return(t=ke.find(a=>o.includes(`${a.toUpperCase()} | `)))!=null?t:"Other"},we=o=>ln.toHtml(o.replaceAll("<","<").replaceAll(">",">").replaceAll(" "," "));function dn(){ke.forEach(e=>{ne[e]={name:e,enabled:!0,count:A(()=>Z.reduce((s,i)=>s+(i.level===e),0))}});const o=A(()=>Z.filter(e=>e.message.toLowerCase().includes(xe.value.toLowerCase())).filter(e=>ne[e.level].enabled));return{levels:ne,log:o,filter:xe,sanitizeMessage:we,logMessage:e=>{Z.push({id:rn.value++,message:we(e),level:cn(e)})},clearLog:()=>{Z.length=0}}}const w=f(),se=f("");function un(){const o=K("/api/console/sock").replace("https","wss").replace("http","ws"),t=()=>{var u;return((u=w.value)==null?void 0:u.readyState)===WebSocket.OPEN},a=()=>{t()&&w.value.close()},e=()=>{w.value.send(se.value),se.value=""},s=f(),i=f(),n=f(),r=f();return{connection:w,connect:u=>{try{w.value=new WebSocket(o),w.value.onopen=()=>{w.value.send(`Authorization:${u.name}:${u.secret}`),s==null||s.value()},w.value.onmessage=_=>{_.data!="keep-alive"&&(i==null||i.value(_.data))},w.value.onerror=_=>n==null?void 0:n.value(_),w.value.onclose=()=>r==null?void 0:r.value();const h=setInterval(()=>{var _;t()?(_=w==null?void 0:w.value)==null||_.send("keep-alive"):clearInterval(h)},1e3*5)}catch(h){console.log(h),n==null||n.value(h)}},close:a,onOpen:s,onMessage:i,onError:n,onClose:r,command:se,execute:e}}const pn={props:{selectedTab:{type:Object,required:!0}},setup(o){const t=o.selectedTab,{levels:a,log:e,logMessage:s,filter:i,clearLog:n}=dn(),{onOpen:r,onMessage:d,onClose:u,onError:h,connect:_,close:I,command:b,execute:x}=un();je(()=>I());const R=()=>{const C=document.getElementById("console");C.scrollTop=C.scrollHeight},J=()=>{q("Connecting to the remote console",{type:"info"});const{token:C}=H();r.value=()=>n(),d.value=N=>{s(N),ge(()=>R())},h.value=N=>q(`${N||""}`,{type:"danger"}),u.value=()=>q("Connection with console has been lost",{type:"danger"}),_(C),ge(()=>{setTimeout(()=>document.getElementById("consoleInput").focus(),200)})};return j(()=>t.value,C=>C==="Console"?J():I(),{immediate:!0}),{log:e,command:b,execute:x,levels:a,filter:i}}},_n={class:"container mx-auto pt-10 px-15 text-xs"},mn={class:"flex text-sm flex-col xl:flex-row w-full py-2 justify-between"},gn={class:"flex flex-row justify-around w-full xl:w-1/2"},fn=["checked","onChange"],hn={class:"pl-2 pr-4"},vn={class:"bg-white dark:bg-gray-900 rounded-lg"},bn={id:"console",class:"overflow-scroll h-144 px-4"},yn=["innerHTML"],kn=l("hr",{class:"dark:border-dark-300"},null,-1);function xn(o,t,a,e,s,i){return c(),p("div",_n,[l("div",mn,[U(l("input",{placeholder:"Filter","onUpdate:modelValue":t[0]||(t[0]=n=>e.filter=n),class:"w-full xl:w-1/2 mr-5 py-1 px-4 rounded-lg bg-white dark:bg-gray-900"},null,512),[[W,e.filter]]),l("div",gn,[(c(!0),p(M,null,P(e.levels,n=>(c(),p("div",{key:n.name,class:"pt-1.9 xl:pt-0.8 font-sans"},[l("input",{type:"checkbox",checked:n.enabled,onChange:r=>n.enabled=!n.enabled},null,40,fn),l("span",hn,$(n.name)+" ("+$(n.count)+")",1)]))),128))])]),l("div",vn,[l("div",bn,[(c(!0),p(M,null,P(e.log,n=>(c(),p("p",{key:n.id,innerHTML:n.message,class:"whitespace-nowrap"},null,8,yn))),128))]),kn,U(l("input",{id:"consoleInput",placeholder:"Type command or '?' to get help",class:"w-full py-2 px-4 rounded-b-lg bg-white dark:bg-gray-900 dark:text-white","onUpdate:modelValue":t[1]||(t[1]=n=>e.command=n),onKeyup:t[2]||(t[2]=De(n=>e.execute(),["enter"]))},null,544),[[W,e.command]])])])}var wn=v(pn,[["render",xn]]);const $n={components:{Header:oo,Browser:Xo,Endpoints:an,Console:wn},props:{qualifier:{type:Object,required:!0},token:{type:Object,required:!0},session:{type:Object,required:!0}},setup(o){const t=o.qualifier,a=o.token,e=o.session,{isManager:s}=H(),i=O({value:localStorage.getItem("selectedTab")||"Overview"});G(()=>localStorage.setItem("selectedTab",i.value));const n=[{name:"Overview"},{name:"Endpoints"},{name:"Console",manager:!0}],r=A(()=>n.filter(u=>!(u==null?void 0:u.manager)||s(e.details)).map(u=>u.name)),d=A(()=>r.value.some(u=>u=="Console"));return{qualifier:t,token:a,isManager:s,menuTabs:r,consoleEnabled:d,selectedTab:i}}},In=o=>(_e("data-v-63b115f1"),o=o(),me(),o),Tn={class:"bg-gray-100 dark:bg-black"},En={class:"container mx-auto"},Cn=In(()=>l("hr",{class:"dark:border-gray-700"},null,-1)),Ln={class:"overflow-auto"};function Sn(o,t,a,e,s,i){const n=m("Header"),r=m("tab"),d=m("tabs"),u=m("Browser"),h=m("tab-panel"),_=m("Endpoints"),I=m("Console"),b=m("tab-panels");return c(),p("div",null,[g(n,{token:e.token},null,8,["token"]),l("div",Tn,[l("div",En,[g(d,{modelValue:e.selectedTab.value,"onUpdate:modelValue":t[0]||(t[0]=x=>e.selectedTab.value=x)},{default:k(()=>[(c(!0),p(M,null,P(e.menuTabs,(x,R)=>(c(),L(r,{class:"item font-normal",key:`menu${R}`,val:x,label:x,indicator:!0},null,8,["val","label"]))),128))]),_:1},8,["modelValue"])]),Cn,l("div",Ln,[g(b,{modelValue:e.selectedTab.value,"onUpdate:modelValue":t[1]||(t[1]=x=>e.selectedTab.value=x),animate:!0},{default:k(()=>[g(h,{val:"Overview"},{default:k(()=>[g(u,{qualifier:e.qualifier,token:e.token,ref:""},null,8,["qualifier","token"])]),_:1}),g(h,{val:"Endpoints"},{default:k(()=>[g(_)]),_:1}),e.consoleEnabled?(c(),L(h,{key:0,val:"Console"},{default:k(()=>[g(I,{selectedTab:e.selectedTab},null,8,["selectedTab"])]),_:1})):E("",!0)]),_:1},8,["modelValue"])])])])}var Mn=v($n,[["render",Sn],["__scopeId","data-v-63b115f1"]]);const On=He({history:Ne(),routes:[{path:"/:qualifier(.*)",name:"Index",component:Mn}]});const ae=ze(ot);ae.config.globalProperties.append=(o,t)=>o+(o.endsWith("/")?"":"/")+t;ae.config.globalProperties.drop=o=>(o.endsWith("/")?o.slice(0,-1):o).split("/").slice(0,-1).join("/");ae.use(Ue()).use(We,ce).use(Ge).use(On).mount("#app");
diff --git a/reposilite-backend/src/main/resources/reposilite-frontend/assets/index.f5778053.js b/reposilite-backend/src/main/resources/reposilite-frontend/assets/index.f5778053.js
new file mode 100644
index 000000000..19cf50a8e
--- /dev/null
+++ b/reposilite-backend/src/main/resources/reposilite-frontend/assets/index.f5778053.js
@@ -0,0 +1,16 @@
+var Ie=Object.defineProperty;var le=Object.getOwnPropertySymbols;var Te=Object.prototype.hasOwnProperty,Ce=Object.prototype.propertyIsEnumerable;var ie=(o,t,r)=>t in o?Ie(o,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):o[t]=r,ce=(o,t)=>{for(var r in t||(t={}))Te.call(t,r)&&ie(o,r,t[r]);if(le)for(var r of le(t))Ce.call(t,r)&&ie(o,r,t[r]);return o};import{r as q,a as de,u as ue,w as D,d as Ee,b as Se,c as m,o as d,e as u,f as g,g as l,t as k,h as C,n as ee,i as pe,E as Le,S as Me,j as f,k as x,l as Oe,m as G,v as K,p as qe,q as A,s as _e,x as me,y as B,z as E,A as P,P as Ae,B as Be,C as H,D as te,F as S,G as O,T as Pe,H as Re,I as je,J as Ve,K as De,L as ge,M as He,N as Ne,O as ze,Q as Ue,R as We,U as Ge}from"./vendor.5488b9be.js";const Ke=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))e(s);new MutationObserver(s=>{for(const i of s)if(i.type==="childList")for(const n of i.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&e(n)}).observe(document,{childList:!0,subtree:!0});function r(s){const i={};return s.integrity&&(i.integrity=s.integrity),s.referrerpolicy&&(i.referrerPolicy=s.referrerpolicy),s.crossorigin==="use-credentials"?i.credentials="include":s.crossorigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function e(s){if(s.ep)return;s.ep=!0;const i=r(s);fetch(s.href,i)}};Ke();const N=q({isDark:!1}),he="dark-theme";function fe(){return{theme:N,fetchTheme:()=>{N.isDark=localStorage.getItem(he)==="true"},toggleTheme:()=>{N.isDark=!N.isDark,localStorage.setItem(he,N.isDark)}}}function V(){const o=!"{{REPOSILITE.BASE_PATH}}".includes("REPOSILITE.BASE_PATH"),t=o?"{{REPOSILITE.BASE_PATH}}":"/",r=o?"{{REPOSILITE.ID}}":"reposilite-repository",e=o?"{{REPOSILITE.TITLE}}":"Reposilite Repository",s=o?"{{REPOSILITE.DESCRIPTION}}":"Public Maven repository hosted through the Reposilite",i=o?"{{REPOSILITE.ORGANIZATION_WEBSITE}}":location.protocol+"//"+location.host+t;return{available:o,basePath:t,id:r,title:e,description:s,organizationWebsite:i,organizationLogo:o?"{{REPOSILITE.ORGANIZATION_LOGO}}":"https://avatars.githubusercontent.com/u/75123628?s=200&v=4",icpLicense:o?"{{REPOSILITE.ICP_LICENSE}}":"\u56FDICP\u5907000000000\u53F7"}}const{basePath:Fe}=V(),oe=()=>window.location.protocol+"//"+location.host+Fe,Qe=()=>oe().endsWith("/")?oe().slice(0,-1):oe(),F=o=>Qe()+o,ne=(o,t)=>{const r=()=>o&&t?e(o,t):{},e=(n,a)=>({headers:{Authorization:`xBasic ${btoa(`${n}:${a}`)}`}}),s=(n,a)=>(a=a||r(),de.get(F(n),ce({},a)));return{createURL:F,client:{auth:{me(n,a){return s("/api/auth/me",e(n,a))}},console:{},maven:{content(n){return s(`/${n}`)},details(n){return s(`/api/maven/details/${n||""}`)}}}}},L="",ve="session-token-name",be="session-token-secret",Ye="access-token:manager",Q=q({name:L,secret:L}),ye={id:L,name:L,createdAt:L,permissions:[],routes:[]},Y=q({details:ye});function z(){const o=(n,a)=>{localStorage.setItem(ve,n),Q.name=n,localStorage.setItem(be,a),Q.secret=a},t=()=>{o(L,L),Y.details=ye},r=async(n,a)=>{try{const{client:p}=ne();if(n==L)throw new Error("Missing credentials");const c=await p.auth.me(n,a);return o(n,a),Y.details=c.data,{token:Q,session:Y}}catch(p){throw t(),p}};return{token:Q,session:Y,login:r,logout:t,fetchSession:()=>r(localStorage.getItem(ve),localStorage.getItem(be)),isLogged:n=>(n==null?void 0:n.name)!=L,isManager:n=>{var a;return(a=n==null?void 0:n.permissions)==null?void 0:a.find(p=>p.identifier==Ye)}}}const Z=q({watchable:0,path:""});function Ze(o){const t=ue();return D(()=>t.params.qualifier,r=>{Z.path=r,Z.watchable++},{immediate:!0}),D(()=>o.name,r=>Z.watchable++),{qualifier:Z}}var v=(o,t)=>{for(const[r,e]of t)o[r]=e;return o};const Je=Ee({setup(){const{title:o,description:t,organizationLogo:r,icpLicense:e}=V();Se({title:o,description:t});const{theme:s,fetchTheme:i}=fe(),{fetchSession:n,token:a,session:p}=z(),{qualifier:c}=Ze(a);return i(),n().catch(h=>{}),{theme:s,qualifier:c,token:a,session:p,icpLicense:e}}}),Xe={key:0,class:"absolute h-3 pt-1.5 pb-1.5 w-full text-center text-xs"},et={href:"https://beian.miit.gov.cn",target:"_blank"};function tt(o,t,r,e,s,i){const n=m("router-view");return d(),u("div",{class:ee({dark:o.theme.isDark})},[g(n,{class:"dark:bg-black dark:text-white",qualifier:o.qualifier,token:o.token,session:o.session},null,8,["qualifier","token","session"]),o.icpLicense?(d(),u("div",Xe,[l("a",et,k(o.icpLicense),1)])):C("",!0)],2)}var ot=v(Je,[["render",tt]]);const nt={},st={class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},rt=l("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"},null,-1),at=[rt];function lt(o,t){return d(),u("svg",st,at)}var it=v(nt,[["render",lt]]);const ct={components:{GlobeIcon:it},setup(){const{description:o,organizationWebsite:t,organizationLogo:r}=V();return{description:o,organizationWebsite:t,organizationLogo:r}}},dt={class:"bg-gray-100 dark:bg-black"},ut={class:"container mx-auto flex flex-row t.value=!1;return{name:r,secret:e,close:s,showLogin:t,signin:(n,a)=>{o(n,a).then(p=>A(`Dashboard accessed as ${n}`,{position:"bottom-right"})).then(p=>s()).catch(p=>{console.log(p),A(`${p.response.status}: ${p.response.data.message}`,{type:"danger"})})}}}},It=o=>(_e("data-v-1d9af69e"),o=o(),me(),o),Tt={class:"relative border bg-white dark:bg-gray-900 border-gray-100 dark:border-black m-w-20 py-5 px-10 rounded-2xl shadow-xl text-center"},Ct=It(()=>l("p",{class:"font-bold text-xl pb-4"},"Login with access token",-1)),Et={class:"text-right mt-1"};function St(o,t,r,e,s,i){const n=m("vue-final-modal");return d(),u("div",null,[g(n,qe({modelValue:e.showLogin,"onUpdate:modelValue":t[6]||(t[6]=a=>e.showLogin=a)},o.$attrs,{classes:"flex justify-center items-center"}),{default:x(()=>[l("div",Tt,[Ct,l("form",{class:"flex flex-col w-96",onSubmit:t[4]||(t[4]=Oe(a=>e.signin(e.name,e.secret),["prevent"]))},[G(l("input",{placeholder:"Name","onUpdate:modelValue":t[0]||(t[0]=a=>e.name=a),type:"text",class:"input"},null,512),[[K,e.name]]),G(l("input",{placeholder:"Secret","onUpdate:modelValue":t[1]||(t[1]=a=>e.secret=a),type:"password",class:"input"},null,512),[[K,e.secret]]),l("div",Et,[l("button",{onClick:t[2]||(t[2]=a=>e.close()),class:"text-blue-400 text-xs"},"\u2190 Back to index")]),l("div",{class:"bg-gray-100 dark:bg-gray-800 py-2 my-3 rounded-md cursor-pointer",onClick:t[3]||(t[3]=a=>e.signin(e.name,e.secret))},"Sign in")],32),l("button",{class:"absolute top-0 right-0 mt-5 mr-5",onClick:t[5]||(t[5]=a=>e.close())},"\u{1F5D9}")])]),_:1},16,["modelValue"]),l("div",{onClick:t[7]||(t[7]=a=>e.showLogin=!0)},[pe(o.$slots,"button",{},void 0,!0)])])}var Lt=v($t,[["render",St],["__scopeId","data-v-1d9af69e"]]);const Mt={},Ot={class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},qt=l("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"},null,-1),At=[qt];function Bt(o,t){return d(),u("svg",Ot,At)}var Pt=v(Mt,[["render",Bt]]);const Rt={},jt={class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},Vt=l("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"},null,-1),Dt=[Vt];function Ht(o,t){return d(),u("svg",jt,Dt)}var Nt=v(Rt,[["render",Ht]]);const zt={components:{MenuButton:wt,LoginModal:Lt,MoonIcon:Pt,SunIcon:Nt},props:{token:{type:Object,required:!0}},setup(o){const{theme:t,toggleTheme:r}=fe(),{title:e}=V(),{isLogged:s,logout:i}=z(),n=o.token,a=B(()=>s(n));return{token:n,title:e,logged:a,signout:()=>i(),theme:t,toggleTheme:r}}},Ut={class:"flex flex-row"},Wt={key:0,class:"pt-1.1 px-2"},Gt=P(" Welcome "),Kt={class:"font-bold underline"},Ft=P(" Sign in "),Qt=P(" Logout ");function Yt(o,t,r,e,s,i){const n=m("MenuButton"),a=m("LoginModal"),p=m("SunIcon"),c=m("MoonIcon");return d(),u("nav",Ut,[e.logged?(d(),u("div",Wt,[Gt,l("span",Kt,k(e.token.name),1)])):C("",!0),g(a,null,{button:x(()=>[e.logged?C("",!0):(d(),E(n,{key:0},{default:x(()=>[Ft]),_:1}))]),_:1}),e.logged?(d(),E(n,{key:1,onClick:t[0]||(t[0]=h=>e.signout())},{default:x(()=>[Qt]),_:1})):C("",!0),l("div",{class:"pl-2 pt-1.3 cursor-pointer rounded-full bg-white dark:bg-gray-900 max-h-33px",onClick:t[1]||(t[1]=h=>e.toggleTheme())},[e.theme.isDark?(d(),E(p,{key:0,class:"mr-1.9"})):(d(),E(c,{key:1,class:"mr-1.5"}))])])}var Zt=v(zt,[["render",Yt]]);const Jt={components:{Hero:bt,Menu:Zt},props:{token:{type:Object,required:!0}},setup(o){const t=o.token,{title:r}=V();return{token:t,title:r}}},Xt={class:"bg-gray-100 dark:bg-black dark:text-white"},eo={class:"container mx-auto flex flex-row py-10 justify-between [P(k(e.title),1)]),_:1})]),g(a,{token:e.token,class:"mt-0.5"},null,8,["token"])]),g(p,{class:"pt-2 pb-11 [{name:"Maven",lang:"xml",snippet:`
+
+ ${t}
+ ${r}
+ ${e}
+ `.trim()},{name:"Gradle Groovy",lang:"xml",snippet:`implementation "${t}:${r}:${e}"`},{name:"Gradle Kotlin",lang:"kotlin",snippet:`implementation("${t}:${r}:${e}")`},{name:"SBT",lang:"scala",snippet:`"${t}" %% "${r}" %% "${e}"`}]}}function ro(){const{id:o,title:t}=V();return{createRepositories:e=>{const s=B(()=>e.path.split("/")[0]),i=o+(e.path?`-${s.value}`:""),n=location.protocol+"//"+location.host+(e.path?`/${s.value}`:"/{repository}");return[{name:"Maven",lang:"xml",snippet:`
+
+ ${i}
+ ${t}
+ ${n}
+
+ `.trim()},{name:"Gradle Groovy",lang:"groovy",snippet:`maven {
+ url "${n}"
+ }`.trim()},{name:"Gradle Kotlin",lang:"kotlin",snippet:`maven {
+ url = uri("${n}")
+}`},{name:"SBT",lang:"scala",snippet:`resolvers += "${i}" at "${n}"`}]}}}const ao=new DOMParser;function lo(){return{parseMetadata:s=>ao.parseFromString(s,"text/xml"),groupId:s=>{var i,n;return(n=(i=s==null?void 0:s.getElementsByTagName("groupId")[0])==null?void 0:i.firstChild)==null?void 0:n.nodeValue},artifactId:s=>{var i,n;return(n=(i=s==null?void 0:s.getElementsByTagName("artifactId")[0])==null?void 0:i.firstChild)==null?void 0:n.nodeValue},versions:s=>{var i,n,a;return(a=(n=Array.from((i=s==null?void 0:s.getElementsByTagName("versioning")[0])==null?void 0:i.children))==null?void 0:n.map(p=>p.firstChild.nodeValue))!=null?a:["{unknown}"]}}}const io={},co={class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},uo=l("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"},null,-1),po=[uo];function _o(o,t){return d(),u("svg",co,po)}var mo=v(io,[["render",_o]]);const go={},ho={class:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},fo=l("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M19 9l-7 7-7-7"},null,-1),vo=[fo];function bo(o,t){return d(),u("svg",ho,vo)}var yo=v(go,[["render",bo]]);const ko={components:{PrismEditor:Ae,CopyIcon:mo,DownIcon:yo},props:{qualifier:{type:Object,required:!0},token:{type:Object,required:!0}},setup(o){const t=o.qualifier,r=o.token,e=f(""),s=f([]),{createRepositories:i}=ro(),{createSnippets:n}=so(),{parseMetadata:a,groupId:p,artifactId:c,versions:h}=lo(),{client:_}=ne(r.name,r.secret),{copy:I,isSupported:b}=Be(),w=f(localStorage.getItem("card-tab")||"Maven"),R=f(localStorage.getItem("dropdown-open")||!1);H(()=>localStorage.setItem("card-tab",w.value)),H(()=>localStorage.setItem("dropdown-open",R.value));const U=()=>{s.value=i(t),e.value="Repository details"},M=T=>{const y=a(T);s.value=n(p(y),c(y),h(y)[0]),e.value="Artifact details"};H(()=>{const T=t.path.split("/");if(T.length==1&&T[0]==""){U();return}_.maven.content(`${t.path}/maven-metadata.xml`).then(y=>M(y.data)).catch(y=>{y.message!=="Request failed with status code 404"&&console.log(y),U()})}),H(()=>{s.value.forEach(T=>{T.highlighter=y=>{var W;return te.highlight(y,(W=te.languages[T.lang])!=null?W:te.languages.js)}})});const j=f("slide-right");return D(w,(T,y)=>{const W=s.value.findIndex(X=>X.name===T),$e=s.value.findIndex(X=>X.name===y);j.value=W-$e<0?"slide-left":"slide-right"}),{title:e,configurations:s,selectedTab:w,transitionName:j,copy:async()=>{const{snippet:T}=s.value.find(y=>y.name===w.value);return await I(T),A("Copied snippet",{type:"info"})},isCopySupported:b,dropdownOpen:R}}},xo={class:"bg-white dark:bg-gray-900 shadow-lg p-7 rounded-xl border-gray-100 dark:border-black"},wo={class:"flex flex-row justify-between"},$o={class:"font-bold flex items-center w-full"},Io={class:"flex e.copy&&e.copy(...c)),class:"ml-auto cursor-pointer"},[g(n)])):C("",!0)])]),l("div",Io,[(d(!0),u(S,null,O(e.configurations,c=>(d(),u("div",{key:c.name,onClick:h=>e.selectedTab=c.name,class:ee(["py-4 px-7 flex-grow text-center border-b-2 cursor-pointer border-transparent",{"!border-gray-800":c.name===e.selectedTab}])},k(c.name),11,To))),128))]),l("div",Co,[l("div",{class:"w-full box-border py-5px p-2 rounded-lg border-1 border-true-gray-200 dark:border-dark-300",onClick:t[1]||(t[1]=c=>e.dropdownOpen=!e.dropdownOpen)},[P(k(e.selectedTab)+" ",1),l("div",Eo,[g(a)])]),e.dropdownOpen?C("",!0):(d(),u("ul",So,[(d(!0),u(S,null,O(e.configurations,c=>(d(),u("li",{key:c.name,onClick:h=>{e.selectedTab=c.name,e.dropdownOpen=!e.dropdownOpen},class:ee(["dropdown py-1",{hidden:c.name===e.selectedTab}])},k(c.name),11,Lo))),128))]))]),Mo,l("div",Oo,[g(Pe,{name:e.transitionName,mode:"out-in"},{default:x(()=>[(d(),u("div",{key:e.selectedTab,class:"relative h-33 mt-6 p-4 mr-1 rounded-lg bg-gray-100 dark:bg-gray-800"},[(d(!0),u(S,null,O(e.configurations,c=>(d(),u(S,null,[c.name===e.selectedTab?(d(),E(p,{key:0,class:"snippet absolute text-sm",modelValue:c.snippet,"onUpdate:modelValue":h=>c.snippet=h,highlight:c.highlighter,readonly:"","line-numbers":""},null,8,["modelValue","onUpdate:modelValue","highlight"])):C("",!0)],64))),256))]))]),_:1},8,["name"])])])}var Ao=v(ko,[["render",qo]]);const Bo={props:{file:{type:Object,required:!0}},setup(o){return{file:o.file,prettyBytes:Re}}},Po={class:"flex flex-row justify-between mb-1.5 py-3 rounded-full bg-white dark:bg-gray-900 lg:max-w-2/5 xl:max-w-1/2 cursor-pointer"},Ro={class:"flex flex-row"},jo={key:0,class:"text-xm px-6 pt-1.75"},Vo={key:1,class:"text-xm px-6 pt-1.75"},Do={class:"font-semibold"},Ho={key:0,class:"px-6"};function No(o,t,r,e,s,i){return d(),u("div",Po,[l("div",Ro,[e.file.type=="DIRECTORY"?(d(),u("div",jo,"\u26AB")):(d(),u("div",Vo,"\u26AA")),l("div",Do,k(e.file.name),1)]),e.file.contentLength?(d(),u("div",Ho,k(e.prettyBytes(e.file.contentLength)),1)):C("",!0)])}var zo=v(Bo,[["render",No]]);const Uo={components:{Card:Ao,Entry:zo},props:{qualifier:{type:Object,required:!0},token:{type:Object,required:!0}},setup(o){const t=o.qualifier,r=o.token,e=f(""),s=f([]),i=f(!1),n=f(void 0),a=_=>_.type=="DIRECTORY",p=ue(),c=_=>(_.endsWith("/")?_.slice(0,-1):_).split("/").slice(0,-1).join("/")||"/";D(()=>t.watchable,async _=>{const{client:I}=ne(r.name,r.secret);I.maven.details(t.path).then(b=>{s.value=b.data.files,i.value=s.value.length==0,n.value=void 0}).catch(b=>{console.log(b),A(`${b.response.status}: ${b.response.data.message}`,{type:"danger"}),n.value=b}),e.value=c(`/${t.path}`)},{immediate:!0});const h=B(()=>{const _=p.path.split("/");return _.map((I,b)=>({link:_.slice(0,b+1).join("/")||"/",name:b===_.length-1?I:I+"/"}))});return{qualifier:t,token:r,parentPath:e,files:s,isEmpty:i,isErrored:n,isDirectory:a,createURL:F,breadcrumbs:h}}},Wo={class:"bg-gray-100"},Go={class:"bg-gray-100 dark:bg-black"},Ko={class:"container mx-auto"},Fo={class:"pt-7 pb-3 pl-2 font-semibold"},Qo=l("span",{class:"select-none"},"Index of ",-1),Yo={class:"select-text"},Zo=l("span",{class:"font-normal text-xl text-gray-500 select-none"}," \u2934 ",-1),Jo={class:"dark:bg-black"},Xo={class:"container mx-auto relative min-h-320px mb-1.5"},en={class:"lg:absolute pt-5 -top-5 right-8"},tn={class:"pt-4"},on=["href"],nn={key:0},sn=l("p",null,"Directory is empty",-1),rn=[sn],an={key:1},ln=l("p",null,"Directory not found",-1),cn=[ln];function dn(o,t,r,e,s,i){const n=m("router-link"),a=m("Card"),p=m("Entry");return d(),u("div",Wo,[l("div",Go,[l("div",Ko,[l("p",Fo,[Qo,l("span",Yo,[(d(!0),u(S,null,O(e.breadcrumbs,c=>(d(),E(n,{key:c.link,to:c.link},{default:x(()=>[P(k(c.name),1)]),_:2},1032,["to"]))),128))]),g(n,{to:e.parentPath},{default:x(()=>[Zo]),_:1},8,["to"])])])]),l("div",Jo,[l("div",Xo,[l("div",en,[g(a,{qualifier:e.qualifier,token:e.token},null,8,["qualifier","token"])]),l("div",tn,[(d(!0),u(S,null,O(e.files,c=>(d(),u("div",{key:c},[e.isDirectory(c)?(d(),E(n,{key:0,to:o.append(o.$route.path,c.name)},{default:x(()=>[g(p,{file:c},null,8,["file"])]),_:2},1032,["to"])):(d(),u("a",{key:1,href:e.createURL(o.$route.path+"/"+c.name),target:"_blank"},[g(p,{file:c},null,8,["file"])],8,on))]))),128)),e.isEmpty?(d(),u("div",nn,rn)):C("",!0),e.isErrored?(d(),u("div",an,cn)):C("",!0)])])])])}var un=v(Uo,[["render",dn]]);const pn={},_n={class:"container mx-auto pt-10 px-15"},mn=l("i",null,"Endpoints :: soon\u2122",-1),gn=[mn];function hn(o,t){return d(),u("div",_n,gn)}var fn=v(pn,[["render",hn]]);const ke=["Other","Trace","Debug","Info","Warn","Error"],se=q({}),xe=f(""),vn=f(0),J=q([]),bn=new je,yn=o=>{var t;return(t=ke.find(r=>o.includes(`${r.toUpperCase()} | `)))!=null?t:"Other"},we=o=>bn.toHtml(o.replaceAll("<","<").replaceAll(">",">").replaceAll(" "," "));function kn(){ke.forEach(e=>{se[e]={name:e,enabled:!0,count:B(()=>J.reduce((s,i)=>s+(i.level===e),0))}});const o=B(()=>J.filter(e=>e.message.toLowerCase().includes(xe.value.toLowerCase())).filter(e=>se[e.level].enabled));return{levels:se,log:o,filter:xe,sanitizeMessage:we,logMessage:e=>{J.push({id:vn.value++,message:we(e),level:yn(e)})},clearLog:()=>{J.length=0}}}const $=f(),re=f("");function xn(){const o=F("/api/console/sock").replace("https","wss").replace("http","ws"),t=()=>{var c;return((c=$.value)==null?void 0:c.readyState)===WebSocket.OPEN},r=()=>{t()&&$.value.close()},e=()=>{$.value.send(re.value),re.value=""},s=f(),i=f(),n=f(),a=f();return{connection:$,connect:c=>{try{$.value=new WebSocket(o),$.value.onopen=()=>{$.value.send(`Authorization:${c.name}:${c.secret}`),s==null||s.value()},$.value.onmessage=_=>{_.data!="keep-alive"&&(i==null||i.value(_.data))},$.value.onerror=_=>n==null?void 0:n.value(_),$.value.onclose=()=>a==null?void 0:a.value();const h=setInterval(()=>{var _;t()?(_=$==null?void 0:$.value)==null||_.send("keep-alive"):clearInterval(h)},1e3*5)}catch(h){console.log(h),n==null||n.value(h)}},close:r,onOpen:s,onMessage:i,onError:n,onClose:a,command:re,execute:e}}const wn={props:{selectedTab:{type:Object,required:!0}},setup(o){const t=o.selectedTab,{levels:r,log:e,logMessage:s,filter:i,clearLog:n}=kn(),{onOpen:a,onMessage:p,onClose:c,onError:h,connect:_,close:I,command:b,execute:w}=xn();Ve(()=>I());const R=()=>{const M=document.getElementById("console");M.scrollTop=M.scrollHeight},U=()=>{A("Connecting to the remote console",{type:"info"});const{token:M}=z();a.value=()=>n(),p.value=j=>{s(j),ge(()=>R())},h.value=j=>A(`${j||""}`,{type:"danger"}),c.value=()=>A("Connection with console has been lost",{type:"danger"}),_(M),ge(()=>{setTimeout(()=>document.getElementById("consoleInput").focus(),200)})};return D(()=>t.value,M=>M==="Console"?U():I(),{immediate:!0}),{log:e,command:b,execute:w,levels:r,filter:i}}},$n={class:"container mx-auto pt-10 px-15 text-xs"},In={class:"flex text-sm flex-col xl:flex-row w-full py-2 justify-between"},Tn={class:"flex flex-row justify-around w-full xl:w-1/2"},Cn=["checked","onChange"],En={class:"pl-2 pr-4"},Sn={class:"bg-white dark:bg-gray-900 rounded-lg"},Ln={id:"console",class:"overflow-scroll h-144 px-4"},Mn=["innerHTML"],On=l("hr",{class:"dark:border-dark-300"},null,-1);function qn(o,t,r,e,s,i){return d(),u("div",$n,[l("div",In,[G(l("input",{placeholder:"Filter","onUpdate:modelValue":t[0]||(t[0]=n=>e.filter=n),class:"w-full xl:w-1/2 mr-5 py-1 px-4 rounded-lg bg-white dark:bg-gray-900"},null,512),[[K,e.filter]]),l("div",Tn,[(d(!0),u(S,null,O(e.levels,n=>(d(),u("div",{key:n.name,class:"pt-1.9 xl:pt-0.8 font-sans"},[l("input",{type:"checkbox",checked:n.enabled,onChange:a=>n.enabled=!n.enabled},null,40,Cn),l("span",En,k(n.name)+" ("+k(n.count)+")",1)]))),128))])]),l("div",Sn,[l("div",Ln,[(d(!0),u(S,null,O(e.log,n=>(d(),u("p",{key:n.id,innerHTML:n.message,class:"whitespace-nowrap"},null,8,Mn))),128))]),On,G(l("input",{id:"consoleInput",placeholder:"Type command or '?' to get help",class:"w-full py-2 px-4 rounded-b-lg bg-white dark:bg-gray-900 dark:text-white","onUpdate:modelValue":t[1]||(t[1]=n=>e.command=n),onKeyup:t[2]||(t[2]=De(n=>e.execute(),["enter"]))},null,544),[[K,e.command]])])])}var An=v(wn,[["render",qn]]);const Bn={components:{Header:no,Browser:un,Endpoints:fn,Console:An},props:{qualifier:{type:Object,required:!0},token:{type:Object,required:!0},session:{type:Object,required:!0}},setup(o){const t=o.qualifier,r=o.token,e=o.session,{isManager:s}=z(),i=q({value:localStorage.getItem("selectedTab")||"Overview"});H(()=>localStorage.setItem("selectedTab",i.value));const n=[{name:"Overview"},{name:"Endpoints"},{name:"Console",manager:!0}],a=B(()=>n.filter(c=>!(c==null?void 0:c.manager)||s(e.details)).map(c=>c.name)),p=B(()=>a.value.some(c=>c=="Console"));return{qualifier:t,token:r,isManager:s,menuTabs:a,consoleEnabled:p,selectedTab:i}}},Pn=o=>(_e("data-v-63b115f1"),o=o(),me(),o),Rn={class:"bg-gray-100 dark:bg-black"},jn={class:"container mx-auto"},Vn=Pn(()=>l("hr",{class:"dark:border-gray-700"},null,-1)),Dn={class:"overflow-auto"};function Hn(o,t,r,e,s,i){const n=m("Header"),a=m("tab"),p=m("tabs"),c=m("Browser"),h=m("tab-panel"),_=m("Endpoints"),I=m("Console"),b=m("tab-panels");return d(),u("div",null,[g(n,{token:e.token},null,8,["token"]),l("div",Rn,[l("div",jn,[g(p,{modelValue:e.selectedTab.value,"onUpdate:modelValue":t[0]||(t[0]=w=>e.selectedTab.value=w)},{default:x(()=>[(d(!0),u(S,null,O(e.menuTabs,(w,R)=>(d(),E(a,{class:"item font-normal",key:`menu${R}`,val:w,label:w,indicator:!0},null,8,["val","label"]))),128))]),_:1},8,["modelValue"])]),Vn,l("div",Dn,[g(b,{modelValue:e.selectedTab.value,"onUpdate:modelValue":t[1]||(t[1]=w=>e.selectedTab.value=w),animate:!0},{default:x(()=>[g(h,{val:"Overview"},{default:x(()=>[g(c,{qualifier:e.qualifier,token:e.token,ref:""},null,8,["qualifier","token"])]),_:1}),g(h,{val:"Endpoints"},{default:x(()=>[g(_)]),_:1}),e.consoleEnabled?(d(),E(h,{key:0,val:"Console"},{default:x(()=>[g(I,{selectedTab:e.selectedTab},null,8,["selectedTab"])]),_:1})):C("",!0)]),_:1},8,["modelValue"])])])])}var Nn=v(Bn,[["render",Hn],["__scopeId","data-v-63b115f1"]]);const zn=He({history:Ne(),routes:[{path:"/:qualifier(.*)",name:"Index",component:Nn}]});const ae=ze(ot);ae.config.globalProperties.append=(o,t)=>o+(o.endsWith("/")?"":"/")+t;ae.config.globalProperties.drop=o=>(o.endsWith("/")?o.slice(0,-1):o).split("/").slice(0,-1).join("/");ae.use(Ue()).use(We,de).use(Ge).use(zn).mount("#app");
diff --git a/reposilite-backend/src/main/resources/reposilite-frontend/index.html b/reposilite-backend/src/main/resources/reposilite-frontend/index.html
index 98f041792..1b5f86003 100644
--- a/reposilite-backend/src/main/resources/reposilite-frontend/index.html
+++ b/reposilite-backend/src/main/resources/reposilite-frontend/index.html
@@ -32,9 +32,9 @@
-
+
-
+
diff --git a/reposilite-backend/src/test/kotlin/com/reposilite/ReposiliteRunner.kt b/reposilite-backend/src/test/kotlin/com/reposilite/ReposiliteRunner.kt
index d57a63eaf..58f3f7d58 100644
--- a/reposilite-backend/src/test/kotlin/com/reposilite/ReposiliteRunner.kt
+++ b/reposilite-backend/src/test/kotlin/com/reposilite/ReposiliteRunner.kt
@@ -20,7 +20,6 @@ import com.reposilite.config.Configuration
import com.reposilite.config.Configuration.RepositoryConfiguration
import com.reposilite.journalist.Channel
import com.reposilite.journalist.backend.PrintStreamLogger
-import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.io.TempDir
@@ -49,7 +48,7 @@ internal abstract class ReposiliteRunner {
protected lateinit var reposilite: Reposilite
@BeforeEach
- protected fun bootApplication() = runBlocking {
+ protected fun bootApplication() {
if (!_extensionInitialized) {
throw IllegalStateException("Missing Reposilite extension on integration test")
}
diff --git a/reposilite-backend/src/test/kotlin/com/reposilite/ReposiliteSpecification.kt b/reposilite-backend/src/test/kotlin/com/reposilite/ReposiliteSpecification.kt
index fa962bc97..545327e1e 100644
--- a/reposilite-backend/src/test/kotlin/com/reposilite/ReposiliteSpecification.kt
+++ b/reposilite-backend/src/test/kotlin/com/reposilite/ReposiliteSpecification.kt
@@ -28,7 +28,7 @@ internal abstract class ReposiliteSpecification : ReposiliteRunner() {
fun usePredefinedTemporaryAuth(): Pair =
Pair("manager", "manager-secret")
- suspend fun useAuth(name: String, secret: String, routes: Map = emptyMap()): Pair {
+ fun useAuth(name: String, secret: String, routes: Map = emptyMap()): Pair {
val accessTokenFacade = reposilite.accessTokenFacade
var accessToken = accessTokenFacade.createAccessToken(CreateAccessTokenRequest(name, secret)).accessToken
diff --git a/reposilite-backend/src/test/kotlin/com/reposilite/statistics/specification/StatisticsIntegrationSpecification.kt b/reposilite-backend/src/test/kotlin/com/reposilite/statistics/specification/StatisticsIntegrationSpecification.kt
index 43429d04a..fefa2479f 100644
--- a/reposilite-backend/src/test/kotlin/com/reposilite/statistics/specification/StatisticsIntegrationSpecification.kt
+++ b/reposilite-backend/src/test/kotlin/com/reposilite/statistics/specification/StatisticsIntegrationSpecification.kt
@@ -18,14 +18,13 @@ package com.reposilite.statistics.specification
import com.reposilite.ReposiliteSpecification
import kong.unirest.Unirest.get
-import kotlinx.coroutines.runBlocking
internal abstract class StatisticsIntegrationSpecification : ReposiliteSpecification() {
- fun useRecordedRecord(uri: String): String = runBlocking {
+ fun useRecordedRecord(uri: String): String {
get("$base$uri").asEmpty()
reposilite.statisticsFacade.saveRecordsBulk()
- uri
+ return uri
}
}
\ No newline at end of file
diff --git a/reposilite-backend/src/test/kotlin/com/reposilite/statistics/specification/StatisticsSpecification.kt b/reposilite-backend/src/test/kotlin/com/reposilite/statistics/specification/StatisticsSpecification.kt
index 913e704d0..a0967714d 100644
--- a/reposilite-backend/src/test/kotlin/com/reposilite/statistics/specification/StatisticsSpecification.kt
+++ b/reposilite-backend/src/test/kotlin/com/reposilite/statistics/specification/StatisticsSpecification.kt
@@ -26,7 +26,7 @@ internal open class StatisticsSpecification {
private val logger = InMemoryLogger()
protected val statisticsFacade = StatisticsFacade(logger, InMemoryStatisticsRepository())
- protected suspend fun useRecordedIdentifier(type: RecordType, identifier: String, times: Int = 1): Pair {
+ protected fun useRecordedIdentifier(type: RecordType, identifier: String, times: Int = 1): Pair {
repeat(times) {
increaseAndSave(type, identifier)
}
@@ -34,7 +34,7 @@ internal open class StatisticsSpecification {
return Pair(type, identifier)
}
- protected suspend fun increaseAndSave(type: RecordType, identifier: String) {
+ protected fun increaseAndSave(type: RecordType, identifier: String) {
statisticsFacade.increaseRecord(type, identifier)
statisticsFacade.saveRecordsBulk()
}
diff --git a/reposilite-backend/src/test/kotlin/com/reposilite/token/specification/AccessTokenSpecification.kt b/reposilite-backend/src/test/kotlin/com/reposilite/token/specification/AccessTokenSpecification.kt
index 9d5197841..e30be0a54 100644
--- a/reposilite-backend/src/test/kotlin/com/reposilite/token/specification/AccessTokenSpecification.kt
+++ b/reposilite-backend/src/test/kotlin/com/reposilite/token/specification/AccessTokenSpecification.kt
@@ -26,10 +26,10 @@ internal open class AccessTokenSpecification {
protected val accessTokenFacade = AccessTokenFacade(InMemoryAccessTokenRepository(), InMemoryAccessTokenRepository())
- protected suspend fun createToken(name: String): CreateAccessTokenResponse =
+ protected fun createToken(name: String): CreateAccessTokenResponse =
accessTokenFacade.createAccessToken(CreateAccessTokenRequest(name))
- protected suspend fun createToken(name: String, secret: String): AccessToken =
+ protected fun createToken(name: String, secret: String): AccessToken =
accessTokenFacade.createAccessToken(CreateAccessTokenRequest(name, secret)).accessToken
}
\ No newline at end of file
diff --git a/reposilite-backend/src/test/workspace/reposilite.cdn b/reposilite-backend/src/test/workspace/reposilite.cdn
index 7eddddc93..76f2e3f77 100644
--- a/reposilite-backend/src/test/workspace/reposilite.cdn
+++ b/reposilite-backend/src/test/workspace/reposilite.cdn
@@ -145,15 +145,6 @@ keyStorePath: ${WORKING_DIRECTORY}/keystore.jks
keyStorePassword: reposilite
# Redirect http traffic to https
enforceSsl: false
-
-# Note: It might be hard to estimate the best amount of threads for your use case,
-# but you can safely increase amount of threads if needed and Reposilite will create only as much as it needs.
-# This option might be more useful to limit available memory resources to minimum (1 thread requires around 200kb to 1MB of memory)
-
-# By default, Reposilite 3.x uses experimental reactive mode to maximize performance of each spawned thread.
-# If you've noticed various unresolved behaviours like freezing and deadlocking, you can switch to the standard blocking mode.
-# Remember: Blocking mode requires more resources (threads) to handle the same throughput.
-reactiveMode: false
# Max amount of threads used by core thread pool (min: 4)
# The web thread pool handles first few steps of incoming http connections, as soon as possible all tasks are redirected to IO thread pool.
webThreadPool: 4
diff --git a/reposilite-frontend/package-lock.json b/reposilite-frontend/package-lock.json
index 688466989..9a90df26c 100644
--- a/reposilite-frontend/package-lock.json
+++ b/reposilite-frontend/package-lock.json
@@ -1,11 +1,11 @@
{
"name": "reposilite-frontend",
- "version": "3.0.0-alpha.3",
+ "version": "3.0.0-alpha.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "version": "3.0.0-alpha.3",
+ "version": "3.0.0-alpha.4",
"dependencies": {
"@vueuse/core": "^6.5.3",
"@vueuse/head": "^0.6.0",
diff --git a/reposilite-frontend/package.json b/reposilite-frontend/package.json
index 15a6a556c..8cf0b20c5 100644
--- a/reposilite-frontend/package.json
+++ b/reposilite-frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "reposilite-frontend",
- "version": "3.0.0-alpha.3",
+ "version": "3.0.0-alpha.4",
"scripts": {
"dev": "vite",
"build": "vite build && git add --all ../reposilite-backend/src/main/resources/reposilite-frontend/",
diff --git a/reposilite-site/docs/docker.md b/reposilite-site/docs/docker.md
index 85ddc7346..ea8decb3c 100644
--- a/reposilite-site/docs/docker.md
+++ b/reposilite-site/docs/docker.md
@@ -13,8 +13,8 @@ Reposilite defines two types of builds:
First of all, you have to pull the image from [DockerHub](https://hub.docker.com/r/dzikoysk/reposilite):
```shell-session
-// released builds, e.g. 3.0.0-alpha.3
-$ docker pull dzikoysk/reposilite:3.0.0-alpha.3
+// released builds, e.g. 3.0.0-alpha.4
+$ docker pull dzikoysk/reposilite:3.0.0-alpha.4
// nightly builds
$ docker pull dzikoysk/reposilite:nightly