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>
|
||||
</service>
|
||||
<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>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -30,7 +30,6 @@ import im.status.ethereum.module.StatusThreadPoolExecutor;
|
|||
public class MainActivity extends ReactActivity
|
||||
implements ActivityCompat.OnRequestPermissionsResultCallback{
|
||||
|
||||
private static final int PERMISSION_REQUEST_CAMERA = 0;
|
||||
|
||||
@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)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/CMakeModules/${SCRIPT}.cmake.in ${SCRIPT}.cmake @ONLY)
|
||||
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})")
|
||||
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_CFLAGS ${${mod}_EXECUTABLE_COMPILE_FLAGS})
|
||||
|
|
|
@ -14,7 +14,6 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11")
|
|||
set(APP_NAME "reportApp")
|
||||
|
||||
set(MAIN_CPP_SOURCE reportpublisher.cpp
|
||||
reportpublisher.cpp
|
||||
main.cpp)
|
||||
|
||||
add_executable(
|
||||
|
@ -23,7 +22,7 @@ add_executable(
|
|||
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})
|
||||
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
package im.status.ethereum.module;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.*;
|
||||
import android.view.WindowManager;
|
||||
import android.support.v4.content.FileProvider ;
|
||||
import android.text.TextUtils;
|
||||
import android.view.WindowManager;
|
||||
import android.util.Log;
|
||||
import android.webkit.CookieManager;
|
||||
import android.webkit.CookieSyncManager;
|
||||
|
@ -16,17 +21,25 @@ import com.facebook.react.bridge.*;
|
|||
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
||||
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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.OutputStream;
|
||||
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.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.json.JSONException;
|
||||
|
@ -36,11 +49,12 @@ import javax.annotation.Nullable;
|
|||
class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventListener, ConnectorHandler {
|
||||
|
||||
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 HashMap<String, Callback> callbacks = new HashMap<>();
|
||||
|
||||
private static StatusModule module;
|
||||
private ServiceConnector status = null;
|
||||
private ExecutorService executor = null;
|
||||
|
@ -114,10 +128,15 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
|||
this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("gethEvent", params);
|
||||
}
|
||||
|
||||
private static String prepareLogsFile() {
|
||||
String gethLogFileName = "geth.log";
|
||||
File pubDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
File logFile = new File(pubDirectory, gethLogFileName);
|
||||
private File getLogsFile() {
|
||||
final File pubDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
final File logFile = new File(pubDirectory, gethLogFileName);
|
||||
|
||||
return logFile;
|
||||
}
|
||||
|
||||
private String prepareLogsFile(final Context context) {
|
||||
final File logFile = getLogsFile();
|
||||
|
||||
try {
|
||||
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
|
||||
final String absDataDirPath = pathCombine(absRootDirPath, jsonConfig.getString("DataDir"));
|
||||
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("KeyStoreDir", absKeystoreDirPath);
|
||||
|
@ -456,6 +476,156 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
|||
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
|
||||
public void addPeer(final String enode, final Callback callback) {
|
||||
Log.d(TAG, "addPeer");
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "eventdispatcher.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
#include <QStorageInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QByteArray>
|
||||
#include <QVariantMap>
|
||||
|
@ -22,6 +24,9 @@
|
|||
|
||||
#include "libstatus.h"
|
||||
|
||||
extern QString getDataStoragePath();
|
||||
extern QString getLogFilePath();
|
||||
|
||||
namespace {
|
||||
struct RegisterQMLMetaType {
|
||||
RegisterQMLMetaType() {
|
||||
|
@ -92,7 +97,7 @@ void RCTStatus::startNode(QString configString) {
|
|||
if (!relativeDataDirPath.startsWith("/"))
|
||||
relativeDataDirPath.prepend("/");
|
||||
|
||||
QString rootDirPath = getRootDirPath();
|
||||
QString rootDirPath = getDataStoragePath();
|
||||
QDir rootDir(rootDirPath);
|
||||
QString absDataDirPath = rootDirPath + relativeDataDirPath;
|
||||
QDir dataDir(absDataDirPath);
|
||||
|
@ -100,10 +105,11 @@ void RCTStatus::startNode(QString configString) {
|
|||
dataDir.mkpath(".");
|
||||
}
|
||||
|
||||
d_gethLogFilePath = dataDir.absoluteFilePath("geth.log");
|
||||
configJSON["DataDir"] = absDataDirPath;
|
||||
configJSON["BackupDisabledDataDir"] = absDataDirPath;
|
||||
configJSON["KeyStoreDir"] = rootDir.absoluteFilePath("keystore");
|
||||
configJSON["LogFile"] = dataDir.absoluteFilePath("geth.log");
|
||||
configJSON["LogFile"] = d_gethLogFilePath;
|
||||
|
||||
const QJsonDocument& updatedJsonDoc = QJsonDocument::fromVariant(configJSON);
|
||||
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) {
|
||||
Q_D(RCTStatus);
|
||||
qCDebug(RCTSTATUS) << "::addPeer call - callbackId:" << callbackId;
|
||||
|
@ -177,7 +274,7 @@ void RCTStatus::verify(QString address, QString password, double callbackId) {
|
|||
Q_D(RCTStatus);
|
||||
qCInfo(RCTSTATUS) << "::verify call - callbackId:" << callbackId;
|
||||
QtConcurrent::run([&](QString address, QString password, double callbackId) {
|
||||
QDir rootDir(getRootDirPath());
|
||||
QDir rootDir(getDataStoragePath());
|
||||
QString keystorePath = rootDir.absoluteFilePath("keystore");
|
||||
const char* result = VerifyAccountPassword(keystorePath.toUtf8().data(), address.toUtf8().data(), password.toUtf8().data());
|
||||
logStatusGoResult("::verify VerifyAccountPassword", result);
|
||||
|
@ -334,17 +431,3 @@ void RCTStatus::updateMailservers(QString enodes, double callbackId) {
|
|||
d->bridge->invokePromiseCallback(callbackId, QVariantList{result});
|
||||
}, 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 createAccount(QString password, 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 recoverAccount(QString passphrase, QString password, double callbackId);
|
||||
Q_INVOKABLE void login(QString address, QString password, double callbackId);
|
||||
|
@ -75,9 +76,9 @@ private Q_SLOTS:
|
|||
|
||||
private:
|
||||
void logStatusGoResult(const char* methodName, const char* result);
|
||||
QString getRootDirPath();
|
||||
|
||||
QScopedPointer<RCTStatusPrivate> d_ptr;
|
||||
QString d_gethLogFilePath;
|
||||
};
|
||||
|
||||
#endif // RCTSTATUS_H
|
||||
|
|
|
@ -196,6 +196,16 @@ RCT_EXPORT_METHOD(notifyUsers:(NSString *)message
|
|||
#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
|
||||
RCT_EXPORT_METHOD(addPeer:(NSString *)enode
|
||||
callback:(RCTResponseSenderBlock)callback) {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
[status-im.hardwallet.core :as hardwallet]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.init.core :as init]
|
||||
[status-im.utils.logging.core :as logging]
|
||||
[status-im.log-level.core :as log-level]
|
||||
[status-im.mailserver.core :as mailserver]
|
||||
[status-im.network.core :as network]
|
||||
|
@ -543,6 +544,13 @@
|
|||
(fn [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
|
||||
|
||||
(handlers/register-handler-fx
|
||||
|
|
|
@ -2,17 +2,20 @@
|
|||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.accounts.update.core :as accounts.update]
|
||||
[status-im.react-native.js-dependencies :as rn-dependencies]
|
||||
[status-im.node.core :as node]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.fx :as fx]))
|
||||
|
||||
(fx/defn save-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])
|
||||
new-settings (if log-level
|
||||
(assoc settings :log-level log-level)
|
||||
(dissoc settings :log-level))]
|
||||
(accounts.update/update-settings cofx
|
||||
(if log-level
|
||||
(assoc settings :log-level log-level)
|
||||
(dissoc settings :log-level))
|
||||
{:success-event [:accounts.update.callback/save-settings-success]})))
|
||||
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
|
||||
[{:keys [db]} {:keys [name value] :as log-level}]
|
||||
|
|
|
@ -54,6 +54,9 @@
|
|||
(defn notify-users [m callback]
|
||||
(native-module/notify-users m callback))
|
||||
|
||||
(defn send-logs [dbJson]
|
||||
(native-module/send-logs dbJson))
|
||||
|
||||
(defn add-peer [enode callback]
|
||||
(native-module/add-peer enode callback))
|
||||
|
||||
|
|
|
@ -87,6 +87,10 @@
|
|||
(when status
|
||||
(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]
|
||||
(when (and @node-started status)
|
||||
(call-module #(.addPeer status enode on-result))))
|
||||
|
|
|
@ -72,6 +72,12 @@
|
|||
[limit 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]
|
||||
(let [accounts (get db :accounts/accounts)
|
||||
current-fleet-key (fleet/current-fleet db address)
|
||||
|
@ -85,9 +91,7 @@
|
|||
:installation-id (get db :accounts/new-installation-id)}
|
||||
(get accounts address))
|
||||
use-custom-bootnodes (get-in settings [:bootnodes network])
|
||||
log-level (or (:log-level settings)
|
||||
(if utils.platform/desktop? ""
|
||||
config/log-level-status-go))]
|
||||
log-level (get-log-level settings)]
|
||||
(cond-> (get-in networks [network :config])
|
||||
:always
|
||||
(get-base-node-config)
|
||||
|
|
|
@ -183,6 +183,19 @@
|
|||
:margin-bottom 8
|
||||
: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
|
||||
{:margin-left 32})
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
[status-im.utils.utils :as utils]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[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]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.utils.gfycat.core :as gfy]
|
||||
|
@ -135,15 +137,21 @@
|
|||
|
||||
(views/defview logging-display []
|
||||
(views/letsubs [logging-enabled [:settings/logging-enabled]]
|
||||
[react/view {:style (styles/profile-row false)}
|
||||
[react/text {:style (assoc (styles/profile-row-text colors/black)
|
||||
:font-size 14)} (i18n/label :t/logging-enabled)]
|
||||
[react/switch {:on-tint-color colors/blue
|
||||
:value logging-enabled
|
||||
:on-value-change #(re-frame/dispatch [:log-level.ui/logging-enabled (not logging-enabled)])}]]))
|
||||
[react/view
|
||||
[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)]
|
||||
[react/switch {:on-tint-color colors/blue
|
||||
:value 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/letsubs [current-mailserver-id [:mailserver/current-id]
|
||||
(views/letsubs [installations [:pairing/installations]
|
||||
current-mailserver-id [:mailserver/current-id]
|
||||
mailservers [:mailserver/fleet-mailservers]
|
||||
mailserver-state [:mailserver/state]
|
||||
node-status [:node-status]
|
||||
|
@ -197,21 +205,21 @@
|
|||
[vector-icons/icon :icons/qr {:style {:tint-color colors/blue}}]]]])
|
||||
|
||||
(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)])}
|
||||
[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)}
|
||||
(i18n/label :t/help-center)]
|
||||
[vector-icons/icon :icons/forward {:style {:tint-color colors/gray}}]]])
|
||||
|
||||
(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
|
||||
(re-frame/dispatch [:navigate-to (if adv-settings-open? :home :advanced-settings)])
|
||||
(re-frame/dispatch [:load-debug-metrics]))}
|
||||
[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)}
|
||||
(i18n/label :t/advanced-settings)]
|
||||
[vector-icons/icon :icons/forward {:style {:tint-color colors/gray}}]]])
|
||||
|
|
|
@ -38,6 +38,14 @@
|
|||
(defstyle my-profile-settings-logout
|
||||
{: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
|
||||
{:margin-top 16
|
||||
:margin-bottom 12})
|
||||
|
|
|
@ -170,6 +170,13 @@
|
|||
{:label-kw :t/log-level
|
||||
:action-fn #(re-frame/dispatch [:navigate-to :log-level-settings])
|
||||
: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
|
||||
{:label-kw :t/fleet
|
||||
|
@ -250,7 +257,7 @@
|
|||
{:contact current-account
|
||||
:source :public-key
|
||||
:value public-key}])
|
||||
:style styles/share-contact-code-button
|
||||
:style styles/share-contact-code-button
|
||||
:accessibility-label :share-my-profile-button}
|
||||
(i18n/label :t/share-my-profile)]]
|
||||
[react/view styles/my-profile-info-container
|
||||
|
|
|
@ -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",
|
||||
"status-not-sent-tap": "Not confirmed. Tap for options",
|
||||
"status-not-sent-click": "Not confirmed. Click for options",
|
||||
"send-logs": "Send logs",
|
||||
"edit-network-config": "Edit network config",
|
||||
"clear-history-confirmation": "Clear history?",
|
||||
"connect": "Connect",
|
||||
|
|
Loading…
Reference in New Issue