Merge pull request #1417 from HeyPuter/workers-in-dev-center

Workers and websites in dev center
This commit is contained in:
Nariman Jelveh authored and GitHub committed 2025-08-05 19:06:44 -07:00
commit 5968da1202
27 files changed
+4331 -2855

No files matched your search

+201 -80
View File
@@ -24,9 +24,7 @@
}
html {
width: 100vw;
height: 100vh;
background-color: #eff1f5
}
body{
@@ -37,7 +35,7 @@ body{
flex: 1;
}
h1 .app-count{
h1 .app-count, h1 .worker-count, h1 .website-count{
font-size: 20px;
color: #6d767d;
font-weight: 400;
@@ -46,6 +44,7 @@ h1 .app-count{
padding: 2px 10px;
border-radius: 3px;
}
/* ------------------------------------
Button
------------------------------------*/
@@ -72,10 +71,6 @@ h1 .app-count{
border-style: solid;
border-width: 1px;
line-height: 35px;
background: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e1e1e1));
background: linear-gradient(#f6f6f6, #e1e1e1);
-webkit-box-shadow: inset 0px 1px 0px rgb(255 255 255 / 30%), 0 1px 2px rgb(0 0 0 / 15%);
box-shadow: inset 0px 1px 0px rgb(255 255 255 / 30%), 0 1px 2px rgb(0 0 0 / 15%);
border-radius: 4px;
outline: none;
@@ -206,6 +201,25 @@ h1 .app-count{
padding: 0 24px;
}
.refresh-app-list, .refresh-worker-list, .refresh-website-list {
width:40px;
padding: 10px;
float: right;
margin-right: 10px;
background-color: white;
border: none;
color: #0d6efd;
font-size: 13px;
cursor: pointer;
opacity: 0.9;
}
.refresh-app-list:hover, .refresh-worker-list:hover, .refresh-website-list:hover {
opacity: 1;
}
.refresh-icon{
width: 18px; height: 18px; margin-top: -2px; display: block;
}
a {
color: #0d6efd;
text-decoration: none;
@@ -223,55 +237,57 @@ section {
padding: 10px;
overflow: hidden;
width: 100%;
padding-right: 20px;
padding-left: 20px;
padding-right: 40px;
padding-left: 40px;
box-sizing: border-box;
}
#app-list{
#app-list, #website-list, #worker-list {
display: none;
}
#app-list-table > thead{
font-size:14px; background-color: #f7fafc; border-top: 1px solid #DDD; text-transform: uppercase; color: #6d767d;
#app-list-table > thead, #website-list-table > thead, #worker-list-table > thead{
font-size:14px;
border-top: 1px solid #DDD;
text-transform: uppercase;
color: #6d767d;
cursor: default;
}
.app-card {
.app-card, .website-card, .worker-card {
padding: 12px;
border-top: 1px solid #d8dce5;
border-top: 1px solid #f1f2f5;
border-radius: 0;
clear: both;
background: white;
overflow: hidden;
position: relative;
}
.app-card:hover {
background-color: #e8eff66e;
.app-card:hover, .website-card:hover, .worker-card:hover {
background-color: #f0f5fb6e;
}
.app-card.active {
background-color: #e8eff6;
.app-card.active, .website-card.active, .worker-card.active {
background-color: #ebeff39a;
}
.app-card:hover .app-row-toolbar {
.app-card:hover .app-row-toolbar, .website-card:hover .website-row-toolbar, .worker-card:hover .worker-row-toolbar {
visibility: visible;
opacity: 1;
}
.app-toolbar-container {
.app-toolbar-container, .website-toolbar-container, .worker-toolbar-container {
height: 16px;
overflow: hidden;
position: relative;
}
.app-card h1 {
.app-card h1, .website-card h1, .worker-card h1 {
margin-top: 0;
color: #657188;
font-weight: 400;
font-size: 25px;
}
#create-app-success {
#create-app-success, #create-website-success, #create-worker-success {
height: calc(100vh - 40px);
overflow: hidden;
text-align: center;
@@ -286,7 +302,7 @@ label, input[type="text"] {
user-select: none;
}
#delete-app {
#delete-app, #delete-website, #delete-worker {
cursor: pointer;
float: left;
margin-top: 30px;
@@ -294,7 +310,7 @@ label, input[type="text"] {
font-size: 13px;
}
#delete-app:hover {
#delete-app:hover, #delete-website:hover, #delete-worker:hover {
text-decoration: underline;
}
@@ -420,7 +436,7 @@ label {
}
.app-card-link:hover{
text-decoration: underline;
text-decoration: underline !important;
}
.edit-app img {
@@ -439,17 +455,19 @@ label {
left: 0;
z-index: 1000;
display: block;
padding: 30px 16px;
padding: 40px 16px 0;
overflow-x: hidden;
overflow-y: auto;
background-color: white;
border-right: 1px solid #e1e1e1;
background-color: rgb(249, 249, 249);
border-right: 1px solid #eeeeee;
color: rgb(51, 51, 51);
font-weight: 400;
width: 25px;
}
.sidebar .sidebar-content{
display: none;
position: relative;
height: 100%;
}
.sidebar.open{
width: 250px;
@@ -465,15 +483,16 @@ label {
right: 0;
top: 0;
padding: 10px;
background-size: 15px;
background-size: 25px;
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
background-image: url("data:image/svg+xml,%3Csvg%20enable-background%3D%22new%200%200%2024%2024%22%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22m3%2023-3-3%208-8-8-8%203-3%2011%2011zm10%200-3-3%208-8-8-8%203-3%2011%2011z%22%2F%3E%3C%2Fsvg%3E");
opacity: 0.3;
background-image: url("data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!--%20Uploaded%20to%3A%20SVG%20Repo%2C%20www.svgrepo.com%2C%20Generator%3A%20SVG%20Repo%20Mixer%20Tools%20--%3E%3Csvg%20fill%3D%22%23000000%22%20width%3D%22800px%22%20height%3D%22800px%22%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M15.2928932%2C12%20L12.1464466%2C8.85355339%20C11.9511845%2C8.65829124%2011.9511845%2C8.34170876%2012.1464466%2C8.14644661%20C12.3417088%2C7.95118446%2012.6582912%2C7.95118446%2012.8535534%2C8.14644661%20L16.8535534%2C12.1464466%20C17.0488155%2C12.3417088%2017.0488155%2C12.6582912%2016.8535534%2C12.8535534%20L12.8535534%2C16.8535534%20C12.6582912%2C17.0488155%2012.3417088%2C17.0488155%2012.1464466%2C16.8535534%20C11.9511845%2C16.6582912%2011.9511845%2C16.3417088%2012.1464466%2C16.1464466%20L15.2928932%2C13%20L4.5%2C13%20C4.22385763%2C13%204%2C12.7761424%204%2C12.5%20C4%2C12.2238576%204.22385763%2C12%204.5%2C12%20L15.2928932%2C12%20Z%20M19%2C5.5%20C19%2C5.22385763%2019.2238576%2C5%2019.5%2C5%20C19.7761424%2C5%2020%2C5.22385763%2020%2C5.5%20L20%2C19.5%20C20%2C19.7761424%2019.7761424%2C20%2019.5%2C20%20C19.2238576%2C20%2019%2C19.7761424%2019%2C19.5%20L19%2C5.5%20Z%22%2F%3E%3C%2Fsvg%3E");
opacity: 0.6;
margin-top: 5px;
}
.open .sidebar-toggle{
background-image: url("data:image/svg+xml,%3Csvg%20enable-background%3D%22new%200%200%2024%2024%22%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22m21%201%203%203-8%208%208%208-3%203-11-11zm-10%200%203%203-8%208%208%208-3%203-11-11z%22%2F%3E%3C%2Fsvg%3E");
background-image: url("data:image/svg+xml,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!--%20Uploaded%20to%3A%20SVG%20Repo%2C%20www.svgrepo.com%2C%20Generator%3A%20SVG%20Repo%20Mixer%20Tools%20--%3E%3Csvg%20fill%3D%22%23000000%22%20width%3D%22800px%22%20height%3D%22800px%22%20viewBox%3D%220%200%2024%2024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8.70710678%2C12%20L19.5%2C12%20C19.7761424%2C12%2020%2C12.2238576%2020%2C12.5%20C20%2C12.7761424%2019.7761424%2C13%2019.5%2C13%20L8.70710678%2C13%20L11.8535534%2C16.1464466%20C12.0488155%2C16.3417088%2012.0488155%2C16.6582912%2011.8535534%2C16.8535534%20C11.6582912%2C17.0488155%2011.3417088%2C17.0488155%2011.1464466%2C16.8535534%20L7.14644661%2C12.8535534%20C6.95118446%2C12.6582912%206.95118446%2C12.3417088%207.14644661%2C12.1464466%20L11.1464466%2C8.14644661%20C11.3417088%2C7.95118446%2011.6582912%2C7.95118446%2011.8535534%2C8.14644661%20C12.0488155%2C8.34170876%2012.0488155%2C8.65829124%2011.8535534%2C8.85355339%20L8.70710678%2C12%20L8.70710678%2C12%20Z%20M4%2C5.5%20C4%2C5.22385763%204.22385763%2C5%204.5%2C5%20C4.77614237%2C5%205%2C5.22385763%205%2C5.5%20L5%2C19.5%20C5%2C19.7761424%204.77614237%2C20%204.5%2C20%20C4.22385763%2C20%204%2C19.7761424%204%2C19.5%20L4%2C5.5%20Z%22%2F%3E%3C%2Fsvg%3E");
}
.sidebar-toggle:hover{
opacity: 1;
@@ -492,22 +511,30 @@ label {
display: block;
padding: 10px 20px;
cursor: pointer;
margin-bottom: 10px;
margin-left: 10px;
margin-right: 10px;
border-radius: 7px;
}
.sidebar-nav>li:hover {
background-color: #656c771b;
}
.sidebar-nav>li.active {
color: #fff;
background-color: #3273dc;
background-color: #3a85ff;
}
.sidebar-nav > li .app-count{
font-size: 14px;
.sidebar-nav > li .app-count, .sidebar-nav > li .worker-count, .sidebar-nav > li .website-count {
font-size: 16px;
color: #6d767d;
font-weight: 400;
margin-left: 10px;
float: right;
}
.tab-btn.active .app-count{
.tab-btn.active .app-count, .tab-btn.active .worker-count, .tab-btn.active .website-count{
color: #f6f6f6;
}
@@ -537,21 +564,21 @@ label {
.main>section {
background-color: white;
box-shadow: 0px 0px 3px #E1E1E1;
border: 1px solid #e1e1e1;
border-radius: 3px;
}
.link-to-docs{
color: #586373;
font-size: 15px;
}
.link-to-docs, .link-to-docs:hover {
font-size: 14px;
text-decoration: none;
}
.link-to-docs:hover {
text-decoration: underline !important;
}
.link-to-docs img{
width: 12px;
margin-bottom: -1px;
margin-left: 5px;
}
.tab-btn {
background-size: 20px;
@@ -569,6 +596,19 @@ label {
background-image: url(../img/apps-outline-black.svg);
}
.tab-btn[data-tab="websites"] {
background-image: url(../img/website.svg);
}
.tab-btn.active[data-tab="websites"] {
background-image: url(../img/website-white.svg);
}
.tab-btn[data-tab="workers"] {
background-image: url(../img/workers.svg);
}
.tab-btn.active[data-tab="workers"] {
background-image: url(../img/workers-white.svg);
}
.tab-btn[data-tab="payout-method"] {
background-image: url(../img/wallet.svg);
}
@@ -590,7 +630,7 @@ label {
box-sizing: border-box;
}
#no-apps-notice, #loading {
#no-apps-notice, #no-workers-notice, #no-websites-notice, #loading {
display: flex;
flex-direction: column;
justify-content: center;
@@ -627,7 +667,7 @@ table {
.table>thead>tr>th {
vertical-align: bottom;
border-bottom: 2px solid #ddd;
border-bottom: 1px solid #ddd;
}
.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td {
@@ -637,10 +677,17 @@ table {
}
.table>thead>tr>th{
padding: 5px;
background-color: #f8f9fa;
font-weight: 600;
color: #676e77;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.05em;
}
th {
text-align: left;
padding-bottom: 0 !important;
font-size: 12px;
}
td, th {
@@ -717,13 +764,14 @@ th.sorted{
user-select: none !important;
}
.my-apps-title {
font-size: 27px;
.my-apps-title, .my-workers-title, .my-websites-title {
font-size: 22px;
margin-top: 0px;
float: left;
font-weight: 500;
margin-bottom: 15px;
margin-bottom: 0;
color: #394254;
flex-grow: 1;
}
.app-row-toolbar {
@@ -916,14 +964,23 @@ ol li:before {
background-color: rgba(0, 0, 0, 0.4);
}
.create-an-app-btn{
float:right; margin-bottom: 10px;
.create-an-app-btn, .create-a-website-btn{
float:right;
margin-bottom: 0px;
}
.create-an-app-btn img{
width: 20px;
.create-an-app-btn img, .create-a-website-btn img, .create-a-worker-btn img{
width: 25px;
height: 25px;
margin-bottom: -5px;
margin-right: 10px;
margin-bottom: -7px;
margin-right: 4px;
}
.create-a-worker-btn, .create-a-website-btn{
float:right;
margin-bottom: 0px;
}
.jip-submit-btn{
float: left;
margin-top: 10px;
@@ -932,8 +989,13 @@ ol li:before {
.ip-terms-notice{
font-size: 12px; margin-top: 20px; margin-bottom: 0;
}
.app-list-nav{
overflow: hidden; margin-bottom: 40px; margin-top: 10px;
.app-list-nav, .worker-list-nav, .website-list-nav{
overflow: hidden;
margin-bottom: 40px;
margin-top: 10px;
display: flex;
flex-direction: row;
align-items: center;
}
.back-to-main-btn{
float:right; margin-bottom: 10px;
@@ -945,7 +1007,7 @@ ol li:before {
display: flex;
align-items: center;
}
.app-title{
.app-title, .website-title{
margin-top:0px;
margin-bottom: 0;
}
@@ -1087,20 +1149,36 @@ dialog{
width: 100%;
box-sizing: border-box;
background-color: white;
padding: 5px;
background-size: 20px;
padding: 8px;
background-size: 15px;
background-position-y: center;
background-position-x: 5px;
background-position-x: 9px;
padding-left: 35px;
padding-right: 35px;
border: 2px solid #CCC;
border: 2px solid #ebebeb;
font-size: 14px;
}
.search-container{
margin-bottom: 10px;
position: relative;
width: 300px;
float:left;
}
.search::placeholder {
opacity: 0.7;
}
.search:focus, .search:focus-within, .search:active, .search:focus-visible {
border: 2px solid #cbcbcb !important;
outline: none !important;
}
.search.has-value{
border: 2px solid #0066d2 !important;
}
.search-clear {
display: none;
position: absolute;
right: 6px;
top: 6px;
top: 8px;
opacity: 0.3;
height: 20px;
}
@@ -1209,6 +1287,17 @@ dialog{
display: inline-block;
background-color: #e3f2fd;
color: #1976d2;
background-color: #f3f4f6;
color: #374151;
padding: 1px 6px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
margin: 1px 0 0 0;
max-width: fit-content;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.category-select {
@@ -1233,28 +1322,29 @@ dialog{
width: 18px; margin-right: 5px; margin-bottom: -4px;
}
.category-badge {
display: inline-block;
background-color: #f2f2f2;
color: #555;
font-size: 12px;
font-weight: 500;
padding: 2px 6px;
margin-left: 8px;
border-radius: 6px;
vertical-align: middle;
display: inline-block;
background-color: #f2f2f2;
color: #555;
font-size: 12px;
font-weight: 500;
padding: 2px 6px;
margin-left: 8px;
border-radius: 6px;
vertical-align: middle;
}
.app-category {
display: inline-block;
background-color: #f3f4f6;
color: #374151;
padding: 1px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
margin-top: 2px;
max-height: 18px;
line-height: 1.2;
white-space: nowrap;
display: inline-block;
background-color: #f3f4f6;
color: #374151;
padding: 1px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
margin-top: 2px;
max-height: 18px;
line-height: 1.2;
white-space: nowrap;
border: 1px solid #CCC;
}
.app-row-toolbar span {
@@ -1271,4 +1361,35 @@ dialog{
color: #000; /* Optional: darken on hover */
}
.options-icon {
cursor: pointer;
opacity: 0.7;
width: 30px;
height: 30px;
border-radius: 5px;
}
.options-icon:hover {
opacity: 1;
background-color: #edededb4;
}
.worker-checkbox, .app-checkbox, .website-checkbox {
width: 15px; height: 20px;
}
.no-hover:hover {
background-color: transparent !important;
}
.root-dir-name{
cursor: pointer;
}
.worker-file-path{
cursor: pointer;
}
.tab-btn-separator{
display: none;
}
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#000000" stroke-width="1" stroke-linecap="round" stroke-linejoin="miter"><line x1="11.99" y1="6" x2="12" y2="6" stroke-linecap="round" stroke-width="2"></line><line x1="11.99" y1="12" x2="12" y2="12" stroke-linecap="round" stroke-width="2"></line><line x1="11.99" y1="18" x2="12" y2="18" stroke-linecap="round" stroke-width="2"></line></svg>

After

Width:  |  Height:  |  Size: 569 B

+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" fill="rgba(0,0,0,0)"/>
<path d="M12 6V18" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
<path d="M6 12H18" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 408 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-globe-americas" viewBox="0 0 16 16">
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0M2.04 4.326c.325 1.329 2.532 2.54 3.717 3.19.48.263.793.434.743.484q-.121.12-.242.234c-.416.396-.787.749-.758 1.266.035.634.618.824 1.214 1.017.577.188 1.168.38 1.286.983.082.417-.075.988-.22 1.52-.215.782-.406 1.48.22 1.48 1.5-.5 3.798-3.186 4-5 .138-1.243-2-2-3.5-2.5-.478-.16-.755.081-.99.284-.172.15-.322.279-.51.216-.445-.148-2.5-2-1.5-2.5.78-.39.952-.171 1.227.182.078.099.163.208.273.318.609.304.662-.132.723-.633.039-.322.081-.671.277-.867.434-.434 1.265-.791 2.028-1.12.712-.306 1.365-.587 1.579-.88A7 7 0 1 1 2.04 4.327Z"/>
</svg>

After

Width:  |  Height:  |  Size: 710 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-globe-americas" viewBox="0 0 16 16">
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0M2.04 4.326c.325 1.329 2.532 2.54 3.717 3.19.48.263.793.434.743.484q-.121.12-.242.234c-.416.396-.787.749-.758 1.266.035.634.618.824 1.214 1.017.577.188 1.168.38 1.286.983.082.417-.075.988-.22 1.52-.215.782-.406 1.48.22 1.48 1.5-.5 3.798-3.186 4-5 .138-1.243-2-2-3.5-2.5-.478-.16-.755.081-.99.284-.172.15-.322.279-.51.216-.445-.148-2.5-2-1.5-2.5.78-.39.952-.171 1.227.182.078.099.163.208.273.318.609.304.662-.132.723-.633.039-.322.081-.671.277-.867.434-.434 1.265-.791 2.028-1.12.712-.306 1.365-.587 1.579-.88A7 7 0 1 1 2.04 4.327Z"/>
</svg>

After

Width:  |  Height:  |  Size: 717 B

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--gis" preserveAspectRatio="xMidYMid meet"><path d="M50 0C22.404 0 0 22.404 0 50s22.404 50 50 50c27.546 0 49.911-22.324 49.992-49.852A1.5 1.5 0 0 0 100 50a1.5 1.5 0 0 0-.006-.133C99.922 22.332 77.552 0 50 0zm0 3c.47 0 .934.021 1.4.035c3.72.913 6.655 1.695 7.508 5.315c3.96 2.452 7.876 4.644 10.621 8.25c-2.187.64-5.398-.424-4.92-3.032c-1.432 1.981-2.17 1.102-4.117 1.073c1.52.334 2.126 3.453 4.092 1.658c2.367 2.37-3.233 3.463-4.414 2.564c-3.022.28.594 3.349-3.033 3.121c-2.848 2.194-.726 5.881-4.75 6.909c-5.052 1.503 2.347 8.222-2.701 9.043c-.198-2.146-1.58-5.96-4.772-5.905c-3.043 2.139-9.71-1.542-10.271 4.44c-2.162 2.915.289 9.094 4.195 6.904c.742-2.105 6.972-4.895 5.39-.822c-.633 2.547-2.454 4.902 1.176 4.006c5.107.214 1.485 4.535 3.25 7.023c1.98 1.115 5.004.691 7.03.596c.518-3.387 5.9-3.183 6.232-3.315c.562-1.404 3.245.034 2.822 1.567c3.407-.074 2.618-2.08 5.807-.565c2.59-2.399.66 2.8 3.805 3.162c2.099 2.106 6.875.146 7.304 4.621c1.152 4.259 7.465 1.637 9.512 4.604c-.118 4.559-3.97 7.805-5.162 12.08c-1.9 4.756-7.426 5.848-9.92 10.066c-3.576 3.24-7.58 5.573-11.875 7.524c-.845.482-2.273 1.174-3.514 1.842a46.97 46.97 0 0 1-7.107 1.086c.464-1.247 2.653-2.73 3.07-4.014c1.6-3.48 6.445-8.865 2.186-11.883c-3.156-.554-4.111-3.439-5.617-5.58c-.952-2.87-5.13-5.028-2.811-7.736c-1.096-2.628 2.527-5.076 3.69-7.262c.36-2.336-1.94-6.07-3.202-2.97c-2.581-.797-5.73-2.474-6.652-5.184c-2.742-1.805-6.544-3.752-9.195-5.016c-3.306.153-6.324-2.243-8.735-4.24c-.234-3.476-2.303-7.77-3.972-10.01c1.513 2.335 1.282 8.624-1.153 3.38c-.7-2.38-1.053-5.489-1.197-8.142c-1.816-1.224-.973-6.548.805-8.767c1.985-1.55 3.598-6.143 3.496-6.916c-1.121.404.678-1.517 1.539-2.834A46.801 46.801 0 0 1 50 3zm6.785.494a46.706 46.706 0 0 1 8.856 2.178c.043.11.087.222.148.35c4.282 4.875-4.403.474-6.209-.985c-.621-.531-1.66-1.082-2.795-1.543zm-5.834 1.238c-1.098.056-1.974.6-.53 1.436c-.913 2.316-6.523-.88-6.587 2.582c1.738 1.693 5.434 1.518 6.861 3.225c-1.247-2.286 1.986-2.882 1.03-1.448c-.172 1.013 1.223-1.557-.192-.863c-.447-2.703-1.937-2.865 1.184-4.465c-.367-.359-1.107-.5-1.766-.467zm2.485 30.301c1.597.073 2.983 3.4 3.92 3.436c.097-.307 1.185 3.26-.25 1.84c-2.252-.546-1.826-5.038-3.01-1.393c-2.968.075.761-3.487-1.797-3.332c.388-.41.768-.568 1.137-.55zm-3.729 3.557c3.169.096 6.185 3.82 8.564 2.146c.895.68 1.127 2.356 3.891 1.94c3.393 1.103 1.534 2.464-.54 2.23c-.626-.093-.964 1.262-1.524.287c-1.344.154-4.462-.705-1.864-1.697c.01-2.245-4.72 1.662-4.742-1.28c-2.114-.751-3.54-2.205-5.299-.718c-.98-.772-3.473-.698-.699-2.283a3.785 3.785 0 0 1 2.213-.625zm19.107 5.277c.37-.144 1.316 1.79 1.65.387c.646 1.7 2.371 6.354.093 6.605c1.183-2.5-.758-4.772-1.891-6.494c.01-.32.063-.465.148-.498z" fill="#000000"></path></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512" xml:space="preserve">
<g>
<polygon style="fill:#FF9911;" points="357.016,284.718 414.093,251.548 184.458,478.831 127.381,512 "/>
<polygon style="fill:#FFEB9A;" points="246.056,220.655 303.133,187.485 414.093,251.548 357.016,284.718 "/>
<polygon style="fill:#FF9911;" points="327.203,104.979 384.28,71.81 303.133,187.485 246.056,220.655 "/>
<polygon style="fill:#FFEB9A;" points="202.824,33.17 259.902,0 384.28,71.81 327.203,104.979 "/>
<polygon style="fill:#FEDA44;" points="357.016,284.718 127.381,512 218.95,282.398 97.907,212.514 202.824,33.17 327.203,104.979
246.056,220.655 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 896 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-lightning" viewBox="0 0 16 16">
<path d="M5.52.359A.5.5 0 0 1 6 0h4a.5.5 0 0 1 .474.658L8.694 6H12.5a.5.5 0 0 1 .395.807l-7 9a.5.5 0 0 1-.873-.454L6.823 9.5H3.5a.5.5 0 0 1-.48-.641zM6.374 1 4.168 8.5H7.5a.5.5 0 0 1 .478.647L6.78 13.04 11.478 7H8a.5.5 0 0 1-.474-.658L9.306 1z"/>
</svg>

After

Width:  |  Height:  |  Size: 376 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-lightning" viewBox="0 0 16 16">
<path d="M5.52.359A.5.5 0 0 1 6 0h4a.5.5 0 0 1 .474.658L8.694 6H12.5a.5.5 0 0 1 .395.807l-7 9a.5.5 0 0 1-.873-.454L6.823 9.5H3.5a.5.5 0 0 1-.48-.641zM6.374 1 4.168 8.5H7.5a.5.5 0 0 1 .478.647L6.78 13.04 11.478 7H8a.5.5 0 0 1-.474-.658L9.306 1z"/>
</svg>

After

Width:  |  Height:  |  Size: 383 B

+123 -24
View File
@@ -19,6 +19,9 @@
<link rel="preload" as="image" href="./img/wallet.svg">
<link rel="preload" as="image" href="./img/wallet-white.svg">
<link rel="preload" as="image" href="./img/paypal.svg">
<link rel="preload" as="image" href="./img/plus.svg">
<link rel="preload" as="image" href="./img/workers-placeholder.svg">
<link rel="preload" as="image" href="./img/websites-placeholder.svg">
<!-- For tooltip -->
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://unpkg.com/tippy.js@6"></script>
@@ -77,23 +80,28 @@
<section class="sidebar open">
<div class="sidebar-toggle"></div>
<div class="sidebar-content">
<!-- Main Sections -->
<ul class="sidebar-nav disable-user-select">
<li class="tab-btn active" data-tab="apps">My Apps<span class="app-count"></span></li>
<li class="tab-btn active" data-tab="apps">Apps<span class="app-count"></span></li>
<li class="tab-btn" data-tab="websites">Websites<span class="website-count"></span></li>
<li class="tab-btn" data-tab="workers">Workers<span class="worker-count"></span></li>
<hr class="tab-btn-separator">
<li class="tab-btn" data-tab="payout-method" style="display:none;">Payout Method</li>
</ul>
<hr>
<!-- Footer -->
<div style="overflow: hidden; position: absolute; bottom: 0; width: 100%;">
<ul class="sidebar-nav">
<li class="no-hover" style="margin-left:0;"><a href="https://developer.puter.com/" class="link-to-docs" target="_blank">Developer Documentation<img src="img/external-link.svg"></a></li>
</ul>
<ul class="sidebar-nav">
<li><a href="https://docs.puter.com/" class="link-to-docs" target="_blank">Developer Documentation <img src="img/external-link.svg"></a></li>
</ul>
<ul class="sidebar-nav sidebar-nav-social">
<li><a href="https://github.com/HeyPuter/puter" class="social-link" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-github" viewBox="0 0 16 16"> <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8"/> </svg></a></li>
<li><a href="https://dsc.gg/puter" class="social-link" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-discord" viewBox="0 0 16 16"> <path d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"/> </svg></a></li>
<li><a href="https://x.com/HeyPuter" class="social-link" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter-x" viewBox="0 0 16 16"> <path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865z"/> </svg></a></li>
<li><a href="https://reddit.com/r/puter" class="social-link" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-reddit" viewBox="0 0 16 16"> <path d="M6.167 8a.83.83 0 0 0-.83.83c0 .459.372.84.83.831a.831.831 0 0 0 0-1.661m1.843 3.647c.315 0 1.403-.038 1.976-.611a.23.23 0 0 0 0-.306.213.213 0 0 0-.306 0c-.353.363-1.126.487-1.67.487-.545 0-1.308-.124-1.671-.487a.213.213 0 0 0-.306 0 .213.213 0 0 0 0 .306c.564.563 1.652.61 1.977.61zm.992-2.807c0 .458.373.83.831.83s.83-.381.83-.83a.831.831 0 0 0-1.66 0z"/> <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-3.828-1.165c-.315 0-.602.124-.812.325-.801-.573-1.9-.945-3.121-.993l.534-2.501 1.738.372a.83.83 0 1 0 .83-.869.83.83 0 0 0-.744.468l-1.938-.41a.2.2 0 0 0-.153.028.2.2 0 0 0-.086.134l-.592 2.788c-1.24.038-2.358.41-3.17.992-.21-.2-.496-.324-.81-.324a1.163 1.163 0 0 0-.478 2.224q-.03.17-.029.353c0 1.795 2.091 3.256 4.669 3.256s4.668-1.451 4.668-3.256c0-.114-.01-.238-.029-.353.401-.181.688-.592.688-1.069 0-.65-.525-1.165-1.165-1.165"/> </svg></a></li>
</ul>
<ul class="sidebar-nav sidebar-nav-social">
<li class="no-hover"><a href="https://github.com/HeyPuter/puter" class="social-link" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-github" viewBox="0 0 16 16"> <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8"/> </svg></a></li>
<li class="no-hover"><a href="https://dsc.gg/puter" class="social-link" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-discord" viewBox="0 0 16 16"> <path d="M13.545 2.907a13.2 13.2 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.2 12.2 0 0 0-3.658 0 8 8 0 0 0-.412-.833.05.05 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.04.04 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032q.003.022.021.037a13.3 13.3 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019q.463-.63.818-1.329a.05.05 0 0 0-.01-.059l-.018-.011a9 9 0 0 1-1.248-.595.05.05 0 0 1-.02-.066l.015-.019q.127-.095.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.05.05 0 0 1 .053.007q.121.1.248.195a.05.05 0 0 1-.004.085 8 8 0 0 1-1.249.594.05.05 0 0 0-.03.03.05.05 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.2 13.2 0 0 0 4.001-2.02.05.05 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.03.03 0 0 0-.02-.019m-8.198 7.307c-.789 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612m5.316 0c-.788 0-1.438-.724-1.438-1.612s.637-1.613 1.438-1.613c.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612"/> </svg></a></li>
<li class="no-hover"><a href="https://x.com/HeyPuter" class="social-link" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter-x" viewBox="0 0 16 16"> <path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865z"/> </svg></a></li>
<li class="no-hover"><a href="https://reddit.com/r/puter" class="social-link" target="_blank"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-reddit" viewBox="0 0 16 16"> <path d="M6.167 8a.83.83 0 0 0-.83.83c0 .459.372.84.83.831a.831.831 0 0 0 0-1.661m1.843 3.647c.315 0 1.403-.038 1.976-.611a.23.23 0 0 0 0-.306.213.213 0 0 0-.306 0c-.353.363-1.126.487-1.67.487-.545 0-1.308-.124-1.671-.487a.213.213 0 0 0-.306 0 .213.213 0 0 0 0 .306c.564.563 1.652.61 1.977.61zm.992-2.807c0 .458.373.83.831.83s.83-.381.83-.83a.831.831 0 0 0-1.66 0z"/> <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-3.828-1.165c-.315 0-.602.124-.812.325-.801-.573-1.9-.945-3.121-.993l.534-2.501 1.738.372a.83.83 0 1 0 .83-.869.83.83 0 0 0-.744.468l-1.938-.41a.2.2 0 0 0-.153.028.2.2 0 0 0-.086.134l-.592 2.788c-1.24.038-2.358.41-3.17.992-.21-.2-.496-.324-.81-.324a1.163 1.163 0 0 0-.478 2.224q-.03.17-.029.353c0 1.795 2.091 3.256 4.669 3.256s4.668-1.451 4.668-3.256c0-.114-.01-.238-.029-.353.401-.181.688-.592.688-1.069 0-.65-.525-1.165-1.165-1.165"/> </svg></a></li>
</ul>
</div>
</div>
</section>
@@ -174,9 +182,26 @@
<section id="no-apps-notice" style="display:none;">
<img src="./img/apps-black.svg" style="width: 64px; opacity: 0.12;">
<p style="color: #606062;">You haven't created any apps yet.</p>
<button class="create-an-app-btn button button-primary"><img src="./img/app-outline-white.svg">Create an App</button>
<button class="create-an-app-btn button button-primary"><img src="./img/plus.svg">Create an App</button>
</section>
<!---------------------------------------->
<!-- No Workers Message -->
<!---------------------------------------->
<section id="no-workers-notice" style="display:none;">
<img src="./img/workers-placeholder.svg" style="width: 64px; height: 64px; opacity: 0.62; filter: grayscale(100%); transform: rotate(-20deg);">
<p style="color: #606062;">You haven't created any workers yet.</p>
<button class="create-a-worker-btn button button-primary"><img src="./img/plus.svg">Create a Worker</button>
</section>
<!---------------------------------------->
<!-- No Websites Message -->
<!---------------------------------------->
<section id="no-websites-notice" style="display:none;">
<img src="./img/websites-placeholder.svg" style="width: 64px; height: 64px; opacity: 0.22; filter: grayscale(100%);">
<p style="color: #606062;">You haven't created any websites yet.</p>
<button class="create-a-website-btn button button-primary"><img src="./img/plus.svg">Create a Website</button>
</section>
<!---------------------------------------->
<!-- Edit App -->
<!---------------------------------------->
@@ -209,26 +234,61 @@
<section id="app-list">
<div class="app-list-nav">
<h1 class="my-apps-title">My Apps<span class="app-count"></span></h1>
<button class="create-an-app-btn button button-primary"><img src="./img/app-outline-white.svg"> New App</button>
<button class="refresh-app-list button button-default" style="width:40px; padding: 10px; float: right; margin-right: 10px;" title="Refresh"><svg style="width: 18px; height: 18px; margin-top: -2px; display: block;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32" stroke-width="2"><g stroke-width="2" transform="translate(0.5, 0.5)"><path data-cap="butt" d="M29.382,9.217A15,15,0,0,0,1,16" fill="none" stroke="#444444" stroke-miterlimit="10" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter"></path><polyline points="28.383 1.22 29.383 9.22 21.383 8.22" fill="none" stroke="#444444" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2" stroke-linejoin="miter"></polyline><path data-cap="butt" data-color="color-2" d="M2.618,22.783A15,15,0,0,0,31,16" fill="none" stroke="#444444" stroke-miterlimit="10" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter"></path><polyline data-color="color-2" points="3.617 30.78 2.617 22.78 10.617 23.78" fill="none" stroke="#444444" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2" stroke-linejoin="miter"></polyline></g></svg></button>
<button class="create-an-app-btn button button-primary"><img src="./img/plus.svg" style="width: 25px; height: 25px;">New App</button>
</div>
<div style="margin-bottom: 10px; position: relative; width: 300px; float:left;">
<input style="background-image:url(./img/magnifier-outline.svg);" class="search">
<img class="search-clear" src="./img/close.svg">
<div class="search-container">
<input style="background-image:url(./img/magnifier-outline.svg);" class="search search-apps" placeholder="Search apps">
<img class="search-clear search-clear-apps" src="./img/close.svg">
</div>
<button class="button button-danger disabled delete-apps-btn" style="float:right;">Delete</button>
<button class="refresh-app-list" title="Refresh"><svg class="refresh-icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32" stroke-width="2"><g stroke-width="2" transform="translate(0.5, 0.5)"><path data-cap="butt" d="M29.382,9.217A15,15,0,0,0,1,16" fill="none" stroke="#444444" stroke-miterlimit="10" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter"></path><polyline points="28.383 1.22 29.383 9.22 21.383 8.22" fill="none" stroke="#444444" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2" stroke-linejoin="miter"></polyline><path data-cap="butt" data-color="color-2" d="M2.618,22.783A15,15,0,0,0,31,16" fill="none" stroke="#444444" stroke-miterlimit="10" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter"></path><polyline data-color="color-2" points="3.617 30.78 2.617 22.78 10.617 23.78" fill="none" stroke="#444444" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2" stroke-linejoin="miter"></polyline></g></svg></button>
<div style="overflow-x: auto; clear: both;">
<table class="table" id="app-list-table">
<thead class="disable-user-select">
<tr>
<th><input type="checkbox" class="select-all-apps" style="width: 20px; height: 20px; margin-left:3px;"></th>
<th><input type="checkbox" class="select-all-apps" style="width: 15px; height: 20px; margin-left:3px;"></th>
<th class="sort th-name" data-column="name" style="padding-left: 10px !important;">App<span class="sort-arrow sort-arrow-desc">▼</span><span class="sort-arrow sort-arrow-asc">▲</span></th>
<th class="sort th-users" data-column="user_count">Users<span class="sort-arrow sort-arrow-desc">▼</span><span class="sort-arrow sort-arrow-asc">▲</span></th>
<th class="sort th-opens" data-column="open_count">Opens<span class="sort-arrow sort-arrow-desc">▼</span><span class="sort-arrow sort-arrow-asc">▲</span></th>
<th class="sort th-created sorted" data-column="created_at">Created<span class="sort-arrow sort-arrow-desc" style="display:inline;">▼</span><span class="sort-arrow sort-arrow-asc">▲</span></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</section>
<!---------------------------------------->
<!-- Worker List -->
<!---------------------------------------->
<section id="worker-list">
<div class="worker-list-nav">
<h1 class="my-workers-title">My Workers<span class="worker-count"></span></h1>
<button class="create-a-worker-btn button button-primary"><img src="./img/plus.svg" style="width: 25px; height: 25px;"> New Worker</button>
</div>
<div class="search-container">
<input style="background-image:url(./img/magnifier-outline.svg);" class="search search-workers" placeholder="Search workers">
<img class="search-clear search-clear-workers" src="./img/close.svg">
</div>
<button class="button button-danger disabled delete-workers-btn" style="float:right;">Delete</button>
<button class="refresh-worker-list" title="Refresh"><svg class="refresh-icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32" stroke-width="2"><g stroke-width="2" transform="translate(0.5, 0.5)"><path data-cap="butt" d="M29.382,9.217A15,15,0,0,0,1,16" fill="none" stroke="#444444" stroke-miterlimit="10" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter"></path><polyline points="28.383 1.22 29.383 9.22 21.383 8.22" fill="none" stroke="#444444" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2" stroke-linejoin="miter"></polyline><path data-cap="butt" data-color="color-2" d="M2.618,22.783A15,15,0,0,0,31,16" fill="none" stroke="#444444" stroke-miterlimit="10" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter"></path><polyline data-color="color-2" points="3.617 30.78 2.617 22.78 10.617 23.78" fill="none" stroke="#444444" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2" stroke-linejoin="miter"></polyline></g></svg></button>
<div style="overflow-x: auto; clear: both;">
<table class="table" id="worker-list-table">
<thead class="disable-user-select">
<tr>
<th><input type="checkbox" class="select-all-workers" style="width: 15px; height: 20px; margin-left:3px;"></th>
<th class="sort th-name" data-column="name" style="padding-left: 10px !important;">Worker<span class="sort-arrow sort-arrow-desc">▼</span><span class="sort-arrow sort-arrow-asc">▲</span></th>
<th class="sort th-file" data-column="file_path">File<span class="sort-arrow sort-arrow-desc">▼</span><span class="sort-arrow sort-arrow-asc">▲</span></th>
<th class="sort th-created sorted" data-column="created_at">Created<span class="sort-arrow sort-arrow-desc" style="display:inline;">▼</span><span class="sort-arrow sort-arrow-asc">▲</span></th>
<th></th>
</tr>
</thead>
<tbody>
@@ -237,14 +297,53 @@
</table>
</div>
</section>
<!---------------------------------------->
<!-- Websites List -->
<!---------------------------------------->
<section id="website-list">
<div class="website-list-nav">
<h1 class="my-websites-title">My Websites<span class="website-count"></span></h1>
<button class="create-a-website-btn button button-primary"><img src="./img/plus.svg" style="width: 25px; height: 25px;">New Website</button>
</div>
<div class="search-container">
<input style="background-image:url(./img/magnifier-outline.svg);" class="search search-websites" placeholder="Search websites">
<img class="search-clear search-clear-websites" src="./img/close.svg">
</div>
<button class="button button-danger disabled delete-websites-btn" style="float:right;">Delete</button>
<button class="refresh-website-list" style="width:40px; padding: 10px; float: right; margin-right: 10px;" title="Refresh"><svg class="refresh-icon" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32" stroke-width="2"><g stroke-width="2" transform="translate(0.5, 0.5)"><path data-cap="butt" d="M29.382,9.217A15,15,0,0,0,1,16" fill="none" stroke="#444444" stroke-miterlimit="10" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter"></path><polyline points="28.383 1.22 29.383 9.22 21.383 8.22" fill="none" stroke="#444444" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2" stroke-linejoin="miter"></polyline><path data-cap="butt" data-color="color-2" d="M2.618,22.783A15,15,0,0,0,31,16" fill="none" stroke="#444444" stroke-miterlimit="10" stroke-width="2" stroke-linecap="butt" stroke-linejoin="miter"></path><polyline data-color="color-2" points="3.617 30.78 2.617 22.78 10.617 23.78" fill="none" stroke="#444444" stroke-linecap="square" stroke-miterlimit="10" stroke-width="2" stroke-linejoin="miter"></polyline></g></svg></button>
<div style="overflow-x: auto; clear: both;">
<table class="table" id="website-list-table">
<thead class="disable-user-select">
<tr>
<th><input type="checkbox" class="select-all-websites" style="width: 15px; height: 20px; margin-left:3px;"></th>
<th class="sort th-name" data-column="name" style="padding-left: 10px !important;">Website<span class="sort-arrow sort-arrow-desc">▼</span><span class="sort-arrow sort-arrow-asc">▲</span></th>
<th class="sort th-root-dir" data-column="root_dir">Connected Directory<span class="sort-arrow sort-arrow-desc">▼</span><span class="sort-arrow sort-arrow-asc">▲</span></th>
<th class="sort th-created sorted" data-column="created_at">Created<span class="sort-arrow sort-arrow-desc" style="display:inline;">▼</span><span class="sort-arrow sort-arrow-asc">▲</span></th>
<th></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</section>
</div>
<script src="./js/jquery-3.6.0.min.js"></script>
<script src="./js/jquery.dragster.js"></script>
<script src="http://puter.localhost:4100/puter.js/v2"></script>
<script src="./js/slugify.js"></script>
<script type="module" src="./js/html-entities.js"></script>
<!-- <script src="https://js.puter.com/v2/"></script> -->
<script src="http://puter.localhost:4100/sdk/puter.dev.js"></script>
<script src="./js/libs/jquery-3.6.0.min.js"></script>
<script src="./js/libs/jquery.dragster.js"></script>
<script src="./js/libs/slugify.js"></script>
<script type="module" src="./js/libs/html-entities.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="./js/images.js"></script>
<script type="module" src="./js/apps.js"></script>
<script type="module" src="./js/workers.js"></script>
<script type="module" src="./js/websites.js"></script>
<script type="module" src="./js/dev-center.js"></script>
</body>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+7
View File
@@ -0,0 +1,7 @@
window.deploying_spinner = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_P7sC{transform-origin:center;animation:spinner_svv2 .75s infinite linear}@keyframes spinner_svv2{100%{transform:rotate(360deg)}}</style><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" class="spinner_P7sC"/></svg>`;
window.loading_spinner = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_P7sC{transform-origin:center;animation:spinner_svv2 .75s infinite linear}@keyframes spinner_svv2{100%{transform:rotate(360deg)}}</style><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" class="spinner_P7sC"/></svg>`;
window.drop_area_placeholder = `<p>Drop your app folder and files here to deploy.</p><p style="font-size: 16px; margin-top: 0px;">HTML, JS, CSS, ...</p>`;
window.index_missing_error = `Please upload an 'index.html' file or if you're uploading a directory, make sure it contains an 'index.html' file at its root.`;
window.lock_svg = `<svg style="opacity: 0.8; margin-bottom: -3px; margin-left: 5px; width: 15px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-shield-shaded" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M8 14.933a1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56"/> </svg>`;
window.lock_svg_tippy = `<svg title="Delete Protection enabled." style="opacity: 0.8; margin-bottom: -3px; margin-left: 5px; width: 15px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-shield-shaded tippy" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M8 14.933a1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56"/> </svg>`;
window.copy_svg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-copy" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/> </svg>`;
File renamed without changes.
File renamed without changes.
File renamed without changes.
+501
View File
@@ -0,0 +1,501 @@
let sortBy = 'created_at';
let sortDirection = 'desc';
window.websites = [];
let search_query;
window.create_website = async (name, directoryPath = null) => {
let website;
// Use provided directory path or default to the default website file
const websiteDir = directoryPath || window.default_website_file;
try {
website = await puter.hosting.create(name, websiteDir);
} catch (error) {
puter.ui.alert(`Error creating website: ${error.error.message}`);
}
return website;
}
window.refresh_websites_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 websites
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_websites();
}
async function init_websites() {
puter.hosting.list().then((websites) => {
window.websites = websites;
count_websites();
});
}
$(document).on('click', '.create-a-website-btn', async function (e) {
// Step 1: Show directory picker
let selectedDirectory;
try {
selectedDirectory = await puter.ui.showDirectoryPicker();
} catch (err) {
// User cancelled directory picker or there was an error
console.log('Directory picker cancelled or error:', err);
return;
}
// Step 2: Ask for website name
if (selectedDirectory && selectedDirectory.path) {
let name = await puter.ui.prompt('Please enter a name for your website:', 'my-awesome-website');
// Step 3: Create website with selected directory
if (name) {
await create_website(name, selectedDirectory.path);
refresh_websites_list();
}
}
})
$(document).on('click', '.website-checkbox', function (e) {
// was shift key pressed?
if (e.originalEvent && e.originalEvent.shiftKey) {
// select all checkboxes in range
const currentIndex = $('.website-checkbox').index(this);
const startIndex = Math.min(window.last_clicked_website_checkbox_index, currentIndex);
const endIndex = Math.max(window.last_clicked_website_checkbox_index, currentIndex);
// set all checkboxes in range to the same state as current checkbox
for (let i = startIndex; i <= endIndex; i++) {
const checkbox = $('.website-checkbox').eq(i);
checkbox.prop('checked', $(this).is(':checked'));
// activate row
if ($(checkbox).is(':checked'))
$(checkbox).closest('tr').addClass('active');
else
$(checkbox).closest('tr').removeClass('active');
}
}
// determine if select-all checkbox should be checked, indeterminate, or unchecked
if ($('.website-checkbox:checked').length === $('.website-checkbox').length) {
$('.select-all-websites').prop('indeterminate', false);
$('.select-all-websites').prop('checked', true);
} else if ($('.website-checkbox:checked').length > 0) {
$('.select-all-websites').prop('indeterminate', true);
$('.select-all-websites').prop('checked', false);
}
else {
$('.select-all-websites').prop('indeterminate', false);
$('.select-all-websites').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 ($('.website-checkbox:checked').length > 0)
$('.delete-websites-btn').removeClass('disabled');
else
$('.delete-websites-btn').addClass('disabled');
// store the index of the last clicked checkbox
window.last_clicked_website_checkbox_index = $('.website-checkbox').index(this);
})
$(document).on('change', '.select-all-websites', function (e) {
if ($(this).is(':checked')) {
$('.website-checkbox').prop('checked', true);
$('.website-card').addClass('active');
$('.delete-websites-btn').removeClass('disabled');
} else {
$('.website-checkbox').prop('checked', false);
$('.website-card').removeClass('active');
$('.delete-websites-btn').addClass('disabled');
}
})
$('.refresh-website-list').on('click', function (e) {
puter.ui.showSpinner();
refresh_websites_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');
$('#website-list-table').find('th').removeClass('sorted');
$(this).find('.sort-arrow-' + sortDirection).css('display', 'inline');
$(this).addClass('sorted');
sort_websites();
});
function sort_websites() {
let sorted_websites;
// sort
if (sortDirection === 'asc'){
sorted_websites = websites.sort((a, b) => {
if(sortBy === 'name'){
return a.subdomain.localeCompare(b.subdomain);
}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 if(sortBy === 'root_dir'){
const aRootDir = a.root_dir?.name || '';
const bRootDir = b.root_dir?.name || '';
return aRootDir.localeCompare(bRootDir);
}else{
return a[sortBy] > b[sortBy] ? 1 : -1;
}
});
}else{
sorted_websites = websites.sort((a, b) => {
if(sortBy === 'name'){
return b.subdomain.localeCompare(a.subdomain);
}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 if(sortBy === 'root_dir'){
const aRootDir = a.root_dir?.name || '';
const bRootDir = b.root_dir?.name || '';
return bRootDir.localeCompare(aRootDir);
}else{
return b[sortBy] > a[sortBy] ? 1 : -1;
}
});
}
// refresh website list
$('.website-card').remove();
sorted_websites.forEach(website => {
$('#website-list-table > tbody').append(generate_website_card(website));
});
count_websites();
// show websites that match search_query and hide websites that don't
if (search_query) {
// show websites that match search_query and hide websites that don't
websites.forEach((website) => {
if (website.subdomain.toLowerCase().includes(search_query.toLowerCase())) {
$(`.website-card[data-name="${html_encode(website.subdomain)}"]`).show();
} else {
$(`.website-card[data-name="${html_encode(website.subdomain)}"]`).hide();
}
})
}
}
function count_websites() {
let count = window.websites.length;
$('.website-count').html(count ? count : '');
return count;
}
function generate_website_card(website) {
return `
<tr class="website-card" data-name="${html_encode(website.subdomain)}">
<td style="width:30px; vertical-align: middle; line-height: 1;">
<input type="checkbox" class="website-checkbox" data-website-name="${website.subdomain}">
</td>
<td style="font-family: monospace; font-size: 14px; vertical-align: middle;"><a href="https://${website.subdomain}.puter.site" target="_blank">${website.subdomain}.puter.site</a></td>
<td style="font-size: 14px; vertical-align: middle;"> <span class="root-dir-name" data-root-dir-path="${website.root_dir ? html_encode(website.root_dir.path) : ''}">${website.root_dir ? website.root_dir.name : ''}</span></td>
<td style="font-size: 14px; vertical-align: middle;">${website.created_at}</td>
<td style="vertical-align: middle;"><img class="options-icon options-icon-website" data-website-name="${website.subdomain}" src="./img/options.svg"></td>
</tr>
`;
}
$(document).on('input change keyup keypress keydown paste cut', '.search-websites', function (e) {
search_websites();
})
window.search_websites = function() {
// search websites for query
search_query = $('.search-websites').val().toLowerCase();
if (search_query === '') {
// hide 'clear search' button
$('.search-clear-websites').hide();
// show all websites again
$(`.website-card`).show();
// remove 'has-value' class from search input
$('.search-websites').removeClass('has-value');
} else {
// show 'clear search' button
$('.search-clear-websites').show();
// show websites that match search_query and hide websites that don't
websites.forEach((website) => {
if (
website.subdomain.toLowerCase().includes(search_query.toLowerCase()) ||
website.root_dir?.name?.toLowerCase().includes(search_query.toLowerCase())
)
{
$(`.website-card[data-name="${website.subdomain}"]`).show();
} else {
$(`.website-card[data-name="${website.subdomain}"]`).hide();
}
})
// add 'has-value' class to search input
$('.search-websites').addClass('has-value');
}
}
$(document).on('click', '.search-clear-websites', function (e) {
$('.search-websites').val('');
$('.search-websites').trigger('change');
$('.search-websites').focus();
search_query = '';
// remove 'has-value' class from search input
$('.search-websites').removeClass('has-value');
})
function remove_website_card(website_name, callback = null) {
$(`.website-card[data-name="${website_name}"]`).fadeOut(200, function() {
$(this).remove();
if ($(`.website-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-websites-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#website-list').show();
}
// update select-all-websites checkbox's state
if($('.website-checkbox:checked').length === 0){
$('.select-all-websites').prop('indeterminate', false);
$('.select-all-websites').prop('checked', false);
}
else if($('.website-checkbox:checked').length === $('.website-card').length){
$('.select-all-websites').prop('indeterminate', false);
$('.select-all-websites').prop('checked', true);
}
else{
$('.select-all-websites').prop('indeterminate', true);
}
count_websites();
if (callback) callback();
});
}
$(document).on('click', '.delete-websites-btn', async function (e) {
// show confirmation alert
let resp = await puter.ui.alert(`Are you sure you want to delete the selected websites?`, [
{
label: 'Delete',
type: 'danger',
value: 'delete',
},
{
label: 'Cancel',
},
], {
type: 'warning',
});
if (resp === 'delete') {
// disable delete button
$('.delete-websites-btn').addClass('disabled');
// show 'deleting' modal
puter.ui.showSpinner();
let start_ts = Date.now();
const websites = $('.website-checkbox:checked').toArray();
// delete all checked websites
for (let website of websites) {
let website_name = $(website).attr('data-website-name');
// delete website
await puter.hosting.delete(website_name)
// remove website card
remove_website_card(website_name);
try{
count_websites();
} catch(err) {
console.log(err);
}
}
// close 'deleting' modal
setTimeout(() => {
puter.ui.hideSpinner();
if($('.website-checkbox:checked').length === 0){
// disable delete button
$('.delete-websites-btn').addClass('disabled');
// reset the 'select all' checkbox
$('.select-all-websites').prop('indeterminate', false);
$('.select-all-websites').prop('checked', false);
}
}, (start_ts - Date.now()) > 500 ? 0 : 500);
}
})
$(document).on('click', '.options-icon-website', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
puter.ui.contextMenu({
items: [
{
label: 'Change Directory',
action: () => {
change_website_directory($(this).attr('data-website-name'));
},
},
'-',
{
label: 'Delete',
type: 'danger',
action: () => {
attempt_website_deletion($(this).attr('data-website-name'));
},
},
],
});
})
async function attempt_website_deletion(website_name) {
// confirm delete
const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete <strong>${html_encode(website_name)}.puter.site</strong>?`,
[
{
label: 'Yes, delete permanently',
value: 'delete',
type: 'danger',
},
{
label: 'Cancel'
},
]
);
if (alert_resp === 'delete') {
// remove website card and update website count
remove_website_card(website_name);
// delete website
puter.hosting.delete(website_name);
}
}
async function change_website_directory(website_name) {
try {
// Step 1: Show directory picker
const selectedDirectory = await puter.ui.showDirectoryPicker();
if (!selectedDirectory || !selectedDirectory.path) {
return; // User cancelled
}
// Step 2: Confirm the change since it will replace the current website
const confirmResp = await puter.ui.alert(
`Are you sure you want to change the directory for <strong>${html_encode(website_name)}.puter.site</strong>?<br><br>This will update the website to serve files from the new directory.`,
[
{
label: 'Yes, change directory',
value: 'change',
type: 'primary',
},
{
label: 'Cancel'
},
],
{
type: 'info',
}
);
if (confirmResp !== 'change') {
return;
}
// Step 3: Show loading spinner
puter.ui.showSpinner();
try {
// Step 4: Delete the existing website
await puter.hosting.delete(website_name);
// Step 5: Create a new website with the same name but new directory
await puter.hosting.create(website_name, selectedDirectory.path);
// Step 6: Refresh the websites list to show the updated directory
await refresh_websites_list();
// Step 7: Show success message
puter.ui.alert(`Website directory changed successfully! <strong>${html_encode(website_name)}.puter.site</strong> now serves files from the new directory.`, [], {
type: 'success',
});
} catch (error) {
// If there's an error, show error message
puter.ui.alert(`Error changing website directory: ${error.error?.message || error.message || 'Unknown error'}`, [], {
type: 'error',
});
} finally {
// Hide loading spinner
puter.ui.hideSpinner();
}
} catch (error) {
// Handle directory picker error
console.log('Directory picker cancelled or error:', error);
}
}
$(document).on('click', '.root-dir-name', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const root_dir_path = $(this).attr('data-root-dir-path');
if(root_dir_path){
puter.ui.launchApp('explorer', {
path: root_dir_path,
});
}
})
export default init_websites;
+445
View File
@@ -0,0 +1,445 @@
let sortBy = 'created_at';
let sortDirection = 'desc';
window.workers = [];
let search_query;
window.create_worker = async (name, filePath = null) => {
let worker;
// Use provided file path or default to the default worker file
const workerFile = filePath;
try {
worker = await puter.workers.create(name, workerFile);
} catch (err) {
puter.ui.alert(`Error creating worker: ${err.error?.message}`);
}
return worker;
}
window.refresh_worker_list = async (show_loading = false) => {
if (show_loading)
puter.ui.showSpinner();
// puter.workers.list() returns an array of worker objects
try {
window.workers = await puter.workers.list();
} catch (err) {
console.error('Error refreshing worker list:', err);
}
// Get workers
if (window.activeTab === 'workers' && window.workers.length > 0) {
$('.worker-card').remove();
$('#no-workers-notice').hide();
$('#worker-list').show();
window.workers.forEach((worker) => {
// append row to worker-list-table
$('#worker-list-table > tbody').append(generate_worker_card(worker));
});
} else {
$('#no-workers-notice').show();
$('#worker-list').hide();
}
count_workers();
}
async function init_workers() {
window.workers = await puter.workers.list();
count_workers();
}
$(document).on('click', '.create-a-worker-btn', async function (e) {
// if user doesn't have an email, request it
if(!window.user?.email || !window.user?.email_confirmed){
const email_confirm_resp = await puter.ui.requestEmailConfirmation();
if(!email_confirm_resp)
UIAlert('Email confirmation required to create a worker.');
return;
}
// refresh user data
window.user = await puter.auth.getUser();
// Step 1: Show file picker limited to .js files
let selectedFile;
try {
selectedFile = await puter.ui.showOpenFilePicker({
accept: ".js",
});
} catch (err) {
// User cancelled file picker or there was an error
console.log('File picker cancelled or error:', err);
return;
}
// Step 2: Ask for worker name
if (selectedFile && selectedFile.path) {
let name = await puter.ui.prompt('Please enter a name for your worker:', 'my-awesome-worker');
// Step 3: Create worker with selected file
if (name) {
await create_worker(name, selectedFile.path);
// Refresh the worker list to show the new worker
await refresh_worker_list();
}
}
})
$(document).on('click', '.worker-checkbox', function (e) {
// was shift key pressed?
if (e.originalEvent && e.originalEvent.shiftKey) {
// select all checkboxes in range
const currentIndex = $('.worker-checkbox').index(this);
const startIndex = Math.min(window.last_clicked_worker_checkbox_index, currentIndex);
const endIndex = Math.max(window.last_clicked_worker_checkbox_index, currentIndex);
// set all checkboxes in range to the same state as current checkbox
for (let i = startIndex; i <= endIndex; i++) {
const checkbox = $('.worker-checkbox').eq(i);
checkbox.prop('checked', $(this).is(':checked'));
// activate row
if ($(checkbox).is(':checked'))
$(checkbox).closest('tr').addClass('active');
else
$(checkbox).closest('tr').removeClass('active');
}
}
// 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');
// store the index of the last clicked checkbox
window.last_clicked_worker_checkbox_index = $('.worker-checkbox').index(this);
})
$(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 === 'file_path'){
return a[sortBy].localeCompare(b[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 === 'file_path'){
return b[sortBy].localeCompare(a[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 = window.workers.length;
$('.worker-count').html(count ? count : '');
return count;
}
function generate_worker_card(worker) {
return `
<tr class="worker-card" data-name="${html_encode(worker.name)}">
<td style="width:50px; vertical-align: middle; line-height: 1;">
<input type="checkbox" class="worker-checkbox" data-worker-name="${worker.name}">
</td>
<td style="font-family: monospace; font-size: 14px; vertical-align: middle;">${worker.name}</td>
<td style="font-family: monospace; font-size: 14px; vertical-align: middle;"><span class="worker-file-path" data-worker-file-path="${html_encode(worker.file_path)}">${worker.file_path ? worker.file_path : ''}</span></td>
<td style="font-size: 14px; vertical-align: middle;">${worker.created_at}</td>
<td style="vertical-align: middle;"><img class="options-icon options-icon-worker" data-worker-name="${worker.name}" src="./img/options.svg"></td>
</tr>
`;
}
$(document).on('input change keyup keypress keydown paste cut', '.search-workers', function (e) {
search_workers();
})
window.search_workers = function() {
// search workers for query
search_query = $('.search-workers').val().toLowerCase();
if (search_query === '') {
// hide 'clear search' button
$('.search-clear-workers').hide();
// show all workers again
$(`.worker-card`).show();
// remove 'has-value' class from search input
$('.search-workers').removeClass('has-value');
} else {
// show 'clear search' button
$('.search-clear-workers').show();
// 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="${worker.name}"]`).show();
} else {
$(`.worker-card[data-name="${worker.name}"]`).hide();
}
})
// add 'has-value' class to search input
$('.search-workers').addClass('has-value');
}
}
$(document).on('click', '.search-clear-workers', function (e) {
$('.search-workers').val('');
$('.search-workers').trigger('change');
$('.search-workers').focus();
search_query = '';
// remove 'has-value' class from search input
$('.search-workers').removeClass('has-value');
})
function remove_worker_card(worker_name, callback = null) {
$(`.worker-card[data-name="${worker_name}"]`).fadeOut(200, function() {
$(this).remove();
if ($(`.worker-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-workers-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#worker-list').show();
}
// update select-all-workers checkbox's state
if($('.worker-checkbox:checked').length === 0){
$('.select-all-workers').prop('indeterminate', false);
$('.select-all-workers').prop('checked', false);
}
else if($('.worker-checkbox:checked').length === $('.worker-card').length){
$('.select-all-workers').prop('indeterminate', false);
$('.select-all-workers').prop('checked', true);
}
else{
$('.select-all-workers').prop('indeterminate', true);
}
count_workers();
if (callback) callback();
});
}
$(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
remove_worker_card(worker_name);
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', '.options-icon-worker', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
puter.ui.contextMenu({
items: [
{
label: 'Delete',
type: 'danger',
action: () => {
attempt_worker_deletion($(this).attr('data-worker-name'));
},
},
],
});
})
async function attempt_worker_deletion(worker_name) {
// confirm delete
const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete <strong>${html_encode(worker_name)}</strong>?`,
[
{
label: 'Yes, delete permanently',
value: 'delete',
type: 'danger',
},
{
label: 'Cancel'
},
]
);
if (alert_resp === 'delete') {
// remove worker card and update worker count
remove_worker_card(worker_name);
// delete worker
puter.workers.delete(worker_name).then().catch(async (err) => {
puter.ui.alert(err?.message, [
{
label: 'Ok',
},
]);
})
}
}
$(document).on('click', '.worker-file-path', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const file_path = $(this).attr('data-worker-file-path');
if(file_path){
puter.ui.launchApp({
name: 'editor',
file_paths: [file_path],
});
}
})
export default init_workers;
+51
View File
@@ -32,6 +32,8 @@ import update_mouse_position from './helpers/update_mouse_position.js';
import item_icon from './helpers/item_icon.js';
import UIPopover from './UI/UIPopover.js';
import socialLink from './helpers/socialLink.js';
import UIWindowEmailConfirmationRequired from './UI/UIWindowEmailConfirmationRequired.js';
import UIWindowSaveAccount from './UI/UIWindowSaveAccount.js';
import { PROCESS_IPC_ATTACHED } from './definitions.js';
@@ -157,6 +159,55 @@ const ipc_listener = async (event, handled) => {
// TODO: Respond to this
}
//--------------------------------------------------------
// requestEmailConfirmation
//--------------------------------------------------------
else if(event.data.msg === 'requestEmailConfirmation'){
// If the user has an email and it is confirmed, respond with success
if(window.user.email && window.user.email_confirmed){
target_iframe.contentWindow.postMessage({
original_msg_id: msg_id,
msg: 'requestEmailConfirmationResponded',
response: true,
}, '*');
}
// If the user is a temporary user, show the save account window
if(window.user.is_temp &&
!await UIWindowSaveAccount({
send_confirmation_code: true,
message: 'Please create an account to proceed.',
window_options: {
backdrop: true,
close_on_backdrop_click: false,
}
})){
target_iframe.contentWindow.postMessage({
original_msg_id: msg_id,
msg: 'requestEmailConfirmationResponded',
response: false,
}, '*');
return;
}
else if(!window.user.email_confirmed && !await UIWindowEmailConfirmationRequired()){
target_iframe.contentWindow.postMessage({
original_msg_id: msg_id,
msg: 'requestEmailConfirmationResponded',
response: false,
}, '*');
return;
}
const email_confirm_resp = await UIWindowEmailConfirmationRequired({
email: window.user.email,
});
target_iframe.contentWindow.postMessage({
original_msg_id: msg_id,
msg: 'requestEmailConfirmationResponded',
response: email_confirm_resp,
}, '*');
}
//--------------------------------------------------------
// ALERT
//--------------------------------------------------------
else if(event.data.msg === 'ALERT' && event.data.message !== undefined){
+4
View File
@@ -97,6 +97,10 @@ function UIAlert(options) {
santized_message = santized_message.replace(/&lt;p&gt;/g, '<p>');
santized_message = santized_message.replace(/&lt;\/p&gt;/g, '</p>');
// replace sanitized <br> with <br>
santized_message = santized_message.replace(/&lt;br&gt;/g, '<br>');
santized_message = santized_message.replace(/&lt;\/br&gt;/g, '</br>');
let h = '';
// icon
h += `<img class="window-alert-icon" src="${html_encode(options.body_icon)}">`;
+13
View File
@@ -1145,6 +1145,19 @@ function UIItem(options){
menu_items.push({
html: i18n('publish_as_serverless_worker'),
onClick: async function(){
if(window.user.is_temp &&
!await UIWindowSaveAccount({
send_confirmation_code: true,
message: 'Please create an account to proceed.',
window_options: {
backdrop: true,
close_on_backdrop_click: false,
}
}))
return;
else if(!window.user.email_confirmed && !await UIWindowEmailConfirmationRequired())
return;
UIWindowPublishWorker(options.uid, $(el_item).attr('data-name'), $(el_item).attr('data-path'));
}
});
+1 -1
View File
@@ -824,7 +824,7 @@ async function UIWindow(options) {
// No item selected, return current directory
// ------------------------------------------------
if(selected_els.length === 0){
selected_dirs = await puter.fs.sign(options.initiating_app_uuid, {uid: $(el_window).attr('data-uid'), action: 'write'})
selected_dirs = await puter.fs.sign(options.initiating_app_uuid, {uid: $(el_window).attr('data-uid'), action: 'write', path: $(el_window).attr('data-path')})
selected_dirs = selected_dirs.items;
}
-5
View File
@@ -185,7 +185,6 @@ async function UIWindowLogin(options){
// Disable the login button to prevent multiple submissions
$(el_window).find('.login-btn').prop('disabled', true);
console.log('Sending login AJAX request with async: true');
$.ajax({
url: window.gui_origin + "/login",
type: 'POST',
@@ -194,7 +193,6 @@ async function UIWindowLogin(options){
contentType: "application/json",
data: data,
success: async function (data){
console.log('Login request successful');
// Keep the button disabled on success since we're redirecting or closing
let p = Promise.resolve();
if ( data.next_step === 'otp' ) {
@@ -252,7 +250,6 @@ async function UIWindowLogin(options){
p.resolve();
} catch (e) {
// keeping this log; useful in screenshots
console.log('2FA Login Error', e);
component.set('error', i18n(error_i18n_key));
component.set('is_checking_code', false);
}
@@ -319,7 +316,6 @@ async function UIWindowLogin(options){
p.resolve();
} catch (e) {
// keeping this log; useful in screenshots
console.log('2FA Recovery Error', e);
component.set('error', i18n(error_i18n_key));
}
}
@@ -366,7 +362,6 @@ async function UIWindowLogin(options){
if(options.reload_on_success){
sessionStorage.setItem('playChimeNextUpdate', 'yes');
window.onbeforeunload = null;
console.log('About to redirect, checking URL parameters:', window.location.search);
// Replace with a clean URL to prevent password leakage
const cleanUrl = window.location.origin + window.location.pathname;
window.location.replace(cleanUrl);
+1 -1
View File
@@ -2591,7 +2591,7 @@ label {
.taskbar.taskbar-position-right .taskbar-item[data-app="separator"]{
max-height: 10px;
min-height: 10px !important;
padding: 5px !important;
padding: 5px 3px 5px 7px !important;
}
.taskbar.taskbar-position-left .taskbar-item[data-app="separator"] .taskbar-icon,
.taskbar.taskbar-position-right .taskbar-item[data-app="separator"] .taskbar-icon{
+59 -4
View File
@@ -48,7 +48,7 @@ export class ExecService extends Service {
}
// This method is exposed to apps via IPCService.
async launchApp ({ app_name, args, pseudonym }, { ipc_context, msg_id } = {}) {
async launchApp ({ app_name, args, pseudonym, file_paths }, { ipc_context, msg_id } = {}) {
const app = ipc_context?.caller?.app;
const process = ipc_context?.caller?.process;
@@ -68,8 +68,8 @@ export class ExecService extends Service {
Object.assign(params, provider());
}
// The "body" of this method is in a separate file
const child_process = await launch_app({
// Handle file paths if provided and caller is in godmode
let launch_options = {
launched_by_exec_service: true,
name: app_name,
pseudonym,
@@ -80,7 +80,62 @@ export class ExecService extends Service {
...(connection ? {
parent_pseudo_id: connection.backward.uuid,
} : {}),
});
};
// Check if file_paths are provided and caller has godmode permissions
if (file_paths && Array.isArray(file_paths) && file_paths.length > 0 && process) {
try {
// Get caller app info to check godmode status
const caller_app_name = process.name;
const caller_app_info = await window.get_apps(caller_app_name);
// Check if caller is in godmode
if (caller_app_info && caller_app_info.godmode === 1) {
this.log.info(`⚠️ GODMODE app ${caller_app_name} launching ${app_name} with files:`, file_paths);
// Get target app info to create file signatures
const target_app_info = await puter.apps.get(app_name);
// For the first file, create a file signature and set it up like opening a file
if (file_paths.length > 0) {
const first_file_path = file_paths[0];
try {
// Get file stats to verify it exists
const file_stat = await puter.fs.stat(first_file_path);
// Create file signature for the target app
const file_signature_result = await puter.fs.sign(target_app_info.uuid, {
path: first_file_path,
action: 'write'
});
// Set up launch options with file information
launch_options.file_signature = file_signature_result.items;
launch_options.file_path = first_file_path;
launch_options.token = file_signature_result.token;
// Add all file paths to args for the target app
launch_options.args.file_paths = file_paths;
} catch (file_error) {
this.log.warn(`Failed to process file ${first_file_path}:`, file_error);
// Continue with launch but without file signature
}
}
} else {
console.log(`⚠️ App ${caller_app_name} attempted to launch ${app_name} with files but does not have godmode permissions`);
// Continue with normal launch, ignoring file_paths
}
} catch (error) {
console.log('Error checking godmode permissions:', error);
// Continue with normal launch
}
}
// The "body" of this method is in a separate file
const child_process = await launch_app(launch_options);
const send_child_launched_msg = (...a) => {
if ( ! process ) return;
+20 -1
View File
@@ -629,6 +629,12 @@ class UI extends EventListener {
this.#onLaunchedWithItems = callback;
}
requestEmailConfirmation = function() {
return new Promise((resolve, reject) => {
this.#postMessageWithCallback('requestEmailConfirmation', resolve, { });
});
}
alert = function(message, buttons, options, callback) {
return new Promise((resolve) => {
this.#postMessageWithCallback('ALERT', resolve, { message, buttons, options });
@@ -1047,7 +1053,19 @@ class UI extends EventListener {
// Returns a Promise<AppConnection>
launchApp = async function launchApp(app_name, args, callback) {
let pseudonym = undefined;
if ( app_name.includes('#(as)') ) {
let file_paths = undefined;
// Handle case where app_name is an options object
if (typeof app_name === 'object' && app_name !== null) {
const options = app_name;
app_name = options.name || options.app_name;
file_paths = options.file_paths;
args = args || options.args;
callback = callback || options.callback;
pseudonym = options.pseudonym;
}
if ( app_name && app_name.includes('#(as)') ) {
[app_name, pseudonym] = app_name.split('#(as)');
}
const app_info = await this.#ipc_stub({
@@ -1055,6 +1073,7 @@ class UI extends EventListener {
callback,
parameters: {
app_name,
file_paths,
pseudonym,
args,
},