├── .gitignore ├── README.md ├── app ├── HELP.md ├── HOW-TO-INSTALL.txt ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── web │ │ │ │ └── console │ │ │ │ └── app │ │ │ │ ├── AppContext.java │ │ │ │ ├── Application.java │ │ │ │ ├── SinglePageAppController.java │ │ │ │ ├── config │ │ │ │ └── ConfigManager.java │ │ │ │ ├── controllers │ │ │ │ ├── AppController.java │ │ │ │ ├── FilesApiController.java │ │ │ │ ├── HealthApiController.java │ │ │ │ └── SearchApiController.java │ │ │ │ ├── files │ │ │ │ ├── BinaryDataController.java │ │ │ │ ├── FileInfo.java │ │ │ │ ├── FileOperations.java │ │ │ │ ├── FileService.java │ │ │ │ ├── FileTransfer.java │ │ │ │ ├── FileTypeDetector.java │ │ │ │ ├── PosixPermission.java │ │ │ │ ├── SearchOperations.java │ │ │ │ ├── copy │ │ │ │ │ ├── FileCopyProgressResponse.java │ │ │ │ │ ├── FileCopyRequest.java │ │ │ │ │ └── FileCopyTask.java │ │ │ │ └── search │ │ │ │ │ ├── SearchResult.java │ │ │ │ │ └── SearchTask.java │ │ │ │ ├── health │ │ │ │ ├── ProcessInfo.java │ │ │ │ ├── SystemHealthMonitor.java │ │ │ │ └── SystemStats.java │ │ │ │ ├── security │ │ │ │ ├── CustomAuthEntryPoint.java │ │ │ │ ├── JwtAuthorizationFilter.java │ │ │ │ ├── JwtTokenController.java │ │ │ │ └── WebshellUserDetailsService.java │ │ │ │ └── terminal │ │ │ │ ├── PtyProcessPipe.java │ │ │ │ ├── PtySession.java │ │ │ │ ├── SshPtyProcess.java │ │ │ │ ├── SshPtyProcessPipe.java │ │ │ │ └── TerminalWebsocketHandler.java │ │ └── resources │ │ │ └── application.properties │ └── test │ │ └── java │ │ └── cloudshell │ │ └── app │ │ └── AppApplicationTests.java └── start-web-console.sh └── ui └── web-console ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── package-lock.json ├── package.json ├── src ├── app │ ├── app-routing.module.ts │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── data.service.spec.ts │ ├── data.service.ts │ ├── guards │ │ ├── auth-guard.guard.spec.ts │ │ └── auth-guard.guard.ts │ ├── home │ │ ├── editor │ │ │ ├── editor.component.css │ │ │ ├── editor.component.html │ │ │ ├── editor.component.spec.ts │ │ │ └── editor.component.ts │ │ ├── files │ │ │ ├── browser │ │ │ │ ├── browser.component.css │ │ │ │ ├── browser.component.html │ │ │ │ ├── browser.component.spec.ts │ │ │ │ ├── browser.component.ts │ │ │ │ ├── info │ │ │ │ │ ├── info.component.css │ │ │ │ │ ├── info.component.html │ │ │ │ │ ├── info.component.spec.ts │ │ │ │ │ └── info.component.ts │ │ │ │ ├── new-item │ │ │ │ │ ├── new-item.component.css │ │ │ │ │ ├── new-item.component.html │ │ │ │ │ ├── new-item.component.spec.ts │ │ │ │ │ └── new-item.component.ts │ │ │ │ └── rename │ │ │ │ │ ├── rename.component.css │ │ │ │ │ ├── rename.component.html │ │ │ │ │ ├── rename.component.spec.ts │ │ │ │ │ └── rename.component.ts │ │ │ ├── files.component.css │ │ │ ├── files.component.html │ │ │ ├── files.component.spec.ts │ │ │ ├── files.component.ts │ │ │ ├── tree │ │ │ │ ├── tree.component.css │ │ │ │ ├── tree.component.html │ │ │ │ ├── tree.component.spec.ts │ │ │ │ └── tree.component.ts │ │ │ └── viewer │ │ │ │ ├── image-viewer │ │ │ │ ├── image-viewer.component.css │ │ │ │ ├── image-viewer.component.html │ │ │ │ ├── image-viewer.component.spec.ts │ │ │ │ └── image-viewer.component.ts │ │ │ │ ├── media-player │ │ │ │ ├── media-player.component.css │ │ │ │ ├── media-player.component.html │ │ │ │ ├── media-player.component.spec.ts │ │ │ │ └── media-player.component.ts │ │ │ │ └── unsupported-content-viewer │ │ │ │ ├── unsupported-content-viewer.component.css │ │ │ │ ├── unsupported-content-viewer.component.html │ │ │ │ ├── unsupported-content-viewer.component.spec.ts │ │ │ │ └── unsupported-content-viewer.component.ts │ │ ├── home.component.css │ │ ├── home.component.html │ │ ├── home.component.spec.ts │ │ ├── home.component.ts │ │ ├── monitoring │ │ │ ├── monitoring.component.css │ │ │ ├── monitoring.component.html │ │ │ ├── monitoring.component.spec.ts │ │ │ └── monitoring.component.ts │ │ ├── search │ │ │ ├── search.component.css │ │ │ ├── search.component.html │ │ │ ├── search.component.spec.ts │ │ │ └── search.component.ts │ │ ├── settings │ │ │ ├── settings.component.css │ │ │ ├── settings.component.html │ │ │ ├── settings.component.spec.ts │ │ │ └── settings.component.ts │ │ ├── terminal │ │ │ ├── terminal.component.css │ │ │ ├── terminal.component.html │ │ │ ├── terminal.component.spec.ts │ │ │ └── terminal.component.ts │ │ └── uploader-progress │ │ │ ├── uploader-progress.component.css │ │ │ ├── uploader-progress.component.html │ │ │ ├── uploader-progress.component.spec.ts │ │ │ └── uploader-progress.component.ts │ ├── intercepters │ │ └── auth.ts │ ├── login │ │ ├── login.component.css │ │ ├── login.component.html │ │ ├── login.component.spec.ts │ │ └── login.component.ts │ ├── model │ │ ├── editor-context.ts │ │ ├── file-info.ts │ │ ├── file-item.ts │ │ ├── file-operation-item.ts │ │ ├── file-upload-item.ts │ │ ├── folder-tab.ts │ │ ├── folder-upload-item.ts │ │ ├── navigation-tree-node.ts │ │ ├── posix-permissions.ts │ │ ├── search-context.ts │ │ ├── tab-item.ts │ │ └── terminal-session.ts │ └── utility │ │ └── utils.ts ├── assets │ ├── .gitkeep │ ├── file.png │ ├── files.json │ ├── folder.png │ └── logo.png ├── browserslist ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See http://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # compiled output 5 | ui/dist 6 | ui/tmp 7 | ui/out-tsc 8 | 9 | # dependencies 10 | ui/node_modules 11 | 12 | # profiling files 13 | ui/chrome-profiler-events.json 14 | ui/speed-measure-plugin.json 15 | 16 | # IDEs and editors 17 | ui/.idea 18 | ui.project 19 | ui.classpath 20 | ui.c9/ 21 | ui*.launch 22 | ui.settings/ 23 | ui*.sublime-workspace 24 | 25 | 26 | 27 | # misc 28 | ui/.sass-cache 29 | ui/connect.lock 30 | ui/coverage 31 | ui/libpeerconnection.log 32 | uinpm-debug.log 33 | uiyarn-error.log 34 | uitestem.log 35 | ui/typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | app/.mvn 42 | app/.settings/ 43 | app/.classpath 44 | app/.project 45 | app/target/ 46 | 47 | app/src/main/resources/static 48 | 49 | ui/package-lock.json 50 | app/cloud-shell-bin.tar 51 | app/cloud-shell-bin.tar.gz 52 | app/cloud-shell.jar 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Linux Web Console 2 | 3 | Easy to use web based file manager with built in text editor, terminal, image viewer and video player 4 | 5 |

What is it and how can it be useful to you?

6 |

7 | It is an web based file manager and terminal emulator, with some built in tools like tabbed text editor, search and image/video player. 8 | It can be useful to you if you want to access/manage/deploy apps on a remote linux server from your desktop and mobile browser. You can create/edit/manage/search files in multiple tabs, 9 | run commands using built in terminal, and view images and videos, all from your browser. Setting it up on server is also very easy, as it generates and configure itself to use self signed certificate for ssl. 10 |

11 | 12 |

13 | 14 | 15 | 16 | 17 | 18 |

