Website: Add app library & app details page (#24205)
Related to: #23792 Changes: - Added /app-library, a page that displays information about Fleet-maintained apps - Added the app details page (/app-library/{app identifier}), a page that gives users detailed information about a single Fleet-maintained app - Updated the build-static-content script to add information about Fleet-maintained apps to the website's configuration.
@@ -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,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
@@ -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};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1016 B |
|
After Width: | Height: | Size: 977 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
@@ -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: {
|
||||
//…
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
parasails.registerPage('app-library', {
|
||||
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
|
||||
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
|
||||
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
|
||||
data: {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
|
||||
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
|
||||
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
|
||||
beforeMount: function() {
|
||||
//…
|
||||
},
|
||||
mounted: async function() {
|
||||
//…
|
||||
},
|
||||
|
||||
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
|
||||
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
|
||||
methods: {
|
||||
//…
|
||||
}
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
// ╦ ╔═╗╔═╗╔═╗╔═╗╦ ╦ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗
|
||||
// ║ ║╣ ║ ╦╠═╣║ ╚╦╝ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗
|
||||
// ╩═╝╚═╝╚═╝╩ ╩╚═╝ ╩ ╩╚═╚═╝═╩╝╩╩╚═╚═╝╚═╝ ╩ ╚═╝
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
]);
|
||||
// ██████╗ ███████╗██████╗ ██╗ █████╗ ██████╗███████╗ ███████╗ █████╗ ██╗██╗ ███████╗██████╗ ██████╗
|
||||
// ██╔══██╗██╔════╝██╔══██╗██║ ██╔══██╗██╔════╝██╔════╝ ██╔════╝██╔══██╗██║██║ ██╔════╝██╔══██╗██╔════╝██╗
|
||||
|
||||
@@ -467,6 +467,8 @@
|
||||
<script src="/js/pages/admin/email-templates.page.js"></script>
|
||||
<script src="/js/pages/admin/generate-license.page.js"></script>
|
||||
<script src="/js/pages/admin/sandbox-waitlist.page.js"></script>
|
||||
<script src="/js/pages/app-details.page.js"></script>
|
||||
<script src="/js/pages/app-library.page.js"></script>
|
||||
<script src="/js/pages/articles/articles.page.js"></script>
|
||||
<script src="/js/pages/articles/basic-article.page.js"></script>
|
||||
<script src="/js/pages/connect-vanta.page.js"></script>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<div id="app-details" v-cloak>
|
||||
<div purpose="page-container">
|
||||
<div purpose="page-content" class="container p-0">
|
||||
<div purpose="breadcrumbs-and-search" class="d-flex flex-md-row flex-column justify-content-between align-items-md-center align-items-start">
|
||||
<div purpose="breadcrumbs" class="d-flex flex-row align-items-start">
|
||||
<div>
|
||||
<a purpose="breadcrumbs-category" class="text-nowrap" href="/app-library">App library</a>/
|
||||
</div>
|
||||
<div purpose="breadcrumbs-title">
|
||||
<span><%- thisApp.name %></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div purpose="search" id="docsearch-query" class="d-flex">
|
||||
<div purpose="disabled-search" class="d-flex w-100">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text border-0 bg-transparent pl-3" >
|
||||
<img style="height: 16px; width: 16px;" class="search" alt="search" src="/images/icon-search-16x16@2x.png">
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-control border-0 ">
|
||||
<input class="docsearch-input pr-1"
|
||||
placeholder="Search" aria-label="Search the handbook"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div purpose="app-details-and-sidebar" class="d-flex flex-nowrap flex-lg-row flex-column">
|
||||
<div purpose="app-details" class="d-inline-flex flex-column flex-shrink w-0">
|
||||
<div purpose="icon-and-name" class="d-flex">
|
||||
<div purpose="app-icon"><img alt="<%- thisApp.name %> icon" src="/images/app-icon-<%- thisApp.identifier %>-60x60@2x.png"></div>
|
||||
<div class="d-flex flex-column">
|
||||
<h2 purpose="app-name"><%- thisApp.name %></h2>
|
||||
<div purpose="platform-and-version" class="d-flex flex-row">
|
||||
<p>macOS | <%- thisApp.version %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p purpose="app-description"><%- thisApp.description %></p>
|
||||
<div purpose="app-install">
|
||||
<h3>Self-service install</h3>
|
||||
<p>To install <%- thisApp.name %> on your work computer:</p>
|
||||
<ol>
|
||||
<li>Navigate to the <strong>Fleet Desktop</strong> icon in the OS menu bar and select <strong>My device</strong>.</li>
|
||||
<li>From the <strong>Self-service</strong> tab, navigate to <%- thisApp.name %> and click <strong>Install</strong>.</li>
|
||||
</ol>
|
||||
<p>Don’t see <%- thisApp.name %> or the Fleet Desktop icon? Send a <a :href="'/app-library/'+thisApp.identifier">link to this page</a> to your IT team.</p>
|
||||
</div>
|
||||
<!-- <div purpose="app-uninstall">
|
||||
<h3>Uninstall <%- thisApp.name %></h3>
|
||||
<p>Run the following script in your terminal to uninstall <%- thisApp.name %>:</p>
|
||||
<div purpose="codeblock">
|
||||
<div purpose="copy-button"></div>
|
||||
<pre><code class="hljs bash"><%= thisApp.uninstallScript %></code></pre>
|
||||
</div>
|
||||
</div> -->
|
||||
<div purpose="app-check">
|
||||
<h3>Is <%- thisApp.name %> up to date?</h3>
|
||||
<p>Run this query in Fleet to find old versions of 1Password across all your computers:</p>
|
||||
<div purpose="codeblock">
|
||||
<div purpose="copy-button"></div>
|
||||
<pre><code class="hljs sql">SELECT 1 FROM apps WHERE bundle_identifier = '<%= thisApp.bundleIdentifier %>';</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="right-sidebar" class="d-flex flex-column">
|
||||
<div purpose="social-share-buttons" class="d-flex flex-column order-lg-2 order-1">
|
||||
<p><strong>Share</strong></p>
|
||||
<div class="d-flex flex-row">
|
||||
<a :href="`https://news.ycombinator.com/submitlink?u=https://fleetdm.com/app-library/${encodeURIComponent(thisApp.identifier)}&t=${encodeURIComponent(thisApp.name)}`"><img src="/images/social-share-icon-hacker-news-20x20@2x.png" alt="Share this article on Hacker News"></a>
|
||||
<a :href="`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent('https://fleetdm.com/app-library/'+thisApp.identifier)}`"><img src="/images/social-share-icon-linkedin-20x20@2x.png" alt="Share this article on LinkedIn"></a>
|
||||
<a :href="`https://twitter.com/intent/tweet?url=${encodeURIComponent('https://fleetdm.com/app-library/'+thisApp.identifier)}`"><img src="/images/social-share-icon-twitter-20x20@2x.png" alt="Share this article on Twitter"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="docs-links" class="order-3">
|
||||
<a href="/docs">Docs</a>
|
||||
<a href="/docs/rest-api">REST API</a>
|
||||
<a href="/guides">Guides</a>
|
||||
<!-- <a purpose="edit-button" class="d-flex align-items-center text-nowrap" target="_blank" :href="'https://github.com/fleetdm/fleet/edit/main/'+queryLibraryYmlRepoPath"><img alt="A pencil icon" src="/images/pencil-16x16@2x.png">Edit page</a> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
|
||||
@@ -0,0 +1,64 @@
|
||||
<div id="app-library" v-cloak>
|
||||
<div purpose="page-container">
|
||||
<div purpose="page-content" class="d-flex flex-column">
|
||||
<div purpose="search-and-headline" class="d-flex flex-md-row flex-column justify-content-between align-items-md-center">
|
||||
<div purpose="page-title" class="d-flex flex-column">
|
||||
<h1>App library</h1>
|
||||
<!-- <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam porta urna non massa viverra, in consequat urna tincidunt.</p> -->
|
||||
</div>
|
||||
<!-- <div purpose="app-search" id="docsearch-query" class="d-flex">
|
||||
<div purpose="disabled-search" class="d-flex w-100">
|
||||
<div class="input-group d-flex flex-nowrap">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text border-0 bg-transparent" >
|
||||
<img style="height: 16px; width: 16px;" class="search" alt="search" src="/images/icon-search-16x16@2x.png">
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-control border-0 ">
|
||||
<input class="docsearch-input pr-1"
|
||||
placeholder="Search" aria-label="Search"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div purpose="app-cards" class="card-deck flex-wrap">
|
||||
<% for(let app of allApps) { %>
|
||||
|
||||
<a href="/app-library/<%- app.identifier %>" purpose="app-card" class="card d-flex">
|
||||
<div class="d-flex flex-row align-items-center justify-content-start">
|
||||
<div purpose="app-icon">
|
||||
<img alt="<%- app.name %> icon" src="/images/app-icon-<%- app.identifier %>-60x60@2x.png">
|
||||
</div>
|
||||
<div class="flex-column">
|
||||
<h4><%- app.name %></h4>
|
||||
<p purpose="app-version" class="mb-0">
|
||||
<%- app.version %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<% } %>
|
||||
<!--
|
||||
<a :href="'/app-library/'+app.identifier" purpose="app-card" class="card d-flex" v-for="app in allApps">
|
||||
<div class="d-flex flex-row align-items-center justify-content-start">
|
||||
<div purpose="app-icon">
|
||||
<img :src="'/images/app-icon-'+app.identifier+'-60x60@2x.png'">
|
||||
</div>
|
||||
<div class="flex-column">
|
||||
<h4>{{app.name}}</h4>
|
||||
<p purpose="app-version" class="mb-0">
|
||||
{{app.version}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a> -->
|
||||
<div purpose="app-card" class="card d-flex invisible"></div>
|
||||
<div purpose="app-card" class="card d-flex invisible"></div>
|
||||
<div purpose="app-card" class="card d-flex invisible"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>
|
||||