Commit 880a6d56 authored by jonasled's avatar jonasled

Merge remote-tracking branch 'origin/develop'

parents 765f783c 5ddb1e65
......@@ -10,7 +10,7 @@ android {
minSdkVersion 23
targetSdkVersion 29
versionCode Integer.valueOf(System.env.VERSION_CODE ?: 10)
versionName "1.10.0-${System.env.VERSION_SHA}"
versionName "1.11.0-${System.env.VERSION_SHA}"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
......@@ -52,7 +52,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'com.google.android.material:material:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation group: 'com.google.guava', name: 'guava', version: '29.0-android'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.jjoe64:graphview:4.2.2'
......@@ -61,6 +61,8 @@ dependencies {
implementation 'de.taimos:totp:1.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.navigation:navigation-fragment:2.3.0'
implementation 'androidx.navigation:navigation-ui:2.3.0'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
......
......@@ -15,7 +15,11 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:label,android:theme">
<activity android:name=".debugActivity"></activity>
<activity
android:name=".backupCreateActivity"
android:theme="@style/AppTheme.NoActionBar"></activity>
<activity android:name=".backupActivity" />
<activity android:name=".debugActivity" />
<activity
android:name=".StorageControlActivity"
android:label="@string/title_activity_server_control"
......
......@@ -61,7 +61,9 @@ public class ServerControlActivity extends AppCompatActivity {
Button startButton;
Button shutdownButton;
Button stopButton;
Button resetButton;
Button notesButton;
Button backupButton;
TextView nameText;
TextView nodeText;
TextView idText;
......@@ -122,6 +124,8 @@ public class ServerControlActivity extends AppCompatActivity {
shutdownButton = findViewById(R.id.buttonShutdown);
stopButton = findViewById(R.id.buttonStop);
notesButton = findViewById(R.id.buttonNotes);
resetButton = findViewById(R.id.buttonReset);
backupButton = findViewById(R.id.buttonBackup);
nameText = findViewById(R.id.labelName);
nodeText = findViewById(R.id.labelNode);
idText = findViewById(R.id.labelID);
......@@ -164,6 +168,7 @@ public class ServerControlActivity extends AppCompatActivity {
setTitle(node + " - " + id);
if(type.equals("qemu")) swapTableRow.setVisibility(View.GONE);
if(type.equals("lxc")) resetButton.setVisibility(View.GONE);
graphSpinnerValues = ArrayAdapter.createFromResource(this, R.array.spinnerGraph, android.R.layout.simple_spinner_dropdown_item);
graphSpinner.setAdapter(graphSpinnerValues);
......@@ -212,9 +217,22 @@ public class ServerControlActivity extends AppCompatActivity {
startButton.setOnClickListener((View v) -> new startStopAction().execute("start"));
shutdownButton.setOnClickListener((View v) -> new startStopAction().execute("shutdown"));
stopButton.setOnClickListener((View v) -> new startStopAction().execute("stop"));
resetButton.setOnClickListener((View v) -> new startStopAction().execute("reset"));
notesButton.setOnClickListener((View v) -> new getNotes().execute());
backupButton.setOnClickListener((View v) -> {
Intent intent = new Intent(getApplicationContext(), de.jonasled.proxdroid.backupActivity.class);
intent.putExtra("ticket", ticket);
intent.putExtra("id", id);
intent.putExtra("node", node);
intent.putExtra("url", urlString);
intent.putExtra("type", type);
intent.putExtra("name", name);
intent.putExtra("CSRFPreventionToken", CSRFPreventionToken);
startActivity(intent);
});
ramGraph.getGridLabelRenderer().setLabelFormatter(new DefaultLabelFormatter() {
@Override
public String formatLabel(double value, boolean isValueX) {
......
package de.jonasled.proxdroid;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.common.primitives.Ints;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import javax.net.ssl.HttpsURLConnection;
public class backupActivity extends AppCompatActivity {
String ticket;
String id;
String node;
String urlString;
String type;
String CSRFPreventionToken;
String name;
ArrayList<String> backupStorages = new ArrayList<>();
ArrayList<String> backups = new ArrayList<>();
de.jonasled.proxdroid.ClusterListAdapter adapter;
ListView backupListView;
FloatingActionButton fab;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(startActivity.theme == 3) setTheme(R.style.Theme_App_black);
setContentView(R.layout.activity_backup);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ticket = getIntent().getStringExtra("ticket");
id = getIntent().getStringExtra("id");
node = getIntent().getStringExtra("node");
urlString = getIntent().getStringExtra("url");
type = getIntent().getStringExtra("type");
CSRFPreventionToken = getIntent().getStringExtra("CSRFPreventionToken");
name = getIntent().getStringExtra("name");
backupListView = findViewById(R.id.backupList);
fab = findViewById(R.id.backupFAB);
setTitle(name + " - " + getResources().getString(R.string.backup));
fab.setOnClickListener((View v) -> {
Intent intent = new Intent(getApplicationContext(), de.jonasled.proxdroid.backupCreateActivity.class);
intent.putExtra("ticket", ticket);
intent.putExtra("id", id);
intent.putExtra("node", node);
intent.putExtra("url", urlString);
intent.putExtra("type", type);
intent.putExtra("name", name);
intent.putExtra("CSRFPreventionToken", CSRFPreventionToken);
startActivity(intent);
});
new getConfig().execute();
}
private class getConfig extends AsyncTask<String, String, String> {
private ProgressDialog pd;
@Override
protected void onPreExecute() {
HelperFunctions.buildKeystore();
pd = ProgressDialog.show(backupActivity.this, "", getResources().getString(R.string.loading), true,//open a loading dialog
false);
backupStorages = new ArrayList<>();
}
@Override
protected String doInBackground(String... strings) {
try {
URL url = new URL( urlString + "api2/json/nodes/" + node + "/storage?format=1&content=backup");
HttpsURLConnection conn2 = (HttpsURLConnection) url.openConnection();
conn2.setSSLSocketFactory(HelperFunctions.context.getSocketFactory());
conn2.setHostnameVerifier(HelperFunctions.hostnameVerifier);
conn2.setRequestProperty("Cookie", "PVEAuthCookie=" + ticket);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn2.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
// Append server response in string
sb.append(line + "\n");
}
JSONArray resourcesJSON = (JSONArray) (new JSONObject(sb.toString())).get("data");
for(int i=0; i < resourcesJSON.length(); i++){
JSONObject temp = resourcesJSON.getJSONObject(i);
try {
backupStorages.add(temp.getString("storage"));
} catch (Exception e) {}
}
for (String storage:backupStorages) {
url = new URL( urlString + "api2/json/nodes/" + node +"/storage/" +storage + "/content?content=backup");
conn2 = (HttpsURLConnection) url.openConnection();
conn2.setSSLSocketFactory(HelperFunctions.context.getSocketFactory());
conn2.setHostnameVerifier(HelperFunctions.hostnameVerifier);
conn2.setRequestProperty("Cookie", "PVEAuthCookie=" + ticket);
reader = new BufferedReader(new InputStreamReader(conn2.getInputStream()));
sb = new StringBuilder();
while ((line = reader.readLine()) != null) {
// Append server response in string
sb.append(line + "\n");
}
resourcesJSON = (JSONArray) (new JSONObject(sb.toString())).get("data");
for(int i=0; i < resourcesJSON.length(); i++){
JSONObject temp = resourcesJSON.getJSONObject(i);
try {
if(temp.getString("vmid").equals(id)){
backups.add(storage + ": " + temp.getString("volid").split("backup/")[1]);
}
} catch (Exception e) {e.printStackTrace();}
}
}
} catch (IOException e){
e.printStackTrace();
return "failedIO";
} catch (JSONException e){
e.printStackTrace();
return "failedJSON";
}
return "";
}
@Override
protected void onPostExecute(String result) {
ArrayList sortedKeys = new ArrayList(backups);//The response from proxmox is a mess, no System or whatever, so we sort the array after the key
Collections.sort(sortedKeys);
ArrayList<String> label = new ArrayList<>();
ArrayList<Integer> icon = new ArrayList<>();
for (Object key : sortedKeys.toArray()) {//parse the sorted key list
label.add(key.toString());
icon.add(R.drawable.backup);
int[] iconsInt = Ints.toArray(icon);
String[] labelArray = label.toArray(new String[0]);//convert the List to array and show the listView
adapter = new de.jonasled.proxdroid.ClusterListAdapter(backupActivity.this, labelArray, iconsInt);
backupListView.setAdapter(adapter);
}
pd.dismiss();
}
}
}
\ No newline at end of file
package de.jonasled.proxdroid;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.primitives.Ints;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.view.View;
import android.widget.Adapter;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import javax.net.ssl.HttpsURLConnection;
public class backupCreateActivity extends AppCompatActivity {
String ticket;
String id;
String node;
String urlString;
String type;
String CSRFPreventionToken;
String name;
FloatingActionButton fab;
Spinner storageSpinner;
Spinner modeSpinner;
Spinner compressionSpinner;
EditText emailInput;
ArrayList<String> backupStorages = new ArrayList<>();
ArrayAdapter<CharSequence> backupModeAdapter;
ArrayAdapter<CharSequence> backupCompressionAdapter;
ArrayAdapter<String> backupStoragesAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(startActivity.theme == 3) setTheme(R.style.Theme_App_black);
setContentView(R.layout.activity_backup_create);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ticket = getIntent().getStringExtra("ticket");
id = getIntent().getStringExtra("id");
node = getIntent().getStringExtra("node");
urlString = getIntent().getStringExtra("url");
type = getIntent().getStringExtra("type");
CSRFPreventionToken = getIntent().getStringExtra("CSRFPreventionToken");
name = getIntent().getStringExtra("name");
fab = findViewById(R.id.fab);
storageSpinner = findViewById(R.id.spinnerStorage);
modeSpinner = findViewById(R.id.spinnerMode);
compressionSpinner = findViewById(R.id.spinnerCompression);
emailInput = findViewById(R.id.inputEmail);
setTitle(name + " - " + getResources().getString(R.string.backup));
fab.setOnClickListener(view -> {
new createBackupTask().execute();
});
new getConfig().execute();
}
private class createBackupTask extends AsyncTask<String, String, String> {
private ProgressDialog pd;
private String storage;
private String mode;
private String mailto;
private String compress;
@Override
protected void onPreExecute() {
HelperFunctions.buildKeystore();
pd = ProgressDialog.show(backupCreateActivity.this, "", getResources().getString(R.string.loading), true,//open a loading dialog
false);
storage = backupStorages.get(storageSpinner.getSelectedItemPosition());
switch (modeSpinner.getSelectedItemPosition()){
case 0:
mode = "snapshot";
break;
case 1:
mode = "suspend";
break;
case 2:
mode = "stop";
break;
}
switch(compressionSpinner.getSelectedItemPosition()){
case 0:
compress = "0";
break;
case 1:
compress = "gzip";
break;
case 2:
compress = "lzo";
break;
case 3:
compress = "zstd";
break;
}
mailto = emailInput.getText().toString();
}
@Override
protected String doInBackground(String... strings) {
try {
URL url = new URL( urlString + "api2/extjs/nodes/" + node + "/vzdump");
HttpsURLConnection conn2 = (HttpsURLConnection) url.openConnection();
conn2.setSSLSocketFactory(HelperFunctions.context.getSocketFactory());
conn2.setHostnameVerifier(HelperFunctions.hostnameVerifier);
conn2.setRequestProperty("Cookie", "PVEAuthCookie=" + ticket);
conn2.setRequestProperty("CSRFPreventionToken", CSRFPreventionToken);
conn2.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn2.getOutputStream());//write the POST data
wr.write("storage=" + storage + "&vmid=" + id + "&mode=" + mode + "&remove=0&mailto=" + mailto + "&compress=" + compress);
wr.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn2.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
// Append server response in string
sb.append(line + "\n");
}
if(new JSONObject(sb.toString()).getInt("success") == 1){
return "OK";
}
} catch (IOException | JSONException e){
e.printStackTrace();
return "failedIO";
}
return "";
}
@Override
protected void onPostExecute(String result) {
pd.dismiss();
if(result == "OK"){
Toast.makeText(getApplicationContext(),R.string.backupStarted,Toast.LENGTH_SHORT).show();
finish();
}
}
}
private class getConfig extends AsyncTask<String, String, String> {
private ProgressDialog pd;
@Override
protected void onPreExecute() {
HelperFunctions.buildKeystore();
pd = ProgressDialog.show(backupCreateActivity.this, "", getResources().getString(R.string.loading), true,//open a loading dialog
false);
backupStorages = new ArrayList<>();
}
@Override
protected String doInBackground(String... strings) {
try {
URL url = new URL( urlString + "api2/json/nodes/" + node + "/storage?format=1&content=backup");
HttpsURLConnection conn2 = (HttpsURLConnection) url.openConnection();
conn2.setSSLSocketFactory(HelperFunctions.context.getSocketFactory());
conn2.setHostnameVerifier(HelperFunctions.hostnameVerifier);
conn2.setRequestProperty("Cookie", "PVEAuthCookie=" + ticket);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn2.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
// Append server response in string
sb.append(line + "\n");
}
JSONArray resourcesJSON = (JSONArray) (new JSONObject(sb.toString())).get("data");
for(int i=0; i < resourcesJSON.length(); i++){
JSONObject temp = resourcesJSON.getJSONObject(i);
try {
backupStorages.add(temp.getString("storage"));
} catch (Exception e) {e.printStackTrace();}
}
} catch (IOException e){
e.printStackTrace();
return "failedIO";
} catch (JSONException e){
e.printStackTrace();
return "failedJSON";
}
return "";
}
@Override
protected void onPostExecute(String result) {
backupStoragesAdapter = new ArrayAdapter<String>(backupCreateActivity.this, R.layout.support_simple_spinner_dropdown_item, backupStorages);
backupModeAdapter = ArrayAdapter.createFromResource(backupCreateActivity.this, R.array.backupMode, R.layout.support_simple_spinner_dropdown_item);
backupCompressionAdapter = ArrayAdapter.createFromResource(backupCreateActivity.this, R.array.backupCompressionList, R.layout.support_simple_spinner_dropdown_item);
storageSpinner.setAdapter(backupStoragesAdapter);
modeSpinner.setAdapter(backupModeAdapter);
compressionSpinner.setAdapter(backupCompressionAdapter);
pd.dismiss();
}
}
}
\ No newline at end of file
Neue Funktionen:
* Die Einheit der Graphen wird nun automatisch angepasst.
* RAM / Festplattenverwendung Diagramme zeigen reale Werte statt Prozent an.
Fehlerbehebung:
* Schwarzes Design funktioniert nun auch in der VM / Container Steuerung.
* Durchschnitt / Maximum Spinner wurde in der Speicher Ansicht entfernt, da nicht unterstützt von Proxmox
* Diagramme sind teilweise abgeschnitten.
\ No newline at end of file
* Backups können nun in der App erstellt werden
\ No newline at end of file
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/buttonIconColor" android:pathData="M7,2v11h3v9l7,-12h-4l4,-8z"/>
</vector>
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@color/buttonIconColor" android:pathData="M14,12c0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2 0.9,2 2,2 2,-0.9 2,-2zM12,3c-4.97,0 -9,4.03 -9,9L0,12l4,4 4,-4L5,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.51,0 -2.91,-0.49 -4.06,-1.3l-1.42,1.44C8.04,20.3 9.94,21 12,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".backupActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/backupFAB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/backupList"
app:srcCompat="@android:drawable/ic_menu_save"
android:focusable="true" />
<ListView
android:id="@+id/backupList"
android:layout_width="409dp"
android:layout_height="729dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".backupCreateActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_backup_create" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@android:drawable/ic_menu_save" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file