19 | 20 | Installation 21 | - Install java 22 | - Download the binary archive from https://github.com/subhra74/linux-web-console/releases 23 | - Extract the archive to a suitable location 24 | - Make file executable with chmod 25 | - Run ./web-console.sh (For security reasons do not use root user) 26 | - Open recent version on Chrome or Firefox and visit https://[your ip address]:8055/ or, on local machine use https://localhost:8055/ 27 | - Ignore any certificate error, appeared due to newly created self signed certificate by linux web console. 28 | - Initial credential: 29 | Username: admin 30 | Password: admin 31 | Please change default username and password from Settings tab in the app 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) 7 | 8 | ### Guides 9 | The following guides illustrate how to use some features concretely: 10 | 11 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 12 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 13 | * [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) 14 | * [Using WebSocket to build an interactive web application](https://spring.io/guides/gs/messaging-stomp-websocket/) 15 | 16 | -------------------------------------------------------------------------------- /app/HOW-TO-INSTALL.txt: -------------------------------------------------------------------------------- 1 | 1. Install java and make sure java is in PATH 2 | 2. Download the binary archive from https://github.com/subhra74/linux-web-console/releases 3 | 3. Extract the archive to a suitable location and switch to that directory 4 | 4. Make file executable with chmod if required 5 | 5. Run ./start-web-console.sh (For security reasons do not use root user) 6 | 6. Open recent version on Chrome or Firefox and visit https://[your ip address]:8055/ or, on local machine use https://localhost:8055/ 7 | 7. Ignore any certificate error, appeared due to newly created self signed certificate by the app. 8 | 8. Initial credential: admin/admin 9 | Please change default username and password from Settings tab in the app. 10 | Also you can change default port by setting environment variable server.port= -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/app/README.md -------------------------------------------------------------------------------- /app/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.1.5.RELEASE 10 | 11 | 12 | web-console 13 | app 14 | 0.0.1-SNAPSHOT 15 | app 16 | Demo project for Spring Boot 17 | 18 | 19 | 1.8 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-web 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-websocket 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-devtools 35 | runtime 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | test 41 | 42 | 43 | 44 | org.jetbrains.pty4j 45 | pty4j 46 | 0.9.3 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-starter-security 58 | 59 | 60 | io.jsonwebtoken 61 | jjwt-api 62 | 0.10.5 63 | 64 | 65 | io.jsonwebtoken 66 | jjwt-impl 67 | 0.10.5 68 | runtime 69 | 70 | 71 | io.jsonwebtoken 72 | jjwt-jackson 73 | 0.10.5 74 | runtime 75 | 76 | 77 | com.jcraft 78 | jsch 79 | 0.1.54 80 | 81 | 82 | net.java.dev.jna 83 | jna-platform 84 | 5.3.1 85 | 86 | 87 | net.java.dev.jna 88 | jna 89 | 5.3.1 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | org.bouncycastle 115 | bcprov-jdk15on 116 | 1.62 117 | 118 | 119 | 120 | org.bouncycastle 121 | bcpkix-jdk15on 122 | 1.62 123 | 124 | 125 | 126 | org.apache.tika 127 | tika-core 128 | 1.21 129 | 130 | 131 | 132 | com.github.oshi 133 | oshi-dist 134 | 3.13.3 135 | pom 136 | 137 | 138 | 139 | com.github.oshi 140 | oshi-core 141 | 3.13.3 142 | 143 | 144 | 145 | com.github.oshi 146 | oshi-json 147 | 3.13.3 148 | 149 | 150 | 151 | com.github.oshi 152 | oshi-parent 153 | 3.13.3 154 | pom 155 | 156 | 157 | 158 | 159 | 160 | 161 | bintray-jetbrains-pty4j 162 | bintray 163 | https://jetbrains.bintray.com/pty4j 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | org.springframework.boot 172 | spring-boot-maven-plugin 173 | 174 | 175 | web-console 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/AppContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app; 5 | 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | 9 | import web.console.app.terminal.PtySession; 10 | 11 | /** 12 | * @author subhro 13 | * 14 | */ 15 | 16 | public class AppContext { 17 | public static final Map INSTANCES = new ConcurrentHashMap<>(); 18 | public static final Map SESSION_MAP = new ConcurrentHashMap<>(); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/Application.java: -------------------------------------------------------------------------------- 1 | package web.console.app; 2 | 3 | import java.security.Security; 4 | import java.util.Collections; 5 | 6 | import javax.crypto.SecretKey; 7 | 8 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.CommandLineRunner; 11 | import org.springframework.boot.SpringApplication; 12 | import org.springframework.boot.autoconfigure.SpringBootApplication; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.core.env.Environment; 15 | import org.springframework.security.authentication.AuthenticationProvider; 16 | import org.springframework.security.authentication.BadCredentialsException; 17 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 18 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 19 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 20 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 21 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 22 | import org.springframework.security.config.http.SessionCreationPolicy; 23 | import org.springframework.security.core.Authentication; 24 | import org.springframework.security.core.AuthenticationException; 25 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 26 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 27 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 28 | import org.springframework.web.socket.config.annotation.EnableWebSocket; 29 | import org.springframework.web.socket.config.annotation.WebSocketConfigurer; 30 | import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; 31 | 32 | import io.jsonwebtoken.SignatureAlgorithm; 33 | import io.jsonwebtoken.security.Keys; 34 | import web.console.app.config.ConfigManager; 35 | import web.console.app.security.CustomAuthEntryPoint; 36 | import web.console.app.security.JwtAuthorizationFilter; 37 | import web.console.app.terminal.TerminalWebsocketHandler; 38 | 39 | @EnableWebSecurity 40 | @EnableWebSocket 41 | @SpringBootApplication 42 | public class Application extends WebSecurityConfigurerAdapter 43 | implements WebSocketConfigurer, WebMvcConfigurer, CommandLineRunner { 44 | 45 | @Autowired 46 | private Environment env; 47 | 48 | static { 49 | // adds the Bouncy castle provider to java security 50 | Security.addProvider(new BouncyCastleProvider()); 51 | ConfigManager.checkAndConfigureSSL(); 52 | } 53 | 54 | public static final SecretKey SECRET_KEY = Keys 55 | .secretKeyFor(SignatureAlgorithm.HS256); 56 | 57 | @Autowired 58 | private CustomAuthEntryPoint auth; 59 | 60 | public static void main(String[] args) throws Exception { 61 | SpringApplication.run(Application.class, args); 62 | } 63 | 64 | @Override 65 | public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { 66 | System.out.println("Registered ws"); 67 | registry.addHandler(new TerminalWebsocketHandler(), "/term*") 68 | .setAllowedOrigins("*");// .withSockJS(); 69 | } 70 | 71 | @Override 72 | public void addCorsMappings(CorsRegistry registry) { 73 | registry.addMapping("/**"); 74 | } 75 | 76 | @Override 77 | protected void configure(HttpSecurity http) throws Exception { 78 | http.httpBasic().authenticationEntryPoint(auth).and().cors().and() 79 | .csrf().disable().authorizeRequests().antMatchers("/term**") 80 | .permitAll().and().authorizeRequests().antMatchers("/bin/**") 81 | .permitAll().and().authorizeRequests().antMatchers("/api/**") 82 | .authenticated().and().authorizeRequests().antMatchers("/**") 83 | .permitAll().and() 84 | .addFilter(new JwtAuthorizationFilter(authenticationManager())) 85 | .sessionManagement() 86 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 87 | } 88 | 89 | @Bean 90 | public BCryptPasswordEncoder passwordEncoder() { 91 | return new BCryptPasswordEncoder(); 92 | } 93 | 94 | @Override 95 | public void configure(AuthenticationManagerBuilder auth) throws Exception { 96 | 97 | AuthenticationProvider provider = new AuthenticationProvider() { 98 | 99 | @Override 100 | public boolean supports(Class authentication) { 101 | return authentication == (UsernamePasswordAuthenticationToken.class); 102 | } 103 | 104 | @Override 105 | public Authentication authenticate(Authentication authentication) 106 | throws AuthenticationException { 107 | String user = authentication.getName(); 108 | String pass = authentication.getCredentials().toString(); 109 | 110 | if (user.equals(System.getProperty("app.default-user")) 111 | && passwordEncoder().matches(pass, 112 | System.getProperty("app.default-pass"))) { 113 | return new UsernamePasswordAuthenticationToken(user, pass, 114 | Collections.emptyList()); 115 | } else { 116 | throw new BadCredentialsException( 117 | "External system authentication failed"); 118 | } 119 | } 120 | }; 121 | auth.authenticationProvider(provider); 122 | // auth.authenticationProvider(new UserDetailsService() { 123 | // 124 | // @Override 125 | // public UserDetails loadUserByUsername(String username) 126 | // throws UsernameNotFoundException { 127 | // // TODO Auto-generated method stub 128 | // return null; 129 | // } 130 | // }); 131 | // auth.inMemoryAuthentication() 132 | // .withUser(System.getProperty("app.default-user")) 133 | // .password("{noop}" + System.getProperty("app.default-pass")) 134 | // .authorities("ROLE_USER"); 135 | } 136 | 137 | @Override 138 | public void run(String... args) throws Exception { 139 | ConfigManager.loadUserDetails(env); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/SinglePageAppController.java: -------------------------------------------------------------------------------- 1 | package web.console.app; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | 6 | @Controller 7 | public class SinglePageAppController { 8 | @RequestMapping(value = { "/", "/app/**", "/login" }) 9 | public String index() { 10 | return "/index.html"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/controllers/AppController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.controllers; 5 | 6 | import java.io.IOException; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 12 | import org.springframework.web.bind.annotation.CrossOrigin; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.PostMapping; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | import web.console.app.AppContext; 21 | import web.console.app.config.ConfigManager; 22 | import web.console.app.terminal.PtySession; 23 | 24 | /** 25 | * @author subhro 26 | * 27 | */ 28 | @RestController 29 | @CrossOrigin(allowCredentials = "true") 30 | @RequestMapping("/api") 31 | public class AppController { 32 | 33 | @Autowired 34 | private BCryptPasswordEncoder passwordEncoder; 35 | 36 | @PostMapping("/app/terminal/{appId}/resize") 37 | public void resizePty(@PathVariable String appId, 38 | @RequestBody Map body) { 39 | AppContext.INSTANCES.get(appId).resizePty(body.get("row"), 40 | body.get("col")); 41 | } 42 | 43 | @PostMapping("/app/terminal") 44 | public Map createTerminal() throws Exception { 45 | PtySession pty = new PtySession(); 46 | AppContext.INSTANCES.put(pty.getId(), pty); 47 | Map map = new HashMap(); 48 | map.put("id", pty.getId()); 49 | return map; 50 | } 51 | 52 | @GetMapping("/app/config") 53 | public Map getConfig() { 54 | Map map = new HashMap<>(); 55 | map.put("app.default-user", System.getProperty("app.default-user")); 56 | map.put("app.default-pass", System.getProperty("app.default-pass")); 57 | map.put("app.default-shell", System.getProperty("app.default-shell")); 58 | return map; 59 | } 60 | 61 | @PostMapping("/app/config") 62 | public void setConfig(@RequestBody Map map) 63 | throws IOException { 64 | for (String key : map.keySet()) { 65 | String val = map.get(key); 66 | if (val != null && val.length() > 0) { 67 | if (key.equals("app.default-pass")) { 68 | System.setProperty(key, passwordEncoder.encode(val)); 69 | } else { 70 | System.setProperty(key, val); 71 | } 72 | } 73 | } 74 | ConfigManager.saveUserDetails(); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/controllers/HealthApiController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.controllers; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.web.bind.annotation.CrossOrigin; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | import web.console.app.health.ProcessInfo; 19 | import web.console.app.health.SystemHealthMonitor; 20 | import web.console.app.health.SystemStats; 21 | 22 | /** 23 | * @author subhro 24 | * 25 | */ 26 | @RestController 27 | @CrossOrigin(allowCredentials = "true") 28 | @RequestMapping("/api/app") 29 | public class HealthApiController { 30 | 31 | @Autowired 32 | private SystemHealthMonitor healthMon; 33 | 34 | @GetMapping("/sys/stats") 35 | public SystemStats getStats() { 36 | return this.healthMon.getStats(); 37 | } 38 | 39 | @GetMapping("/sys/procs") 40 | public List getProcessList() { 41 | return this.healthMon.getProcessList(); 42 | } 43 | 44 | @PostMapping("/sys/procs") 45 | public Map killProcesses( 46 | @RequestBody List pidList) { 47 | Map map = new HashMap<>(); 48 | map.put("success", this.healthMon.killProcess(pidList)); 49 | return map; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/controllers/SearchApiController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.controllers; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.web.bind.annotation.CrossOrigin; 11 | import org.springframework.web.bind.annotation.DeleteMapping; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | import org.springframework.web.bind.annotation.RequestBody; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestParam; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | import web.console.app.files.FileService; 21 | import web.console.app.files.search.SearchResult; 22 | 23 | /** 24 | * @author subhro 25 | * 26 | */ 27 | @RestController 28 | @CrossOrigin(allowCredentials = "true") 29 | @RequestMapping("/api/app/fs/search") 30 | public class SearchApiController { 31 | 32 | @Autowired 33 | private FileService service; 34 | 35 | @PostMapping("/") 36 | public Map initSearch( 37 | @RequestBody Map request) { 38 | String id = service.createSearch(request.get("folder"), 39 | request.get("searchText")); 40 | Map response = new HashMap(); 41 | response.put("id", id); 42 | return response; 43 | } 44 | 45 | @DeleteMapping("/{id}") 46 | public void cancelSearch(@PathVariable String id) { 47 | this.service.cancelSearch(id); 48 | } 49 | 50 | @GetMapping("/{id}") 51 | public SearchResult getSearchResult(@PathVariable String id, 52 | @RequestParam(defaultValue = "0", required = false) int fileIndex, 53 | @RequestParam(defaultValue = "0", required = false) int folderIndex) { 54 | return this.service.getSearchResult(id, fileIndex, folderIndex); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/BinaryDataController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files; 5 | 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.nio.file.Files; 11 | import java.nio.file.Paths; 12 | import java.util.ArrayList; 13 | import java.util.Base64; 14 | import java.util.List; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | import javax.servlet.http.HttpServletResponse; 18 | 19 | import org.springframework.beans.factory.annotation.Autowired; 20 | import org.springframework.http.HttpHeaders; 21 | import org.springframework.http.HttpStatus; 22 | import org.springframework.http.ResponseEntity; 23 | import org.springframework.util.MultiValueMap; 24 | import org.springframework.web.bind.annotation.CrossOrigin; 25 | import org.springframework.web.bind.annotation.GetMapping; 26 | import org.springframework.web.bind.annotation.PathVariable; 27 | import org.springframework.web.bind.annotation.RequestMapping; 28 | import org.springframework.web.bind.annotation.RequestParam; 29 | import org.springframework.web.bind.annotation.RestController; 30 | 31 | import io.jsonwebtoken.Claims; 32 | import io.jsonwebtoken.Jws; 33 | import io.jsonwebtoken.Jwts; 34 | import web.console.app.Application; 35 | 36 | /** 37 | * @author subhro 38 | * 39 | */ 40 | @RestController 41 | @CrossOrigin(allowCredentials = "true") 42 | @RequestMapping("/bin") 43 | public class BinaryDataController { 44 | 45 | @Autowired 46 | private FileTypeDetector typeDetector; 47 | 48 | private void validateToken(String token) throws Exception { 49 | Jws parsedToken = Jwts.parser() 50 | .setSigningKey(Application.SECRET_KEY).parseClaimsJws(token); 51 | 52 | String username = parsedToken.getBody().getSubject(); 53 | 54 | if (username != null && username.length() > 0 55 | && username.equals(System.getProperty("app.default-user"))) { 56 | System.out.println("Token and user is valid"); 57 | return; 58 | } 59 | 60 | throw new Exception("Invalid username: " + username); 61 | } 62 | 63 | @GetMapping("/image/{encodedPath}") 64 | public ResponseEntity getImage(@PathVariable String encodedPath, 65 | @RequestParam String token) throws Exception { 66 | validateToken(token); 67 | String path = new String(Base64.getDecoder().decode(encodedPath)); 68 | byte[] b = Files.readAllBytes(Paths.get(path)); 69 | MultiValueMap headers = new HttpHeaders(); 70 | headers.add("Content-Type", getImageType(path)); 71 | ResponseEntity resp = new ResponseEntity(b, headers, 72 | HttpStatus.OK); 73 | return resp; 74 | } 75 | 76 | @GetMapping("/blob/{encodedPath}") 77 | public void getBlob(@PathVariable String encodedPath, 78 | @RequestParam String token, HttpServletRequest request, 79 | HttpServletResponse resp) throws Exception { 80 | validateToken(token); 81 | String path = new String(Base64.getDecoder().decode(encodedPath)); 82 | System.out.println("Get file: " + path); 83 | long fileSize = Files.size(Paths.get(path)); 84 | String r = request.getHeader("Range"); 85 | long upperBound = fileSize - 1; 86 | long lowerBound = 0; 87 | if (r != null) { 88 | System.out.println(r); 89 | String rstr = r.split("=")[1]; 90 | String arr[] = rstr.split("-"); 91 | lowerBound = Long.parseLong(arr[0]); 92 | if (arr.length > 1 && arr[1].length() > 0) { 93 | upperBound = Long.parseLong(arr[1]); 94 | } 95 | } 96 | 97 | System.out.println("lb: " + lowerBound + " ub: " + fileSize); 98 | 99 | if (r != null) { 100 | resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); 101 | } 102 | resp.addHeader("Content-Length", (fileSize - lowerBound) + ""); 103 | resp.addHeader("Content-Range", 104 | "bytes " + lowerBound + "-" + upperBound + "/" + fileSize);// (fileSize 105 | // - 106 | // lowerBound) 107 | // + 108 | // ""); 109 | resp.addHeader("Content-Type", typeDetector.getType(new File(path))); 110 | long rem = fileSize - lowerBound; 111 | try (InputStream in = new FileInputStream(path)) { 112 | in.skip(lowerBound); 113 | byte[] b = new byte[8192]; 114 | while (rem > 0) { 115 | int x = in.read(b, 0, (int) (rem > b.length ? b.length : rem)); 116 | if (x == -1) 117 | break; 118 | resp.getOutputStream().write(b, 0, x); 119 | rem -= x; 120 | } 121 | } 122 | // Files.copy(Paths.get(path), resp.getOutputStream()); 123 | } 124 | 125 | @GetMapping("/download") 126 | public void downloadFiles( 127 | @RequestParam(name = "folder") String encodedFolder, 128 | @RequestParam(name = "files", required = false) String encodedFiles, 129 | @RequestParam String token, HttpServletResponse response) 130 | throws Exception { 131 | validateToken(token); 132 | int fileCount = 0; 133 | 134 | String folder = new String(Base64.getDecoder().decode(encodedFolder), 135 | "utf-8"); 136 | 137 | List fileList = new ArrayList<>(); 138 | 139 | if (encodedFiles != null) { 140 | String files = new String(Base64.getDecoder().decode(encodedFiles), 141 | "utf-8"); 142 | String[] arr1 = files.split("/"); 143 | if (arr1 != null && arr1.length > 0) { 144 | fileCount = arr1.length; 145 | for (String str : arr1) { 146 | fileList.add(new File(folder, str).getAbsolutePath()); 147 | } 148 | } 149 | } 150 | 151 | if (fileList.size() == 0) { 152 | throw new IOException(); 153 | } 154 | 155 | String name = "download.zip"; 156 | 157 | boolean compress = false; 158 | 159 | if (fileCount == 1) { 160 | File f = new File(fileList.get(0)); 161 | name = f.getName(); 162 | compress = f.isDirectory(); 163 | if (name.length() < 1) { 164 | name = "files"; 165 | } 166 | } else { 167 | compress = true; 168 | } 169 | 170 | response.addHeader("Content-Disposition", 171 | "attachment; filename=\"" + name + ".zip" + "\""); 172 | response.setContentType("application/octet-stream"); 173 | FileTransfer fs = new FileTransfer(compress); 174 | fs.transferFiles(fileList, response.getOutputStream()); 175 | } 176 | 177 | /** 178 | * @param path 179 | * @return 180 | */ 181 | private String getImageType(String path) { 182 | return typeDetector.getTypeByName(path); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/FileInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | * @author subhro 10 | * 11 | */ 12 | public class FileInfo { 13 | 14 | private String name, path, permissionString, user, type; 15 | private long size; 16 | private Date lastModified; 17 | private boolean posix; 18 | 19 | /** 20 | * @param name 21 | * @param path 22 | * @param permissionString 23 | * @param user 24 | * @param type 25 | * @param size 26 | * @param permission 27 | * @param lastModified 28 | */ 29 | public FileInfo(String name, String path, String permissionString, 30 | String user, String type, long size, long permission, 31 | Date lastModified, boolean posix) { 32 | super(); 33 | this.name = name; 34 | this.path = path; 35 | this.permissionString = permissionString; 36 | this.user = user; 37 | this.type = type; 38 | this.size = size; 39 | this.lastModified = lastModified; 40 | this.posix = posix; 41 | } 42 | 43 | /** 44 | * @return the name 45 | */ 46 | public String getName() { 47 | return name; 48 | } 49 | 50 | /** 51 | * @param name the name to set 52 | */ 53 | public void setName(String name) { 54 | this.name = name; 55 | } 56 | 57 | /** 58 | * @return the path 59 | */ 60 | public String getPath() { 61 | return path; 62 | } 63 | 64 | /** 65 | * @param path the path to set 66 | */ 67 | public void setPath(String path) { 68 | this.path = path; 69 | } 70 | 71 | /** 72 | * @return the permissionString 73 | */ 74 | public String getPermissionString() { 75 | return permissionString; 76 | } 77 | 78 | /** 79 | * @param permissionString the permissionString to set 80 | */ 81 | public void setPermissionString(String permissionString) { 82 | this.permissionString = permissionString; 83 | } 84 | 85 | /** 86 | * @return the user 87 | */ 88 | public String getUser() { 89 | return user; 90 | } 91 | 92 | /** 93 | * @param user the user to set 94 | */ 95 | public void setUser(String user) { 96 | this.user = user; 97 | } 98 | 99 | /** 100 | * @return the type 101 | */ 102 | public String getType() { 103 | return type; 104 | } 105 | 106 | /** 107 | * @param type the type to set 108 | */ 109 | public void setType(String type) { 110 | this.type = type; 111 | } 112 | 113 | /** 114 | * @return the size 115 | */ 116 | public long getSize() { 117 | return size; 118 | } 119 | 120 | /** 121 | * @param size the size to set 122 | */ 123 | public void setSize(long size) { 124 | this.size = size; 125 | } 126 | 127 | /** 128 | * @return the lastModified 129 | */ 130 | public Date getLastModified() { 131 | return lastModified; 132 | } 133 | 134 | /** 135 | * @param lastModified the lastModified to set 136 | */ 137 | public void setLastModified(Date lastModified) { 138 | this.lastModified = lastModified; 139 | } 140 | 141 | /** 142 | * @return the posix 143 | */ 144 | public boolean isPosix() { 145 | return posix; 146 | } 147 | 148 | /** 149 | * @param posix the posix to set 150 | */ 151 | public void setPosix(boolean posix) { 152 | this.posix = posix; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/FileService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files; 5 | 6 | import java.io.File; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | import java.nio.file.attribute.FileAttributeView; 10 | import java.nio.file.attribute.FileOwnerAttributeView; 11 | import java.nio.file.attribute.PosixFilePermission; 12 | import java.nio.file.attribute.PosixFilePermissions; 13 | import java.util.ArrayList; 14 | import java.util.Date; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.stereotype.Service; 20 | 21 | import web.console.app.files.search.SearchResult; 22 | 23 | /** 24 | * @author subhro 25 | * 26 | */ 27 | @Service 28 | public class FileService { 29 | 30 | private boolean posix; 31 | 32 | /** 33 | * 34 | */ 35 | public FileService() { 36 | posix = !System.getProperty("os.name").toLowerCase() 37 | .contains("windows"); 38 | } 39 | 40 | @Autowired 41 | private SearchOperations searchOp; 42 | 43 | @Autowired 44 | private FileTypeDetector typeDetector; 45 | 46 | public String createSearch(String folder, String searchText) { 47 | return searchOp.createSearchTask(folder, searchText); 48 | } 49 | 50 | public void cancelSearch(String id) { 51 | searchOp.cancelSearch(id); 52 | } 53 | 54 | public SearchResult getSearchResult(String id, int fileIndex, 55 | int folderIndex) { 56 | return searchOp.getSearchResult(id, fileIndex, folderIndex); 57 | } 58 | 59 | private String getFileType(File f) { 60 | return typeDetector.getType(f); 61 | } 62 | 63 | public List list(String path) { 64 | System.out.println("Listing file: " + path); 65 | File[] files = new File(path).listFiles(); 66 | List list = new ArrayList<>(); 67 | if (files == null) { 68 | return list; 69 | } 70 | for (File f : files) { 71 | FileInfo info = new FileInfo(f.getName(), f.getAbsolutePath(), null, 72 | null, f.isDirectory() ? "Directory" : getFileType(f), 73 | f.length(), -1, new Date(f.lastModified()), posix); 74 | Set filePerm = null; 75 | try { 76 | filePerm = Files.getPosixFilePermissions(f.toPath()); 77 | String permission = PosixFilePermissions.toString(filePerm); 78 | info.setPermissionString(permission); 79 | 80 | FileOwnerAttributeView fv = Files.getFileAttributeView( 81 | f.toPath(), FileOwnerAttributeView.class); 82 | info.setUser(fv.getOwner().getName()); 83 | } catch (Exception e) { 84 | // e.printStackTrace(); 85 | info.setPermissionString("---"); 86 | } 87 | 88 | list.add(info); 89 | } 90 | list.sort((FileInfo a, FileInfo b) -> { 91 | String type1 = a.getType(); 92 | String type2 = b.getType(); 93 | if (type1.equals("Directory") && type2.equals("Directory")) { 94 | return 0; 95 | } else if (type1.equals("Directory")) { 96 | return -1; 97 | } else if (type2.equals("Directory")) { 98 | return 1; 99 | } else { 100 | return 0; 101 | } 102 | }); 103 | return list; 104 | } 105 | 106 | public void setText(String path, String text) throws Exception { 107 | Files.writeString(Paths.get(path), text); 108 | } 109 | 110 | public String getText(String path) throws Exception { 111 | return new String(Files.readAllBytes(Paths.get(path)), "utf-8"); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/FileTransfer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files; 5 | 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.OutputStream; 12 | import java.util.List; 13 | import java.util.zip.ZipEntry; 14 | import java.util.zip.ZipOutputStream; 15 | 16 | /** 17 | * @author subhro 18 | * 19 | */ 20 | public class FileTransfer { 21 | 22 | private final byte[] BUFFER = new byte[8192]; 23 | private boolean compress; 24 | 25 | /** 26 | * 27 | */ 28 | public FileTransfer(boolean compress) { 29 | this.compress = compress; 30 | } 31 | 32 | public void transferFile(String relativePath, String folder, 33 | InputStream in) { 34 | File f = new File(folder, relativePath); 35 | File parent=f.getParentFile(); 36 | parent.mkdirs(); 37 | System.out.println("Creating folder: "+parent.getAbsolutePath()); 38 | System.out.println("Creating file: "+f.getAbsolutePath()); 39 | try (FileOutputStream out = new FileOutputStream(f)) { 40 | copyStream(in, out); 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | 46 | public void transferFiles(List files, OutputStream out) { 47 | if (!compress) { 48 | try (InputStream in = new FileInputStream(files.get(0))) { 49 | copyStream(in, out); 50 | } catch (Exception e) { 51 | e.printStackTrace(); 52 | } 53 | } else { 54 | try (ZipOutputStream zout = new ZipOutputStream(out)) { 55 | for (String file : files) { 56 | File f = new File(file); 57 | if (f.isDirectory()) { 58 | if (!walkTree(f, zout, f.getName())) { 59 | return; 60 | } 61 | } else { 62 | boolean writing = false; 63 | try (InputStream in = new FileInputStream(f)) { 64 | ZipEntry ze = new ZipEntry(f.getName()); 65 | ze.setSize(f.length()); 66 | writing = true; 67 | zout.putNextEntry(ze); 68 | copyStream(in, zout); 69 | zout.closeEntry(); 70 | writing = false; 71 | } catch (Exception e) { 72 | e.printStackTrace(); 73 | } 74 | if (writing) { 75 | return; 76 | } 77 | } 78 | } 79 | } catch (Exception e) { 80 | e.printStackTrace(); 81 | } 82 | } 83 | } 84 | 85 | private boolean walkTree(File folder, ZipOutputStream zout, 86 | String relativePath) { 87 | System.out.println( 88 | "Walking: " + folder.getAbsolutePath() + " - " + relativePath); 89 | try { 90 | File[] files = folder.listFiles(); 91 | if (files != null) { 92 | for (File f : files) { 93 | if (f.isDirectory()) { 94 | if (!walkTree(f, zout, combine(relativePath, 95 | f.getName(), File.separator))) { 96 | return false; 97 | } 98 | } else { 99 | boolean writing = false; 100 | try (InputStream in = new FileInputStream(f)) { 101 | ZipEntry ze = new ZipEntry(combine(relativePath, 102 | f.getName(), File.separator)); 103 | ze.setSize(f.length()); 104 | writing = true; 105 | zout.putNextEntry(ze); 106 | copyStream(in, zout); 107 | zout.closeEntry(); 108 | writing = false; 109 | } catch (Exception e) { 110 | e.printStackTrace(); 111 | } 112 | if (writing) { 113 | throw new Exception("error reading file: " 114 | + f.getAbsolutePath()); 115 | } 116 | } 117 | } 118 | } 119 | } catch (Exception e) { 120 | e.printStackTrace(); 121 | return false; 122 | } 123 | return true; 124 | } 125 | 126 | private void copyStream(InputStream in, OutputStream out) 127 | throws IOException { 128 | while (true) { 129 | int x = in.read(BUFFER); 130 | if (x == -1) 131 | break; 132 | out.write(BUFFER, 0, x); 133 | } 134 | } 135 | 136 | public String combine(String path1, String path2, String separator) { 137 | if (path2.startsWith(separator)) { 138 | path2 = path2.substring(1); 139 | } 140 | if (!path1.endsWith(separator)) { 141 | return path1 + separator + path2; 142 | } else { 143 | return path1 + path2; 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/FileTypeDetector.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | import org.apache.tika.Tika; 10 | import org.springframework.stereotype.Component; 11 | 12 | /** 13 | * @author subhro 14 | * 15 | */ 16 | @Component 17 | public class FileTypeDetector { 18 | /** 19 | * 20 | */ 21 | private Tika tika; 22 | 23 | public FileTypeDetector() { 24 | tika = new Tika(); 25 | } 26 | 27 | public synchronized String getType(File file) { 28 | try { 29 | return tika.detect(file); 30 | } catch (IOException e) { 31 | // TODO Auto-generated catch block 32 | return "application/octet-stream"; 33 | } 34 | } 35 | 36 | public synchronized String getTypeByName(String file) { 37 | return tika.detect(file); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/PosixPermission.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files; 5 | 6 | /** 7 | * @author subhro 8 | * 9 | */ 10 | public class PosixPermission { 11 | private String owner, group; 12 | private String ownerAccess, groupAccess, otherAccess; 13 | private boolean executable; 14 | 15 | /** 16 | * @return the owner 17 | */ 18 | public String getOwner() { 19 | return owner; 20 | } 21 | 22 | /** 23 | * @param owner the owner to set 24 | */ 25 | public void setOwner(String owner) { 26 | this.owner = owner; 27 | } 28 | 29 | /** 30 | * @return the group 31 | */ 32 | public String getGroup() { 33 | return group; 34 | } 35 | 36 | /** 37 | * @param group the group to set 38 | */ 39 | public void setGroup(String group) { 40 | this.group = group; 41 | } 42 | 43 | /** 44 | * @return the ownerAccess 45 | */ 46 | public String getOwnerAccess() { 47 | return ownerAccess; 48 | } 49 | 50 | /** 51 | * @param ownerAccess the ownerAccess to set 52 | */ 53 | public void setOwnerAccess(String ownerAccess) { 54 | this.ownerAccess = ownerAccess; 55 | } 56 | 57 | /** 58 | * @return the groupAccess 59 | */ 60 | public String getGroupAccess() { 61 | return groupAccess; 62 | } 63 | 64 | /** 65 | * @param groupAccess the groupAccess to set 66 | */ 67 | public void setGroupAccess(String groupAccess) { 68 | this.groupAccess = groupAccess; 69 | } 70 | 71 | /** 72 | * @return the otherAccess 73 | */ 74 | public String getOtherAccess() { 75 | return otherAccess; 76 | } 77 | 78 | /** 79 | * @param otherAccess the otherAccess to set 80 | */ 81 | public void setOtherAccess(String otherAccess) { 82 | this.otherAccess = otherAccess; 83 | } 84 | 85 | /** 86 | * @return the executable 87 | */ 88 | public boolean isExecutable() { 89 | return executable; 90 | } 91 | 92 | /** 93 | * @param executable the executable to set 94 | */ 95 | public void setExecutable(boolean executable) { 96 | this.executable = executable; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/SearchOperations.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files; 5 | 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.Executors; 10 | import java.util.stream.Collectors; 11 | 12 | import org.springframework.stereotype.Component; 13 | 14 | import web.console.app.files.search.SearchResult; 15 | import web.console.app.files.search.SearchTask; 16 | 17 | /** 18 | * @author subhro 19 | * 20 | */ 21 | @Component 22 | public class SearchOperations { 23 | private final Map pendingOperations = new ConcurrentHashMap<>(); 24 | private final ExecutorService threadPool = Executors.newFixedThreadPool(5); 25 | 26 | public String createSearchTask(String folder, String searchText) { 27 | SearchTask task = new SearchTask(folder, searchText); 28 | pendingOperations.put(task.getId(), task); 29 | threadPool.submit(task); 30 | return task.getId(); 31 | } 32 | 33 | public SearchResult getSearchResult(String id, int fileIndex, 34 | int folderIndex) { 35 | SearchTask task = pendingOperations.get(id); 36 | SearchResult res = new SearchResult(task.isDone(), task.getFiles() 37 | .stream().skip(fileIndex).collect(Collectors.toList())); 38 | return res; 39 | } 40 | 41 | public void cancelSearch(String id) { 42 | SearchTask task = pendingOperations.get(id); 43 | task.stop(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/copy/FileCopyProgressResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files.copy; 5 | 6 | /** 7 | * @author subhro 8 | * 9 | */ 10 | public class FileCopyProgressResponse { 11 | private int progress; 12 | private String status; 13 | private String name; 14 | private String errors; 15 | private boolean hasErrors; 16 | private String id; 17 | 18 | /** 19 | * @param progress 20 | * @param status 21 | * @param errors 22 | * @param hasErrors 23 | */ 24 | public FileCopyProgressResponse(String id, String name, int progress, 25 | String status, String errors, boolean hasErrors) { 26 | super(); 27 | this.id = id; 28 | this.name = name; 29 | this.progress = progress; 30 | this.status = status; 31 | this.errors = errors; 32 | this.hasErrors = hasErrors; 33 | } 34 | 35 | /** 36 | * @return the progress 37 | */ 38 | public int getProgress() { 39 | return progress; 40 | } 41 | 42 | /** 43 | * @param progress the progress to set 44 | */ 45 | public void setProgress(int progress) { 46 | this.progress = progress; 47 | } 48 | 49 | /** 50 | * @return the status 51 | */ 52 | public String getStatus() { 53 | return status; 54 | } 55 | 56 | /** 57 | * @param status the status to set 58 | */ 59 | public void setStatus(String status) { 60 | this.status = status; 61 | } 62 | 63 | /** 64 | * @return the errors 65 | */ 66 | public String getErrors() { 67 | return errors; 68 | } 69 | 70 | /** 71 | * @param errors the errors to set 72 | */ 73 | public void setErrors(String errors) { 74 | this.errors = errors; 75 | } 76 | 77 | /** 78 | * @return the hasErrors 79 | */ 80 | public boolean isHasErrors() { 81 | return hasErrors; 82 | } 83 | 84 | /** 85 | * @param hasErrors the hasErrors to set 86 | */ 87 | public void setHasErrors(boolean hasErrors) { 88 | this.hasErrors = hasErrors; 89 | } 90 | 91 | /** 92 | * @return the id 93 | */ 94 | public String getId() { 95 | return id; 96 | } 97 | 98 | /** 99 | * @param id the id to set 100 | */ 101 | public void setId(String id) { 102 | this.id = id; 103 | } 104 | 105 | /** 106 | * @return the name 107 | */ 108 | public String getName() { 109 | return name; 110 | } 111 | 112 | /** 113 | * @param name the name to set 114 | */ 115 | public void setName(String name) { 116 | this.name = name; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/copy/FileCopyRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files.copy; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author subhro 10 | * 11 | */ 12 | public class FileCopyRequest { 13 | private List sourceFile; 14 | private String targetFolder; 15 | 16 | /** 17 | * @return the sourceFile 18 | */ 19 | public List getSourceFile() { 20 | return sourceFile; 21 | } 22 | 23 | /** 24 | * @param sourceFile the sourceFile to set 25 | */ 26 | public void setSourceFile(List sourceFile) { 27 | this.sourceFile = sourceFile; 28 | } 29 | 30 | /** 31 | * @return the targetFolder 32 | */ 33 | public String getTargetFolder() { 34 | return targetFolder; 35 | } 36 | 37 | /** 38 | * @param targetFolder the targetFolder to set 39 | */ 40 | public void setTargetFolder(String targetFolder) { 41 | this.targetFolder = targetFolder; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/search/SearchResult.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files.search; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import web.console.app.files.FileInfo; 10 | 11 | /** 12 | * @author subhro 13 | * 14 | */ 15 | public class SearchResult { 16 | /** 17 | * @param isDone 18 | * @param files 19 | * @param folders 20 | */ 21 | public SearchResult(boolean isDone, List files) { 22 | super(); 23 | this.isDone = isDone; 24 | this.files = files; 25 | } 26 | 27 | private boolean isDone; 28 | private List files = new ArrayList<>(); 29 | 30 | /** 31 | * @return the isDone 32 | */ 33 | public boolean isDone() { 34 | return isDone; 35 | } 36 | 37 | /** 38 | * @param isDone the isDone to set 39 | */ 40 | public void setDone(boolean isDone) { 41 | this.isDone = isDone; 42 | } 43 | 44 | /** 45 | * @return the files 46 | */ 47 | public List getFiles() { 48 | return files; 49 | } 50 | 51 | /** 52 | * @param files the files to set 53 | */ 54 | public void setFiles(List files) { 55 | this.files = files; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/files/search/SearchTask.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.files.search; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.FileVisitResult; 9 | import java.nio.file.FileVisitor; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.nio.file.attribute.BasicFileAttributes; 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.List; 17 | import java.util.Locale; 18 | import java.util.UUID; 19 | import java.util.concurrent.atomic.AtomicBoolean; 20 | 21 | import web.console.app.files.FileInfo; 22 | 23 | /** 24 | * @author subhro 25 | * 26 | */ 27 | public class SearchTask implements Runnable { 28 | 29 | private boolean posix; 30 | 31 | /** 32 | * @param folder 33 | * @param searchText 34 | */ 35 | public SearchTask(String folder, String searchText) { 36 | super(); 37 | this.id = UUID.randomUUID().toString(); 38 | if (folder == null || folder.length() < 1) { 39 | this.folder = System.getProperty("user.home"); 40 | } else { 41 | this.folder = folder; 42 | } 43 | 44 | this.searchText = searchText.toLowerCase(Locale.ENGLISH); 45 | posix = !System.getProperty("os.name").toLowerCase() 46 | .contains("windows"); 47 | } 48 | 49 | private String id; 50 | private AtomicBoolean isDone = new AtomicBoolean(false); 51 | private List files = Collections 52 | .synchronizedList(new ArrayList<>()); 53 | private String folder; 54 | private String searchText; 55 | private AtomicBoolean stopRequested = new AtomicBoolean(false); 56 | 57 | /** 58 | * @return the isDone 59 | */ 60 | public boolean isDone() { 61 | return isDone.get(); 62 | } 63 | 64 | /** 65 | * @param isDone the isDone to set 66 | */ 67 | public void setDone(boolean isDone) { 68 | this.isDone.set(isDone); 69 | } 70 | 71 | /** 72 | * @return the files 73 | */ 74 | public List getFiles() { 75 | return files; 76 | } 77 | 78 | /** 79 | * @param files the files to set 80 | */ 81 | public void setFiles(List files) { 82 | this.files = files; 83 | } 84 | 85 | /** 86 | * @return the stopRequested 87 | */ 88 | public AtomicBoolean getStopRequested() { 89 | return stopRequested; 90 | } 91 | 92 | /** 93 | * 94 | */ 95 | public void stop() { 96 | System.out.println("Stopping..."); 97 | this.stopRequested.set(true); 98 | } 99 | 100 | @Override 101 | public void run() { 102 | try { 103 | System.out.println("Search start"); 104 | Files.walkFileTree(Paths.get(folder), new FileVisitor() { 105 | @Override 106 | public FileVisitResult preVisitDirectory(Path dir, 107 | BasicFileAttributes attrs) throws IOException { 108 | // System.out.println("Search: " + dir.toString()); 109 | if (stopRequested.get()) { 110 | return FileVisitResult.TERMINATE; 111 | } 112 | 113 | if (isMatch(dir, searchText)) { 114 | File fdir = dir.toFile(); 115 | FileInfo info = new FileInfo(fdir.getName(), 116 | fdir.getAbsolutePath(), "", "", "Directory", 0, 117 | 0, null, posix); 118 | files.add(info); 119 | } 120 | return FileVisitResult.CONTINUE; 121 | } 122 | 123 | @Override 124 | public FileVisitResult visitFile(Path file, 125 | BasicFileAttributes attrs) throws IOException { 126 | // System.out.println("Search: " + file); 127 | if (stopRequested.get()) { 128 | return FileVisitResult.TERMINATE; 129 | } 130 | 131 | if (isMatch(file, searchText)) { 132 | File f = file.toFile(); 133 | FileInfo info = new FileInfo(f.getName(), 134 | f.getAbsolutePath(), "", "", "File", 0, 0, null, 135 | posix); 136 | files.add(info); 137 | } 138 | return FileVisitResult.CONTINUE; 139 | } 140 | 141 | @Override 142 | public FileVisitResult visitFileFailed(Path file, 143 | IOException exc) throws IOException { 144 | // System.out.println("visit failed: " + file); 145 | if (stopRequested.get()) { 146 | return FileVisitResult.TERMINATE; 147 | } 148 | return FileVisitResult.CONTINUE; 149 | } 150 | 151 | @Override 152 | public FileVisitResult postVisitDirectory(Path dir, 153 | IOException exc) throws IOException { 154 | // System.out.println("post visit failed: " + dir); 155 | if (stopRequested.get()) { 156 | return FileVisitResult.TERMINATE; 157 | } 158 | return FileVisitResult.CONTINUE; 159 | } 160 | 161 | private boolean isMatch(Path path, String text) { 162 | String name = path.toString(); 163 | return name.toLowerCase(Locale.ENGLISH).contains(text); 164 | } 165 | }); 166 | } catch (Exception e) { 167 | // TODO Auto-generated catch block 168 | e.printStackTrace(); 169 | } 170 | System.out.println("Search finished"); 171 | isDone.set(true); 172 | } 173 | 174 | /** 175 | * @return the searchText 176 | */ 177 | public String getSearchText() { 178 | return searchText; 179 | } 180 | 181 | /** 182 | * @param searchText the searchText to set 183 | */ 184 | public void setSearchText(String searchText) { 185 | this.searchText = searchText.toLowerCase(Locale.ENGLISH); 186 | } 187 | 188 | /** 189 | * @return the folder 190 | */ 191 | public String getFolder() { 192 | return folder; 193 | } 194 | 195 | /** 196 | * @param folder the folder to set 197 | */ 198 | public void setFolder(String folder) { 199 | this.folder = folder; 200 | } 201 | 202 | /** 203 | * @return the id 204 | */ 205 | public String getId() { 206 | return id; 207 | } 208 | 209 | /** 210 | * @param id the id to set 211 | */ 212 | public void setId(String id) { 213 | this.id = id; 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/health/ProcessInfo.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.health; 5 | 6 | /** 7 | * @author subhro 8 | * 9 | */ 10 | public class ProcessInfo { 11 | private String name, command, user, state; 12 | private int pid, priority; 13 | private double cpuUsage, memoryUsage, vmUsage; 14 | private long startTime; 15 | 16 | /** 17 | * @return the name 18 | */ 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | /** 24 | * @param name the name to set 25 | */ 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | 30 | /** 31 | * @return the command 32 | */ 33 | public String getCommand() { 34 | return command; 35 | } 36 | 37 | /** 38 | * @param command the command to set 39 | */ 40 | public void setCommand(String command) { 41 | this.command = command; 42 | } 43 | 44 | /** 45 | * @return the user 46 | */ 47 | public String getUser() { 48 | return user; 49 | } 50 | 51 | /** 52 | * @param user the user to set 53 | */ 54 | public void setUser(String user) { 55 | this.user = user; 56 | } 57 | 58 | /** 59 | * @return the state 60 | */ 61 | public String getState() { 62 | return state; 63 | } 64 | 65 | /** 66 | * @param state the state to set 67 | */ 68 | public void setState(String state) { 69 | this.state = state; 70 | } 71 | 72 | /** 73 | * @return the pid 74 | */ 75 | public int getPid() { 76 | return pid; 77 | } 78 | 79 | /** 80 | * @param pid the pid to set 81 | */ 82 | public void setPid(int pid) { 83 | this.pid = pid; 84 | } 85 | 86 | /** 87 | * @return the cpuUsage 88 | */ 89 | public double getCpuUsage() { 90 | return cpuUsage; 91 | } 92 | 93 | /** 94 | * @param cpuUsage the cpuUsage to set 95 | */ 96 | public void setCpuUsage(double cpuUsage) { 97 | this.cpuUsage = cpuUsage; 98 | } 99 | 100 | /** 101 | * @return the memoryUsage 102 | */ 103 | public double getMemoryUsage() { 104 | return memoryUsage; 105 | } 106 | 107 | /** 108 | * @param memoryUsage the memoryUsage to set 109 | */ 110 | public void setMemoryUsage(double memoryUsage) { 111 | this.memoryUsage = memoryUsage; 112 | } 113 | 114 | /** 115 | * @return the vmUsage 116 | */ 117 | public double getVmUsage() { 118 | return vmUsage; 119 | } 120 | 121 | /** 122 | * @param vmUsage the vmUsage to set 123 | */ 124 | public void setVmUsage(double vmUsage) { 125 | this.vmUsage = vmUsage; 126 | } 127 | 128 | /** 129 | * @return the startTime 130 | */ 131 | public long getStartTime() { 132 | return startTime; 133 | } 134 | 135 | /** 136 | * @param startTime the startTime to set 137 | */ 138 | public void setStartTime(long startTime) { 139 | this.startTime = startTime; 140 | } 141 | 142 | /** 143 | * @return the priority 144 | */ 145 | public int getPriority() { 146 | return priority; 147 | } 148 | 149 | /** 150 | * @param priority the priority to set 151 | */ 152 | public void setPriority(int priority) { 153 | this.priority = priority; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/health/SystemHealthMonitor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.health; 5 | 6 | import java.io.File; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | import org.springframework.stereotype.Component; 12 | 13 | import oshi.SystemInfo; 14 | import oshi.hardware.CentralProcessor; 15 | import oshi.hardware.GlobalMemory; 16 | import oshi.hardware.HardwareAbstractionLayer; 17 | import oshi.software.os.OSProcess; 18 | import oshi.software.os.OperatingSystem; 19 | 20 | /** 21 | * @author subhro 22 | * 23 | */ 24 | @Component 25 | public class SystemHealthMonitor { 26 | /** 27 | * 28 | */ 29 | private CentralProcessor processor; 30 | private GlobalMemory memory; 31 | private OperatingSystem os; 32 | private boolean isWindows; 33 | 34 | private SystemInfo si; 35 | 36 | public SystemHealthMonitor() { 37 | si = new SystemInfo(); 38 | HardwareAbstractionLayer hal = si.getHardware(); 39 | os = si.getOperatingSystem(); 40 | processor = hal.getProcessor(); 41 | memory = hal.getMemory(); 42 | processor.getSystemCpuLoadBetweenTicks(); 43 | isWindows = System.getProperty("os.name").toLowerCase() 44 | .contains("windows"); 45 | } 46 | 47 | public synchronized SystemStats getStats() { 48 | SystemStats stats = new SystemStats(); 49 | double cpuUsed = processor.getSystemCpuLoadBetweenTicks() * 100; 50 | 51 | stats.setCpuUsed(cpuUsed); 52 | stats.setCpuFree(100 - cpuUsed); 53 | 54 | long avail = memory.getAvailable(); 55 | long total = memory.getTotal(); 56 | if (total > 0) { 57 | double memoryUsed = ((total - avail) * 100) / total; 58 | stats.setMemoryUsed(memoryUsed); 59 | stats.setMemoryFree(100 - memoryUsed); 60 | } 61 | 62 | File f = new File("/"); 63 | long totalDiskSpace = f.getTotalSpace(); 64 | long freeDiskSpace = f.getFreeSpace(); 65 | if (totalDiskSpace > 0) { 66 | double diskUsed = ((totalDiskSpace - freeDiskSpace) * 100) 67 | / totalDiskSpace; 68 | stats.setDiskUsed(diskUsed); 69 | stats.setDiskFree(100 - diskUsed); 70 | } 71 | 72 | long totalSwap = memory.getSwapTotal(); 73 | long usedSwap = memory.getSwapUsed(); 74 | if (totalSwap > 0) { 75 | double swapUsed = usedSwap * 100 / totalSwap; 76 | stats.setSwapUsed(swapUsed); 77 | stats.setSwapFree(100 - swapUsed); 78 | } 79 | 80 | return stats; 81 | } 82 | 83 | private String formatCmd(String args) { 84 | StringBuilder sb = new StringBuilder(); 85 | for (char ch : args.toCharArray()) { 86 | if (ch == 0) { 87 | sb.append(" "); 88 | } 89 | sb.append(ch); 90 | } 91 | return sb.toString(); 92 | } 93 | 94 | public synchronized List getProcessList() { 95 | OSProcess[] procs = os.getProcesses(0, null, false); 96 | List list = new ArrayList<>(); 97 | if (procs != null && procs.length > 0) { 98 | for (OSProcess proc : procs) { 99 | ProcessInfo info = new ProcessInfo(); 100 | info.setPid(proc.getProcessID()); 101 | info.setName(proc.getName()); 102 | info.setCommand(formatCmd(proc.getCommandLine())); 103 | info.setCpuUsage(proc.calculateCpuPercent()); 104 | info.setMemoryUsage(proc.getResidentSetSize()); 105 | info.setVmUsage(proc.getVirtualSize()); 106 | info.setState(proc.getState().toString()); 107 | info.setStartTime(proc.getStartTime()); 108 | info.setUser(proc.getUser()); 109 | info.setPriority(proc.getPriority()); 110 | list.add(info); 111 | } 112 | } 113 | return list; 114 | } 115 | 116 | private boolean killProcess(int pid) { 117 | ProcessBuilder pb = new ProcessBuilder( 118 | isWindows ? Arrays.asList("taskkill", "/pid", pid + "", "/f") 119 | : Arrays.asList("kill", "-9", pid + "")); 120 | try { 121 | Process proc = pb.start(); 122 | int ret = proc.waitFor(); 123 | return ret == 0; 124 | } catch (Exception e) { 125 | e.printStackTrace(); 126 | return false; 127 | } 128 | } 129 | 130 | public synchronized boolean killProcess(List pidList) { 131 | boolean success = true; 132 | for (Integer pid : pidList) { 133 | if (success) { 134 | success = killProcess(pid); 135 | } 136 | } 137 | return success; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/health/SystemStats.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.health; 5 | 6 | /** 7 | * @author subhro 8 | * 9 | */ 10 | public class SystemStats { 11 | private double cpuUsed = -1, cpuFree = -1, memoryUsed = -1, memoryFree = -1, 12 | swapUsed = -1, swapFree = -1, diskUsed = -1, diskFree = -1; 13 | 14 | /** 15 | * @return the cpuUsed 16 | */ 17 | public double getCpuUsed() { 18 | return cpuUsed; 19 | } 20 | 21 | /** 22 | * @param cpuUsed the cpuUsed to set 23 | */ 24 | public void setCpuUsed(double cpuUsed) { 25 | this.cpuUsed = cpuUsed; 26 | } 27 | 28 | /** 29 | * @return the cpuFree 30 | */ 31 | public double getCpuFree() { 32 | return cpuFree; 33 | } 34 | 35 | /** 36 | * @param cpuFree the cpuFree to set 37 | */ 38 | public void setCpuFree(double cpuFree) { 39 | this.cpuFree = cpuFree; 40 | } 41 | 42 | /** 43 | * @return the memoryUsed 44 | */ 45 | public double getMemoryUsed() { 46 | return memoryUsed; 47 | } 48 | 49 | /** 50 | * @param memoryUsed the memoryUsed to set 51 | */ 52 | public void setMemoryUsed(double memoryUsed) { 53 | this.memoryUsed = memoryUsed; 54 | } 55 | 56 | /** 57 | * @return the memoryFree 58 | */ 59 | public double getMemoryFree() { 60 | return memoryFree; 61 | } 62 | 63 | /** 64 | * @param memoryFree the memoryFree to set 65 | */ 66 | public void setMemoryFree(double memoryFree) { 67 | this.memoryFree = memoryFree; 68 | } 69 | 70 | /** 71 | * @return the swapUsed 72 | */ 73 | public double getSwapUsed() { 74 | return swapUsed; 75 | } 76 | 77 | /** 78 | * @param swapUsed the swapUsed to set 79 | */ 80 | public void setSwapUsed(double swapUsed) { 81 | this.swapUsed = swapUsed; 82 | } 83 | 84 | /** 85 | * @return the swapFree 86 | */ 87 | public double getSwapFree() { 88 | return swapFree; 89 | } 90 | 91 | /** 92 | * @param swapFree the swapFree to set 93 | */ 94 | public void setSwapFree(double swapFree) { 95 | this.swapFree = swapFree; 96 | } 97 | 98 | /** 99 | * @return the diskUsed 100 | */ 101 | public double getDiskUsed() { 102 | return diskUsed; 103 | } 104 | 105 | /** 106 | * @param diskUsed the diskUsed to set 107 | */ 108 | public void setDiskUsed(double diskUsed) { 109 | this.diskUsed = diskUsed; 110 | } 111 | 112 | /** 113 | * @return the diskFree 114 | */ 115 | public double getDiskFree() { 116 | return diskFree; 117 | } 118 | 119 | /** 120 | * @param diskFree the diskFree to set 121 | */ 122 | public void setDiskFree(double diskFree) { 123 | this.diskFree = diskFree; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/security/CustomAuthEntryPoint.java: -------------------------------------------------------------------------------- 1 | package web.console.app.security; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | public class CustomAuthEntryPoint extends BasicAuthenticationEntryPoint { 15 | @Override 16 | public void commence(HttpServletRequest request, HttpServletResponse response, 17 | AuthenticationException authException) throws IOException, ServletException { 18 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 19 | } 20 | 21 | @Override 22 | public void afterPropertiesSet() throws Exception { 23 | setRealmName("CustomBasicAuth"); 24 | super.afterPropertiesSet(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/security/JwtAuthorizationFilter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.security; 5 | 6 | import java.io.IOException; 7 | import java.util.Collections; 8 | 9 | import javax.servlet.FilterChain; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | import org.springframework.security.authentication.AuthenticationManager; 15 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 16 | import org.springframework.security.core.context.SecurityContextHolder; 17 | import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; 18 | 19 | import io.jsonwebtoken.Claims; 20 | import io.jsonwebtoken.Jws; 21 | import io.jsonwebtoken.Jwts; 22 | import web.console.app.Application; 23 | 24 | /** 25 | * @author subhro 26 | * 27 | */ 28 | public class JwtAuthorizationFilter extends BasicAuthenticationFilter { 29 | 30 | /** 31 | * @param authenticationManager 32 | */ 33 | public JwtAuthorizationFilter(AuthenticationManager authenticationManager) { 34 | super(authenticationManager); 35 | } 36 | 37 | @Override 38 | protected void doFilterInternal(HttpServletRequest request, 39 | HttpServletResponse response, FilterChain filterChain) 40 | throws IOException, ServletException { 41 | UsernamePasswordAuthenticationToken authentication = getAuthentication( 42 | request); 43 | if (authentication == null) { 44 | filterChain.doFilter(request, response); 45 | return; 46 | } 47 | 48 | SecurityContextHolder.getContext().setAuthentication(authentication); 49 | filterChain.doFilter(request, response); 50 | } 51 | 52 | private UsernamePasswordAuthenticationToken getAuthentication( 53 | HttpServletRequest request) { 54 | String token = request.getHeader("Authorization"); 55 | if (token != null && token.length() > 0 && token.startsWith("Bearer")) { 56 | try { 57 | Jws parsedToken = Jwts.parser() 58 | .setSigningKey(Application.SECRET_KEY) 59 | .parseClaimsJws(token.replace("Bearer ", "")); 60 | 61 | String username = parsedToken.getBody().getSubject(); 62 | 63 | if (username != null && username.length() > 0) { 64 | return new UsernamePasswordAuthenticationToken(System.getProperty("app.default-user"), 65 | null, Collections.emptyList()); 66 | } 67 | } catch (Exception exception) { 68 | exception.printStackTrace(); 69 | } 70 | } 71 | 72 | return null; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/security/JwtTokenController.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.security; 5 | 6 | import java.security.Principal; 7 | import java.util.Date; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import org.springframework.web.bind.annotation.CrossOrigin; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import io.jsonwebtoken.Jwts; 18 | import web.console.app.Application; 19 | 20 | /** 21 | * @author subhro 22 | * 23 | */ 24 | @RestController 25 | @CrossOrigin(allowCredentials = "true") 26 | @RequestMapping("/api") 27 | public class JwtTokenController { 28 | 29 | @PostMapping("/token") 30 | public Map generateToken(Principal principal) { 31 | Map map = new HashMap<>(); 32 | String token = Jwts.builder().setSubject(principal.getName()) 33 | .signWith(Application.SECRET_KEY).compact(); 34 | map.put("token", token); 35 | map.put("posix", (!System.getProperty("os.name").toLowerCase() 36 | .contains("windows")) + ""); 37 | return map; 38 | } 39 | 40 | @PostMapping("/auth") 41 | public void checkToken() { 42 | } 43 | 44 | @GetMapping("/token/temp") 45 | public Map generateTempToken(Principal principal) { 46 | Map map = new HashMap<>(); 47 | String token = Jwts.builder().setSubject(principal.getName()) 48 | .setExpiration( 49 | new Date(System.currentTimeMillis() + (60 * 1000))) 50 | .signWith(Application.SECRET_KEY).compact(); 51 | map.put("token", token); 52 | return map; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/security/WebshellUserDetailsService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.security; 5 | 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileNotFoundException; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Properties; 14 | 15 | import org.springframework.security.core.GrantedAuthority; 16 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 17 | import org.springframework.security.core.userdetails.User; 18 | import org.springframework.security.core.userdetails.UserDetails; 19 | import org.springframework.security.core.userdetails.UserDetailsService; 20 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 21 | 22 | /** 23 | * @author subhro 24 | * 25 | */ 26 | public class WebshellUserDetailsService implements UserDetailsService { 27 | Properties users = new Properties(); 28 | 29 | /** 30 | * 31 | */ 32 | public WebshellUserDetailsService() { 33 | loadProperties(); 34 | } 35 | 36 | private void loadProperties() { 37 | users.clear(); 38 | String propertyPath = System.getenv("easy-web-shell.config-path"); 39 | if (propertyPath == null) { 40 | propertyPath = System.getProperty("user.home"); 41 | } 42 | 43 | File f = new File(propertyPath, ".users"); 44 | 45 | if (f.exists()) { 46 | try (InputStream in = new FileInputStream(f)) { 47 | users.load(in); 48 | } catch (FileNotFoundException e) { 49 | // TODO Auto-generated catch block 50 | e.printStackTrace(); 51 | } catch (IOException e) { 52 | // TODO Auto-generated catch block 53 | e.printStackTrace(); 54 | } 55 | } 56 | 57 | if (users.size() < 1) { 58 | users.put("admin", "admin"); 59 | } 60 | } 61 | 62 | @Override 63 | public UserDetails loadUserByUsername(String username) 64 | throws UsernameNotFoundException { 65 | if (!users.containsKey(username)) { 66 | return null; 67 | } 68 | String password = users.getProperty(username); 69 | List authorities = new ArrayList<>(); 70 | authorities.add(new SimpleGrantedAuthority("ROLE_USER")); 71 | return new User(username, password, authorities); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/terminal/PtyProcessPipe.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package web.console.app.terminal; 5 | 6 | import java.io.InputStream; 7 | import java.io.OutputStream; 8 | 9 | /** 10 | * @author subhro 11 | * 12 | */ 13 | public interface PtyProcessPipe { 14 | public int read(byte[] b) throws Exception; 15 | 16 | public void write(byte[] b, int off, int len) throws Exception; 17 | 18 | public void resizePty(int col, int row, int wp, int hp); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/terminal/SshPtyProcessPipe.java: -------------------------------------------------------------------------------- 1 | ///** 2 | // * 3 | // */ 4 | //package web.console.app.terminal; 5 | // 6 | //import java.io.IOException; 7 | //import java.io.InputStream; 8 | //import java.io.InputStreamReader; 9 | //import java.io.OutputStream; 10 | //import java.io.PipedInputStream; 11 | //import java.io.PipedOutputStream; 12 | //import java.io.PipedReader; 13 | //import java.io.PipedWriter; 14 | //import java.io.Reader; 15 | //import java.io.UnsupportedEncodingException; 16 | //import java.util.concurrent.atomic.AtomicBoolean; 17 | // 18 | //import com.jcraft.jsch.ChannelShell; 19 | //import com.jcraft.jsch.JSch; 20 | //import com.jcraft.jsch.Session; 21 | //import com.jcraft.jsch.UserInfo; 22 | // 23 | ///** 24 | // * @author subhro 25 | // * 26 | // */ 27 | //public class SshPtyProcessPipe implements PtyProcessPipe, UserInfo { 28 | // 29 | // private PipedInputStream _in, in; 30 | // private PipedOutputStream _out, out; 31 | // private String hostName, keyFile, user; 32 | // private int port; 33 | // private JSch jsch; 34 | // private Session session; 35 | // private ChannelShell shell; 36 | // 37 | // /** 38 | // * @throws IOException 39 | // * @throws UnsupportedEncodingException 40 | // * 41 | // */ 42 | // public SshPtyProcessPipe() { 43 | // hostName = "192.168.56.106"; 44 | // port = 22; 45 | // user = "subhro"; 46 | // } 47 | // 48 | // public void start() { 49 | // try { 50 | // _out = new PipedOutputStream(); 51 | // in = new PipedInputStream(_out, 1); 52 | // _in = new PipedInputStream(1); 53 | // out = new PipedOutputStream(_in); 54 | // } catch (Exception e) { 55 | // // TODO Auto-generated catch block 56 | // e.printStackTrace(); 57 | // } 58 | // 59 | // new Thread(() -> { 60 | // try { 61 | // _start(); 62 | // } catch (Exception e) { 63 | // // TODO Auto-generated catch block 64 | // e.printStackTrace(); 65 | // } 66 | // }).start(); 67 | // } 68 | // 69 | // public void _start() throws Exception { 70 | // 71 | // jsch = new JSch(); 72 | // JSch.setConfig("MaxAuthTries", "5"); 73 | // 74 | // if (keyFile != null && keyFile.length() > 0) { 75 | // jsch.addIdentity(keyFile); 76 | // } 77 | // 78 | // session = jsch.getSession(user, hostName, port); 79 | // 80 | // session.setUserInfo(this); 81 | // // session.setConfig("StrictHostKeyChecking", "no"); 82 | // session.setConfig("PreferredAuthentications", 83 | // "publickey,keyboard-interactive,password"); 84 | // 85 | // System.out.println("Before connect"); 86 | // 87 | // session.connect(); 88 | // 89 | // System.out.println("Client version: " + session.getClientVersion()); 90 | // System.out.println("Server host: " + session.getHost()); 91 | // System.out.println("Server version: " + session.getServerVersion()); 92 | // System.out.println( 93 | // "Hostkey: " + session.getHostKey().getFingerPrint(jsch)); 94 | // 95 | // shell = (ChannelShell) session.openChannel("shell"); 96 | // shell.setEnv("TERM", "xterm"); 97 | // 98 | // shell.setInputStream(in); 99 | // shell.setOutputStream(out); 100 | // 101 | // shell.connect(); 102 | // } 103 | // 104 | // public void resizePty(int col, int row, int wp, int hp) { 105 | // shell.setPtySize(col, row, wp, hp); 106 | // } 107 | // 108 | // private String readLine() throws IOException { 109 | // System.out.println("Attempt readline"); 110 | // StringBuilder sb = new StringBuilder(); 111 | // while (true) { 112 | // int ch = in.read(); 113 | // System.out.println("char: " + ch); 114 | // if (ch == -1 || ch == '\n'|| ch == '\r') 115 | // break; 116 | // sb.append((char) ch); 117 | // } 118 | // return sb.toString(); 119 | // } 120 | // 121 | // @Override 122 | // public String getPassphrase() { 123 | // try { 124 | // return readLine(); 125 | // } catch (IOException e) { 126 | // e.printStackTrace(); 127 | // } 128 | // return null; 129 | // } 130 | // 131 | // @Override 132 | // public String getPassword() { 133 | // try { 134 | // String pass = readLine(); 135 | // System.out.println("Paww: " + pass); 136 | // return pass; 137 | // } catch (IOException e) { 138 | // e.printStackTrace(); 139 | // } 140 | // return null; 141 | // } 142 | // 143 | // @Override 144 | // public boolean promptPassword(String message) { 145 | // System.out.println("prompt password: " + message); 146 | // try { 147 | // out.write(message.getBytes("utf-8")); 148 | // } catch (UnsupportedEncodingException e) { 149 | // // TODO Auto-generated catch block 150 | // e.printStackTrace(); 151 | // } catch (IOException e) { 152 | // // TODO Auto-generated catch block 153 | // e.printStackTrace(); 154 | // } 155 | // return true; 156 | // } 157 | // 158 | // @Override 159 | // public boolean promptPassphrase(String message) { 160 | // try { 161 | // out.write(message.getBytes("utf-8")); 162 | // } catch (UnsupportedEncodingException e) { 163 | // // TODO Auto-generated catch block 164 | // e.printStackTrace(); 165 | // } catch (IOException e) { 166 | // // TODO Auto-generated catch block 167 | // e.printStackTrace(); 168 | // } 169 | // return true; 170 | // } 171 | // 172 | // @Override 173 | // public boolean promptYesNo(String message) { 174 | // return true; 175 | // } 176 | // 177 | // @Override 178 | // public void showMessage(String message) { 179 | // System.out.println("prompt messae: " + message); 180 | // try { 181 | // out.write(message.getBytes("utf-8")); 182 | // } catch (UnsupportedEncodingException e) { 183 | // // TODO Auto-generated catch block 184 | // e.printStackTrace(); 185 | // } catch (IOException e) { 186 | // // TODO Auto-generated catch block 187 | // e.printStackTrace(); 188 | // } 189 | // } 190 | // 191 | // @Override 192 | // public int read(byte[] b) throws Exception { 193 | // return _in.read(b); 194 | // } 195 | // 196 | // @Override 197 | // public void write(byte[] b, int off, int len) throws Exception { 198 | // this._out.write(b, off, len); 199 | // } 200 | // 201 | //} 202 | -------------------------------------------------------------------------------- /app/src/main/java/web/console/app/terminal/TerminalWebsocketHandler.java: -------------------------------------------------------------------------------- 1 | package web.console.app.terminal; 2 | 3 | import java.net.URI; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.util.MultiValueMap; 8 | import org.springframework.web.socket.CloseStatus; 9 | import org.springframework.web.socket.TextMessage; 10 | import org.springframework.web.socket.WebSocketSession; 11 | import org.springframework.web.socket.handler.AbstractWebSocketHandler; 12 | import org.springframework.web.util.UriComponentsBuilder; 13 | 14 | import io.jsonwebtoken.Jwts; 15 | import web.console.app.AppContext; 16 | import web.console.app.Application; 17 | 18 | /** 19 | * @author subhro 20 | * 21 | */ 22 | public class TerminalWebsocketHandler extends AbstractWebSocketHandler { 23 | private static final Logger logger = LoggerFactory 24 | .getLogger(TerminalWebsocketHandler.class); 25 | 26 | @Override 27 | public void afterConnectionEstablished(WebSocketSession session) 28 | throws Exception { 29 | logger.info("Incoming session: " + session.getId() + " url: " 30 | + session.getUri()); 31 | 32 | synchronized (this) { 33 | URI uri = session.getUri(); 34 | if (uri == null) { 35 | session.close(CloseStatus.BAD_DATA); 36 | return; 37 | } 38 | MultiValueMap params = UriComponentsBuilder 39 | .fromUri(uri).build().getQueryParams(); 40 | 41 | try { 42 | String token = params.getFirst("token"); 43 | Jwts.parser().setSigningKey(Application.SECRET_KEY) 44 | .parseClaimsJws(token); 45 | } catch (Exception exception) { 46 | exception.printStackTrace(); 47 | session.close(CloseStatus.BAD_DATA); 48 | return; 49 | } 50 | 51 | String appId = params.getFirst("id"); 52 | if (appId == null) { 53 | session.close(CloseStatus.BAD_DATA); 54 | return; 55 | } 56 | PtySession app = AppContext.INSTANCES.get(appId); 57 | if (app == null) { 58 | logger.error("No instance for session id: " + session.getId() 59 | + " data: " + uri); 60 | session.close(CloseStatus.BAD_DATA); 61 | return; 62 | } 63 | app.setWs(session); 64 | AppContext.SESSION_MAP.put(session.getId(), app); 65 | app.start(); 66 | } 67 | 68 | logger.info("Handshake complete session: " + session.getId() + " url: " 69 | + session.getUri()); 70 | 71 | } 72 | 73 | @Override 74 | protected void handleTextMessage(WebSocketSession session, 75 | TextMessage message) throws Exception { 76 | System.out.println("Message received: " + message.getPayload()); 77 | 78 | PtySession app = AppContext.SESSION_MAP.get(session.getId()); 79 | 80 | if (app == null) { 81 | logger.error("Invalid app id: " + session.getId()); 82 | session.close(CloseStatus.BAD_DATA); 83 | return; 84 | } 85 | 86 | app.sendData(message.getPayload()); 87 | } 88 | 89 | @Override 90 | public void afterConnectionClosed(WebSocketSession session, 91 | CloseStatus status) throws Exception { 92 | System.out.println("Session closed: " + session.getId()); 93 | PtySession app = AppContext.SESSION_MAP.get(session.getId()); 94 | if (app != null) { 95 | app.close(); 96 | AppContext.SESSION_MAP.remove(session.getId()); 97 | logger.info("Session closed"); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8055 2 | app.default-admin-user=admin 3 | app.default-admin-pass=$2a$10$U8V0ssyrquzGKBjGu3ui5esNr9mdGoyeOfPC9K7SFxX2ISA9T8VlC 4 | # The format used for the keystore. It could be set to JKS in case it is a JKS file 5 | server.ssl.key-store-type=PKCS12 6 | # The path to the keystore containing the certificate 7 | #server.ssl.key-store=classpath:keystore/easy-webshell.p12 8 | # The password used to generate the certificate 9 | #server.ssl.key-store-password=webshell 10 | # The alias mapped to the certificate 11 | #server.ssl.key-alias=easy-webshell 12 | 13 | security.require-ssl=true 14 | -------------------------------------------------------------------------------- /app/src/test/java/cloudshell/app/AppApplicationTests.java: -------------------------------------------------------------------------------- 1 | package cloudshell.app; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | @RunWith(SpringRunner.class) 11 | @SpringBootTest(classes = AppApplicationTests.class) 12 | public class AppApplicationTests { 13 | 14 | @Test 15 | public void contextLoads() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/start-web-console.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | mkdir ~/.linux-web-console 3 | nohup java -jar web-console.jar >$HOME/.linux-web-console/log.txt 2>&1& 4 | 5 | -------------------------------------------------------------------------------- /ui/web-console/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /ui/web-console/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # profiling files 12 | chrome-profiler-events.json 13 | speed-measure-plugin.json 14 | 15 | # IDEs and editors 16 | /.idea 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # IDE - VSCode 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # misc 33 | /.sass-cache 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | npm-debug.log 38 | yarn-error.log 39 | testem.log 40 | /typings 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | -------------------------------------------------------------------------------- /ui/web-console/README.md: -------------------------------------------------------------------------------- 1 | # WebConsole 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.2.2. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /ui/web-console/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "web-console": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/web-console", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/styles.css" 27 | ], 28 | "scripts": [] 29 | }, 30 | "configurations": { 31 | "production": { 32 | "fileReplacements": [ 33 | { 34 | "replace": "src/environments/environment.ts", 35 | "with": "src/environments/environment.prod.ts" 36 | } 37 | ], 38 | "optimization": true, 39 | "outputHashing": "all", 40 | "sourceMap": false, 41 | "extractCss": true, 42 | "namedChunks": false, 43 | "aot": true, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true, 47 | "budgets": [ 48 | { 49 | "type": "initial", 50 | "maximumWarning": "2mb", 51 | "maximumError": "5mb" 52 | } 53 | ] 54 | } 55 | } 56 | }, 57 | "serve": { 58 | "builder": "@angular-devkit/build-angular:dev-server", 59 | "options": { 60 | "browserTarget": "web-console:build" 61 | }, 62 | "configurations": { 63 | "production": { 64 | "browserTarget": "web-console:build:production" 65 | } 66 | } 67 | }, 68 | "extract-i18n": { 69 | "builder": "@angular-devkit/build-angular:extract-i18n", 70 | "options": { 71 | "browserTarget": "web-console:build" 72 | } 73 | }, 74 | "test": { 75 | "builder": "@angular-devkit/build-angular:karma", 76 | "options": { 77 | "main": "src/test.ts", 78 | "polyfills": "src/polyfills.ts", 79 | "tsConfig": "src/tsconfig.spec.json", 80 | "karmaConfig": "src/karma.conf.js", 81 | "styles": [ 82 | "src/styles.css" 83 | ], 84 | "scripts": [], 85 | "assets": [ 86 | "src/favicon.ico", 87 | "src/assets" 88 | ] 89 | } 90 | }, 91 | "lint": { 92 | "builder": "@angular-devkit/build-angular:tslint", 93 | "options": { 94 | "tsConfig": [ 95 | "src/tsconfig.app.json", 96 | "src/tsconfig.spec.json" 97 | ], 98 | "exclude": [ 99 | "**/node_modules/**" 100 | ] 101 | } 102 | } 103 | } 104 | }, 105 | "web-console-e2e": { 106 | "root": "e2e/", 107 | "projectType": "application", 108 | "prefix": "", 109 | "architect": { 110 | "e2e": { 111 | "builder": "@angular-devkit/build-angular:protractor", 112 | "options": { 113 | "protractorConfig": "e2e/protractor.conf.js", 114 | "devServerTarget": "web-console:serve" 115 | }, 116 | "configurations": { 117 | "production": { 118 | "devServerTarget": "web-console:serve:production" 119 | } 120 | } 121 | }, 122 | "lint": { 123 | "builder": "@angular-devkit/build-angular:tslint", 124 | "options": { 125 | "tsConfig": "e2e/tsconfig.e2e.json", 126 | "exclude": [ 127 | "**/node_modules/**" 128 | ] 129 | } 130 | } 131 | } 132 | } 133 | }, 134 | "defaultProject": "web-console" 135 | } -------------------------------------------------------------------------------- /ui/web-console/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /ui/web-console/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to web-console!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | })); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /ui/web-console/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ui/web-console/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /ui/web-console/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-console", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "~7.2.0", 15 | "@angular/common": "~7.2.0", 16 | "@angular/compiler": "~7.2.0", 17 | "@angular/core": "~7.2.0", 18 | "@angular/forms": "~7.2.0", 19 | "@angular/platform-browser": "~7.2.0", 20 | "@angular/platform-browser-dynamic": "~7.2.0", 21 | "@angular/router": "~7.2.0", 22 | "core-js": "^2.5.4", 23 | "rxjs": "~6.3.3", 24 | "tslib": "^1.9.0", 25 | "zone.js": "~0.8.26", 26 | "@ng-bootstrap/ng-bootstrap": "^4.2.0", 27 | "@swimlane/ngx-charts": "^12.0.1", 28 | "bootstrap": "^4.3.1", 29 | "chart.js": "^2.8.0", 30 | "font-awesome": "^4.7.0", 31 | "ng2-ace-editor": "^0.3.9", 32 | "ng2-charts": "2.2.5", 33 | "xterm": "^3.14.5" 34 | }, 35 | "devDependencies": { 36 | "@angular-devkit/build-angular": "~0.12.0", 37 | "@angular/cli": "~7.2.2", 38 | "@angular/compiler-cli": "~7.2.0", 39 | "@angular/language-service": "~7.2.0", 40 | "@types/node": "~8.9.4", 41 | "@types/jasmine": "~2.8.8", 42 | "@types/jasminewd2": "~2.0.3", 43 | "codelyzer": "~4.5.0", 44 | "jasmine-core": "~2.99.1", 45 | "jasmine-spec-reporter": "~4.2.1", 46 | "karma": "~3.1.1", 47 | "karma-chrome-launcher": "~2.2.0", 48 | "karma-coverage-istanbul-reporter": "~2.0.1", 49 | "karma-jasmine": "~1.1.2", 50 | "karma-jasmine-html-reporter": "^0.2.2", 51 | "protractor": "~5.4.0", 52 | "ts-node": "~7.0.0", 53 | "tslint": "~5.11.0", 54 | "typescript": "~3.2.2" 55 | } 56 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { HomeComponent } from './home/home.component'; 4 | import { FilesComponent } from './home/files/files.component'; 5 | import { SearchComponent } from './home/search/search.component'; 6 | import { MonitoringComponent } from './home/monitoring/monitoring.component'; 7 | import { EditorComponent } from './home/editor/editor.component'; 8 | import { LoginComponent } from './login/login.component'; 9 | import { AuthGuardGuard } from './guards/auth-guard.guard'; 10 | import { SettingsComponent } from './home/settings/settings.component'; 11 | 12 | const routes: Routes = [ 13 | { 14 | path: 'app', 15 | component: HomeComponent, 16 | canActivate: [AuthGuardGuard], 17 | // children: [ 18 | // { 19 | // path: 'files', 20 | // component: FilesComponent 21 | // }, 22 | // { 23 | // path: 'search', 24 | // component: SearchComponent 25 | // }, 26 | // { 27 | // path: 'monitoring', 28 | // component: MonitoringComponent 29 | // }, 30 | // { 31 | // path: 'editor', 32 | // component: EditorComponent 33 | // }, 34 | // { 35 | // path: 'settings', 36 | // component: SettingsComponent 37 | // } 38 | // ] 39 | }, 40 | { 41 | path: 'login', 42 | component: LoginComponent, 43 | }, 44 | { 45 | path: '', 46 | redirectTo: 'app', 47 | pathMatch: 'full' 48 | } 49 | ]; 50 | 51 | @NgModule({ 52 | imports: [RouterModule.forRoot(routes)], 53 | exports: [RouterModule] 54 | }) 55 | export class AppRoutingModule { } 56 | -------------------------------------------------------------------------------- /ui/web-console/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/app.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ui/web-console/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'web-shell-js'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('web-shell-js'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to web-shell-js!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /ui/web-console/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'web-shell-js'; 10 | } 11 | -------------------------------------------------------------------------------- /ui/web-console/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations' 3 | import { NgModule } from '@angular/core'; 4 | 5 | import { HttpClientModule } from '@angular/common/http'; 6 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 7 | 8 | import { ChartsModule } from 'ng2-charts'; 9 | import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; 10 | 11 | import { AppRoutingModule } from './app-routing.module'; 12 | import { AppComponent } from './app.component'; 13 | import { HomeComponent } from './home/home.component'; 14 | import { FilesComponent } from './home/files/files.component'; 15 | import { SearchComponent } from './home/search/search.component'; 16 | import { MonitoringComponent } from './home/monitoring/monitoring.component'; 17 | import { BrowserComponent } from './home/files/browser/browser.component'; 18 | import { UploaderProgressComponent } from './home/uploader-progress/uploader-progress.component'; 19 | import { TerminalComponent } from './home/terminal/terminal.component'; 20 | import { TreeComponent } from './home/files/tree/tree.component'; 21 | import { EditorComponent } from './home/editor/editor.component'; 22 | import { RenameComponent } from './home/files/browser/rename/rename.component'; 23 | import { InfoComponent } from './home/files/browser/info/info.component'; 24 | import { LoginComponent } from './login/login.component'; 25 | import { httpInterceptorProviders } from './intercepters/auth'; 26 | import { ImageViewerComponent } from './home/files/viewer/image-viewer/image-viewer.component'; 27 | import { MediaPlayerComponent } from './home/files/viewer/media-player/media-player.component'; 28 | import { SettingsComponent } from './home/settings/settings.component'; 29 | import { UnsupportedContentViewerComponent } from './home/files/viewer/unsupported-content-viewer/unsupported-content-viewer.component'; 30 | import { NewItemComponent } from './home/files/browser/new-item/new-item.component'; 31 | 32 | @NgModule({ 33 | declarations: [ 34 | AppComponent, 35 | HomeComponent, 36 | FilesComponent, 37 | SearchComponent, 38 | MonitoringComponent, 39 | TerminalComponent, 40 | BrowserComponent, 41 | UploaderProgressComponent, 42 | TreeComponent, 43 | EditorComponent, 44 | RenameComponent, 45 | InfoComponent, 46 | LoginComponent, 47 | ImageViewerComponent, 48 | MediaPlayerComponent, 49 | SettingsComponent, 50 | UnsupportedContentViewerComponent, 51 | NewItemComponent 52 | ], 53 | imports: [ 54 | BrowserModule, 55 | AppRoutingModule, 56 | FormsModule, 57 | ReactiveFormsModule, 58 | NgbModule, 59 | HttpClientModule, 60 | BrowserAnimationsModule, 61 | ChartsModule 62 | ], 63 | providers: [httpInterceptorProviders], 64 | bootstrap: [AppComponent] 65 | }) 66 | export class AppModule { } 67 | -------------------------------------------------------------------------------- /ui/web-console/src/app/data.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { DataService } from './data.service'; 4 | 5 | describe('DataService', () => { 6 | beforeEach(() => TestBed.configureTestingModule({})); 7 | 8 | it('should be created', () => { 9 | const service: DataService = TestBed.get(DataService); 10 | expect(service).toBeTruthy(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /ui/web-console/src/app/guards/auth-guard.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async, inject } from '@angular/core/testing'; 2 | 3 | import { AuthGuardGuard } from './auth-guard.guard'; 4 | 5 | describe('AuthGuardGuard', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [AuthGuardGuard] 9 | }); 10 | }); 11 | 12 | it('should ...', inject([AuthGuardGuard], (guard: AuthGuardGuard) => { 13 | expect(guard).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /ui/web-console/src/app/guards/auth-guard.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; 3 | import { Observable, Subject } from 'rxjs'; 4 | import { DataService } from '../data.service'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthGuardGuard implements CanActivate { 10 | private sub = new Subject(); 11 | constructor(private router: Router, private service: DataService) { 12 | } 13 | canActivate( 14 | next: ActivatedRouteSnapshot, 15 | state: RouterStateSnapshot): Observable | Promise | boolean { 16 | 17 | if (this.service.getJwtToken()) { 18 | this.service.checkToken().subscribe((resp: any) => { 19 | this.sub.next(true); 20 | }, (err: any) => { 21 | this.sub.next(false); 22 | this.service.clearOldToken(); 23 | this.router.navigate(["/login"]); 24 | }); 25 | return this.sub; 26 | } else { 27 | this.router.navigate(["/login"]); 28 | return; 29 | return false; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/editor/editor.component.css: -------------------------------------------------------------------------------- 1 | .desktop-tabs { 2 | display: none; 3 | border-bottom: 1px solid rgb(230, 230, 230); 4 | cursor: pointer; 5 | overflow: auto; 6 | } 7 | 8 | .mobile-tabs{ 9 | display: flex; 10 | } 11 | 12 | @media(min-width: 800px){ 13 | [class="desktop-tabs"]{ 14 | display: flex; 15 | } 16 | 17 | [class="mobile-tabs"]{ 18 | display: none; 19 | } 20 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/home/editor/editor.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
7 | {{getTabName(tab)}} 8 |
9 | 11 |
12 |
13 |
14 |
15 | 19 |
20 |
21 |
22 |
23 | 28 | 29 | 30 |
31 |
32 | 33 |
35 | No file is opened, please goto files and select some. 36 |
-------------------------------------------------------------------------------- /ui/web-console/src/app/home/editor/editor.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EditorComponent } from './editor.component'; 4 | 5 | describe('EditorComponent', () => { 6 | let component: EditorComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ EditorComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EditorComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/editor/editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; 2 | 3 | import * as ace from 'ace-builds'; 4 | //import 'ace-builds/src-noconflict/mode-javascript'; 5 | import 'ace-builds/src-noconflict/theme-github'; 6 | 7 | import 'ace-builds/src-noconflict/ext-language_tools'; 8 | import 'ace-builds/src-noconflict/ext-beautify'; 9 | import { DataService } from 'src/app/data.service'; 10 | import { ActivatedRoute, ParamMap } from '@angular/router'; 11 | 12 | const THEME = 'ace/theme/github'; 13 | const LANG = 'ace/mode/javascript'; 14 | 15 | @Component({ 16 | selector: 'app-editor', 17 | templateUrl: './editor.component.html', 18 | styleUrls: ['./editor.component.css'], 19 | host: { 20 | '(window:resize)': 'onResize($event)' 21 | } 22 | }) 23 | export class EditorComponent implements OnInit,AfterViewInit { 24 | 25 | @ViewChild('codeEditor') codeEditorElmRef: ElementRef; 26 | private codeEditor: ace.Ace.Editor; 27 | 28 | tabKeys: string[] = []; 29 | saving: boolean; 30 | 31 | constructor(public service: DataService, private route: ActivatedRoute) { } 32 | 33 | ngOnInit() { 34 | console.log("editor: ngOnInit "+this.codeEditorElmRef); 35 | this.getTabbedSessions(); 36 | 37 | this.service.viewTextRequests.subscribe(a=>{ 38 | this.getTabbedSessions(); 39 | this.loadSession(this.service.selectedEditorTab); 40 | }); 41 | 42 | // console.log("selected tab: " + this.service.selectedEditorTab+" tabkexs: "+this.tabKeys); 43 | if(this.tabKeys.length<1){ 44 | return; 45 | } 46 | // if(!this.codeEditorElmRef){ 47 | // return; 48 | // } 49 | // console.log("editor loaded"); 50 | 51 | 52 | // this.codeEditor.on("change", ()=> { 53 | // let doc:any = this.codeEditor.getSession().getDocument(); 54 | // let r:any=this.codeEditor.renderer; 55 | // let lineHeight = r.lineHeight; 56 | // console.log("doc.getLength(): "+doc.getLength()+" lineHeight: "+lineHeight) 57 | // this.codeEditorElmRef.nativeElement.style.height = +lineHeight * +doc.getLength() + "px"; 58 | // this.codeEditor.resize(); 59 | 60 | // this.codeEditor.setOption("maxLines", doc.getLength()); 61 | //}); 62 | 63 | // this.route.params.subscribe((params: ParamMap) => { 64 | // // console.log("route init of editor: "+JSON.stringify(Object.keys(this.service.editorContexts))); 65 | // // console.log("loading tabs") 66 | 67 | // }); 68 | 69 | 70 | } 71 | 72 | shouldShowTab(){ 73 | console.log("Tab count: "+this.tabKeys.length); 74 | return this.tabKeys.length>0; 75 | } 76 | 77 | ngAfterViewInit(){ 78 | console.log("editor: ngAfterViewInit "+this.codeEditorElmRef); 79 | ace.require('ace/ext/language_tools'); 80 | if(!this.codeEditorElmRef){ 81 | console.log("Code editor reference not found"); 82 | return; 83 | } 84 | const element = this.codeEditorElmRef.nativeElement; 85 | const editorOptions = this.getEditorOptions(); 86 | 87 | this.codeEditor = ace.edit(element, editorOptions); 88 | 89 | this.codeEditor.setTheme(THEME); 90 | //this.codeEditor.getSession().setMode(LANG); 91 | this.codeEditor.setShowFoldWidgets(true); // for the scope fold feature 92 | this.loadSession(this.service.selectedEditorTab); 93 | } 94 | 95 | // missing propery on EditorOptions 'enableBasicAutocompletion' so this is a wolkaround still using ts 96 | private getEditorOptions(): Partial & { enableBasicAutocompletion?: boolean; } { 97 | const basicEditorOptions: Partial = { 98 | highlightActiveLine: true, 99 | // minLines: 50, 100 | // maxLines: Infinity, 101 | autoScrollEditorIntoView: true 102 | }; 103 | 104 | const extraEditorOptions = { 105 | enableBasicAutocompletion: true 106 | }; 107 | const margedOptions = Object.assign(basicEditorOptions, extraEditorOptions); 108 | return margedOptions; 109 | } 110 | 111 | getTabbedSessions(): void { 112 | this.tabKeys = Object.keys(this.service.editorContexts); 113 | console.log("tab keys: "+JSON.stringify(this.tabKeys)) 114 | // this.service.editorContexts.forEach((value: EditorContext, key: string) => { 115 | // this.tabKeys.push(key); 116 | // console.log("tabbed key: " + key); 117 | // }); 118 | } 119 | 120 | selectItem(id: string) { 121 | console.log(id) 122 | this.loadSession(id); 123 | this.selectTab(id); 124 | } 125 | 126 | getTabName(key: string): string { 127 | return this.service.editorContexts[key].name; 128 | } 129 | 130 | loadSession(key: string) { 131 | if (key) { 132 | this.codeEditor.setSession(this.service.editorContexts[key].session); 133 | } 134 | } 135 | 136 | selectTab(key: string) { 137 | this.service.selectedEditorTab = key; 138 | } 139 | 140 | onResize(event: any) { 141 | if(!this.codeEditorElmRef){ 142 | return; 143 | } 144 | //console.log("window resized"); 145 | let doc: any = this.codeEditor.getSession().getDocument(); 146 | let r: any = this.codeEditor.renderer; 147 | let lineHeight = r.lineHeight; 148 | //console.log("doc.getLength(): "+doc.getLength()+" lineHeight: "+lineHeight) 149 | this.codeEditorElmRef.nativeElement.style.height = +lineHeight * +doc.getLength() + "px"; 150 | this.codeEditor.resize(); 151 | } 152 | 153 | closeTab(key: string) { 154 | let index: number = -1; 155 | for (let i = 0; i < this.tabKeys.length; i++) { 156 | if (this.tabKeys[i] == key) { 157 | index = i; 158 | this.tabKeys.splice(i, 1); 159 | delete this.service.editorContexts[key]; 160 | break; 161 | } 162 | } 163 | if (index >= this.tabKeys.length && this.tabKeys.length > 0) { 164 | this.service.selectedEditorTab = this.tabKeys[this.tabKeys.length - 1]; 165 | this.loadSession(this.service.selectedEditorTab); 166 | } 167 | } 168 | 169 | save() { 170 | this.saving = true; 171 | this.service.setText(this.service.selectedEditorTab).subscribe((resp: any) => { 172 | console.log("save done"); 173 | this.saving = false; 174 | },err=>{ 175 | this.saving = false; 176 | alert("Failed to save file"); 177 | }); 178 | } 179 | 180 | close(){ 181 | this.closeTab(this.service.selectedEditorTab); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/browser.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/files/browser/browser.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/browser.component.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/files/browser/browser.component.html -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/browser.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { BrowserComponent } from './browser.component'; 4 | 5 | describe('BrowserComponent', () => { 6 | let component: BrowserComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ BrowserComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(BrowserComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/browser.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { TabItem } from 'src/app/model/tab-item'; 3 | 4 | @Component({ 5 | selector: 'app-browser', 6 | templateUrl: './browser.component.html', 7 | styleUrls: ['./browser.component.css'] 8 | }) 9 | export class BrowserComponent implements OnInit { 10 | 11 | tabs:TabItem; 12 | constructor() { } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/info/info.component.css: -------------------------------------------------------------------------------- 1 | .info-item { 2 | display: flex; 3 | justify-content: space-between; 4 | padding-bottom: 10px; 5 | overflow: hidden; 6 | text-overflow: clip; 7 | white-space: nowrap; 8 | } 9 | 10 | .width-200px { 11 | max-width: 200px; 12 | width: 200px; 13 | } 14 | 15 | .info-wrapper { 16 | display: flex; 17 | flex-direction: column; 18 | background: white; 19 | padding: 20px; 20 | } 21 | 22 | .info-wrapper-container { 23 | overflow: auto; 24 | } 25 | 26 | @media (min-width:800px) { 27 | [class="info-wrapper-container"] { 28 | margin: auto; 29 | box-shadow: 5px 5px 5px black; 30 | } 31 | [class="info-wrapper"] { 32 | border-radius: 5px; 33 | } 34 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/info/info.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 |
5 |
6 | Information 7 |
8 |
9 | Name 10 | {{info.name}} 11 |
12 |
13 | Type 14 | {{info.type}} 15 |
16 |
17 | Size 18 | {{info.size}} 19 |
20 |
21 | Modified date 22 | {{info.lastModified|date:'medium'}} 23 |
24 |
25 | Creation date 26 | {{info.lastModified|date:'medium'}} 27 |
28 |
29 | Owner 30 | {{posixPerm.owner}} 31 |
32 |
33 | Owner access 34 | 40 |
41 |
42 | Group 43 | {{posixPerm.group}} 44 |
45 |
46 | Group access 47 | 53 |
54 |
55 | Others access 56 | 62 |
63 |
64 |
Executable
65 |
66 |
67 | 68 | 69 |
70 |
71 |
72 |
-------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/info/info.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { InfoComponent } from './info.component'; 4 | 5 | describe('InfoComponent', () => { 6 | let component: InfoComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ InfoComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(InfoComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/info/info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, OnChanges, Output, EventEmitter } from '@angular/core'; 2 | import { FileInfo } from 'src/app/model/file-info'; 3 | import { DataService } from 'src/app/data.service'; 4 | import { PosixPermissions } from 'src/app/model/posix-permissions'; 5 | import { FileItem } from 'src/app/model/file-item'; 6 | 7 | @Component({ 8 | selector: 'app-info', 9 | templateUrl: './info.component.html', 10 | styleUrls: ['./info.component.css'] 11 | }) 12 | export class InfoComponent implements OnInit { 13 | 14 | info: FileItem; 15 | posixPerm: PosixPermissions; 16 | 17 | @Output() 18 | dialogClosed = new EventEmitter(); 19 | 20 | constructor(private service: DataService) { } 21 | 22 | ngOnInit() { 23 | for (let i = 0; i < this.service.tabs[this.service.selectedTab].files.length; i++) { 24 | if (this.service.tabs[this.service.selectedTab].files[i].selected) { 25 | this.info = this.service.tabs[this.service.selectedTab].files[i]; 26 | break; 27 | } 28 | } 29 | if (this.info) { 30 | this.service.getPermissionDetails(this.info.path).subscribe((resp: PosixPermissions) => { 31 | this.posixPerm = resp; 32 | }); 33 | } 34 | } 35 | 36 | saveAndClose() { 37 | this.service.setPermissionDetails(this.info.path, this.posixPerm).subscribe((resp: PosixPermissions) => { 38 | this.dialogClosed.emit({}); 39 | }); 40 | } 41 | 42 | closeDialog() { 43 | this.dialogClosed.emit({}); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/new-item/new-item.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/files/browser/new-item/new-item.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/new-item/new-item.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 |
5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/new-item/new-item.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NewItemComponent } from './new-item.component'; 4 | 5 | describe('NewItemComponent', () => { 6 | let component: NewItemComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ NewItemComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(NewItemComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/new-item/new-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-new-item', 5 | templateUrl: './new-item.component.html', 6 | styleUrls: ['./new-item.component.css'] 7 | }) 8 | export class NewItemComponent implements OnInit { 9 | 10 | @Input() 11 | itemType: string; 12 | fileName: string; 13 | 14 | @Output() 15 | dialogCancelled = new EventEmitter(); 16 | 17 | @Output() 18 | fileNameEntered = new EventEmitter(); 19 | 20 | constructor() { } 21 | 22 | ngOnInit() { 23 | } 24 | 25 | createItem() { 26 | this.fileNameEntered.emit(this.fileName); 27 | } 28 | 29 | cancelDialog() { 30 | this.dialogCancelled.emit(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/rename/rename.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/files/browser/rename/rename.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/rename/rename.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 |
5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/rename/rename.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RenameComponent } from './rename.component'; 4 | 5 | describe('RenameComponent', () => { 6 | let component: RenameComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ RenameComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RenameComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/browser/rename/rename.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, OnChanges, Output, EventEmitter } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | import { DataService } from 'src/app/data.service'; 4 | 5 | @Component({ 6 | selector: 'app-rename', 7 | templateUrl: './rename.component.html', 8 | styleUrls: ['./rename.component.css'] 9 | }) 10 | export class RenameComponent implements OnInit { 11 | 12 | @Output() 13 | fileNameChanged = new EventEmitter(); 14 | 15 | @Output() 16 | dialogCancelled = new EventEmitter(); 17 | 18 | newFileName: string; 19 | 20 | constructor(private service:DataService) { } 21 | 22 | ngOnInit() { 23 | for (let i = 0; i < this.service.tabs[this.service.selectedTab].files.length; i++) { 24 | if (this.service.tabs[this.service.selectedTab].files[i].selected) { 25 | this.newFileName = this.service.tabs[this.service.selectedTab].files[i].name; 26 | console.log("new filename: "+this.newFileName); 27 | break; 28 | } 29 | } 30 | } 31 | 32 | renameFile() { 33 | this.fileNameChanged.emit(this.newFileName); 34 | } 35 | 36 | cancelDialog() { 37 | this.dialogCancelled.emit(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/files.component.css: -------------------------------------------------------------------------------- 1 | .selected-tab{ 2 | border-bottom: 3px solid dodgerblue; 3 | border-right: 1px solid rgb(230,230,230); 4 | } 5 | 6 | .normal-tab{ 7 | border-bottom: 3px solid rgb(240,240,240); 8 | border-right: 1px solid rgb(230,230,230); 9 | } 10 | 11 | .nav-tree { 12 | height: 100%; 13 | width: 250px; 14 | min-width: 250px; 15 | background: rgb(240, 240, 240); 16 | overflow: hidden; 17 | overflow-x: auto; 18 | overflow-y: auto; 19 | border-right: 1px solid rgb(230, 230, 230); 20 | padding-top: 10px; 21 | } 22 | 23 | .file-list { 24 | flex: 1; 25 | overflow: hidden; 26 | overflow-x: auto; 27 | overflow-y: auto; 28 | -webkit-overflow-scrolling: touch; 29 | position: relative; 30 | max-width: calc(100vw - 250px); 31 | } 32 | 33 | .file-list-header { 34 | display: flex; 35 | height: 51px; 36 | background: white; 37 | border-bottom: 1px solid rgb(230, 230, 230); 38 | cursor: pointer; 39 | position: sticky; 40 | top: 0px; 41 | box-shadow: 0px 0px 6px 0px rgb(230, 230, 230); 42 | } 43 | 44 | .file-list-item-row { 45 | display: flex; 46 | height: 51px; 47 | border-bottom: 1px solid rgb(240, 240, 240); 48 | overflow: hidden; 49 | } 50 | 51 | .desktop-file-list-first-item { 52 | flex: 2; 53 | overflow-x: hidden; 54 | line-height: 50px; 55 | display: flex; 56 | padding-left: 10px; 57 | } 58 | 59 | .desktop-file-list-other-item { 60 | flex: 1; 61 | line-height: 50px; 62 | overflow: hidden; 63 | text-overflow: ellipsis; 64 | white-space: nowrap; 65 | } 66 | 67 | .mobile-file-list-row { 68 | display: none; 69 | width: 100%; 70 | } 71 | 72 | .desktop-nav { 73 | display: flex; 74 | padding: 10px; 75 | padding-left: 15px; 76 | background: rgb(245, 245, 245); 77 | border-bottom: 1px solid rgb(230, 230, 230); 78 | flex-wrap: nowrap; 79 | overflow: hidden; 80 | } 81 | 82 | .mobile-nav { 83 | display: none; 84 | justify-content: space-around; 85 | flex-wrap: nowrap; 86 | overflow: hidden; 87 | background: rgb(245, 245, 245); 88 | border-top: 1px solid rgb(230, 230, 230); 89 | } 90 | 91 | .desktop-file-tabs { 92 | height: 50px; 93 | width: 100%; 94 | display: flex; 95 | background: rgb(240, 240, 240); 96 | border-bottom: 1px solid rgb(220,220,220); 97 | } 98 | 99 | .dropdown-ctx-menu { 100 | display: flex; 101 | width: 200px; 102 | flex-direction: column; 103 | position: fixed; 104 | right: 0px; 105 | max-height: 50vh; 106 | background: white; 107 | border: 1px solid rgb(200, 200, 200); 108 | box-shadow: 3px 3px 10px 0px rgb(200, 200, 200); 109 | z-index: 301; 110 | overflow-y: auto; 111 | } 112 | 113 | .noselect { 114 | -webkit-touch-callout: none; 115 | /* iOS Safari */ 116 | -webkit-user-select: none; 117 | /* Safari */ 118 | -khtml-user-select: none; 119 | /* Konqueror HTML */ 120 | -moz-user-select: none; 121 | /* Firefox */ 122 | -ms-user-select: none; 123 | /* Internet Explorer/Edge */ 124 | user-select: none; 125 | /* Non-prefixed version, currently 126 | supported by Chrome and Opera */ 127 | } 128 | 129 | @media (max-width:800px) { 130 | [class="nav-tree"] { 131 | display: none; 132 | } 133 | [class="file-list"] { 134 | max-width: 100vw; 135 | } 136 | [class="file-list-header"] { 137 | display: none; 138 | } 139 | [class*="file-list-content-wrapper"] { 140 | padding-top: 0px; 141 | } 142 | [class*="file-list-item-row"] { 143 | height: 80px; 144 | } 145 | [class="desktop-file-list-first-item"] { 146 | display: none; 147 | } 148 | [class="desktop-file-list-other-item"] { 149 | display: none; 150 | } 151 | [class="mobile-file-list-row"] { 152 | display: flex; 153 | } 154 | [class="desktop-file-list-other-item"] { 155 | display: none; 156 | } 157 | [class="desktop-nav"] { 158 | display: none; 159 | } 160 | [class="mobile-nav"] { 161 | display: flex; 162 | } 163 | [class*="desktop-file-tabs"] { 164 | display: none; 165 | } 166 | [class="dropdown-ctx-menu"] { 167 | bottom: 40px; 168 | } 169 | } 170 | 171 | @media (min-width:800px) { 172 | [class="dropdown-ctx-menu"] { 173 | top: 100px; 174 | } 175 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/files.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FilesComponent } from './files.component'; 4 | 5 | describe('FilesComponent', () => { 6 | let component: FilesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ FilesComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FilesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/tree/tree.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/files/tree/tree.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/tree/tree.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{model.name}} 13 |
14 |
16 | 17 |
18 | 19 | 20 |
23 |
Open in new tab
24 |
Open
25 |
Copy path
26 |
-------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/tree/tree.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TreeComponent } from './tree.component'; 4 | 5 | describe('TreeComponent', () => { 6 | let component: TreeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TreeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TreeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/tree/tree.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, OnChanges, ViewChild, ElementRef, OnDestroy, Output, EventEmitter } from '@angular/core'; 2 | import { DataService } from 'src/app/data.service'; 3 | import { NavigationTreeNode } from 'src/app/model/navigation-tree-node'; 4 | import { Subscriber, Subscription } from 'rxjs'; 5 | import { FolderTab } from 'src/app/model/folder-tab'; 6 | 7 | @Component({ 8 | selector: 'app-tree', 9 | templateUrl: './tree.component.html', 10 | styleUrls: ['./tree.component.css'] 11 | }) 12 | export class TreeComponent implements OnInit, OnChanges, OnDestroy { 13 | 14 | @Input() 15 | model: NavigationTreeNode; 16 | 17 | @Input() 18 | icon: string; 19 | 20 | @Input() 21 | parent: NavigationTreeNode; 22 | 23 | showCtx: boolean = false; 24 | 25 | menuEventSubsciption: Subscription; 26 | 27 | constructor(public service: DataService) { } 28 | 29 | ngOnInit() { 30 | } 31 | 32 | ngOnChanges() { 33 | if (this.model && this.model.expanded) { 34 | this.loadChildren(); 35 | } 36 | } 37 | 38 | ngOnDestroy() { 39 | if (this.menuEventSubsciption) { 40 | this.menuEventSubsciption.unsubscribe(); 41 | } 42 | } 43 | 44 | toggleState() { 45 | if (!this.model) { 46 | return; 47 | } 48 | if (this.model.leafNode) { 49 | return; 50 | } 51 | console.log("Togging state") 52 | this.model.expanded = !this.model.expanded; 53 | if (this.model.expanded) { 54 | this.loadChildren(); 55 | } 56 | } 57 | 58 | loadChildren() { 59 | if (!this.model.children) { 60 | if (!this.parent && this.model.name == "Home") { 61 | console.log("loading children for: " + this.model.name); 62 | this.service.getHomeChildren().subscribe((data: NavigationTreeNode[]) => { 63 | //console.log("loading children for: " + JSON.stringify(data)); 64 | this.model.children = data; 65 | }); 66 | } 67 | else if (this.parent) { 68 | console.log("loading children for: " + this.model.name); 69 | this.service.getDirectoryChildren(this.model.path).subscribe((data: NavigationTreeNode[]) => { 70 | //console.log("loading children for: " + JSON.stringify(data)); 71 | this.model.children = data; 72 | }); 73 | } 74 | else if (!this.parent && this.model.name == "File system") { 75 | console.log("loading children for: " + this.model.name); 76 | this.service.getFsChildren().subscribe((data: NavigationTreeNode[]) => { 77 | // console.log("loading children for: " + JSON.stringify(data)); 78 | this.model.children = data; 79 | }); 80 | } 81 | } 82 | } 83 | 84 | onClick() { 85 | if (this.model.path) { 86 | console.log("tree clicked: " + this.model.path); 87 | this.openInTab(); 88 | } 89 | } 90 | 91 | onContextMenu(a: any, c: any) { 92 | if (this.model.leafNode) { 93 | a.preventDefault(); 94 | return; 95 | } 96 | this.showCtx = true; 97 | c.style.display = 'flex'; 98 | 99 | console.log("Ww: " + window.innerWidth + " Wh: " + window.innerHeight + " height: " + c.offsetHeight); 100 | 101 | if (a.pageX > window.innerWidth / 2) { 102 | c.style.left = (a.pageX - c.offsetWidth) + "px"; 103 | } else { 104 | c.style.left = a.pageX + "px"; 105 | } 106 | 107 | if (a.pageY > window.innerHeight / 2) { 108 | c.style.top = (a.pageY - c.offsetHeight) + "px"; 109 | } else { 110 | c.style.top = a.pageY + "px"; 111 | } 112 | 113 | this.service.sharedMenuListener.next(); 114 | this.menuEventSubsciption = this.service.sharedMenuListener.subscribe((event) => { 115 | this.hideContextMenu(); 116 | this.menuEventSubsciption.unsubscribe(); 117 | this.menuEventSubsciption = null; 118 | }) 119 | console.log("context menu on tree clicked: " + this.model.path); 120 | a.preventDefault(); 121 | } 122 | 123 | hideContextMenu() { 124 | this.showCtx = false; 125 | console.log("Hiding context menu") 126 | } 127 | 128 | openInNewTab() { 129 | console.log("openInNewTab " + this.model.path); 130 | let tab: FolderTab = new FolderTab(); 131 | tab.currentDirectory = this.model.path; 132 | tab.folderName = this.model.name; 133 | tab.files = null; 134 | this.service.newTabListener.next(tab); 135 | } 136 | 137 | openInTab() { 138 | console.log("openInTab " + this.model.path); 139 | this.service.currentTabListener.next({ path: this.model.path, file: this.model.leafNode }); 140 | } 141 | 142 | copyPath() { 143 | console.log("copyPath " + this.model.path); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/image-viewer/image-viewer.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/files/viewer/image-viewer/image-viewer.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/image-viewer/image-viewer.component.html: -------------------------------------------------------------------------------- 1 |
3 | 4 |
5 | 6 |
7 |
-------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/image-viewer/image-viewer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ImageViewerComponent } from './image-viewer.component'; 4 | 5 | describe('ImageViewerComponent', () => { 6 | let component: ImageViewerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ImageViewerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ImageViewerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/image-viewer/image-viewer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, OnChanges, Output, EventEmitter } from '@angular/core'; 2 | import { environment } from 'src/environments/environment'; 3 | import { DataService } from 'src/app/data.service'; 4 | 5 | @Component({ 6 | selector: 'app-image-viewer', 7 | templateUrl: './image-viewer.component.html', 8 | styleUrls: ['./image-viewer.component.css'] 9 | }) 10 | export class ImageViewerComponent implements OnInit, OnChanges { 11 | 12 | @Input() 13 | url: string; 14 | src: string; 15 | 16 | @Output() 17 | componentClosed = new EventEmitter(); 18 | 19 | constructor(private service: DataService) { } 20 | 21 | ngOnInit() { 22 | console.log("image viewer init"); 23 | } 24 | 25 | ngOnChanges() { 26 | if (this.url) { 27 | this.src = environment.BIN_URL + "image/" + btoa(this.url) + "?token=" + this.service.getJwtToken(); 28 | console.log("image src " + this.src); 29 | } 30 | } 31 | 32 | close() { 33 | this.componentClosed.emit({}); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/media-player/media-player.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/files/viewer/media-player/media-player.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/media-player/media-player.component.html: -------------------------------------------------------------------------------- 1 |
3 | 6 |
7 | 8 |
9 |
10 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/media-player/media-player.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MediaPlayerComponent } from './media-player.component'; 4 | 5 | describe('MediaPlayerComponent', () => { 6 | let component: MediaPlayerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ MediaPlayerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MediaPlayerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/media-player/media-player.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core'; 2 | import { DataService } from 'src/app/data.service'; 3 | import { environment } from 'src/environments/environment'; 4 | 5 | @Component({ 6 | selector: 'app-media-player', 7 | templateUrl: './media-player.component.html', 8 | styleUrls: ['./media-player.component.css'] 9 | }) 10 | export class MediaPlayerComponent implements OnInit, OnChanges { 11 | @Input() 12 | url: string; 13 | src: string; 14 | 15 | @Output() 16 | componentClosed = new EventEmitter(); 17 | constructor(private service: DataService) { } 18 | 19 | ngOnInit() { 20 | } 21 | 22 | ngOnChanges() { 23 | if (this.url) { 24 | this.src = environment.BIN_URL + "blob/" + btoa(this.url) + "?token=" + this.service.getJwtToken(); 25 | console.log("image src " + this.src); 26 | } 27 | } 28 | 29 | close() { 30 | this.componentClosed.emit({}); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/unsupported-content-viewer/unsupported-content-viewer.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/files/viewer/unsupported-content-viewer/unsupported-content-viewer.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/unsupported-content-viewer/unsupported-content-viewer.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | {{file}} 5 |
6 |
7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 | 15 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/unsupported-content-viewer/unsupported-content-viewer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UnsupportedContentViewerComponent } from './unsupported-content-viewer.component'; 4 | 5 | describe('UnsupportedContentViewerComponent', () => { 6 | let component: UnsupportedContentViewerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ UnsupportedContentViewerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(UnsupportedContentViewerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/files/viewer/unsupported-content-viewer/unsupported-content-viewer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-unsupported-content-viewer', 5 | templateUrl: './unsupported-content-viewer.component.html', 6 | styleUrls: ['./unsupported-content-viewer.component.css'] 7 | }) 8 | export class UnsupportedContentViewerComponent implements OnInit { 9 | @Input() 10 | file: string; 11 | @Input() 12 | url: string; 13 | 14 | @Output() 15 | componentClosed = new EventEmitter(); 16 | 17 | @Output() 18 | onDownload = new EventEmitter(); 19 | 20 | @Output() 21 | onOpen = new EventEmitter(); 22 | 23 | constructor() { } 24 | 25 | ngOnInit() { 26 | } 27 | 28 | close() { 29 | this.componentClosed.emit({}); 30 | } 31 | 32 | download(){ 33 | this.onDownload.emit({}); 34 | } 35 | 36 | open(){ 37 | this.onOpen.emit({}); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | .fullscreen { 2 | width: 100vw; 3 | height: 100vh; 4 | position: fixed; 5 | bottom: 0px; 6 | right: 0px; 7 | display: flex; 8 | flex-direction: column; 9 | background: black; 10 | color: white; 11 | z-index: 100; 12 | } 13 | 14 | .minimized { 15 | width: 200px; 16 | position: fixed; 17 | right: 10px; 18 | bottom: 0px; 19 | color: white; 20 | background: black; 21 | border-top-left-radius: 5px; 22 | border-top-right-radius: 5px; 23 | } 24 | 25 | .rounded-top { 26 | border-top-left-radius: 10px; 27 | border-top-right-radius: 10px; 28 | } 29 | 30 | .normal { 31 | width: 740px; 32 | height: 450px; 33 | position: fixed; 34 | bottom: 0px; 35 | right: 10px; 36 | display: flex; 37 | flex-direction: column; 38 | background: black; 39 | color: white; 40 | z-index: 100; 41 | border-top-left-radius: 5px; 42 | border-top-right-radius: 5px; 43 | } 44 | 45 | .term-normal { 46 | height: 410px; 47 | } 48 | 49 | .term-fullscreen { 50 | height: calc(100vh - 40px); 51 | } 52 | 53 | 54 | .router-wrapper { 55 | padding-top: 55px; 56 | height: 100%; 57 | } 58 | 59 | .desktop-title { 60 | flex: 1; 61 | line-height: 55px; 62 | color: gray; 63 | font-size: 22px; 64 | } 65 | 66 | .mobile-title { 67 | flex: 1; 68 | line-height: 55px; 69 | color: gray; 70 | font-size: 22px; 71 | display: none; 72 | } 73 | 74 | .desktop-nav { 75 | height: 55px; 76 | /* background: rgb(33, 37, 43); */ 77 | background: rgb(36, 46, 43);; 78 | display: flex; 79 | justify-content: flex-end; 80 | box-shadow: 0px 3px 6px 0px rgb(200, 200, 200); 81 | margin-bottom: 5px; 82 | position: fixed; 83 | width: 100vw; 84 | z-index: 150; 85 | padding-left: 20px; 86 | padding-right: 20px; 87 | left: 0px; 88 | top: 0px; 89 | } 90 | 91 | .mobile-nav { 92 | background: rgb(33, 37, 43); 93 | display: none; 94 | justify-content: space-around; 95 | box-shadow: 0px 3px 6px 0px rgb(200, 200, 200); 96 | margin-bottom: 5px; 97 | position: fixed; 98 | width: 100vw; 99 | z-index: 150; 100 | padding-left: 20px; 101 | padding-right: 20px; 102 | left: 0px; 103 | top: 0px; 104 | } 105 | 106 | .desktop-terminal-wrapper { 107 | display: block; 108 | } 109 | 110 | .terminal-btn { 111 | position: fixed; 112 | right: 30px; 113 | bottom: 30px; 114 | border-radius: 50%; 115 | background: rgb(20, 20, 20); 116 | color: white; 117 | width: 50px; 118 | text-align: center; 119 | height: 50px; 120 | z-index: 300; 121 | box-shadow: 2px 2px 5px gray; 122 | } 123 | 124 | .active-link { 125 | background: rgb(62, 66, 81) 126 | } 127 | 128 | .device-keyboard { 129 | padding-right: 10px; 130 | cursor: pointer; 131 | color: white; 132 | display: none; 133 | } 134 | 135 | @media (max-width:800px) { 136 | [class="desktop-nav"] { 137 | display: none; 138 | } 139 | [class="mobile-nav"] { 140 | display: flex; 141 | } 142 | [class*="desktop-terminal-wrapper"] { 143 | display: none; 144 | } 145 | [class="terminal-btn"] { 146 | bottom: 60px; 147 | right: 30px; 148 | } 149 | [class*="desktop-title"] { 150 | display: none; 151 | } 152 | [class*="mobile-title"] { 153 | display: block; 154 | } 155 | [class="device-keyboard"] { 156 | display: block; 157 | } 158 | [class="term-fullscreen"]{ 159 | height: 50vh 160 | } 161 | } 162 | 163 | .bg-gradient { 164 | background: #3A1C71; 165 | /* fallback for old browsers */ 166 | background: -webkit-linear-gradient(to right, #FFAF7B, #D76D77, #3A1C71); 167 | /* Chrome 10-25, Safari 5.1-6 */ 168 | background: linear-gradient(to right, #FFAF7B, #D76D77, #3A1C71); 169 | /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ 170 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { TerminalSession } from '../model/terminal-session'; 4 | import { DataService } from '../data.service'; 5 | 6 | @Component({ 7 | selector: 'app-home', 8 | templateUrl: './home.component.html', 9 | styleUrls: ['./home.component.css'] 10 | }) 11 | export class HomeComponent implements OnInit { 12 | 13 | selectedIndex: number = 0; 14 | // tabs = [ 15 | // { 16 | // text: 'File browser', 17 | // url: '/app/files', 18 | // icon: 'fa-folder' 19 | // }, 20 | // { 21 | // text: 'Editor', 22 | // url: '/app/editor', 23 | // icon: 'fa-file' 24 | // }, 25 | // { 26 | // text: 'Search', 27 | // url: '/app/search', 28 | // icon: 'fa-search' 29 | // }, 30 | // { 31 | // text: 'Settings', 32 | // url: '/app/search', 33 | // icon: 'fa-cogs' 34 | // } 35 | // ]; 36 | 37 | // selectTab(i: number) { 38 | // this.selectedIndex = i; 39 | // this.router.navigate([this.tabs[i].url]); 40 | // } 41 | 42 | session: TerminalSession; 43 | 44 | mobileTerminalVisible: boolean = false; 45 | 46 | view: string; 47 | 48 | constructor(private router: Router, public service: DataService) { } 49 | 50 | ngOnInit() { 51 | this.service.currentViewChanger.subscribe((view: string) => { 52 | this.view = view; 53 | }) 54 | if (!this.service.terminalSession) { 55 | this.service.connect().subscribe((data: any) => { 56 | console.log("terminal created on server: " + JSON.stringify(data)); 57 | let instance = new TerminalSession(); 58 | instance.displayMode = 'normal'; 59 | let listener: any = (ev: any) => { 60 | instance.socket.removeEventListener('open', listener); 61 | console.log("Socket created: " + JSON.stringify(instance)); 62 | this.service.terminalSession = instance; 63 | this.session = instance; 64 | } 65 | let ws: WebSocket = instance.createSocket(data.id, this.service.getJwtToken()); 66 | ws.addEventListener('open', listener); 67 | ws.addEventListener('error', err => { 68 | console.log("Error: " + JSON.stringify(err)); 69 | }); 70 | }); 71 | } else { 72 | this.session = this.service.terminalSession; 73 | } 74 | } 75 | 76 | toggleFullscreen() { 77 | if (this.session.displayMode == 'fullscreen') { 78 | this.session.displayMode = this.session.lastDisplayMode; 79 | } else { 80 | this.session.lastDisplayMode = this.session.displayMode; 81 | this.session.displayMode = 'fullscreen'; 82 | } 83 | } 84 | 85 | toggleMinimize() { 86 | if (this.session.displayMode == 'minimized') { 87 | this.session.lastDisplayMode = this.session.displayMode; 88 | this.session.displayMode = 'normal'; 89 | } else { 90 | this.session.lastDisplayMode = this.session.displayMode; 91 | this.session.displayMode = 'minimized'; 92 | } 93 | } 94 | 95 | logout() { 96 | this.service.clearOldToken(); 97 | this.router.navigate(["/login"]); 98 | } 99 | 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/monitoring/monitoring.component.css: -------------------------------------------------------------------------------- 1 | .search-box{ 2 | border-bottom: 1px solid rgb(200,200,200); 3 | margin-right: 10px; 4 | } 5 | 6 | .search-text{ 7 | border: none; 8 | margin-left: 5px; 9 | } 10 | 11 | .search-text:focus{ 12 | outline: none; 13 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/home/monitoring/monitoring.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MonitoringComponent } from './monitoring.component'; 4 | 5 | describe('MonitoringComponent', () => { 6 | let component: MonitoringComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ MonitoringComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MonitoringComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/search/search.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/search/search.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/home/search/search.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 7 | We will try to find all the files/folders that whose name 8 | contains above text 9 |
10 |
11 | 12 | 13 | We will look for the matching files or folders only in this 14 | location 15 |
16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 | Loading... 24 |
25 | Searching 26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 | {{getItemCount()}} 34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | {{file.name}} 45 |
46 |
47 | {{file.path}} 48 |
49 |
50 | 58 |
59 |
60 |
61 | 62 |
64 |
65 |
66 | Loading... 67 |
68 |
69 |
-------------------------------------------------------------------------------- /ui/web-console/src/app/home/search/search.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SearchComponent } from './search.component'; 4 | 5 | describe('SearchComponent', () => { 6 | let component: SearchComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SearchComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SearchComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/search/search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { DataService } from 'src/app/data.service'; 3 | import { SearchContext } from 'src/app/model/search-context'; 4 | import { EditorContext } from 'src/app/model/editor-context'; 5 | import { utility } from '../../utility/utils'; 6 | import { Router } from '@angular/router'; 7 | import { FileItem } from 'src/app/model/file-item'; 8 | 9 | @Component({ 10 | selector: 'app-search', 11 | templateUrl: './search.component.html', 12 | styleUrls: ['./search.component.css'] 13 | }) 14 | export class SearchComponent implements OnInit, OnDestroy { 15 | ctx: SearchContext; 16 | timer: any; 17 | loading: boolean; 18 | 19 | constructor(public service: DataService, private router: Router) { } 20 | 21 | ngOnInit() { 22 | this.ctx = this.service.searchContext; 23 | if (this.ctx.searching && !this.ctx.isDone) { 24 | this.timer = setInterval(() => { 25 | this.getUpdates(); 26 | }, 3000); 27 | } 28 | } 29 | 30 | ngOnDestroy() { 31 | if (this.timer) { 32 | clearInterval(this.timer); 33 | } 34 | } 35 | 36 | cancel() { 37 | this.service.cancelSearch(this.ctx.id).subscribe((res) => { 38 | console.log("cancelled success!"); 39 | if (this.timer) { 40 | clearInterval(this.timer); 41 | this.timer = null; 42 | this.ctx.isDone = true; 43 | } 44 | }); 45 | } 46 | 47 | search(txt: string, folder: string) { 48 | console.log("Search: " + txt + " folder: " + folder); 49 | let ctx = new SearchContext(); 50 | ctx.isDone = false; 51 | ctx.searchText = txt; 52 | ctx.files = []; 53 | ctx.folders = []; 54 | ctx.folder = folder; 55 | ctx.searching = true; 56 | this.ctx = ctx; 57 | 58 | this.service.startSearch(folder, txt).subscribe((result: any) => { 59 | let id: string = result.id; 60 | console.log("id of search: " + id); 61 | this.ctx.id = id; 62 | this.service.searchContext = ctx; 63 | this.timer = setInterval(() => { 64 | this.getUpdates(); 65 | }, 3000); 66 | }); 67 | } 68 | 69 | getUpdates() { 70 | this.service.getSearchResults(this.ctx.id, this.ctx.folders.length, this.ctx.files.length).subscribe((result: any) => { 71 | this.ctx.folders = this.ctx.folders.concat(result.folders); 72 | this.ctx.files = this.ctx.files.concat(result.files); 73 | this.ctx.isDone = result.done; 74 | if (this.ctx.isDone) { 75 | clearInterval(this.timer); 76 | this.timer = null; 77 | } 78 | console.log(JSON.stringify(this.ctx)); 79 | }); 80 | } 81 | 82 | openFile(file: FileItem) { 83 | this.service.fileOpenRequests.next(file); 84 | this.service.currentViewChanger.next(null); 85 | // this.loading = true; 86 | // console.log("Opening file: " + file) 87 | // this.service.getText(file).subscribe((text: string) => { 88 | // this.service.selectedEditorTab = file; 89 | // let ctx = new EditorContext(utility.getFileName(file), file, text); 90 | // ctx.session.setUseWrapMode(false); 91 | // this.service.editorContexts[file] = ctx; 92 | // this.loading = false; 93 | // console.log("before route init of editor: " + JSON.stringify(Object.keys(this.service.editorContexts))); 94 | // this.router.navigate(["/app/editor"]); 95 | // }); 96 | } 97 | 98 | // openFolder(folder: string) { 99 | // this.service.tabs.push({ 100 | // files: null, 101 | // currentDirectory: folder, 102 | // selected: true, 103 | // folderName: utility.getFileName(folder), 104 | // posix: false 105 | // }); 106 | // this.service.selectedTab = this.service.tabs.length - 1; 107 | // this.router.navigate(["/app/files"]); 108 | // } 109 | 110 | getItemCount() { 111 | let count = 0; 112 | if (this.ctx.files) { 113 | count += this.ctx.files.length; 114 | } 115 | if (this.ctx.folders) { 116 | count += this.ctx.folders.length; 117 | } 118 | return "Total " + count + " item(s) found."; 119 | } 120 | 121 | newSearch() { 122 | this.ctx.searching = false; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/settings/settings.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/settings/settings.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/home/settings/settings.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 |
5 | 6 | 7 |
8 |
9 | 10 | 11 |
12 |
13 | 14 | 15 |
16 |
17 | 18 | 19 |
20 | 24 |
25 | {{error}} 26 |
27 |
28 |
-------------------------------------------------------------------------------- /ui/web-console/src/app/home/settings/settings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SettingsComponent } from './settings.component'; 4 | 5 | describe('SettingsComponent', () => { 6 | let component: SettingsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SettingsComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SettingsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { DataService } from 'src/app/data.service'; 3 | 4 | @Component({ 5 | selector: 'app-settings', 6 | templateUrl: './settings.component.html', 7 | styleUrls: ['./settings.component.css'] 8 | }) 9 | export class SettingsComponent implements OnInit { 10 | 11 | userName: string; 12 | password: string; 13 | shell: string; 14 | error: string; 15 | saving: boolean; 16 | 17 | constructor(private service: DataService) { } 18 | 19 | ngOnInit() { 20 | this.service.getConfig().subscribe((res: any) => { 21 | this.userName = res["app.default-user"]; 22 | this.shell = res["app.default-shell"]; 23 | }) 24 | } 25 | 26 | save() { 27 | if (!this.userName) { 28 | this.error = "Please enter username"; 29 | return; 30 | } 31 | 32 | if (!this.shell) { 33 | this.error = "Please enter shell"; 34 | return; 35 | } 36 | this.saving = true; 37 | this.error = null; 38 | let obj = {}; 39 | obj["app.default-user"] = this.userName; 40 | if (this.password && this.password.length > 0) { 41 | obj["app.default-pass"] = this.password; 42 | } 43 | obj["app.default-shell"] = this.shell; 44 | 45 | console.log(JSON.stringify(obj)) 46 | 47 | this.service.setConfig(obj).subscribe((res: any) => { 48 | this.saving = false; 49 | this.error = null; 50 | }, err => { 51 | this.saving = false; 52 | this.error = "Error saving configuration"; 53 | }) 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/terminal/terminal.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/app/home/terminal/terminal.component.css -------------------------------------------------------------------------------- /ui/web-console/src/app/home/terminal/terminal.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |
-------------------------------------------------------------------------------- /ui/web-console/src/app/home/terminal/terminal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TerminalComponent } from './terminal.component'; 4 | 5 | describe('TerminalComponent', () => { 6 | let component: TerminalComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TerminalComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TerminalComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/terminal/terminal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChild, ElementRef, OnDestroy, AfterViewInit, Input, OnChanges } from '@angular/core'; 2 | import { Terminal, ITerminalOptions } from 'xterm'; 3 | import * as attach from 'xterm/lib/addons/attach/attach' 4 | import * as fit from 'xterm/lib/addons/fit/fit' 5 | import { container } from '@angular/core/src/render3'; 6 | import { DataService } from '../../data.service'; 7 | import { Observable } from 'rxjs'; 8 | 9 | @Component({ 10 | selector: 'app-terminal', 11 | templateUrl: './terminal.component.html', 12 | styleUrls: ['./terminal.component.css'], 13 | host: { 14 | '(window:resize)': 'onResize($event)' 15 | } 16 | }) 17 | export class TerminalComponent implements OnInit, OnDestroy, AfterViewInit, OnChanges { 18 | 19 | @ViewChild('terminal') 20 | container: ElementRef; 21 | 22 | @Input() 23 | public socket: WebSocket; 24 | 25 | @Input() 26 | public oldText: string 27 | 28 | @Input() 29 | public appId: string; 30 | 31 | @Input() 32 | displayMode: string; 33 | 34 | viewInit: boolean = false; 35 | 36 | lastDisplayMode: string; 37 | 38 | // @ViewChild('parent') 39 | // parent: ElementRef; 40 | 41 | private xterm: any; 42 | 43 | private textInit: boolean = false; 44 | private socketAttached: boolean = false; 45 | 46 | constructor(public service: DataService) { 47 | console.log("constructor called") 48 | Terminal.applyAddon(attach); 49 | Terminal.applyAddon(fit); 50 | 51 | this.xterm = new Terminal({ 52 | convertEol: false, 53 | fontFamily: `"Courier New", Courier, monospace`, 54 | rendererType: 'canvas', 55 | cols: 80, 56 | rows: 24 57 | }); 58 | 59 | } 60 | 61 | ngOnChanges() { 62 | if ((!this.textInit) && this.oldText) { 63 | this.xterm.write(this.oldText); 64 | this.textInit = true; 65 | } 66 | 67 | if ((!this.socketAttached) && this.socket) { 68 | console.log("attaching socket") 69 | this.xterm.attach(this.socket); 70 | this.socketAttached = true; 71 | } 72 | 73 | console.log("this.displayMode: "+this.displayMode+" this.lastDisplayMode: "+this.lastDisplayMode); 74 | 75 | if (this.displayMode != this.lastDisplayMode) { 76 | if (this.viewInit) { 77 | console.log("fitting") 78 | this.xterm.fit(); 79 | this.lastDisplayMode = this.displayMode; 80 | } 81 | } 82 | } 83 | 84 | ngOnInit() { 85 | console.log("terminal component ngOnInit"); 86 | } 87 | 88 | ngOnDestroy() { 89 | if (this.socketAttached && this.socket) { 90 | this.xterm.detach(this.socket); 91 | } 92 | 93 | if (this.xterm) { 94 | this.xterm.dispose(); 95 | } 96 | 97 | } 98 | 99 | ngAfterViewInit() { 100 | let el = this.container.nativeElement as HTMLElement; 101 | this.xterm.open(el); 102 | this.xterm.fit(); 103 | let rows = this.xterm.proposeGeometry().rows; 104 | let cols = this.xterm.proposeGeometry().cols; 105 | console.log("Rows: " + rows + " cols: " + cols + " real-rows: " + this.xterm.getOption("rows") + " real-cols: " + this.xterm.getOption("cols")); 106 | this.service.resizePty(this.appId, cols, rows, el.offsetWidth, el.offsetHeight); 107 | this.viewInit = true; 108 | } 109 | 110 | onResize(event: any) { 111 | if (this.displayMode == 'normal' && this.lastDisplayMode == 'normal') { 112 | return; 113 | } 114 | let el = this.container.nativeElement as HTMLElement; 115 | this.xterm.fit(); 116 | let rows = this.xterm.proposeGeometry().rows; 117 | let cols = this.xterm.proposeGeometry().cols; 118 | this.service.resizePty(this.appId, cols, rows, el.offsetWidth, el.offsetHeight); 119 | console.log("after window resize - Rows: " + rows + " cols: " + cols + " real-rows: " + this.xterm.getOption("rows") + " real-cols: " + this.xterm.getOption("cols")); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/uploader-progress/uploader-progress.component.css: -------------------------------------------------------------------------------- 1 | .desktop-label { 2 | color: white; 3 | line-height: 55px; 4 | padding-left: 10px; 5 | padding-right: 10px; 6 | border-left: 1px solid black; 7 | min-width: 100px; 8 | text-align: center; 9 | position: relative; 10 | } 11 | 12 | .mobile-label { 13 | cursor: pointer; 14 | line-height: 55px; 15 | padding-left: 15px; 16 | padding-right: 15px; 17 | color: gray; 18 | text-align: center; 19 | display: none; 20 | } 21 | 22 | .popup-close { 23 | display: flex; 24 | justify-content: space-between 25 | } 26 | 27 | .progress-popup { 28 | position: fixed; 29 | right: 0px; 30 | border-left: 1px solid rgb(230, 230, 230); 31 | color: black; 32 | background: white; 33 | top: 56px; 34 | height: calc(100vh - 55px); 35 | width: 400px; 36 | box-shadow: -3px 3px 3px 0px rgb(230, 230, 230); 37 | z-index: 101; 38 | } 39 | 40 | @media (max-width:800px) { 41 | [class="desktop-label"] { 42 | display: none; 43 | } 44 | [class="mobile-label"] { 45 | display: inline; 46 | } 47 | [class="progress-popup"] { 48 | top: 0px; 49 | height: 100vh; 50 | width: 100vw; 51 | } 52 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/home/uploader-progress/uploader-progress.component.html: -------------------------------------------------------------------------------- 1 |
2 | Transfers 3 | {{getTransferCount()}} 4 |
5 | 6 |
7 | 8 | {{getTransferCount()}} 9 |
10 | 11 |
12 | 13 | 14 | 21 | 22 |
24 |
25 | 26 |
27 |
29 |
31 | {{upload.name}} 32 |
33 |
34 | Error: Uploading failed 35 | 36 | 37 |
38 |
39 |
40 | × 41 |
42 |
43 |
45 |
47 |
49 | {{fileop.name}} 50 |
51 |
52 | Error: Operation failed 53 | 54 | 55 |
56 |
57 |
59 | × 60 |
61 |
62 |
-------------------------------------------------------------------------------- /ui/web-console/src/app/home/uploader-progress/uploader-progress.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UploaderProgressComponent } from './uploader-progress.component'; 4 | 5 | describe('UploaderProgressComponent', () => { 6 | let component: UploaderProgressComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ UploaderProgressComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(UploaderProgressComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/home/uploader-progress/uploader-progress.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { DataService } from 'src/app/data.service'; 3 | 4 | @Component({ 5 | selector: 'app-uploader-progress', 6 | templateUrl: './uploader-progress.component.html', 7 | styleUrls: ['./uploader-progress.component.css'] 8 | }) 9 | export class UploaderProgressComponent implements OnInit { 10 | showPopup: boolean = false; 11 | hoverIndex: number; 12 | constructor(public service: DataService) { } 13 | 14 | ngOnInit() { 15 | } 16 | 17 | hasAnyTransfers() { 18 | let t: boolean = (this.service.uploads && this.service.uploads.length > 0) || (this.service.fileOperations && this.service.fileOperations.length > 0); 19 | return t; 20 | } 21 | 22 | getTransferCount() { 23 | let c: number = 0; 24 | if (this.service.uploads && this.service.uploads.length > 0) { 25 | c += this.service.uploads.length; 26 | } 27 | if (this.service.fileOperations && this.service.fileOperations.length > 0) { 28 | c += this.service.fileOperations.length; 29 | } 30 | return c; 31 | } 32 | 33 | togglePopup() { 34 | console.log("toggle popup"); 35 | this.showPopup = !this.showPopup; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /ui/web-console/src/app/intercepters/auth.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http'; 3 | import { DataService } from '../data.service'; 4 | 5 | import { HTTP_INTERCEPTORS } from '@angular/common/http'; 6 | 7 | @Injectable() 8 | export class AuthInterceptor implements HttpInterceptor { 9 | 10 | constructor(private service: DataService) { } 11 | 12 | intercept(req: HttpRequest, next: HttpHandler) { 13 | // Get the auth token from the service. 14 | const authToken = this.service.getJwtToken(); 15 | 16 | if (authToken) { 17 | // Clone the request and replace the original headers with 18 | // cloned headers, updated with the authorization. 19 | const authReq = req.clone({ 20 | headers: req.headers.set('Authorization', "Bearer " + authToken) 21 | }); 22 | 23 | // send cloned request with header to the next handler. 24 | return next.handle(authReq); 25 | } else { 26 | return next.handle(req); 27 | } 28 | } 29 | } 30 | 31 | export const httpInterceptorProviders = [ 32 | { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, 33 | ]; -------------------------------------------------------------------------------- /ui/web-console/src/app/login/login.component.css: -------------------------------------------------------------------------------- 1 | .bg-gradient { 2 | background: #3A1C71; 3 | /* fallback for old browsers */ 4 | background: -webkit-linear-gradient(to right, #FFAF7B, #D76D77, #3A1C71); 5 | /* Chrome 10-25, Safari 5.1-6 */ 6 | background: linear-gradient(to right, #FFAF7B, #D76D77, #3A1C71); 7 | /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ 8 | } 9 | 10 | .container { 11 | display: block; 12 | position: relative; 13 | padding-left: 35px; 14 | margin-bottom: 12px; 15 | cursor: pointer; 16 | -webkit-user-select: none; 17 | -moz-user-select: none; 18 | -ms-user-select: none; 19 | user-select: none; 20 | } 21 | 22 | /* Hide the browser's default checkbox */ 23 | 24 | .container input { 25 | position: absolute; 26 | opacity: 0; 27 | cursor: pointer; 28 | height: 0; 29 | width: 0; 30 | } 31 | 32 | /* Create a custom checkbox */ 33 | 34 | .checkmark { 35 | position: absolute; 36 | top: 0; 37 | left: 0; 38 | height: 25px; 39 | width: 25px; 40 | background-color: #eee; 41 | } 42 | 43 | /* On mouse-over, add a grey background color */ 44 | 45 | .container:hover input~.checkmark { 46 | background-color: #ccc; 47 | } 48 | 49 | /* When the checkbox is checked, add a blue background */ 50 | 51 | .container input:checked~.checkmark { 52 | background-color: #2196F3; 53 | } 54 | 55 | /* Create the checkmark/indicator (hidden when not checked) */ 56 | 57 | .checkmark:after { 58 | content: ""; 59 | position: absolute; 60 | display: none; 61 | } 62 | 63 | /* Show the checkmark when checked */ 64 | 65 | .container input:checked~.checkmark:after { 66 | display: block; 67 | } 68 | 69 | /* Style the checkmark/indicator */ 70 | 71 | .container .checkmark:after { 72 | left: 9px; 73 | top: 5px; 74 | width: 5px; 75 | height: 10px; 76 | border: solid white; 77 | border-width: 0 3px 3px 0; 78 | -webkit-transform: rotate(45deg); 79 | -ms-transform: rotate(45deg); 80 | transform: rotate(45deg); 81 | } 82 | 83 | -------------------------------------------------------------------------------- /ui/web-console/src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | 5 |
7 | Easy Cloud Shell 8 |
9 |
11 |
12 | Sign In 13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 | 24 | 28 | 36 |
37 | {{errorMessage}} 38 |
39 |
40 |
41 |
-------------------------------------------------------------------------------- /ui/web-console/src/app/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { LoginComponent } from './login.component'; 4 | 5 | describe('LoginComponent', () => { 6 | let component: LoginComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ LoginComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(LoginComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /ui/web-console/src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { DataService } from '../data.service'; 4 | import { HttpErrorResponse } from '@angular/common/http'; 5 | 6 | @Component({ 7 | selector: 'app-login', 8 | templateUrl: './login.component.html', 9 | styleUrls: ['./login.component.css'] 10 | }) 11 | export class LoginComponent implements OnInit { 12 | errorMessage: string; 13 | busy: boolean = false; 14 | constructor(private router: Router, public service: DataService) { } 15 | 16 | ngOnInit() { 17 | } 18 | 19 | signIn(user: string, pass: string, chk: boolean) { 20 | console.log(user + " " + pass + " " + chk); 21 | this.busy = true; 22 | this.service.signIn(user, pass).subscribe((resp: any) => { 23 | console.log(resp.token); 24 | this.service.posix = resp.posix; 25 | this.service.setToken(resp.token, chk); 26 | this.router.navigate(["/"]); 27 | }, (error: HttpErrorResponse) => { 28 | if (error.status == 401) { 29 | this.errorMessage = "Invalid username/password"; 30 | } else { 31 | this.errorMessage = "Unable to login, error: " + error.statusText; 32 | } 33 | this.busy = false; 34 | }); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /ui/web-console/src/app/model/editor-context.ts: -------------------------------------------------------------------------------- 1 | import * as ace from 'ace-builds'; 2 | 3 | export class EditorContext { 4 | session: ace.Ace.EditSession; 5 | 6 | public constructor(public name: string, public key: string, text: string) { 7 | this.session = new ace.EditSession(text); 8 | } 9 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/file-info.ts: -------------------------------------------------------------------------------- 1 | export class FileInfo { 2 | posixFile: boolean; 3 | name: string; 4 | size: number; 5 | modified: Date; 6 | created: Date; 7 | type: string; 8 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/file-item.ts: -------------------------------------------------------------------------------- 1 | export class FileItem { 2 | name: string; 3 | size: number; 4 | lastModified: Date; 5 | permission: number; 6 | permissionString: string; 7 | user: string; 8 | path: string; 9 | type: string; 10 | selected:boolean; 11 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/file-operation-item.ts: -------------------------------------------------------------------------------- 1 | export class FileOperationItem { 2 | id: string; 3 | name: string; 4 | progress: number; 5 | errors: string; 6 | hasError: boolean; 7 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/file-upload-item.ts: -------------------------------------------------------------------------------- 1 | import { Subscription } from "rxjs"; 2 | 3 | export class FileUploadItem { 4 | name: string; 5 | size: number; 6 | chunkCount: number; 7 | chunkFinished: number; 8 | percent: number; 9 | bytesUploaded: number; 10 | file: Blob; 11 | subscription: Subscription; 12 | relativePath: string; 13 | folder:string; 14 | status: string; 15 | 16 | constructor() { 17 | this.percent = 0; 18 | this.chunkFinished = 0; 19 | this.chunkCount = 0; 20 | this.bytesUploaded = 0; 21 | } 22 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/folder-tab.ts: -------------------------------------------------------------------------------- 1 | import { FileItem } from './file-item'; 2 | 3 | export class FolderTab { 4 | files: FileItem[] = []; 5 | currentDirectory: string; 6 | selected: boolean; 7 | folderName: string; 8 | posix: boolean; 9 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/folder-upload-item.ts: -------------------------------------------------------------------------------- 1 | import { Subscription } from "rxjs"; 2 | 3 | export class FolderUploadItem { 4 | name: string; 5 | size: number; 6 | percent: number; 7 | bytesUploaded: number; 8 | files: Blob[]; 9 | relatvePaths: string[]; 10 | status: string; 11 | needle: number; 12 | basePath: string; 13 | subscription: Subscription; 14 | 15 | constructor() { 16 | this.percent = 0; 17 | this.bytesUploaded = 0; 18 | } 19 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/navigation-tree-node.ts: -------------------------------------------------------------------------------- 1 | export class NavigationTreeNode { 2 | name: string; 3 | path: string; 4 | expanded: boolean; 5 | leafNode: false; 6 | children: NavigationTreeNode[]; 7 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/posix-permissions.ts: -------------------------------------------------------------------------------- 1 | export class PosixPermissions { 2 | group: string; 3 | owner: string; 4 | ownerAccess: string; 5 | groupAccess: string; 6 | otherAccess: string; 7 | executable: boolean; 8 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/search-context.ts: -------------------------------------------------------------------------------- 1 | import { FileItem } from './file-item'; 2 | 3 | export class SearchContext { 4 | id: string; 5 | searchText: string; 6 | files: FileItem[]=[]; 7 | folders: FileItem[]=[]; 8 | searching: boolean; 9 | isDone: boolean; 10 | folder: string; 11 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/tab-item.ts: -------------------------------------------------------------------------------- 1 | import { FileItem } from './file-item'; 2 | 3 | export class TabItem { 4 | name: string; 5 | path: string; 6 | files: FileItem[]; 7 | selected: boolean; 8 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/model/terminal-session.ts: -------------------------------------------------------------------------------- 1 | import { environment } from "../../environments/environment"; 2 | 3 | export class TerminalSession { 4 | socket: WebSocket; 5 | appId: string; 6 | bufferText: string; 7 | displayMode: string; 8 | lastDisplayMode: string; 9 | 10 | getWS_Url(): string { 11 | console.log("Return prod ws url") 12 | return "wss://" + window.location.host + "/"; 13 | } 14 | 15 | public createSocket(id: string, token: string): WebSocket { 16 | this.appId = id; 17 | this.socket = new WebSocket((environment.production ? this.getWS_Url() : environment.TERMINAL_URL) + 'term?id=' + id + '&token=' + token); 18 | this.socket.addEventListener('message', (ev: MessageEvent) => { 19 | if (!this.bufferText) { 20 | this.bufferText = ev.data; 21 | } else { 22 | this.bufferText += ev.data; 23 | } 24 | }); 25 | return this.socket; 26 | } 27 | } -------------------------------------------------------------------------------- /ui/web-console/src/app/utility/utils.ts: -------------------------------------------------------------------------------- 1 | const GB = 1024 * 1000 * 1000; 2 | const MB = 1024 * 1000; 3 | const KB = 1024; 4 | export const utility = { 5 | 6 | getFileName(path: string): string { 7 | let fragments = path.split(/\\|\//); 8 | return fragments.pop(); 9 | }, 10 | 11 | formatSize(sz: number): string { 12 | if (sz > GB) { 13 | return (sz / GB).toFixed(1) + " GB"; 14 | } else if (sz > MB) { 15 | return (sz / MB).toFixed(1) + " MB"; 16 | } else if (sz > KB) { 17 | return (sz / KB).toFixed(1) + " KB"; 18 | } else { 19 | return sz + " B"; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /ui/web-console/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/assets/.gitkeep -------------------------------------------------------------------------------- /ui/web-console/src/assets/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/assets/file.png -------------------------------------------------------------------------------- /ui/web-console/src/assets/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "/home/subhro", 3 | "files": [ 4 | { 5 | "name": "abc", 6 | "type": "File", 7 | "size": 1000, 8 | "lastModified": "12-01-2019", 9 | "permission": "rwxrwxrwx", 10 | "permissionString": "rwxrwxrwx", 11 | "user": "subhro", 12 | "path": "/home/subhro/abc" 13 | }, 14 | { 15 | "name": "xyz", 16 | "type": "Directory", 17 | "size": 0, 18 | "lastModified": "12-01-2019", 19 | "permission": "rwxrwxrwx", 20 | "permissionString": "rwxrwxrwx", 21 | "user": "subhro", 22 | "path": "/home/subhro/xyz" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /ui/web-console/src/assets/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/assets/folder.png -------------------------------------------------------------------------------- /ui/web-console/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/assets/logo.png -------------------------------------------------------------------------------- /ui/web-console/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /ui/web-console/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | BASE_URL: "/api/", 4 | BIN_URL: "/bin/", 5 | TERMINAL_URL: "wss://192.168.56.106:8055/", 6 | }; 7 | -------------------------------------------------------------------------------- /ui/web-console/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | BASE_URL: "https://localhost:8055/api/", 8 | BIN_URL: "https://localhost:8055/bin/", //"http://192.168.56.106:8055/api/",// 9 | TERMINAL_URL: "wss://localhost:8055/"//"ws://192.168.56.106:8055/", 10 | }; 11 | 12 | /* 13 | * For easier debugging in development mode, you can import the following file 14 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 15 | * 16 | * This import should be commented out in production mode because it will have a negative impact 17 | * on performance if an error is thrown. 18 | */ 19 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 20 | -------------------------------------------------------------------------------- /ui/web-console/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/subhra74/linux-web-console/0591902de1bd5c820d3cae0f02b7ec6e8834c214/ui/web-console/src/favicon.ico -------------------------------------------------------------------------------- /ui/web-console/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Easy Cloud Shell 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ui/web-console/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /ui/web-console/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /ui/web-console/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills. 22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot 23 | */ 24 | 25 | // import 'core-js/es6/symbol'; 26 | // import 'core-js/es6/object'; 27 | // import 'core-js/es6/function'; 28 | // import 'core-js/es6/parse-int'; 29 | // import 'core-js/es6/parse-float'; 30 | // import 'core-js/es6/number'; 31 | // import 'core-js/es6/math'; 32 | // import 'core-js/es6/string'; 33 | // import 'core-js/es6/date'; 34 | // import 'core-js/es6/array'; 35 | // import 'core-js/es6/regexp'; 36 | // import 'core-js/es6/map'; 37 | // import 'core-js/es6/weak-map'; 38 | // import 'core-js/es6/set'; 39 | 40 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 41 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 42 | 43 | /** IE10 and IE11 requires the following for the Reflect API. */ 44 | // import 'core-js/es6/reflect'; 45 | 46 | /** 47 | * Web Animations `@angular/platform-browser/animations` 48 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 49 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 50 | */ 51 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 52 | 53 | /** 54 | * By default, zone.js will patch all possible macroTask and DomEvents 55 | * user can disable parts of macroTask/DomEvents patch by setting following flags 56 | * because those flags need to be set before `zone.js` being loaded, and webpack 57 | * will put import in the top of bundle, so user need to create a separate file 58 | * in this directory (for example: zone-flags.ts), and put the following flags 59 | * into that file, and then add the following code before importing zone.js. 60 | * import './zone-flags.ts'; 61 | * 62 | * The flags allowed in zone-flags.ts are listed here. 63 | * 64 | * The following flags will work for all browsers. 65 | * 66 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 67 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 68 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 69 | * 70 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 71 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 72 | * 73 | * (window as any).__Zone_enable_cross_context_check = true; 74 | * 75 | */ 76 | 77 | /*************************************************************************************************** 78 | * Zone JS is required by default for Angular itself. 79 | */ 80 | import 'zone.js/dist/zone'; // Included with Angular CLI. 81 | 82 | 83 | /*************************************************************************************************** 84 | * APPLICATION IMPORTS 85 | */ 86 | -------------------------------------------------------------------------------- /ui/web-console/src/styles.css: -------------------------------------------------------------------------------- 1 | @import url("bootstrap/dist/css/bootstrap.css"); 2 | @import url("font-awesome/css/font-awesome.css"); 3 | @import url("xterm/dist/xterm.css"); 4 | html, body { 5 | margin: 0px; 6 | padding: 0px; 7 | } 8 | 9 | div:focus{ 10 | outline: none; 11 | } 12 | 13 | .noselect { 14 | -webkit-touch-callout: none; /* iOS Safari */ 15 | -webkit-user-select: none; /* Safari */ 16 | -khtml-user-select: none; /* Konqueror HTML */ 17 | -moz-user-select: none; /* Firefox */ 18 | -ms-user-select: none; /* Internet Explorer/Edge */ 19 | user-select: none; /* Non-prefixed version, currently 20 | supported by Chrome and Opera */ 21 | } 22 | 23 | .context-menu-items { 24 | padding: 10px; 25 | padding-top: 5px; 26 | padding-bottom: 5px; 27 | cursor: pointer; 28 | } 29 | 30 | .context-menu-items:hover { 31 | background: deepskyblue; 32 | color: white; 33 | } -------------------------------------------------------------------------------- /ui/web-console/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /ui/web-console/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /ui/web-console/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /ui/web-console/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ui/web-console/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ui/web-console/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-redundant-jsdoc": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-use-before-declare": true, 77 | "no-var-keyword": true, 78 | "object-literal-sort-keys": false, 79 | "one-line": [ 80 | true, 81 | "check-open-brace", 82 | "check-catch", 83 | "check-else", 84 | "check-whitespace" 85 | ], 86 | "prefer-const": true, 87 | "quotemark": [ 88 | true, 89 | "single" 90 | ], 91 | "radix": true, 92 | "semicolon": [ 93 | true, 94 | "always" 95 | ], 96 | "triple-equals": [ 97 | true, 98 | "allow-null-check" 99 | ], 100 | "typedef-whitespace": [ 101 | true, 102 | { 103 | "call-signature": "nospace", 104 | "index-signature": "nospace", 105 | "parameter": "nospace", 106 | "property-declaration": "nospace", 107 | "variable-declaration": "nospace" 108 | } 109 | ], 110 | "unified-signatures": true, 111 | "variable-name": false, 112 | "whitespace": [ 113 | true, 114 | "check-branch", 115 | "check-decl", 116 | "check-operator", 117 | "check-separator", 118 | "check-type" 119 | ], 120 | "no-output-on-prefix": true, 121 | "use-input-property-decorator": true, 122 | "use-output-property-decorator": true, 123 | "use-host-property-decorator": true, 124 | "no-input-rename": true, 125 | "no-output-rename": true, 126 | "use-life-cycle-interface": true, 127 | "use-pipe-transform-interface": true, 128 | "component-class-suffix": true, 129 | "directive-class-suffix": true 130 | } 131 | } 132 | --------------------------------------------------------------------------------