UNPKG

@underpostnet/underpost

Version:

Underpost Platform — end-to-end CI/CD and application-delivery toolchain CLI. Covers bare metal, Kubernetes, K3s, kubeadm, LXD, container/image orchestration, secrets, databases, cron jobs, monitoring, SSH, runners, PWA + Workbox delivery, and release orc

766 lines (718 loc) 26.7 kB
import { Account } from '../core/Account.js'; import { BtnIcon } from '../core/BtnIcon.js'; import { getId, newInstance } from '../core/CommonJs.js'; import { Css, darkTheme, ThemeEvents, Themes } from '../core/Css.js'; import { EventsUI } from '../core/EventsUI.js'; import { LogIn } from '../core/LogIn.js'; import { LogOut } from '../core/LogOut.js'; import { buildBadgeToolTipMenuOption, Modal, renderMenuLabel, renderViewTitle } from '../core/Modal.js'; import { SignUp } from '../core/SignUp.js'; import { Translate } from '../core/Translate.js'; import { htmls, s } from '../core/VanillaJs.js'; import { extractUsernameFromPath, getProxyPath, getQueryParams } from '../core/Router.js'; import { AppStoreDefault } from './AppStoreDefault.js'; import Sortable from 'sortablejs'; import { RouterDefault, BannerAppTemplate } from './RouterDefault.js'; import { SettingsDefault } from './SettingsDefault.js'; import { Badge } from '../core/Badge.js'; import { Recover } from '../core/Recover.js'; import { DefaultManagement } from '../../services/default/default.management.js'; import { Page500 } from '../core/500.js'; import { Page404 } from '../core/404.js'; import { PanelForm } from '../core/PanelForm.js'; import { Chat } from '../core/Chat.js'; import { PublicProfile } from '../core/PublicProfile.js'; class AppShellDefault { static Data = {}; static async instance(options = { htmlMainBody: () => html`` }) { const id = getId(AppShellDefault.Data, 'menu-'); AppShellDefault.Data[id] = {}; const RouterInstance = RouterDefault.instance(); const { barConfig } = await Themes[Css.currentTheme](); const badgeNotificationMenuStyle = { top: '-33px', left: '24px' }; const barMode = undefined; // 'top-bottom-bar'; await Modal.instance({ id: 'modal-menu', html: html` <div class="style-lading-render"></div> <div class="fl menu-btn-container"> ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-home main-btn-menu-active', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fas fa-home"></i>`, text: html`<span class="menu-label-text">${Translate.instance('home')}</span>`, }), // style: 'display: none', attrs: `data-id="home"`, tabHref: `${getProxyPath()}`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('home')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-log-in', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fas fa-sign-in-alt"></i>`, text: html`<span class="menu-label-text">${Translate.instance('log-in')}</span>`, }), attrs: `data-id="log-in"`, tabHref: `${getProxyPath()}log-in`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('log-in')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-sign-up', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fas fa-user-plus"></i>`, text: html`<span class="menu-label-text">${Translate.instance('sign-up')}</span>`, }), attrs: `data-id="sign-up"`, tabHref: `${getProxyPath()}sign-up`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('sign-up')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-log-out', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fas fa-sign-out-alt"></i>`, text: html`<span class="menu-label-text">${Translate.instance('log-out')}</span>`, }), attrs: `data-id="log-out"`, style: 'display: none', tabHref: `${getProxyPath()}log-out`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('log-out')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-account', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fas fa-user-circle"></i>`, text: html`<span class="menu-label-text">${Translate.instance('account')}</span>`, }), style: 'display: none', attrs: `data-id="account"`, tabHref: `${getProxyPath()}account`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('account')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-public-profile', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fas fa-user-tag"></i>`, text: html`<span class="menu-label-text">${Translate.instance('public-profile')}</span>`, }), style: 'display: none', attrs: `data-id="public-profile"`, tabHref: `${getProxyPath()}u`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('public-profile')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-settings', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fas fa-sliders-h"></i>`, text: html`<span class="menu-label-text">${Translate.instance('settings')}</span>`, }), attrs: `data-id="settings"`, tabHref: `${getProxyPath()}settings`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('settings')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-recover hide', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fa-solid fa-arrow-rotate-left"></i>`, text: html`<span class="menu-label-text">${Translate.instance('recover')}</span>`, }), attrs: `data-id="recover"`, tabHref: `${getProxyPath()}recover`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('recover')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-default-management', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fa-solid fa-rectangle-list"></i>`, text: html`<span class="menu-label-text">${Translate.instance('default-management')}</span>`, }), attrs: `data-id="default-management"`, tabHref: `${getProxyPath()}default-management`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('default-management')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-404 hide', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fa-solid fa-triangle-exclamation"></i>`, text: html`<span class="menu-label-text">${Translate.instance('404')}</span>`, }), attrs: `data-id="404"`, tabHref: `${getProxyPath()}404`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('404')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-500 hide', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fa-solid fa-circle-exclamation"></i>`, text: html`<span class="menu-label-text">${Translate.instance('500')}</span>`, }), attrs: `data-id="500"`, tabHref: `${getProxyPath()}500`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('500')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-blog', useMenuBtn: true, label: renderMenuLabel({ icon: html`<i class="fa-solid fa-file-invoice"></i>`, text: html`<span class="menu-label-text">${Translate.instance('blog')}</span>`, }), attrs: `data-id="blog"`, tabHref: `${getProxyPath()}blog`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('blog')), })} ${await BtnIcon.instance({ class: 'in wfa main-btn-menu main-btn-chat', useMenuBtn: true, label: html`${renderMenuLabel({ icon: html`<i class="far fa-comments"></i>`, text: html`<span class="menu-label-text">${Translate.instance('chat')}</span>`, })} ${await Badge.instance({ id: 'main-btn-chat', type: 'circle-red', style: badgeNotificationMenuStyle, classList: 'hide', })}`, attrs: `data-id="chat"`, tabHref: `${getProxyPath()}chat`, handleContainerClass: 'handle-btn-container', tooltipHtml: await Badge.instance(buildBadgeToolTipMenuOption('chat')), })} </div> `, barConfig: newInstance(barConfig), title: BannerAppTemplate, // titleClass: 'hide', titleRender: () => { ThemeEvents['titleRender'] = () => { const srcLogo = `${getProxyPath()}apple-touch-icon-114x114-precomposed.png`; htmls( '.action-btn-app-icon-render', html`<img class="inl top-bar-app-icon ${darkTheme ? 'negative-color' : ''}" src="${srcLogo}" />`, ); }; setTimeout(ThemeEvents['titleRender']); return ''; }, mode: 'slide-menu', RouterInstance, htmlMainBody: options.htmlMainBody, }); ThemeEvents['main-theme-handler'] = () => { if (darkTheme) { const backgroundImage = `${getProxyPath()}assets/background/dark.svg`; htmls( `.style-ssr-background-image`, css` .ssr-background-image { background-image: url('${backgroundImage}'); } `, ); } else { const backgroundImage = `${getProxyPath()}assets/background/white0-min.jpg`; htmls( `.style-ssr-background-image`, css` .ssr-background-image { background-image: url('${backgroundImage}'); } `, ); } htmls( `.style-lading-render`, html` <style> .landing-container { min-height: calc(100vh - 100px); display: flex; /* align-items: center; */ justify-content: center; padding: 2rem; color: ${darkTheme ? '#fff' : '#333'}; transition: all 0.3s ease; } .content-wrapper { text-align: center; max-width: 1200px; width: 100%; padding: 2rem; animation: fadeIn 1s ease-out; } .animated-text { margin-bottom: 3rem; } .greeting { display: block; font-size: 3.5rem; font-weight: 700; margin-bottom: 1rem; background: linear-gradient(90deg, #4f46e5, #7c3aed); -webkit-background-clip: text; -webkit-text-fill-color: transparent; animation: slideIn 1s ease-out; } .subtitle { display: block; font-size: 1.5rem; color: ${darkTheme ? '#a0aec0' : '#4a5568'}; margin-top: 1rem; opacity: 0; animation: fadeInUp 0.8s ease-out 0.3s forwards; } .features { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 2rem; margin: 4rem 0; } .feature-card { background: ${darkTheme ? 'rgba(255, 255, 255, 0.05)' : 'white'}; padding: 2rem; border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; opacity: 0; animation: fadeInUp 0.6s ease-out forwards; } .feature-card:nth-child(1) { animation-delay: 0.5s; } .feature-card:nth-child(2) { animation-delay: 0.7s; } .feature-card:nth-child(3) { animation-delay: 0.9s; } .feature-card:hover { transform: translateY(-5px); box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1); } .feature-card .icon { font-size: 2.5rem; margin-bottom: 1rem; display: inline-block; } .feature-card h3 { font-size: 1.25rem; margin-bottom: 0.75rem; color: ${darkTheme ? '#e2e8f0' : '#2d3748'}; } .feature-card p { color: ${darkTheme ? '#a0aec0' : '#4a5568'}; line-height: 1.6; } .cta-button { background: linear-gradient(90deg, #4f46e5, #7c3aed); color: white; border: none; padding: 1rem 2.5rem; font-size: 1.1rem; border-radius: 50px; cursor: pointer; display: inline-flex; align-items: center; gap: 0.5rem; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(79, 70, 229, 0.3); opacity: 0; animation: fadeIn 0.8s ease-out 1.2s forwards; } .cta-button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(79, 70, 229, 0.4); background: linear-gradient(90deg, #3e38b4, #602bbc); color: white; } .cta-button:active { transform: translateY(0); } .button-icon { transition: transform 0.3s ease; } .cta-button:hover .button-icon { transform: translateX(4px); } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } @keyframes slideIn { from { opacity: 0; transform: translateX(-30px); } to { opacity: 1; transform: translateX(0); } } @media (max-width: 768px) { .greeting { font-size: 2.5rem; } .subtitle { font-size: 1.25rem; } .features { grid-template-columns: 1fr; } } </style>`, ); }; setTimeout(ThemeEvents['main-theme-handler']); AppShellDefault.Data[id].sortable = new Sortable(s(`.menu-btn-container`), { animation: 150, group: `menu-sortable`, forceFallback: true, fallbackOnBody: true, handle: '.handle-btn-container', store: { /** * Get the order of elements. Called once during initialization. * @param {Sortable} sortable * @returns {Array} */ get: function (sortable) { const order = localStorage.getItem(sortable.options.group.name); return order ? order.split('|') : []; }, /** * Save the order of elements. Called onEnd (when the item is dropped). * @param {Sortable} sortable */ set: function (sortable) { const order = sortable.toArray(); localStorage.setItem(sortable.options.group.name, order.join('|')); }, }, // chosenClass: 'css-class', // ghostClass: 'css-class', // Element dragging ended onEnd: function (/**Event*/ evt) { // console.log('Sortable onEnd', evt); // console.log('evt.oldIndex', evt.oldIndex); // console.log('evt.newIndex', evt.newIndex); const slotId = Array.from(evt.item.classList).pop(); // console.log('slotId', slotId); if (evt.oldIndex === evt.newIndex) s(`.${slotId}`).click(); // var itemEl = evt.item; // dragged HTMLElement // evt.to; // target list // evt.from; // previous list // evt.oldIndex; // element's old index within old parent // evt.newIndex; // element's new index within new parent // evt.oldDraggableIndex; // element's old index within old parent, only counting draggable elements // evt.newDraggableIndex; // element's new index within new parent, only counting draggable elements // evt.clone; // the clone element // evt.pullMode; // when item is in another sortable: `"clone"` if cloning, `true` if moving }, }); EventsUI.onClick(`.main-btn-sign-up`, async () => { const { barConfig } = await Themes[Css.currentTheme](); await Modal.instance({ id: 'modal-sign-up', route: 'sign-up', barConfig, title: renderViewTitle({ icon: html`<i class="fas fa-user-plus"></i>`, text: Translate.instance('sign-up'), }), html: async () => await SignUp.instance({ idModal: 'modal-sign-up' }), handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, }); }); EventsUI.onClick(`.main-btn-log-out`, async () => { const { barConfig } = await Themes[Css.currentTheme](); await Modal.instance({ id: 'modal-log-out', route: 'log-out', barConfig, title: renderViewTitle({ icon: html`<i class="fas fa-sign-out-alt"></i>`, text: Translate.instance('log-out'), }), html: async () => await LogOut.instance(), handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, }); }); EventsUI.onClick(`.main-btn-log-in`, async () => { const { barConfig } = await Themes[Css.currentTheme](); await Modal.instance({ id: 'modal-log-in', route: 'log-in', barConfig, title: renderViewTitle({ icon: html`<i class="fas fa-sign-in-alt"></i>`, text: Translate.instance('log-in'), }), html: async () => await LogIn.instance(), handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, }); }); EventsUI.onClick(`.main-btn-account`, async () => { const { barConfig } = await Themes[Css.currentTheme](); await Modal.instance({ id: 'modal-account', route: 'account', barConfig, title: renderViewTitle({ icon: html`<i class="fas fa-user-circle"></i>`, text: Translate.instance('account'), }), html: async () => await Account.instance({ idModal: 'modal-account', user: AppStoreDefault.Data.user.main.model.user, disabled: [], }), handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, }); }); EventsUI.onClick(`.main-btn-public-profile`, async () => { const { barConfig } = await Themes[Css.currentTheme](); const idModal = 'modal-public-profile'; const user = AppStoreDefault.Data.user.main.model.user; // Check if modal already exists const existingModal = s(`.${idModal}`); if (existingModal) { const usernameFromPath = extractUsernameFromPath(); const queryParams = getQueryParams(); const cid = usernameFromPath || queryParams.cid || user.username || null; if (cid) { await PublicProfile.Update({ idModal: 'modal-public-profile', user: { username: cid }, }); return; } } await Modal.instance({ id: idModal, route: 'u', barConfig, title: '', // renderViewTitle({ // icon: html`<i class="fas fa-user-circle"></i>`, // text: Translate.instance('public-profile'), // }), html: async () => await PublicProfile.instance({ idModal, user, }), handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, observer: true, }); }); EventsUI.onClick(`.main-btn-settings`, async () => { const { barConfig } = await Themes[Css.currentTheme](); await Modal.instance({ id: 'modal-settings', route: 'settings', barConfig, title: renderViewTitle({ icon: html` <i class="fas fa-sliders-h"></i>`, text: Translate.instance('settings'), }), html: async () => await SettingsDefault.instance({ idModal: 'modal-settings' }), handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, }); }); EventsUI.onClick(`.main-btn-recover`, async () => { const { barConfig } = await Themes[Css.currentTheme](); await Modal.instance({ id: 'modal-recover', route: 'recover', barConfig, title: renderViewTitle({ icon: html`<i class="fa-solid fa-arrow-rotate-left"></i>`, text: Translate.instance('recover'), }), html: async () => await Recover.instance({ idModal: 'modal-recover', user: AppStoreDefault.Data.user.main.model.user }), handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, }); }); EventsUI.onClick(`.main-btn-default-management`, async () => { const { barConfig } = await Themes[Css.currentTheme](); await Modal.instance({ id: 'modal-default-management', route: 'default-management', barConfig, title: renderViewTitle({ icon: html`<i class="fa-solid fa-rectangle-list"></i>`, text: Translate.instance('default-management'), }), html: async () => await DefaultManagement.instance(), handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, observer: true, }); }); EventsUI.onClick(`.main-btn-404`, async () => { const { barConfig } = await Themes[Css.currentTheme](); await Modal.instance({ id: 'modal-404', route: '404', barConfig, title: renderViewTitle({ icon: html`<i class="fa-solid fa-triangle-exclamation"></i>`, text: Translate.instance('404'), }), html: async () => await Page404.instance({ idModal: 'modal-404' }), handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, observer: true, }); }); EventsUI.onClick(`.main-btn-500`, async () => { const { barConfig } = await Themes[Css.currentTheme](); await Modal.instance({ id: 'modal-500', route: '500', barConfig, title: renderViewTitle({ icon: html`<i class="fa-solid fa-circle-exclamation"></i>`, text: Translate.instance('500'), }), html: async () => await Page500.instance({ idModal: 'modal-500' }), handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, observer: true, }); }); EventsUI.onClick(`.main-btn-blog`, async () => { const { barConfig } = await Themes[Css.currentTheme](); const idModal = 'modal-blog'; const routeModal = 'blog'; const idEvent = `form-panel-${idModal}`; await Modal.instance({ id: idModal, route: routeModal, barConfig, title: renderViewTitle({ icon: html`<i class="fa-solid fa-file-invoice"></i>`, text: Translate.instance('blog'), }), observer: true, html: async () => { setTimeout(async () => { await PanelForm.instance({ idPanel: 'default-blog', defaultUrlImage: `${getProxyPath()}android-chrome-96x96.png`, appStore: AppStoreDefault, parentIdModal: idModal, scrollClassContainer: `html-${idModal}`, route: routeModal, }); }); }, handleType: 'bar', maximize: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, barMode, }); }); EventsUI.onClick(`.main-btn-chat`, async () => { const { barConfig } = await Themes[Css.currentTheme](); await Modal.instance({ id: 'modal-chat', route: 'chat', barConfig, title: renderViewTitle({ icon: html` <i class="far fa-comments"></i>`, text: Translate.instance('chat'), }), html: async () => await Chat.instance({ idModal: 'modal-chat' }), handleType: 'bar', maximize: true, observer: true, mode: 'view', slideMenu: 'modal-menu', RouterInstance, barMode, }); }); } } export { AppShellDefault };