mirror of
https://github.com/citizenfx/cfx-server-data.git
synced 2025-12-12 06:14:09 +01:00
add runcode resource
This commit is contained in:
8
resources/runcode/__resource.lua
Normal file
8
resources/runcode/__resource.lua
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
client_script 'runcode_cl.lua'
|
||||||
|
server_script 'runcode_sv.lua'
|
||||||
|
server_script 'runcode_web.lua'
|
||||||
|
|
||||||
|
client_script 'runcode_shared.lua'
|
||||||
|
server_script 'runcode_shared.lua'
|
||||||
|
|
||||||
|
resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937'
|
||||||
3
resources/runcode/runcode.lua
Normal file
3
resources/runcode/runcode.lua
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
RegisterCommand('runcode', function(source, args, rawCommand)
|
||||||
|
RunCode('return ' .. rawCommand:sub(8))
|
||||||
|
end, true)
|
||||||
15
resources/runcode/runcode_cl.lua
Normal file
15
resources/runcode/runcode_cl.lua
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
RegisterNetEvent('runcode:gotSnippet')
|
||||||
|
|
||||||
|
AddEventHandler('runcode:gotSnippet', function(id, code)
|
||||||
|
local res, err = RunCode(code)
|
||||||
|
|
||||||
|
if not err then
|
||||||
|
if type(res) == 'vector3' then
|
||||||
|
res = json.encode({ table.unpack(res) })
|
||||||
|
elseif type(res) == 'table' then
|
||||||
|
res = json.encode(res)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
TriggerServerEvent('runcode:gotResult', id, res, err)
|
||||||
|
end)
|
||||||
17
resources/runcode/runcode_shared.lua
Normal file
17
resources/runcode/runcode_shared.lua
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
function RunCode(code)
|
||||||
|
local code, err = load(code, '@runcode')
|
||||||
|
|
||||||
|
if err then
|
||||||
|
print(err)
|
||||||
|
return nil, err
|
||||||
|
end
|
||||||
|
|
||||||
|
local status, result = pcall(code)
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
if status then
|
||||||
|
return result
|
||||||
|
else
|
||||||
|
return nil, result
|
||||||
|
end
|
||||||
|
end
|
||||||
7
resources/runcode/runcode_sv.lua
Normal file
7
resources/runcode/runcode_sv.lua
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
RegisterCommand('run', function(source, args, rawCommand)
|
||||||
|
local res, err = RunCode('return ' .. rawCommand:sub(4))
|
||||||
|
end, true)
|
||||||
|
|
||||||
|
RegisterCommand('crun', function(source, args, rawCommand)
|
||||||
|
TriggerClientEvent('runcode:gotSnippet', source, -1, 'return ' .. rawCommand:sub(5))
|
||||||
|
end, true)
|
||||||
133
resources/runcode/runcode_web.lua
Normal file
133
resources/runcode/runcode_web.lua
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
local cachedFiles = {}
|
||||||
|
|
||||||
|
local function sendFile(res, fileName)
|
||||||
|
if cachedFiles[fileName] then
|
||||||
|
res.send(cachedFiles[fileName])
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local fileData = LoadResourceFile(GetCurrentResourceName(), 'web/' .. fileName)
|
||||||
|
|
||||||
|
if not fileData then
|
||||||
|
res.writeHead(404)
|
||||||
|
res.send('Not found.')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
cachedFiles[fileName] = fileData
|
||||||
|
res.send(fileData)
|
||||||
|
end
|
||||||
|
|
||||||
|
local codeId = 1
|
||||||
|
local codes = {}
|
||||||
|
|
||||||
|
local function handlePost(req, res)
|
||||||
|
req.setDataHandler(function(body)
|
||||||
|
local data = json.decode(body)
|
||||||
|
|
||||||
|
if not data or not data.password or not data.code then
|
||||||
|
res.send(json.encode({ error = 'Bad request.'}))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if GetConvar('rcon_password', '') == '' then
|
||||||
|
res.send(json.encode({ error = 'The server has an empty rcon_password.'}))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if data.password ~= GetConvar('rcon_password', '') then
|
||||||
|
res.send(json.encode({ error = 'Bad password.'}))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not data.client or data.client == '' then
|
||||||
|
CreateThread(function()
|
||||||
|
local result, err = RunCode(data.code)
|
||||||
|
|
||||||
|
res.send(json.encode({
|
||||||
|
result = result,
|
||||||
|
error = err
|
||||||
|
}))
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
codes[codeId] = {
|
||||||
|
timeout = GetGameTimer() + 1000,
|
||||||
|
res = res
|
||||||
|
}
|
||||||
|
|
||||||
|
TriggerClientEvent('runcode:gotSnippet', tonumber(data.client), codeId, data.code)
|
||||||
|
|
||||||
|
codeId = codeId + 1
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function returnCode(id, res, err)
|
||||||
|
if not codes[id] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local code = codes[id]
|
||||||
|
codes[id] = nil
|
||||||
|
|
||||||
|
local gotFrom
|
||||||
|
|
||||||
|
if source then
|
||||||
|
gotFrom = GetPlayerName(source) .. ' [' .. tostring(source) .. ']'
|
||||||
|
end
|
||||||
|
|
||||||
|
code.res.send(json.encode({
|
||||||
|
result = res,
|
||||||
|
error = err,
|
||||||
|
from = gotFrom
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
Wait(100)
|
||||||
|
|
||||||
|
for k, v in ipairs(codes) do
|
||||||
|
if GetGameTimer() > v.timeout then
|
||||||
|
source = nil
|
||||||
|
returnCode(k, '', 'Timed out waiting on the target client.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('runcode:gotResult')
|
||||||
|
AddEventHandler('runcode:gotResult', returnCode)
|
||||||
|
|
||||||
|
SetHttpHandler(function(req, res)
|
||||||
|
local path = req.path
|
||||||
|
|
||||||
|
if req.method == 'POST' then
|
||||||
|
return handlePost(req, res)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- client shortcuts
|
||||||
|
if req.path == '/clients' then
|
||||||
|
local clientList = {}
|
||||||
|
|
||||||
|
for _, id in ipairs(GetPlayers()) do
|
||||||
|
table.insert(clientList, { GetPlayerName(id), id })
|
||||||
|
end
|
||||||
|
|
||||||
|
res.send(json.encode({
|
||||||
|
clients = clientList
|
||||||
|
}))
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- should this be the index?
|
||||||
|
if req.path == '/' then
|
||||||
|
path = 'index.html'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- remove any '..' from the path
|
||||||
|
path = path:gsub("%.%.", "")
|
||||||
|
|
||||||
|
return sendFile(res, path)
|
||||||
|
end)
|
||||||
97
resources/runcode/web/index.html
Normal file
97
resources/runcode/web/index.html
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>fivem runcode</title>
|
||||||
|
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
font-family: "Segoe UI", sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="code-container" style="width:800px;height:600px;border:1px solid grey"></div><br>
|
||||||
|
Password: <input type="password" id="password"> (use your rcon password)<br>
|
||||||
|
Client ID: <input type="text" id="client"> (leave blank for server)<br>
|
||||||
|
<div id="clients"></div>
|
||||||
|
<button id="run">Run</button>
|
||||||
|
<div id="result">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
to use a local deployment, uncomment; do note currently the server isn't optimized to serve >1MB files
|
||||||
|
<script src="monaco-editor/vs/loader.js"></script>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/monaco-editor@0.10.0/min/vs/loader.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function fetchClients() {
|
||||||
|
fetch('/runcode/clients').then(res => res.json()).then(res => {
|
||||||
|
const el = document.querySelector('#clients');
|
||||||
|
el.innerHTML = '';
|
||||||
|
|
||||||
|
const clients = res.clients;
|
||||||
|
clients.push(['All', -1]);
|
||||||
|
|
||||||
|
for (const client of clients) {
|
||||||
|
const l = document.createElement('a');
|
||||||
|
l.addEventListener('click', e => {
|
||||||
|
document.querySelector('#client').value = client[1];
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
l.setAttribute('href', 'javascript:void(0)');
|
||||||
|
l.appendChild(document.createTextNode(client[0]));
|
||||||
|
|
||||||
|
el.appendChild(l);
|
||||||
|
el.appendChild(document.createTextNode(' '));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(() => fetchClients(), 1000);
|
||||||
|
|
||||||
|
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.10.0/min/vs' }});
|
||||||
|
require(['vs/editor/editor.main'], function() {
|
||||||
|
const editor = monaco.editor.create(document.getElementById('code-container'), {
|
||||||
|
value: 'return 42',
|
||||||
|
language: 'lua'
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#run').addEventListener('click', e => {
|
||||||
|
const text = editor.getValue();
|
||||||
|
|
||||||
|
fetch('/runcode/', {
|
||||||
|
method: 'post',
|
||||||
|
body: JSON.stringify({
|
||||||
|
password: document.querySelector('#password').value,
|
||||||
|
client: document.querySelector('#client').value,
|
||||||
|
code: text
|
||||||
|
})
|
||||||
|
}).then(res => res.json()).then(res => {
|
||||||
|
const resultElement = document.querySelector('#result');
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
resultElement.style.color = '#aa0000';
|
||||||
|
} else {
|
||||||
|
resultElement.style.color = '#000000';
|
||||||
|
}
|
||||||
|
|
||||||
|
resultElement.innerHTML = res.error || res.result;
|
||||||
|
|
||||||
|
if (res.from) {
|
||||||
|
resultElement.innerHTML += ' (from ' + res.from + ')';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user