mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-04 08:00:44 +00:00
4d401d7f20
* 1.18.1 * improve stack header / all resource links * disable build config selector * clean up deployment header * update build header * builder header * update repo header * start adding repo links from api * implement list item repo link * clean up fe * gen client * repo links across the board * include state tracking buffer, so alerts are only triggered by consecutive out of bounds conditions * add runnables-cli link in runfile * improve frontend first load time through some code splitting * add services count to stack header * fix repo on pull * Add dedicated Deploying state to Deployments and Stacks * move predeploy script before compose config (#584) * Periphery / core version mismatch check / red text * move builders / alerts out of sidebar, into settings * remove force push * list schedules api * dev-1 * actually dev-3 * fix action * filter none procedures * fix schedule api * dev-5 * basic schedules page * prog on schedule page * simplify schedule * use name to sort target * add resource tags to schedule * Schedule page working * dev-6 * remove schedule table type column * reorder schedule table * force confirm dialogs for delete, even if disabled in config * 1.18.1 --------- Co-authored-by: undaunt <31376520+undaunt@users.noreply.github.com>
272 lines
7.3 KiB
TypeScript
272 lines
7.3 KiB
TypeScript
import { ResourceComponents } from "@components/resources";
|
|
import { Types } from "komodo_client";
|
|
import { UsableResource } from "@types";
|
|
import Convert from "ansi-to-html";
|
|
import { type ClassValue, clsx } from "clsx";
|
|
import sanitizeHtml from "sanitize-html";
|
|
import { twMerge } from "tailwind-merge";
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
return twMerge(clsx(inputs));
|
|
}
|
|
|
|
export const object_keys = <T extends object>(o: T): (keyof T)[] =>
|
|
Object.keys(o) as (keyof T)[];
|
|
|
|
export const RESOURCE_TARGETS: UsableResource[] = [
|
|
"Server",
|
|
"Stack",
|
|
"Deployment",
|
|
"Build",
|
|
"Repo",
|
|
"Procedure",
|
|
"Action",
|
|
"Builder",
|
|
"Alerter",
|
|
"ResourceSync",
|
|
];
|
|
|
|
export const SETTINGS_RESOURCES: UsableResource[] = ["Builder", "Alerter"];
|
|
|
|
export const SIDEBAR_RESOURCES: UsableResource[] = RESOURCE_TARGETS.filter(
|
|
(target) => !SETTINGS_RESOURCES.includes(target)
|
|
);
|
|
|
|
export function env_to_text(envVars: Types.EnvironmentVar[] | undefined) {
|
|
return envVars?.reduce(
|
|
(prev, { variable, value }) =>
|
|
prev + (prev ? "\n" : "") + `${variable}: ${value}`,
|
|
""
|
|
);
|
|
}
|
|
|
|
export function text_to_env(env: string): Types.EnvironmentVar[] {
|
|
return env
|
|
.split("\n")
|
|
.filter((line) => keep_line(line))
|
|
.map((entry) => {
|
|
const [first, ...rest] = entry.replaceAll('"', "").split("=");
|
|
return [first, rest.join("=")];
|
|
})
|
|
.map(([variable, value]) => ({ variable, value }));
|
|
}
|
|
|
|
function keep_line(line: string) {
|
|
if (line.length === 0) return false;
|
|
let firstIndex = -1;
|
|
for (let i = 0; i < line.length; i++) {
|
|
if (line[i] !== " ") {
|
|
firstIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (firstIndex === -1) return false;
|
|
if (line[firstIndex] === "#") return false;
|
|
return true;
|
|
}
|
|
|
|
export function parse_key_value(
|
|
input: string
|
|
): Array<{ key: string; value: string }> {
|
|
const trimmed = input.trim();
|
|
if (trimmed.length === 0) return [];
|
|
return trimmed
|
|
.split("\n")
|
|
.map((line) => line.trim())
|
|
.filter(
|
|
(line) =>
|
|
line.length > 0 && !line.startsWith("#") && !line.startsWith("//")
|
|
)
|
|
.map((line) => {
|
|
const no_comment = line.split(" #", 1)[0].trim();
|
|
const no_dash = no_comment.startsWith("-")
|
|
? no_comment.slice(1).trim()
|
|
: no_comment;
|
|
const no_leading_quote = no_dash.startsWith('"')
|
|
? no_dash.slice(1)
|
|
: no_dash;
|
|
const no_trailing_quote = no_leading_quote.endsWith('"')
|
|
? no_leading_quote.slice(0, -1)
|
|
: no_leading_quote;
|
|
const res = no_trailing_quote.split(/[=: ]/, 1);
|
|
const [key, value] = [res[0]?.trim() ?? "", res[1]?.trim() ?? ""];
|
|
const value_no_leading_quote = value.startsWith('"')
|
|
? value.slice(1)
|
|
: value;
|
|
const value_no_trailing_quote = value_no_leading_quote.endsWith('"')
|
|
? value_no_leading_quote.slice(0, -1)
|
|
: value_no_leading_quote;
|
|
return { key, value: value_no_trailing_quote.trim() };
|
|
});
|
|
}
|
|
|
|
export function version_is_none(version?: Types.Version) {
|
|
if (!version) return true;
|
|
return version.major === 0 && version.minor === 0 && version.patch === 0;
|
|
}
|
|
|
|
export function resource_name(type: UsableResource, id: string) {
|
|
const Components = ResourceComponents[type];
|
|
return Components.list_item(id)?.name;
|
|
}
|
|
|
|
export const level_to_number = (level: Types.PermissionLevel | undefined) => {
|
|
switch (level) {
|
|
case undefined:
|
|
return 0;
|
|
case Types.PermissionLevel.None:
|
|
return 0;
|
|
case Types.PermissionLevel.Read:
|
|
return 1;
|
|
case Types.PermissionLevel.Execute:
|
|
return 2;
|
|
case Types.PermissionLevel.Write:
|
|
return 3;
|
|
}
|
|
};
|
|
|
|
export const has_minimum_permissions = (
|
|
permission: Types.PermissionLevelAndSpecifics | undefined,
|
|
greater_than: Types.PermissionLevel,
|
|
specific?: Types.SpecificPermission[]
|
|
) => {
|
|
if (!permission) return false;
|
|
if (level_to_number(permission.level) < level_to_number(greater_than))
|
|
return false;
|
|
if (!specific) return true;
|
|
for (const s of specific) {
|
|
if (!permission.specific.includes(s)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
const tzOffsetMs = new Date().getTimezoneOffset() * 60 * 1000;
|
|
|
|
export const convertTsMsToLocalUnixTsInMs = (ts: number) => ts - tzOffsetMs;
|
|
|
|
export const usableResourcePath = (resource: UsableResource) => {
|
|
if (resource === "ResourceSync") return "resource-syncs";
|
|
return `${resource.toLowerCase()}s`;
|
|
};
|
|
|
|
export const usableResourceExecuteKey = (resource: UsableResource) => {
|
|
if (resource === "ResourceSync") return "sync";
|
|
return `${resource.toLowerCase()}`;
|
|
};
|
|
|
|
export const sanitizeOnlySpan = (log: string) => {
|
|
return sanitizeHtml(log, {
|
|
allowedTags: ["span"],
|
|
allowedAttributes: {
|
|
span: ["class"],
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Converts the ansi colors in an Update log to html.
|
|
* sanitizes incoming log first for any eg. script tags.
|
|
* @param log incoming log string
|
|
*/
|
|
export const updateLogToHtml = (log: string) => {
|
|
if (!log) return "No log.";
|
|
return convert.toHtml(sanitizeOnlySpan(log));
|
|
};
|
|
|
|
const convert = new Convert();
|
|
/**
|
|
* Converts the ansi colors in log to html.
|
|
* sanitizes incoming log first for any eg. script tags.
|
|
* @param log incoming log string
|
|
*/
|
|
export const logToHtml = (log: string) => {
|
|
if (!log) return "No log.";
|
|
const sanitized = sanitizeHtml(log, {
|
|
allowedTags: sanitizeHtml.defaults.allowedTags.filter(
|
|
(tag) => tag !== "script"
|
|
),
|
|
allowedAttributes: sanitizeHtml.defaults.allowedAttributes,
|
|
});
|
|
return convert.toHtml(sanitized);
|
|
};
|
|
|
|
export const getUpdateQuery = (
|
|
target: Types.ResourceTarget,
|
|
deployments: Types.DeploymentListItem[] | undefined
|
|
) => {
|
|
const build_id =
|
|
target.type === "Deployment"
|
|
? deployments?.find((d) => d.id === target.id)?.info.build_id
|
|
: undefined;
|
|
if (build_id) {
|
|
return {
|
|
$or: [
|
|
{
|
|
"target.type": target.type,
|
|
"target.id": target.id,
|
|
},
|
|
{
|
|
"target.type": "Build",
|
|
"target.id": build_id,
|
|
operation: {
|
|
$in: [Types.Operation.RunBuild, Types.Operation.CancelBuild],
|
|
},
|
|
},
|
|
],
|
|
};
|
|
} else {
|
|
return {
|
|
"target.type": target.type,
|
|
"target.id": target.id,
|
|
};
|
|
}
|
|
};
|
|
|
|
export const filterBySplit = <T>(
|
|
items: T[] | undefined,
|
|
search: string,
|
|
extract: (item: T) => string
|
|
) => {
|
|
const split = search.toLowerCase().split(" ");
|
|
return (
|
|
(split.length
|
|
? items?.filter((item) => {
|
|
const target = extract(item).toLowerCase();
|
|
return split.every((term) => target.includes(term));
|
|
})
|
|
: items) ?? []
|
|
);
|
|
};
|
|
|
|
export const sync_no_changes = (sync: Types.ResourceSync) => {
|
|
return (
|
|
(sync.info?.pending_deploy?.to_deploy ?? 0) === 0 &&
|
|
(sync.info?.resource_updates?.length ?? 0) === 0 &&
|
|
(sync.info?.variable_updates?.length ?? 0) === 0 &&
|
|
(sync.info?.user_group_updates?.length ?? 0) === 0
|
|
);
|
|
};
|
|
|
|
export const extract_registry_domain = (image_name: string) => {
|
|
if (!image_name) return "docker.io";
|
|
const maybe_domain = image_name.split("/")[0];
|
|
if (maybe_domain.includes(".")) {
|
|
return maybe_domain;
|
|
} else {
|
|
return "docker.io";
|
|
}
|
|
};
|
|
|
|
/** Checks file contents empty, not including whitespace / comments */
|
|
export const file_contents_empty = (contents?: string) => {
|
|
if (!contents) return true;
|
|
return (
|
|
contents
|
|
.split("\n")
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.length !== 0 && !line.startsWith("#")).length === 0
|
|
);
|
|
};
|