├── .github └── workflows │ └── main.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── SECURITY.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── doyouhost │ │ └── servercommander │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── doyouhost │ │ │ └── servercommander │ │ │ ├── Container.kt │ │ │ ├── ContainersAdapter.kt │ │ │ ├── ContainersDiffCallback.kt │ │ │ ├── DockerViewPagerAdapter.kt │ │ │ ├── LoginActivity.kt │ │ │ ├── MainActivity.kt │ │ │ ├── SshConnection.kt │ │ │ ├── YunohostConnection.kt │ │ │ ├── YunohostViewPagerAdapter.kt │ │ │ ├── fragments │ │ │ ├── DockerFragment.kt │ │ │ ├── HomeFragment.kt │ │ │ ├── SettingsFragment.kt │ │ │ ├── SystemFragment.kt │ │ │ ├── TerminalFragment.kt │ │ │ └── YunohostFragment.kt │ │ │ └── viewModels │ │ │ └── RefreshViewModel.kt │ ├── res │ │ ├── drawable-v24 │ │ │ ├── account_group.xml │ │ │ ├── alpha_y_circle_outline.xml │ │ │ ├── application.xml │ │ │ ├── application_export.xml │ │ │ ├── bottle_tonic_plus.xml │ │ │ ├── briefcase_arrow_up_down.xml │ │ │ ├── console_network.xml │ │ │ ├── docker.xml │ │ │ ├── domain.xml │ │ │ ├── download.xml │ │ │ ├── ic_launcher_foreground.xml │ │ │ ├── oci.xml │ │ │ ├── package_variant_closed.xml │ │ │ ├── power.xml │ │ │ ├── run_fast.xml │ │ │ ├── server_network.xml │ │ │ ├── server_network_off.xml │ │ │ ├── stop_circle.xml │ │ │ └── swap_vertical_bold.xml │ │ ├── drawable │ │ │ ├── archive_arrow_up.xml │ │ │ ├── archive_search.xml │ │ │ ├── bmc_button.xml │ │ │ ├── bmc_logo.xml │ │ │ ├── circular_loading_bar.xml │ │ │ ├── cpu_64_bit.xml │ │ │ ├── fire_alert.xml │ │ │ ├── harddisk.xml │ │ │ ├── ic_baseline_device_thermostat_24.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── memory.xml │ │ │ ├── package_variant_closed.xml │ │ │ ├── penguin.xml │ │ │ ├── play_circle.xml │ │ │ ├── refresh.xml │ │ │ ├── restart.xml │ │ │ ├── router_network.xml │ │ │ ├── router_wireless.xml │ │ │ ├── thermometer.xml │ │ │ └── weather_sunny.xml │ │ ├── layout │ │ │ ├── activity_login.xml │ │ │ ├── activity_main.xml │ │ │ ├── alert_dialog_container_stats.xml │ │ │ ├── alert_dialog_password.xml │ │ │ ├── alert_dialog_updates.xml │ │ │ ├── dashboard_item_disk_info.xml │ │ │ ├── dashboard_item_heaviest_app_info.xml │ │ │ ├── dashboard_item_kernel_info.xml │ │ │ ├── dashboard_item_localip_info.xml │ │ │ ├── dashboard_item_package_info.xml │ │ │ ├── dashboard_item_publicip_info.xml │ │ │ ├── dashboard_item_uptime_info.xml │ │ │ ├── docker_app_item.xml │ │ │ ├── edit_text_dialog.xml │ │ │ ├── fragment_docker.xml │ │ │ ├── fragment_home.xml │ │ │ ├── fragment_settings.xml │ │ │ ├── fragment_system.xml │ │ │ ├── fragment_terminal.xml │ │ │ ├── fragment_yunohost.xml │ │ │ ├── yunohost_dashboard_item_1.xml │ │ │ └── yunohost_dashboard_item_2.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ ├── ic_launcher_round.xml │ │ │ ├── server_commander.xml │ │ │ └── server_commander_round.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ ├── server_commander.png │ │ │ ├── server_commander_background.png │ │ │ ├── server_commander_foreground.png │ │ │ └── server_commander_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ ├── server_commander.png │ │ │ ├── server_commander_background.png │ │ │ ├── server_commander_foreground.png │ │ │ └── server_commander_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ ├── server_commander.png │ │ │ ├── server_commander_background.png │ │ │ ├── server_commander_foreground.png │ │ │ └── server_commander_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ ├── server_commander.png │ │ │ ├── server_commander_background.png │ │ │ ├── server_commander_foreground.png │ │ │ └── server_commander_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ ├── ic_launcher_round.webp │ │ │ ├── server_commander.png │ │ │ ├── server_commander_background.png │ │ │ ├── server_commander_foreground.png │ │ │ └── server_commander_round.png │ │ ├── values-land │ │ │ └── dimens.xml │ │ ├── values-night │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ ├── values-v29 │ │ │ └── themes.xml │ │ ├── values-w1240dp │ │ │ └── dimens.xml │ │ ├── values-w600dp │ │ │ └── dimens.xml │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── xml │ │ │ ├── backup_rules.xml │ │ │ └── data_extraction_rules.xml │ └── server_commander-playstore.png │ └── test │ └── java │ └── com │ └── doyouhost │ └── servercommander │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and create release on merged PR to master 2 | on: 3 | workflow_dispatch: 4 | 5 | pull_request: 6 | types: [closed] 7 | branches: 8 | - master 9 | 10 | jobs: 11 | gradle: 12 | if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - uses: actions/setup-java@v3 18 | with: 19 | distribution: temurin 20 | java-version: 11 21 | 22 | - name: Setup Gradle 23 | uses: gradle/gradle-build-action@v2 24 | 25 | - name: Get version name 26 | run: echo "VERSION_INFORMATION=$(${{github.workspace}}/gradlew -q --no-configuration-cache printVersionName)" >> $GITHUB_ENV 27 | 28 | - name: Execute Gradle build 29 | run: ./gradlew assembleDebug 30 | 31 | - name: Upload APK 32 | uses: actions/upload-artifact@v3 33 | with: 34 | name: apk 35 | path: app/build/outputs/apk/debug/app-debug.apk 36 | 37 | - name: Check for new app version 38 | run: | 39 | TAGS=$(git tag -l) 40 | if echo "$TAGS" | grep -q "${{ env.VERSION_INFORMATION }}"; then 41 | echo "This is not a new version! Exiting..." 42 | exit 1 43 | else 44 | echo "New version!" 45 | fi 46 | 47 | - name: Create tag 48 | id: create_tag 49 | run: | 50 | tag=${{ env.VERSION_INFORMATION }} 51 | echo "::set-output name=tag::$tag" 52 | 53 | - name: Create Draft Release 54 | id: create_release 55 | uses: actions/create-release@v1 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | with: 59 | tag_name: ${{ steps.create_tag.outputs.tag }} 60 | release_name: ${{ steps.create_tag.outputs.tag }} 61 | draft: true 62 | prerelease: false 63 | 64 | - uses: actions/upload-release-asset@v1.0.1 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | with: 68 | upload_url: ${{ steps.create_release.outputs.upload_url }} 69 | asset_path: app/build/outputs/apk/debug/app-debug.apk 70 | asset_name: app-debug.apk 71 | asset_content_type: application/apk 72 | 73 | - uses: eregon/publish-release@v1 74 | env: 75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 76 | with: 77 | release_id: ${{ steps.create_release.outputs.id }} 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | *.aab 17 | *.apk -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ServerCommander -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 120 | 121 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![ServerCommander](https://user-images.githubusercontent.com/33269270/207451491-55d576e8-ad3e-485e-8fb9-508d8dbb8dbb.png) 2 | # ServerCommander 3 | [![CodeFactor](https://www.codefactor.io/repository/github/morganmlgman/servercommander/badge)](https://www.codefactor.io/repository/github/morganmlgman/servercommander) 4 | 5 | - [ServerCommander compatibility](https://github.com/MorganMLGman/ServerCommander#servercommander-compatibility) 6 | - [How to install?](https://github.com/MorganMLGman/ServerCommander#installation) 7 | - [Why YunoHost?](https://github.com/MorganMLGman/ServerCommander#why-yunohost) 8 | - [Why Docker?](https://github.com/MorganMLGman/ServerCommander#why-docker) 9 | - [Known bugs](https://github.com/MorganMLGman/ServerCommander#known-bugs-) 10 | - [Changelog](https://github.com/MorganMLGman/ServerCommander#changelog) 11 | - [Privacy Policy](https://github.com/MorganMLGman/ServerCommander#privacy-policy) 12 | - [Thanks to](https://github.com/MorganMLGman/ServerCommander#thanks-to) 13 | 14 | 15 | ![ServerCommanderAll](https://user-images.githubusercontent.com/33269270/207556035-e45db8a6-45f2-4a95-b8ab-77b6893356d3.png) 16 | 17 | Mobile application that helps manage self-hosted homelab. The mobile client application is a great tool that allows you to manage your server quickly, conveniently, and almost anytime. For now, the application is mainly focused on retrieving information from the server. In the app you can see nicely displayed stats about your server kernel version, server uptime, boot disk usage, currently most demanding process, number of installed packages, and much more. If you are running Docker on your system there are some great options available for you. Listing Docker containers, showing statistics like processor and memory usage, disk or network i/o operations. Starting, stopping, and restarting containers is also ready to use. If you are running Yunohost instead, have no fear, there are also plenty of options available for you! You can check if you have any apps updates, and how many domains or users you have. With one click you can write the SSH key for a specific system user or check how many backups are created. Without system restrictions, auto update of the dashboard is available with a minimum period of 10 seconds and a maximum of 1 minute. 18 | 19 | **ATTENTION:** *Check Connection* button will download [Python script](https://github.com/MorganMLGman/copilot) onto your desktop in `/home/{user}/` directory. YunoHost uses only API and You can use this function without clicking on *Check connection* button. 20 | 21 | Within **ServerCommander** you can check such things as: 22 | - CPU temperature 23 | - CPU usage 24 | - RAM usage 25 | - Linux kernel version 26 | - Your local and public IP 27 | - System uptime 28 | - Disk usage 29 | - Most demanding app at the moment 30 | - Number of installed packages 31 | - And much much more (new features will be released in app updates) 32 | 33 | If your server is running Docker in addition you can _**start/stop/restart**_ containers, briefly view containers summary and check statistics about single container. 34 | 35 | If you are running YunoHost you can see ***how many users, backups and domains*** you have. Also there is a possibility to see ***how many applications are waiting for an update*** and ***you can list them***. You can ***push new ssh keys*** for a chosen user. 36 | 37 | However no matter what type of server you have, you can always perform actions like: 38 | - Server shutdown 39 | - Server reboot 40 | - Check system updates 41 | - Perform system update 42 | - (New features will be released in upcoming app updates) 43 | 44 | ## ServerCommander compatibility 45 | 46 | App is compatible with servers running: 47 | - [YunoHost](https://github.com/YunoHost) 48 | - [Docker](https://github.com/docker) 49 | 50 | under **Debian** and **Debian-based** operating system. 51 | 52 | Our app has been tested under these systems: 53 | - Debian 11 54 | - Ubuntu 22.04, Ubuntu 22.10 55 | - Raspberry Pi OS 56 | 57 | ## Installation 58 | 59 | You can install ServerCommander in three ways: 60 | - by downloading from [Google Play Store](https://play.google.com/store/apps/details?id=com.doyouhost.servercommander) 61 | - by downloading `.apk` from [GitHub Releases](https://github.com/MorganMLGman/ServerCommander/releases) 62 | - by building from source with Android Studio 63 | 64 | ## Why YunoHost? 65 | 66 | YunoHost is a great system that helps run own server without professional IT knowledge. That's why it is a great option for a lot of users that want to have own, private server with services, tools and applications. WebAdmin is a great option to manage server, but it is not that comfortable on mobile devices like it is on PCs. 67 | 68 | ## Why Docker? 69 | 70 | Docker is very powerful tool to host many applications on one machine. One of **ServerCommander** developers is using such machine :) Self-hosting with Docker is very flexible, you can choose one of thousands containers with the application you like and deploy it in a breeze. 71 | 72 | ## Known bugs :) 73 | - SwipeRefreshLayout on _SYSTEM_ and _DOCKER_ tabs in not returning to default state when _PasswordAlertDialog_ is dismissed by clicking outside the dialog or pressing back button. 74 | - App sometimes crashes when _TestConnection_ is ongoing and connection settings in _SETTINGS_ tab are changed. 75 | 76 | ## Changelog 77 | ### [v1.1.0](https://github.com/MorganMLGman/ServerCommander/releases/tag/v1.1.0) 78 | App is now compatible with new YunoHost 11.1 API. Some other small fixes. 79 | 80 | ### [v1.0.3](https://github.com/MorganMLGman/ServerCommander/releases/tag/v1.0.3) 81 | Fixed app crashing when running on VPS and temperature sensors are not available. 82 | 83 | Fixed app not able to call Python script on systems where `~` in file path is not allowed. 84 | 85 | ### [v20221220.143412](https://github.com/MorganMLGman/ServerCommander/releases/tag/v20221220.143412) 86 | Implemented changing SSH port and fixed some issues with too low contrast. 87 | 88 | ### [v20221213.202445](https://github.com/MorganMLGman/ServerCommander/releases/tag/v20221213.202445) 89 | This is out first _**"production"**_ ready release. Feel free to check it out and submit issues. 90 | 91 | ## Privacy Policy 92 | 93 | **Our app is not collecting any user data.** 94 | 95 | We have never collected, do not collect, nor will we collect any user data, usage statistics or any other data about app users, their system or other installed applications. 96 | 97 | If you're still not convinced that our app is in a **completely anonymous** state, you're in the perfect place, the app code is **fully open source** and is available at https://github.com/MorganMLGman/ServerCommander 98 | 99 | ## Thanks to 100 | 101 | - [Docker authors](https://github.com/docker) 102 | - [YunoHost authors](https://github.com/YunoHost) 103 | - [Cedrica from DCU](https://dribbble.com/shots/3896634-Profile-Screens) for dashboard idea and [Chirag Kachhadiya](https://www.youtube.com/watch?v=ZjAxAw0kmrY) for showing how to make it real 104 | - Intuit developers for providing [sdp](https://github.com/intuit/sdp) library 105 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | |------------------| ------------------ | 7 | | v1.1.0 | ✅ | 8 | | v1.0.3 | ❌ | 9 | | v20221220.143412 | ❌ | 10 | | v20221213.202445 | ❌ | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | If you have any problem you can report that on [ServerCommander issues](https://github.com/MorganMLGman/ServerCommander/issues) 15 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.doyouhost.servercommander' 8 | compileSdk 33 9 | 10 | defaultConfig { 11 | applicationId "com.doyouhost.servercommander" 12 | minSdk 30 13 | targetSdk 33 14 | versionCode 6 15 | versionName "1.1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | buildFeatures { 34 | viewBinding true 35 | } 36 | } 37 | 38 | task printVersionName { 39 | println "v" + android.defaultConfig.versionName 40 | } 41 | 42 | dependencies { 43 | 44 | implementation 'androidx.core:core-ktx:1.9.0' 45 | implementation 'androidx.appcompat:appcompat:1.5.1' 46 | implementation 'com.google.android.material:material:1.7.0' 47 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 48 | implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' 49 | implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' 50 | 51 | implementation("com.github.mwiede:jsch:0.2.4") 52 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1") 53 | 54 | testImplementation 'junit:junit:4.13.2' 55 | androidTestImplementation 'androidx.test.ext:junit:1.1.4' 56 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' 57 | 58 | implementation 'com.intuit.sdp:sdp-android:1.0.6' 59 | implementation 'com.intuit.ssp:ssp-android:1.0.6' 60 | implementation 'androidx.cardview:cardview:1.0.0' 61 | implementation "androidx.recyclerview:recyclerview:1.2.1" 62 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" 63 | implementation("com.squareup.okhttp3:okhttp:4.10.0") 64 | } 65 | 66 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/doyouhost/servercommander/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.doyouhost.servercommander 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.example.servercommander", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 21 | 25 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/doyouhost/servercommander/Container.kt: -------------------------------------------------------------------------------- 1 | package com.doyouhost.servercommander 2 | 3 | class Container(val name: String, val isRunning: Boolean, val runtime: String) { 4 | 5 | companion object { 6 | 7 | fun createContainersList(numOfContainers: Int): ArrayList { 8 | val containers = ArrayList() 9 | for (i in 1..numOfContainers) { 10 | containers.add(Container("Container ", false, "Pull to refresh")) 11 | } 12 | return containers 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/doyouhost/servercommander/ContainersAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.doyouhost.servercommander 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import androidx.appcompat.widget.AppCompatImageButton 8 | import androidx.recyclerview.widget.DiffUtil 9 | import androidx.recyclerview.widget.RecyclerView 10 | 11 | class ContainersAdapter(var mContainers: List, var mListener: OnViewClickListener) : RecyclerView.Adapter() { 12 | 13 | inner class ViewHolder(itemView: View, mListener: OnViewClickListener) : RecyclerView.ViewHolder(itemView) { 14 | 15 | val dockerAppName: TextView = itemView.findViewById(R.id.dockerAppName) 16 | val dockerAppStatus: TextView = itemView.findViewById(R.id.dockerAppStatus) 17 | val dockerAppRuntime: TextView = itemView.findViewById(R.id.dockerAppRuntime) 18 | val buttonContainerUp: AppCompatImageButton = itemView.findViewById(R.id.buttonContainerStart) 19 | val buttonContainerDown: AppCompatImageButton = itemView.findViewById(R.id.buttonContainerDown) 20 | val buttonContainerRestart: AppCompatImageButton = itemView.findViewById(R.id.buttonContainerRestart) 21 | 22 | init { 23 | 24 | } 25 | } 26 | 27 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 28 | val context = parent.context 29 | val inflater = LayoutInflater.from(context) 30 | val contactView = inflater.inflate(R.layout.docker_app_item, parent, false) 31 | return ViewHolder(contactView, mListener) 32 | } 33 | 34 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 35 | val container = mContainers[position] 36 | 37 | val dockerAppName = holder.dockerAppName 38 | val dockerAppStatus = holder.dockerAppStatus 39 | val dockerAppRuntime = holder.dockerAppRuntime 40 | 41 | dockerAppName.text = container.name 42 | 43 | if (container.isRunning) dockerAppStatus.text = "RUNNING" 44 | else dockerAppStatus.text = "STOPPED" 45 | 46 | dockerAppRuntime.text = container.runtime 47 | 48 | holder.itemView.setOnClickListener{ 49 | mListener.onRowClickListener(holder.itemView, container) 50 | } 51 | 52 | holder.buttonContainerUp.setOnClickListener{ 53 | mListener.onButtonStartClickListener(holder.buttonContainerUp, container) 54 | } 55 | 56 | holder.buttonContainerDown.setOnClickListener{ 57 | mListener.onButtonStopClickListener(holder.buttonContainerDown, container) 58 | } 59 | 60 | holder.buttonContainerRestart.setOnClickListener { 61 | mListener.onButtonRestartClickListener(holder.buttonContainerRestart, container) 62 | } 63 | 64 | } 65 | 66 | override fun getItemCount(): Int { 67 | return mContainers.size 68 | } 69 | 70 | fun updateList(newContainers: List){ 71 | val diffCallback = ContainersDiffCallback(this.mContainers, newContainers) 72 | val diffResult = DiffUtil.calculateDiff(diffCallback) 73 | diffResult.dispatchUpdatesTo(this) 74 | this.mContainers = newContainers 75 | } 76 | 77 | interface OnViewClickListener{ 78 | fun onRowClickListener(view: View, container: Container) 79 | fun onButtonStartClickListener(button: AppCompatImageButton, container: Container) 80 | fun onButtonStopClickListener(button: AppCompatImageButton, container: Container) 81 | fun onButtonRestartClickListener(button: AppCompatImageButton, container: Container) 82 | } 83 | } -------------------------------------------------------------------------------- /app/src/main/java/com/doyouhost/servercommander/ContainersDiffCallback.kt: -------------------------------------------------------------------------------- 1 | package com.doyouhost.servercommander 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | open class ContainersDiffCallback( 6 | private val oldList: List, 7 | private val newList: List 8 | ): DiffUtil.Callback() { 9 | override fun getOldListSize(): Int { 10 | return oldList.size 11 | } 12 | 13 | override fun getNewListSize(): Int { 14 | return newList.size 15 | } 16 | 17 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 18 | return oldList[oldItemPosition].name == newList[newItemPosition].name 19 | } 20 | 21 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 22 | val oldContainer = oldList[oldItemPosition] 23 | val newContainer = newList[newItemPosition] 24 | 25 | return (oldContainer.name == newContainer.name) and 26 | (oldContainer.runtime == newContainer.runtime) and 27 | (oldContainer.isRunning == newContainer.isRunning) 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/doyouhost/servercommander/DockerViewPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.doyouhost.servercommander 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentActivity 5 | import androidx.viewpager2.adapter.FragmentStateAdapter 6 | import com.doyouhost.servercommander.fragments.* 7 | 8 | class DockerViewPagerAdapter(fragmentActivity: FragmentActivity) : 9 | FragmentStateAdapter(fragmentActivity) { 10 | override fun getItemCount(): Int { 11 | return 5 12 | } 13 | 14 | override fun createFragment(position: Int): Fragment { 15 | return when(position){ 16 | 0 -> HomeFragment() 17 | 1 -> SystemFragment() 18 | 2 -> DockerFragment() 19 | 3 -> TerminalFragment() 20 | 4 -> SettingsFragment() 21 | else -> HomeFragment() 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/doyouhost/servercommander/LoginActivity.kt: -------------------------------------------------------------------------------- 1 | package com.doyouhost.servercommander 2 | 3 | import android.app.Activity 4 | import android.content.ClipData 5 | import android.content.ClipboardManager 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.os.Bundle 9 | import android.widget.Button 10 | import android.widget.EditText 11 | import android.widget.RadioButton 12 | import android.widget.Toast 13 | import androidx.appcompat.app.AppCompatActivity 14 | import java.io.File 15 | 16 | class LoginActivity : AppCompatActivity() { 17 | 18 | private var backPressed: Boolean = false 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | setContentView(R.layout.activity_login) 23 | 24 | val sharedPref = getSharedPreferences( 25 | "ServerCommander", Context.MODE_PRIVATE 26 | ) 27 | 28 | val serverUrl = findViewById(R.id.serverUrl) 29 | val username = findViewById(R.id.username) 30 | val sshPort = findViewById(R.id.sshPort) 31 | val pubkey = findViewById(R.id.pubkey) 32 | val radioYunohost = findViewById(R.id.radioYH) 33 | // val radioDocker = findViewById(R.id.radioDocker) 34 | val generateButton = findViewById