diff --git a/.github/workflows/page-release.yml b/.github/workflows/page-release.yml new file mode 100644 index 0000000..31dcce8 --- /dev/null +++ b/.github/workflows/page-release.yml @@ -0,0 +1,39 @@ +name: Build and Push Wasm Assets + +on: + release: + types: [published] + +jobs: + build-and-push: + runs-on: ubuntu-latest # Adjust OS if needed + steps: + - uses: actions/checkout@v4 # Checkout the source code + + - name: Run Gradle Build + run: ./gradlew :launchpad:wasmJsBrowserProductionWebpack + + - name: Extract Wasm Assets + run: | + # Assuming launchpad/build/dist/wasmJs/productionExecutable exists after build + mkdir -p ./wasmJs # Create a folder to store extracted assets locally + cp -r launchpad/build/dist/wasmJs/productionExecutable/* ./wasmJs/ + + - name: Checkout Destination Repo (shallow clone) + uses: actions/checkout@v4 # Checkout the destination repository (shallow clone) + with: + repository: https://github.com/mdsadique-inam/launchpad + ref: main # Adjust branch if needed + token: ${{ secrets.PUSH_TOKEN }} # Use a secret for access token + fetch-depth: 1 # Only download the latest commit for efficiency + + - name: Push Wasm Assets to /docs + run: | + git config user.name "mdsadique-inam" # Replace with your name + git config user.email "md.sadique32@gmail.com" # Replace with your email + git checkout -b main # Create a new branch (replace with desired branch) + mv ./wasmJs/* ./docs/ # Move extracted assets to /docs folder + git add docs/ # Add the /docs folder to Git + git commit -m "Update Wasm assets from build" + git push origin main # Push the new branch + diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..e917c35 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,74 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.jetbrainsCompose) + alias(libs.plugins.serialization) +} + +kotlin { + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "app" + browser { + commonWebpackConfig { + outputFileName = "app.js" + devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { + static = (static ?: mutableListOf()).apply { + // Serve sources to debug inside browser + add(project.projectDir.path) + add(project.projectDir.path + "/commonMain/") + add(project.projectDir.path + "/wasmJsMain/") + } + proxy = (proxy ?: mutableMapOf()).apply { + put("/api", mapOf( + "target" to "http://localhost:8000", + "pathRewrite" to mapOf( + "/api" to "" + ) + )) + } + } + } + } + binaries.executable() + } + + sourceSets { + all { + languageSettings { + optIn("androidx.compose.material3.ExperimentalMaterial3Api") + optIn("org.jetbrains.compose.resources.ExperimentalResourceApi") + } + } + + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.ui) + implementation(compose.components.uiToolingPreview) + implementation(compose.components.resources) + implementation(compose.materialIconsExtended) + implementation(projects.commonCompose) + implementation(projects.shared) + implementation(projects.pdnsClient) + implementation(projects.koinCompose) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.navigation.compose) + implementation(libs.bundles.ktor.client) + implementation(libs.kotlinx.serialization.core) + implementation(libs.kotlinx.coroutines.core) + } + val wasmJsMain by getting { + dependencies { + implementation(libs.bundles.ktor.client.wasm) + } + } + } +} + +compose.experimental { + web.application {} +} \ No newline at end of file diff --git a/app/src/commonMain/kotlin/App.kt b/app/src/commonMain/kotlin/App.kt new file mode 100644 index 0000000..e69de29 diff --git a/commonCompose/build.gradle.kts b/commonCompose/build.gradle.kts index 49fc64a..4d71543 100644 --- a/commonCompose/build.gradle.kts +++ b/commonCompose/build.gradle.kts @@ -43,4 +43,10 @@ kotlin { compose.experimental { web.application {} +} + +compose.resources { + publicResClass = true + packageOfResClass = "pdnsmanager.commonCompose.resources" + generateResClass = auto } \ No newline at end of file diff --git a/commonCompose/src/commonMain/composeResources/values/strings.xml b/commonCompose/src/commonMain/composeResources/values/strings.xml index 425f4e1..85e4c29 100644 --- a/commonCompose/src/commonMain/composeResources/values/strings.xml +++ b/commonCompose/src/commonMain/composeResources/values/strings.xml @@ -1,6 +1,19 @@ + Error + Login + Login to PowerDNS manager + Register + Create an account in PowerDNS manager Icon warning Show Password Hide Password - Error + Don't have an account? Create one + Sign Up + Full Name + Username + Email + Password + Confirm Password + Username / Email + Already have an account? Login \ No newline at end of file diff --git a/commonCompose/src/commonMain/kotlin/ui/components/Button.kt b/commonCompose/src/commonMain/kotlin/ui/components/Button.kt index 7f53339..43c9ed5 100644 --- a/commonCompose/src/commonMain/kotlin/ui/components/Button.kt +++ b/commonCompose/src/commonMain/kotlin/ui/components/Button.kt @@ -1,15 +1,12 @@ package ui.components -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.RowScope import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonColors -import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon @@ -60,6 +57,20 @@ fun PMCTextButton( } } + +@Composable +fun PMCTextButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + loading: Boolean = false, +) { + PMCTextButton(onClick = onClick, modifier = modifier, enabled = enabled, loading = loading) { + Text(text) + } +} + @Composable fun PMCIconButton( onClick: () -> Unit, diff --git a/commonCompose/src/commonMain/kotlin/ui/components/TextField.kt b/commonCompose/src/commonMain/kotlin/ui/components/TextField.kt index f3cfb62..b4501a1 100644 --- a/commonCompose/src/commonMain/kotlin/ui/components/TextField.kt +++ b/commonCompose/src/commonMain/kotlin/ui/components/TextField.kt @@ -29,9 +29,10 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import org.jetbrains.compose.resources.stringResource -import pdnsmanager.commoncompose.generated.resources.Res -import pdnsmanager.commoncompose.generated.resources.hide_password -import pdnsmanager.commoncompose.generated.resources.show_password +import pdnsmanager.commonCompose.resources.Res +import pdnsmanager.commonCompose.resources.error +import pdnsmanager.commonCompose.resources.hide_password +import pdnsmanager.commonCompose.resources.show_password @Composable fun PMCOutlinedTextField( @@ -74,7 +75,7 @@ fun PMCOutlinedTextField( verticalAlignment = Alignment.CenterVertically ) { if (isError) { - Icon(Icons.Filled.Error, "error", tint = MaterialTheme.colorScheme.error) + Icon(Icons.Filled.Error, stringResource(Res.string.error), tint = MaterialTheme.colorScheme.error) } trailingIcon?.invoke() } diff --git a/commonCompose/src/commonMain/kotlin/ui/states/Res.kt b/commonCompose/src/commonMain/kotlin/ui/states/Res.kt index 9a78e28..a5b50a8 100644 --- a/commonCompose/src/commonMain/kotlin/ui/states/Res.kt +++ b/commonCompose/src/commonMain/kotlin/ui/states/Res.kt @@ -6,7 +6,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import org.jetbrains.compose.resources.ExperimentalResourceApi -import pdnsmanager.commoncompose.generated.resources.Res +import pdnsmanager.commonCompose.resources.Res @OptIn(ExperimentalResourceApi::class) @Composable diff --git a/database/migrations/20240304093131_pdns.ts b/database/migrations/20240304093131_pdns.ts new file mode 100644 index 0000000..a3fa9e6 --- /dev/null +++ b/database/migrations/20240304093131_pdns.ts @@ -0,0 +1,130 @@ +import type { Knex } from "knex"; + +export async function up(knex: Knex): Promise { + return knex.schema.raw(` + CREATE TABLE domains ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + master VARCHAR(128) DEFAULT NULL, + last_check INT DEFAULT NULL, + type TEXT NOT NULL, + notified_serial BIGINT DEFAULT NULL, + account VARCHAR(40) DEFAULT NULL, + options TEXT DEFAULT NULL, + catalog TEXT DEFAULT NULL, + CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + ); + + CREATE UNIQUE INDEX name_index ON domains(name); + CREATE INDEX catalog_idx ON domains(catalog); + + + CREATE TABLE records ( + id BIGSERIAL PRIMARY KEY, + domain_id INT DEFAULT NULL, + name VARCHAR(255) DEFAULT NULL, + type VARCHAR(10) DEFAULT NULL, + content VARCHAR(65535) DEFAULT NULL, + ttl INT DEFAULT NULL, + prio INT DEFAULT NULL, + disabled BOOL DEFAULT 'f', + ordername VARCHAR(255), + auth BOOL DEFAULT 't', + CONSTRAINT domain_exists + FOREIGN KEY(domain_id) REFERENCES domains(id) + ON DELETE CASCADE, + CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + ); + + CREATE INDEX rec_name_index ON records(name); + CREATE INDEX nametype_index ON records(name,type); + CREATE INDEX domain_id ON records(domain_id); + CREATE INDEX recordorder ON records (domain_id, ordername text_pattern_ops); + + + CREATE TABLE supermasters ( + ip INET NOT NULL, + nameserver VARCHAR(255) NOT NULL, + account VARCHAR(40) NOT NULL, + PRIMARY KEY(ip, nameserver) + ); + + + CREATE TABLE comments ( + id SERIAL PRIMARY KEY, + domain_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(10) NOT NULL, + modified_at INT NOT NULL, + account VARCHAR(40) DEFAULT NULL, + comment VARCHAR(65535) NOT NULL, + CONSTRAINT domain_exists + FOREIGN KEY(domain_id) REFERENCES domains(id) + ON DELETE CASCADE, + CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + ); + + CREATE INDEX comments_domain_id_idx ON comments (domain_id); + CREATE INDEX comments_name_type_idx ON comments (name, type); + CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); + + + CREATE TABLE domainmetadata ( + id SERIAL PRIMARY KEY, + domain_id INT REFERENCES domains(id) ON DELETE CASCADE, + kind VARCHAR(32), + content TEXT + ); + + CREATE INDEX domainidmetaindex ON domainmetadata(domain_id); + + + CREATE TABLE cryptokeys ( + id SERIAL PRIMARY KEY, + domain_id INT REFERENCES domains(id) ON DELETE CASCADE, + flags INT NOT NULL, + active BOOL, + published BOOL DEFAULT TRUE, + content TEXT + ); + + CREATE INDEX domainidindex ON cryptokeys(domain_id); + + + CREATE TABLE tsigkeys ( + id SERIAL PRIMARY KEY, + name VARCHAR(255), + algorithm VARCHAR(50), + secret VARCHAR(255), + CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT))) + ); + + CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); + `) +} + + +export async function down(knex: Knex): Promise { + return knex.schema.raw(` + DROP INDEX namealgoindex; + DROP TABLE tsigkeys; + DROP INDEX domainidindex; + DROP TABLE cryptokeys; + DROP INDEX domainidmetaindex; + DROP TABLE domainmetadata; + DROP INDEX comments_order_idx; + DROP INDEX comments_name_type_idx; + DROP INDEX comments_domain_id_idx; + DROP TABLE comments; + DROP TABLE supermasters; + DROP INDEX recordorder; + DROP INDEX domain_id; + DROP INDEX nametype_index; + DROP INDEX rec_name_index; + DROP TABLE records; + DROP INDEX catalog_idx; + DROP INDEX name_index; + DROP INDEX domains; + `) +} + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b85c4bc..f901465 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,13 @@ [versions] +java = "VERSION_17" agp = "8.2.0" -compose = "1.6.10-dev1557" -androidx = "2.8.0-dev1557" +compose = "1.6.10-dev1580" +androidx = "2.8.0-dev1580" junit = "4.13.2" kotlin = "1.9.23" kotlinx-serialization = "1.6.3" coroutines = "1.8.0" -ktor = "2.3.9" +ktor = "2.3.10" ktor-wasm = "3.0.0-wasm2" exposed = "0.48.0" postgres = "42.7.3" diff --git a/launchpad/src/commonMain/composeResources/values/strings.xml b/launchpad/src/commonMain/composeResources/values/strings.xml index 325af9d..6b30d8d 100644 --- a/launchpad/src/commonMain/composeResources/values/strings.xml +++ b/launchpad/src/commonMain/composeResources/values/strings.xml @@ -1,18 +1,3 @@ - Login - Login to PowerDNS manager - Register - Create an account in PowerDNS manager - Icon warning - Show Password - Hide Password - Don't have an account? Create one - Sign Up - Full Name - Username - Email - Password - Confirm Password - Username / Email - Already have an account? Login + \ No newline at end of file diff --git a/launchpad/src/commonMain/kotlin/ui/screens/LoginScreen.kt b/launchpad/src/commonMain/kotlin/ui/screens/LoginScreen.kt index d4f2c40..1123bc4 100644 --- a/launchpad/src/commonMain/kotlin/ui/screens/LoginScreen.kt +++ b/launchpad/src/commonMain/kotlin/ui/screens/LoginScreen.kt @@ -23,13 +23,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewModel.koinViewModel -import pdnsmanager.launchpad.generated.resources.Res -import pdnsmanager.launchpad.generated.resources.dont_have_an_account_create_one -import pdnsmanager.launchpad.generated.resources.icon_warning -import pdnsmanager.launchpad.generated.resources.login -import pdnsmanager.launchpad.generated.resources.login_to_powerdns_manager -import pdnsmanager.launchpad.generated.resources.password -import pdnsmanager.launchpad.generated.resources.username_or_email +import pdnsmanager.commonCompose.resources.Res +import pdnsmanager.commonCompose.resources.dont_have_an_account_create_one +import pdnsmanager.commonCompose.resources.icon_warning +import pdnsmanager.commonCompose.resources.login +import pdnsmanager.commonCompose.resources.login_to_powerdns_manager +import pdnsmanager.commonCompose.resources.password +import pdnsmanager.commonCompose.resources.username_or_email import ui.components.PMCButton import ui.components.PMCOutlinedTextField import ui.components.PMCPasswordTextField @@ -66,7 +66,11 @@ fun LoginScreen( horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { - Icon(Icons.Filled.Warning, contentDescription = stringResource(Res.string.icon_warning), tint = MaterialTheme.colorScheme.error) + Icon( + Icons.Filled.Warning, + contentDescription = stringResource(Res.string.icon_warning), + tint = MaterialTheme.colorScheme.error + ) Spacer(modifier = Modifier.width(5.dp)) Text( text = uiState.error ?: "", @@ -103,10 +107,9 @@ fun LoginScreen( Spacer(modifier = Modifier.height(15.dp)) PMCTextButton( + text = stringResource(Res.string.dont_have_an_account_create_one), onClick = navigateToRegister, - ) { - Text(stringResource(Res.string.dont_have_an_account_create_one)) - } + ) } } } diff --git a/launchpad/src/commonMain/kotlin/ui/screens/RegisterScreen.kt b/launchpad/src/commonMain/kotlin/ui/screens/RegisterScreen.kt index b7d0e16..902943b 100644 --- a/launchpad/src/commonMain/kotlin/ui/screens/RegisterScreen.kt +++ b/launchpad/src/commonMain/kotlin/ui/screens/RegisterScreen.kt @@ -23,16 +23,16 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewModel.koinViewModel -import pdnsmanager.launchpad.generated.resources.Res -import pdnsmanager.launchpad.generated.resources.already_have_an_account_login -import pdnsmanager.launchpad.generated.resources.confirm_password -import pdnsmanager.launchpad.generated.resources.create_an_account_in_powerdns_manager -import pdnsmanager.launchpad.generated.resources.email -import pdnsmanager.launchpad.generated.resources.full_name -import pdnsmanager.launchpad.generated.resources.icon_warning -import pdnsmanager.launchpad.generated.resources.password -import pdnsmanager.launchpad.generated.resources.signup -import pdnsmanager.launchpad.generated.resources.username +import pdnsmanager.commonCompose.resources.Res +import pdnsmanager.commonCompose.resources.already_have_an_account_login +import pdnsmanager.commonCompose.resources.confirm_password +import pdnsmanager.commonCompose.resources.create_an_account_in_powerdns_manager +import pdnsmanager.commonCompose.resources.email +import pdnsmanager.commonCompose.resources.full_name +import pdnsmanager.commonCompose.resources.icon_warning +import pdnsmanager.commonCompose.resources.password +import pdnsmanager.commonCompose.resources.signup +import pdnsmanager.commonCompose.resources.username import ui.components.PMCButton import ui.components.PMCOutlinedTextField import ui.components.PMCPasswordTextField @@ -131,10 +131,9 @@ fun RegisterScreen( Spacer(modifier = Modifier.height(15.dp)) PMCTextButton( + text = stringResource(Res.string.already_have_an_account_login), onClick = navigateToLogin, - ) { - Text(stringResource(Res.string.already_have_an_account_login)) - } + ) } } } diff --git a/launchpad/src/commonMain/kotlin/ui/screens/Screen.kt b/launchpad/src/commonMain/kotlin/ui/screens/Screen.kt index ba9447d..3402a25 100644 --- a/launchpad/src/commonMain/kotlin/ui/screens/Screen.kt +++ b/launchpad/src/commonMain/kotlin/ui/screens/Screen.kt @@ -1,9 +1,9 @@ package ui.screens import org.jetbrains.compose.resources.StringResource -import pdnsmanager.launchpad.generated.resources.Res -import pdnsmanager.launchpad.generated.resources.login -import pdnsmanager.launchpad.generated.resources.register +import pdnsmanager.commonCompose.resources.Res +import pdnsmanager.commonCompose.resources.login +import pdnsmanager.commonCompose.resources.register sealed class Screen(val route: String, val resource: StringResource) { data object Login : Screen("/login", Res.string.login) diff --git a/pdnsClient/build.gradle.kts b/pdnsClient/build.gradle.kts index a9e7456..ad0ebd9 100644 --- a/pdnsClient/build.gradle.kts +++ b/pdnsClient/build.gradle.kts @@ -6,6 +6,10 @@ plugins { } kotlin { + jvmToolchain { + languageVersion = JavaLanguageVersion.of(JavaVersion.valueOf(libs.versions.java.get()).toString()) + vendor = JvmVendorSpec.AZUL + } jvm() @OptIn(ExperimentalWasmDsl::class) wasmJs { browser() diff --git a/server/build.gradle.kts b/server/build.gradle.kts index cba5411..8629c27 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -16,7 +16,7 @@ application { kotlin { jvmToolchain { - languageVersion = JavaLanguageVersion.of(JavaVersion.VERSION_20.toString()) + languageVersion = JavaLanguageVersion.of(JavaVersion.valueOf(libs.versions.java.get()).toString()) vendor = JvmVendorSpec.AZUL } } @@ -42,3 +42,10 @@ dependencies { testImplementation(libs.koin.test.junit) } +ktor { + docker { + jreVersion.set(JavaVersion.valueOf(libs.versions.java.get())) + localImageName.set("pdns-webserver") + imageTag.set("0.0.1-preview") + } +} \ No newline at end of file diff --git a/server/src/main/kotlin/mdsadiqueinam/github/io/repositories/CertificateRepository.kt b/server/src/main/kotlin/mdsadiqueinam/github/io/repositories/CertificateRepository.kt index 431819b..b0c1efd 100644 --- a/server/src/main/kotlin/mdsadiqueinam/github/io/repositories/CertificateRepository.kt +++ b/server/src/main/kotlin/mdsadiqueinam/github/io/repositories/CertificateRepository.kt @@ -17,8 +17,4 @@ fun getConnectedNetworkIPv6(): String? { } return null -} - -fun main() { - println(getConnectedNetworkIPv6()) } \ No newline at end of file diff --git a/server/src/main/resources/application.conf b/server/src/main/resources/application.conf index cccd34a..648cd4c 100644 --- a/server/src/main/resources/application.conf +++ b/server/src/main/resources/application.conf @@ -19,7 +19,7 @@ jwt { database { pg { - url = "jdbc:postgresql://localhost:5432/powerdns" + url = "jdbc:postgresql://postgres:5432/powerdns" driver = "org.postgresql.Driver" user = "struxe" password = "strongPassword" diff --git a/settings.gradle.kts b/settings.gradle.kts index 311b402..eb113ea 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,9 +19,15 @@ dependencyResolutionManagement { maven("https://maven.pkg.jetbrains.space/kotlin/p/wasm/experimental") } } + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version("0.8.0") +} + include(":koin-compose") include(":commonCompose") include(":launchpad") +include(":app") include(":server") include(":pdnsClient") include(":shared") \ No newline at end of file diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 8bc1888..cac79b2 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -6,6 +6,10 @@ plugins { } kotlin { + jvmToolchain { + languageVersion = JavaLanguageVersion.of(JavaVersion.valueOf(libs.versions.java.get()).toString()) + vendor = JvmVendorSpec.AZUL + } @OptIn(ExperimentalWasmDsl::class) wasmJs { browser()