coderaiser / cloudcmd

✨☁️📁✨ Cloud Commander file manager for the web with console and editor.
https://cloudcmd.io
MIT License
1.84k stars 259 forks source link

buttons executing custom commands or user menu like in mc #221

Closed ulno closed 5 years ago

ulno commented 5 years ago

As most of my users completely fear the command line, I would like to be able to implement buttons (like for copy, mkdir, or console start) or a user menu (like happening on right click or like seen in midnight commander pressing F3) to execute some pre-defined commands in the respectively activated folder and showing their output while they are executing in the console.

How much effort would this be? Where would I have to start?

Usecase example

I have cloudcmd running on a headless raspberry pi that I control via the web using cloudcmd. I just made a copy of a template source folder and now want to use the initialize command to write this configuration to a microcontroller connected to the raspberry pi (or gateway computer). Now, I am starting the console per button press and type initialize and look at the output. It would be nicer if I had some kind of deploy button executing initialize in the right folder directly for me. If there were multiple commands possible (initialize, deploy, upgrade), maybe a pop-up menu would make sense.

coderaiser commented 5 years ago

As I understood what you want is ability to define shell commands, and run them somehow not opening console, from user menu, for example. To implement such feature a couple things should be solved:

If all you need is to run a command, and see output, in this case it will be something like console or terminal anyway, but it should run predefined command, and show output.

You can use --terminal-command for this purpose. By default it is bash or cmd depends on operation system, but you can define any program.

The tool can be built using terminal-kit that will contain menu options and run what you need. You can read more about it here. Here is example code:

const {terminal} = require('terminal-kit');

terminal.cyan('The hall is spacious. Someone lighted few chandeliers.\n') ;
terminal.cyan('There are doorways south and west.\n');

const items = [
    'a. Go south' ,
    'b. Go west' ,
    'c. Go back to the street',
] ;

terminal.singleColumnMenu( items, (error , response) => {
    terminal('\n').eraseLineAfter.green(
        "#%s selected: %s (%s,%s)\n" ,
        response.selectedIndex ,
        response.selectedText ,
        response.x ,
        response.y,
    ) ;
    process.exit() ;
});

And result it produces: single-column-menu-doc1

There is a couple benefits of using terminal for this purpose:

  1. Commands should be run and input captured, that's terminal already does well.
  2. The solution prototype can be implemented very fast.

You can use you own repo to implement proof of concept with predefined scripts, and latter it can be extended to ability to add and remove scripts in a comfortable way. And added to cloudcmd organization.

In a future user menu can be started with help of a hot key, like ALT + F2, or Ctrl + ~.

What do you think about such solution?

ulno commented 5 years ago

Thanks for that idea - it sounds like a good first step. The only worry I have with this approach is: can I get the currently selected path in cloudcmd somehow (is the cloudcmd path environment variable updated all the time and I can just read it out before I execute the user-command)?

I would also need to make the choices - mouse clickable, but I think ncurses in the terminal should be my friend there (either terminal-kit or any other console-ncurses menu should work there I assume, I am more a python guy than a java-script guy, so my choice might be ptk). Coudl even initially just make a shell escape.

coderaiser commented 5 years ago

client/modules/terminal.js passes env variables with currently selected path to executable command and it is updated.

terminal-kit should support mouse click as well. And resulting user menu can be bundle to Cloud Commander in case if it will be written in JavaScript. In the browser side it can be something similar to client/modules/terminal.js.

ulno commented 5 years ago

I will give it a go the coming week, using your js-skeleton and terminal-kit and report back here.

ulno commented 5 years ago

client/modules/terminal.js passes env variables with currently selected path to executable command and it is updated.

This is incorrect. The variable is set only once (first start of terminal) and never updated. Worse, when the shell or command in terminal quits/ends the respawned process doesn't have the environment variables anymore at all. You have to refresh the cloudcmd and terminal browser page to access the variable again.

Also, when the path in cloudcmd is changed after terminal was started, the variable doesn't change in the started program (of course - a fork has a copy of the environment of the process it was started from).

If we can't find a quick fix for this, this renders all my efforts useless - see below for some ideas.

terminal-kit should support mouse click as well. And resulting user menu can be bundle to Cloud Commander in case if it will be written in JavaScript. In the browser side it can be something similar to client/modules/terminal.js.

