diff --git a/appinfo/info.xml b/appinfo/info.xml
index 0c39953..45c1f76 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -5,7 +5,7 @@
OCC Web
OCC Commands in a web terminal
- 0.2.3
+ 0.3.0
agpl
Adphi
OCCWeb
@@ -15,7 +15,7 @@
https://github.com/adphi/occweb
https://github.com/adphi/occweb/raw/main/appinfo/screenshot.png
-
+
diff --git a/css/style.css b/css/style.css
index 6141904..b4fda40 100644
--- a/css/style.css
+++ b/css/style.css
@@ -773,3 +773,15 @@ terminal .terminal-output > div {
.prompt span, .cursor-line span span, .command div span{
color: #009ae3 !important;
}
+
+/* Keep terminal content left-aligned even when Nextcloud runs in RTL mode */
+html[dir="rtl"] #app-content.terminal,
+html[dir="rtl"] #app-content .terminal,
+html[dir="rtl"] #app-content .terminal-output,
+html[dir="rtl"] #app-content .terminal-output > div > div,
+html[dir="rtl"] #app-content .cmd,
+html[dir="rtl"] #app-content .cmd div,
+html[dir="rtl"] #app-content .cmd .prompt {
+ direction: ltr;
+ text-align: left;
+}
diff --git a/js/index.js b/js/index.js
index 4641951..df715dd 100644
--- a/js/index.js
+++ b/js/index.js
@@ -1,6 +1,42 @@
(function (OC, window, $, undefined) {
'use strict';
$(function() {
+ var longRunningCommandPatterns = [
+ /^files:scan(?:\s|$)/,
+ /^files:transfer-ownership(?:\s|$)/,
+ /^encryption:/,
+ /^fulltextsearch:/,
+ /^preview:generate-all(?:\s|$)/,
+ /^versions:cleanup(?:\s|$)/,
+ /^trashbin:cleanup(?:\s|$)/,
+ /^db:add-missing-indices(?:\s|$)/,
+ /^db:add-missing-columns(?:\s|$)/,
+ /^db:add-missing-primary-keys(?:\s|$)/,
+ /^maintenance:repair(?:\s|$)/
+ ];
+
+ function isPotentiallyLongRunning(command) {
+ var normalizedCommand = (command || '').trim().toLowerCase();
+ if (!normalizedCommand) {
+ return false;
+ }
+ return longRunningCommandPatterns.some(function(pattern) {
+ return pattern.test(normalizedCommand);
+ });
+ }
+
+ function confirmLongRunningCommand(command) {
+ if (!isPotentiallyLongRunning(command)) {
+ return true;
+ }
+
+ return window.confirm(
+ 'Warning: "' + command + '" can take a long time on large instances and may timeout in the browser.\n\n' +
+ 'Recommendation: Run this command via CLI/SSH for better reliability.\n\n' +
+ 'Do you want to run it anyway?'
+ );
+ }
+
function scrollToBottom(){
var html = $('html');
html.scrollTop(html.prop('scrollHeight'));
@@ -16,6 +52,10 @@
this.reset();
break;
default:
+ if (!confirmLongRunningCommand(command)) {
+ term.echo('\nCommand canceled.');
+ break;
+ }
var occCommand = {
command: command
};
diff --git a/lib/Controller/OccController.php b/lib/Controller/OccController.php
index 3ead8c1..bfae7ef 100644
--- a/lib/Controller/OccController.php
+++ b/lib/Controller/OccController.php
@@ -20,7 +20,6 @@ class OccController extends Controller
private $userId;
private $application;
- private $symphonyApplication;
private $output;
public function __construct($AppName, IRequest $request, $userId)
@@ -41,14 +40,12 @@ public function __construct($AppName, IRequest $request, $userId)
);
$this->application->setAutoExit(false);
$this->output = new OccOutput(OutputInterface::VERBOSITY_NORMAL, true);
- $this->application->loadCommands(new StringInput(""), $this->output);
- $reflectionProperty = new \ReflectionProperty(Application::class, 'application');
- $reflectionProperty->setAccessible(true);
- $this->symphonyApplication = $reflectionProperty->getValue($this->application);
+ $this->application->loadCommands(new StringInput(""), $this->output);
}
/**
* @NoCSRFRequired
+ * @AdminRequired
*/
public function index()
{
@@ -73,23 +70,51 @@ private function run($input)
/**
* @param string $command
* @return DataResponse
+ * @AdminRequired
*/
public function cmd($command)
{
- $this->logger->debug($command);
- $input = new StringInput($command);
+ $startedAt = microtime(true);
+ $rawCommand = trim((string)$command);
+ $commandName = strtok($rawCommand, ' ') ?: 'unknown';
+
+ $this->logger->info('occweb command started', [
+ 'user' => $this->userId,
+ 'command' => $commandName,
+ ]);
+
+ $input = new StringInput($rawCommand);
$response = $this->run($input);
- $this->logger->debug($response);
+
+ $this->logger->info('occweb command finished', [
+ 'user' => $this->userId,
+ 'command' => $commandName,
+ 'duration_ms' => (int)round((microtime(true) - $startedAt) * 1000),
+ ]);
+
return new DataResponse($response);
}
+ /**
+ * @AdminRequired
+ */
public function list() {
- $defs = $this->symphonyApplication->all();
- $cmds = array();
- foreach ($defs as $d) {
- array_push($cmds, $d->getName());
+ $output = $this->run(new StringInput('list --raw'));
+ $lines = preg_split('/\r\n|\r|\n/', (string)$output);
+ $cmds = [];
+
+ foreach ($lines as $line) {
+ $line = trim($line);
+ if ($line === '') {
+ continue;
+ }
+
+ $parts = preg_split('/\s+/', $line, 2);
+ if (!empty($parts[0])) {
+ $cmds[] = $parts[0];
+ }
}
- return new DataResponse($cmds);
+
+ return new DataResponse(array_values(array_unique($cmds)));
}
}
-
diff --git a/lib/Controller/OccOutput.php b/lib/Controller/OccOutput.php
index 5f0c0d0..74891b2 100644
--- a/lib/Controller/OccOutput.php
+++ b/lib/Controller/OccOutput.php
@@ -12,6 +12,7 @@
class OccOutput extends BufferedOutput implements ConsoleOutputInterface
{
private $consoleSectionOutputs = [];
+ private $errorOutput;
private $stream;
/**
@@ -21,13 +22,16 @@ class OccOutput extends BufferedOutput implements ConsoleOutputInterface
*/
public function getErrorOutput(): OutputInterface
{
- // TODO: Implement getErrorOutput() method.
- return $this;
+ if ($this->errorOutput === null) {
+ // Keep compatibility when no explicit stderr output is configured.
+ $this->errorOutput = $this;
+ }
+ return $this->errorOutput;
}
public function setErrorOutput(OutputInterface $error)
{
-
+ $this->errorOutput = $error;
}
/**