diff --git a/src/dev-center/index.html b/src/dev-center/index.html
index 0470af8f3..adec95ec8 100644
--- a/src/dev-center/index.html
+++ b/src/dev-center/index.html
@@ -222,7 +222,6 @@
My Apps
-
@@ -231,6 +230,7 @@
+
diff --git a/src/dev-center/js/websites.js b/src/dev-center/js/websites.js
index e896bbb27..9e37ce3f4 100644
--- a/src/dev-center/js/websites.js
+++ b/src/dev-center/js/websites.js
@@ -1,4 +1,386 @@
-async function init_websites() {
+let sortBy = 'created_at';
+let sortDirection = 'desc';
+window.websites = [];
+let search_query;
+
+window.create_website = async (subdomain) => {
+ let website = await puter.hosting.create(subdomain);
+
+ return website;
}
-export default init_websites;
\ No newline at end of file
+window.refresh_website_list = async (show_loading = false) => {
+ if (show_loading)
+ puter.ui.showSpinner();
+
+ // puter.hosting.list() returns an array of website objects
+ window.websites = await puter.hosting.list();
+
+ // Get workers
+ if (window.activeTab === 'websites' && window.websites.length > 0) {
+ $('.website-card').remove();
+ $('#no-websites-notice').hide();
+ $('#website-list').show();
+ window.websites.forEach((website) => {
+ // append row to website-list-table
+ $('#website-list-table > tbody').append(generate_website_card(website));
+ });
+ } else {
+ $('#no-websites-notice').show();
+ $('#website-list').hide();
+ }
+
+ count_workers();
+}
+
+
+async function init_workers() {
+}
+
+$(document).on('click', '.create-a-worker-btn', async function (e) {
+ let name = await puter.ui.prompt('Please enter a name for your worker:', 'my-awesome-worker');
+
+ if (name) {
+ console.log(name);
+ await create_worker(name);
+ }
+})
+
+window.createDefaultWorkerFile = async () => {
+ window.default_worker_file = `/${auth_username}/AppData/${dev_center_uid}/default_worker_file.js`;
+ let existingFile;
+ try {
+ // Check if default_worker_file exists
+ existingFile = await puter.fs.read(default_worker_file);
+ } catch (err) {
+ console.error('Error creating default worker file:', err);
+ }
+
+ if (!existingFile) {
+ // Create default_worker_file
+ await puter.fs.write(default_worker_file, `// 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});
+});`);
+ }
+
+}
+
+$(document).on('change', '.worker-checkbox', function (e) {
+ // determine if select-all checkbox should be checked, indeterminate, or unchecked
+ if ($('.worker-checkbox:checked').length === $('.worker-checkbox').length) {
+ $('.select-all-workers').prop('indeterminate', false);
+ $('.select-all-workers').prop('checked', true);
+ } else if ($('.worker-checkbox:checked').length > 0) {
+ $('.select-all-workers').prop('indeterminate', true);
+ $('.select-all-workers').prop('checked', false);
+ }
+ else {
+ $('.select-all-workers').prop('indeterminate', false);
+ $('.select-all-workers').prop('checked', false);
+ }
+
+ // activate row
+ if ($(this).is(':checked'))
+ $(this).closest('tr').addClass('active');
+ else
+ $(this).closest('tr').removeClass('active');
+
+ // enable delete button if at least one checkbox is checked
+ if ($('.worker-checkbox:checked').length > 0)
+ $('.delete-workers-btn').removeClass('disabled');
+ else
+ $('.delete-workers-btn').addClass('disabled');
+})
+
+$(document).on('change', '.select-all-workers', function (e) {
+ if ($(this).is(':checked')) {
+ $('.worker-checkbox').prop('checked', true);
+ $('.worker-card').addClass('active');
+ $('.delete-workers-btn').removeClass('disabled');
+ } else {
+ $('.worker-checkbox').prop('checked', false);
+ $('.worker-card').removeClass('active');
+ $('.delete-workers-btn').addClass('disabled');
+ }
+})
+
+$('.refresh-worker-list').on('click', function (e) {
+ puter.ui.showSpinner();
+ refresh_worker_list();
+
+ puter.ui.hideSpinner();
+})
+
+$('th.sort').on('click', function (e) {
+ // determine what column to sort by
+ const sortByColumn = $(this).attr('data-column');
+
+ // toggle sort direction
+ if (sortByColumn === sortBy) {
+ if (sortDirection === 'asc')
+ sortDirection = 'desc';
+ else
+ sortDirection = 'asc';
+ }
+ else {
+ sortBy = sortByColumn;
+ sortDirection = 'desc';
+ }
+
+ // update arrow
+ $('.sort-arrow').css('display', 'none');
+ $('#worker-list-table').find('th').removeClass('sorted');
+ $(this).find('.sort-arrow-' + sortDirection).css('display', 'inline');
+ $(this).addClass('sorted');
+
+ sort_workers();
+});
+
+function sort_workers() {
+ let sorted_workers;
+
+ // sort
+ if (sortDirection === 'asc'){
+ sorted_workers = workers.sort((a, b) => {
+ if(sortBy === 'name'){
+ return a[sortBy].localeCompare(b[sortBy]);
+ }else if(sortBy === 'created_at'){
+ return new Date(a[sortBy]) - new Date(b[sortBy]);
+ } else if(sortBy === 'user_count' || sortBy === 'open_count'){
+ return a.stats[sortBy] - b.stats[sortBy];
+ }else{
+ a[sortBy] > b[sortBy] ? 1 : -1
+ }
+ });
+ }else{
+ sorted_workers = workers.sort((a, b) => {
+ if(sortBy === 'name'){
+ return b[sortBy].localeCompare(a[sortBy]);
+ }else if(sortBy === 'created_at'){
+ return new Date(b[sortBy]) - new Date(a[sortBy]);
+ } else if(sortBy === 'user_count' || sortBy === 'open_count'){
+ return b.stats[sortBy] - a.stats[sortBy];
+ }else{
+ b[sortBy] > a[sortBy] ? 1 : -1
+ }
+ });
+ }
+ // refresh worker list
+ $('.worker-card').remove();
+ sorted_workers.forEach(worker => {
+ $('#worker-list-table > tbody').append(generate_worker_card(worker));
+ });
+
+ count_workers();
+
+ // show workers that match search_query and hide workers that don't
+ if (search_query) {
+ // show workers that match search_query and hide workers that don't
+ workers.forEach((worker) => {
+ if (worker.name.toLowerCase().includes(search_query.toLowerCase())) {
+ $(`.worker-card[data-name="${html_encode(worker.name)}"]`).show();
+ } else {
+ $(`.worker-card[data-name="${html_encode(worker.name)}"]`).hide();
+ }
+ })
+ }
+}
+
+function count_workers() {
+ let count = 0;
+ $('.worker-card').each(function () {
+ count++;
+ })
+ $('.worker-count').html(count);
+ return count;
+}
+
+function generate_worker_card(worker) {
+ return `
+
+ |
+
+ |
+ ${worker.name} |
+ ${worker.created_at} |
+
+
+ |
+
+ `;
+}
+
+$(document).on('input change keyup keypress keydown paste cut', '.search', function (e) {
+ // search apps for query
+ search_query = $(this).val().toLowerCase();
+ if (search_query === '') {
+ // hide 'clear search' button
+ $('.search-clear').hide();
+ // show all apps again
+ $(`.worker-card`).show();
+ } else {
+ // show 'clear search' button
+ $('.search-clear').show();
+ // show apps that match search_query and hide apps that don't
+ workers.forEach((worker) => {
+ if (
+ worker.name.toLowerCase().includes(search_query.toLowerCase())
+ )
+ {
+ $(`.worker-card[data-name="${worker.name}"]`).show();
+ } else {
+ $(`.worker-card[data-name="${worker.name}"]`).hide();
+ }
+ })
+ }
+})
+
+$(document).on('click', '.delete-workers-btn', async function (e) {
+ // show confirmation alert
+ let resp = await puter.ui.alert(`Are you sure you want to delete the selected workers?`, [
+ {
+ label: 'Delete',
+ type: 'danger',
+ value: 'delete',
+ },
+ {
+ label: 'Cancel',
+ },
+ ], {
+ type: 'warning',
+ });
+
+ if (resp === 'delete') {
+ // disable delete button
+ $('.delete-workers-btn').addClass('disabled');
+
+ // show 'deleting' modal
+ puter.ui.showSpinner();
+
+ let start_ts = Date.now();
+ const workers = $('.worker-checkbox:checked').toArray();
+
+ // delete all checked workers
+ for (let worker of workers) {
+ let worker_name = $(worker).attr('data-worker-name');
+ // delete worker
+ await puter.workers.delete(worker_name)
+
+ // remove worker card
+ $(`.worker-card[data-name="${worker_name}"]`).fadeOut(200, function name(params) {
+ $(this).remove();
+ if ($(`.worker-card`).length === 0) {
+ $('section:not(.sidebar)').hide();
+ $('#no-workers-notice').show();
+ } else {
+ $('section:not(.sidebar)').hide();
+ $('#worker-list').show();
+ }
+ count_workers();
+ });
+
+ try{
+ count_workers();
+ } catch(err) {
+ console.log(err);
+ }
+ }
+
+ // close 'deleting' modal
+ setTimeout(() => {
+ puter.ui.hideSpinner();
+ if($('.worker-checkbox:checked').length === 0){
+ // disable delete button
+ $('.delete-workers-btn').addClass('disabled');
+ // reset the 'select all' checkbox
+ $('.select-all-workers').prop('indeterminate', false);
+ $('.select-all-workers').prop('checked', false);
+ }
+ }, (start_ts - Date.now()) > 500 ? 0 : 500);
+ }
+})
+
+$(document).on('click', '.delete-worker-btn', async function (e) {
+ let worker_name = $(this).attr('data-worker-name');
+
+ // get worker
+ const worker_data = await puter.workers.get(worker_name);
+
+ if(worker_data.metadata?.locked){
+ puter.ui.alert(`${worker_data.name} is locked and cannot be deleted.`, [
+ {
+ label: 'Ok',
+ },
+ ], {
+ type: 'warning',
+ });
+ return;
+ }
+
+ // confirm delete
+ const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete ${html_encode(worker_data.name)}?`,
+ [
+ {
+ label: 'Yes, delete permanently',
+ value: 'delete',
+ type: 'danger',
+ },
+ {
+ label: 'Cancel'
+ },
+ ]
+ );
+
+ if (alert_resp === 'delete') {
+ let init_ts = Date.now();
+ puter.ui.showSpinner();
+ puter.workers.delete(worker_name).then(async (worker) => {
+ setTimeout(() => {
+ puter.ui.hideSpinner();
+ $(`.worker-card[data-name="${worker_name}"]`).fadeOut(200, function name(params) {
+ $(this).remove();
+ if ($(`.worker-card`).length === 0) {
+ $('section:not(.sidebar)').hide();
+ $('#no-workers-notice').show();
+ } else {
+ $('section:not(.sidebar)').hide();
+ $('#worker-list').show();
+ }
+ count_workers();
+ });
+ },
+ // make sure the modal was shown for at least 2 seconds
+ (Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
+
+ // delete worker directory
+ puter.fs.delete(
+ `/${auth_username}/AppData/${dev_center_uid}/${worker_name}`,
+ { recursive: true }
+ )
+
+ }).catch(async (err) => {
+ setTimeout(() => {
+ puter.ui.hideSpinner();
+ puter.ui.alert(err?.message, [
+ {
+ label: 'Ok',
+ },
+ ]);
+ },
+ // make sure the modal was shown for at least 2 seconds
+ (Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
+ })
+ }
+})
+
+
+
+export default init_workers;
\ No newline at end of file