This works pretty well, I have spent a lot of time to convince terminal-kit to do something decent here and was kind of proud of my results (compare: https://github.com/ulno/ulnoiot/blob/master/bin/simple_shell.js, I wasted also a lot of time as I wanted to do it in python first, but python and ncurses is too slow for the raspberry pi, terminal-kit works good enough).

Unfortunately, this all falls apart as there is no way for me to get an updated path from cloudcmd.

Is there (1) a way to close the terminal window after an action is done (eventually adding a menu for different terminal sessions there - 1a) or (2) writing the paths always to a tmp-file or /var/run/cloudcmd that can be read before I execute something in the terminal, maybe the terminal should have an environment variable with an ID (2a) that refers to the temp file or a location in the tmp-file to allow multiple sessions?

I (and my students) would be very thankful if there would be a solution to this. I will start looking at cloudcmd code, if I can quickly hack solution 2 (2a can wait after my next class).

coderaiser commented 5 years ago

This is incorrect. The variable is set only once (first start of terminal) and never updated. Worse, when the shell or command in terminal quits/ends the respawned process doesn't have the environment variables anymore at all. You have to refresh the cloudcmd and terminal browser page to access the variable again.

You right, this is how console-io works, because it runs new command every time it is update env variables. You can start menu program on open and terminate it when user chose one of items and see output, close terminal window when process exit. This is the simplest solution.

You can set Cloud Commander's options:

cloudcmd --terminal-command ~/your-menu-program.js --no-terminal-auto-restart

Do not forgot to add shebang and make chmod +x to your file.

And modify client side part to start menu on every show and hide terminal on every exit.

This works pretty well, I have spent a lot of time to convince terminal-kit to do something decent here and was kind of proud of my results (compare: https://github.com/ulno/ulnoiot/blob/master/bin/simple_shell.js, I wasted also a lot of time as I wanted to do it in python first, but python and ncurses is too slow for the raspberry pi, terminal-kit works good enough).

This is great 👍, we can use it as a user menu in Cloud Commander in a future.

Is there (1) a way to close the terminal window after an action is done

Yes gritty uses socket.io and emits exit event on the server side. On the client side there is socket, and you can write something like:

socket.on('exit', hide);

To close terminal window on program exit.

(2) writing the paths always to a tmp-file or /var/run/cloudcmd that can be read before I execute something in the terminal, maybe the terminal should have an environment variable with an ID (2a) that refers to the temp file or a location in the tmp-file to allow multiple sessions?

I think would be simpler to run menu every time, and get data from environment variables, what do you think about it? I don't think this will be very slow, but communication thru environment is the simplest cross operational future proof way of doing things (12 factor app agree).

ulno commented 5 years ago

You can set Cloud Commander's options:

cloudcmd --terminal-command ~/your-menu-program.js --no-terminal-auto-restart

Do not forgot to add shebang and make chmod +x to your file.

I did already toy with the no-auto-restart-option, running this script:

#!/bin/bash
for i in {1..5}; do
    echo "$i. $(date +%H:%M:%S) >$ACTIVE_DIR<"
    sleep 5
done
exit 0

However, it does not close and restart...

And modify client side part to start menu on every show and hide terminal on every exit.

... which you must be hinting at here

Yes gritty uses socket.io and emits exit event on the server side. On the client side there is socket, and you can write something like:

socket.on('exit', hide);

To close terminal window on program exit.

... and here

So, I changed the code this way:

//    const {socket, terminal} = gritty(document.body, options);
    var {socket, terminal} = gritty(document.body, options); // make sure to open new each time

    Terminal = terminal;

    socket.on('exit', hide); // ulno: hint from coderaiser for closing all the time // TODO: kill menu too?

and copied it over an obfuscated file into a new local installation in node_modules/cloudcmd/dist/modules (so I basically changed two lines)

(I tried to create the dist folder locally, but I have no idea how - npm install and what else?)

The resulting cloudcmd starts and looks ok, but does not start the terminal anymore, I assume I have somehow correctly build the package, how can I do that?

Thanks for all the help and sorry for my lack of nodejs knowledge.

ulno commented 5 years ago

Update1: managed to create dist folder with yarn.

Update2: only managed it once and might be an accidental call of webpack, however can't reproduce, still always failing creating the dist folder and can't start cloudcmd at all from my local directory neither on the pi nor on my desktop.

webpack always fails now (several times) with:

    ERROR in sw.js from Terser
    TypeError: Cannot read property 'minify' of undefined
        at minify (/media/ulno-x230-big/ulno-extra/tmp/cloudcmd.git/node_modules/terser-webpack-plugin/dist/minify.js:175:23)

Update3: webpack --mode development might have done the trick, got a dist folder again.

ulno commented 5 years ago

Yep that works, now the hide-part, when shell-command ends, works already, however the terminal does not restart yet, my trick using var instead of const to avoid building a singleton was probably not smart enough (even if I think it's the right idea).

However, I don't find anything to destroy the old Terminal (and view) and where I can then call create without messing up the view. Also have no idea about the order of execution of things due to the usage of await and async.

On another note, for others, I add here a quick summary how to make cloudcmd development possible in a local directory in a Linux environment:

Make sure you have nodejs, npm, yarn, and webpack globally installed, then ...

cd <personal-development-folder>  # adjust!
git clone git clone ssh://git@github.com/coderaiser/cloudcmd  # or your personal fork
cd cloudcmd
npm i gritty
npm i rimraf
npm i madrun
export NODE_PATH="$(pwd)/node_modules:/usr/lib/node_modules"
alias redrun="node $(pwd)/node_modules/redrun/bin/redrun.js"
alias rimraf="node $(pwd)/node_modules/rimraf/rimraf.js"
yarn  # install webpack-cli, when asked
webpack --mode development
node bin/cloudcmd.js

Maybe that needs to go somewhere or is there an easier solution?

coderaiser commented 5 years ago

Yes this could be simpler:

git clone https://github.com/coderaiser/cloudcmd.git
cd cloudcmd
npm i
npm run build:start:dev

However, I don't find anything to destroy the old Terminal (and view) and where I can then call create without messing up the view. Also have no idea about the order of execution of things due to the usage of await and async.

Looks like this will be a little bit harder then I thought, and gritty should be modified to have ability to spawn different programs, or to destroy itself. I should think about it.

You moving in a right direction, thank you for all your work.

ulno commented 5 years ago

Ok, thanks for the development shortcut.

I guess, now I am back to square one. I have a small workshop Wednesday at a Makerspace and would have liked to show off the user menu there, so I think I have to proceed with step (2) (at least temporarily, if there is a chance to finish this with a couple of hours of coding investment):

(2) writing the paths always to a tmp-file or /var/run/cloudcmd that can be read before I execute something in the terminal,

Could you point me to a position in one of the server modules, where I could write to such a file? Anything that is triggered when the directory or focus is switched? Or is that all happening on client side? How could I trigger then an event from client side to execute something in the server code each time the path is changed?

I appreciate all your help, I hope I can help soon to provide some "nicer" code for the user menu when we straighten out the workflow between cloudcmd and gritty to allow new processes to be started.

coderaiser commented 5 years ago

Could you point me to a position in one of the server modules, where I could write to such a file? Anything that is triggered when the directory or focus is switched? Or is that all happening on client side? How could I trigger then an event from client side to execute something in the server code each time the path is changed?

Look, it is much harder, because server side doesn't know what file is selected, and what directory you observing. This is how you can handle this things just restarting menu program:

--- a/client/modules/terminal.js
+++ b/client/modules/terminal.js
@@ -52,7 +52,6 @@ module.exports.init = async () => {

     await CloudCmd.View();
     await loadAll();
-    await create();
 };

 module.exports.show = show;
@@ -87,6 +86,7 @@ function create() {
         fontFamily: 'Droid Sans Mono',
     };

+    delete window.IntersectionObserver;
     const {socket, terminal} = gritty(document.body, options);

     Terminal = terminal;
@@ -115,6 +115,7 @@ function show(callback) {
     if (!config('terminal'))
         return;

+    create();
     CloudCmd.View.show(Terminal.element, {
         afterShow: () => {
             if (Terminal)

Then run Cloud Commander with:

cloudcmd --terminal-command env --no-terminal-auto-restart

And every time you will open terminal you will see current directory and file name:

Here is what I see when I in home directory and select coderaiser

ACTIVE_DIR=/home/
PASSIVE_DIR=/home/
CURRENT_NAME=coderaiser
CURRENT_PATH=/home/coderaiser

And here is what I see when I in / directory and select lib:

ACTIVE_DIR=/
PASSIVE_DIR=/
CURRENT_NAME=lib
CURRENT_PATH=/lib

To hide terminal when menu is exit, add:

     socket.on('connect', exec.with(authCheck, socket));
+    socket.on('exit', hide);

About this error

   ERROR in sw.js from Terser
    TypeError: Cannot read property 'minify' of undefined
        at minify (/media/ulno-x230-big/ulno-extra/tmp/cloudcmd.git/node_modules/terser-webpack-plugin/dist/minify.js:175:23)

It is related to https://github.com/terser-js/terser/issues/251 and should be fixed already.

ulno commented 5 years ago

Thanks a lot!

This starts to work quite ok (I updated to your latest version from github and manually applied the patches). I can now see the different paths, when restarting Terminal from a different folder.

However, adding delete window.IntersectionObserver; before the terminal creation, seems to break the folder navigation (both on Chrome and Firefox): Coming back to a folder that had already been visited, all folder and file mini icons are gone (apart from the icon before .. -> which still works), and folders cannot be entered anymore (on the other column it still works). Does something else have to be created here to keep navigation working?

Update: it can be actually any folder and it does not happen all the time. It seems to happen more often in Firefox and Chromium and less often on Chrome. However, on all browsers it happens usually before you change into 10 different folders - on the desktop. I will try to see if it is an issue at all on the pi (which is much slower than the desktop).

A reload of the current window restores the icons again but just navigating into a folder and back up again destroys them again.

coderaiser commented 5 years ago

However, adding delete window.IntersectionObserver; before the terminal creation, seems to break the folder navigation (both on Chrome and Firefox):

This is a hot fix for chrome, if you using firefox ignore it. It is fixes first load of terminal in chrome, but as soon as https://github.com/xtermjs/xterm.js/pull/1929 will be merged, this behavior will be fixed.

Update: it can be actually any folder and it does not happen all the time. It seems to happen more often in Firefox and Chromium and less often on Chrome. However, on all browsers it happens usually before you change into 10 different folders - on the desktop. I will try to see if it is an issue at all on the pi (which is much slower than the desktop).

I can be related to https://github.com/coderaiser/cloudcmd/issues/224, could you please tell me names of this directories? I can't reproduce this with my directories.

ulno commented 5 years ago

In Firefox, for example, I just need to open http://localhost:8000 (I end in /), then I double-click (also when I use the keybaord and press enter)on home, and I can only navigate back (icons are gone), this happened 5/5 times I just tested it. In Chrome, it does not happen when I enter Chrome, but when I double-click .., I am back at / and no icons at all.

It happens in both: one column and two column mode. Only change I made is, adding the delete window.IntersectionObserver patch.

Screenshots atached. f1-root-firefox f2-home-firefox c1-home-chrome c2-root-chrome

ulno commented 5 years ago

Yes, issue #224 seems to be related, however, navigation problems stop for me if I take the delete out - was that ever there upstream?

I also have the same problems in Firefox incognito mode.

Same problems on Android with Firefox and Chrome.

Same problems with and without authentication enabled.

coderaiser commented 5 years ago

I can't reproduce this anyway. No it wasn't upstream, I just today figure out what broke first show of a terminal on chrome, and it is relates to IntersectionObserver.

ulno commented 5 years ago

Positive Update: running on the raspberry pi in latest raspbian, I don't have the problems (and terminal re-opening in new environment works, too -> which means I can move ahead, yeah, thanks so much).

So, why does it not work on my desktop?

As mentioned in thread beginning, cloudcmd pi-configuration is this:

* **Version** (`cloudcmd -v`): v10.8.3

now upgraded to v11.8.3 and patched as advised (cloned from current http://github.com/ulno/cloudcmd)

* **Node Version** `node -v`: v8.15.0

* **OS** (`uname -a` on Linux):  Linux ulnoiotgw 4.14.79-v7+ #1159 SMP Sun Nov 4 17:50:20 GMT 2018 armv7l GNU/Linux

* **Browser name/version**: all

My desktop:

Any idea, what could cause this and let me know if I can help to track this down - but apart from that, I am pretty happy at this point as it seems to work well on the pi and I might show off cloudcmd in my IoT framework on Wednesday with some simple user menus!

Update: issues with icons are gone in 11.8.5

ulno commented 5 years ago

Things are working out mostly fine with the user-menu on the pi, just one very small issue:

Often a program that ran in the user-menu is modifying the directory that it was started in (creating for example new files or directories).

How can I trigger a refresh on exit of the terminal of the current file column to reflect th echange (like pressing on the two small errors or doing ctrl-r)? What do I have to call for that?

coderaiser commented 5 years ago

How can I trigger a refresh on exit of the terminal of the current file column to reflect the change (like pressing on the two small arrows or doing ctrl-r)? What do I have to call for that?

Just call CloudCmd.refresh() and it will refresh content of current directory.

httpsgithu commented 5 years ago

so where to do that in the console or in the terminal? i think i can install zsh plugin to do that or make a lot of buttons at the bottom of the page?

ulno commented 5 years ago

At the moment, you need to make the changes to the source mentioned hereto be able to use that feature (you lose though the terminal). It works great for my students and has simplified the workflow greatly.

@coderaiser: How much effort would it be to clone the current terminal for this "User-Menu" functionality" and offer another optional button for this? Or even just have a user_menu flag to make the terminal behave diffrently when called from the "User-menu"-button?

Here is a summary of the changes I made to my version of cloudcmd:

Enable closing of terminal after command is run and refresh when opening again

-------------------- client/modules/terminal.js --------------------------
index a635e939..d971c854 100644
@@ -52,7 +52,6 @@ module.exports.init = async () => {

     await CloudCmd.View();
     await loadAll();
-    await create();
 };

 module.exports.show = show;
@@ -87,13 +86,10 @@ function create() {
         fontFamily: 'Droid Sans Mono',
     };

-
-//    const {socket, terminal} = gritty(document.body, options);
-    var {socket, terminal} = gritty(document.body, options); // make sure to open new each time
-
-    Terminal = terminal;
+    delete window.IntersectionObserver;
+    const {socket, terminal} = gritty(document.body, options);

-    socket.on('exit', hide); // ulno: hint from coderaiser for closing all the time // TODO: kill menu too?
+    Terminal = terminal;

     terminal.on('key', (char, {keyCode, shiftKey}) => {
         if (shiftKey && keyCode === Key.ESC) {
@@ -118,6 +114,8 @@ function show(callback) {

     if (!config('terminal'))
         return;
+
+    create();

     CloudCmd.View.show(Terminal.element, {

Autohide after execution finishes

-------------------------- client/modules/terminal.js --------------------------
index d971c854..c15cb3c3 100644
@@ -98,6 +98,7 @@ function create() {
     });

     socket.on('connect', exec.with(authCheck, socket));
+    socket.on('exit', hide);
 }

 function authCheck(spawn) {

Refresh of directory after close of terminal

-------------------------- client/modules/terminal.js --------------------------
index c15cb3c3..69a605ce 100644
@@ -94,11 +94,12 @@ function create() {
     terminal.on('key', (char, {keyCode, shiftKey}) => {
         if (shiftKey && keyCode === Key.ESC) {
             hide();
+            CloudCmd.refresh();
         }
     });

     socket.on('connect', exec.with(authCheck, socket));
-    socket.on('exit', hide);
+    socket.on('exit', function() { hide(); CloudCmd.refresh(); });
 }

 function authCheck(spawn) {

Renaming the terminal button into User Menu

------------------------------- html/index.html -------------------------------
index e0fd8595..7a3bdc62 100644
@@ -30,7 +30,7 @@
             <button id=f9      class="cmd-button reduce-text icon-menu"      title="Menu"            >F9</button>
             <button id=f10     class="cmd-button reduce-text icon-config"    title="Config"          >F10</button>
             <button id=~       class="cmd-button reduce-text icon-console"   title="Console"         >~</button>
-            <button id=shift~  class="cmd-button reduce-text icon-terminal"  title="Terminal"        >⇧ ~</button>
+            <button id=shift~  class="cmd-button reduce-text icon-terminal"  title="User Menu"        >⇧ ~</button>
             <button id=contact class="cmd-button reduce-text icon-contact"   title="Contact"         ></button>
         </div>
         <script src="{{ prefix }}/dist/cloudcmd.common.js"></script>
coderaiser commented 5 years ago

I think we can keep terminal as it is, and add user menu which will be shown with help of Shift + F2, for this purpose we should:

Arguments with --experimental prefix can be changed or removed with a minor version change. As it is not completed feature, we can use this prefix, and when we are done we will remove --experimental prefix.

@ulno what do you think about it?

ulno commented 5 years ago

Sounds like a great idea to me. Do you need any help to make that happen - maybe in terms of documentation/youtube usage video?

We can use my user-menu from ulnoiot/iot (https://github.com/iotempire/iotempower/blob/master/bin/user_menu.js) empower as an example? Or shall I strip out just the button list? Could even make that a bit more generic and take a json file with button titles and respective commands as as input?

coderaiser commented 5 years ago

Sounds like a great idea to me. Do you need any help to make that happen - maybe in terms of documentation/youtube usage video?

Yes, I need help with a pull request that contains changes described above :)

We can use my user-menu from [ulnoiot/iot] (https://github.com/iotempire/iotempower/blob/master/bin/user_menu.js) empower as an example? Or shall I strip out just the button list? Could even make that a bit more generic and take a json file with button titles and respective commands as as input?

Yes, would be great if user menu would be as generic as possible. Ideally would be great if our format will be compatible to mc. But we can start from generic format comfortable for you, json is great.

ulno commented 5 years ago

Yes, I need help with a pull request that contains changes described above :)

Ok, you asked for it. I will be on vacation for 2 weeks now, but I will give it a shot the following week.

Yes, would be great if user menu would be as generic as possible. Ideally would be great if our format will be compatible to mc. But we can start from generic format comfortable for you, json is great.

Hmm, if it should be compatible to mc, shouldn't it be also called with F3? (Btw. would it make sense to make the keys and buttons customizable in a configuration file? Probably should file that as new issue.)

coderaiser commented 5 years ago

Hmm, if it should be compatible to mc, shouldn't it be also called with F3?

Do you mean as F2? The thing is F2 is already used for renaming files, just like it is in Windows Explorer, it can be useful to just rename a file, without opening the same panels and moving to it. Removing this feature is a major version bump.

(Btw. would it make sense to make the keys and buttons customizable in a configuration file? Probably should file that as new issue.)

We can make it in a future, when main functionality will be ready.

ulno commented 5 years ago

Yes, F2, I am using shift-f6 for rename in mc, but this leads off topic, can work on having a user-menu first.

coderaiser commented 5 years ago

Yes, F2, I am using shift-f6 for rename in mc, but this leads off topic, can work on having a user-menu first.

That's interesting, I should think about it, maybe there is a reason to bind keys in a similar way as mc does🤔. Shift+ F6 less comfortable then F3, for such useful operation as renaming, in the other hand, user menu can be used much often, and this is a reason for such a simple hot key. Anyways, it is a major version bump, so we can get back to this later when user menu will be ready.

coderaiser commented 5 years ago

@ulno don't rush with a pull request, I have an idea, how all the things can be simplified a lot. User menu can work just like config, or editor. It can use view to show modal, and then just show list, or select input. Also http endpoint can be added /api/v1/user-menu. I'll think how to make it work similar to mc. Have you used mc's user menu? As I understand it is only one level deep. But as I understood from your source code your menu is a couple levels deep. Is it possible to make it one level deep?

Also I have an idea about simplest json format similar to mc, it can look this way:

{
  "create list file": {
    "key": "c",
    "command": "ls > 1.txt"
  },
  "convert mp3 to flac": {
     "key": "m",
     "command": "mp3toFlac %s"
  }
}

For first implementation we can avoid key field:

{
  "create list file": {
    "command": "ls > 1.txt"
  },
  "convert mp3 to flac": {
     "command": "mp3toFlac %s"
    }
}

The field command can contain bash script.

We can even use:

{
  "create list file": "ls > 1.txt",
  "convert mp3 to flac": "mp3toFlac %s"
}

But in this case there won't be ability to set key, just like in mc. What do you think about it?

ulno commented 5 years ago

I prefer version 1 (or the abbreviated version in 2) to be closer to mc's format and being able to add a key or even a nested option.

(Sorry for late answer still on vacation)

coderaiser commented 5 years ago

(Sorry for late answer still on vacation)

That's OK, I'm trying to determine what format would fit your and everyone else needs and will be simple enough to parse :).

Latest version, I came up with, to handle your case and previous with file rename is .cloudcmd.menu.js file:

 module.exports = {
    'F2 - Rename file': () => {
        const {element} = DOM.CurrentInfo;
        DOM.renameCurrent(element);
    },
    'D - Deploy': async ({DOM, push}) => {
        const  {dirPath} = DOM.CurrentInfo;
        const msg = `You are about to deploy from the following path: ${dirPath}.\n Are you sure?`;

        await DOM.Dialog.confirm(msg);
        push('deploy');
    },
};

In case of Deploy, if user declines push will not executes, so function result will be empty, and nothing will be send to /api/v1/user-menu. Same with F2. In other case will be send deploy command, and executed on the server side.

With this format file rename can be kept, with help of <F2> pressed twice :). This is not the final solution. Any ideas will help a lot.

ulno commented 5 years ago

Having the option of a native confirmation dialog would of course be very awesome (that might be even superior to mc - but of course you can simulate that in mc).

Also being able to influence all the buttons this way would of course offer a host of options.

How much of this configuration should be exposed to the user?

coderaiser commented 5 years ago

Also being able to influence all the buttons this way would of course offer a host of options.

Actually not all the buttons, the idea is to not use modifiers like Alt, Ctrl, Shift etc.

How much of this configuration should be exposed to the user?

All of this can be exposed, it can be any JavaScript code that will be executed in the browser. But now it is only a prototype, a lot things should be made, for example server side isn't ready at all.

coderaiser commented 5 years ago

Just added a base implementation of user-menu https://github.com/coderaiser/cloudcmd/commit/4604b352465bdb367ec6d693b325f9e2e24d12d7 you can check it out in feature/user-menu branch. All you need to see how it works it is to create file .cloudcmd.menu.js:

'use strict';

module.exports = {
    'F2 - Rename file': ({DOM}) => {
        const {element} = DOM.CurrentInfo;
        DOM.renameCurrent(element);
    },
    'D - Deploy': async ({DOM}) => {
        const  {dirPath} = DOM.CurrentInfo;
        const msg = `You are about to deploy from the following path: ${dirPath}.\n Are you sure?`;

        await DOM.Dialog.confirm(msg);

        return 'deploy';
    },
    'L - List files': () => {
        return 'ls';
    },
};

Here is how it works. When you access the user menu, the file .cloudcmd.menu.js from the current directory is used if it exists, if no such file found, ~/.cloudcmd.menu.jswill be used (again if exists). In other case default user menu will be shown.

It's very raw solution, WIP, any comments would be appropriate :).

coderaiser commented 5 years ago

@ulno could you please tell me, do you show any output of your commands when running via user menu? What do you think should we show any output of commands, or it is doesn't matter?

ulno commented 5 years ago

I do show a lot of output. For example deploy is basically a compilation process calling scons and then gcc and compiling code of/in the current folder. It takes 40 seconds to 5 minutes, so it's quite crucial that you can see that something is happening and also see if it was successful or not (and if not what are the mistakes).

In a super long-term perspective it would be cool to call this compilation directly from the editor and maybe then later see the errors highlighted in the editor, but I don't know what the best way forward here is.

Also the option to press ctrl-c to interrupt (or kill or suspend) the started process is kind of useful here. I guess, I could wrap my scripts into something which generates some dedicated percentage values -> if you want even somehow "tagged" and some "log-information" (tagged differently), but maybe that is starting getting too far away from the terminal-based approach?

coderaiser commented 5 years ago

Yes, this makes things harder. Maybe would be better to call terminal for executing command selected from user menu. What do you think about this approach? Would this feet the needs for most users of user menu? Or maybe wee need some flag or ability to call terminal directly from user menu with help of something like:


module.exports = {
    'D - Deploy': async ({DOM}) => {
        const  {dirPath} = DOM.CurrentInfo;
        const msg = `You are about to deploy from the following path: ${dirPath}.\n Are you sure?`;

        await DOM.Dialog.confirm(msg);

        CloudCmd.Terminal.show({
            execute: 'deploy'
        });
    },
};

It wan't close terminal on end of executing, user must close it by himself. Should we provide option for this? Or it is OK?

ulno commented 5 years ago

On my way back, so I will be able to respond quicker and hopefully even be able to do some testing and coding tomorrow (looking forward to trying out your test branch).

I think we need to be able to use terminal as discussed for showing command execution. This opens automation of all kind of different things for potential users. I can imagine quick solutions for tons of small local personalized small businesses or school or univetsity class networks, where the file metaphor and some customized commands would be extremely useful.

But I also think it should close automatically or this should be at least configurable.

If it closes automatically, I can delay the closing with a small terminal dialog ("press enter or click here"), but if it stays open, I cannot force-close it from the terminal.

ulno commented 5 years ago

Looks nice, but my lack of experience with nodejs and npm is getting the better of me again.

I am able to start the git-branch version of cloudcmd with npm run build:start:dev, but I cannot give this any parameters. Running npm run build:start:dev -- --help gives me the help of madrun.

Any hints how I can give this --user-menu?

coderaiser commented 5 years ago

After building with npm run build:client:dev, start with NODE_ENV=development bin/cloudcmd.js —user-menu

coderaiser commented 5 years ago

But I also think it should close automatically or this should be at least configurable.

If it closes automatically, I can delay the closing with a small terminal dialog ("press enter or click > here"), but if it stays open, I cannot force-close it from the terminal.

I think this can be possible. All we need is modify gritty to add support of this.

ulno commented 5 years ago

After building with npm run build:client:dev, start with NODE_ENV=development bin/cloudcmd.js --user-menu

That works well. Very nice! I think, I kind of like the double F2 press for rename idea - maybe shift-f2 as short cut to just rename without user-menu?

In user-menu, I tried running the build development as example and that shows the problem not using a terminal directly going into hiding for a while and then suddenly showing a huge dialog with lots of tagged output (though getting the result of a short running command in a dialog is cool too!).

So what is needed to have the option of running things in a potentially closing terminal? Why would it be needed to patch gritty? With the formerly mentioned patches that was already working? Is it an issue in having two instances of gritty running at the same time? Or did you mean patching gritty to allow a force-close with a shell command - does that make sense if we just need it to be allowed to be closed with destroying its view after the command in there finishes?

coderaiser commented 5 years ago

Just updated feature/user-menu branch. Here is how .cloudcmd.menu.js can look like:

module.exports = {
    'F2 - Rename file': ({DOM}) => {
        const {element} = DOM.CurrentInfo;
        DOM.renameCurrent(element);
    },
    'D - Build Dev': ({CloudCmd}) => {
        CloudCmd.TerminalRun.show({
            command: 'npm run build:client:dev',
        });
    },
    'P - Build Prod': ({CloudCmd}) => {
        CloudCmd.TerminalRun.show({
            command: 'npm run build:client',
        });
    },
};

You have CloudCmd.TerminalRun, when you need run something in terminal you can just use it. When command exit terminal will show: Press any key to close Terminal, and close it. So you can see all the output of a program. Also there is possibility to use other inner modules of Cloud Commander in the similar way :).

So what is needed to have the option of running things in a potentially closing terminal? Why would it be needed to patch gritty? With the formerly mentioned patches that was already working? Is it an issue in having two instances of gritty running at the same time? Or did you mean patching gritty to allow a force-close with a shell command - does that make sense if we just need it to be allowed to be closed with destroying its view after the command in there finishes?

Also updated gritty to get things working. Now it can accept command, and autoRestart at the moment of creating terminal.

What do you think about it?

ulno commented 5 years ago

I think it's great - and I would be willing to start using it already as is for production environment.

However, there are some small things that should still be considered:

coderaiser commented 5 years ago

An option for auto-close of the terminal after command finished (not having the "press any key"-message or allowing you to define your own closing message - my users want to press a button to close the window - yes I know the small x or ESC work, but some might like a terminal button)

It is a good point. I added this and now it is possible with closeMessage and autoClose options.

If there is no .cloudcmd.menu.json maybe the f2 behavior should be the old one where you press it only once? Or/and we should be able to specify a default .cloudcmd.menu.json file that takes effect when no local one is specified? Or/and you could crawcloudcmd-usermenul up the directory structure and use a potential one of a higher directory?

This is not obvious behavior to use f2 as rename if no .cludcmd.menu.js. I think f2 should be for rename or user-menu only (depending on option used) . About different paths, the idea is to have ~/.cloudcmd.menu.js and ./.cloudcmd.menu.js, when the last one is exist, it will be read. You said that you using shift-f6 for rename in mc, we can add shift-f6 for rename as well, when user menu enabled.

(This is a bit more major) Also, I think clicking on a user-menu entry with the mouse should directly execute it and not just select it (or there should be an ok and cancel button or double click needs to work for execution) - maybe same behavior as the right click-menu would be nice?

It is a good point about double click, about right click I have no idea for now :). Maybe in a future we will add something.

Here is how .cloudcmd.menu.js can look like with all changes:

module.exports = {
    'F2 - Rename file': async ({DOM}) => {
        await DOM.renameCurrent();
    },
    'D - Build Dev': async ({CloudCmd}) => {
        await CloudCmd.TerminalRun.show({
            command: 'npm run build:client:dev',
            closeMessage: 'Press any button to close Terminal',
        });

         CloudCmd.refresh();
    },
    'P - Build Prod': async ({CloudCmd}) => {
        await CloudCmd.TerminalRun.show({
            command: 'npm run build:client',
            autoClose: true,
        });

        CloudCmd.refresh();
    },
};
coderaiser commented 5 years ago

User Menu landed in v12.2.0 🎉. @ulno would be great if you help to improve documentation and tests similar to eb4f7c0d7cba6df68807005d49b085b4edbf5e68.

coderaiser commented 5 years ago

Added User Menu Cookbook, you can add your own scripts, as examples 🙂.