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:
@@ -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
@@ -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 {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
Vendored
+1
@@ -50,6 +50,7 @@
|
||||
"Chart": true,
|
||||
"gtag": true,
|
||||
"analytics": true,
|
||||
"ace": true,
|
||||
// ...etc.
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
+26525
File diff suppressed because one or more lines are too long
+477
@@ -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;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
+3122
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 447 B |
Vendored
+1
-1
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
@@ -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);
|
||||
},
|
||||
}
|
||||
});
|
||||
Vendored
+1
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
+2
@@ -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,
|
||||
};
|
||||
|
||||
Vendored
+10
@@ -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 },
|
||||
};
|
||||
|
||||
Vendored
+4
@@ -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>
|
||||
|
||||
Vendored
+50
@@ -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 can’t 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() %>
|
||||
Reference in New Issue
Block a user