Add Send Logs command. Closes #6710
Signed-off-by: Pedro Pombeiro <pombeirp@users.noreply.github.com>
This commit is contained in:
parent
cdf8dc043a
commit
9d21cf143e
|
@ -91,6 +91,17 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
<service android:name="io.invertase.firebase.messaging.RNFirebaseBackgroundMessagingService" />
|
<service android:name="io.invertase.firebase.messaging.RNFirebaseBackgroundMessagingService" />
|
||||||
|
<provider
|
||||||
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.provider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
tools:replace="android:authorities">
|
||||||
|
<meta-data
|
||||||
|
tools:replace="android:resource"
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_provider_paths" />
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -30,7 +30,6 @@ import im.status.ethereum.module.StatusThreadPoolExecutor;
|
||||||
public class MainActivity extends ReactActivity
|
public class MainActivity extends ReactActivity
|
||||||
implements ActivityCompat.OnRequestPermissionsResultCallback{
|
implements ActivityCompat.OnRequestPermissionsResultCallback{
|
||||||
|
|
||||||
private static final int PERMISSION_REQUEST_CAMERA = 0;
|
|
||||||
|
|
||||||
@Nullable private PermissionListener mPermissionListener;
|
@Nullable private PermissionListener mPermissionListener;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<cache-path name="logs" path="logs" />
|
||||||
|
<!-- NOTE: Ensure that the paths here take into account those of npm dependencies as well, e.g. node_modules/react-native-image-crop-picker/android/src/main/res/xml/provider_paths.xml -->
|
||||||
|
<external-path name="external_files" path="."/>
|
||||||
|
</paths>
|
|
@ -9,4 +9,4 @@ endif(APPLE)
|
||||||
if(SCRIPT AND EXISTS ${CMAKE_SOURCE_DIR}/CMakeModules/${SCRIPT}.cmake.in)
|
if(SCRIPT AND EXISTS ${CMAKE_SOURCE_DIR}/CMakeModules/${SCRIPT}.cmake.in)
|
||||||
configure_file(${CMAKE_SOURCE_DIR}/CMakeModules/${SCRIPT}.cmake.in ${SCRIPT}.cmake @ONLY)
|
configure_file(${CMAKE_SOURCE_DIR}/CMakeModules/${SCRIPT}.cmake.in ${SCRIPT}.cmake @ONLY)
|
||||||
include(${CMAKE_CURRENT_BINARY_DIR}/${SCRIPT}.cmake)
|
include(${CMAKE_CURRENT_BINARY_DIR}/${SCRIPT}.cmake)
|
||||||
endif(SCRIPT)
|
endif()
|
||||||
|
|
|
@ -14,6 +14,10 @@ macro(import_qt_modules)
|
||||||
|
|
||||||
#message("${mod}_INCLUDE_DIRS: include_directories(${${mod}_INCLUDE_DIRS})")
|
#message("${mod}_INCLUDE_DIRS: include_directories(${${mod}_INCLUDE_DIRS})")
|
||||||
include_directories(${${mod}_INCLUDE_DIRS})
|
include_directories(${${mod}_INCLUDE_DIRS})
|
||||||
|
if (${COMP} STREQUAL "Quick")
|
||||||
|
# We need to include the private headers for QZipWriter. If in the future we can't use that class anymore, we can always resort to the QuaZIP OSS library
|
||||||
|
include_directories(${${mod}_PRIVATE_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
list(APPEND QT5_LIBRARIES ${${mod}_LIBRARIES})
|
list(APPEND QT5_LIBRARIES ${${mod}_LIBRARIES})
|
||||||
list(APPEND QT5_CFLAGS ${${mod}_EXECUTABLE_COMPILE_FLAGS})
|
list(APPEND QT5_CFLAGS ${${mod}_EXECUTABLE_COMPILE_FLAGS})
|
||||||
|
|
|
@ -14,7 +14,6 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11")
|
||||||
set(APP_NAME "reportApp")
|
set(APP_NAME "reportApp")
|
||||||
|
|
||||||
set(MAIN_CPP_SOURCE reportpublisher.cpp
|
set(MAIN_CPP_SOURCE reportpublisher.cpp
|
||||||
reportpublisher.cpp
|
|
||||||
main.cpp)
|
main.cpp)
|
||||||
|
|
||||||
add_executable(
|
add_executable(
|
||||||
|
@ -23,7 +22,7 @@ add_executable(
|
||||||
main.qrc
|
main.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
set(USED_QT_MODULES Core Qml Quick WebSockets Svg)
|
set(USED_QT_MODULES Core Qml Quick WebSockets Widgets Svg)
|
||||||
|
|
||||||
qt5_use_modules(${APP_NAME} ${USED_QT_MODULES})
|
qt5_use_modules(${APP_NAME} ${USED_QT_MODULES})
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
package im.status.ethereum.module;
|
package im.status.ethereum.module;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.DialogInterface.OnClickListener;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.*;
|
import android.os.*;
|
||||||
import android.view.WindowManager;
|
import android.support.v4.content.FileProvider ;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.view.WindowManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.webkit.CookieManager;
|
import android.webkit.CookieManager;
|
||||||
import android.webkit.CookieSyncManager;
|
import android.webkit.CookieSyncManager;
|
||||||
|
@ -16,17 +21,25 @@ import com.facebook.react.bridge.*;
|
||||||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||||
import com.github.status_im.status_go.Statusgo;
|
import com.github.status_im.status_go.Statusgo;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Stack;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
@ -36,11 +49,12 @@ import javax.annotation.Nullable;
|
||||||
class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventListener, ConnectorHandler {
|
class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventListener, ConnectorHandler {
|
||||||
|
|
||||||
private static final String TAG = "StatusModule";
|
private static final String TAG = "StatusModule";
|
||||||
|
private static final String logsZipFileName = "Status-debug-logs.zip";
|
||||||
|
private static final String gethLogFileName = "geth.log";
|
||||||
|
private static final String statusLogFileName = "Status.log";
|
||||||
|
|
||||||
private final static int TESTNET_NETWORK_ID = 3;
|
private final static int TESTNET_NETWORK_ID = 3;
|
||||||
|
|
||||||
private HashMap<String, Callback> callbacks = new HashMap<>();
|
|
||||||
|
|
||||||
private static StatusModule module;
|
private static StatusModule module;
|
||||||
private ServiceConnector status = null;
|
private ServiceConnector status = null;
|
||||||
private ExecutorService executor = null;
|
private ExecutorService executor = null;
|
||||||
|
@ -114,10 +128,15 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("gethEvent", params);
|
this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("gethEvent", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String prepareLogsFile() {
|
private File getLogsFile() {
|
||||||
String gethLogFileName = "geth.log";
|
final File pubDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
File pubDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
final File logFile = new File(pubDirectory, gethLogFileName);
|
||||||
File logFile = new File(pubDirectory, gethLogFileName);
|
|
||||||
|
return logFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String prepareLogsFile(final Context context) {
|
||||||
|
final File logFile = getLogsFile();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logFile.setReadable(true);
|
logFile.setReadable(true);
|
||||||
|
@ -149,7 +168,8 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
// retrieve parameters from app config, that will be applied onto the Go-side config later on
|
// retrieve parameters from app config, that will be applied onto the Go-side config later on
|
||||||
final String absDataDirPath = pathCombine(absRootDirPath, jsonConfig.getString("DataDir"));
|
final String absDataDirPath = pathCombine(absRootDirPath, jsonConfig.getString("DataDir"));
|
||||||
final Boolean logEnabled = jsonConfig.getBoolean("LogEnabled");
|
final Boolean logEnabled = jsonConfig.getBoolean("LogEnabled");
|
||||||
final String gethLogFilePath = logEnabled ? prepareLogsFile() : null;
|
final Context context = this.getReactApplicationContext();
|
||||||
|
final String gethLogFilePath = logEnabled ? prepareLogsFile(context) : null;
|
||||||
|
|
||||||
jsonConfig.put("DataDir", absDataDirPath);
|
jsonConfig.put("DataDir", absDataDirPath);
|
||||||
jsonConfig.put("KeyStoreDir", absKeystoreDirPath);
|
jsonConfig.put("KeyStoreDir", absKeystoreDirPath);
|
||||||
|
@ -456,6 +476,156 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
StatusThreadPoolExecutor.getInstance().execute(r);
|
StatusThreadPoolExecutor.getInstance().execute(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean zip(File[] _files, File zipFile, Stack<String> errorList) {
|
||||||
|
final int BUFFER = 0x8000;
|
||||||
|
|
||||||
|
try {
|
||||||
|
BufferedInputStream origin = null;
|
||||||
|
FileOutputStream dest = new FileOutputStream(zipFile);
|
||||||
|
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest));
|
||||||
|
byte data[] = new byte[BUFFER];
|
||||||
|
|
||||||
|
for (int i = 0; i < _files.length; i++) {
|
||||||
|
final File file = _files[i];
|
||||||
|
if (file == null || !file.exists()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v("Compress", "Adding: " + file.getAbsolutePath());
|
||||||
|
try {
|
||||||
|
FileInputStream fi = new FileInputStream(file);
|
||||||
|
origin = new BufferedInputStream(fi, BUFFER);
|
||||||
|
|
||||||
|
ZipEntry entry = new ZipEntry(file.getName());
|
||||||
|
out.putNextEntry(entry);
|
||||||
|
int count;
|
||||||
|
|
||||||
|
while ((count = origin.read(data, 0, BUFFER)) != -1) {
|
||||||
|
out.write(data, 0, count);
|
||||||
|
}
|
||||||
|
origin.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, e.getMessage());
|
||||||
|
errorList.push(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dumpAdbLogsTo(final FileOutputStream statusLogStream) throws IOException {
|
||||||
|
final String filter = "logcat -d -b main ReactNativeJS:D StatusModule:D StatusService:D StatusNativeLogs:D *:S";
|
||||||
|
final java.lang.Process p = Runtime.getRuntime().exec(filter);
|
||||||
|
final java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(p.getInputStream()));
|
||||||
|
final java.io.BufferedWriter out = new java.io.BufferedWriter(new java.io.OutputStreamWriter(statusLogStream));
|
||||||
|
String line;
|
||||||
|
while ((line = in.readLine()) != null) {
|
||||||
|
out.write(line);
|
||||||
|
out.newLine();
|
||||||
|
}
|
||||||
|
out.close();
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showErrorMessage(final String message) {
|
||||||
|
final Activity activity = getCurrentActivity();
|
||||||
|
|
||||||
|
new AlertDialog.Builder(activity)
|
||||||
|
.setTitle("Error")
|
||||||
|
.setMessage(message)
|
||||||
|
.setNegativeButton("Exit", new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(final DialogInterface dialog, final int id) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void sendLogs(final String dbJson) {
|
||||||
|
Log.d(TAG, "sendLogs");
|
||||||
|
if (!checkAvailability()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Context context = this.getReactApplicationContext();
|
||||||
|
final File logsTempDir = new File(context.getCacheDir(), "logs"); // This path needs to be in sync with android/app/src/main/res/xml/file_provider_paths.xml
|
||||||
|
logsTempDir.mkdir();
|
||||||
|
|
||||||
|
final File dbFile = new File(logsTempDir, "db.json");
|
||||||
|
try {
|
||||||
|
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream(dbFile));
|
||||||
|
outputStreamWriter.write(dbJson);
|
||||||
|
outputStreamWriter.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
Log.e(TAG, "File write failed: " + e.toString());
|
||||||
|
showErrorMessage(e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
final File zipFile = new File(logsTempDir, logsZipFileName);
|
||||||
|
final File statusLogFile = new File(logsTempDir, statusLogFileName);
|
||||||
|
final File gethLogFile = getLogsFile();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (zipFile.exists() || zipFile.createNewFile()) {
|
||||||
|
final long usableSpace = zipFile.getUsableSpace();
|
||||||
|
if (usableSpace < 20 * 1024 * 1024) {
|
||||||
|
final String message = String.format("Insufficient space available on device (%s) to write logs.\nPlease free up some space.", android.text.format.Formatter.formatShortFileSize(context, usableSpace));
|
||||||
|
Log.e(TAG, message);
|
||||||
|
showErrorMessage(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dumpAdbLogsTo(new FileOutputStream(statusLogFile));
|
||||||
|
|
||||||
|
final Stack<String> errorList = new Stack<String>();
|
||||||
|
final Boolean zipped = zip(new File[] {dbFile, gethLogFile, statusLogFile}, zipFile, errorList);
|
||||||
|
if (zipped && zipFile.exists()) {
|
||||||
|
Log.d(TAG, "Sending " + zipFile.getAbsolutePath() + " file through share intent");
|
||||||
|
|
||||||
|
final String providerName = context.getPackageName() + ".provider";
|
||||||
|
final Activity activity = getCurrentActivity();
|
||||||
|
zipFile.setReadable(true, false);
|
||||||
|
final Uri dbJsonURI = FileProvider.getUriForFile(activity, providerName, zipFile);
|
||||||
|
|
||||||
|
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
|
||||||
|
|
||||||
|
intentShareFile.setType("application/json");
|
||||||
|
intentShareFile.putExtra(Intent.EXTRA_STREAM, dbJsonURI);
|
||||||
|
|
||||||
|
SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
dateFormatGmt.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
|
||||||
|
intentShareFile.putExtra(Intent.EXTRA_SUBJECT, "Status.im logs");
|
||||||
|
intentShareFile.putExtra(Intent.EXTRA_TEXT,
|
||||||
|
String.format("Logs from %s GMT\n\nThese logs have been generated automatically by the user's request for debugging purposes.\n\n%s",
|
||||||
|
dateFormatGmt.format(new java.util.Date()),
|
||||||
|
errorList));
|
||||||
|
|
||||||
|
activity.startActivity(Intent.createChooser(intentShareFile, "Share Debug Logs"));
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "File " + zipFile.getAbsolutePath() + " does not exist");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, e.getMessage());
|
||||||
|
showErrorMessage(e.getLocalizedMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
dbFile.delete();
|
||||||
|
statusLogFile.delete();
|
||||||
|
zipFile.deleteOnExit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void addPeer(final String enode, final Callback callback) {
|
public void addPeer(final String enode, final Callback callback) {
|
||||||
Log.d(TAG, "addPeer");
|
Log.d(TAG, "addPeer");
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
#include "eventdispatcher.h"
|
#include "eventdispatcher.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QStorageInfo>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QVariantMap>
|
#include <QVariantMap>
|
||||||
|
@ -22,6 +24,9 @@
|
||||||
|
|
||||||
#include "libstatus.h"
|
#include "libstatus.h"
|
||||||
|
|
||||||
|
extern QString getDataStoragePath();
|
||||||
|
extern QString getLogFilePath();
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
struct RegisterQMLMetaType {
|
struct RegisterQMLMetaType {
|
||||||
RegisterQMLMetaType() {
|
RegisterQMLMetaType() {
|
||||||
|
@ -92,7 +97,7 @@ void RCTStatus::startNode(QString configString) {
|
||||||
if (!relativeDataDirPath.startsWith("/"))
|
if (!relativeDataDirPath.startsWith("/"))
|
||||||
relativeDataDirPath.prepend("/");
|
relativeDataDirPath.prepend("/");
|
||||||
|
|
||||||
QString rootDirPath = getRootDirPath();
|
QString rootDirPath = getDataStoragePath();
|
||||||
QDir rootDir(rootDirPath);
|
QDir rootDir(rootDirPath);
|
||||||
QString absDataDirPath = rootDirPath + relativeDataDirPath;
|
QString absDataDirPath = rootDirPath + relativeDataDirPath;
|
||||||
QDir dataDir(absDataDirPath);
|
QDir dataDir(absDataDirPath);
|
||||||
|
@ -100,10 +105,11 @@ void RCTStatus::startNode(QString configString) {
|
||||||
dataDir.mkpath(".");
|
dataDir.mkpath(".");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d_gethLogFilePath = dataDir.absoluteFilePath("geth.log");
|
||||||
configJSON["DataDir"] = absDataDirPath;
|
configJSON["DataDir"] = absDataDirPath;
|
||||||
configJSON["BackupDisabledDataDir"] = absDataDirPath;
|
configJSON["BackupDisabledDataDir"] = absDataDirPath;
|
||||||
configJSON["KeyStoreDir"] = rootDir.absoluteFilePath("keystore");
|
configJSON["KeyStoreDir"] = rootDir.absoluteFilePath("keystore");
|
||||||
configJSON["LogFile"] = dataDir.absoluteFilePath("geth.log");
|
configJSON["LogFile"] = d_gethLogFilePath;
|
||||||
|
|
||||||
const QJsonDocument& updatedJsonDoc = QJsonDocument::fromVariant(configJSON);
|
const QJsonDocument& updatedJsonDoc = QJsonDocument::fromVariant(configJSON);
|
||||||
qCInfo(RCTSTATUS) << "::startNode updated configString: " << updatedJsonDoc.toVariant().toMap();
|
qCInfo(RCTSTATUS) << "::startNode updated configString: " << updatedJsonDoc.toVariant().toMap();
|
||||||
|
@ -141,6 +147,97 @@ void RCTStatus::notifyUsers(QString token, QString payloadJSON, QString tokensJS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QtGui/private/qzipwriter_p.h>
|
||||||
|
|
||||||
|
void showFileInGraphicalShell(QWidget *parent, const QFileInfo &fileInfo)
|
||||||
|
{
|
||||||
|
// Mac, Windows support folder or file.
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
const QString explorer = QStandardPaths::findExecutable(QLatin1String("explorer.exe"));
|
||||||
|
if (explorer.isEmpty()) {
|
||||||
|
QMessageBox::warning(parent,
|
||||||
|
QApplication::translate("Core::Internal",
|
||||||
|
"Launching Windows Explorer Failed"),
|
||||||
|
QApplication::translate("Core::Internal",
|
||||||
|
"Could not find explorer.exe in path to launch Windows Explorer."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QStringList param;
|
||||||
|
if (!fileInfo.isDir())
|
||||||
|
param += QLatin1String("/select,");
|
||||||
|
param += QDir::toNativeSeparators(fileInfo.canonicalFilePath());
|
||||||
|
QProcess::startDetached(explorer, param);
|
||||||
|
#elif defined(Q_OS_MAC)
|
||||||
|
QStringList scriptArgs;
|
||||||
|
scriptArgs << QLatin1String("-e")
|
||||||
|
<< QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"")
|
||||||
|
.arg(fileInfo.canonicalFilePath());
|
||||||
|
QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
|
||||||
|
scriptArgs.clear();
|
||||||
|
scriptArgs << QLatin1String("-e")
|
||||||
|
<< QLatin1String("tell application \"Finder\" to activate");
|
||||||
|
QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs);
|
||||||
|
#else
|
||||||
|
// we cannot select a file here, because no file browser really supports it...
|
||||||
|
const QString folder = fileInfo.isDir() ? fileInfo.absoluteFilePath() : fileInfo.dir().absolutePath();
|
||||||
|
QProcess browserProc;
|
||||||
|
browserProc.setProgram("xdg-open");
|
||||||
|
browserProc.setArguments(QStringList(folder));
|
||||||
|
bool success = browserProc.startDetached();
|
||||||
|
const QString error = QString::fromLocal8Bit(browserProc.readAllStandardError());
|
||||||
|
success = success && error.isEmpty();
|
||||||
|
if (!success) {
|
||||||
|
QMessageBox::warning(parent, "Launching Explorer Failed", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RCTStatus::sendLogs(QString dbJSON) {
|
||||||
|
Q_D(RCTStatus);
|
||||||
|
|
||||||
|
qCDebug(RCTSTATUS) << "::sendLogs call - logFilePath:" << getLogFilePath()
|
||||||
|
<< "d_gethLogFilePath:" << d_gethLogFilePath
|
||||||
|
<< "dbJSON:" << dbJSON;
|
||||||
|
|
||||||
|
QString tmpDirName("Status.im");
|
||||||
|
QDir tmpDir(QStandardPaths::writableLocation(QStandardPaths::TempLocation));
|
||||||
|
if (!tmpDir.mkpath(tmpDirName)) {
|
||||||
|
qCWarning(RCTSTATUS) << "::sendLogs could not create temp dir:" << tmpDirName;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that at least 20MB are available for log generation
|
||||||
|
QStorageInfo storage(tmpDir);
|
||||||
|
if (storage.bytesAvailable() < 20 * 1024 * 1024) {
|
||||||
|
QMessageBox dlg;
|
||||||
|
dlg.warning(QApplication::activeWindow(),
|
||||||
|
"Error", QString("Insufficient storage space available in %1 for log generation. Please free up some space.").arg(storage.rootPath()),
|
||||||
|
QMessageBox::Close);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile zipFile(tmpDir.absoluteFilePath(tmpDirName + QDir::separator() + "Status-debug-logs.zip"));
|
||||||
|
QZipWriter zipWriter(&zipFile);
|
||||||
|
QFile gethLogFile(d_gethLogFilePath);
|
||||||
|
QFile logFile(getLogFilePath());
|
||||||
|
zipWriter.addFile("db.json", dbJSON.toUtf8());
|
||||||
|
if (gethLogFile.exists()) {
|
||||||
|
zipWriter.addFile(QFileInfo(gethLogFile).fileName(), &gethLogFile);
|
||||||
|
}
|
||||||
|
if (logFile.exists()) {
|
||||||
|
zipWriter.addFile(QFileInfo(logFile).fileName(), &logFile);
|
||||||
|
}
|
||||||
|
zipWriter.close();
|
||||||
|
|
||||||
|
showFileInGraphicalShell(QApplication::activeWindow(), QFileInfo(zipFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void RCTStatus::addPeer(QString enode, double callbackId) {
|
void RCTStatus::addPeer(QString enode, double callbackId) {
|
||||||
Q_D(RCTStatus);
|
Q_D(RCTStatus);
|
||||||
qCDebug(RCTSTATUS) << "::addPeer call - callbackId:" << callbackId;
|
qCDebug(RCTSTATUS) << "::addPeer call - callbackId:" << callbackId;
|
||||||
|
@ -177,7 +274,7 @@ void RCTStatus::verify(QString address, QString password, double callbackId) {
|
||||||
Q_D(RCTStatus);
|
Q_D(RCTStatus);
|
||||||
qCInfo(RCTSTATUS) << "::verify call - callbackId:" << callbackId;
|
qCInfo(RCTSTATUS) << "::verify call - callbackId:" << callbackId;
|
||||||
QtConcurrent::run([&](QString address, QString password, double callbackId) {
|
QtConcurrent::run([&](QString address, QString password, double callbackId) {
|
||||||
QDir rootDir(getRootDirPath());
|
QDir rootDir(getDataStoragePath());
|
||||||
QString keystorePath = rootDir.absoluteFilePath("keystore");
|
QString keystorePath = rootDir.absoluteFilePath("keystore");
|
||||||
const char* result = VerifyAccountPassword(keystorePath.toUtf8().data(), address.toUtf8().data(), password.toUtf8().data());
|
const char* result = VerifyAccountPassword(keystorePath.toUtf8().data(), address.toUtf8().data(), password.toUtf8().data());
|
||||||
logStatusGoResult("::verify VerifyAccountPassword", result);
|
logStatusGoResult("::verify VerifyAccountPassword", result);
|
||||||
|
@ -334,17 +431,3 @@ void RCTStatus::updateMailservers(QString enodes, double callbackId) {
|
||||||
d->bridge->invokePromiseCallback(callbackId, QVariantList{result});
|
d->bridge->invokePromiseCallback(callbackId, QVariantList{result});
|
||||||
}, enodes, callbackId);
|
}, enodes, callbackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString RCTStatus::getRootDirPath() {
|
|
||||||
QString statusDataDir = qgetenv("STATUS_DATA_DIR");
|
|
||||||
QString rootDirPath;
|
|
||||||
if (!statusDataDir.isEmpty()) {
|
|
||||||
rootDirPath = statusDataDir;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rootDirPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rootDirPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ public:
|
||||||
Q_INVOKABLE void stopNode();
|
Q_INVOKABLE void stopNode();
|
||||||
Q_INVOKABLE void createAccount(QString password, double callbackId);
|
Q_INVOKABLE void createAccount(QString password, double callbackId);
|
||||||
Q_INVOKABLE void notifyUsers(QString token, QString payloadJSON, QString tokensJSON, double callbackId);
|
Q_INVOKABLE void notifyUsers(QString token, QString payloadJSON, QString tokensJSON, double callbackId);
|
||||||
|
Q_INVOKABLE void sendLogs(QString dbJSON);
|
||||||
Q_INVOKABLE void addPeer(QString enode, double callbackId);
|
Q_INVOKABLE void addPeer(QString enode, double callbackId);
|
||||||
Q_INVOKABLE void recoverAccount(QString passphrase, QString password, double callbackId);
|
Q_INVOKABLE void recoverAccount(QString passphrase, QString password, double callbackId);
|
||||||
Q_INVOKABLE void login(QString address, QString password, double callbackId);
|
Q_INVOKABLE void login(QString address, QString password, double callbackId);
|
||||||
|
@ -75,9 +76,9 @@ private Q_SLOTS:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void logStatusGoResult(const char* methodName, const char* result);
|
void logStatusGoResult(const char* methodName, const char* result);
|
||||||
QString getRootDirPath();
|
|
||||||
|
|
||||||
QScopedPointer<RCTStatusPrivate> d_ptr;
|
QScopedPointer<RCTStatusPrivate> d_ptr;
|
||||||
|
QString d_gethLogFilePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RCTSTATUS_H
|
#endif // RCTSTATUS_H
|
||||||
|
|
|
@ -196,6 +196,16 @@ RCT_EXPORT_METHOD(notifyUsers:(NSString *)message
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////
|
||||||
|
#pragma mark - SendLogs method
|
||||||
|
//////////////////////////////////////////////////////////////////// sendLogs
|
||||||
|
RCT_EXPORT_METHOD(sendLogs:(NSString *)dbJson) {
|
||||||
|
// TODO: Implement SendLogs for iOS
|
||||||
|
#if DEBUG
|
||||||
|
NSLog(@"SendLogs() method called, not implemented");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////// addPeer
|
//////////////////////////////////////////////////////////////////// addPeer
|
||||||
RCT_EXPORT_METHOD(addPeer:(NSString *)enode
|
RCT_EXPORT_METHOD(addPeer:(NSString *)enode
|
||||||
callback:(RCTResponseSenderBlock)callback) {
|
callback:(RCTResponseSenderBlock)callback) {
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
[status-im.hardwallet.core :as hardwallet]
|
[status-im.hardwallet.core :as hardwallet]
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
[status-im.init.core :as init]
|
[status-im.init.core :as init]
|
||||||
|
[status-im.utils.logging.core :as logging]
|
||||||
[status-im.log-level.core :as log-level]
|
[status-im.log-level.core :as log-level]
|
||||||
[status-im.mailserver.core :as mailserver]
|
[status-im.mailserver.core :as mailserver]
|
||||||
[status-im.network.core :as network]
|
[status-im.network.core :as network]
|
||||||
|
@ -543,6 +544,13 @@
|
||||||
(fn [cofx [_ url data modal?]]
|
(fn [cofx [_ url data modal?]]
|
||||||
(extensions.registry/install cofx url data modal?)))
|
(extensions.registry/install cofx url data modal?)))
|
||||||
|
|
||||||
|
;; logging module
|
||||||
|
|
||||||
|
(handlers/register-handler-fx
|
||||||
|
:logging.ui/send-logs-pressed
|
||||||
|
(fn [cofx _]
|
||||||
|
(logging/send-logs cofx)))
|
||||||
|
|
||||||
;; log-level module
|
;; log-level module
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
|
|
|
@ -2,17 +2,20 @@
|
||||||
(:require [re-frame.core :as re-frame]
|
(:require [re-frame.core :as re-frame]
|
||||||
[status-im.accounts.update.core :as accounts.update]
|
[status-im.accounts.update.core :as accounts.update]
|
||||||
[status-im.react-native.js-dependencies :as rn-dependencies]
|
[status-im.react-native.js-dependencies :as rn-dependencies]
|
||||||
|
[status-im.node.core :as node]
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
[status-im.utils.fx :as fx]))
|
[status-im.utils.fx :as fx]))
|
||||||
|
|
||||||
(fx/defn save-log-level
|
(fx/defn save-log-level
|
||||||
[{:keys [db now] :as cofx} log-level]
|
[{:keys [db now] :as cofx} log-level]
|
||||||
(let [settings (get-in db [:account/account :settings])]
|
(let [settings (get-in db [:account/account :settings])
|
||||||
(accounts.update/update-settings cofx
|
new-settings (if log-level
|
||||||
(if log-level
|
|
||||||
(assoc settings :log-level log-level)
|
(assoc settings :log-level log-level)
|
||||||
(dissoc settings :log-level))
|
(dissoc settings :log-level))]
|
||||||
{:success-event [:accounts.update.callback/save-settings-success]})))
|
(accounts.update/update-settings cofx
|
||||||
|
new-settings
|
||||||
|
(when (not= (node/get-log-level settings) (node/get-log-level new-settings))
|
||||||
|
{:success-event [:accounts.update.callback/save-settings-success]}))))
|
||||||
|
|
||||||
(fx/defn show-change-log-level-confirmation
|
(fx/defn show-change-log-level-confirmation
|
||||||
[{:keys [db]} {:keys [name value] :as log-level}]
|
[{:keys [db]} {:keys [name value] :as log-level}]
|
||||||
|
|
|
@ -54,6 +54,9 @@
|
||||||
(defn notify-users [m callback]
|
(defn notify-users [m callback]
|
||||||
(native-module/notify-users m callback))
|
(native-module/notify-users m callback))
|
||||||
|
|
||||||
|
(defn send-logs [dbJson]
|
||||||
|
(native-module/send-logs dbJson))
|
||||||
|
|
||||||
(defn add-peer [enode callback]
|
(defn add-peer [enode callback]
|
||||||
(native-module/add-peer enode callback))
|
(native-module/add-peer enode callback))
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,10 @@
|
||||||
(when status
|
(when status
|
||||||
(call-module #(.notifyUsers status message payload tokens on-result))))
|
(call-module #(.notifyUsers status message payload tokens on-result))))
|
||||||
|
|
||||||
|
(defn send-logs [dbJson]
|
||||||
|
(when status
|
||||||
|
(call-module #(.sendLogs status dbJson))))
|
||||||
|
|
||||||
(defn add-peer [enode on-result]
|
(defn add-peer [enode on-result]
|
||||||
(when (and @node-started status)
|
(when (and @node-started status)
|
||||||
(call-module #(.addPeer status enode on-result))))
|
(call-module #(.addPeer status enode on-result))))
|
||||||
|
|
|
@ -72,6 +72,12 @@
|
||||||
[limit nodes]
|
[limit nodes]
|
||||||
(take limit (shuffle nodes)))
|
(take limit (shuffle nodes)))
|
||||||
|
|
||||||
|
(defn get-log-level
|
||||||
|
[account-settings]
|
||||||
|
(or (:log-level account-settings)
|
||||||
|
(if utils.platform/desktop? ""
|
||||||
|
config/log-level-status-go)))
|
||||||
|
|
||||||
(defn- get-account-node-config [db address]
|
(defn- get-account-node-config [db address]
|
||||||
(let [accounts (get db :accounts/accounts)
|
(let [accounts (get db :accounts/accounts)
|
||||||
current-fleet-key (fleet/current-fleet db address)
|
current-fleet-key (fleet/current-fleet db address)
|
||||||
|
@ -85,9 +91,7 @@
|
||||||
:installation-id (get db :accounts/new-installation-id)}
|
:installation-id (get db :accounts/new-installation-id)}
|
||||||
(get accounts address))
|
(get accounts address))
|
||||||
use-custom-bootnodes (get-in settings [:bootnodes network])
|
use-custom-bootnodes (get-in settings [:bootnodes network])
|
||||||
log-level (or (:log-level settings)
|
log-level (get-log-level settings)]
|
||||||
(if utils.platform/desktop? ""
|
|
||||||
config/log-level-status-go))]
|
|
||||||
(cond-> (get-in networks [network :config])
|
(cond-> (get-in networks [network :config])
|
||||||
:always
|
:always
|
||||||
(get-base-node-config)
|
(get-base-node-config)
|
||||||
|
|
|
@ -183,6 +183,19 @@
|
||||||
:margin-bottom 8
|
:margin-bottom 8
|
||||||
:background-color colors/gray-light})
|
:background-color colors/gray-light})
|
||||||
|
|
||||||
|
(defn adv-settings-row-text [color]
|
||||||
|
{:color color
|
||||||
|
:font-size 14})
|
||||||
|
|
||||||
|
(defn adv-settings-row [active?]
|
||||||
|
{:justify-content :space-between
|
||||||
|
:flex-direction :row
|
||||||
|
:padding-horizontal 24
|
||||||
|
:height 56
|
||||||
|
:align-self :stretch
|
||||||
|
:align-items :center
|
||||||
|
:background-color (if active? colors/gray-lighter colors/white)})
|
||||||
|
|
||||||
(def pair-button
|
(def pair-button
|
||||||
{:margin-left 32})
|
{:margin-left 32})
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
[status-im.utils.utils :as utils]
|
[status-im.utils.utils :as utils]
|
||||||
[status-im.ui.components.colors :as colors]
|
[status-im.ui.components.colors :as colors]
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
|
[status-im.utils.logging.core :as logging]
|
||||||
|
[status-im.utils.platform :as platform]
|
||||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||||
[taoensso.timbre :as log]
|
[taoensso.timbre :as log]
|
||||||
[status-im.utils.gfycat.core :as gfy]
|
[status-im.utils.gfycat.core :as gfy]
|
||||||
|
@ -135,15 +137,21 @@
|
||||||
|
|
||||||
(views/defview logging-display []
|
(views/defview logging-display []
|
||||||
(views/letsubs [logging-enabled [:settings/logging-enabled]]
|
(views/letsubs [logging-enabled [:settings/logging-enabled]]
|
||||||
[react/view {:style (styles/profile-row false)}
|
[react/view
|
||||||
[react/text {:style (assoc (styles/profile-row-text colors/black)
|
[react/view {:style (styles/adv-settings-row false)}
|
||||||
|
[react/text {:style (assoc (styles/adv-settings-row-text colors/black)
|
||||||
:font-size 14)} (i18n/label :t/logging-enabled)]
|
:font-size 14)} (i18n/label :t/logging-enabled)]
|
||||||
[react/switch {:on-tint-color colors/blue
|
[react/switch {:on-tint-color colors/blue
|
||||||
:value logging-enabled
|
:value logging-enabled
|
||||||
:on-value-change #(re-frame/dispatch [:log-level.ui/logging-enabled (not logging-enabled)])}]]))
|
:on-value-change #(re-frame/dispatch [:log-level.ui/logging-enabled (not logging-enabled)])}]]
|
||||||
|
[react/view {:style (styles/adv-settings-row false)}
|
||||||
|
[react/touchable-highlight {:on-press #(re-frame/dispatch [:logging.ui/send-logs-pressed])}
|
||||||
|
[react/text {:style (styles/adv-settings-row-text colors/red)}
|
||||||
|
(i18n/label :t/send-logs)]]]]))
|
||||||
|
|
||||||
(views/defview advanced-settings []
|
(views/defview advanced-settings []
|
||||||
(views/letsubs [current-mailserver-id [:mailserver/current-id]
|
(views/letsubs [installations [:pairing/installations]
|
||||||
|
current-mailserver-id [:mailserver/current-id]
|
||||||
mailservers [:mailserver/fleet-mailservers]
|
mailservers [:mailserver/fleet-mailservers]
|
||||||
mailserver-state [:mailserver/state]
|
mailserver-state [:mailserver/state]
|
||||||
node-status [:node-status]
|
node-status [:node-status]
|
||||||
|
@ -197,21 +205,21 @@
|
||||||
[vector-icons/icon :icons/qr {:style {:tint-color colors/blue}}]]]])
|
[vector-icons/icon :icons/qr {:style {:tint-color colors/blue}}]]]])
|
||||||
|
|
||||||
(defn help-item [help-open?]
|
(defn help-item [help-open?]
|
||||||
[react/touchable-highlight {:style (styles/profile-row help-open?)
|
[react/touchable-highlight {:style (styles/adv-settings-row help-open?)
|
||||||
:on-press #(re-frame/dispatch [:navigate-to (if help-open? :home :help-center)])}
|
:on-press #(re-frame/dispatch [:navigate-to (if help-open? :home :help-center)])}
|
||||||
[react/view {:style styles/adv-settings}
|
[react/view {:style styles/adv-settings}
|
||||||
[react/text {:style (styles/profile-row-text colors/black)
|
[react/text {:style (styles/adv-settings-row-text colors/black)
|
||||||
:font (if help-open? :medium :default)}
|
:font (if help-open? :medium :default)}
|
||||||
(i18n/label :t/help-center)]
|
(i18n/label :t/help-center)]
|
||||||
[vector-icons/icon :icons/forward {:style {:tint-color colors/gray}}]]])
|
[vector-icons/icon :icons/forward {:style {:tint-color colors/gray}}]]])
|
||||||
|
|
||||||
(defn advanced-settings-item [adv-settings-open?]
|
(defn advanced-settings-item [adv-settings-open?]
|
||||||
[react/touchable-highlight {:style (styles/profile-row adv-settings-open?)
|
[react/touchable-highlight {:style (styles/adv-settings-row adv-settings-open?)
|
||||||
:on-press #(do
|
:on-press #(do
|
||||||
(re-frame/dispatch [:navigate-to (if adv-settings-open? :home :advanced-settings)])
|
(re-frame/dispatch [:navigate-to (if adv-settings-open? :home :advanced-settings)])
|
||||||
(re-frame/dispatch [:load-debug-metrics]))}
|
(re-frame/dispatch [:load-debug-metrics]))}
|
||||||
[react/view {:style styles/adv-settings}
|
[react/view {:style styles/adv-settings}
|
||||||
[react/text {:style (styles/profile-row-text colors/black)
|
[react/text {:style (styles/adv-settings-row-text colors/black)
|
||||||
:font (if adv-settings-open? :medium :default)}
|
:font (if adv-settings-open? :medium :default)}
|
||||||
(i18n/label :t/advanced-settings)]
|
(i18n/label :t/advanced-settings)]
|
||||||
[vector-icons/icon :icons/forward {:style {:tint-color colors/gray}}]]])
|
[vector-icons/icon :icons/forward {:style {:tint-color colors/gray}}]]])
|
||||||
|
|
|
@ -38,6 +38,14 @@
|
||||||
(defstyle my-profile-settings-logout
|
(defstyle my-profile-settings-logout
|
||||||
{:min-width "50%"})
|
{:min-width "50%"})
|
||||||
|
|
||||||
|
(defstyle my-profile-settings-send-logs-wrapper
|
||||||
|
{:flex-direction :row
|
||||||
|
:justify-content :space-between
|
||||||
|
:align-items :center})
|
||||||
|
|
||||||
|
(defstyle my-profile-settings-send-logs
|
||||||
|
{:min-width "50%"})
|
||||||
|
|
||||||
(def advanced-button
|
(def advanced-button
|
||||||
{:margin-top 16
|
{:margin-top 16
|
||||||
:margin-bottom 12})
|
:margin-bottom 12})
|
||||||
|
|
|
@ -170,6 +170,13 @@
|
||||||
{:label-kw :t/log-level
|
{:label-kw :t/log-level
|
||||||
:action-fn #(re-frame/dispatch [:navigate-to :log-level-settings])
|
:action-fn #(re-frame/dispatch [:navigate-to :log-level-settings])
|
||||||
:accessibility-label :log-level-settings-button}]
|
:accessibility-label :log-level-settings-button}]
|
||||||
|
(when (and dev-mode? (not platform/ios?))
|
||||||
|
[react/view styles/my-profile-settings-send-logs-wrapper
|
||||||
|
[react/view styles/my-profile-settings-send-logs
|
||||||
|
[profile.components/settings-item {:label-kw :t/send-logs
|
||||||
|
:destructive? true
|
||||||
|
:hide-arrow? true
|
||||||
|
:action-fn #(re-frame/dispatch [:logging.ui/send-logs-pressed])}]]])
|
||||||
[profile.components/settings-item-separator]
|
[profile.components/settings-item-separator]
|
||||||
[profile.components/settings-item
|
[profile.components/settings-item
|
||||||
{:label-kw :t/fleet
|
{:label-kw :t/fleet
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
(ns status-im.utils.logging.core
|
||||||
|
(:require [re-frame.core :as re-frame]
|
||||||
|
[status-im.native-module.core :as status]
|
||||||
|
[status-im.utils.fx :as fx]
|
||||||
|
[status-im.utils.types :as types]))
|
||||||
|
|
||||||
|
(fx/defn send-logs
|
||||||
|
[{:keys [db] :as cofx}]
|
||||||
|
;; TODO: Add message explaining db export
|
||||||
|
(let [db-json (types/clj->json (select-keys db [:app-state
|
||||||
|
:current-chat-id
|
||||||
|
:discover-current-dapp
|
||||||
|
:discover-search-tags
|
||||||
|
:discoveries
|
||||||
|
:initial-props
|
||||||
|
:keyboard-height
|
||||||
|
:keyboard-max-height
|
||||||
|
:navigation-stack
|
||||||
|
:network
|
||||||
|
:network-status
|
||||||
|
:peers-count
|
||||||
|
:peers-summary
|
||||||
|
:status-module-initialized?
|
||||||
|
:sync-state
|
||||||
|
:tab-bar-visible?
|
||||||
|
:view-id
|
||||||
|
:chat/cooldown-enabled?
|
||||||
|
:chat/cooldowns
|
||||||
|
:chat/last-outgoing-message-sent-at
|
||||||
|
:chat/spam-messages-frequency
|
||||||
|
:chats/loading?
|
||||||
|
:desktop/desktop
|
||||||
|
:dimensions/window
|
||||||
|
:my-profile/editing?
|
||||||
|
:node/status]))]
|
||||||
|
(status/send-logs db-json)))
|
|
@ -261,6 +261,7 @@
|
||||||
"logout": "Log out",
|
"logout": "Log out",
|
||||||
"status-not-sent-tap": "Not confirmed. Tap for options",
|
"status-not-sent-tap": "Not confirmed. Tap for options",
|
||||||
"status-not-sent-click": "Not confirmed. Click for options",
|
"status-not-sent-click": "Not confirmed. Click for options",
|
||||||
|
"send-logs": "Send logs",
|
||||||
"edit-network-config": "Edit network config",
|
"edit-network-config": "Edit network config",
|
||||||
"clear-history-confirmation": "Clear history?",
|
"clear-history-confirmation": "Clear history?",
|
||||||
"connect": "Connect",
|
"connect": "Connect",
|
||||||
|
|
Loading…
Reference in New Issue