Gui deploy workers (#1409)
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test (18.x) (push) Has been cancelled
test / test (20.x) (push) Has been cancelled
test / test (22.x) (push) Has been cancelled

* add deploy as worker context menu item

* Update UIWindowPublishWorker.js

* quick fix

* Update new_context_menu_item.js

* Show spinner when publishing workers

* Improve worker default file

* fix

---------

Co-authored-by: ProgrammerIn-wonderland <3838shah@gmail.com>
This commit is contained in:
Nariman Jelveh
2025-08-01 14:52:58 -07:00
committed by GitHub
co-authored by ProgrammerIn-wonderland
parent 6578d4d91c
commit 54aaed3459
6 changed files with 182 additions and 7 deletions
+12 -1
View File
@@ -25,6 +25,7 @@ import UIPopover from './UIPopover.js';
import UIWindowEmailConfirmationRequired from './UIWindowEmailConfirmationRequired.js';
import UIContextMenu from './UIContextMenu.js'
import UIAlert from './UIAlert.js'
import UIWindowPublishWorker from './UIWindowPublishWorker.js';
import path from "../lib/path.js"
import truncate_filename from '../helpers/truncate_filename.js';
import launch_app from "../helpers/launch_app.js"
@@ -1137,6 +1138,17 @@ function UIItem(options){
});
}
//-------------------------------------------
// Publish as Worker
// -------------------------------------------
if(!is_trashed && !is_trash && !options.is_dir && $(el_item).attr('data-name').toLowerCase().endsWith('.js')){
menu_items.push({
html: i18n('publish_as_serverless_worker'),
onClick: async function(){
UIWindowPublishWorker(options.uid, $(el_item).attr('data-name'), $(el_item).attr('data-path'));
}
});
}
// -------------------------------------------
// Deploy As App
// -------------------------------------------
@@ -1158,7 +1170,6 @@ function UIItem(options){
menu_items.push('-');
}
// -------------------------------------------
// Empty Trash
// -------------------------------------------
+136
View File
@@ -0,0 +1,136 @@
/**
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import UIWindow from './UIWindow.js'
import UIWindowMyWebsites from './UIWindowMyWebsites.js'
async function UIWindowPublishWorker(target_dir_uid, target_dir_name, target_dir_path){
let h = '';
h += `<div class="window-publishWorker-content" style="padding: 20px; border-bottom: 1px solid #ced7e1;">`;
// success
h += `<div class="window-publishWorker-success">`;
h += `<img src="${html_encode(window.icons['c-check.svg'])}" style="width:80px; height:80px; display: block; margin:10px auto;">`;
h += `<p style="text-align:center;">${i18n('dir_published_as_website', `<strong>${html_encode(target_dir_name)}</strong>`, false)}<p>`;
h += `<p style="text-align:center;"><a class="publishWorker-published-link" target="_blank"></a><img class="publishWorker-published-link-icon" src="${html_encode(window.icons['launch.svg'])}"></p>`;
h += `<button class="button button-normal button-block button-primary publish-window-ok-btn" style="margin-top:20px;">${i18n('ok')}</button>`;
h+= `</div>`;
// form
h += `<form class="window-publishWorker-form">`;
// error msg
h += `<div class="publish-worker-error-msg"></div>`;
// worker name
h += `<div style="overflow: hidden;">`;
h += `<label style="margin-bottom: 10px;">${i18n('pick_name_for_worker')}</label>`;
h += `<div style="font-family: monospace;">${html_encode(window.extractProtocol(window.url))}://<input class="publish-worker-name" style="width:235px;" type="text" autocomplete="subdomain" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false"/>${html_encode('.puter.work')}</div>`;
h += `</div>`;
// uid
h += `<input class="publishWebsiteTargetDirUID" type="hidden" value="${html_encode(target_dir_uid)}"/>`;
// Publish
h += `<button class="publish-btn button button-action button-block button-normal">${i18n('publish')}</button>`
h += `</form>`;
h += `</div>`;
const el_window = await UIWindow({
title: i18n('window_title_publish_worker'),
icon: null,
uid: null,
is_dir: false,
body_content: h,
has_head: true,
selectable_body: false,
draggable_body: false,
allow_context_menu: false,
is_resizable: false,
is_droppable: false,
init_center: true,
allow_native_ctxmenu: true,
allow_user_select: true,
width: 450,
dominant: true,
onAppend: function(this_window){
$(this_window).find(`.publish-worker-name`).val(window.generate_identifier());
$(this_window).find(`.publish-worker-name`).get(0).focus({preventScroll:true});
},
window_class: 'window-publishWorker',
window_css:{
height: 'initial'
},
body_css: {
width: 'initial',
height: '100%',
'background-color': 'rgb(245 247 249)',
'backdrop-filter': 'blur(3px)',
}
})
$(el_window).find('.publish-btn').on('click', function(e){
// todo do some basic validation client-side
//Worker name
let worker_name = $(el_window).find('.publish-worker-name').val();
// Store original text and replace with spinner
const originalText = $(el_window).find('.publish-btn').text();
$(el_window).find('.publish-btn').prop('disabled', true).html(`
<div style="display: inline-block; margin-top: 10px; width: 16px; height: 16px; border: 2px solid #ffffff; border-radius: 50%; border-top: 2px solid transparent; animation: spin 1s linear infinite;"></div>
`);
puter.workers.create(
worker_name,
target_dir_path).then((res)=>{
let url = 'https://' + worker_name + '.puter.work';
$(el_window).find('.window-publishWorker-form').hide(100, function(){
$(el_window).find('.publishWorker-published-link').attr('href', url);
$(el_window).find('.publishWorker-published-link').text(url);
$(el_window).find('.window-publishWorker-success').show(100)
$(`.item[data-uid="${target_dir_uid}"] .item-has-website-badge`).show();
});
// find all items whose path starts with target_dir_path
$(`.item[data-path^="${target_dir_path}/"]`).each(function(){
// show the link badge
$(this).find('.item-has-website-url-badge').show();
// update item's website_url attribute
$(this).attr('data-website_url', url + $(this).attr('data-path').substring(target_dir_path.length));
})
}).catch((err)=>{
err = err.error;
$(el_window).find('.publish-worker-error-msg').html(
err.message + (
err.code === 'subdomain_limit_reached' ?
' <span class="manage-your-websites-link">' + i18n('manage_your_subdomains') + '</span>' : ''
)
);
$(el_window).find('.publish-worker-error-msg').fadeIn();
// re-enable 'Publish' button and restore original text
$(el_window).find('.publish-btn').prop('disabled', false).text(originalText);
})
})
$(el_window).find('.publish-window-ok-btn').on('click', function(){
$(el_window).close();
})
}
$(document).on('click', '.manage-your-websites-link', async function(e){
UIWindowMyWebsites();
})
export default UIWindowPublishWorker
+5 -5
View File
@@ -1900,7 +1900,7 @@ label {
}
/***************************************************/
.login-error-msg, .signup-error-msg, .publish-website-error-msg, .form-error-msg {
.login-error-msg, .signup-error-msg, .publish-website-error-msg, .form-error-msg, .publish-worker-error-msg {
display: none;
color: red;
border: 1px solid red;
@@ -1937,7 +1937,7 @@ label {
margin-top: 20px;
}
.window-publishWebsite-success, .window-give-item-access-success {
.window-publishWebsite-success, .window-give-item-access-success, .window-publishWorker-success {
display: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -1950,16 +1950,16 @@ label {
cursor: pointer;
}
.publishWebsite-published-link {
.publishWebsite-published-link, .publishWorker-published-link {
text-decoration: none;
color: #007cff;
}
.publishWebsite-published-link:hover {
.publishWebsite-published-link:hover, .publishWorker-published-link:hover {
text-decoration: underline;
}
.publishWebsite-published-link-icon {
.publishWebsite-published-link-icon, .publishWorker-published-link-icon {
display: inline-block;
width: 12px;
margin-left: 5px;
@@ -155,6 +155,30 @@ const new_context_menu_item = function(dirname, append_to_element){
});
}
},
// Worker
{
html: i18n('worker'),
icon: `<img src="${html_encode(window.icons['file-js.svg'])}" class="ctx-item-icon">`,
onClick: async function() {
await window.create_file({
dirname: dirname,
append_to_element: append_to_element,
name: 'New Worker.js',
content: `// This is an example application for Puter Workers
router.get('/', ({request}) => {
return 'Hello World'; // returns a string
});
router.get('/api/hello', ({request}) => {
return {'msg': 'hello'}; // returns a JSON object
});
router.get('/*page', ({request, params}) => {
return new Response(\`Page \${params.page} not found\`, {status: 404});
});
`
});
}
}
];
//Show file_templates on the lower part of "New"
+4
View File
@@ -211,6 +211,7 @@ const en = {
path: 'Path',
personalization: "Personalization",
pick_name_for_website: "Pick a name for your website:",
pick_name_for_worker: "Pick a name for your worker:",
picture: "Picture",
pictures: 'Pictures',
plural_suffix: 's',
@@ -230,6 +231,7 @@ const en = {
public: 'Public',
publish: "Publish",
publish_as_website: 'Publish as website',
publish_as_serverless_worker: 'Publish as Worker',
puter_description: `Puter is a privacy-first personal cloud to keep all your files, apps, and games in one secure place, accessible from anywhere at any time.`,
reading: "Reading %strong%",
writing: "Writing %strong%",
@@ -331,6 +333,7 @@ const en = {
you_have_been_referred_to_puter_by_a_friend: "You have been referred to Puter by a friend!",
zip: "Zip",
sequencing: "Sequencing %strong%",
worker: "Worker",
zipping: "Zipping %strong%",
// === 2FA Setup ===
@@ -461,6 +464,7 @@ const en = {
'window_title_set_new_password': 'Set New Password',
'window_title_instant_login': 'Instant Login!',
'window_title_publish_website': 'Publish Website',
'window_title_publish_worker': 'Publish Worker',
'window_title_authenticating': 'Authenticating...',
'window_title_refer_friend': 'Refer a friend!',
+1 -1
View File
@@ -99,7 +99,7 @@ export class WorkersHandler {
if (!driverCall.result.result) {
new Error("Worker doesn't exist");
}
throw driverCall.error || new Error(driverResult?.errors || "Driver failed to execute, do you have the necessary permissions?");
throw driverCall.error || new Error(driverCall.result?.errors || "Driver failed to execute, do you have the necessary permissions?");
} else {
let currentWorkers = await puter.kv.get("user-workers");