mirror of
https://github.com/citizenfx/cfx-server-data.git
synced 2025-01-08 06:02:57 +08:00
Compare commits
2 Commits
64a26ad1de
...
bbc77613f0
Author | SHA1 | Date | |
---|---|---|---|
|
bbc77613f0 | ||
|
54f75bca86 |
@ -9,7 +9,9 @@ AddEventHandler('rlPlayerActivated', function()
|
|||||||
|
|
||||||
names[source] = { name = GetPlayerName(source), id = source }
|
names[source] = { name = GetPlayerName(source), id = source }
|
||||||
|
|
||||||
TriggerClientEvent('rlUpdateNames', GetHostId())
|
if GetHostId() then
|
||||||
|
TriggerClientEvent('rlUpdateNames', GetHostId())
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterServerEvent('rlUpdateNamesResult')
|
RegisterServerEvent('rlUpdateNamesResult')
|
||||||
|
1
resources/runcode/.gitignore
vendored
Normal file
1
resources/runcode/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
data.json
|
@ -2,7 +2,16 @@ client_script 'runcode_cl.lua'
|
|||||||
server_script 'runcode_sv.lua'
|
server_script 'runcode_sv.lua'
|
||||||
server_script 'runcode_web.lua'
|
server_script 'runcode_web.lua'
|
||||||
|
|
||||||
client_script 'runcode_shared.lua'
|
shared_script 'runcode_shared.lua'
|
||||||
server_script 'runcode_shared.lua'
|
|
||||||
|
|
||||||
resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937'
|
shared_script 'runcode.js'
|
||||||
|
|
||||||
|
resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937'
|
||||||
|
|
||||||
|
client_script 'runcode_ui.lua'
|
||||||
|
|
||||||
|
ui_page 'web/nui.html'
|
||||||
|
|
||||||
|
files {
|
||||||
|
'web/nui.html'
|
||||||
|
}
|
11
resources/runcode/runcode.js
Normal file
11
resources/runcode/runcode.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
exports('runJS', (snippet) => {
|
||||||
|
if (IsDuplicityVersion() && GetInvokingResource() !== GetCurrentResourceName()) {
|
||||||
|
return [ 'Invalid caller.', false ];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return [ new Function(snippet)(), false ];
|
||||||
|
} catch (e) {
|
||||||
|
return [ false, e.toString() ];
|
||||||
|
}
|
||||||
|
});
|
@ -1,7 +1,7 @@
|
|||||||
RegisterNetEvent('runcode:gotSnippet')
|
RegisterNetEvent('runcode:gotSnippet')
|
||||||
|
|
||||||
AddEventHandler('runcode:gotSnippet', function(id, code)
|
AddEventHandler('runcode:gotSnippet', function(id, lang, code)
|
||||||
local res, err = RunCode(code)
|
local res, err = RunCode(lang, code)
|
||||||
|
|
||||||
if not err then
|
if not err then
|
||||||
if type(res) == 'vector3' then
|
if type(res) == 'vector3' then
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
function RunCode(code)
|
local runners = {}
|
||||||
local code, err = load(code, '@runcode')
|
|
||||||
|
function runners.lua(arg)
|
||||||
|
local code, err = load('return ' .. arg, '@runcode')
|
||||||
|
|
||||||
|
-- if failed, try without return
|
||||||
|
if err then
|
||||||
|
code, err = load(arg, '@runcode')
|
||||||
|
end
|
||||||
|
|
||||||
if err then
|
if err then
|
||||||
print(err)
|
print(err)
|
||||||
@ -11,7 +18,15 @@ function RunCode(code)
|
|||||||
|
|
||||||
if status then
|
if status then
|
||||||
return result
|
return result
|
||||||
else
|
|
||||||
return nil, result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return nil, result
|
||||||
|
end
|
||||||
|
|
||||||
|
function runners.js(arg)
|
||||||
|
return table.unpack(exports[GetCurrentResourceName()]:runJS(arg))
|
||||||
|
end
|
||||||
|
|
||||||
|
function RunCode(lang, str)
|
||||||
|
return runners[lang](str)
|
||||||
end
|
end
|
@ -1,7 +1,42 @@
|
|||||||
|
function GetPrivs(source)
|
||||||
|
return {
|
||||||
|
canServer = IsPlayerAceAllowed(source, 'command.run'),
|
||||||
|
canClient = IsPlayerAceAllowed(source, 'command.crun'),
|
||||||
|
canSelf = IsPlayerAceAllowed(source, 'runcode.self'),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
RegisterCommand('run', function(source, args, rawCommand)
|
RegisterCommand('run', function(source, args, rawCommand)
|
||||||
local res, err = RunCode('return ' .. rawCommand:sub(4))
|
local res, err = RunCode('lua', rawCommand:sub(4))
|
||||||
end, true)
|
end, true)
|
||||||
|
|
||||||
RegisterCommand('crun', function(source, args, rawCommand)
|
RegisterCommand('crun', function(source, args, rawCommand)
|
||||||
TriggerClientEvent('runcode:gotSnippet', source, -1, 'return ' .. rawCommand:sub(5))
|
if not source then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
TriggerClientEvent('runcode:gotSnippet', source, -1, 'lua', rawCommand:sub(5))
|
||||||
|
end, true)
|
||||||
|
|
||||||
|
RegisterCommand('runcode', function(source, args, rawCommand)
|
||||||
|
if not source then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local df = LoadResourceFile(GetCurrentResourceName(), 'data.json')
|
||||||
|
local saveData = {}
|
||||||
|
|
||||||
|
if df then
|
||||||
|
saveData = json.decode(df)
|
||||||
|
end
|
||||||
|
|
||||||
|
local p = GetPrivs(source)
|
||||||
|
|
||||||
|
if not p.canServer and not p.canClient and not p.canSelf then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
p.saveData = saveData
|
||||||
|
|
||||||
|
TriggerClientEvent('runcode:openUi', source, p)
|
||||||
end, true)
|
end, true)
|
66
resources/runcode/runcode_ui.lua
Normal file
66
resources/runcode/runcode_ui.lua
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
local openData
|
||||||
|
|
||||||
|
RegisterNetEvent('runcode:openUi')
|
||||||
|
|
||||||
|
AddEventHandler('runcode:openUi', function(options)
|
||||||
|
openData = {
|
||||||
|
type = 'open',
|
||||||
|
options = options,
|
||||||
|
url = 'http://' .. GetCurrentServerEndpoint() .. '/' .. GetCurrentResourceName() .. '/',
|
||||||
|
res = GetCurrentResourceName()
|
||||||
|
}
|
||||||
|
|
||||||
|
SendNuiMessage(json.encode(openData))
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNUICallback('getOpenData', function(args, cb)
|
||||||
|
cb(openData)
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNUICallback('doOk', function(args, cb)
|
||||||
|
SendNuiMessage(json.encode({
|
||||||
|
type = 'ok'
|
||||||
|
}))
|
||||||
|
|
||||||
|
SetNuiFocus(true, true)
|
||||||
|
|
||||||
|
cb('ok')
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNUICallback('doClose', function(args, cb)
|
||||||
|
SendNuiMessage(json.encode({
|
||||||
|
type = 'close'
|
||||||
|
}))
|
||||||
|
|
||||||
|
SetNuiFocus(false, false)
|
||||||
|
|
||||||
|
cb('ok')
|
||||||
|
end)
|
||||||
|
|
||||||
|
local rcCbs = {}
|
||||||
|
local id = 1
|
||||||
|
|
||||||
|
RegisterNUICallback('runCodeInBand', function(args, cb)
|
||||||
|
id = id + 1
|
||||||
|
|
||||||
|
rcCbs[id] = cb
|
||||||
|
|
||||||
|
TriggerServerEvent('runcode:runInBand', id, args)
|
||||||
|
end)
|
||||||
|
|
||||||
|
RegisterNetEvent('runcode:inBandResult')
|
||||||
|
|
||||||
|
AddEventHandler('runcode:inBandResult', function(id, result)
|
||||||
|
if rcCbs[id] then
|
||||||
|
local cb = rcCbs[id]
|
||||||
|
rcCbs[id] = nil
|
||||||
|
|
||||||
|
cb(result)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
AddEventHandler('onResourceStop', function(resourceName)
|
||||||
|
if resourceName == GetCurrentResourceName() then
|
||||||
|
SetNuiFocus(false, false)
|
||||||
|
end
|
||||||
|
end)
|
@ -21,6 +21,69 @@ end
|
|||||||
local codeId = 1
|
local codeId = 1
|
||||||
local codes = {}
|
local codes = {}
|
||||||
|
|
||||||
|
local attempts = 0
|
||||||
|
local lastAttempt
|
||||||
|
|
||||||
|
local function handleRunCode(data, res)
|
||||||
|
if not data.lang then
|
||||||
|
data.lang = 'lua'
|
||||||
|
end
|
||||||
|
|
||||||
|
if not data.client or data.client == '' then
|
||||||
|
CreateThread(function()
|
||||||
|
local result, err = RunCode(data.lang, 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.lang, data.code)
|
||||||
|
|
||||||
|
codeId = codeId + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RegisterNetEvent('runcode:runInBand')
|
||||||
|
|
||||||
|
AddEventHandler('runcode:runInBand', function(id, data)
|
||||||
|
local s = source
|
||||||
|
local privs = GetPrivs(s)
|
||||||
|
|
||||||
|
local res = {
|
||||||
|
send = function(str)
|
||||||
|
TriggerClientEvent('runcode:inBandResult', s, id, str)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
if (not data.client or data.client == '') and not privs.canServer then
|
||||||
|
res.send(json.encode({ error = 'Insufficient permissions.'}))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if (data.client and data.client ~= '') and not privs.canClient then
|
||||||
|
if privs.canSelf then
|
||||||
|
data.client = s
|
||||||
|
else
|
||||||
|
res.send(json.encode({ error = 'Insufficient permissions.'}))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SaveResourceFile(GetCurrentResourceName(), 'data.json', json.encode({
|
||||||
|
lastSnippet = data.code,
|
||||||
|
lastLang = data.lang or 'lua'
|
||||||
|
}), -1)
|
||||||
|
|
||||||
|
handleRunCode(data, res)
|
||||||
|
end)
|
||||||
|
|
||||||
local function handlePost(req, res)
|
local function handlePost(req, res)
|
||||||
req.setDataHandler(function(body)
|
req.setDataHandler(function(body)
|
||||||
local data = json.decode(body)
|
local data = json.decode(body)
|
||||||
@ -35,33 +98,29 @@ local function handlePost(req, res)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if data.password ~= GetConvar('rcon_password', '') then
|
if attempts > 5 or data.password ~= GetConvar('rcon_password', '') then
|
||||||
|
attempts = attempts + 1
|
||||||
|
lastAttempt = GetGameTimer()
|
||||||
|
|
||||||
res.send(json.encode({ error = 'Bad password.'}))
|
res.send(json.encode({ error = 'Bad password.'}))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if not data.client or data.client == '' then
|
handleRunCode(data, res)
|
||||||
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)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
CreateThread(function()
|
||||||
|
while true do
|
||||||
|
Wait(1000)
|
||||||
|
|
||||||
|
if attempts > 0 and (GetGameTimer() - lastAttempt) > 5000 then
|
||||||
|
attempts = 0
|
||||||
|
lastAttempt = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
local function returnCode(id, res, err)
|
local function returnCode(id, res, err)
|
||||||
if not codes[id] then
|
if not codes[id] then
|
||||||
return
|
return
|
||||||
|
@ -6,79 +6,468 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulmaswatch/0.7.2/cyborg/bulmaswatch.min.css">
|
||||||
|
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body {
|
body {
|
||||||
font-family: "Segoe UI", sans-serif;
|
font-family: "Segoe UI", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
z-index: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.in-nui {
|
||||||
|
overflow: hidden;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
margin-top: 5vh;
|
||||||
|
margin-left: 7.5vw;
|
||||||
|
margin-right: 7.5vw;
|
||||||
|
margin-bottom: 5vh;
|
||||||
|
|
||||||
|
height: calc(100% - 10vh);
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.in-nui body > div.bg {
|
||||||
|
background-color: rgba(0, 0, 0, 1);
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: -999;
|
||||||
|
|
||||||
|
box-shadow: 0 22px 70px 4px rgba(0, 0, 0, 0.56);
|
||||||
|
}
|
||||||
|
|
||||||
|
span.nui-edition {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.in-nui span.nui-edition {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#close {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.in-nui #close {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#result {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
border-top: none;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="code-container" style="width:800px;height:600px;border:1px solid grey"></div><br>
|
<div class="bg">
|
||||||
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>
|
</div>
|
||||||
|
|
||||||
|
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item" href="/runcode">
|
||||||
|
<strong>runcode</strong> <span class="nui-edition"> in-game</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarMain">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="navbarMain" class="navbar-menu">
|
||||||
|
<div class="navbar-end">
|
||||||
|
<div class="navbar-item">
|
||||||
|
<div class="field" id="cl-field">
|
||||||
|
<div class="control has-icons-left">
|
||||||
|
<div class="select">
|
||||||
|
<select id="cl-select">
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="icon is-small is-left">
|
||||||
|
<i class="fas fa-user"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="navbar-item">
|
||||||
|
<div class="field has-addons" id="lang-toggle">
|
||||||
|
<p class="control">
|
||||||
|
<button class="button" id="lua-button">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fas fa-moon"></i>
|
||||||
|
</span>
|
||||||
|
<span>Lua</span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<button class="button" id="js-button">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fab fa-js"></i>
|
||||||
|
</span>
|
||||||
|
<span>JS</span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<!-- TODO pending add-on resource that'll contain webpack'd compiler
|
||||||
|
<p class="control">
|
||||||
|
<button class="button" id="ts-button">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fas fa-code"></i>
|
||||||
|
</span>
|
||||||
|
<span>TS</span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="navbar-item">
|
||||||
|
<div class="field has-addons" id="cl-sv-toggle">
|
||||||
|
<p class="control">
|
||||||
|
<button class="button" id="cl-button">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fas fa-user-friends"></i>
|
||||||
|
</span>
|
||||||
|
<span>Client</span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<button class="button" id="sv-button">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<i class="fas fa-server"></i>
|
||||||
|
</span>
|
||||||
|
<span>Server</span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="navbar-item" id="close">
|
||||||
|
<button class="button is-danger">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<div id="code-container" style="width:100%;height:60vh;border:1px solid grey"></div><br>
|
||||||
|
<div class="field" id="passwordField">
|
||||||
|
<p class="control has-icons-left">
|
||||||
|
<input class="input" type="password" id="password" placeholder="RCon Password">
|
||||||
|
<span class="icon is-small is-left">
|
||||||
|
<i class="fas fa-lock"></i>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button class="button is-primary" id="run">Run</button>
|
||||||
|
<div id="result">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
to use a local deployment, uncomment; do note currently the server isn't optimized to serve >1MB files
|
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="monaco-editor/vs/loader.js"></script>
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script src="https://unpkg.com/monaco-editor@0.10.0/min/vs/loader.js"></script>
|
<script src="https://unpkg.com/monaco-editor@0.18.1/min/vs/loader.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function fetchClients() {
|
function fetchClients() {
|
||||||
fetch('/runcode/clients').then(res => res.json()).then(res => {
|
fetch('/runcode/clients').then(res => res.json()).then(res => {
|
||||||
const el = document.querySelector('#clients');
|
const el = document.querySelector('#cl-select');
|
||||||
el.innerHTML = '';
|
|
||||||
|
|
||||||
const clients = res.clients;
|
const clients = res.clients;
|
||||||
clients.push(['All', -1]);
|
const realClients = [['All', '-1'], ...clients];
|
||||||
|
|
||||||
for (const client of clients) {
|
const createdClients = new Set([...el.querySelectorAll('option').entries()].map(([i, el]) => el.value));
|
||||||
const l = document.createElement('a');
|
const existentClients = new Set(realClients.map(([ name, id ]) => id));
|
||||||
l.addEventListener('click', e => {
|
|
||||||
document.querySelector('#client').value = client[1];
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
l.setAttribute('href', 'javascript:void(0)');
|
const toRemove = [...createdClients].filter(a => !existentClients.has(a));
|
||||||
l.appendChild(document.createTextNode(client[0]));
|
|
||||||
|
|
||||||
el.appendChild(l);
|
for (const [name, id] of realClients) {
|
||||||
el.appendChild(document.createTextNode(' '));
|
const ex = el.querySelector(`option[value="${id}"]`);
|
||||||
|
|
||||||
|
if (!ex) {
|
||||||
|
const l = document.createElement('option');
|
||||||
|
l.setAttribute('value', id);
|
||||||
|
l.appendChild(document.createTextNode(name));
|
||||||
|
|
||||||
|
el.appendChild(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id of toRemove) {
|
||||||
|
const l = el.querySelector(`option[value="${id}"]`);
|
||||||
|
|
||||||
|
if (l) {
|
||||||
|
el.removeChild(l);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let useClient = false;
|
||||||
|
let editServerCb = null;
|
||||||
|
|
||||||
|
[['#cl-button', true], ['#sv-button', false]].forEach(([ selector, isClient ]) => {
|
||||||
|
const eh = () => {
|
||||||
|
if (isClient) {
|
||||||
|
document.querySelector('#cl-select').disabled = false;
|
||||||
|
useClient = true;
|
||||||
|
} else {
|
||||||
|
document.querySelector('#cl-select').disabled = true;
|
||||||
|
useClient = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('#cl-sv-toggle button').forEach(el => {
|
||||||
|
el.classList.remove('is-selected', 'is-info');
|
||||||
|
});
|
||||||
|
|
||||||
|
const tgt = document.querySelector(selector);
|
||||||
|
|
||||||
|
tgt.classList.add('is-selected', 'is-info');
|
||||||
|
|
||||||
|
if (editServerCb) {
|
||||||
|
editServerCb();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// default to not-client
|
||||||
|
if (!isClient) {
|
||||||
|
eh();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector(selector).addEventListener('click', ev => {
|
||||||
|
eh();
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let lang = 'lua';
|
||||||
|
let editLangCb = null;
|
||||||
|
let initCb = null;
|
||||||
|
|
||||||
|
function getLangCode(lang) {
|
||||||
|
switch (lang) {
|
||||||
|
case 'js':
|
||||||
|
return 'javascript';
|
||||||
|
case 'ts':
|
||||||
|
return 'typescript';
|
||||||
|
}
|
||||||
|
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
[['#lua-button', 'lua'], ['#js-button', 'js']/*, ['#ts-button', 'ts']*/].forEach(([ selector, langOpt ]) => {
|
||||||
|
const eh = () => {
|
||||||
|
lang = langOpt;
|
||||||
|
|
||||||
|
document.querySelectorAll('#lang-toggle button').forEach(el => {
|
||||||
|
el.classList.remove('is-selected', 'is-info');
|
||||||
|
});
|
||||||
|
|
||||||
|
const tgt = document.querySelector(selector);
|
||||||
|
|
||||||
|
tgt.classList.add('is-selected', 'is-info');
|
||||||
|
|
||||||
|
if (editLangCb) {
|
||||||
|
editLangCb();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// default to not-client
|
||||||
|
if (langOpt === 'lua') {
|
||||||
|
eh();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector(selector).addEventListener('click', ev => {
|
||||||
|
eh();
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
setInterval(() => fetchClients(), 1000);
|
setInterval(() => fetchClients(), 1000);
|
||||||
|
|
||||||
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.10.0/min/vs' }});
|
const inNui = (!!window.invokeNative);
|
||||||
|
let openData = {};
|
||||||
|
|
||||||
|
if (inNui) {
|
||||||
|
document.querySelector('#passwordField').style.display = 'none';
|
||||||
|
document.querySelector('html').classList.add('in-nui');
|
||||||
|
|
||||||
|
fetch(`http://${window.parent.GetParentResourceName()}/getOpenData`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: '{}'
|
||||||
|
}).then(a => a.json())
|
||||||
|
.then(a => {
|
||||||
|
openData = a;
|
||||||
|
|
||||||
|
if (!openData.options.canServer) {
|
||||||
|
document.querySelector('#cl-sv-toggle').style.display = 'none';
|
||||||
|
|
||||||
|
const trigger = document.createEvent('HTMLEvents');
|
||||||
|
trigger.initEvent('click', true, true);
|
||||||
|
|
||||||
|
document.querySelector('#cl-button').dispatchEvent(trigger);
|
||||||
|
} else if (!openData.options.canClient && !openData.options.canSelf) {
|
||||||
|
document.querySelector('#cl-sv-toggle').style.display = 'none';
|
||||||
|
document.querySelector('#cl-field').style.display = 'none';
|
||||||
|
|
||||||
|
const trigger = document.createEvent('HTMLEvents');
|
||||||
|
trigger.initEvent('click', true, true);
|
||||||
|
|
||||||
|
document.querySelector('#sv-button').dispatchEvent(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!openData.options.canClient && openData.options.canSelf) {
|
||||||
|
document.querySelector('#cl-field').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openData.options.saveData) {
|
||||||
|
const cb = () => {
|
||||||
|
if (initCb) {
|
||||||
|
initCb({
|
||||||
|
lastLang: openData.options.saveData.lastLang,
|
||||||
|
lastSnippet: openData.options.saveData.lastSnippet
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTimeout(cb, 50);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(cb, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`https://${window.parent.GetParentResourceName()}/doOk`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: '{}'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#close button').addEventListener('click', ev => {
|
||||||
|
fetch(`https://${window.parent.GetParentResourceName()}/doClose`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: '{}'
|
||||||
|
});
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const defFiles = ['index.d.ts'];
|
||||||
|
const defFilesServer = [...defFiles, 'natives_server.d.ts'];
|
||||||
|
const defFilesClient = [...defFiles, 'natives_universal.d.ts'];
|
||||||
|
|
||||||
|
const prefix = 'https://unpkg.com/@citizenfx/{}/';
|
||||||
|
const prefixClient = prefix.replace('{}', 'client');
|
||||||
|
const prefixServer = prefix.replace('{}', 'server');
|
||||||
|
|
||||||
|
require.config({ paths: { 'vs': 'https://unpkg.com/monaco-editor@0.18.1/min/vs' }});
|
||||||
require(['vs/editor/editor.main'], function() {
|
require(['vs/editor/editor.main'], function() {
|
||||||
const editor = monaco.editor.create(document.getElementById('code-container'), {
|
const editor = monaco.editor.create(document.getElementById('code-container'), {
|
||||||
value: 'return 42',
|
value: 'return 42',
|
||||||
language: 'lua'
|
language: 'lua'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
monaco.editor.setTheme('vs-dark');
|
||||||
|
|
||||||
|
let finalizers = [];
|
||||||
|
|
||||||
|
const updateScript = (client, lang) => {
|
||||||
|
finalizers.forEach(a => a());
|
||||||
|
finalizers = [];
|
||||||
|
|
||||||
|
if (lang === 'js' || lang === 'ts') {
|
||||||
|
const defaults = (lang === 'js') ? monaco.languages.typescript.javascriptDefaults :
|
||||||
|
monaco.languages.typescript.typescriptDefaults;
|
||||||
|
|
||||||
|
defaults.setCompilerOptions({
|
||||||
|
noLib: true,
|
||||||
|
allowNonTsExtensions: true
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const file of (client ? defFilesClient : defFilesServer)) {
|
||||||
|
const prefix = (client ? prefixClient : prefixServer);
|
||||||
|
|
||||||
|
fetch(`${prefix}${file}`)
|
||||||
|
.then(a => a.text())
|
||||||
|
.then(a => {
|
||||||
|
const l = defaults.addExtraLib(a, file);
|
||||||
|
|
||||||
|
finalizers.push(() => l.dispose());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editLangCb = () => {
|
||||||
|
monaco.editor.setModelLanguage(editor.getModel(), getLangCode(lang));
|
||||||
|
|
||||||
|
updateScript(useClient, lang);
|
||||||
|
};
|
||||||
|
|
||||||
|
editServerCb = () => {
|
||||||
|
updateScript(useClient, lang);
|
||||||
|
};
|
||||||
|
|
||||||
|
initCb = (data) => {
|
||||||
|
if (data.lastLang) {
|
||||||
|
const trigger = document.createEvent('HTMLEvents');
|
||||||
|
trigger.initEvent('click', true, true);
|
||||||
|
document.querySelector(`#${data.lastLang}-button`).dispatchEvent(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.lastSnippet) {
|
||||||
|
editor.getModel().setValue(data.lastSnippet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
document.querySelector('#run').addEventListener('click', e => {
|
document.querySelector('#run').addEventListener('click', e => {
|
||||||
const text = editor.getValue();
|
const text = editor.getValue();
|
||||||
|
|
||||||
fetch('/runcode/', {
|
fetch((!inNui) ? '/runcode/' : `https://${openData.res}/runCodeInBand`, {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
password: document.querySelector('#password').value,
|
password: document.querySelector('#password').value,
|
||||||
client: document.querySelector('#client').value,
|
client: (useClient) ? document.querySelector('#cl-select').value : '',
|
||||||
code: text
|
code: text,
|
||||||
|
lang: lang
|
||||||
})
|
})
|
||||||
}).then(res => res.json()).then(res => {
|
}).then(res => res.json()).then(res => {
|
||||||
|
if (inNui) {
|
||||||
|
res = JSON.parse(res); // double packing for sad msgpack-to-json
|
||||||
|
}
|
||||||
|
|
||||||
const resultElement = document.querySelector('#result');
|
const resultElement = document.querySelector('#result');
|
||||||
|
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
resultElement.style.color = '#aa0000';
|
resultElement.classList.remove('notification', 'is-success');
|
||||||
|
resultElement.classList.add('notification', 'is-danger');
|
||||||
} else {
|
} else {
|
||||||
resultElement.style.color = '#000000';
|
resultElement.classList.remove('notification', 'is-danger');
|
||||||
|
resultElement.classList.add('notification', 'is-success');
|
||||||
}
|
}
|
||||||
|
|
||||||
resultElement.innerHTML = res.error || res.result;
|
resultElement.innerHTML = res.error || res.result;
|
||||||
|
60
resources/runcode/web/nui.html
Normal file
60
resources/runcode/web/nui.html
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>runcode nui</title>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
html {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: transparent;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div id="holder">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
let openData = null;
|
||||||
|
|
||||||
|
window.addEventListener('message', ev => {
|
||||||
|
switch (ev.data.type) {
|
||||||
|
case 'open':
|
||||||
|
const frame = document.createElement('iframe');
|
||||||
|
|
||||||
|
frame.name = 'rc';
|
||||||
|
frame.allow = 'microphone *;';
|
||||||
|
frame.src = ev.data.url;
|
||||||
|
frame.style.visibility = 'hidden';
|
||||||
|
|
||||||
|
openData = ev.data;
|
||||||
|
openData.frame = frame;
|
||||||
|
|
||||||
|
document.querySelector('#holder').appendChild(frame);
|
||||||
|
break;
|
||||||
|
case 'ok':
|
||||||
|
openData.frame.style.visibility = 'visible';
|
||||||
|
break;
|
||||||
|
case 'close':
|
||||||
|
document.querySelector('#holder').removeChild(openData.frame);
|
||||||
|
|
||||||
|
openData = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Reference in New Issue
Block a user