mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2026-08-31 03:37:35 +02:00
improve docs, fix links, and add container console support
This update improves documentation clarity, fixes broken documentation links, and introduces a new feature that allows you to access and use the console directly inside containers from CyberPanel.
This commit is contained in:
@@ -1319,4 +1319,97 @@ class ContainerManager(multi.Thread):
|
||||
except BaseException as msg:
|
||||
data_ret = {'removeImageStatus': 0, 'error_message': str(msg)}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
def executeContainerCommand(self, userID=None, data=None):
|
||||
"""
|
||||
Execute a command inside a running Docker container
|
||||
"""
|
||||
try:
|
||||
name = data['name']
|
||||
command = data['command']
|
||||
|
||||
# Check if container is registered in database or unlisted
|
||||
if Containers.objects.filter(name=name).exists():
|
||||
if ACLManager.checkContainerOwnership(name, userID) != 1:
|
||||
return ACLManager.loadErrorJson('commandStatus', 0)
|
||||
|
||||
client = docker.from_env()
|
||||
dockerAPI = docker.APIClient()
|
||||
|
||||
try:
|
||||
container = client.containers.get(name)
|
||||
except docker.errors.NotFound as err:
|
||||
data_ret = {'commandStatus': 0, 'error_message': 'Container does not exist'}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
except:
|
||||
data_ret = {'commandStatus': 0, 'error_message': 'Unknown error'}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
# Check if container is running
|
||||
if container.status != 'running':
|
||||
data_ret = {'commandStatus': 0, 'error_message': 'Container must be running to execute commands'}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
try:
|
||||
# Execute command in container
|
||||
# Split command into parts for proper execution
|
||||
import shlex
|
||||
command_parts = shlex.split(command)
|
||||
|
||||
# Execute command with proper shell
|
||||
exec_result = container.exec_run(
|
||||
command_parts,
|
||||
stdout=True,
|
||||
stderr=True,
|
||||
stdin=False,
|
||||
tty=False,
|
||||
privileged=False,
|
||||
user='',
|
||||
detach=False,
|
||||
demux=False,
|
||||
workdir=None,
|
||||
environment=None
|
||||
)
|
||||
|
||||
# Get output and exit code
|
||||
output = exec_result.output.decode('utf-8') if exec_result.output else ''
|
||||
exit_code = exec_result.exit_code
|
||||
|
||||
# Format the response
|
||||
if exit_code == 0:
|
||||
data_ret = {
|
||||
'commandStatus': 1,
|
||||
'error_message': 'None',
|
||||
'output': output,
|
||||
'exit_code': exit_code,
|
||||
'command': command
|
||||
}
|
||||
else:
|
||||
data_ret = {
|
||||
'commandStatus': 1,
|
||||
'error_message': 'Command executed with non-zero exit code',
|
||||
'output': output,
|
||||
'exit_code': exit_code,
|
||||
'command': command
|
||||
}
|
||||
|
||||
json_data = json.dumps(data_ret, ensure_ascii=False)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except docker.errors.APIError as err:
|
||||
data_ret = {'commandStatus': 0, 'error_message': f'Docker API error: {str(err)}'}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
except Exception as err:
|
||||
data_ret = {'commandStatus': 0, 'error_message': f'Execution error: {str(err)}'}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
|
||||
except BaseException as msg:
|
||||
data_ret = {'commandStatus': 0, 'error_message': str(msg)}
|
||||
json_data = json.dumps(data_ret)
|
||||
return HttpResponse(json_data)
|
||||
@@ -976,6 +976,101 @@ app.controller('viewContainer', function ($scope, $http, $interval, $timeout) {
|
||||
}
|
||||
};
|
||||
|
||||
// Command execution functionality
|
||||
$scope.commandToExecute = '';
|
||||
$scope.executingCommand = false;
|
||||
$scope.commandOutput = null;
|
||||
$scope.commandHistory = [];
|
||||
|
||||
$scope.showCommandModal = function() {
|
||||
$scope.commandToExecute = '';
|
||||
$scope.commandOutput = null;
|
||||
$("#commandModal").modal("show");
|
||||
};
|
||||
|
||||
$scope.executeCommand = function() {
|
||||
if (!$scope.commandToExecute.trim()) {
|
||||
new PNotify({
|
||||
title: 'Error',
|
||||
text: 'Please enter a command to execute',
|
||||
type: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.executingCommand = true;
|
||||
$scope.commandOutput = null;
|
||||
|
||||
url = "/docker/executeContainerCommand";
|
||||
var data = {
|
||||
name: $scope.cName,
|
||||
command: $scope.commandToExecute.trim()
|
||||
};
|
||||
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(ListInitialData, cantLoadInitialData);
|
||||
|
||||
function ListInitialData(response) {
|
||||
console.log(response);
|
||||
$scope.executingCommand = false;
|
||||
|
||||
if (response.data.commandStatus === 1) {
|
||||
$scope.commandOutput = {
|
||||
command: response.data.command,
|
||||
output: response.data.output,
|
||||
exit_code: response.data.exit_code
|
||||
};
|
||||
|
||||
// Add to command history
|
||||
$scope.commandHistory.unshift({
|
||||
command: response.data.command,
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
// Keep only last 10 commands
|
||||
if ($scope.commandHistory.length > 10) {
|
||||
$scope.commandHistory = $scope.commandHistory.slice(0, 10);
|
||||
}
|
||||
|
||||
// Show success notification
|
||||
new PNotify({
|
||||
title: 'Command Executed',
|
||||
text: 'Command completed with exit code: ' + response.data.exit_code,
|
||||
type: response.data.exit_code === 0 ? 'success' : 'warning'
|
||||
});
|
||||
}
|
||||
else {
|
||||
new PNotify({
|
||||
title: 'Command Execution Failed',
|
||||
text: response.data.error_message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function cantLoadInitialData(response) {
|
||||
$scope.executingCommand = false;
|
||||
new PNotify({
|
||||
title: 'Command Execution Failed',
|
||||
text: 'Could not connect to server',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.selectCommand = function(command) {
|
||||
$scope.commandToExecute = command;
|
||||
};
|
||||
|
||||
$scope.clearOutput = function() {
|
||||
$scope.commandOutput = null;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -756,6 +756,11 @@
|
||||
<i class="fas fa-terminal action-icon" style="color: #ec4899;"></i>
|
||||
<div class="action-text">{% trans "Processes" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="action-btn" ng-click="showCommandModal()" ng-disabled="status!='running'">
|
||||
<i class="fas fa-code action-icon" style="color: #10b981;"></i>
|
||||
<div class="action-text">{% trans "Run Command" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -957,6 +962,94 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Command Execution Modal -->
|
||||
<div id="commandModal" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
<i class="fas fa-code" style="margin-right: 0.5rem;"></i>
|
||||
{% trans "Execute Command" %}
|
||||
</h4>
|
||||
<button type="button" class="close" data-dismiss="modal"
|
||||
style="font-size: 1.5rem; background: transparent; border: none;">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="commandInput" class="control-label">
|
||||
<i class="fas fa-terminal" style="margin-right: 0.5rem;"></i>
|
||||
{% trans "Command to execute" %}
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="text"
|
||||
id="commandInput"
|
||||
class="form-control"
|
||||
ng-model="commandToExecute"
|
||||
placeholder="Enter command (e.g., ls -la, ps aux, whoami, env)"
|
||||
ng-keyup="$event.keyCode === 13 && executeCommand()"
|
||||
style="font-family: 'Courier New', monospace;">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-outline-secondary" type="button" ng-click="executeCommand()" ng-disabled="!commandToExecute || executingCommand">
|
||||
<i class="fas fa-play" ng-hide="executingCommand"></i>
|
||||
<i class="fas fa-spinner fa-spin" ng-show="executingCommand"></i>
|
||||
{% trans "Execute" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<small class="form-text text-muted">
|
||||
{% trans "Commands will be executed inside the running container. Use proper shell syntax." %}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Command History -->
|
||||
<div class="form-group" ng-show="commandHistory.length > 0">
|
||||
<label class="control-label">
|
||||
<i class="fas fa-history" style="margin-right: 0.5rem;"></i>
|
||||
{% trans "Command History" %}
|
||||
</label>
|
||||
<div class="command-history">
|
||||
<div class="history-item"
|
||||
ng-repeat="cmd in commandHistory track by $index"
|
||||
ng-click="selectCommand(cmd.command)"
|
||||
style="cursor: pointer; padding: 0.25rem 0.5rem; margin: 0.125rem 0; background: #f8f9fa; border-radius: 4px; border-left: 3px solid #007bff;">
|
||||
<code style="font-size: 0.875rem;">{{ cmd.command }}</code>
|
||||
<small class="text-muted" style="float: right;">{{ cmd.timestamp | date:'short' }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Output Display -->
|
||||
<div class="form-group" ng-show="commandOutput">
|
||||
<label class="control-label">
|
||||
<i class="fas fa-terminal" style="margin-right: 0.5rem;"></i>
|
||||
{% trans "Command Output" %}
|
||||
</label>
|
||||
<div class="terminal-output" style="background: #1a202c; color: #e2e8f0; padding: 1rem; border-radius: 8px; font-family: 'Courier New', monospace; font-size: 0.875rem; max-height: 300px; overflow-y: auto; white-space: pre-wrap;">
|
||||
<div ng-show="commandOutput.exit_code !== undefined" style="margin-bottom: 0.5rem;">
|
||||
<span style="color: #68d391;">$</span> <span style="color: #fbb6ce;">{{ commandOutput.command }}</span>
|
||||
<span style="color: #a0aec0; margin-left: 1rem;">(exit code: {{ commandOutput.exit_code }})</span>
|
||||
</div>
|
||||
<div ng-bind="commandOutput.output"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" ng-click="executeCommand()" ng-disabled="!commandToExecute || executingCommand">
|
||||
<i class="fas fa-play" ng-hide="executingCommand"></i>
|
||||
<i class="fas fa-spinner fa-spin" ng-show="executingCommand"></i>
|
||||
{% trans "Execute Command" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" ng-click="clearOutput()" ng-disabled="!commandOutput">
|
||||
<i class="fas fa-trash"></i> {% trans "Clear Output" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
<i class="fas fa-times"></i> {% trans "Close" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -36,6 +36,7 @@ urlpatterns = [
|
||||
path('recreateappcontainer', views.recreateappcontainer, name='recreateappcontainer'),
|
||||
path('RestartContainerAPP', views.RestartContainerAPP, name='RestartContainerAPP'),
|
||||
path('StopContainerAPP', views.StopContainerAPP, name='StopContainerAPP'),
|
||||
path('executeContainerCommand', views.executeContainerCommand, name='executeContainerCommand'),
|
||||
|
||||
# Docker Container Actions
|
||||
path('startContainer', startContainer, name='startContainer'),
|
||||
|
||||
@@ -537,6 +537,24 @@ def StopContainerAPP(request):
|
||||
cm = ContainerManager()
|
||||
coreResult = cm.StopContainerAPP(userID, json.loads(request.body))
|
||||
|
||||
return coreResult
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
|
||||
@preDockerRun
|
||||
def executeContainerCommand(request):
|
||||
try:
|
||||
userID = request.session['userID']
|
||||
currentACL = ACLManager.loadedACL(userID)
|
||||
|
||||
if currentACL['admin'] == 1:
|
||||
pass
|
||||
else:
|
||||
return ACLManager.loadErrorJson()
|
||||
|
||||
cm = ContainerManager()
|
||||
coreResult = cm.executeContainerCommand(userID, json.loads(request.body))
|
||||
|
||||
return coreResult
|
||||
except KeyError:
|
||||
return redirect(loadLoginPage)
|
||||
Reference in New Issue
Block a user