From 54aaed3459fd64aa1318a58bf9ba941dc91ea3e8 Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Fri, 1 Aug 2025 14:52:58 -0700 Subject: [PATCH] Gui deploy workers (#1409) * 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> --- src/gui/src/UI/UIItem.js | 13 +- src/gui/src/UI/UIWindowPublishWorker.js | 136 +++++++++++++++++++ src/gui/src/css/style.css | 10 +- src/gui/src/helpers/new_context_menu_item.js | 24 ++++ src/gui/src/i18n/translations/en.js | 4 + src/puter-js/src/modules/Workers.js | 2 +- 6 files changed, 182 insertions(+), 7 deletions(-) create mode 100644 src/gui/src/UI/UIWindowPublishWorker.js diff --git a/src/gui/src/UI/UIItem.js b/src/gui/src/UI/UIItem.js index 494cf302f..48dfd3341 100644 --- a/src/gui/src/UI/UIItem.js +++ b/src/gui/src/UI/UIItem.js @@ -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 // ------------------------------------------- diff --git a/src/gui/src/UI/UIWindowPublishWorker.js b/src/gui/src/UI/UIWindowPublishWorker.js new file mode 100644 index 000000000..708e898b6 --- /dev/null +++ b/src/gui/src/UI/UIWindowPublishWorker.js @@ -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 . + */ + +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 += `
`; + // success + h += `
`; + h += ``; + h += `

${i18n('dir_published_as_website', `${html_encode(target_dir_name)}`, false)}

`; + h += `

`; + h += ``; + h+= `
`; + // form + h += `
`; + // error msg + h += `
`; + // worker name + h += `
`; + h += ``; + h += `
${html_encode(window.extractProtocol(window.url))}://${html_encode('.puter.work')}
`; + h += `
`; + // uid + h += ``; + // Publish + h += `` + h += `
`; + h += `
`; + + 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(` +
+ `); + + 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' ? + ' ' + i18n('manage_your_subdomains') + '' : '' + ) + ); + $(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 \ No newline at end of file diff --git a/src/gui/src/css/style.css b/src/gui/src/css/style.css index 8959946ca..24bff3f2b 100644 --- a/src/gui/src/css/style.css +++ b/src/gui/src/css/style.css @@ -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; diff --git a/src/gui/src/helpers/new_context_menu_item.js b/src/gui/src/helpers/new_context_menu_item.js index cfaf3e5f4..1d2e20e7e 100644 --- a/src/gui/src/helpers/new_context_menu_item.js +++ b/src/gui/src/helpers/new_context_menu_item.js @@ -155,6 +155,30 @@ const new_context_menu_item = function(dirname, append_to_element){ }); } }, + // Worker + { + html: i18n('worker'), + 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" diff --git a/src/gui/src/i18n/translations/en.js b/src/gui/src/i18n/translations/en.js index abfe3a5f7..dfe1a7f58 100644 --- a/src/gui/src/i18n/translations/en.js +++ b/src/gui/src/i18n/translations/en.js @@ -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!', diff --git a/src/puter-js/src/modules/Workers.js b/src/puter-js/src/modules/Workers.js index 5f045e8c3..e03002eaa 100644 --- a/src/puter-js/src/modules/Workers.js +++ b/src/puter-js/src/modules/Workers.js @@ -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");