Skip to content
This repository was archived by the owner on Apr 13, 2025. It is now read-only.

Automatically set instances with no required config #211

Merged
merged 2 commits into from
Mar 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 48 additions & 31 deletions nodecg-io-core/dashboard/serviceInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { config, sendAuthenticatedMessage } from "./crypto";

const editorDefaultText = "<---- Select a service instance to start editing it in here";
const editorCreateText = "<---- Create a new service instance on the left and then you can edit it in here";
const editorInvalidServiceText = "!!!!! Service of this instance couldn't be found.";
const editorNotConfigurableText = "----- This service cannot be configured.";

document.addEventListener("DOMContentLoaded", () => {
config.onChange(() => {
Expand Down Expand Up @@ -59,18 +61,12 @@ export function onInstanceSelectChange(value: string): void {
showNotice(undefined);
switch (value) {
case "new":
editor?.updateOptions({
readOnly: true,
});
editor?.setModel(monaco.editor.createModel(editorCreateText, "text"));
showInMonaco("text", true, editorCreateText);
setCreateInputs(true, false);
inputInstanceName.value = "";
break;
case "select":
editor?.updateOptions({
readOnly: true,
});
editor?.setModel(monaco.editor.createModel(editorDefaultText, "text"));
showInMonaco("text", true, editorDefaultText);
setCreateInputs(false, false);
break;
default:
Expand All @@ -82,30 +78,15 @@ function showConfig(value: string) {
const inst = config.data?.instances[value];
const service = config.data?.services.find((svc) => svc.serviceType === inst?.serviceType);

editor?.updateOptions({
readOnly: false,
});

// Get rid of old models, as they have to be unique and we may add the same again
monaco.editor.getModels().forEach((m) => m.dispose());
if (!service) {
showInMonaco("text", true, editorInvalidServiceText);
} else if (service.requiresNoConfig) {
showInMonaco("text", true, editorNotConfigurableText);
} else {
const jsonString = JSON.stringify(inst?.config || {}, null, 4);
showInMonaco("json", false, jsonString, service?.schema);
}

// This model uri can be completely made up as long the uri in the schema matches with the one in the language model.
const modelUri = monaco.Uri.parse(`mem://nodecg-io/${inst?.serviceType}.json`);
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: service?.schema !== undefined,
schemas:
service?.schema !== undefined
? [
{
uri: modelUri.toString(),
fileMatch: [modelUri.toString()],
schema: objectDeepCopy(service?.schema),
},
]
: [],
});
const model = monaco.editor.createModel(JSON.stringify(inst?.config || {}, null, 4), "json", modelUri);
editor?.setModel(model);
setCreateInputs(false, true);
}

Expand Down Expand Up @@ -246,3 +227,39 @@ export function showNotice(msg: string | undefined): void {
spanInstanceNotice.innerText = msg !== undefined ? msg : "";
}
}

function showInMonaco(
type: "text" | "json",
readOnly: boolean,
content: string,
schema?: Record<string, unknown>,
): void {
editor?.updateOptions({ readOnly });

// JSON Schema stuff
// Get rid of old models, as they have to be unique and we may add the same again
monaco.editor.getModels().forEach((m) => m.dispose());

// This model uri can be completely made up as long the uri in the schema matches with the one in the language model.
const modelUri = monaco.Uri.parse(`mem://nodecg-io/selectedServiceSchema.json`);

monaco.languages.json.jsonDefaults.setDiagnosticsOptions(
schema
? {
validate: true,
schemas: [
{
uri: modelUri.toString(),
fileMatch: [modelUri.toString()],
schema: objectDeepCopy(schema),
},
],
}
: {
validate: false, // if not set we disable validation again.
schemas: [],
},
);

editor?.setModel(monaco.editor.createModel(content, type));
}
2 changes: 1 addition & 1 deletion nodecg-io-core/extension/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports = (nodecg: NodeCG): NodeCGIOCore => {
const serviceManager = new ServiceManager(nodecg);
const bundleManager = new BundleManager(nodecg);
const instanceManager = new InstanceManager(nodecg, serviceManager, bundleManager);
const persistenceManager = new PersistenceManager(nodecg, instanceManager, bundleManager);
const persistenceManager = new PersistenceManager(nodecg, serviceManager, instanceManager, bundleManager);

new MessageManager(
nodecg,
Expand Down
11 changes: 9 additions & 2 deletions nodecg-io-core/extension/instanceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,23 @@ export class InstanceManager extends EventEmitter {
const service = svcResult.result;

// Create actual instance and save it
this.serviceInstances[instanceName] = {
const inst = {
serviceType: service.serviceType,
config: service.defaultConfig,
client: undefined,
};
this.serviceInstances[instanceName] = inst;
this.emit("change");

this.nodecg.log.info(
`Service instance "${instanceName}" of service "${service.serviceType}" has been successfully created.`,
);

// Service requires no config, we can create it right now.
if (service.requiresNoConfig) {
this.updateInstanceClient(inst, instanceName, service);
}

return emptySuccess();
}

Expand Down Expand Up @@ -145,7 +152,7 @@ export class InstanceManager extends EventEmitter {
return error("The service of this instance couldn't be found.");
}

if (validation) {
if (validation || !service.result.requiresNoConfig) {
const schemaValid = this.ajv.validate(service.result.schema, config);
if (!schemaValid) {
return error("Config invalid: " + this.ajv.errorsText());
Expand Down
7 changes: 7 additions & 0 deletions nodecg-io-core/extension/persistenceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BundleManager } from "./bundleManager";
import * as crypto from "crypto-js";
import { emptySuccess, error, Result, success } from "./utils/result";
import { ObjectMap, ServiceDependency, ServiceInstance } from "./types";
import { ServiceManager } from "./serviceManager";

/**
* Models all the data that needs to be persistent in a plain manner.
Expand Down Expand Up @@ -58,6 +59,7 @@ export class PersistenceManager {

constructor(
private readonly nodecg: NodeCG,
private readonly services: ServiceManager,
private readonly instances: InstanceManager,
private readonly bundles: BundleManager,
) {
Expand Down Expand Up @@ -158,6 +160,11 @@ export class PersistenceManager {
continue;
}

const svc = this.services.getService(inst.serviceType);
if (!svc.failed && svc.result.requiresNoConfig) {
continue;
}

// Re-set config of this instance.
// We can skip the validation here because the config was already validated when it was initially set,
// before getting saved to disk.
Expand Down
9 changes: 8 additions & 1 deletion nodecg-io-core/extension/serviceBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,17 @@ export abstract class ServiceBundle<R, C> implements Service<R, C> {
* It gets rid of the handlers by stopping the client and creating a new one, to which then only the
* now wanted handlers get registered (e.g. if a bundle doesn't uses this service anymore but another still does).
* Not ideal, but if your service can't implement removeHandlers for some reason it is still better than
* having dangling handlers that still fire eventho they shouldn't.
* having dangling handlers that still fire events eventho they shouldn't.
*/
reCreateClientToRemoveHandlers = false;

/**
* This flag says that this service cannot be configured and doesn't need any config passed to {@link createClient}.
* If this is set {@link validateConfig} will never be called.
* @default false
*/
requiresNoConfig = false;

private readSchema(pathSegments: string[]): unknown {
const joinedPath = path.resolve(...pathSegments);
try {
Expand Down
10 changes: 9 additions & 1 deletion nodecg-io-core/extension/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,22 @@ export interface Service<R, C extends ServiceClient<unknown>> {
readonly removeHandlers?(client: C): void;

/**
* This flag can be enabled by services if they can't implement removeHandlers but also have some handlers that
* This flag can be enabled by services if they can't implement {@link removeHandlers} but also have some handlers that
* should be reset if a bundleDependency has been changed.
* It gets rid of the handlers by stopping the client and creating a new one, to which then only the
* now wanted handlers get registered (e.g. if a bundle doesn't uses this service anymore but another still does).
* Not ideal, but if your service can't implement removeHandlers for some reason it is still better than
* having dangling handlers that still fire eventho they shouldn't.
* @default false
*/
reCreateClientToRemoveHandlers: boolean;

/**
* This flag says that this service cannot be configured and doesn't need any config passed to {@link createClient}.
* If this is set {@link validateConfig} will never be called.
* @default false
*/
requiresNoConfig: boolean;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions nodecg-io-curseforge/extension/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@ class CurseforgeService extends ServiceBundle<never, CurseForgeClient> {
stopClient(_: CurseForgeClient): void {
this.nodecg.log.info("Successfully stopped CurseForge client.");
}

requiresNoConfig = true;
}