Website: add configuration profile generator (#26244)

Changes:
- Added /os-settings, a page where users can generate configuration
profiles
- Updated the docs navigation component to have a link to the os
settings page
- Added a new action: `get-llm-generated-configuration-profile` that
generates a configuration profile in either .mobileconfig, DDM, or CSP
formats.
- Added a new website dependency: ace editor.
This commit is contained in:
Eric
2025-02-11 12:33:54 +09:00
committed by GitHub
parent ebdb4b860d
commit 360c23d75e
16 changed files with 30613 additions and 1 deletions
@@ -0,0 +1,103 @@
module.exports = {
friendlyName: 'Get llm generated configuration profile',
description: '',
inputs: {
profileType: {
type: 'string',
isIn: [
'mobileconfig',
'csp',
'ddm',
],
required: true
},
naturalLanguageInstructions: {
type: 'string',
required: true
}
},
exits: {
success: {
description: 'A configuration profile was successfully generated for a user.',
},
couldNotGenerateProfile: {
description: 'A configuration profile could not be generated for a user using the provided instructions.',
responseType: 'badRequest'
}
},
fn: async function ({profileType, naturalLanguageInstructions}) {
let promptStringByProfileType = {
'csp': 'CSP XML profile that enforces OS settings on Windows devices',
'mobileconfig': 'XML .mobileconfig profile that enforces OS settings on macOS devices',
'ddm': 'Apple DDM MDM profile in XML format that enforces OS settings on macOS devices',
};
let configurationProfilePrompt = `Given this question from an IT admin, generate a ${promptStringByProfileType[profileType]}.
Here are the instructions:
\`\`\`
${naturalLanguageInstructions}
\`\`\`
Please give me all of the above in JSON, with this data shape:
{
"configurationProfile": "TODO"
"profileFilename": "TODO"
"settingsEnforced": [// For each setting enforced by the configuration profile.
{
// The name (key) of the settings that is enforced. e.g., LoginwindowText
name: "TODO",
// The value of the setting that is enforced
value: "TODO",
},
{...}
]
}
If a configuration profile cannot be generated from the provided instructions do not return the datashape above and instead return this JSON in this exact data shape:
{
"couldNotGenerateProfile": true
}
`;
// console.log(configurationProfilePrompt);
let configurationProfileGenerationResult = await sails.helpers.ai.prompt.with({
prompt: configurationProfilePrompt,
baseModel: 'o3-mini-2025-01-31',
// expectJson: true
}).intercept((err)=>{
return new Error(`When trying generate a configuration profile for a user, an error occurred. Full error: ${require('util').inspect(err, {depth: 2})}`);
});
let jsonResult = JSON.parse(configurationProfileGenerationResult);
// console.log(configurationProfileGenerationResult);
// All done.
if(jsonResult.couldNotGenerateProfile) {
throw 'couldNotGenerateProfile';
}
if(!jsonResult.configurationProfile || !jsonResult.profileFilename || !jsonResult.settingsEnforced){
throw 'couldNotGenerateProfile';
}
return {
profile: jsonResult.configurationProfile,
profileFilename: jsonResult.profileFilename,
items: jsonResult.settingsEnforced
};
}
};
+27
View File
@@ -0,0 +1,27 @@
module.exports = {
friendlyName: 'View os settings',
description: 'Display "Os settings" page.',
exits: {
success: {
viewTemplatePath: 'pages/os-settings'
}
},
fn: async function () {
// Respond with view.
return {};
}
};
+1
View File
@@ -50,6 +50,7 @@
"Chart": true,
"gtag": true,
"analytics": true,
"ace": true,
// ...etc.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
File diff suppressed because one or more lines are too long
+477
View File
@@ -0,0 +1,477 @@
ace.define("ace/mode/xml_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(require, exports, module){"use strict";
var oop = require("../lib/oop");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
var XmlHighlightRules = function (normalize) {
var tagRegex = "[_:a-zA-Z\xc0-\uffff][-_:.a-zA-Z0-9\xc0-\uffff]*";
this.$rules = {
start: [
{ token: "string.cdata.xml", regex: "<\\!\\[CDATA\\[", next: "cdata" },
{
token: ["punctuation.instruction.xml", "keyword.instruction.xml"],
regex: "(<\\?)(" + tagRegex + ")", next: "processing_instruction"
},
{ token: "comment.start.xml", regex: "<\\!--", next: "comment" },
{
token: ["xml-pe.doctype.xml", "xml-pe.doctype.xml"],
regex: "(<\\!)(DOCTYPE)(?=[\\s])", next: "doctype", caseInsensitive: true
},
{ include: "tag" },
{ token: "text.end-tag-open.xml", regex: "</" },
{ token: "text.tag-open.xml", regex: "<" },
{ include: "reference" },
{ defaultToken: "text.xml" }
],
processing_instruction: [{
token: "entity.other.attribute-name.decl-attribute-name.xml",
regex: tagRegex
}, {
token: "keyword.operator.decl-attribute-equals.xml",
regex: "="
}, {
include: "whitespace"
}, {
include: "string"
}, {
token: "punctuation.xml-decl.xml",
regex: "\\?>",
next: "start"
}],
doctype: [
{ include: "whitespace" },
{ include: "string" },
{ token: "xml-pe.doctype.xml", regex: ">", next: "start" },
{ token: "xml-pe.xml", regex: "[-_a-zA-Z0-9:]+" },
{ token: "punctuation.int-subset", regex: "\\[", push: "int_subset" }
],
int_subset: [{
token: "text.xml",
regex: "\\s+"
}, {
token: "punctuation.int-subset.xml",
regex: "]",
next: "pop"
}, {
token: ["punctuation.markup-decl.xml", "keyword.markup-decl.xml"],
regex: "(<\\!)(" + tagRegex + ")",
push: [{
token: "text",
regex: "\\s+"
},
{
token: "punctuation.markup-decl.xml",
regex: ">",
next: "pop"
},
{ include: "string" }]
}],
cdata: [
{ token: "string.cdata.xml", regex: "\\]\\]>", next: "start" },
{ token: "text.xml", regex: "\\s+" },
{ token: "text.xml", regex: "(?:[^\\]]|\\](?!\\]>))+" }
],
comment: [
{ token: "comment.end.xml", regex: "-->", next: "start" },
{ defaultToken: "comment.xml" }
],
reference: [{
token: "constant.language.escape.reference.xml",
regex: "(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"
}],
attr_reference: [{
token: "constant.language.escape.reference.attribute-value.xml",
regex: "(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"
}],
tag: [{
token: ["meta.tag.punctuation.tag-open.xml", "meta.tag.punctuation.end-tag-open.xml", "meta.tag.tag-name.xml"],
regex: "(?:(<)|(</))((?:" + tagRegex + ":)?" + tagRegex + ")",
next: [
{ include: "attributes" },
{ token: "meta.tag.punctuation.tag-close.xml", regex: "/?>", next: "start" }
]
}],
tag_whitespace: [
{ token: "text.tag-whitespace.xml", regex: "\\s+" }
],
whitespace: [
{ token: "text.whitespace.xml", regex: "\\s+" }
],
string: [{
token: "string.xml",
regex: "'",
push: [
{ token: "string.xml", regex: "'", next: "pop" },
{ defaultToken: "string.xml" }
]
}, {
token: "string.xml",
regex: '"',
push: [
{ token: "string.xml", regex: '"', next: "pop" },
{ defaultToken: "string.xml" }
]
}],
attributes: [{
token: "entity.other.attribute-name.xml",
regex: tagRegex
}, {
token: "keyword.operator.attribute-equals.xml",
regex: "="
}, {
include: "tag_whitespace"
}, {
include: "attribute_value"
}],
attribute_value: [{
token: "string.attribute-value.xml",
regex: "'",
push: [
{ token: "string.attribute-value.xml", regex: "'", next: "pop" },
{ include: "attr_reference" },
{ defaultToken: "string.attribute-value.xml" }
]
}, {
token: "string.attribute-value.xml",
regex: '"',
push: [
{ token: "string.attribute-value.xml", regex: '"', next: "pop" },
{ include: "attr_reference" },
{ defaultToken: "string.attribute-value.xml" }
]
}]
};
if (this.constructor === XmlHighlightRules)
this.normalizeRules();
};
(function () {
this.embedTagRules = function (HighlightRules, prefix, tag) {
this.$rules.tag.unshift({
token: ["meta.tag.punctuation.tag-open.xml", "meta.tag." + tag + ".tag-name.xml"],
regex: "(<)(" + tag + "(?=\\s|>|$))",
next: [
{ include: "attributes" },
{ token: "meta.tag.punctuation.tag-close.xml", regex: "/?>", next: prefix + "start" }
]
});
this.$rules[tag + "-end"] = [
{ include: "attributes" },
{ token: "meta.tag.punctuation.tag-close.xml", regex: "/?>", next: "start",
onMatch: function (value, currentState, stack) {
stack.splice(0);
return this.token;
} }
];
this.embedRules(HighlightRules, prefix, [{
token: ["meta.tag.punctuation.end-tag-open.xml", "meta.tag." + tag + ".tag-name.xml"],
regex: "(</)(" + tag + "(?=\\s|>|$))",
next: tag + "-end"
}, {
token: "string.cdata.xml",
regex: "<\\!\\[CDATA\\["
}, {
token: "string.cdata.xml",
regex: "\\]\\]>"
}]);
};
}).call(TextHighlightRules.prototype);
oop.inherits(XmlHighlightRules, TextHighlightRules);
exports.XmlHighlightRules = XmlHighlightRules;
});
ace.define("ace/mode/behaviour/xml",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator"], function(require, exports, module){"use strict";
var oop = require("../../lib/oop");
var Behaviour = require("../behaviour").Behaviour;
var TokenIterator = require("../../token_iterator").TokenIterator;
function is(token, type) {
return token && token.type.lastIndexOf(type + ".xml") > -1;
}
var XmlBehaviour = function () {
this.add("string_dquotes", "insertion", function (state, action, editor, session, text) {
if (text == '"' || text == "'") {
var quote = text;
var selected = session.doc.getTextRange(editor.getSelectionRange());
if (selected !== "" && selected !== "'" && selected != '"' && editor.getWrapBehavioursEnabled()) {
return {
text: quote + selected + quote,
selection: false
};
}
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
var rightChar = line.substring(cursor.column, cursor.column + 1);
var iterator = new TokenIterator(session, cursor.row, cursor.column);
var token = iterator.getCurrentToken();
if (rightChar == quote && (is(token, "attribute-value") || is(token, "string"))) {
return {
text: "",
selection: [1, 1]
};
}
if (!token)
token = iterator.stepBackward();
if (!token)
return;
while (is(token, "tag-whitespace") || is(token, "whitespace")) {
token = iterator.stepBackward();
}
var rightSpace = !rightChar || rightChar.match(/\s/);
if (is(token, "attribute-equals") && (rightSpace || rightChar == '>') || (is(token, "decl-attribute-equals") && (rightSpace || rightChar == '?'))) {
return {
text: quote + quote,
selection: [1, 1]
};
}
}
});
this.add("string_dquotes", "deletion", function (state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && (selected == '"' || selected == "'")) {
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
if (rightChar == selected) {
range.end.column++;
return range;
}
}
});
this.add("autoclosing", "insertion", function (state, action, editor, session, text) {
if (text == '>') {
var position = editor.getSelectionRange().start;
var iterator = new TokenIterator(session, position.row, position.column);
var token = iterator.getCurrentToken() || iterator.stepBackward();
if (!token || !(is(token, "tag-name") || is(token, "tag-whitespace") || is(token, "attribute-name") || is(token, "attribute-equals") || is(token, "attribute-value")))
return;
if (is(token, "reference.attribute-value"))
return;
if (is(token, "attribute-value")) {
var tokenEndColumn = iterator.getCurrentTokenColumn() + token.value.length;
if (position.column < tokenEndColumn)
return;
if (position.column == tokenEndColumn) {
var nextToken = iterator.stepForward();
if (nextToken && is(nextToken, "attribute-value"))
return;
iterator.stepBackward();
}
}
if (/^\s*>/.test(session.getLine(position.row).slice(position.column)))
return;
while (!is(token, "tag-name")) {
token = iterator.stepBackward();
if (token.value == "<") {
token = iterator.stepForward();
break;
}
}
var tokenRow = iterator.getCurrentTokenRow();
var tokenColumn = iterator.getCurrentTokenColumn();
if (is(iterator.stepBackward(), "end-tag-open"))
return;
var element = token.value;
if (tokenRow == position.row)
element = element.substring(0, position.column - tokenColumn);
if (this.voidElements && this.voidElements.hasOwnProperty(element.toLowerCase()))
return;
return {
text: ">" + "</" + element + ">",
selection: [1, 1]
};
}
});
this.add("autoindent", "insertion", function (state, action, editor, session, text) {
if (text == "\n") {
var cursor = editor.getCursorPosition();
var line = session.getLine(cursor.row);
var iterator = new TokenIterator(session, cursor.row, cursor.column);
var token = iterator.getCurrentToken();
if (is(token, "") && token.type.indexOf("tag-close") !== -1) {
if (token.value == "/>")
return;
while (token && token.type.indexOf("tag-name") === -1) {
token = iterator.stepBackward();
}
if (!token) {
return;
}
var tag = token.value;
var row = iterator.getCurrentTokenRow();
token = iterator.stepBackward();
if (!token || token.type.indexOf("end-tag") !== -1) {
return;
}
if (this.voidElements && !this.voidElements[tag] || !this.voidElements) {
var nextToken = session.getTokenAt(cursor.row, cursor.column + 1);
var line = session.getLine(row);
var nextIndent = this.$getIndent(line);
var indent = nextIndent + session.getTabString();
if (nextToken && nextToken.value === "</") {
return {
text: "\n" + indent + "\n" + nextIndent,
selection: [1, indent.length, 1, indent.length]
};
}
else {
return {
text: "\n" + indent
};
}
}
}
}
});
};
oop.inherits(XmlBehaviour, Behaviour);
exports.XmlBehaviour = XmlBehaviour;
});
ace.define("ace/mode/folding/xml",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"], function(require, exports, module){"use strict";
var oop = require("../../lib/oop");
var Range = require("../../range").Range;
var BaseFoldMode = require("./fold_mode").FoldMode;
var FoldMode = exports.FoldMode = function (voidElements, optionalEndTags) {
BaseFoldMode.call(this);
this.voidElements = voidElements || {};
this.optionalEndTags = oop.mixin({}, this.voidElements);
if (optionalEndTags)
oop.mixin(this.optionalEndTags, optionalEndTags);
};
oop.inherits(FoldMode, BaseFoldMode);
var Tag = function () {
this.tagName = "";
this.closing = false;
this.selfClosing = false;
this.start = { row: 0, column: 0 };
this.end = { row: 0, column: 0 };
};
function is(token, type) {
return token.type.lastIndexOf(type + ".xml") > -1;
}
(function () {
this.getFoldWidget = function (session, foldStyle, row) {
var tag = this._getFirstTagInLine(session, row);
if (!tag)
return this.getCommentFoldWidget(session, row);
if (tag.closing || (!tag.tagName && tag.selfClosing))
return foldStyle === "markbeginend" ? "end" : "";
if (!tag.tagName || tag.selfClosing || this.voidElements.hasOwnProperty(tag.tagName.toLowerCase()))
return "";
if (this._findEndTagInLine(session, row, tag.tagName, tag.end.column))
return "";
return "start";
};
this.getCommentFoldWidget = function (session, row) {
if (/comment/.test(session.getState(row)) && /<!-/.test(session.getLine(row)))
return "start";
return "";
};
this._getFirstTagInLine = function (session, row) {
var tokens = session.getTokens(row);
var tag = new Tag();
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (is(token, "tag-open")) {
tag.end.column = tag.start.column + token.value.length;
tag.closing = is(token, "end-tag-open");
token = tokens[++i];
if (!token)
return null;
tag.tagName = token.value;
if (token.value === "") { //skip empty tag name token for fragment
token = tokens[++i];
if (!token)
return null;
tag.tagName = token.value;
}
tag.end.column += token.value.length;
for (i++; i < tokens.length; i++) {
token = tokens[i];
tag.end.column += token.value.length;
if (is(token, "tag-close")) {
tag.selfClosing = token.value == '/>';
break;
}
}
return tag;
}
else if (is(token, "tag-close")) {
tag.selfClosing = token.value == '/>';
return tag;
}
tag.start.column += token.value.length;
}
return null;
};
this._findEndTagInLine = function (session, row, tagName, startColumn) {
var tokens = session.getTokens(row);
var column = 0;
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
column += token.value.length;
if (column < startColumn - 1)
continue;
if (is(token, "end-tag-open")) {
token = tokens[i + 1];
if (is(token, "tag-name") && token.value === "") {
token = tokens[i + 2];
}
if (token && token.value == tagName)
return true;
}
}
return false;
};
this.getFoldWidgetRange = function (session, foldStyle, row) {
var firstTag = this._getFirstTagInLine(session, row);
if (!firstTag) {
return this.getCommentFoldWidget(session, row) && session.getCommentFoldRange(row, session.getLine(row).length);
}
var tags = session.getMatchingTags({ row: row, column: 0 });
if (tags) {
return new Range(tags.openTag.end.row, tags.openTag.end.column, tags.closeTag.start.row, tags.closeTag.start.column);
}
};
}).call(FoldMode.prototype);
});
ace.define("ace/mode/xml",["require","exports","module","ace/lib/oop","ace/lib/lang","ace/mode/text","ace/mode/xml_highlight_rules","ace/mode/behaviour/xml","ace/mode/folding/xml","ace/worker/worker_client"], function(require, exports, module){"use strict";
var oop = require("../lib/oop");
var lang = require("../lib/lang");
var TextMode = require("./text").Mode;
var XmlHighlightRules = require("./xml_highlight_rules").XmlHighlightRules;
var XmlBehaviour = require("./behaviour/xml").XmlBehaviour;
var XmlFoldMode = require("./folding/xml").FoldMode;
var WorkerClient = require("../worker/worker_client").WorkerClient;
var Mode = function () {
this.HighlightRules = XmlHighlightRules;
this.$behaviour = new XmlBehaviour();
this.foldingRules = new XmlFoldMode();
};
oop.inherits(Mode, TextMode);
(function () {
this.voidElements = lang.arrayToMap([]);
this.blockComment = { start: "<!--", end: "-->" };
this.createWorker = function (session) {
var worker = new WorkerClient(["ace"], "ace/mode/xml_worker", "Worker");
worker.attachToDocument(session.getDocument());
worker.on("error", function (e) {
session.setAnnotations(e.data);
});
worker.on("terminate", function () {
session.clearAnnotations();
});
return worker;
};
this.$id = "ace/mode/xml";
}).call(Mode.prototype);
exports.Mode = Mode;
}); (function() {
ace.require(["ace/mode/xml"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) {
module.exports = m;
}
});
})();
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -40,6 +40,7 @@ parasails.registerComponent('docsNavAndSearch', {
<a :class="[currentSection === 'queries' ? 'active' : '']" purpose="docs-top-nav-menu-link" href="/queries" style="text-decoration: none; text-decoration-line: none;">Queries</a>
<a :class="[currentSection === 'policies' ? 'active' : '']" purpose="docs-top-nav-menu-link" href="/policies" style="text-decoration: none; text-decoration-line: none;">Policies</a>
<a :class="[currentSection === 'software' ? 'active' : '']" purpose="docs-top-nav-menu-link" href="/app-library" style="text-decoration: none; text-decoration-line: none;">Software</a>
<!-- <a :class="[currentSection === 'osSettings' ? 'active' : '']" purpose="docs-top-nav-menu-link" href="/os-settings" style="text-decoration: none; text-decoration-line: none;">OS settings</a> -->
<a :class="[currentSection === 'tables' ? 'active' : '']" purpose="docs-top-nav-menu-link" href="/tables" style="text-decoration: none; text-decoration-line: none;">Data tables</a>
</div>
</div>
+109
View File
@@ -0,0 +1,109 @@
parasails.registerPage('os-settings', {
// ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗
// ║║║║║ ║ ║╠═╣║ ╚═╗ ║ ╠═╣ ║ ║╣
// ╩╝╚╝╩ ╩ ╩╩ ╩╩═╝ ╚═╝ ╩ ╩ ╩ ╩ ╚═╝
data: {
generatedOutput: ``,
ace: undefined,
parsedItemsInProfile: [],
formData: {
profileType: 'mobileconfig'
},
// For tracking client-side validation errors in our form.
// > Has property set to `true` for each invalid property in `formData`.
formErrors: { /* … */ },
// Form rules
formRules: {
naturalLanguageInstructions: {required: true},
profileType: {required: true},
},
// Syncing / loading state
syncing: false,
queryResult: '',
// Server error state
cloudError: '',
modal: '',
filenameOfGeneratedProfile: 'Generated profile',
hasGeneratedProfile: false,
},
// ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗
// ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣
// ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝
watch: {
//…
},
beforeMount: function() {
},
mounted: async function() {
this._setUpAceEditor();
//…
},
// ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
// ║║║║ ║ ║╣ ╠╦╝╠═╣║ ║ ║║ ║║║║╚═╗
// ╩╝╚╝ ╩ ╚═╝╩╚═╩ ╩╚═╝ ╩ ╩╚═╝╝╚╝╚═╝
methods: {
handleSubmittingForm: async function() {
let argins = this.formData;
this.syncing = true;
let generatedResult = await Cloud.getLlmGeneratedConfigurationProfile.with(argins)
.tolerate((err)=>{
this.cloudError = err;
this.syncing = false;
});
if(!this.cloudError) {
this.generatedOutput = generatedResult.profile;
this.filenameOfGeneratedProfile = generatedResult.profileFilename;
this.hasGeneratedProfile = true;
ace.edit('editor').setValue(generatedResult.profile);
this.parsedItemsInProfile = generatedResult.items;
this.modal = '';
this.syncing = false;
}
},
closeModal: async function() {
if(!this.syncing){
this.modal = '';
await this.forceRender();
}
},
getUpdatedValueFromEditor: function() {
this.generatedOutput = ace.edit('editor').getValue();
},
clickDownloadResult: function() {
let exportUrl = URL.createObjectURL(new Blob([this.generatedOutput], { type: 'text/xml;' }));
let exportDownloadLink = document.createElement('a');
exportDownloadLink.href = exportUrl;
// Parse the XML to determine if it is a .mobileconfig or a CSP.
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(this.generatedOutput, 'application/xml');
let hasPlistNode = xmlDoc.getElementsByTagName('plist')[0];
if(!this.filenameOfGeneratedProfile) {
if(hasPlistNode){
exportDownloadLink.download = `Generated configuration profile.mobileconfig`;
} else {
exportDownloadLink.download = 'Generated CSP.xml';
}
} else {
exportDownloadLink.download = this.filenameOfGeneratedProfile;
}
exportDownloadLink.click();
URL.revokeObjectURL(exportUrl);
},
_setUpAceEditor: function() {
var editor = ace.edit('editor');
editor.setTheme('ace/theme/fleet');
editor.session.setMode('ace/mode/xml');
editor.setOptions({
minLines: this.minLines ? this.minLines : 20 ,
maxLines: this.maxLines ? this.maxLines : 40 ,
});
editor.setReadOnly(true);
},
}
});
+1
View File
@@ -86,3 +86,4 @@
@import 'pages/vital-details.less';
@import 'pages/policy-library.less';
@import 'pages/policy-details.less';
@import 'pages/os-settings.less';
+180
View File
@@ -0,0 +1,180 @@
#os-settings {
p, strong {
color: #515774;
}
a {
color: #515774;
text-decoration: underline;
text-underline-offset: 2px;
cursor: pointer;
}
[purpose='page-container'] {
padding: 48px;
}
[purpose='page-content'] {
margin-left: auto;
margin-right: auto;
max-width: 1104px;
color: #515774;
}
.ace-editor-container {
}
[purpose='page-headline'] {
margin-bottom: 32px;
h1 {
margin-bottom: 16px;
}
p {
color: #515774;
margin-bottom: 0px;
}
}
[purpose='ace-editor'] {
min-width: 50vw;
max-width: 100%;
}
[parasails-component='modal'] [purpose='modal-dialog'] {
max-width: 688px;
[purpose='modal-content'] {
padding: 32px;
max-width: 688px;
}
}
[purpose='profile-editor'] {
position: relative;
.ace_content {
background: #F9FAFC;
}
}
[purpose='profile-type-selector'] {
max-width: 361px;
}
[purpose='submit-button'] {
max-width: 141px;
min-width: 141px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
[purpose='mobile-generate-button'], [purpose='submit-button'] {
border: none;
display: flex;
cursor: pointer;
height: 48px;
padding: 16px 32px;
justify-content: center;
align-items: center;
gap: 4px;
align-self: stretch;
color: #FFF;
text-align: center;
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 700;
line-height: 21px;
margin-bottom: 16px;
border-radius: 8px;
background: #192147;
position: relative;
img {
height: 28px;
margin-right: 8px;
filter: brightness(3.5);
}
&:active:focus {
box-shadow: none;
}
&:before {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%);
opacity: 1;
content: ' ';
position: absolute;
top: 0;
left: -5px;
width: 50%;
height: 100%;
transform: skew(-10deg);
transition: left 0.5s ease-in, opacity 0.50s ease-in, width 0.5s ease-in;
}
&:hover:before {
opacity: 0;
left: 160px;
width: 110%;
}
}
[purpose='generate-button'] {
position: absolute;
bottom: 12px;
right: 12px;
padding: 10px;
border-radius: 50%;
border: 1px solid #E2E4EA;
background: #192147;
cursor: pointer;
img {
filter: brightness(3.5);
height: 28px;
}
&:hover {
background: #515774;
}
}
[purpose='enforced-settings'] {
padding: 24px;
border-radius: var(--spacing-spacing-xxs, 4px);
border: 1px solid var(--border-border-primary, #E2E4EA);
background: var(--surface-surface-secondary, #F9FAFC);
margin-left: 16px;
width: 100%;
}
li {
a {
&:after {
background-image: url('/images/icon-arrow-upper-right-7x7@2x.png');
background-size: 7px 7px;
display: inline-block;
position: relative;
top: -4px;
left: 4px;
width: 7px;
height: 7px;
content: '';
}
}
}
@media(max-width: 991px) {
[purpose='page-container'] {
padding: 32px;
}
[purpose='enforced-settings'] {
margin-top: 24px;
margin-left: auto;
}
}
@media(max-width: 767px) {
[purpose='generate-button'] {
display: none;
}
[purpose='page-container'] {
padding: 32px 24px;
}
}
@media(max-width: 576px) {
[purpose='page-container'] {
padding: 32px 16px;
}
}
}
+2
View File
@@ -65,4 +65,6 @@ module.exports.policies = {
'view-app-library': true,
'view-app-details': true,
'view-meetups': true,
'view-os-settings': true,
'get-llm-generated-configuration-profile': true,
};
+10
View File
@@ -476,6 +476,15 @@ module.exports.routes = {
}
},
'GET /os-settings': {
action: 'view-os-settings',
locals: {
currentSection: 'documentation',
pageTitleForMeta: 'OS settings',
pageDescriptionForMeta: 'Generate OS settings in CSP, .mobileconfig, and DDM format',
}
},
// ╦ ╔═╗╔═╗╔═╗╔═╗╦ ╦ ╦═╗╔═╗╔╦╗╦╦═╗╔═╗╔═╗╔╦╗╔═╗
// ║ ║╣ ║ ╦╠═╣║ ╚╦╝ ╠╦╝║╣ ║║║╠╦╝║╣ ║ ║ ╚═╗
// ╩═╝╚═╝╚═╝╩ ╩╚═╝ ╩ ╩╚═╚═╝═╩╝╩╩╚═╚═╝╚═╝ ╩ ╚═╝
@@ -940,4 +949,5 @@ module.exports.routes = {
'/api/v1/unsubscribe-from-marketing-emails': { action: 'unsubscribe-from-marketing-emails' },
'POST /api/v1/customers/get-stripe-checkout-session-url': { action: 'customers/get-stripe-checkout-session-url' },
'/api/v1/query-generator/get-llm-generated-sql': { action: 'query-generator/get-llm-generated-sql' },
'POST /api/v1/get-llm-generated-configuration-profile': { action: 'get-llm-generated-configuration-profile', hasSocketFeatures: true },
};
+4
View File
@@ -448,6 +448,9 @@
<script src="/dependencies/jquery.min.js"></script>
<script src="/dependencies/vue.js"></script>
<script src="/dependencies/vue-router.js"></script>
<script src="/dependencies/ace-editor/ace-editor.js"></script>
<script src="/dependencies/ace-editor/mode-xml.js"></script>
<script src="/dependencies/ace-editor/worker-xml.js"></script>
<script src="/dependencies/bootstrap-4/bootstrap-4.bundle.js"></script>
<script src="/dependencies/chart.min.js"></script>
<script src="/dependencies/cloud.js"></script>
@@ -507,6 +510,7 @@
<script src="/js/pages/legal/terms.page.js"></script>
<script src="/js/pages/meetups.page.js"></script>
<script src="/js/pages/observability.page.js"></script>
<script src="/js/pages/os-settings.page.js"></script>
<script src="/js/pages/osquery-table-details.page.js"></script>
<script src="/js/pages/policy-details.page.js"></script>
<script src="/js/pages/policy-library.page.js"></script>
+50
View File
@@ -0,0 +1,50 @@
<div id="os-settings" v-cloak>
<div purpose="page-container">
<div purpose="page-content">
<docs-nav-and-search current-section="osSettings" ></docs-nav-and-search>
<div purpose="page-headline">
<h1>OS settings</h1>
<p>Generate OS settings in CSP, .mobileconfig, and DDM format. Just click the sparkle button.</p>
</div>
<div purpose="mobile-generate-button" class="d-flex d-md-none" @click="modal = 'generate-profile'"><img alt="Generate a configuration profile" src="/images/icon-generate-14x14@2x.png">Generate OS settings</div>
<div class="d-flex flex-lg-row flex-column mb-4">
<div purpose="profile-editor">
<div id="editor" @paste="getUpdatedValueFromEditor" @input="getUpdatedValueFromEditor" purpose="ace-editor" mode="xml" :value.sync="generatedOutput"></div>
<div purpose="generate-button" @click="modal = 'generate-profile'"><img alt="Generate a configuration profile" src="/images/icon-generate-14x14@2x.png"></div>
</div>
<div purpose="enforced-settings">
<p><strong>Settings enforced:</strong></p>
<div v-for="item in parsedItemsInProfile">
<p>{{item.name}}: {{item.value}}</p>
</div>
</div>
</div>
<p v-if="hasGeneratedProfile"><a @click="clickDownloadResult()">Download</a> this profile and deploy it using Fleet, Jamf, Intune or Airwatch.</p>
</div>
</div>
<modal v-if="modal === 'generate-profile'" @close="closeModal()">
<div purpose="modal-form">
<ajax-form :handle-submitting="handleSubmittingForm" :cloud-error.sync="cloudError" :form-errors.sync="formErrors" :form-data="formData" :form-rules="formRules">
<div class="d-flex flex-row align-items-center mb-3">
<img style="height: 24px;" alt="Generate a configuration profile" src="/images/icon-generate-14x14@2x.png">
<p class="mb-0 ml-2"><strong>What would you like to happen?</strong></p>
</div>
<div class="form-group">
<select class="form-control" style="color: #515774;" purpose="profile-type-selector" id="profile-type" :class="[formErrors.profileType ? 'is-invalid' : '']" v-model.trim="formData.profileType">
<option value="mobileconfig" selected>.mobileconfig (Apple)</option>
<option value="ddm">DDM (Apple)</option>
<option value="csp">CSP (Windows)</option>
</select>
<div class="invalid-feedback" v-if="formErrors.profileType" focus-first>Select the type of profile that you want to generate.</div>
</div>
<div class="form-group">
<textarea class="form-control" type="textarea" id="profile" placeholder="Ensure users cant change the system clock." :class="[formErrors.naturalLanguageInstructions ? 'is-invalid' : '']" v-model.trim="formData.naturalLanguageInstructions"></textarea>
<div class="invalid-feedback" v-if="formErrors.naturalLanguageInstructions" focus-first>Please provide instructions.</div>
</div>
<p v-if="cloudError" class="text-danger">Your request could not be processed. Please try again.</p>
<ajax-button purpose="submit-button" spinner="true" type="submit" :syncing="syncing" class="btn btn-primary mt-4 float-left">Generate</ajax-button>
</ajax-form>
</div>
</modal>
</div>
<%- /* Expose server-rendered data as window.SAILS_LOCALS :: */ exposeLocalsToBrowser() %>