mirror of
https://github.com/PhyreApps/PhyrePanel.git
synced 2024-11-22 07:30:25 +00:00
update
This commit is contained in:
parent
52573ae24b
commit
bdbe5b4440
33 changed files with 781 additions and 4 deletions
0
web/Modules/Terminal/App/Http/Controllers/.gitkeep
Normal file
0
web/Modules/Terminal/App/Http/Controllers/.gitkeep
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Terminal\App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class TerminalController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return view('terminal::index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('terminal::create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the specified resource.
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
return view('terminal::show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit($id)
|
||||
{
|
||||
return view('terminal::edit');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, $id): RedirectResponse
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
0
web/Modules/Terminal/App/Providers/.gitkeep
Normal file
0
web/Modules/Terminal/App/Providers/.gitkeep
Normal file
59
web/Modules/Terminal/App/Providers/RouteServiceProvider.php
Normal file
59
web/Modules/Terminal/App/Providers/RouteServiceProvider.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Terminal\App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The module namespace to assume when generating URLs to actions.
|
||||
*/
|
||||
protected string $moduleNamespace = 'Modules\Terminal\App\Http\Controllers';
|
||||
|
||||
/**
|
||||
* Called before routes are registered.
|
||||
*
|
||||
* Register any model bindings or pattern based filters.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the routes for the application.
|
||||
*/
|
||||
public function map(): void
|
||||
{
|
||||
$this->mapApiRoutes();
|
||||
|
||||
$this->mapWebRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "web" routes for the application.
|
||||
*
|
||||
* These routes all receive session state, CSRF protection, etc.
|
||||
*/
|
||||
protected function mapWebRoutes(): void
|
||||
{
|
||||
Route::middleware('web')
|
||||
->namespace($this->moduleNamespace)
|
||||
->group(module_path('Terminal', '/routes/web.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "api" routes for the application.
|
||||
*
|
||||
* These routes are typically stateless.
|
||||
*/
|
||||
protected function mapApiRoutes(): void
|
||||
{
|
||||
Route::prefix('api')
|
||||
->middleware('api')
|
||||
->namespace($this->moduleNamespace)
|
||||
->group(module_path('Terminal', '/routes/api.php'));
|
||||
}
|
||||
}
|
114
web/Modules/Terminal/App/Providers/TerminalServiceProvider.php
Normal file
114
web/Modules/Terminal/App/Providers/TerminalServiceProvider.php
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Terminal\App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class TerminalServiceProvider extends ServiceProvider
|
||||
{
|
||||
protected string $moduleName = 'Terminal';
|
||||
|
||||
protected string $moduleNameLower = 'terminal';
|
||||
|
||||
/**
|
||||
* Boot the application events.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->registerCommands();
|
||||
$this->registerCommandSchedules();
|
||||
$this->registerTranslations();
|
||||
$this->registerConfig();
|
||||
$this->registerViews();
|
||||
$this->loadMigrationsFrom(module_path($this->moduleName, 'Database/migrations'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the service provider.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->register(RouteServiceProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register commands in the format of Command::class
|
||||
*/
|
||||
protected function registerCommands(): void
|
||||
{
|
||||
// $this->commands([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register command Schedules.
|
||||
*/
|
||||
protected function registerCommandSchedules(): void
|
||||
{
|
||||
// $this->app->booted(function () {
|
||||
// $schedule = $this->app->make(Schedule::class);
|
||||
// $schedule->command('inspire')->hourly();
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* Register translations.
|
||||
*/
|
||||
public function registerTranslations(): void
|
||||
{
|
||||
$langPath = resource_path('lang/modules/'.$this->moduleNameLower);
|
||||
|
||||
if (is_dir($langPath)) {
|
||||
$this->loadTranslationsFrom($langPath, $this->moduleNameLower);
|
||||
$this->loadJsonTranslationsFrom($langPath);
|
||||
} else {
|
||||
$this->loadTranslationsFrom(module_path($this->moduleName, 'lang'), $this->moduleNameLower);
|
||||
$this->loadJsonTranslationsFrom(module_path($this->moduleName, 'lang'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register config.
|
||||
*/
|
||||
protected function registerConfig(): void
|
||||
{
|
||||
$this->publishes([module_path($this->moduleName, 'config/config.php') => config_path($this->moduleNameLower.'.php')], 'config');
|
||||
$this->mergeConfigFrom(module_path($this->moduleName, 'config/config.php'), $this->moduleNameLower);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register views.
|
||||
*/
|
||||
public function registerViews(): void
|
||||
{
|
||||
$viewPath = resource_path('views/modules/'.$this->moduleNameLower);
|
||||
$sourcePath = module_path($this->moduleName, 'resources/views');
|
||||
|
||||
$this->publishes([$sourcePath => $viewPath], ['views', $this->moduleNameLower.'-module-views']);
|
||||
|
||||
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower);
|
||||
|
||||
$componentNamespace = str_replace('/', '\\', config('modules.namespace').'\\'.$this->moduleName.'\\'.config('modules.paths.generator.component-class.path'));
|
||||
Blade::componentNamespace($componentNamespace, $this->moduleNameLower);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the services provided by the provider.
|
||||
*/
|
||||
public function provides(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
private function getPublishableViewPaths(): array
|
||||
{
|
||||
$paths = [];
|
||||
foreach (config('view.paths') as $path) {
|
||||
if (is_dir($path.'/modules/'.$this->moduleNameLower)) {
|
||||
$paths[] = $path.'/modules/'.$this->moduleNameLower;
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
}
|
0
web/Modules/Terminal/Database/Seeders/.gitkeep
Normal file
0
web/Modules/Terminal/Database/Seeders/.gitkeep
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Modules\Terminal\Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class TerminalDatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// $this->call([]);
|
||||
}
|
||||
}
|
31
web/Modules/Terminal/composer.json
Normal file
31
web/Modules/Terminal/composer.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "nwidart/terminal",
|
||||
"description": "",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Widart",
|
||||
"email": "n.widart@gmail.com"
|
||||
}
|
||||
],
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [],
|
||||
"aliases": {
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Modules\\Terminal\\": "",
|
||||
"Modules\\Terminal\\App\\": "app/",
|
||||
"Modules\\Terminal\\Database\\Factories\\": "database/factories/",
|
||||
"Modules\\Terminal\\Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Modules\\Terminal\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
0
web/Modules/Terminal/config/.gitkeep
Normal file
0
web/Modules/Terminal/config/.gitkeep
Normal file
5
web/Modules/Terminal/config/config.php
Normal file
5
web/Modules/Terminal/config/config.php
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'name' => 'Terminal',
|
||||
];
|
11
web/Modules/Terminal/module.json
Normal file
11
web/Modules/Terminal/module.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "Terminal",
|
||||
"alias": "terminal",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"priority": 0,
|
||||
"providers": [
|
||||
"Modules\\Terminal\\App\\Providers\\TerminalServiceProvider"
|
||||
],
|
||||
"files": []
|
||||
}
|
78
web/Modules/Terminal/nodejs/terminal/package-lock.json
generated
Normal file
78
web/Modules/Terminal/nodejs/terminal/package-lock.json
generated
Normal file
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"name": "phyre-terminal",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "phyre-terminal",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"node-pty": "^1.0.0",
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.12.5",
|
||||
"@types/ws": "^8.5.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.12.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz",
|
||||
"integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
|
||||
"integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
|
||||
"integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw=="
|
||||
},
|
||||
"node_modules/node-pty": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz",
|
||||
"integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"nan": "^2.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
|
||||
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
web/Modules/Terminal/nodejs/terminal/package.json
Normal file
17
web/Modules/Terminal/nodejs/terminal/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "phyre-terminal",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-pty": "^1.0.0",
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ws": "^8.5.10",
|
||||
"@types/node": "^20.12.5"
|
||||
}
|
||||
}
|
137
web/Modules/Terminal/nodejs/terminal/server.js
Normal file
137
web/Modules/Terminal/nodejs/terminal/server.js
Normal file
|
@ -0,0 +1,137 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { spawn } from 'node-pty';
|
||||
import { WebSocketServer } from 'ws';
|
||||
|
||||
const sessionName = 'PHYRESID';
|
||||
const hostname = execSync('hostname', { silent: true }).toString().trim();
|
||||
// const systemIPs = JSON.parse(
|
||||
// execSync(`${process.env.PHYRE}/bin/v-list-sys-ips json`, { silent: true }).toString(),
|
||||
// );
|
||||
const systemIPs = [];
|
||||
// const { config } = JSON.parse(
|
||||
// execSync(`${process.env.PHYRE}/bin/v-list-sys-config json`, { silent: true }).toString(),
|
||||
// );
|
||||
const config = {
|
||||
WEB_TERMINAL_PORT: 8449,
|
||||
BACKEND_PORT: 8443,
|
||||
};
|
||||
|
||||
const wss = new WebSocketServer({
|
||||
port: parseInt(config.WEB_TERMINAL_PORT, 10),
|
||||
verifyClient: async (info, cb) => {
|
||||
|
||||
// if (!info.req.headers.cookie.includes(sessionName)) {
|
||||
// cb(false, 401, 'Unauthorized');
|
||||
// console.error('Unauthorized connection attempt');
|
||||
// return;
|
||||
// }
|
||||
|
||||
const origin = info.origin || info.req.headers.origin;
|
||||
let matches = origin === `https://${hostname}:${config.BACKEND_PORT}`;
|
||||
|
||||
if (!matches) {
|
||||
for (const ip of Object.keys(systemIPs)) {
|
||||
if (origin === `https://${ip}:${config.BACKEND_PORT}`) {
|
||||
matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
matches = true;
|
||||
|
||||
if (matches) {
|
||||
cb(true);
|
||||
console.log(`Accepted connection from ${info.req.headers['x-real-ip']} to ${origin}`);
|
||||
return;
|
||||
}
|
||||
// console.error(`Forbidden connection attempt from ${info.req.headers['x-real-ip']} to ${origin}`);
|
||||
// cb(false, 403, 'Forbidden');
|
||||
},
|
||||
});
|
||||
|
||||
wss.on('listening', () => {
|
||||
console.log(`Listening on port ${config.WEB_TERMINAL_PORT}`);
|
||||
});
|
||||
|
||||
wss.on('connection', (ws, req) => {
|
||||
|
||||
console.log('New connection');
|
||||
|
||||
wss.clients.add(ws);
|
||||
|
||||
const remoteIP = req.headers['x-real-ip'] || req.socket.remoteAddress;
|
||||
|
||||
console.log(req.headers);
|
||||
|
||||
// Check if session is valid
|
||||
// const sessionID = req.headers.cookie.split(`${sessionName}=`)[1].split(';')[0];
|
||||
// console.log(`New connection from ${remoteIP} (${sessionID})`);
|
||||
//
|
||||
// const file = readFileSync(`${process.env.PHYRE}/data/sessions/sess_${sessionID}`);
|
||||
// if (!file) {
|
||||
// console.error(`Invalid session ID ${sessionID}, refusing connection`);
|
||||
// ws.close(1000, 'Your session has expired.');
|
||||
// return;
|
||||
// }
|
||||
// const session = file.toString();
|
||||
//
|
||||
// // Get username
|
||||
// const login = session.split('user|s:')[1].split('"')[1];
|
||||
// const impersonating = session.split('look|s:')[1].split('"')[1];
|
||||
// const username = impersonating.length > 0 ? impersonating : login;
|
||||
|
||||
const username = 'root';
|
||||
|
||||
// Get user info
|
||||
const passwd = readFileSync('/etc/passwd').toString();
|
||||
const userline = passwd.split('\n').find((line) => line.startsWith(`${username}:`));
|
||||
if (!userline) {
|
||||
console.error(`User ${username} not found, refusing connection`);
|
||||
ws.close(1000, 'You are not allowed to access this server.');
|
||||
return;
|
||||
}
|
||||
const [, , uid, gid, , homedir, shell] = userline.split(':');
|
||||
|
||||
if (shell.endsWith('nologin')) {
|
||||
console.error(`User ${username} has no shell, refusing connection`);
|
||||
ws.close(1000, 'You have no shell access.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn shell as logged in user
|
||||
const pty = spawn(shell, [], {
|
||||
name: 'xterm-color',
|
||||
uid: parseInt(uid, 10),
|
||||
gid: parseInt(gid, 10),
|
||||
cwd: homedir,
|
||||
env: {
|
||||
SHELL: shell,
|
||||
TERM: 'xterm-color',
|
||||
USER: username,
|
||||
HOME: homedir,
|
||||
PWD: homedir,
|
||||
PHYRE: process.env.PHYRE,
|
||||
},
|
||||
});
|
||||
console.log(`New pty (${pty.pid}): ${shell} as ${username} (${uid}:${gid}) in ${homedir}`);
|
||||
|
||||
// Send/receive data from websocket/pty
|
||||
pty.on('data', (data) => ws.send(data));
|
||||
ws.on('message', (data) => pty.write(data));
|
||||
|
||||
// Ensure pty is killed when websocket is closed and vice versa
|
||||
pty.on('exit', () => {
|
||||
console.log(`Ended pty (${pty.pid})`);
|
||||
if (ws.OPEN) {
|
||||
ws.close();
|
||||
}
|
||||
});
|
||||
ws.on('close', () => {
|
||||
console.log(`Ended connection from ${remoteIP} (${sessionID})`);
|
||||
pty.kill();
|
||||
wss.clients.delete(ws);
|
||||
});
|
||||
});
|
15
web/Modules/Terminal/package.json
Normal file
15
web/Modules/Terminal/package.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"axios": "^1.1.2",
|
||||
"laravel-vite-plugin": "^0.7.5",
|
||||
"sass": "^1.69.5",
|
||||
"postcss": "^8.3.7",
|
||||
"vite": "^4.0.0"
|
||||
}
|
||||
}
|
0
web/Modules/Terminal/resources/assets/js/app.js
Normal file
0
web/Modules/Terminal/resources/assets/js/app.js
Normal file
0
web/Modules/Terminal/resources/assets/sass/app.scss
Normal file
0
web/Modules/Terminal/resources/assets/sass/app.scss
Normal file
0
web/Modules/Terminal/resources/views/.gitkeep
Normal file
0
web/Modules/Terminal/resources/views/.gitkeep
Normal file
7
web/Modules/Terminal/resources/views/index.blade.php
Normal file
7
web/Modules/Terminal/resources/views/index.blade.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
@extends('terminal::layouts.master')
|
||||
|
||||
@section('content')
|
||||
<h1>Hello World</h1>
|
||||
|
||||
<p>Module: {!! config('terminal.name') !!}</p>
|
||||
@endsection
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
||||
<title>Terminal Module - {{ config('app.name', 'Laravel') }}</title>
|
||||
|
||||
<meta name="description" content="{{ $description ?? '' }}">
|
||||
<meta name="keywords" content="{{ $keywords ?? '' }}">
|
||||
<meta name="author" content="{{ $author ?? '' }}">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
{{-- Vite CSS --}}
|
||||
{{-- {{ module_vite('build-terminal', 'resources/assets/sass/app.scss') }} --}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@yield('content')
|
||||
|
||||
{{-- Vite JS --}}
|
||||
{{-- {{ module_vite('build-terminal', 'resources/assets/js/app.js') }} --}}
|
||||
</body>
|
0
web/Modules/Terminal/routes/.gitkeep
Normal file
0
web/Modules/Terminal/routes/.gitkeep
Normal file
19
web/Modules/Terminal/routes/api.php
Normal file
19
web/Modules/Terminal/routes/api.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| API Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register API routes for your application. These
|
||||
| routes are loaded by the RouteServiceProvider within a group which
|
||||
| is assigned the "api" middleware group. Enjoy building your API!
|
||||
|
|
||||
*/
|
||||
|
||||
Route::middleware(['auth:sanctum'])->prefix('v1')->name('api.')->group(function () {
|
||||
Route::get('terminal', fn (Request $request) => $request->user())->name('terminal');
|
||||
});
|
19
web/Modules/Terminal/routes/web.php
Normal file
19
web/Modules/Terminal/routes/web.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\Terminal\App\Http\Controllers\TerminalController;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Web Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register web routes for your application. These
|
||||
| routes are loaded by the RouteServiceProvider within a group which
|
||||
| contains the "web" middleware group. Now create something great!
|
||||
|
|
||||
*/
|
||||
|
||||
Route::group([], function () {
|
||||
Route::resource('terminal', TerminalController::class)->names('terminal');
|
||||
});
|
26
web/Modules/Terminal/vite.config.js
Normal file
26
web/Modules/Terminal/vite.config.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { defineConfig } from 'vite';
|
||||
import laravel from 'laravel-vite-plugin';
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
outDir: '../../public/build-terminal',
|
||||
emptyOutDir: true,
|
||||
manifest: true,
|
||||
},
|
||||
plugins: [
|
||||
laravel({
|
||||
publicDirectory: '../../public',
|
||||
buildDirectory: 'build-terminal',
|
||||
input: [
|
||||
__dirname + '/resources/assets/sass/app.scss',
|
||||
__dirname + '/resources/assets/js/app.js'
|
||||
],
|
||||
refresh: true,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
//export const paths = [
|
||||
// 'Modules/$STUDLY_NAME$/resources/assets/sass/app.scss',
|
||||
// 'Modules/$STUDLY_NAME$/resources/assets/js/app.js',
|
||||
//];
|
|
@ -16,4 +16,15 @@ class Terminal extends Page
|
|||
|
||||
protected static ?int $navigationSort = 1;
|
||||
|
||||
protected function getViewData(): array
|
||||
{
|
||||
$sessionId = session()->getId();
|
||||
|
||||
shell_exec('node /usr/local/phyre/web/nodejs/terminal/server.js >> /usr/local/phyre/web/storage/logs/terminal/server-terminal.log &');
|
||||
|
||||
return [
|
||||
'title' => 'Terminal',
|
||||
'sessionId' => $sessionId,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,5 +4,6 @@
|
|||
"Docker": true,
|
||||
"Customer": true,
|
||||
"Model": true,
|
||||
"DockerTemplate": true
|
||||
"DockerTemplate": true,
|
||||
"Terminal": true
|
||||
}
|
1
web/public/build/assets/app-4e206fd3.css
Normal file
1
web/public/build/assets/app-4e206fd3.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
66
web/public/build/assets/web-terminal-6085c523.js
Normal file
66
web/public/build/assets/web-terminal-6085c523.js
Normal file
File diff suppressed because one or more lines are too long
32
web/public/build/assets/web-terminal-c039bc4a.css
Normal file
32
web/public/build/assets/web-terminal-c039bc4a.css
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
* https://github.com/chjj/term.js
|
||||
* @license MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* Originally forked from (with the author's permission):
|
||||
* Fabrice Bellard's javascript vt100 for jslinux:
|
||||
* http://bellard.org/jslinux/
|
||||
* Copyright (c) 2011 Fabrice Bellard
|
||||
* The original design remains. The terminal itself
|
||||
* has been extended to include xterm CSI codes, among
|
||||
* other features.
|
||||
*/.xterm{cursor:text;position:relative;-moz-user-select:none;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::-moz-selection{color:transparent}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{-webkit-user-select:text;-moz-user-select:text;user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{-webkit-text-decoration:double underline;text-decoration:double underline}.xterm-underline-3{-webkit-text-decoration:wavy underline;text-decoration:wavy underline}.xterm-underline-4{-webkit-text-decoration:dotted underline;text-decoration:dotted underline}.xterm-underline-5{-webkit-text-decoration:dashed underline;text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{-webkit-text-decoration:overline double underline;text-decoration:overline double underline}.xterm-overline.xterm-underline-3{-webkit-text-decoration:overline wavy underline;text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{-webkit-text-decoration:overline dotted underline;text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{-webkit-text-decoration:overline dashed underline;text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"resources/css/app.css": {
|
||||
"file": "assets/app-60df93ff.css",
|
||||
"file": "assets/app-4e206fd3.css",
|
||||
"isEntry": true,
|
||||
"src": "resources/css/app.css"
|
||||
},
|
||||
|
@ -8,5 +8,17 @@
|
|||
"file": "assets/app-f9f1eaaf.js",
|
||||
"isEntry": true,
|
||||
"src": "resources/js/app.js"
|
||||
},
|
||||
"resources/js/web-terminal.css": {
|
||||
"file": "assets/web-terminal-c039bc4a.css",
|
||||
"src": "resources/js/web-terminal.css"
|
||||
},
|
||||
"resources/js/web-terminal.js": {
|
||||
"css": [
|
||||
"assets/web-terminal-c039bc4a.css"
|
||||
],
|
||||
"file": "assets/web-terminal-6085c523.js",
|
||||
"isEntry": true,
|
||||
"src": "resources/js/web-terminal.js"
|
||||
}
|
||||
}
|
|
@ -45,7 +45,7 @@ if (terminalElement !== null) {
|
|||
|
||||
fitAddon.fit();
|
||||
|
||||
const socket = new WebSocket(`ws://${window.location.host}/_shell/`);
|
||||
const socket = new WebSocket(`ws://${window.location.host}/_shell/?sessionId=${window.terminal.sessionId}`);
|
||||
socket.addEventListener('open', (_) => {
|
||||
terminal.onData((data) => socket.send(data));
|
||||
socket.addEventListener('message', (evt) => terminal.write(evt.data));
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
<div>
|
||||
|
||||
<script>
|
||||
window.terminal = {};
|
||||
window.terminal.sessionId = '{{ $sessionId }}';
|
||||
</script>
|
||||
|
||||
@vite('resources/js/web-terminal.js')
|
||||
|
||||
<div id="js-web-terminal"></div>
|
||||
|
|
Loading…
Reference in a new issue