Merge pull request #1417 from HeyPuter/workers-in-dev-center
Workers and websites in dev center
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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>
|
||||
|
||||
|
||||
@@ -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>`;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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){
|
||||
|
||||
@@ -97,6 +97,10 @@ function UIAlert(options) {
|
||||
santized_message = santized_message.replace(/<p>/g, '<p>');
|
||||
santized_message = santized_message.replace(/<\/p>/g, '</p>');
|
||||
|
||||
// replace sanitized <br> with <br>
|
||||
santized_message = santized_message.replace(/<br>/g, '<br>');
|
||||
santized_message = santized_message.replace(/<\/br>/g, '</br>');
|
||||
|
||||
let h = '';
|
||||
// icon
|
||||
h += `<img class="window-alert-icon" src="${html_encode(options.body_icon)}">`;
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||