diff --git a/dashboard/index.html b/dashboard/index.html index e6c9f9f56..d6f3d4625 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -1,201 +1,20 @@ - - - - - - - Cloudron Dashboard - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ 'main.offline' | tr }} - - - - -
- -
- - - - - -
-
-
-
- - {{ subscription.plan.id === 'free' ? ('settings.appstoreAccount.subscriptionSetupAction' | tr) : ('settings.appstoreAccount.subscriptionReactivateAction' | tr) }} - -
-
- Subscription Expired -
- - - - {{ notificationCount === 100 ? '100+' : notificationCount }} - - - - - - {{user.username}} - -
- -
-
-
-
-
-
- - -
- - + + + + + + Cloudron Dashboard + + + +
+ + diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 5b5912a19..58bbb5391 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -9,7 +9,7 @@ "@fontsource/arimo": "^5.2.5", "@fontsource/dm-sans": "^5.2.5", "@fontsource/inter": "^5.2.5", - "@fontsource/noto-sans": "^5.2.5", + "@fontsource/noto-sans": "^5.2.6", "@fortawesome/fontawesome-free": "^6.7.2", "@vitejs/plugin-vue": "^5.2.1", "@xterm/addon-attach": "^0.11.0", @@ -24,8 +24,8 @@ "filesize": "^10.1.6", "jquery": "^3.7.1", "marked": "^15.0.7", - "moment": "^2.30.1", - "pankow": "^2.10.0", + "moment-timezone": "^0.5.47", + "pankow": "^2.10.1", "pankow-viewers": "^1.0.11", "sass": "^1.85.1", "vite": "^6.2.2", @@ -631,9 +631,9 @@ } }, "node_modules/@fontsource/noto-sans": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans/-/noto-sans-5.2.5.tgz", - "integrity": "sha512-g3hWgsQayHQGVXgQKjbHKpS7YQFQY8azwXFCi3ZkhgCK5fZXe3udeQYrZJJ4PMswGkNhoDPRR9OJZBlMjzZAEQ==", + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans/-/noto-sans-5.2.6.tgz", + "integrity": "sha512-46D39T1/QJ16LSbCTaNjjrGS6Y69FMLS9FeHKsrXSQg15Z2MFqmlnNMpURx2l6Sr3x9t9EVdr3CAkHnZyvea+w==", "license": "OFL-1.1", "funding": { "url": "https://github.com/sponsors/ayuhito" @@ -2237,6 +2237,18 @@ "node": "*" } }, + "node_modules/moment-timezone": { + "version": "0.5.47", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.47.tgz", + "integrity": "sha512-UbNt/JAWS0m/NJOebR0QMRHBk0hu03r5dx9GK8Cs0AS3I81yDcOc9k+DytPItgVvBP7J6Mf6U2n3BPAacAV9oA==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/monaco-editor": { "version": "0.52.2", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", @@ -2339,9 +2351,9 @@ } }, "node_modules/pankow": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/pankow/-/pankow-2.10.0.tgz", - "integrity": "sha512-qpDVvHgpJYfYPOyPhPL317r6sgMYm+WJHUk5R9g3iy7oqFBu6d+mS3MXgzbAXuGBUAsie+ZTiyoMi2u1OgXy4Q==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/pankow/-/pankow-2.10.1.tgz", + "integrity": "sha512-ePanPbreta20RFgVX3w1sZQqnjNqMzIOm2+T1utxwmc4LwoQYEVSZcKwgF1AXkPdZXwUw8cDX3j/GIcnts0Eyg==", "license": "ISC", "dependencies": { "@fontsource/inter": "^5.2.5", @@ -3104,9 +3116,9 @@ "integrity": "sha512-kbsPKj0S4p44JdYRFiW78Td8Ge2sBVxi/PIBwmih+RpSXUdvS9nbs1HIiuUSPtRMi14CqLEZ/fbk7dj7vni1Sg==" }, "@fontsource/noto-sans": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans/-/noto-sans-5.2.5.tgz", - "integrity": "sha512-g3hWgsQayHQGVXgQKjbHKpS7YQFQY8azwXFCi3ZkhgCK5fZXe3udeQYrZJJ4PMswGkNhoDPRR9OJZBlMjzZAEQ==" + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans/-/noto-sans-5.2.6.tgz", + "integrity": "sha512-46D39T1/QJ16LSbCTaNjjrGS6Y69FMLS9FeHKsrXSQg15Z2MFqmlnNMpURx2l6Sr3x9t9EVdr3CAkHnZyvea+w==" }, "@fortawesome/fontawesome-free": { "version": "6.7.2", @@ -4064,6 +4076,14 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" }, + "moment-timezone": { + "version": "0.5.47", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.47.tgz", + "integrity": "sha512-UbNt/JAWS0m/NJOebR0QMRHBk0hu03r5dx9GK8Cs0AS3I81yDcOc9k+DytPItgVvBP7J6Mf6U2n3BPAacAV9oA==", + "requires": { + "moment": "^2.29.4" + } + }, "monaco-editor": { "version": "0.52.2", "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", @@ -4135,9 +4155,9 @@ } }, "pankow": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/pankow/-/pankow-2.10.0.tgz", - "integrity": "sha512-qpDVvHgpJYfYPOyPhPL317r6sgMYm+WJHUk5R9g3iy7oqFBu6d+mS3MXgzbAXuGBUAsie+ZTiyoMi2u1OgXy4Q==", + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/pankow/-/pankow-2.10.1.tgz", + "integrity": "sha512-ePanPbreta20RFgVX3w1sZQqnjNqMzIOm2+T1utxwmc4LwoQYEVSZcKwgF1AXkPdZXwUw8cDX3j/GIcnts0Eyg==", "requires": { "@fontsource/inter": "^5.2.5", "@fortawesome/fontawesome-free": "^6.7.2", diff --git a/dashboard/package.json b/dashboard/package.json index af92c427f..58ab810bb 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -10,7 +10,7 @@ "@fontsource/arimo": "^5.2.5", "@fontsource/dm-sans": "^5.2.5", "@fontsource/inter": "^5.2.5", - "@fontsource/noto-sans": "^5.2.5", + "@fontsource/noto-sans": "^5.2.6", "@fortawesome/fontawesome-free": "^6.7.2", "@vitejs/plugin-vue": "^5.2.1", "@xterm/addon-attach": "^0.11.0", @@ -25,8 +25,8 @@ "filesize": "^10.1.6", "jquery": "^3.7.1", "marked": "^15.0.7", - "moment": "^2.30.1", - "pankow": "^2.10.0", + "moment-timezone": "^0.5.47", + "pankow": "^2.10.1", "pankow-viewers": "^1.0.11", "sass": "^1.85.1", "vite": "^6.2.2", diff --git a/dashboard/src/Index.vue b/dashboard/src/Index.vue index 8fa7f69fd..d91eec19c 100644 --- a/dashboard/src/Index.vue +++ b/dashboard/src/Index.vue @@ -2,6 +2,11 @@ import { onMounted, ref } from 'vue'; import { Notification } from 'pankow'; +import { API_ORIGIN } from './constants.js'; +import ProfileModel from './models/ProfileModel.js'; +import DashboardModel from './models/DashboardModel.js'; +import Sidebar from './components/Sidebar.vue'; +import Headerbar from './components/Headerbar.vue'; import AppsView from './views/AppsView.vue'; import AppConfigureView from './views/AppConfigureView.vue'; import AppstoreView from './views/AppstoreView.vue'; @@ -48,7 +53,15 @@ const VIEWS = { VOLUMES: 'volumes', }; +const dashboardModel = DashboardModel.create(); +const profileModel = ProfileModel.create(); + const view = ref(''); +const profile = ref({}); +const subscription = ref({ + plan: {}, +}); +const config = ref({}); function onHashChange() { const v = location.hash.slice(2); @@ -96,11 +109,8 @@ function onHashChange() { } else if (v === VIEWS.VOLUMES) { view.value = VIEWS.VOLUMES; } else { - view.value = ''; + window.location.hash = '/' + VIEWS.APPS; } - - // hack for layout to avoid consuming space if vue view is not active - document.getElementById('app').style.height = view.value ? 'auto' : '0'; } onMounted(async () => { @@ -111,34 +121,58 @@ onMounted(async () => { window.addEventListener('hashchange', onHashChange); onHashChange(); + + let [error, result] = await profileModel.get(); + if (error) return console.error(error); + profile.value = result; + + [error, result] = await dashboardModel.config(); + if (error) return console.error(error); + config.value = result; + + // TODO need to be reactive when name or icon changes + window.document.title = result.cloudronName; + document.getElementById('favicon').href = `${API_ORIGIN}/api/v1/cloudron/avatar?ts=${Date.now()}`; }); diff --git a/dashboard/src/components/ApiTokens.vue b/dashboard/src/components/ApiTokens.vue index 46ca8b597..03cc408f1 100644 --- a/dashboard/src/components/ApiTokens.vue +++ b/dashboard/src/components/ApiTokens.vue @@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'; const i18n = useI18n(); const t = i18n.t; -import moment from 'moment'; +import moment from 'moment-timezone'; import { ref, onMounted, computed, useTemplateRef } from 'vue'; import { Button, Dialog, InputDialog, FormGroup, Radiobutton, TableView, TextInput } from 'pankow'; import { copyToClipboard, prettyLongDate } from 'pankow/utils'; diff --git a/dashboard/src/components/AppPasswords.vue b/dashboard/src/components/AppPasswords.vue index 8fb58f707..a04b64dc0 100644 --- a/dashboard/src/components/AppPasswords.vue +++ b/dashboard/src/components/AppPasswords.vue @@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'; const i18n = useI18n(); const t = i18n.t; -import moment from 'moment'; +import moment from 'moment-timezone'; import { ref, onMounted, useTemplateRef, computed } from 'vue'; import { Button, Dialog, Dropdown, FormGroup, TextInput, TableView, InputDialog } from 'pankow'; import { prettyLongDate, copyToClipboard } from 'pankow/utils'; diff --git a/dashboard/src/components/CpuUsage.vue b/dashboard/src/components/CpuUsage.vue index 29b083dcf..845ac1037 100644 --- a/dashboard/src/components/CpuUsage.vue +++ b/dashboard/src/components/CpuUsage.vue @@ -2,7 +2,7 @@ import { ref, onMounted, useTemplateRef } from 'vue'; import Chart from 'chart.js/auto'; -import moment from 'moment'; +import moment from 'moment-timezone'; import Section from './Section.vue'; import SystemModel from '../models/SystemModel.js'; diff --git a/dashboard/src/components/Headerbar.vue b/dashboard/src/components/Headerbar.vue new file mode 100644 index 000000000..22bb42e9e --- /dev/null +++ b/dashboard/src/components/Headerbar.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/dashboard/src/components/MemoryUsage.vue b/dashboard/src/components/MemoryUsage.vue index aaa733320..1a5ed0d6d 100644 --- a/dashboard/src/components/MemoryUsage.vue +++ b/dashboard/src/components/MemoryUsage.vue @@ -2,7 +2,7 @@ import { ref, onMounted, useTemplateRef } from 'vue'; import Chart from 'chart.js/auto'; -import moment from 'moment'; +import moment from 'moment-timezone'; import Section from './Section.vue'; import SystemModel from '../models/SystemModel.js'; diff --git a/dashboard/src/components/Sidebar.vue b/dashboard/src/components/Sidebar.vue new file mode 100644 index 000000000..f7426fd4c --- /dev/null +++ b/dashboard/src/components/Sidebar.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/dashboard/src/models/AppsModel.js b/dashboard/src/models/AppsModel.js index f9ca372e7..fce2a6713 100644 --- a/dashboard/src/models/AppsModel.js +++ b/dashboard/src/models/AppsModel.js @@ -2,7 +2,7 @@ import { API_ORIGIN, APP_TYPES, PROXY_APP_ID, HSTATES, ISTATES, RSTATES } from '../constants.js'; import { fetcher } from 'pankow'; import { sleep } from 'pankow/utils'; -import moment from 'moment'; +import moment from 'moment-timezone'; function installationStateLabel(app) { if (!app) return ''; diff --git a/dashboard/src/models/LogsModel.js b/dashboard/src/models/LogsModel.js index ff4dc0020..189181b36 100644 --- a/dashboard/src/models/LogsModel.js +++ b/dashboard/src/models/LogsModel.js @@ -1,5 +1,5 @@ -import moment from 'moment'; +import moment from 'moment-timezone'; import { ansiToHtml } from 'anser'; import { API_ORIGIN } from '../constants.js'; diff --git a/dashboard/src/models/ProfileModel.js b/dashboard/src/models/ProfileModel.js index 4391d58a5..76e7d16b7 100644 --- a/dashboard/src/models/ProfileModel.js +++ b/dashboard/src/models/ProfileModel.js @@ -30,6 +30,8 @@ function create() { if (error || result.status !== 200) return [error || result]; + result.body.isAtLeastUserManager = [ ROLES.OWNER, ROLES.ADMIN, ROLES.MAIL_MANAGER, ROLES.USER_MANAGER ].indexOf(result.body.role) !== -1; + result.body.isAtLeastMailManager = [ ROLES.OWNER, ROLES.ADMIN, ROLES.MAIL_MANAGER ].indexOf(result.body.role) !== -1; result.body.isAtLeastAdmin = [ ROLES.OWNER, ROLES.ADMIN ].indexOf(result.body.role) !== -1; result.body.isAtLeastOwner = [ ROLES.OWNER ].indexOf(result.body.role) !== -1; diff --git a/dashboard/src/style.css b/dashboard/src/style.css index 718b1c38b..3079c13a5 100644 --- a/dashboard/src/style.css +++ b/dashboard/src/style.css @@ -1,9 +1,26 @@ +:root { + --pankow-color-primary: #0073cf; + --font-family: "Noto Sans",Helvetica,Arial,sans-serif; + --font-family--header: "Noto Sans Light",Helvetica,Arial,sans-serif; + --card-background: #f7f7f8; + --navbar-background: #f3f4f6; +/* --pankow-body-background-color: var(--body-background);*/ +} + +@media (prefers-color-scheme: dark) { + :root { + --card-background: #15181f; + --navbar-background: #11161f; +/* --pankow-body-background-color: var(--body-background);*/ + --pankow-dialog-background-color: var(--navbar-background); + --pankow-color-background: var(--navbar-background); + --pankow-text-color: white; + } +} html, body { - --pankow-color-primary: #0073cf; - font-size: 14px; /* this also defines the overall widget size as all sizes are in rem */ - font-family: "Noto Sans"; + font-family: var(--font-family); font-weight: 300; height: 100%; width: 100%; @@ -19,12 +36,6 @@ html, body { } } -@media (max-width: 576px) { - .hide-mobile { - display: none; - } -} - .shadow { box-shadow: 0 2px 5px rgba(0,0,0,.1); } @@ -33,8 +44,12 @@ html, body { height: 100%; } -h2 { +h1, h2, h3, h4, h5 { + font-family: var(--font-family--header); font-weight: 400; +} + +h2 { font-size: 24px; } @@ -43,6 +58,17 @@ a { color: var(--pankow-color-primary); } +fieldset { + border: none; + margin: 0; + padding: 0; +} + +hr { + border: 0; + border-top: 1px solid rgb(238.425,238.425,238.425); +} + footer { display: block; width: 100%; diff --git a/dashboard/src/views/AppConfigureView.vue b/dashboard/src/views/AppConfigureView.vue index 135a08dba..4bd363e44 100644 --- a/dashboard/src/views/AppConfigureView.vue +++ b/dashboard/src/views/AppConfigureView.vue @@ -293,7 +293,6 @@ onBeforeUnmount(() => { .titlebar { display: flex; justify-content: space-between; - background-color: var(--pankow-body-background-color); } .apptask-progress { diff --git a/dashboard/src/views/AppsView.vue b/dashboard/src/views/AppsView.vue index 6d4b0d4d2..5a1c72c65 100644 --- a/dashboard/src/views/AppsView.vue +++ b/dashboard/src/views/AppsView.vue @@ -412,12 +412,6 @@ onUnmounted(() => { color: var(--pankow-text-color); } -.grid-item:focus .grid-item-label, -.grid-item:hover .grid-item-label { - text-decoration: none; - color: var(--accent-color);; -} - .config { position: absolute; color: var(--pankow-text-color); @@ -436,8 +430,6 @@ onUnmounted(() => { .config:focus, .config:hover { - text-decoration: none; - color: var(--accent-color);; opacity: 1; } diff --git a/dashboard/src/views/SettingsView.vue b/dashboard/src/views/SettingsView.vue index 3f0b7bb23..bf77411d8 100644 --- a/dashboard/src/views/SettingsView.vue +++ b/dashboard/src/views/SettingsView.vue @@ -6,6 +6,7 @@ const t = i18n.t; import { ref, onMounted, watch } from 'vue'; import { Dropdown } from 'pankow'; +import moment from 'moment-timezone'; import Section from '../components/Section.vue'; import CloudronAccount from '../components/CloudronAccount.vue'; import SystemUpdate from '../components/SystemUpdate.vue'; @@ -15,7 +16,7 @@ import CloudronModel from '../models/CloudronModel.js'; const cloudronModel = CloudronModel.create(); // Timezone -const allTimezones = window.timezones; +const allTimezones = moment.tz.names().map(t => { return { id: t }; }); const timeZone = ref(''); const currentTimeZone = ref(''); @@ -92,8 +93,8 @@ onMounted(async () => {
-

- +

+
diff --git a/dashboard/src/views/SystemView.vue b/dashboard/src/views/SystemView.vue index f655da782..2399ce4c5 100644 --- a/dashboard/src/views/SystemView.vue +++ b/dashboard/src/views/SystemView.vue @@ -2,7 +2,7 @@ import { ref, onMounted } from 'vue'; import { Button } from 'pankow'; -import moment from 'moment'; +import moment from 'moment-timezone'; import { prettyDecimalSize } from 'pankow/utils'; import Section from '../components/Section.vue'; import DiskUsage from '../components/DiskUsage.vue'; @@ -22,6 +22,7 @@ const uptime = ref(''); const activeSince = ref(''); function onReboot() { + // TODO ask the user! systemModel.reboot(); }