diff --git a/website/api/controllers/view-app-details.js b/website/api/controllers/view-app-details.js new file mode 100644 index 0000000000..e757164e3c --- /dev/null +++ b/website/api/controllers/view-app-details.js @@ -0,0 +1,60 @@ +module.exports = { + + + friendlyName: 'View app details', + + + description: 'Display "App details" page.', + + + inputs: { + appIdentifier: { + type: 'string', + required: true, + description: 'the identifier of an app in Fleet\'s maintained app library.', + example: '1password' + }, + }, + + exits: { + + success: { + viewTemplatePath: 'pages/app-details' + }, + + badConfig: { + responseType: 'badConfig' + }, + + notFound: { + responseType: 'notFound' + }, + + }, + + + fn: async function ({appIdentifier}) { + + if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.appLibrary) || !sails.config.builtStaticContent.appLibrary) { + throw {badConfig: 'builtStaticContent.appLibrary'}; + } + + let thisApp = _.find(sails.config.builtStaticContent.appLibrary, { identifier: appIdentifier }); + if (!thisApp) { + throw 'notFound'; + } + // FUTURE: make these better. + let pageTitleForMeta = thisApp.name + ' | Fleet app library'; + // let pageDescriptionForMeta = 'TODO' + + // Respond with view. + return { + thisApp, + // pageDescriptionForMeta, + pageTitleForMeta, + }; + + } + + +}; diff --git a/website/api/controllers/view-app-library.js b/website/api/controllers/view-app-library.js new file mode 100644 index 0000000000..1ea01f1e25 --- /dev/null +++ b/website/api/controllers/view-app-library.js @@ -0,0 +1,33 @@ +module.exports = { + + + friendlyName: 'View app library', + + + description: 'Display "App library" page.', + + + exits: { + + success: { + viewTemplatePath: 'pages/app-library' + }, + badConfig: { responseType: 'badConfig' }, + }, + + + fn: async function () { + + if (!_.isObject(sails.config.builtStaticContent) || !_.isArray(sails.config.builtStaticContent.appLibrary) || !sails.config.builtStaticContent.appLibrary) { + throw {badConfig: 'builtStaticContent.appLibrary'}; + } + + let allApps = sails.config.builtStaticContent.appLibrary; + allApps = _.sortBy(allApps, 'name'); + // Respond with view. + return {allApps}; + + } + + +}; diff --git a/website/assets/images/app-icon-1password-60x60@2x.png b/website/assets/images/app-icon-1password-60x60@2x.png new file mode 100644 index 0000000000..99bf1fe9ba Binary files /dev/null and b/website/assets/images/app-icon-1password-60x60@2x.png differ diff --git a/website/assets/images/app-icon-adobe-acrobat-reader-60x60@2x.png b/website/assets/images/app-icon-adobe-acrobat-reader-60x60@2x.png new file mode 100644 index 0000000000..b7d47badf5 Binary files /dev/null and b/website/assets/images/app-icon-adobe-acrobat-reader-60x60@2x.png differ diff --git a/website/assets/images/app-icon-box-drive-60x60@2x.png b/website/assets/images/app-icon-box-drive-60x60@2x.png new file mode 100644 index 0000000000..807083aca3 Binary files /dev/null and b/website/assets/images/app-icon-box-drive-60x60@2x.png differ diff --git a/website/assets/images/app-icon-brave-browser-60x60@2x.png b/website/assets/images/app-icon-brave-browser-60x60@2x.png new file mode 100644 index 0000000000..53fe7548c4 Binary files /dev/null and b/website/assets/images/app-icon-brave-browser-60x60@2x.png differ diff --git a/website/assets/images/app-icon-cloudflare-warp-60x60@2x.png b/website/assets/images/app-icon-cloudflare-warp-60x60@2x.png new file mode 100644 index 0000000000..3c38697db8 Binary files /dev/null and b/website/assets/images/app-icon-cloudflare-warp-60x60@2x.png differ diff --git a/website/assets/images/app-icon-docker-60x60@2x.png b/website/assets/images/app-icon-docker-60x60@2x.png new file mode 100644 index 0000000000..074766f398 Binary files /dev/null and b/website/assets/images/app-icon-docker-60x60@2x.png differ diff --git a/website/assets/images/app-icon-figma-60x60@2x.png b/website/assets/images/app-icon-figma-60x60@2x.png new file mode 100644 index 0000000000..a30d4e9024 Binary files /dev/null and b/website/assets/images/app-icon-figma-60x60@2x.png differ diff --git a/website/assets/images/app-icon-firefox-60x60@2x.png b/website/assets/images/app-icon-firefox-60x60@2x.png new file mode 100644 index 0000000000..1ff3b7c35d Binary files /dev/null and b/website/assets/images/app-icon-firefox-60x60@2x.png differ diff --git a/website/assets/images/app-icon-google-chrome-60x60@2x.png b/website/assets/images/app-icon-google-chrome-60x60@2x.png new file mode 100644 index 0000000000..3bf92c2a9e Binary files /dev/null and b/website/assets/images/app-icon-google-chrome-60x60@2x.png differ diff --git a/website/assets/images/app-icon-microsoft-edge-60x60@2x.png b/website/assets/images/app-icon-microsoft-edge-60x60@2x.png new file mode 100644 index 0000000000..bd42b2fad4 Binary files /dev/null and b/website/assets/images/app-icon-microsoft-edge-60x60@2x.png differ diff --git a/website/assets/images/app-icon-microsoft-excel-60x60@2x.png b/website/assets/images/app-icon-microsoft-excel-60x60@2x.png new file mode 100644 index 0000000000..8401843239 Binary files /dev/null and b/website/assets/images/app-icon-microsoft-excel-60x60@2x.png differ diff --git a/website/assets/images/app-icon-microsoft-teams-60x60@2x.png b/website/assets/images/app-icon-microsoft-teams-60x60@2x.png new file mode 100644 index 0000000000..08c254d101 Binary files /dev/null and b/website/assets/images/app-icon-microsoft-teams-60x60@2x.png differ diff --git a/website/assets/images/app-icon-microsoft-word-60x60@2x.png b/website/assets/images/app-icon-microsoft-word-60x60@2x.png new file mode 100644 index 0000000000..18cd2362c1 Binary files /dev/null and b/website/assets/images/app-icon-microsoft-word-60x60@2x.png differ diff --git a/website/assets/images/app-icon-notion-60x60@2x.png b/website/assets/images/app-icon-notion-60x60@2x.png new file mode 100644 index 0000000000..7afb82ba82 Binary files /dev/null and b/website/assets/images/app-icon-notion-60x60@2x.png differ diff --git a/website/assets/images/app-icon-postman-60x60@2x.png b/website/assets/images/app-icon-postman-60x60@2x.png new file mode 100644 index 0000000000..3801a78f78 Binary files /dev/null and b/website/assets/images/app-icon-postman-60x60@2x.png differ diff --git a/website/assets/images/app-icon-slack-60x60@2x.png b/website/assets/images/app-icon-slack-60x60@2x.png new file mode 100644 index 0000000000..9811ab1eec Binary files /dev/null and b/website/assets/images/app-icon-slack-60x60@2x.png differ diff --git a/website/assets/images/app-icon-teamviewer-60x60@2x.png b/website/assets/images/app-icon-teamviewer-60x60@2x.png new file mode 100644 index 0000000000..298113cfb1 Binary files /dev/null and b/website/assets/images/app-icon-teamviewer-60x60@2x.png differ diff --git a/website/assets/images/app-icon-visual-studio-code-60x60@2x.png b/website/assets/images/app-icon-visual-studio-code-60x60@2x.png new file mode 100644 index 0000000000..b9fe5290ac Binary files /dev/null and b/website/assets/images/app-icon-visual-studio-code-60x60@2x.png differ diff --git a/website/assets/images/app-icon-whatsapp-60x60@2x.png b/website/assets/images/app-icon-whatsapp-60x60@2x.png new file mode 100644 index 0000000000..0a44830984 Binary files /dev/null and b/website/assets/images/app-icon-whatsapp-60x60@2x.png differ diff --git a/website/assets/images/app-icon-zoom-60x60@2x.png b/website/assets/images/app-icon-zoom-60x60@2x.png new file mode 100644 index 0000000000..069259362d Binary files /dev/null and b/website/assets/images/app-icon-zoom-60x60@2x.png differ diff --git a/website/assets/js/pages/app-details.page.js b/website/assets/js/pages/app-details.page.js new file mode 100644 index 0000000000..bba7bb6ac1 --- /dev/null +++ b/website/assets/js/pages/app-details.page.js @@ -0,0 +1,47 @@ +parasails.registerPage('app-details', { + // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ + // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ + // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ + data: { + //… + }, + + // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ + // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣ + // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝ + beforeMount: function() { + //… + }, + mounted: async function() { + + if(this.algoliaPublicKey) { // Note: Docsearch will only be enabled if sails.config.custom.algoliaPublicKey is set. If the value is undefined, the documentation search will be disabled. + docsearch({ + appId: 'NZXAYZXDGH', + apiKey: this.algoliaPublicKey, + indexName: 'fleetdm', + container: '#docsearch-query', + placeholder: 'Search', + debug: false, + searchParameters: { + 'facetFilters': ['section:queries'] + }, + }); + } + + $('[purpose="copy-button"]').on('click', async function() { + let code = $(this).siblings('pre').find('code').text(); + $(this).addClass('copied'); + await setTimeout(()=>{ + $(this).removeClass('copied'); + }, 2000); + navigator.clipboard.writeText(code); + }); + }, + + // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ + // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ + // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ + methods: { + //… + } +}); diff --git a/website/assets/js/pages/app-library.page.js b/website/assets/js/pages/app-library.page.js new file mode 100644 index 0000000000..595a8ad10e --- /dev/null +++ b/website/assets/js/pages/app-library.page.js @@ -0,0 +1,25 @@ +parasails.registerPage('app-library', { + // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ + // ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣ + // ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝ + data: { + //… + }, + + // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ + // ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣ + // ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝ + beforeMount: function() { + //… + }, + mounted: async function() { + //… + }, + + // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ + // ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗ + // ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝ + methods: { + //… + } +}); diff --git a/website/assets/styles/importer.less b/website/assets/styles/importer.less index 33420eb3ae..8fc197e4c2 100644 --- a/website/assets/styles/importer.less +++ b/website/assets/styles/importer.less @@ -78,4 +78,6 @@ @import 'pages/start.less'; @import 'pages/deals.less'; @import 'pages/testimonials.less'; +@import 'pages/app-library.less'; +@import 'pages/app-details.less'; diff --git a/website/assets/styles/pages/app-details.less b/website/assets/styles/pages/app-details.less new file mode 100644 index 0000000000..b4e52a0343 --- /dev/null +++ b/website/assets/styles/pages/app-details.less @@ -0,0 +1,418 @@ +#app-details { + + h3 { + padding-top: 32px; + color: #192147; + font-size: 24px; + font-weight: 800; + line-height: 120%; + margin-bottom: 0px; + } + + p { + color: #515774; + font-size: 16px; + font-weight: 400; + line-height: 150%; + } + + [purpose='page-container'] { + padding: 64px 64px 32px 64px; + } + [purpose='page-content'] { + margin-left: auto; + margin-right: auto; + max-width: 1072px; + } + + [purpose='breadcrumbs-and-search'] { + margin-bottom: 64px; + max-width: 1072px; + font-size: 14px; + [purpose='breadcrumbs'] { + margin-right: 24px; + } + [purpose='search'] { + // Note: We're using classes here to override the default Docsearch styles; + button { + width: 100%; + cursor: text; + margin: 0; + } + .DocSearch-Button { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border: 1px solid @core-fleet-black-25; + background-color: #FFF; + padding: 6px; + height: 36px; + margin: 0; + width: 256px; + } + .DocSearch-Button:hover { + box-shadow: none; + border: 1px solid @core-fleet-black-25; + color: @core-fleet-black-50; + } + .DocSearch-Search-Icon { + margin-left: 10px; + height: 16px; + width: 16px; + color: @core-fleet-black-50; + stroke-width: 3px; + } + .DocSearch-Button-Keys { + display: none; + } + .input-group:focus-within { + border: 1px solid @core-vibrant-blue; + } + .DocSearch-Button-Placeholder { + font-size: 16px; + font-weight: 400; + padding-left: 12px; + } + [purpose='disabled-search'] { + input { + padding-top: 6px; + padding-bottom: 6px; + border: none; + } &::placeholder { + font-size: 16px; + line-height: 24px; + color: #8B8FA2; + } + .input-group { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border: 1px solid @core-fleet-black-25; + background: #FFF; + } + .input-group:focus-within { + border: 1px solid @core-vibrant-blue; + } + .form-control { + border-radius: 6px; + padding: 6px; + height: 36px; + margin: 0; + width: 212px; + } + .docsearch-input:focus-visible { + outline: none; + } + .ds-input:focus { + outline: rgba(0, 0, 0, 0); + } + .input-group-text { + color: @core-fleet-black-50; + } + .form-control { + height: 36px; + padding: 0px; + font-size: 16px; + } &:focus { + border: none; + } + } + } + + [purpose='breadcrumbs-category'] { + color: #8B8FA2; + margin-right: 8px; + font-size: 14px; + font-weight: 400; + line-height: 150%; /* */ + &:hover { + color: #192147; + text-decoration: none; + } + } + [purpose='breadcrumbs-title'] { + margin-left: 8px; + } + } + [purpose='icon-and-name'] { + flex-direction: row; + align-items: center; + + } + [purpose='app-icon'] { + img { + height: 80px; + } + margin-right: 24px; + } + [purpose='app-name'] { + color: #192147; + font-size: 32px; + font-style: normal; + font-weight: 800; + line-height: 150%; + margin-bottom: 0px; + } + + [purpose='app-description'] { + color: #515774; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 150%; + margin-bottom: 0px; + padding: 16px 0px 32px 0px; + } + [purpose='app-details'] { + padding-right: 64px; + max-width: 800px; + width: 100%; + p { + margin-bottom: 24px; + margin-top: 16px; + color: #515774; + font-size: 16px; + font-weight: 400; + line-height: 150%; + } + a { + color: #515774; + text-decoration: underline; + text-underline-offset: 1px; + &:hover { + color: #515774; + } + } + [purpose='platform-and-version'] { + p { + color: #8B8FA2; + font-size: 16px; + font-weight: 400; + line-height: 150%; + margin-bottom: 0px; + margin-top: 0px; + } + } + + } + [purpose='app-uninstall'] { + pre { + height: 141px; + } + } + [purpose='app-install'] { + ol { + counter-reset: custom-counter; + list-style-type: none; + padding-inline-start: 0px; + padding: 0; + margin-top: 16px; + margin-bottom: 32px; + ul > li { + text-indent: 0px; + margin-left: 0px; + } + > li { + counter-increment: custom-counter; + margin-left: 36px; + text-indent: -36px; + padding-left: 0px; + margin-bottom: 16px; + code:not(.nohighlight):not(.mermaid) { + display: inline; + } + p { + display: inline; + margin-bottom: 0px; + } + blockquote { + text-indent: 0px; + } + } + > li::before { + content: counter(custom-counter); + background-color: #E2E4EA; + width: 24px; + font-size: 13px; + display: inline-block; + border-radius: 50%; + margin-right: 10px; + padding: 2px 4px; + text-align: center; + line-height: 20px; + text-indent: 0px; + } + } + } + [purpose='app-check'] { + padding-bottom: 24px; + } + + [purpose='right-sidebar'] { + width: 256px; + margin-left: 16px; + font-size: 14px; + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 500ms; + a:not([purpose='edit-button']) { + margin-bottom: 8px; + display: block; + color: #515774; + &:hover { + text-decoration: none; + color: @core-fleet-black; + } + } + } + [purpose='docs-links'] { + a { + display: block; + } + } + [purpose='social-share-buttons'] { + padding-bottom: 24px; + margin-bottom: 24px; + border-bottom: 1px solid #E2E4EA; + a { + margin-right: 16px; + } + img { + height: 20px; + width: 20px; + } + } + [purpose='edit-button'] { + margin-top: 24px; + img { + width: 16px; + height: 16px; + display: inline; + margin-right: 8px; + } + padding: 6px 8px; + display: block; + color: @core-fleet-black-75; + text-decoration: none; + font-size: 14px; + line-height: 21px; + border-radius: 6px; + width: 102px; + background: rgba(25, 33, 71, 0.05); + &:hover { + background-color: rgba(25, 33, 71, 0.1); + } + &:active { + background-color: rgba(25, 33, 71, 0.1); + } + } + + [purpose='codeblock'] { + padding: 0; + position: relative; + [purpose='copy-button'] { + position: absolute; + top: 11px; + right: 10px; + border-radius: 8px; + height: 32px; + width: 32px; + background: url('/images/icon-copy-16x16@2x.png'); + background-color: #F9FAFC; + background-size: 14px 14px; + background-position: center; + background-repeat: no-repeat; + cursor: pointer; + &:hover { + background-color: #F2F2F5; + } + &.copied { + background: url('/images/icon-copy-clicked-checkmark-32x32@2x.png'); + background-size: 32px 32px; + background-repeat: no-repeat; + background-position: center; + } + } + } + + pre { + width: 100%; + max-width: 100%; + padding: 16px 44px 16px 24px; + border: 1px solid #E2E4EA; + background: #F9FAFC; + border-radius: 4px; + margin-top: 32px; + margin-bottom: 24px; + white-space: pre-wrap; + word-wrap: break-word; + overflow: auto; + code { + color: #515774; + font-family: 'Source Code Pro'; + font-size: 14px; + font-weight: 400; + line-height: 150%; + background-color: @ui-off-white; + border: none; + padding: 0; + dispaly: block; + } + } + + @media (max-width: 1200px) { + + + } + @media (max-width: 991px) { + [purpose='page-container'] { + padding: 32px; + } + [purpose='app-details'] { + padding-right: 0px; + max-width: 100%; + } + [purpose='right-sidebar'] { + width: 100%; + margin-left: 0px; + } + [purpose='breadcrumbs-and-search'] { + margin-bottom: 32px; + } + } + + @media (max-width: 768px) { + [purpose='breadcrumbs-and-search'] { + max-width: 1072px; + font-size: 14px; + [purpose='breadcrumbs'] { + margin-bottom: 24px; + } + [purpose='search'] { + width: 100%; + .DocSearch-Button { + width: 100%; + } + } + } + } + + @media (max-width: 575px) { + [purpose='page-container'] { + padding: 32px 24px; + } + + } + @media (max-width: 375px) { + [purpose='icon-and-name'] { + flex-direction: column; + align-items: flex-start; + + } + + } + + + } diff --git a/website/assets/styles/pages/app-library.less b/website/assets/styles/pages/app-library.less new file mode 100644 index 0000000000..5b2f66861d --- /dev/null +++ b/website/assets/styles/pages/app-library.less @@ -0,0 +1,205 @@ +#app-library { + + [purpose='page-container'] { + padding: 64px; + } + [purpose='page-content'] { + max-width: 1072px; + margin-left: auto; + margin-right: auto; + } + [purpose='search-and-headline'] { + margin-bottom: 64px; + } + [purpose='page-title'] { + max-width: 662px; + margin-right: 16px; + } + [purpose='app-search'] { + + // Note: We're using classes here to override the default Docsearch styles; + button { + width: 100%; + cursor: text; + margin: 0; + } + .DocSearch-Button { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border: 1px solid @core-fleet-black-25; + background-color: #FFF; + padding: 8px 15px; + height: 36px; + margin: 0; + width: 221px; + } + .DocSearch-Button:hover { + box-shadow: none; + border: 1px solid @core-fleet-black-25; + color: @core-fleet-black-50; + } + .DocSearch-Search-Icon { + margin-left: 0px; + margin-right: 8px; + height: 16px; + width: 16px; + color: @core-fleet-black-50; + stroke-width: 3px; + } + .DocSearch-Button-Keys { + display: none; + } + .input-group:focus-within { + border: 1px solid @core-vibrant-blue; + } + .DocSearch-Button-Placeholder { + font-size: 16px; + line-height: 16px; + font-weight: 400; + padding-left: 0px; + } + [purpose='disabled-search'] { + input { + padding-top: 6px; + padding-bottom: 6px; + border: none; + } &::placeholder { + font-size: 16px; + line-height: 24px; + color: #8B8FA2; + } + .input-group { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + border: 1px solid @core-fleet-black-25; + background: #FFF; + } + .input-group:focus-within { + border: 1px solid @core-vibrant-blue; + } + .form-control { + border-radius: 6px; + padding: 6px; + height: 36px; + margin: 0; + width: 212px; + } + .docsearch-input:focus-visible { + outline: none; + } + .ds-input:focus { + outline: rgba(0, 0, 0, 0); + } + .input-group-text { + color: @core-fleet-black-50; + } + .form-control { + height: 36px; + padding: 0px; + font-size: 16px; + } &:focus { + border: none; + } + } + img { + height: 16px; + margin-right: 8px; + } + background: #FFF; + &::placeholder { + font-size: 16px; + color: @core-fleet-black-50; + } + } + [purpose='app-cards'] { + column-count: 3; + margin-right: -8px; + margin-left: -8px; + margin-bottom: -8px; + margin-top: -8px; + } + + [purpose='app-card'] { + margin-right: 8px; + margin-left: 8px; + margin-bottom: 8px; + margin-top: 8px; + display: flex; + height: 92px; + min-width: 30%; + padding: 16px; + align-items: flex-start; + gap: 16px; + // flex: 1 0 0; + border-radius: 8px; + border: 1px solid #E2E4EA; + background: #FFF; + box-shadow: none; + &:hover { + text-decoration: none; + color: unset; + box-shadow: 0px 0px 0px 2px rgba(0, 0, 0, 0.03); + } + h4 { + color: var(--text-text-brand, #192147); + + /* Title XS */ + font-family: Inter; + font-size: 16px; + font-style: normal; + font-weight: 800; + line-height: 19.2px; /* 120% */ + margin-bottom: 0px; + } + p { + color: var(--text-text-primary, #515774); + + /* Body SM (FKA Card text) */ + font-family: Inter; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 21px; /* 150% */ + } + } + [purpose='app-icon'] { + margin-right: 16px; + img { + height: 60px; + } + } + + @media (max-width: 991px) { + [purpose='page-container'] { + padding: 64px 32px; + } + [purpose='app-card'] { + min-width: 40%; + } + } + + @media (max-width: 768px) { + [purpose='page-container'] { + padding: 48px 24px; + } + [purpose='app-search'] { + margin-top: 32px; + width: 100%; + .input-group { + width: 100%; + } + } + } + + @media (max-width: 575px) { + [purpose='page-container'] { + padding: 32px 24px; + } + + } + +} diff --git a/website/config/policies.js b/website/config/policies.js index becc2c3931..00c0dbecbb 100644 --- a/website/config/policies.js +++ b/website/config/policies.js @@ -58,4 +58,6 @@ module.exports.policies = { 'deliver-deal-registration-submission': true, 'get-est-device-certificate': true, 'view-testimonials': true, + 'view-app-library': true, + 'view-app-details': true, }; diff --git a/website/config/routes.js b/website/config/routes.js index f02a813ae5..be8f24a69c 100644 --- a/website/config/routes.js +++ b/website/config/routes.js @@ -297,10 +297,22 @@ module.exports.routes = { action: 'view-testimonials', locals: { pageTitleForMeta: 'Customer stories', - pageDescriptionForMeta: 'See what people are saying about Fleet' + pageDescriptionForMeta: 'See what people are saying about Fleet.' } }, + 'GET /app-library': { + action: 'view-app-library', + locals: { + pageTitleForMeta: 'App library', + pageDescriptionForMeta: 'Discover what is available for install in Fleet\'s maintained app library.', + } + }, + + 'GET /app-library/:appIdentifier': { + action: 'view-app-details',// Meta title and description set in view action + }, + // ╦ ╔═╗╔═╗╔═╗╔═╗╦ ╦ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗ // ║ ║╣ ║ ╦╠═╣║ ╚╦╝ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗ // ╩═╝╚═╝╚═╝╩ ╩╚═╝ ╩ ╩╚═╚═╝═╩╝╩╩╚═╚═╝╚═╝ ╩ ╚═╝ diff --git a/website/scripts/build-static-content.js b/website/scripts/build-static-content.js index 75d0d64043..d14d4f8e8b 100644 --- a/website/scripts/build-static-content.js +++ b/website/scripts/build-static-content.js @@ -1116,7 +1116,48 @@ module.exports = { // Add the rituals dictionary to builtStaticContent.rituals builtStaticContent.rituals = rituals; }, + // + // █████╗ ██████╗ ██████╗ ██╗ ██╗██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗ + // ██╔══██╗██╔══██╗██╔══██╗ ██║ ██║██╔══██╗██╔══██╗██╔══██╗██╔══██╗╚██╗ ██╔╝ + // ███████║██████╔╝██████╔╝ ██║ ██║██████╔╝██████╔╝███████║██████╔╝ ╚████╔╝ + // ██╔══██║██╔═══╝ ██╔═══╝ ██║ ██║██╔══██╗██╔══██╗██╔══██║██╔══██╗ ╚██╔╝ + // ██║ ██║██║ ██║ ███████╗██║██████╔╝██║ ██║██║ ██║██║ ██║ ██║ + // ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ + // + async()=>{ + let appLibrary = []; + // Get app library json + let appsJsonData = await sails.helpers.fs.readJson(path.join(topLvlRepoPath, '/server/mdm/maintainedapps/apps.json')); + // Then for each item in the json, build a configuration object to add to the sails.builtStaticContent.appLibrary array. + await sails.helpers.flow.simultaneouslyForEach(appsJsonData, async(app)=>{ + let appInformation = { + identifier: app.identifier, + bundleIdentifier: app.bundle_identifier, + installerFormat: app.installer_format, + }; + // Note: This method of getting information about the apps will be out of date until the JSON files in the /server/mdm/maintainedapps/testdata/ folder are updated. + let detailedInformationAboutThisApp = await sails.helpers.fs.readJson(path.join(topLvlRepoPath, '/server/mdm/maintainedapps/testdata/'+app.identifier+'.json')) + .intercept('doesNotExist', ()=>{ + return new Error(`Could not build app library configuration from testdata folder. When attempting to read a JSON configuration file for ${app.identifier}, no file was found at ${path.join(topLvlRepoPath, '/server/mdm/maintainedapps/testdata/'+app.identifier+'.json. Was it moved?')}.`); + }); + // Grab the latest information about these apps from the Homebrew API. + // let detailedInformationAboutThisApp = await sails.helpers.http.get(`https://formulae.brew.sh/api/cask/${app.identifier}.json`) + // .intercept((error)=>{ + // return new Error(`Could not build app library configuration. When attempting to send a request to the homebrew API to get the latest information about ${app.identifier}, an error occured. Full error: ${util.inspect(error, {depth: null})}`); + // }); + // let scriptToUninstallThisApp = await sails.helpers.fs.read(path.join(topLvlRepoPath, `/server/mdm/maintainedapps/testdata/scripts/${app.identifier}_uninstall.golden.sh`)) + // .intercept('doesNotExist', ()=>{ + // return new Error(`Could not build app library configuration from testdata folder. When attempting to read an uninstall script for ${app.identifier}, no file was found at ${path.join(topLvlRepoPath, '/server/mdm/maintainedapps/testdata/scripts/'+app.identifier+'_uninstall.golden.sh. Was it moved?')}.`); + // }); + // appInformation.uninstallScript = scriptToUninstallThisApp; + appInformation.version = detailedInformationAboutThisApp.version.split(',')[0]; + appInformation.description = detailedInformationAboutThisApp.desc; + appInformation.name = detailedInformationAboutThisApp.name[0]; + appLibrary.push(appInformation); + }); + builtStaticContent.appLibrary = appLibrary; + }, ]); // ██████╗ ███████╗██████╗ ██╗ █████╗ ██████╗███████╗ ███████╗ █████╗ ██╗██╗ ███████╗██████╗ ██████╗ // ██╔══██╗██╔════╝██╔══██╗██║ ██╔══██╗██╔════╝██╔════╝ ██╔════╝██╔══██╗██║██║ ██╔════╝██╔══██╗██╔════╝██╗ diff --git a/website/views/layouts/layout.ejs b/website/views/layouts/layout.ejs index 3134e14af1..7aefb13152 100644 --- a/website/views/layouts/layout.ejs +++ b/website/views/layouts/layout.ejs @@ -467,6 +467,8 @@ + + diff --git a/website/views/pages/app-details.ejs b/website/views/pages/app-details.ejs new file mode 100644 index 0000000000..c5e384b948 --- /dev/null +++ b/website/views/pages/app-details.ejs @@ -0,0 +1,88 @@ +
macOS | <%- thisApp.version %>
+<%- thisApp.description %>
+To install <%- thisApp.name %> on your work computer:
+Don’t see <%- thisApp.name %> or the Fleet Desktop icon? Send a link to this page to your IT team.
+Run this query in Fleet to find old versions of 1Password across all your computers:
+SELECT 1 FROM apps WHERE bundle_identifier = '<%= thisApp.bundleIdentifier %>';
+ + <%- app.version %> +
+