= T extends T ? keyof T : never;
+export type Exact = P extends Builtin ? P
+ : P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
+
+if (_m0.util.Long !== Long) {
+ _m0.util.Long = Long as any;
+ _m0.configure();
+}
+
+function isObject(value: any): boolean {
+ return typeof value === "object" && value !== null;
+}
+
+function isSet(value: any): boolean {
+ return value !== null && value !== undefined;
+}
+
+export type ServerStreamingMethodResult = { [Symbol.asyncIterator](): AsyncIterator };
diff --git a/front/src/proto/fileserver/api.ts b/front/src/proto/fileserver/api.ts
new file mode 100644
index 0000000..0220bec
--- /dev/null
+++ b/front/src/proto/fileserver/api.ts
@@ -0,0 +1,218 @@
+// @ts-nocheck
+/* eslint-disable */
+import Long from "long";
+import type { CallContext, CallOptions } from "nice-grpc-common";
+import _m0 from "protobufjs/minimal";
+
+export const protobufPackage = "fileserver";
+
+export interface FileInfo {
+ uuid: string;
+}
+
+export interface FileStream {
+ chunk: Uint8Array;
+}
+
+function createBaseFileInfo(): FileInfo {
+ return { uuid: "" };
+}
+
+export const FileInfo = {
+ encode(message: FileInfo, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ if (message.uuid !== "") {
+ writer.uint32(10).string(message.uuid);
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): FileInfo {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseFileInfo();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.uuid = reader.string();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(object: any): FileInfo {
+ return { uuid: isSet(object.uuid) ? globalThis.String(object.uuid) : "" };
+ },
+
+ toJSON(message: FileInfo): unknown {
+ const obj: any = {};
+ if (message.uuid !== "") {
+ obj.uuid = message.uuid;
+ }
+ return obj;
+ },
+
+ create, I>>(base?: I): FileInfo {
+ return FileInfo.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(object: I): FileInfo {
+ const message = createBaseFileInfo();
+ message.uuid = object.uuid ?? "";
+ return message;
+ },
+};
+
+function createBaseFileStream(): FileStream {
+ return { chunk: new Uint8Array(0) };
+}
+
+export const FileStream = {
+ encode(message: FileStream, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ if (message.chunk.length !== 0) {
+ writer.uint32(10).bytes(message.chunk);
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): FileStream {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseFileStream();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.chunk = reader.bytes();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(object: any): FileStream {
+ return { chunk: isSet(object.chunk) ? bytesFromBase64(object.chunk) : new Uint8Array(0) };
+ },
+
+ toJSON(message: FileStream): unknown {
+ const obj: any = {};
+ if (message.chunk.length !== 0) {
+ obj.chunk = base64FromBytes(message.chunk);
+ }
+ return obj;
+ },
+
+ create, I>>(base?: I): FileStream {
+ return FileStream.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(object: I): FileStream {
+ const message = createBaseFileStream();
+ message.chunk = object.chunk ?? new Uint8Array(0);
+ return message;
+ },
+};
+
+export type ServiceDefinition = typeof ServiceDefinition;
+export const ServiceDefinition = {
+ name: "Service",
+ fullName: "fileserver.Service",
+ methods: {
+ uploadFile: {
+ name: "UploadFile",
+ requestType: FileStream,
+ requestStream: true,
+ responseType: FileInfo,
+ responseStream: false,
+ options: {},
+ },
+ downloadFile: {
+ name: "DownloadFile",
+ requestType: FileInfo,
+ requestStream: false,
+ responseType: FileStream,
+ responseStream: true,
+ options: {},
+ },
+ },
+} as const;
+
+export interface ServiceImplementation {
+ uploadFile(request: AsyncIterable, context: CallContext & CallContextExt): Promise>;
+ downloadFile(
+ request: FileInfo,
+ context: CallContext & CallContextExt,
+ ): ServerStreamingMethodResult>;
+}
+
+export interface ServiceClient {
+ uploadFile(
+ request: AsyncIterable>,
+ options?: CallOptions & CallOptionsExt,
+ ): Promise;
+ downloadFile(request: DeepPartial, options?: CallOptions & CallOptionsExt): AsyncIterable;
+}
+
+function bytesFromBase64(b64: string): Uint8Array {
+ if (globalThis.Buffer) {
+ return Uint8Array.from(globalThis.Buffer.from(b64, "base64"));
+ } else {
+ const bin = globalThis.atob(b64);
+ const arr = new Uint8Array(bin.length);
+ for (let i = 0; i < bin.length; ++i) {
+ arr[i] = bin.charCodeAt(i);
+ }
+ return arr;
+ }
+}
+
+function base64FromBytes(arr: Uint8Array): string {
+ if (globalThis.Buffer) {
+ return globalThis.Buffer.from(arr).toString("base64");
+ } else {
+ const bin: string[] = [];
+ arr.forEach((byte) => {
+ bin.push(globalThis.String.fromCharCode(byte));
+ });
+ return globalThis.btoa(bin.join(""));
+ }
+}
+
+type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
+
+export type DeepPartial = T extends Builtin ? T
+ : T extends Long ? string | number | Long : T extends globalThis.Array ? globalThis.Array>
+ : T extends ReadonlyArray ? ReadonlyArray>
+ : T extends { $case: string } ? { [K in keyof Omit]?: DeepPartial } & { $case: T["$case"] }
+ : T extends {} ? { [K in keyof T]?: DeepPartial }
+ : Partial;
+
+type KeysOfUnion = T extends T ? keyof T : never;
+export type Exact = P extends Builtin ? P
+ : P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
+
+if (_m0.util.Long !== Long) {
+ _m0.util.Long = Long as any;
+ _m0.configure();
+}
+
+function isSet(value: any): boolean {
+ return value !== null && value !== undefined;
+}
+
+export type ServerStreamingMethodResult = { [Symbol.asyncIterator](): AsyncIterator };
diff --git a/front/src/proto/google/protobuf/duration.ts b/front/src/proto/google/protobuf/duration.ts
new file mode 100644
index 0000000..56fe8f3
--- /dev/null
+++ b/front/src/proto/google/protobuf/duration.ts
@@ -0,0 +1,182 @@
+// @ts-nocheck
+/* eslint-disable */
+import Long from "long";
+import _m0 from "protobufjs/minimal";
+
+export const protobufPackage = "google.protobuf";
+
+/**
+ * A Duration represents a signed, fixed-length span of time represented
+ * as a count of seconds and fractions of seconds at nanosecond
+ * resolution. It is independent of any calendar and concepts like "day"
+ * or "month". It is related to Timestamp in that the difference between
+ * two Timestamp values is a Duration and it can be added or subtracted
+ * from a Timestamp. Range is approximately +-10,000 years.
+ *
+ * # Examples
+ *
+ * Example 1: Compute Duration from two Timestamps in pseudo code.
+ *
+ * Timestamp start = ...;
+ * Timestamp end = ...;
+ * Duration duration = ...;
+ *
+ * duration.seconds = end.seconds - start.seconds;
+ * duration.nanos = end.nanos - start.nanos;
+ *
+ * if (duration.seconds < 0 && duration.nanos > 0) {
+ * duration.seconds += 1;
+ * duration.nanos -= 1000000000;
+ * } else if (duration.seconds > 0 && duration.nanos < 0) {
+ * duration.seconds -= 1;
+ * duration.nanos += 1000000000;
+ * }
+ *
+ * Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
+ *
+ * Timestamp start = ...;
+ * Duration duration = ...;
+ * Timestamp end = ...;
+ *
+ * end.seconds = start.seconds + duration.seconds;
+ * end.nanos = start.nanos + duration.nanos;
+ *
+ * if (end.nanos < 0) {
+ * end.seconds -= 1;
+ * end.nanos += 1000000000;
+ * } else if (end.nanos >= 1000000000) {
+ * end.seconds += 1;
+ * end.nanos -= 1000000000;
+ * }
+ *
+ * Example 3: Compute Duration from datetime.timedelta in Python.
+ *
+ * td = datetime.timedelta(days=3, minutes=10)
+ * duration = Duration()
+ * duration.FromTimedelta(td)
+ *
+ * # JSON Mapping
+ *
+ * In JSON format, the Duration type is encoded as a string rather than an
+ * object, where the string ends in the suffix "s" (indicating seconds) and
+ * is preceded by the number of seconds, with nanoseconds expressed as
+ * fractional seconds. For example, 3 seconds with 0 nanoseconds should be
+ * encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
+ * be expressed in JSON format as "3.000000001s", and 3 seconds and 1
+ * microsecond should be expressed in JSON format as "3.000001s".
+ */
+export interface Duration {
+ /**
+ * Signed seconds of the span of time. Must be from -315,576,000,000
+ * to +315,576,000,000 inclusive. Note: these bounds are computed from:
+ * 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
+ */
+ seconds: Long;
+ /**
+ * Signed fractions of a second at nanosecond resolution of the span
+ * of time. Durations less than one second are represented with a 0
+ * `seconds` field and a positive or negative `nanos` field. For durations
+ * of one second or more, a non-zero value for the `nanos` field must be
+ * of the same sign as the `seconds` field. Must be from -999,999,999
+ * to +999,999,999 inclusive.
+ */
+ nanos: number;
+}
+
+function createBaseDuration(): Duration {
+ return { seconds: Long.ZERO, nanos: 0 };
+}
+
+export const Duration = {
+ encode(message: Duration, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ if (!message.seconds.isZero()) {
+ writer.uint32(8).int64(message.seconds);
+ }
+ if (message.nanos !== 0) {
+ writer.uint32(16).int32(message.nanos);
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): Duration {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseDuration();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 8) {
+ break;
+ }
+
+ message.seconds = reader.int64() as Long;
+ continue;
+ case 2:
+ if (tag !== 16) {
+ break;
+ }
+
+ message.nanos = reader.int32();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(object: any): Duration {
+ return {
+ seconds: isSet(object.seconds) ? Long.fromValue(object.seconds) : Long.ZERO,
+ nanos: isSet(object.nanos) ? globalThis.Number(object.nanos) : 0,
+ };
+ },
+
+ toJSON(message: Duration): unknown {
+ const obj: any = {};
+ if (!message.seconds.isZero()) {
+ obj.seconds = (message.seconds || Long.ZERO).toString();
+ }
+ if (message.nanos !== 0) {
+ obj.nanos = Math.round(message.nanos);
+ }
+ return obj;
+ },
+
+ create, I>>(base?: I): Duration {
+ return Duration.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(object: I): Duration {
+ const message = createBaseDuration();
+ message.seconds = (object.seconds !== undefined && object.seconds !== null)
+ ? Long.fromValue(object.seconds)
+ : Long.ZERO;
+ message.nanos = object.nanos ?? 0;
+ return message;
+ },
+};
+
+type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
+
+export type DeepPartial = T extends Builtin ? T
+ : T extends Long ? string | number | Long : T extends globalThis.Array ? globalThis.Array>
+ : T extends ReadonlyArray ? ReadonlyArray>
+ : T extends { $case: string } ? { [K in keyof Omit]?: DeepPartial } & { $case: T["$case"] }
+ : T extends {} ? { [K in keyof T]?: DeepPartial }
+ : Partial;
+
+type KeysOfUnion = T extends T ? keyof T : never;
+export type Exact = P extends Builtin ? P
+ : P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
+
+if (_m0.util.Long !== Long) {
+ _m0.util.Long = Long as any;
+ _m0.configure();
+}
+
+function isSet(value: any): boolean {
+ return value !== null && value !== undefined;
+}
diff --git a/front/src/proto/google/protobuf/empty.ts b/front/src/proto/google/protobuf/empty.ts
new file mode 100644
index 0000000..cd73836
--- /dev/null
+++ b/front/src/proto/google/protobuf/empty.ts
@@ -0,0 +1,79 @@
+// @ts-nocheck
+/* eslint-disable */
+import Long from "long";
+import _m0 from "protobufjs/minimal";
+
+export const protobufPackage = "google.protobuf";
+
+/**
+ * A generic empty message that you can re-use to avoid defining duplicated
+ * empty messages in your APIs. A typical example is to use it as the request
+ * or the response type of an API method. For instance:
+ *
+ * service Foo {
+ * rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
+ * }
+ */
+export interface Empty {
+}
+
+function createBaseEmpty(): Empty {
+ return {};
+}
+
+export const Empty = {
+ encode(_: Empty, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): Empty {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseEmpty();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(_: any): Empty {
+ return {};
+ },
+
+ toJSON(_: Empty): unknown {
+ const obj: any = {};
+ return obj;
+ },
+
+ create, I>>(base?: I): Empty {
+ return Empty.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(_: I): Empty {
+ const message = createBaseEmpty();
+ return message;
+ },
+};
+
+type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
+
+export type DeepPartial = T extends Builtin ? T
+ : T extends Long ? string | number | Long : T extends globalThis.Array ? globalThis.Array>
+ : T extends ReadonlyArray ? ReadonlyArray>
+ : T extends { $case: string } ? { [K in keyof Omit]?: DeepPartial } & { $case: T["$case"] }
+ : T extends {} ? { [K in keyof T]?: DeepPartial }
+ : Partial;
+
+type KeysOfUnion = T extends T ? keyof T : never;
+export type Exact = P extends Builtin ? P
+ : P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
+
+if (_m0.util.Long !== Long) {
+ _m0.util.Long = Long as any;
+ _m0.configure();
+}
diff --git a/front/src/proto/google/protobuf/timestamp.ts b/front/src/proto/google/protobuf/timestamp.ts
new file mode 100644
index 0000000..0947940
--- /dev/null
+++ b/front/src/proto/google/protobuf/timestamp.ts
@@ -0,0 +1,211 @@
+// @ts-nocheck
+/* eslint-disable */
+import Long from "long";
+import _m0 from "protobufjs/minimal";
+
+export const protobufPackage = "google.protobuf";
+
+/**
+ * A Timestamp represents a point in time independent of any time zone or local
+ * calendar, encoded as a count of seconds and fractions of seconds at
+ * nanosecond resolution. The count is relative to an epoch at UTC midnight on
+ * January 1, 1970, in the proleptic Gregorian calendar which extends the
+ * Gregorian calendar backwards to year one.
+ *
+ * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+ * second table is needed for interpretation, using a [24-hour linear
+ * smear](https://developers.google.com/time/smear).
+ *
+ * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+ * restricting to that range, we ensure that we can convert to and from [RFC
+ * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+ *
+ * # Examples
+ *
+ * Example 1: Compute Timestamp from POSIX `time()`.
+ *
+ * Timestamp timestamp;
+ * timestamp.set_seconds(time(NULL));
+ * timestamp.set_nanos(0);
+ *
+ * Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+ *
+ * struct timeval tv;
+ * gettimeofday(&tv, NULL);
+ *
+ * Timestamp timestamp;
+ * timestamp.set_seconds(tv.tv_sec);
+ * timestamp.set_nanos(tv.tv_usec * 1000);
+ *
+ * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+ *
+ * FILETIME ft;
+ * GetSystemTimeAsFileTime(&ft);
+ * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+ *
+ * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+ * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+ * Timestamp timestamp;
+ * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+ * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+ *
+ * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+ *
+ * long millis = System.currentTimeMillis();
+ *
+ * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+ * .setNanos((int) ((millis % 1000) * 1000000)).build();
+ *
+ * Example 5: Compute Timestamp from Java `Instant.now()`.
+ *
+ * Instant now = Instant.now();
+ *
+ * Timestamp timestamp =
+ * Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+ * .setNanos(now.getNano()).build();
+ *
+ * Example 6: Compute Timestamp from current time in Python.
+ *
+ * timestamp = Timestamp()
+ * timestamp.GetCurrentTime()
+ *
+ * # JSON Mapping
+ *
+ * In JSON format, the Timestamp type is encoded as a string in the
+ * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+ * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+ * where {year} is always expressed using four digits while {month}, {day},
+ * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+ * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+ * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+ * is required. A proto3 JSON serializer should always use UTC (as indicated by
+ * "Z") when printing the Timestamp type and a proto3 JSON parser should be
+ * able to accept both UTC and other timezones (as indicated by an offset).
+ *
+ * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+ * 01:30 UTC on January 15, 2017.
+ *
+ * In JavaScript, one can convert a Date object to this format using the
+ * standard
+ * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+ * method. In Python, a standard `datetime.datetime` object can be converted
+ * to this format using
+ * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
+ * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
+ * the Joda Time's [`ISODateTimeFormat.dateTime()`](
+ * http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
+ * ) to obtain a formatter capable of generating timestamps in this format.
+ */
+export interface Timestamp {
+ /**
+ * Represents seconds of UTC time since Unix epoch
+ * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
+ * 9999-12-31T23:59:59Z inclusive.
+ */
+ seconds: Long;
+ /**
+ * Non-negative fractions of a second at nanosecond resolution. Negative
+ * second values with fractions must still have non-negative nanos values
+ * that count forward in time. Must be from 0 to 999,999,999
+ * inclusive.
+ */
+ nanos: number;
+}
+
+function createBaseTimestamp(): Timestamp {
+ return { seconds: Long.ZERO, nanos: 0 };
+}
+
+export const Timestamp = {
+ encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ if (!message.seconds.isZero()) {
+ writer.uint32(8).int64(message.seconds);
+ }
+ if (message.nanos !== 0) {
+ writer.uint32(16).int32(message.nanos);
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): Timestamp {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseTimestamp();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 8) {
+ break;
+ }
+
+ message.seconds = reader.int64() as Long;
+ continue;
+ case 2:
+ if (tag !== 16) {
+ break;
+ }
+
+ message.nanos = reader.int32();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(object: any): Timestamp {
+ return {
+ seconds: isSet(object.seconds) ? Long.fromValue(object.seconds) : Long.ZERO,
+ nanos: isSet(object.nanos) ? globalThis.Number(object.nanos) : 0,
+ };
+ },
+
+ toJSON(message: Timestamp): unknown {
+ const obj: any = {};
+ if (!message.seconds.isZero()) {
+ obj.seconds = (message.seconds || Long.ZERO).toString();
+ }
+ if (message.nanos !== 0) {
+ obj.nanos = Math.round(message.nanos);
+ }
+ return obj;
+ },
+
+ create, I>>(base?: I): Timestamp {
+ return Timestamp.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(object: I): Timestamp {
+ const message = createBaseTimestamp();
+ message.seconds = (object.seconds !== undefined && object.seconds !== null)
+ ? Long.fromValue(object.seconds)
+ : Long.ZERO;
+ message.nanos = object.nanos ?? 0;
+ return message;
+ },
+};
+
+type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
+
+export type DeepPartial = T extends Builtin ? T
+ : T extends Long ? string | number | Long : T extends globalThis.Array ? globalThis.Array>
+ : T extends ReadonlyArray ? ReadonlyArray>
+ : T extends { $case: string } ? { [K in keyof Omit]?: DeepPartial } & { $case: T["$case"] }
+ : T extends {} ? { [K in keyof T]?: DeepPartial }
+ : Partial;
+
+type KeysOfUnion = T extends T ? keyof T : never;
+export type Exact = P extends Builtin ? P
+ : P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
+
+if (_m0.util.Long !== Long) {
+ _m0.util.Long = Long as any;
+ _m0.configure();
+}
+
+function isSet(value: any): boolean {
+ return value !== null && value !== undefined;
+}
diff --git a/front/src/proto/logs/api.ts b/front/src/proto/logs/api.ts
new file mode 100644
index 0000000..b3c12ef
--- /dev/null
+++ b/front/src/proto/logs/api.ts
@@ -0,0 +1,499 @@
+// @ts-nocheck
+/* eslint-disable */
+import Long from "long";
+import type { CallContext, CallOptions } from "nice-grpc-common";
+import _m0 from "protobufjs/minimal";
+import { Empty } from "../google/protobuf/empty";
+import { Timestamp } from "../google/protobuf/timestamp";
+
+export const protobufPackage = "logs";
+
+export interface LogLine {
+ exploit: string;
+ version: Long;
+ message: string;
+ level: string;
+ team: string;
+ timestamp: Timestamp | undefined;
+}
+
+export interface AddLogLinesRequest {
+ lines: LogLine[];
+}
+
+export interface SearchLogLinesRequest {
+ exploit: string;
+ version: Long;
+ limit: Long;
+ lastToken: string;
+}
+
+export interface SearchLogLinesResponse {
+ lines: LogLine[];
+ lastToken: string;
+}
+
+function createBaseLogLine(): LogLine {
+ return { exploit: "", version: Long.ZERO, message: "", level: "", team: "", timestamp: undefined };
+}
+
+export const LogLine = {
+ encode(message: LogLine, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ if (message.exploit !== "") {
+ writer.uint32(10).string(message.exploit);
+ }
+ if (!message.version.isZero()) {
+ writer.uint32(16).int64(message.version);
+ }
+ if (message.message !== "") {
+ writer.uint32(26).string(message.message);
+ }
+ if (message.level !== "") {
+ writer.uint32(34).string(message.level);
+ }
+ if (message.team !== "") {
+ writer.uint32(42).string(message.team);
+ }
+ if (message.timestamp !== undefined) {
+ Timestamp.encode(message.timestamp, writer.uint32(50).fork()).ldelim();
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): LogLine {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseLogLine();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.exploit = reader.string();
+ continue;
+ case 2:
+ if (tag !== 16) {
+ break;
+ }
+
+ message.version = reader.int64() as Long;
+ continue;
+ case 3:
+ if (tag !== 26) {
+ break;
+ }
+
+ message.message = reader.string();
+ continue;
+ case 4:
+ if (tag !== 34) {
+ break;
+ }
+
+ message.level = reader.string();
+ continue;
+ case 5:
+ if (tag !== 42) {
+ break;
+ }
+
+ message.team = reader.string();
+ continue;
+ case 6:
+ if (tag !== 50) {
+ break;
+ }
+
+ message.timestamp = Timestamp.decode(reader, reader.uint32());
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(object: any): LogLine {
+ return {
+ exploit: isSet(object.exploit) ? globalThis.String(object.exploit) : "",
+ version: isSet(object.version) ? Long.fromValue(object.version) : Long.ZERO,
+ message: isSet(object.message) ? globalThis.String(object.message) : "",
+ level: isSet(object.level) ? globalThis.String(object.level) : "",
+ team: isSet(object.team) ? globalThis.String(object.team) : "",
+ timestamp: isSet(object.timestamp) ? fromJsonTimestamp(object.timestamp) : undefined,
+ };
+ },
+
+ toJSON(message: LogLine): unknown {
+ const obj: any = {};
+ if (message.exploit !== "") {
+ obj.exploit = message.exploit;
+ }
+ if (!message.version.isZero()) {
+ obj.version = (message.version || Long.ZERO).toString();
+ }
+ if (message.message !== "") {
+ obj.message = message.message;
+ }
+ if (message.level !== "") {
+ obj.level = message.level;
+ }
+ if (message.team !== "") {
+ obj.team = message.team;
+ }
+ if (message.timestamp !== undefined) {
+ obj.timestamp = fromTimestamp(message.timestamp).toISOString();
+ }
+ return obj;
+ },
+
+ create, I>>(base?: I): LogLine {
+ return LogLine.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(object: I): LogLine {
+ const message = createBaseLogLine();
+ message.exploit = object.exploit ?? "";
+ message.version = (object.version !== undefined && object.version !== null)
+ ? Long.fromValue(object.version)
+ : Long.ZERO;
+ message.message = object.message ?? "";
+ message.level = object.level ?? "";
+ message.team = object.team ?? "";
+ message.timestamp = (object.timestamp !== undefined && object.timestamp !== null)
+ ? Timestamp.fromPartial(object.timestamp)
+ : undefined;
+ return message;
+ },
+};
+
+function createBaseAddLogLinesRequest(): AddLogLinesRequest {
+ return { lines: [] };
+}
+
+export const AddLogLinesRequest = {
+ encode(message: AddLogLinesRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ for (const v of message.lines) {
+ LogLine.encode(v!, writer.uint32(10).fork()).ldelim();
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): AddLogLinesRequest {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseAddLogLinesRequest();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.lines.push(LogLine.decode(reader, reader.uint32()));
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(object: any): AddLogLinesRequest {
+ return { lines: globalThis.Array.isArray(object?.lines) ? object.lines.map((e: any) => LogLine.fromJSON(e)) : [] };
+ },
+
+ toJSON(message: AddLogLinesRequest): unknown {
+ const obj: any = {};
+ if (message.lines?.length) {
+ obj.lines = message.lines.map((e) => LogLine.toJSON(e));
+ }
+ return obj;
+ },
+
+ create, I>>(base?: I): AddLogLinesRequest {
+ return AddLogLinesRequest.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(object: I): AddLogLinesRequest {
+ const message = createBaseAddLogLinesRequest();
+ message.lines = object.lines?.map((e) => LogLine.fromPartial(e)) || [];
+ return message;
+ },
+};
+
+function createBaseSearchLogLinesRequest(): SearchLogLinesRequest {
+ return { exploit: "", version: Long.ZERO, limit: Long.ZERO, lastToken: "" };
+}
+
+export const SearchLogLinesRequest = {
+ encode(message: SearchLogLinesRequest, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ if (message.exploit !== "") {
+ writer.uint32(10).string(message.exploit);
+ }
+ if (!message.version.isZero()) {
+ writer.uint32(16).int64(message.version);
+ }
+ if (!message.limit.isZero()) {
+ writer.uint32(24).int64(message.limit);
+ }
+ if (message.lastToken !== "") {
+ writer.uint32(34).string(message.lastToken);
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): SearchLogLinesRequest {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSearchLogLinesRequest();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.exploit = reader.string();
+ continue;
+ case 2:
+ if (tag !== 16) {
+ break;
+ }
+
+ message.version = reader.int64() as Long;
+ continue;
+ case 3:
+ if (tag !== 24) {
+ break;
+ }
+
+ message.limit = reader.int64() as Long;
+ continue;
+ case 4:
+ if (tag !== 34) {
+ break;
+ }
+
+ message.lastToken = reader.string();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(object: any): SearchLogLinesRequest {
+ return {
+ exploit: isSet(object.exploit) ? globalThis.String(object.exploit) : "",
+ version: isSet(object.version) ? Long.fromValue(object.version) : Long.ZERO,
+ limit: isSet(object.limit) ? Long.fromValue(object.limit) : Long.ZERO,
+ lastToken: isSet(object.lastToken) ? globalThis.String(object.lastToken) : "",
+ };
+ },
+
+ toJSON(message: SearchLogLinesRequest): unknown {
+ const obj: any = {};
+ if (message.exploit !== "") {
+ obj.exploit = message.exploit;
+ }
+ if (!message.version.isZero()) {
+ obj.version = (message.version || Long.ZERO).toString();
+ }
+ if (!message.limit.isZero()) {
+ obj.limit = (message.limit || Long.ZERO).toString();
+ }
+ if (message.lastToken !== "") {
+ obj.lastToken = message.lastToken;
+ }
+ return obj;
+ },
+
+ create, I>>(base?: I): SearchLogLinesRequest {
+ return SearchLogLinesRequest.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(object: I): SearchLogLinesRequest {
+ const message = createBaseSearchLogLinesRequest();
+ message.exploit = object.exploit ?? "";
+ message.version = (object.version !== undefined && object.version !== null)
+ ? Long.fromValue(object.version)
+ : Long.ZERO;
+ message.limit = (object.limit !== undefined && object.limit !== null) ? Long.fromValue(object.limit) : Long.ZERO;
+ message.lastToken = object.lastToken ?? "";
+ return message;
+ },
+};
+
+function createBaseSearchLogLinesResponse(): SearchLogLinesResponse {
+ return { lines: [], lastToken: "" };
+}
+
+export const SearchLogLinesResponse = {
+ encode(message: SearchLogLinesResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ for (const v of message.lines) {
+ LogLine.encode(v!, writer.uint32(10).fork()).ldelim();
+ }
+ if (message.lastToken !== "") {
+ writer.uint32(18).string(message.lastToken);
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): SearchLogLinesResponse {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseSearchLogLinesResponse();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.lines.push(LogLine.decode(reader, reader.uint32()));
+ continue;
+ case 2:
+ if (tag !== 18) {
+ break;
+ }
+
+ message.lastToken = reader.string();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(object: any): SearchLogLinesResponse {
+ return {
+ lines: globalThis.Array.isArray(object?.lines) ? object.lines.map((e: any) => LogLine.fromJSON(e)) : [],
+ lastToken: isSet(object.lastToken) ? globalThis.String(object.lastToken) : "",
+ };
+ },
+
+ toJSON(message: SearchLogLinesResponse): unknown {
+ const obj: any = {};
+ if (message.lines?.length) {
+ obj.lines = message.lines.map((e) => LogLine.toJSON(e));
+ }
+ if (message.lastToken !== "") {
+ obj.lastToken = message.lastToken;
+ }
+ return obj;
+ },
+
+ create, I>>(base?: I): SearchLogLinesResponse {
+ return SearchLogLinesResponse.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(object: I): SearchLogLinesResponse {
+ const message = createBaseSearchLogLinesResponse();
+ message.lines = object.lines?.map((e) => LogLine.fromPartial(e)) || [];
+ message.lastToken = object.lastToken ?? "";
+ return message;
+ },
+};
+
+export type ServiceDefinition = typeof ServiceDefinition;
+export const ServiceDefinition = {
+ name: "Service",
+ fullName: "logs.Service",
+ methods: {
+ addLogLines: {
+ name: "AddLogLines",
+ requestType: AddLogLinesRequest,
+ requestStream: false,
+ responseType: Empty,
+ responseStream: false,
+ options: {},
+ },
+ searchLogLines: {
+ name: "SearchLogLines",
+ requestType: SearchLogLinesRequest,
+ requestStream: false,
+ responseType: SearchLogLinesResponse,
+ responseStream: true,
+ options: {},
+ },
+ },
+} as const;
+
+export interface ServiceImplementation {
+ addLogLines(request: AddLogLinesRequest, context: CallContext & CallContextExt): Promise>;
+ searchLogLines(
+ request: SearchLogLinesRequest,
+ context: CallContext & CallContextExt,
+ ): ServerStreamingMethodResult>;
+}
+
+export interface ServiceClient {
+ addLogLines(request: DeepPartial, options?: CallOptions & CallOptionsExt): Promise;
+ searchLogLines(
+ request: DeepPartial,
+ options?: CallOptions & CallOptionsExt,
+ ): AsyncIterable;
+}
+
+type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
+
+export type DeepPartial = T extends Builtin ? T
+ : T extends Long ? string | number | Long : T extends globalThis.Array ? globalThis.Array>
+ : T extends ReadonlyArray ? ReadonlyArray>
+ : T extends { $case: string } ? { [K in keyof Omit]?: DeepPartial } & { $case: T["$case"] }
+ : T extends {} ? { [K in keyof T]?: DeepPartial }
+ : Partial;
+
+type KeysOfUnion = T extends T ? keyof T : never;
+export type Exact = P extends Builtin ? P
+ : P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
+
+function toTimestamp(date: Date): Timestamp {
+ const seconds = numberToLong(date.getTime() / 1_000);
+ const nanos = (date.getTime() % 1_000) * 1_000_000;
+ return { seconds, nanos };
+}
+
+function fromTimestamp(t: Timestamp): Date {
+ let millis = (t.seconds.toNumber() || 0) * 1_000;
+ millis += (t.nanos || 0) / 1_000_000;
+ return new globalThis.Date(millis);
+}
+
+function fromJsonTimestamp(o: any): Timestamp {
+ if (o instanceof globalThis.Date) {
+ return toTimestamp(o);
+ } else if (typeof o === "string") {
+ return toTimestamp(new globalThis.Date(o));
+ } else {
+ return Timestamp.fromJSON(o);
+ }
+}
+
+function numberToLong(number: number) {
+ return Long.fromNumber(number);
+}
+
+if (_m0.util.Long !== Long) {
+ _m0.util.Long = Long as any;
+ _m0.configure();
+}
+
+function isSet(value: any): boolean {
+ return value !== null && value !== undefined;
+}
+
+export type ServerStreamingMethodResult = { [Symbol.asyncIterator](): AsyncIterator };
diff --git a/front/src/routes/auth.tsx b/front/src/routes/auth.tsx
new file mode 100644
index 0000000..48bd701
--- /dev/null
+++ b/front/src/routes/auth.tsx
@@ -0,0 +1,86 @@
+import { rawExploitServiceClient } from "@/services/exploits";
+import { usePersistentStorageValue } from "@/utils/storage";
+import { Box, TextField } from "@mui/material";
+import { ClientError, Metadata } from "nice-grpc-web";
+import { useEffect, useState } from "react";
+import ReactDOM from "react-dom";
+import { Helmet } from "react-helmet-async";
+import { AuthContext } from "./authContext";
+
+export default function AuthProvider({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const [error, setError] = useState(null);
+ const [token, setToken] = usePersistentStorageValue("token");
+ const [checkedToken, setCheckedToken] = useState(false);
+
+ useEffect(() => {
+ const checkToken = async (token: string) => {
+ try {
+ await rawExploitServiceClient.ping(
+ {
+ payload: { $case: "serverInfoRequest", serverInfoRequest: {} },
+ },
+ { metadata: new Metadata({ authorization: token }) }
+ );
+ ReactDOM.unstable_batchedUpdates(() => {
+ setToken(token);
+ setCheckedToken(true);
+ setError(null);
+ });
+ } catch (e) {
+ let message = "Unknown error";
+ if (e instanceof ClientError) {
+ message = e.message;
+ } else if (e instanceof Error) {
+ message = e.message;
+ }
+
+ ReactDOM.unstable_batchedUpdates(() => {
+ setCheckedToken(false);
+ setError(message);
+ });
+ }
+ };
+
+ if (token) {
+ void checkToken(token);
+ }
+ }, [token, setToken]);
+
+ const handleTokenChange = (event: React.ChangeEvent) => {
+ setToken(event.target.value);
+ };
+
+ if (!token || !checkedToken) {
+ return (
+ <>
+
+ Neo
+
+
+
+
+ >
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/front/src/routes/authContext.ts b/front/src/routes/authContext.ts
new file mode 100644
index 0000000..1c3ac8a
--- /dev/null
+++ b/front/src/routes/authContext.ts
@@ -0,0 +1,14 @@
+import { Metadata } from "nice-grpc-common";
+import { createContext, useContext } from "react";
+
+type AuthContextType = { metadata: Metadata };
+
+export const AuthContext = createContext(null);
+
+export function useAuthContext(): AuthContextType {
+ const ctx = useContext(AuthContext);
+ if (!ctx) {
+ throw new Error("useAuthContext must be used within AuthContext.Provider");
+ }
+ return ctx;
+}
diff --git a/front/src/routes/exploits.tsx b/front/src/routes/exploits.tsx
new file mode 100644
index 0000000..4838157
--- /dev/null
+++ b/front/src/routes/exploits.tsx
@@ -0,0 +1,50 @@
+import ExploitsView from "@/components/ExploitsView";
+import { ExploitState } from "@/proto/exploits/api";
+import { useExploitServiceClient } from "@/services/exploits";
+import { Box } from "@mui/material";
+import { useCallback, useEffect, useState } from "react";
+import { Helmet } from "react-helmet-async";
+
+interface S {
+ exploits: ExploitState[];
+}
+
+export default function ExploitsContainer() {
+ const [state, setState] = useState();
+
+ const exploitServiceClient = useExploitServiceClient();
+
+ const fetchExploits = useCallback(async () => {
+ const response = await exploitServiceClient.ping({
+ payload: { $case: "serverInfoRequest", serverInfoRequest: {} },
+ });
+ setState({
+ exploits:
+ response.state?.exploits.sort((e1: ExploitState, e2: ExploitState) => {
+ return e1.exploitId.localeCompare(e2.exploitId);
+ }) || [],
+ });
+ }, [exploitServiceClient]);
+
+ useEffect(() => {
+ void fetchExploits();
+ }, [exploitServiceClient, fetchExploits]);
+
+ return (
+ <>
+
+ Neo Exploits
+
+
+ {state && (
+ {
+ void fetchExploits();
+ }}
+ />
+ )}
+
+ >
+ );
+}
diff --git a/front/src/routes/logs.tsx b/front/src/routes/logs.tsx
new file mode 100644
index 0000000..727c672
--- /dev/null
+++ b/front/src/routes/logs.tsx
@@ -0,0 +1,40 @@
+import LogsRootView from "@/components/LogsRootView";
+import { ExploitState } from "@/proto/exploits/api";
+import { useExploitServiceClient } from "@/services/exploits";
+import { useEffect, useState } from "react";
+import { Helmet } from "react-helmet-async";
+
+interface S {
+ exploits: ExploitState[];
+}
+
+export default function LogsContainer() {
+ const exploitServiceClient = useExploitServiceClient();
+ const [state, setState] = useState();
+
+ useEffect(() => {
+ const fetchExploits = async () => {
+ const response = await exploitServiceClient.ping({
+ payload: { $case: "serverInfoRequest", serverInfoRequest: {} },
+ });
+ setState({
+ exploits:
+ response.state?.exploits.sort(
+ (e1: ExploitState, e2: ExploitState) => {
+ return e1.exploitId.localeCompare(e2.exploitId);
+ }
+ ) || [],
+ });
+ };
+ void fetchExploits();
+ }, [exploitServiceClient]);
+
+ return (
+ <>
+
+ Neo Logs
+
+ {state && }
+ >
+ );
+}
diff --git a/front/src/routes/root.tsx b/front/src/routes/root.tsx
new file mode 100644
index 0000000..3f9a1f9
--- /dev/null
+++ b/front/src/routes/root.tsx
@@ -0,0 +1,36 @@
+import { AppBar, Box, Button, Toolbar } from "@mui/material";
+import { useRef } from "react";
+import { Link, Outlet } from "react-router-dom";
+import { RootContext } from "./rootContext";
+
+export default function Root() {
+ const topbarRef = useRef(null);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/front/src/routes/rootContext.ts b/front/src/routes/rootContext.ts
new file mode 100644
index 0000000..1f260f6
--- /dev/null
+++ b/front/src/routes/rootContext.ts
@@ -0,0 +1,13 @@
+import { createContext, useContext } from "react";
+
+type RootContextType = { topbarRef: React.RefObject };
+
+export const RootContext = createContext(null);
+
+export function useRootContext(): RootContextType {
+ const ctx = useContext(RootContext);
+ if (!ctx) {
+ throw new Error("useRootContext must be used within RootContext.Provider");
+ }
+ return ctx;
+}
diff --git a/front/src/services/exploits.ts b/front/src/services/exploits.ts
new file mode 100644
index 0000000..31809f7
--- /dev/null
+++ b/front/src/services/exploits.ts
@@ -0,0 +1,38 @@
+import { grpcAddress } from "@/config.ts";
+import { ServiceClient, ServiceDefinition } from "@/proto/exploits/api.ts";
+import { useAuthContext } from "@/routes/authContext";
+import {
+ WebsocketTransport,
+ createChannel,
+ createClient,
+ createClientFactory,
+} from "nice-grpc-web";
+import { useMemo } from "react";
+
+const channel = createChannel(grpcAddress, WebsocketTransport());
+
+export type ExploitServiceClient = ServiceClient;
+
+export const rawExploitServiceClient: ExploitServiceClient = createClient(
+ ServiceDefinition,
+ channel
+);
+
+export function useExploitServiceClient(): ExploitServiceClient {
+ const authContext = useAuthContext();
+ return useMemo(
+ () =>
+ createClientFactory()
+ .use((call, options) =>
+ call.next(call.request, {
+ ...options,
+ metadata: {
+ ...options.metadata,
+ ...authContext.metadata,
+ },
+ })
+ )
+ .create(ServiceDefinition, channel),
+ [authContext]
+ );
+}
diff --git a/front/src/services/logs.ts b/front/src/services/logs.ts
new file mode 100644
index 0000000..7c72a15
--- /dev/null
+++ b/front/src/services/logs.ts
@@ -0,0 +1,32 @@
+import { grpcAddress } from "@/config.ts";
+import { ServiceClient, ServiceDefinition } from "@/proto/logs/api.ts";
+import { useAuthContext } from "@/routes/authContext";
+import {
+ WebsocketTransport,
+ createChannel,
+ createClientFactory,
+} from "nice-grpc-web";
+import { useMemo } from "react";
+
+const channel = createChannel(grpcAddress, WebsocketTransport());
+
+export type LogsServiceClient = ServiceClient;
+
+export function useLogsServiceClient(): LogsServiceClient {
+ const authContext = useAuthContext();
+ return useMemo(
+ () =>
+ createClientFactory()
+ .use((call, options) =>
+ call.next(call.request, {
+ ...options,
+ metadata: {
+ ...options.metadata,
+ ...authContext.metadata,
+ },
+ })
+ )
+ .create(ServiceDefinition, channel),
+ [authContext]
+ );
+}
diff --git a/front/src/theme.ts b/front/src/theme.ts
new file mode 100644
index 0000000..dd390e0
--- /dev/null
+++ b/front/src/theme.ts
@@ -0,0 +1,28 @@
+import { createTheme } from "@mui/material/styles";
+
+declare module "@mui/material/styles" {
+ interface Theme {
+ typography: {
+ fontFamily: string;
+ };
+ palette: {
+ primary: {
+ main: string;
+ contrastText: string;
+ };
+ };
+ }
+}
+
+const theme = createTheme({
+ typography: {
+ fontFamily: "Monospace",
+ },
+ palette: {
+ primary: {
+ main: "#333333",
+ },
+ },
+});
+
+export default theme;
diff --git a/front/src/utils/duration.ts b/front/src/utils/duration.ts
new file mode 100644
index 0000000..303b86c
--- /dev/null
+++ b/front/src/utils/duration.ts
@@ -0,0 +1,60 @@
+import { Duration } from "@/proto/google/protobuf/duration";
+import Long from "long";
+
+export function formatDuration(d: Duration): string {
+ return formatSeconds(d.seconds.toInt());
+}
+
+export function formatSeconds(s: number): string {
+ const hours = Math.floor(s / 3600);
+ const minutes = Math.floor((s % 3600) / 60);
+ const seconds = Math.floor(s % 60);
+
+ let res = "";
+ if (hours > 0) {
+ res += `${hours}h`;
+ }
+ if (minutes > 0) {
+ res += `${minutes}m`;
+ }
+ if (seconds > 0) {
+ res += `${seconds}s`;
+ }
+ if (res.length === 0) {
+ res = "0s";
+ }
+ return res;
+}
+
+export function parseDuration(s: string): Duration {
+ const numRegex = /^\d+$/;
+ if (numRegex.test(s)) {
+ return Duration.create({
+ seconds: Long.fromNumber(parseInt(s)),
+ nanos: 0,
+ });
+ }
+
+ const regex =
+ /^(?(\d+)h)?\s*(?(\d+)m)?\s*(?(\d+)s)?$/;
+ const match = regex.exec(s);
+ if (match === null) {
+ throw new Error(`invalid duration: ${s}`);
+ }
+
+ interface Parsed {
+ hours: string;
+ minutes: string;
+ seconds: string;
+ }
+ const parsed: Partial | undefined = match.groups;
+
+ const hours = parsed?.hours === undefined ? 0 : parseInt(parsed.hours);
+ const minutes = parsed?.minutes === undefined ? 0 : parseInt(parsed.minutes);
+ const seconds = parsed?.seconds === undefined ? 0 : parseInt(parsed.seconds);
+
+ return Duration.create({
+ seconds: Long.fromNumber(hours * 3600 + minutes * 60 + seconds),
+ nanos: 0,
+ });
+}
diff --git a/front/src/utils/storage.ts b/front/src/utils/storage.ts
new file mode 100644
index 0000000..b846c36
--- /dev/null
+++ b/front/src/utils/storage.ts
@@ -0,0 +1,19 @@
+import { useEffect, useState } from "react";
+
+export function usePersistentStorageValue(key: string, initialValue?: T) {
+ const [value, setValue] = useState(() => {
+ const valueFromStorage = window.localStorage.getItem(key);
+ if (valueFromStorage) {
+ return JSON.parse(valueFromStorage) as unknown as T;
+ }
+ return initialValue;
+ });
+
+ useEffect(() => {
+ if (value) {
+ window.localStorage.setItem(key, JSON.stringify(value));
+ }
+ }, [key, value]);
+
+ return [value, setValue] as const;
+}
diff --git a/front/src/utils/window.ts b/front/src/utils/window.ts
new file mode 100644
index 0000000..dcb90b3
--- /dev/null
+++ b/front/src/utils/window.ts
@@ -0,0 +1,34 @@
+import { useSyncExternalStore } from "react";
+
+export function useWindowDimensions() {
+ return useSyncExternalStore(subscribe, getSnapshot);
+}
+
+function subscribe(callback: {
+ (this: Window, ev: UIEvent): void;
+ (this: Window, ev: UIEvent): void;
+}) {
+ window.addEventListener("resize", callback);
+ return () => {
+ window.removeEventListener("resize", callback);
+ };
+}
+
+interface Snapshot {
+ width: number;
+ height: number;
+}
+
+let lastSnapshot: Snapshot | null = null;
+
+function getSnapshot() {
+ const newSnapshot = { width: window.innerWidth, height: window.innerHeight };
+ if (
+ lastSnapshot == null ||
+ newSnapshot.width != lastSnapshot.width ||
+ newSnapshot.height != lastSnapshot.height
+ ) {
+ lastSnapshot = newSnapshot;
+ }
+ return lastSnapshot;
+}
diff --git a/front/src/vite-env.d.ts b/front/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/front/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/front/tsconfig.json b/front/tsconfig.json
new file mode 100644
index 0000000..8775be2
--- /dev/null
+++ b/front/tsconfig.json
@@ -0,0 +1,40 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": [
+ "ES2020",
+ "DOM",
+ "DOM.Iterable"
+ ],
+ "module": "ESNext",
+ "skipLibCheck": true,
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ /* Linting */
+ "strict": true,
+ "strictNullChecks": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": [
+ "src/*",
+ ]
+ },
+ },
+ "include": [
+ "src"
+ ],
+ "references": [
+ {
+ "path": "./tsconfig.node.json"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/front/tsconfig.node.json b/front/tsconfig.node.json
new file mode 100644
index 0000000..7f5d1a8
--- /dev/null
+++ b/front/tsconfig.node.json
@@ -0,0 +1,13 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strictNullChecks": true,
+ "allowSyntheticDefaultImports": true
+ },
+ "include": [
+ "vite.config.ts"
+ ]
+}
\ No newline at end of file
diff --git a/front/vite.config.ts b/front/vite.config.ts
new file mode 100644
index 0000000..e8e2bf8
--- /dev/null
+++ b/front/vite.config.ts
@@ -0,0 +1,10 @@
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vite";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ alias: [{ find: "@", replacement: "/src" }],
+ },
+});
diff --git a/go.mod b/go.mod
index 4d538b6..ecb013a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/c4t-but-s4d/neo/v2
-go 1.21
+go 1.23
require (
github.com/denisbrodbeck/machineid v1.0.1
@@ -23,26 +23,33 @@ require (
)
require (
+ github.com/improbable-eng/grpc-web v0.15.1-0.20230209220825-1d9bbb09a099
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/common v0.44.0
github.com/samber/lo v1.38.1
+ golang.org/x/net v0.17.0
golang.org/x/sys v0.13.0
+ nhooyr.io/websocket v1.8.7
)
require (
github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+ github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/klauspost/compress v1.17.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
+ github.com/rs/cors v1.7.0 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
@@ -51,7 +58,6 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
- golang.org/x/net v0.17.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
diff --git a/go.sum b/go.sum
index 5d00ded..959cdf2 100644
--- a/go.sum
+++ b/go.sum
@@ -38,9 +38,20 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
+github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -57,6 +68,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
+github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I=
+github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -69,11 +82,37 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X
github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
+github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
+github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
+github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
+github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
+github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -100,6 +139,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -116,6 +156,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -136,18 +177,41 @@ github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
+github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/improbable-eng/grpc-web v0.15.1-0.20230209220825-1d9bbb09a099 h1:k07oXM8RqIaaSEF09Frr/iRMlwx2qvx6vRo2XuPIeW8=
+github.com/improbable-eng/grpc-web v0.15.1-0.20230209220825-1d9bbb09a099/go.mod h1:Vkb7Iy2LTlRGIAubpODgfeKPzu8nsh1gO+vvZAiZrcs=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
+github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -155,37 +219,76 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
+github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/grpc-proxy v0.0.0-20181017164139-0f1106ef9c76/go.mod h1:x5OoJHDHqxHS801UIuhqGl6QdSAEJvtausosHSdazIo=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
+github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
@@ -193,6 +296,10 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
@@ -210,8 +317,11 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -221,6 +331,10 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -233,10 +347,14 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -254,6 +372,7 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@@ -281,6 +400,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -288,6 +408,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -298,6 +419,7 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -310,7 +432,9 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -322,6 +446,7 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -333,18 +458,24 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -352,10 +483,13 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -363,15 +497,21 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -385,6 +525,7 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -483,6 +624,7 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
@@ -497,6 +639,7 @@ google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c h1:jHkCUWkseRf+W+edG5hMzr/Uh1xkDREY4caybAq4dpY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231009173412-8bfb1ae86b6c/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0=
@@ -513,6 +656,7 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
@@ -530,10 +674,13 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
@@ -541,10 +688,16 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -554,6 +707,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
+nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/internal/client/client.go b/internal/client/client.go
index 0a8e4fe..ee7b217 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -10,9 +10,9 @@ import (
"google.golang.org/grpc"
"github.com/c4t-but-s4d/neo/v2/pkg/filestream"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
- fspb "github.com/c4t-but-s4d/neo/v2/proto/go/fileserver"
- logspb "github.com/c4t-but-s4d/neo/v2/proto/go/logs"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
+ fspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/fileserver"
+ logspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/logs"
)
func New(cc grpc.ClientConnInterface, id string) *Client {
diff --git a/internal/client/config.go b/internal/client/config.go
index 94dc63c..15ae678 100644
--- a/internal/client/config.go
+++ b/internal/client/config.go
@@ -14,10 +14,17 @@ func MustUnmarshalConfig() *Config {
return cfg
}
+type Metrics struct {
+ URL string `mapstructure:"url"`
+ User string `mapstructure:"user"`
+ Password string `mapstructure:"password"`
+}
+
type Config struct {
Host string `mapstructure:"host"`
- MetricsHost string `mapstructure:"metrics_host"`
ExploitDir string `mapstructure:"exploit_dir"`
GrpcAuthKey string `mapstructure:"grpc_auth_key"`
UseTLS bool `mapstructure:"use_tls"`
+
+ Metrics *Metrics `mapstructure:"metrics"`
}
diff --git a/internal/config/config.go b/internal/config/config.go
index 7f666e3..4e01476 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -7,7 +7,7 @@ import (
"google.golang.org/protobuf/types/known/durationpb"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
)
type ExploitsConfig struct {
diff --git a/internal/exploit/cache.go b/internal/exploit/cache.go
index b368256..a6f9251 100644
--- a/internal/exploit/cache.go
+++ b/internal/exploit/cache.go
@@ -4,7 +4,7 @@ import (
"sync"
"time"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
)
func NewCache() *Cache {
diff --git a/internal/exploit/cache_test.go b/internal/exploit/cache_test.go
index 1cd1812..a3e5d77 100644
--- a/internal/exploit/cache_test.go
+++ b/internal/exploit/cache_test.go
@@ -7,7 +7,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/testing/protocmp"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
)
func TestCache_Diff(t *testing.T) {
diff --git a/internal/exploit/runner.go b/internal/exploit/runner.go
index 0493931..a89eac9 100644
--- a/internal/exploit/runner.go
+++ b/internal/exploit/runner.go
@@ -21,7 +21,7 @@ import (
"github.com/c4t-but-s4d/neo/v2/internal/models"
"github.com/c4t-but-s4d/neo/v2/internal/queue"
"github.com/c4t-but-s4d/neo/v2/pkg/joblogger"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
)
var (
@@ -49,7 +49,8 @@ func NewRunner(
restarts: make(chan struct{}, 1),
logSender: logSender,
metricsPusher: push.
- New(clientConfig.MetricsHost, "neo_runner").
+ New(clientConfig.Metrics.URL, "neo_runner").
+ BasicAuth(clientConfig.Metrics.User, clientConfig.Metrics.Password).
Grouping("client_id", clientID).
Format(expfmt.FmtOpenMetrics_1_0_0).
Gatherer(prometheus.DefaultGatherer),
diff --git a/internal/exploit/storage.go b/internal/exploit/storage.go
index b2397cd..d99bd8f 100644
--- a/internal/exploit/storage.go
+++ b/internal/exploit/storage.go
@@ -12,7 +12,7 @@ import (
"github.com/c4t-but-s4d/neo/v2/internal/client"
"github.com/c4t-but-s4d/neo/v2/pkg/archive"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
)
func NewStorage(cache *Cache, exploitDir string, client *client.Client) *Storage {
diff --git a/internal/exploit/storage_test.go b/internal/exploit/storage_test.go
index 5fb8c86..74cefc8 100644
--- a/internal/exploit/storage_test.go
+++ b/internal/exploit/storage_test.go
@@ -14,7 +14,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/c4t-but-s4d/neo/v2/pkg/archive"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
)
func mockStorage() (*Storage, func() error) {
diff --git a/internal/logstor/interface.go b/internal/logstor/interface.go
new file mode 100644
index 0000000..267a071
--- /dev/null
+++ b/internal/logstor/interface.go
@@ -0,0 +1,43 @@
+package logstor
+
+import (
+ "context"
+)
+
+const defaultLimit = 10000
+
+type SearchConfig struct {
+ limit int
+ lastToken string
+}
+
+type SearchOption func(cfg *SearchConfig)
+
+func SearchWithLimit(limit int) SearchOption {
+ return func(cfg *SearchConfig) {
+ cfg.limit = limit
+ }
+}
+
+func SearchWithLastToken(lastToken string) SearchOption {
+ return func(cfg *SearchConfig) {
+ cfg.lastToken = lastToken
+ }
+}
+
+func GetSearchConfig(opts ...SearchOption) *SearchConfig {
+ cfg := &SearchConfig{
+ limit: defaultLimit,
+ }
+
+ for _, opt := range opts {
+ opt(cfg)
+ }
+
+ return cfg
+}
+
+type Storage interface {
+ Add(ctx context.Context, lines ...*Line) error
+ Search(ctx context.Context, exploit string, version int64, opts ...SearchOption) ([]*Line, error)
+}
diff --git a/internal/logstor/line.go b/internal/logstor/line.go
new file mode 100644
index 0000000..c1cfd82
--- /dev/null
+++ b/internal/logstor/line.go
@@ -0,0 +1,116 @@
+package logstor
+
+import (
+ "fmt"
+ "reflect"
+ "strconv"
+ "time"
+
+ "github.com/mitchellh/mapstructure"
+ "google.golang.org/protobuf/types/known/timestamppb"
+
+ logspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/logs"
+)
+
+func LineDecodeHook(from, to reflect.Type, data any) (any, error) {
+ switch {
+ case from.Kind() == reflect.String && to == reflect.TypeOf(time.Time{}):
+ str, ok := data.(string)
+ if !ok {
+ return data, nil
+ }
+
+ res, err := time.Parse(time.RFC3339Nano, str)
+ if err != nil {
+ return nil, fmt.Errorf("parsing time: %w", err)
+ }
+ return res, nil
+
+ case from.Kind() == reflect.String && to.Kind() == reflect.Int64:
+ str, ok := data.(string)
+ if !ok {
+ return data, nil
+ }
+
+ res, err := strconv.ParseInt(str, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("parsing int64: %w", err)
+ }
+ return res, nil
+
+ default:
+ return data, nil
+ }
+}
+
+func NewLineFromRedis(vals map[string]any) (*Line, error) {
+ var line Line
+ decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
+ DecodeHook: LineDecodeHook,
+ Result: &line,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("creating decoder: %w", err)
+ }
+
+ if err := decoder.Decode(vals); err != nil {
+ return nil, fmt.Errorf("decoding structure: %w", err)
+ }
+ return &line, nil
+}
+
+func NewLineFromProto(p *logspb.LogLine) *Line {
+ return &Line{
+ Timestamp: p.Timestamp.AsTime(),
+ Exploit: p.Exploit,
+ Version: p.Version,
+ Message: p.Message,
+ Level: p.Level,
+ Team: p.Team,
+ }
+}
+
+type Line struct {
+ Timestamp time.Time `mapstructure:"timestamp"`
+ Exploit string `mapstructure:"exploit"`
+ Version int64 `mapstructure:"version"`
+ Message string `mapstructure:"message"`
+ Level string `mapstructure:"level"`
+ Team string `mapstructure:"team"`
+}
+
+func (l *Line) String() string {
+ return fmt.Sprintf("Line(%s.v%d)", l.Exploit, l.Version)
+}
+
+func (l *Line) EstimateSize() int {
+ const (
+ estNum = 5
+ estDenom = 4
+ structSize = 8*4 + 32 + 8
+ )
+ sizeEst := structSize + len(l.Exploit) + len(l.Message) + len(l.Level) + len(l.Team)
+ return sizeEst * estNum / estDenom
+}
+
+func (l *Line) ToRedis() map[string]any {
+ return map[string]any{
+ "timestamp": l.Timestamp.Format(time.RFC3339Nano),
+ "exploit": l.Exploit,
+ "version": strconv.FormatInt(l.Version, 10),
+ "message": l.Message,
+ "level": l.Level,
+ "team": l.Team,
+ }
+}
+
+func (l *Line) ToProto() *logspb.LogLine {
+ return &logspb.LogLine{
+ Timestamp: timestamppb.New(l.Timestamp),
+ Exploit: l.Exploit,
+ Version: l.Version,
+ Message: l.Message,
+ Level: l.Level,
+ Team: l.Team,
+ }
+}
diff --git a/internal/server/logs/storage.go b/internal/logstor/redis.go
similarity index 51%
rename from internal/server/logs/storage.go
rename to internal/logstor/redis.go
index 2050e98..0df43c0 100644
--- a/internal/server/logs/storage.go
+++ b/internal/logstor/redis.go
@@ -1,19 +1,26 @@
-package server
+package logstor
import (
"context"
"fmt"
- "strconv"
"github.com/go-redis/redis/v8"
)
const (
- linesPerSploitLimit = 100000
+ maxRedisStreamLength = 100000
)
-func NewLogStorage(ctx context.Context, redisURL string) (*LogStorage, error) {
- opts, err := redis.ParseURL(redisURL)
+var (
+ _ Storage = (*RedisStorage)(nil)
+)
+
+type RedisStorage struct {
+ rdb *redis.Client
+}
+
+func NewRedisStorage(ctx context.Context, url string) (*RedisStorage, error) {
+ opts, err := redis.ParseURL(url)
if err != nil {
return nil, fmt.Errorf("invalid redis url: %w", err)
}
@@ -21,26 +28,18 @@ func NewLogStorage(ctx context.Context, redisURL string) (*LogStorage, error) {
if err := c.Ping(ctx).Err(); err != nil {
return nil, fmt.Errorf("connecting to redis: %w", err)
}
- return &LogStorage{rdb: c}, nil
+ return &RedisStorage{rdb: c}, nil
}
-type LogStorage struct {
- rdb *redis.Client
-}
-
-func (s *LogStorage) Add(ctx context.Context, lines []LogLine) error {
+func (s *RedisStorage) Add(ctx context.Context, lines ...*Line) error {
if _, err := s.rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
for _, line := range lines {
key := getRedisStream(line.Exploit, line.Version)
- vals, err := line.DumpValues()
- if err != nil {
- return fmt.Errorf("serializing %v: %w", line, err)
- }
args := redis.XAddArgs{
Stream: key,
- MaxLen: linesPerSploitLimit,
+ MaxLen: maxRedisStreamLength,
Approx: true,
- Values: vals,
+ Values: line.ToRedis(),
}
if err := pipe.XAdd(ctx, &args).Err(); err != nil {
return fmt.Errorf("adding %v: %w", line, err)
@@ -53,15 +52,21 @@ func (s *LogStorage) Add(ctx context.Context, lines []LogLine) error {
return nil
}
-func (s *LogStorage) Get(ctx context.Context, opts GetOptions) ([]*LogLine, error) {
- key := getRedisStream(opts.Exploit, strconv.FormatInt(opts.Version, 10))
- res, err := s.rdb.XRange(ctx, key, "-", "+").Result()
+func (s *RedisStorage) Search(ctx context.Context, exploit string, version int64, opts ...SearchOption) ([]*Line, error) {
+ cfg := GetSearchConfig(opts...)
+ key := getRedisStream(exploit, version)
+
+ start := "-"
+ if cfg.lastToken != "" {
+ start = fmt.Sprintf("(%s", cfg.lastToken)
+ }
+ res, err := s.rdb.XRangeN(ctx, key, start, "+", int64(cfg.limit)).Result()
if err != nil {
return nil, fmt.Errorf("querying stream %s: %w", key, err)
}
- lines := make([]*LogLine, 0, len(res))
+ lines := make([]*Line, 0, len(res))
for _, msg := range res {
- line, err := NewLogLineFromValues(msg.Values)
+ line, err := NewLineFromRedis(msg.Values)
if err != nil {
return nil, fmt.Errorf("decoding line from %+v: %w", msg.Values, err)
}
@@ -70,11 +75,6 @@ func (s *LogStorage) Get(ctx context.Context, opts GetOptions) ([]*LogLine, erro
return lines, nil
}
-type GetOptions struct {
- Exploit string
- Version int64
-}
-
-func getRedisStream(exploit string, version string) string {
- return fmt.Sprintf("logs:%s:%s", exploit, version)
+func getRedisStream(exploit string, version int64) string {
+ return fmt.Sprintf("logs:%s:%d", exploit, version)
}
diff --git a/internal/server/config/config.go b/internal/server/config/config.go
index 43faffd..b2da314 100644
--- a/internal/server/config/config.go
+++ b/internal/server/config/config.go
@@ -8,17 +8,20 @@ import (
)
type Config struct {
- Debug bool `mapstructure:"debug"`
- Addr string `mapstructure:"addr"`
- DBPath string `mapstructure:"db_path"`
- RedisURL string `mapstructure:"redis_url"`
- BaseDir string `mapstructure:"base_dir"`
- PingEvery time.Duration `mapstructure:"ping_every"`
- SubmitEvery time.Duration `mapstructure:"submit_every"`
- FarmConfig FarmConfig `mapstructure:"farm"`
- GrpcAuthKey string `mapstructure:"grpc_auth_key"`
- Environ map[string]string `mapstructure:"env"`
- MetricsNamespace string `mapstructure:"metrics_namespace"`
+ Debug bool `mapstructure:"debug"`
+ Address string `mapstructure:"address"`
+ StaticDir string `mapstructure:"static_dir"`
+ DBPath string `mapstructure:"db_path"`
+ RedisURL string `mapstructure:"redis_url"`
+ BaseDir string `mapstructure:"base_dir"`
+ PingEvery time.Duration `mapstructure:"ping_every"`
+ SubmitEvery time.Duration `mapstructure:"submit_every"`
+ FarmConfig FarmConfig `mapstructure:"farm"`
+ GrpcAuthKey string `mapstructure:"grpc_auth_key"`
+ Environ map[string]string `mapstructure:"env"`
+
+ MetricsAddress string `mapstructure:"metrics_address"`
+ MetricsNamespace string `mapstructure:"metrics_namespace"`
}
type FarmConfig struct {
diff --git a/internal/server/exploits/server.go b/internal/server/exploits/server.go
index c084691..8a6ab24 100644
--- a/internal/server/exploits/server.go
+++ b/internal/server/exploits/server.go
@@ -14,8 +14,8 @@ import (
serverConfig "github.com/c4t-but-s4d/neo/v2/internal/server/config"
"github.com/c4t-but-s4d/neo/v2/pkg/gstream"
"github.com/c4t-but-s4d/neo/v2/pkg/hostbucket"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
"github.com/c4t-but-s4d/neo/v2/pkg/pubsub"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
)
func New(cfg *serverConfig.Config, storage *CachedStorage) *Server {
@@ -61,7 +61,7 @@ func (s *Server) Ping(ctx context.Context, r *epb.PingRequest) (*epb.PingRespons
case *epb.PingRequest_LeaveRequest:
s.visits.MarkInvalid(r.ClientId)
default:
- return nil, s.WrapErrorf(ctx, codes.InvalidArgument, "Unknown payload type: %T", p)
+ return nil, s.WrapErrorf(ctx, codes.InvalidArgument, "unknown payload type: %T", p)
}
return &epb.PingResponse{
@@ -78,7 +78,7 @@ func (s *Server) Exploit(ctx context.Context, r *epb.ExploitRequest) (*epb.Explo
state, ok := s.storage.GetState(r.ExploitId)
if !ok {
- return nil, s.WrapErrorf(ctx, codes.NotFound, "Failed to find an exploit state = %v", state.ExploitId)
+ return nil, s.WrapErrorf(ctx, codes.NotFound, "finding an exploit state = %v", state.ExploitId)
}
return &epb.ExploitResponse{
State: state,
@@ -88,9 +88,19 @@ func (s *Server) Exploit(ctx context.Context, r *epb.ExploitRequest) (*epb.Explo
func (s *Server) UpdateExploit(ctx context.Context, r *epb.UpdateExploitRequest) (*epb.UpdateExploitResponse, error) {
s.LogRequest(ctx, r)
+ if r.State.Config == nil {
+ return nil, s.WrapErrorf(ctx, codes.InvalidArgument, "config required")
+ }
+ if r.State.Config.Timeout.AsDuration() == 0 {
+ return nil, s.WrapErrorf(ctx, codes.InvalidArgument, "timeout required")
+ }
+ if r.State.Config.RunEvery.AsDuration() == 0 {
+ return nil, s.WrapErrorf(ctx, codes.InvalidArgument, "run_every required")
+ }
+
newState, err := s.storage.UpdateExploitVersion(r.State)
if err != nil {
- return nil, s.WrapErrorf(ctx, codes.Internal, "Failed to update exploit version: %v", err)
+ return nil, s.WrapErrorf(ctx, codes.Internal, "updating exploit version: %v", err)
}
return &epb.UpdateExploitResponse{State: newState}, nil
}
diff --git a/internal/server/exploits/server_test.go b/internal/server/exploits/server_test.go
index 26deb2b..1dec111 100644
--- a/internal/server/exploits/server_test.go
+++ b/internal/server/exploits/server_test.go
@@ -10,11 +10,12 @@ import (
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
+ "google.golang.org/protobuf/types/known/durationpb"
"github.com/c4t-but-s4d/neo/v2/internal/server/config"
"github.com/c4t-but-s4d/neo/v2/pkg/hostbucket"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
- fspb "github.com/c4t-but-s4d/neo/v2/proto/go/fileserver"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
+ fspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/fileserver"
)
func testServer(t *testing.T) (*Server, func()) {
@@ -40,6 +41,8 @@ func TestServer_UpdateExploit(t *testing.T) {
cfg := &epb.ExploitConfiguration{
Entrypoint: "bin",
IsArchive: false,
+ Timeout: durationpb.New(time.Minute),
+ RunEvery: durationpb.New(time.Minute),
}
r := &epb.UpdateExploitRequest{
State: &epb.ExploitState{
@@ -50,6 +53,9 @@ func TestServer_UpdateExploit(t *testing.T) {
}
resp, err := es.UpdateExploit(context.Background(), r)
require.NoError(t, err)
+
+ cfg.Timeout = durationpb.New(2 * time.Minute)
+ cfg.RunEvery = durationpb.New(2 * time.Minute)
want := &epb.ExploitState{
ExploitId: "1",
Version: 1,
@@ -67,6 +73,8 @@ func TestServer_Exploit(t *testing.T) {
cfg := &epb.ExploitConfiguration{
Entrypoint: "bin",
IsArchive: false,
+ Timeout: durationpb.New(time.Minute),
+ RunEvery: durationpb.New(time.Minute),
}
state := &epb.ExploitState{
ExploitId: "1",
@@ -99,6 +107,8 @@ func TestServer_Ping(t *testing.T) {
cfg := &epb.ExploitConfiguration{
Entrypoint: "bin",
IsArchive: false,
+ Timeout: durationpb.New(time.Minute),
+ RunEvery: durationpb.New(time.Minute),
}
state := &epb.ExploitState{
ExploitId: "1",
diff --git a/internal/server/exploits/storage.go b/internal/server/exploits/storage.go
index c47b1a1..6a61a11 100644
--- a/internal/server/exploits/storage.go
+++ b/internal/server/exploits/storage.go
@@ -10,7 +10,7 @@ import (
bolt "go.etcd.io/bbolt"
"google.golang.org/protobuf/proto"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
)
const (
diff --git a/internal/server/exploits/storage_test.go b/internal/server/exploits/storage_test.go
index 4fc987b..d69a9a5 100644
--- a/internal/server/exploits/storage_test.go
+++ b/internal/server/exploits/storage_test.go
@@ -12,8 +12,8 @@ import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
- fspb "github.com/c4t-but-s4d/neo/v2/proto/go/fileserver"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
+ fspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/fileserver"
)
func testDB() (*bolt.DB, func()) {
diff --git a/internal/server/fs/server.go b/internal/server/fs/server.go
index 79325a0..2286dfc 100644
--- a/internal/server/fs/server.go
+++ b/internal/server/fs/server.go
@@ -12,7 +12,7 @@ import (
serverConfig "github.com/c4t-but-s4d/neo/v2/internal/server/config"
"github.com/c4t-but-s4d/neo/v2/internal/server/utils"
"github.com/c4t-but-s4d/neo/v2/pkg/filestream"
- fspb "github.com/c4t-but-s4d/neo/v2/proto/go/fileserver"
+ fspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/fileserver"
)
func New(cfg *serverConfig.Config) (*Server, error) {
diff --git a/internal/server/logs/logline.go b/internal/server/logs/logline.go
deleted file mode 100644
index f2659c4..0000000
--- a/internal/server/logs/logline.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package server
-
-import (
- "fmt"
- "strconv"
-
- "github.com/mitchellh/mapstructure"
-
- logspb "github.com/c4t-but-s4d/neo/v2/proto/go/logs"
-)
-
-func NewLogLineFromValues(vals map[string]interface{}) (*LogLine, error) {
- var line LogLine
- if err := mapstructure.Decode(vals, &line); err != nil {
- return nil, fmt.Errorf("decoding structure: %w", err)
- }
- return &line, nil
-}
-
-func NewLogLineFromProto(p *logspb.LogLine) *LogLine {
- return &LogLine{
- Exploit: p.Exploit,
- Version: strconv.FormatInt(p.Version, 10),
- Message: p.Message,
- Level: p.Level,
- Team: p.Team,
- }
-}
-
-type LogLine struct {
- Exploit string `mapstructure:"exploit"`
- Version string `mapstructure:"version"`
- Message string `mapstructure:"message"`
- Level string `mapstructure:"level"`
- Team string `mapstructure:"team"`
-}
-
-func (l *LogLine) String() string {
- return fmt.Sprintf("Line(%s.v%s)", l.Exploit, l.Version)
-}
-
-func (l *LogLine) DumpValues() (map[string]interface{}, error) {
- res := make(map[string]interface{})
- if err := mapstructure.Decode(l, &res); err != nil {
- return nil, fmt.Errorf("encoding structure: %w", err)
- }
- return res, nil
-}
-
-func (l *LogLine) EstimateSize() int {
- const (
- estNum = 5
- estDenom = 4
- structSize = 8 * 5
- )
- sizeEst := structSize + len(l.Exploit) + len(l.Version) + len(l.Message) + len(l.Level) + len(l.Team)
- return sizeEst * estNum / estDenom
-}
-
-func (l *LogLine) ToProto() (*logspb.LogLine, error) {
- version, err := strconv.ParseInt(l.Version, 10, 64)
- if err != nil {
- return nil, fmt.Errorf("converting version (%v): %w", l.Version, err)
- }
- return &logspb.LogLine{
- Exploit: l.Exploit,
- Version: version,
- Message: l.Message,
- Level: l.Level,
- Team: l.Team,
- }, nil
-}
diff --git a/internal/server/logs/server.go b/internal/server/logs/server.go
index 3812923..c828230 100644
--- a/internal/server/logs/server.go
+++ b/internal/server/logs/server.go
@@ -2,15 +2,16 @@ package server
import (
"context"
- "fmt"
+ "github.com/samber/lo"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/types/known/emptypb"
+ "github.com/c4t-but-s4d/neo/v2/internal/logstor"
"github.com/c4t-but-s4d/neo/v2/internal/server/common"
"github.com/c4t-but-s4d/neo/v2/internal/server/utils"
"github.com/c4t-but-s4d/neo/v2/pkg/gstream"
- logspb "github.com/c4t-but-s4d/neo/v2/proto/go/logs"
+ logspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/logs"
)
const (
@@ -18,7 +19,7 @@ const (
maxMsgSize = 4 * 1024 * 1024
)
-func New(storage *LogStorage) *Server {
+func New(storage logstor.Storage) *Server {
return &Server{
LoggingServer: common.NewLoggingServer("logs"),
@@ -30,17 +31,16 @@ type Server struct {
logspb.UnimplementedServiceServer
common.LoggingServer
- storage *LogStorage
+ storage logstor.Storage
}
-func (s *Server) AddLogLines(ctx context.Context, lines *logspb.AddLogLinesRequest) (*emptypb.Empty, error) {
- s.GetMethodLogger(ctx).Infof("New request with %d lines", len(lines.Lines))
+func (s *Server) AddLogLines(ctx context.Context, request *logspb.AddLogLinesRequest) (*emptypb.Empty, error) {
+ s.GetMethodLogger(ctx).Infof("New request with %d lines", len(request.Lines))
- decoded := make([]LogLine, 0, len(lines.Lines))
- for _, line := range lines.Lines {
- decoded = append(decoded, *NewLogLineFromProto(line))
- }
- if err := s.storage.Add(ctx, decoded); err != nil {
+ decoded := lo.Map(request.Lines, func(line *logspb.LogLine, _ int) *logstor.Line {
+ return logstor.NewLineFromProto(line)
+ })
+ if err := s.storage.Add(ctx, decoded...); err != nil {
return nil, utils.WrapErrorf(codes.Internal, "adding log lines: %v", err)
}
return &emptypb.Empty{}, nil
@@ -49,30 +49,28 @@ func (s *Server) AddLogLines(ctx context.Context, lines *logspb.AddLogLinesReque
func (s *Server) SearchLogLines(req *logspb.SearchLogLinesRequest, stream logspb.Service_SearchLogLinesServer) error {
s.LogRequest(stream.Context(), req)
- opts := GetOptions{
- Exploit: req.Exploit,
- Version: req.Version,
+ var opts []logstor.SearchOption
+ if req.Limit != 0 {
+ opts = append(opts, logstor.SearchWithLimit(int(req.Limit)))
+ }
+ if req.LastToken != "" {
+ opts = append(opts, logstor.SearchWithLastToken(req.LastToken))
}
- lines, err := s.storage.Get(stream.Context(), opts)
+
+ lines, err := s.storage.Search(stream.Context(), req.Exploit, req.Version, opts...)
if err != nil {
return utils.WrapErrorf(codes.Internal, "searching log lines: %v", err)
}
- cache := gstream.NewDynamicSizeCache[*LogLine, logspb.SearchLogLinesResponse](
+ cache := gstream.NewDynamicSizeCache[*logstor.Line, logspb.SearchLogLinesResponse](
stream,
maxMsgSize,
- func(lines []*LogLine) (*logspb.SearchLogLinesResponse, error) {
- resp := &logspb.SearchLogLinesResponse{
- Lines: make([]*logspb.LogLine, 0, len(lines)),
- }
- for _, line := range lines {
- protoLine, err := line.ToProto()
- if err != nil {
- return nil, fmt.Errorf("converting line to proto: %w", err)
- }
- resp.Lines = append(resp.Lines, protoLine)
- }
- return resp, nil
+ func(lines []*logstor.Line) (*logspb.SearchLogLinesResponse, error) {
+ return &logspb.SearchLogLinesResponse{
+ Lines: lo.Map(lines, func(line *logstor.Line, _ int) *logspb.LogLine {
+ return line.ToProto()
+ }),
+ }, nil
},
)
diff --git a/monitoring/cfg/prometheus/config.yml b/monitoring/cfg/prometheus/config.yml
index 50af731..7c5e269 100644
--- a/monitoring/cfg/prometheus/config.yml
+++ b/monitoring/cfg/prometheus/config.yml
@@ -3,18 +3,18 @@ scrape_configs:
scrape_interval: 10s
scrape_timeout: 3s
static_configs:
- - targets: ['server:3000']
+ - targets: [ 'server:3000' ]
- job_name: 's4d_farm_api'
scrape_interval: 10s
scrape_timeout: 3s
metrics_path: '/api/metrics'
static_configs:
- - targets: ['host.docker.internal:5137']
+ - targets: [ 'host.docker.internal:5137' ]
- job_name: 's4d_farm_celery'
scrape_interval: 10s
scrape_timeout: 3s
metrics_path: '/celery/metrics'
static_configs:
- - targets: ['host.docker.internal:5137']
+ - targets: [ 'host.docker.internal:5137' ]
diff --git a/monitoring/cfg/victoria-proxy/Caddyfile b/monitoring/cfg/victoria-proxy/Caddyfile
new file mode 100644
index 0000000..d856017
--- /dev/null
+++ b/monitoring/cfg/victoria-proxy/Caddyfile
@@ -0,0 +1,9 @@
+http://:8428 {
+ handle {
+ basicauth {
+ {$HTTP_BASIC_AUTH_USER} {$HTTP_BASIC_AUTH_PASSWORD_HASH}
+ }
+
+ reverse_proxy victoria:8428
+ }
+}
diff --git a/pkg/filestream/filestream.go b/pkg/filestream/filestream.go
index 3bed68a..c6a2105 100644
--- a/pkg/filestream/filestream.go
+++ b/pkg/filestream/filestream.go
@@ -5,7 +5,7 @@ import (
"fmt"
"io"
- fspb "github.com/c4t-but-s4d/neo/v2/proto/go/fileserver"
+ fspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/fileserver"
)
const (
diff --git a/pkg/filestream/filestream_test.go b/pkg/filestream/filestream_test.go
index cda51bc..cff0d16 100644
--- a/pkg/filestream/filestream_test.go
+++ b/pkg/filestream/filestream_test.go
@@ -11,7 +11,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
- fspb "github.com/c4t-but-s4d/neo/v2/proto/go/fileserver"
+ fspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/fileserver"
)
type failedReadWriter struct {
diff --git a/pkg/hostbucket/hostbucket.go b/pkg/hostbucket/hostbucket.go
index 30e5ccf..33a46a9 100644
--- a/pkg/hostbucket/hostbucket.go
+++ b/pkg/hostbucket/hostbucket.go
@@ -6,8 +6,8 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
+ epb "github.com/c4t-but-s4d/neo/v2/pkg/proto/exploits"
"github.com/c4t-but-s4d/neo/v2/pkg/rendezvous"
- epb "github.com/c4t-but-s4d/neo/v2/proto/go/exploits"
)
func New(teams map[string]string) *HostBucket {
diff --git a/pkg/joblogger/logger.go b/pkg/joblogger/logger.go
index dd5e3e1..e8213f9 100644
--- a/pkg/joblogger/logger.go
+++ b/pkg/joblogger/logger.go
@@ -6,9 +6,10 @@ import (
"strings"
"github.com/sirupsen/logrus"
+ "google.golang.org/protobuf/types/known/timestamppb"
"github.com/c4t-but-s4d/neo/v2/internal/logger"
- logspb "github.com/c4t-but-s4d/neo/v2/proto/go/logs"
+ logspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/logs"
)
// 1 MB.
@@ -56,11 +57,12 @@ func (l *JobLogger) Errorf(format string, args ...interface{}) {
func (l *JobLogger) newLine(msg, level string) *logspb.LogLine {
return &logspb.LogLine{
- Exploit: l.exploit,
- Version: l.version,
- Message: sanitizeMessage(msg),
- Level: level,
- Team: l.team,
+ Exploit: l.exploit,
+ Version: l.version,
+ Message: sanitizeMessage(msg),
+ Level: level,
+ Team: l.team,
+ Timestamp: timestamppb.Now(),
}
}
@@ -72,7 +74,7 @@ func (l *JobLogger) getLogger() *logrus.Entry {
})
}
-func (l *JobLogger) logProxy(level logrus.Level, format string, args ...interface{}) {
+func (l *JobLogger) logProxy(level logrus.Level, format string, args ...any) {
if logrus.IsLevelEnabled(level) {
l.
getLogger().
diff --git a/pkg/joblogger/sender.go b/pkg/joblogger/sender.go
index faccce5..ed4cb99 100644
--- a/pkg/joblogger/sender.go
+++ b/pkg/joblogger/sender.go
@@ -9,7 +9,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/c4t-but-s4d/neo/v2/internal/client"
- logspb "github.com/c4t-but-s4d/neo/v2/proto/go/logs"
+ logspb "github.com/c4t-but-s4d/neo/v2/pkg/proto/logs"
)
const (
@@ -17,6 +17,7 @@ const (
)
type Sender interface {
+ Start(ctx context.Context)
Add(lines ...*logspb.LogLine)
}
@@ -26,6 +27,8 @@ func NewDummySender() *DummySender {
type DummySender struct{}
+func (s *DummySender) Start(context.Context) {}
+
func (s *DummySender) Add(...*logspb.LogLine) {
}
diff --git a/pkg/mu/options.go b/pkg/mu/options.go
new file mode 100644
index 0000000..725121b
--- /dev/null
+++ b/pkg/mu/options.go
@@ -0,0 +1,26 @@
+package mu
+
+import (
+ "net/http"
+
+ "golang.org/x/net/http2"
+)
+
+type Config struct {
+ http2Server *http2.Server
+ httpHandler http.Handler
+}
+
+type Option func(cfg *Config)
+
+func WithHTTP2Server(server *http2.Server) Option {
+ return func(cfg *Config) {
+ cfg.http2Server = server
+ }
+}
+
+func WithHTTPHandler(handler http.Handler) Option {
+ return func(cfg *Config) {
+ cfg.httpHandler = handler
+ }
+}
diff --git a/pkg/mu/server.go b/pkg/mu/server.go
new file mode 100644
index 0000000..3f7260b
--- /dev/null
+++ b/pkg/mu/server.go
@@ -0,0 +1,57 @@
+package mu
+
+import (
+ "net/http"
+
+ "github.com/improbable-eng/grpc-web/go/grpcweb"
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/h2c"
+ "google.golang.org/grpc"
+ "nhooyr.io/websocket"
+)
+
+func NewHandler(grpcServer *grpc.Server, opts ...Option) http.Handler {
+ cfg := &Config{
+ http2Server: &http2.Server{},
+ httpHandler: http.DefaultServeMux,
+ }
+ for _, opt := range opts {
+ opt(cfg)
+ }
+
+ return h2c.NewHandler(
+ &Server{
+ grpcServer: grpcServer,
+ webServer: grpcweb.WrapServer(
+ grpcServer,
+ grpcweb.WithWebsockets(true),
+ grpcweb.WithOriginFunc(func(string) bool {
+ return true
+ }),
+ grpcweb.WithWebsocketOriginFunc(func(*http.Request) bool {
+ return true
+ }),
+ grpcweb.WithWebsocketCompressionMode(websocket.CompressionDisabled),
+ ),
+ httpHandler: cfg.httpHandler,
+ },
+ cfg.http2Server,
+ )
+}
+
+type Server struct {
+ grpcServer *grpc.Server
+ webServer *grpcweb.WrappedGrpcServer
+ httpHandler http.Handler
+}
+
+func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ switch {
+ case r.ProtoMajor == 2 && r.Header.Get("Content-Type") == "application/grpc":
+ s.grpcServer.ServeHTTP(w, r)
+ case s.webServer.IsAcceptableGrpcCorsRequest(r) || s.webServer.IsGrpcWebRequest(r) || s.webServer.IsGrpcWebSocketRequest(r):
+ s.webServer.ServeHTTP(w, r)
+ default:
+ s.httpHandler.ServeHTTP(w, r)
+ }
+}
diff --git a/pkg/neohttp/static.go b/pkg/neohttp/static.go
new file mode 100644
index 0000000..5c858c0
--- /dev/null
+++ b/pkg/neohttp/static.go
@@ -0,0 +1,21 @@
+package neohttp
+
+import (
+ "net/http"
+ "os"
+ "path/filepath"
+)
+
+func StaticHandler(dir string) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ filePath := filepath.Join(dir, r.URL.Path)
+
+ if r.URL.Path == "" || r.URL.Path == "/" {
+ filePath = filepath.Join(dir, "index.html")
+ } else if _, err := os.Stat(filePath); err != nil {
+ filePath = filepath.Join(dir, "index.html")
+ }
+
+ http.ServeFile(w, r, filePath)
+ }
+}
diff --git a/proto/go/exploits/api.pb.go b/pkg/proto/exploits/api.pb.go
similarity index 98%
rename from proto/go/exploits/api.pb.go
rename to pkg/proto/exploits/api.pb.go
index 76e67b5..42bd408 100644
--- a/proto/go/exploits/api.pb.go
+++ b/pkg/proto/exploits/api.pb.go
@@ -7,7 +7,7 @@
package exploits
import (
- fileserver "github.com/c4t-but-s4d/neo/v2/proto/go/fileserver"
+ fileserver "github.com/c4t-but-s4d/neo/v2/pkg/proto/fileserver"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
durationpb "google.golang.org/protobuf/types/known/durationpb"
@@ -1351,17 +1351,17 @@ var file_exploits_api_proto_rawDesc = []byte{
0x65, 0x52, 0x75, 0x6e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x71,
0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0x2e,
0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x52, 0x75, 0x6e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69,
- 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x89,
+ 0x62, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x8a,
0x01, 0x0a, 0x0c, 0x63, 0x6f, 0x6d, 0x2e, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0x42,
- 0x08, 0x41, 0x70, 0x69, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, 0x67, 0x69, 0x74,
+ 0x08, 0x41, 0x70, 0x69, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x34, 0x74, 0x2d, 0x62, 0x75, 0x74, 0x2d,
- 0x73, 0x34, 0x64, 0x2f, 0x6e, 0x65, 0x6f, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
- 0x2f, 0x67, 0x6f, 0x2f, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0xa2, 0x02, 0x03, 0x45,
- 0x58, 0x58, 0xaa, 0x02, 0x08, 0x45, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0xca, 0x02, 0x08,
- 0x45, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0xe2, 0x02, 0x14, 0x45, 0x78, 0x70, 0x6c, 0x6f,
- 0x69, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea,
- 0x02, 0x08, 0x45, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
- 0x6f, 0x33,
+ 0x73, 0x34, 0x64, 0x2f, 0x6e, 0x65, 0x6f, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0xa2, 0x02, 0x03,
+ 0x45, 0x58, 0x58, 0xaa, 0x02, 0x08, 0x45, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0xca, 0x02,
+ 0x08, 0x45, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0xe2, 0x02, 0x14, 0x45, 0x78, 0x70, 0x6c,
+ 0x6f, 0x69, 0x74, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+ 0xea, 0x02, 0x08, 0x45, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
+ 0x74, 0x6f, 0x33,
}
var (
diff --git a/proto/go/exploits/api_grpc.pb.go b/pkg/proto/exploits/api_grpc.pb.go
similarity index 100%
rename from proto/go/exploits/api_grpc.pb.go
rename to pkg/proto/exploits/api_grpc.pb.go
diff --git a/proto/go/fileserver/api.pb.go b/pkg/proto/fileserver/api.pb.go
similarity index 90%
rename from proto/go/fileserver/api.pb.go
rename to pkg/proto/fileserver/api.pb.go
index 5be2053..97b5bb0 100644
--- a/proto/go/fileserver/api.pb.go
+++ b/pkg/proto/fileserver/api.pb.go
@@ -132,17 +132,17 @@ var file_fileserver_api_proto_rawDesc = []byte{
0x6c, 0x65, 0x12, 0x14, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e,
0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x1a, 0x16, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d,
- 0x22, 0x00, 0x30, 0x01, 0x42, 0x95, 0x01, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x66, 0x69, 0x6c,
+ 0x22, 0x00, 0x30, 0x01, 0x42, 0x96, 0x01, 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x2e, 0x66, 0x69, 0x6c,
0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x08, 0x41, 0x70, 0x69, 0x50, 0x72, 0x6f, 0x74,
- 0x6f, 0x50, 0x01, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
+ 0x6f, 0x50, 0x01, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x63, 0x34, 0x74, 0x2d, 0x62, 0x75, 0x74, 0x2d, 0x73, 0x34, 0x64, 0x2f, 0x6e, 0x65, 0x6f, 0x2f,
- 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x66, 0x69, 0x6c, 0x65,
- 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0xa2, 0x02, 0x03, 0x46, 0x58, 0x58, 0xaa, 0x02, 0x0a, 0x46,
- 0x69, 0x6c, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0xca, 0x02, 0x0a, 0x46, 0x69, 0x6c, 0x65,
- 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0xe2, 0x02, 0x16, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x65, 0x72,
- 0x76, 0x65, 0x72, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea,
- 0x02, 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72,
- 0x6f, 0x74, 0x6f, 0x33,
+ 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x66, 0x69, 0x6c,
+ 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0xa2, 0x02, 0x03, 0x46, 0x58, 0x58, 0xaa, 0x02, 0x0a,
+ 0x46, 0x69, 0x6c, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0xca, 0x02, 0x0a, 0x46, 0x69, 0x6c,
+ 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0xe2, 0x02, 0x16, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x65,
+ 0x72, 0x76, 0x65, 0x72, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+ 0xea, 0x02, 0x0a, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x62, 0x06, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
diff --git a/proto/go/fileserver/api_grpc.pb.go b/pkg/proto/fileserver/api_grpc.pb.go
similarity index 100%
rename from proto/go/fileserver/api_grpc.pb.go
rename to pkg/proto/fileserver/api_grpc.pb.go
diff --git a/proto/go/logs/api.pb.go b/pkg/proto/logs/api.pb.go
similarity index 61%
rename from proto/go/logs/api.pb.go
rename to pkg/proto/logs/api.pb.go
index b9757c8..bb98b68 100644
--- a/proto/go/logs/api.pb.go
+++ b/pkg/proto/logs/api.pb.go
@@ -10,6 +10,7 @@ import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
emptypb "google.golang.org/protobuf/types/known/emptypb"
+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
@@ -26,11 +27,12 @@ type LogLine struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- Exploit string `protobuf:"bytes,1,opt,name=exploit,proto3" json:"exploit,omitempty"`
- Version int64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
- Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
- Level string `protobuf:"bytes,4,opt,name=level,proto3" json:"level,omitempty"`
- Team string `protobuf:"bytes,5,opt,name=team,proto3" json:"team,omitempty"`
+ Exploit string `protobuf:"bytes,1,opt,name=exploit,proto3" json:"exploit,omitempty"`
+ Version int64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
+ Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"`
+ Level string `protobuf:"bytes,4,opt,name=level,proto3" json:"level,omitempty"`
+ Team string `protobuf:"bytes,5,opt,name=team,proto3" json:"team,omitempty"`
+ Timestamp *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
}
func (x *LogLine) Reset() {
@@ -100,6 +102,13 @@ func (x *LogLine) GetTeam() string {
return ""
}
+func (x *LogLine) GetTimestamp() *timestamppb.Timestamp {
+ if x != nil {
+ return x.Timestamp
+ }
+ return nil
+}
+
type AddLogLinesRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
@@ -152,8 +161,10 @@ type SearchLogLinesRequest struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- Exploit string `protobuf:"bytes,1,opt,name=exploit,proto3" json:"exploit,omitempty"`
- Version int64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
+ Exploit string `protobuf:"bytes,1,opt,name=exploit,proto3" json:"exploit,omitempty"`
+ Version int64 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
+ Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"`
+ LastToken string `protobuf:"bytes,4,opt,name=last_token,json=lastToken,proto3" json:"last_token,omitempty"`
}
func (x *SearchLogLinesRequest) Reset() {
@@ -202,12 +213,27 @@ func (x *SearchLogLinesRequest) GetVersion() int64 {
return 0
}
+func (x *SearchLogLinesRequest) GetLimit() int64 {
+ if x != nil {
+ return x.Limit
+ }
+ return 0
+}
+
+func (x *SearchLogLinesRequest) GetLastToken() string {
+ if x != nil {
+ return x.LastToken
+ }
+ return ""
+}
+
type SearchLogLinesResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
- Lines []*LogLine `protobuf:"bytes,1,rep,name=lines,proto3" json:"lines,omitempty"`
+ Lines []*LogLine `protobuf:"bytes,1,rep,name=lines,proto3" json:"lines,omitempty"`
+ LastToken string `protobuf:"bytes,2,opt,name=last_token,json=lastToken,proto3" json:"last_token,omitempty"`
}
func (x *SearchLogLinesResponse) Reset() {
@@ -249,33 +275,51 @@ func (x *SearchLogLinesResponse) GetLines() []*LogLine {
return nil
}
+func (x *SearchLogLinesResponse) GetLastToken() string {
+ if x != nil {
+ return x.LastToken
+ }
+ return ""
+}
+
var File_logs_api_proto protoreflect.FileDescriptor
var file_logs_api_proto_rawDesc = []byte{
0x0a, 0x0e, 0x6c, 0x6f, 0x67, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72,
- 0x6f, 0x74, 0x6f, 0x22, 0x81, 0x01, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65, 0x12,
- 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
- 0x52, 0x07, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72,
- 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73,
- 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03,
- 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a,
- 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x65,
- 0x76, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x61, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28,
- 0x09, 0x52, 0x04, 0x74, 0x65, 0x61, 0x6d, 0x22, 0x39, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x4c, 0x6f,
- 0x67, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a,
- 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c,
- 0x6f, 0x67, 0x73, 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, 0x6e,
- 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x15, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x4c,
- 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65,
- 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x78,
- 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
- 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22,
- 0x3d, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65,
- 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x6c, 0x69, 0x6e,
- 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e,
- 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x32, 0x9d,
+ 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
+ 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70,
+ 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbb, 0x01, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65,
+ 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
+ 0x09, 0x52, 0x07, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x69, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
+ 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72,
+ 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18,
+ 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14,
+ 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c,
+ 0x65, 0x76, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x61, 0x6d, 0x18, 0x05, 0x20, 0x01,
+ 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x61, 0x6d, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65,
+ 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
+ 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
+ 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
+ 0x6d, 0x70, 0x22, 0x39, 0x0a, 0x12, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65,
+ 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x05, 0x6c, 0x69, 0x6e, 0x65,
+ 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x4c,
+ 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x22, 0x80, 0x01,
+ 0x0a, 0x15, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65, 0x73,
+ 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x6c, 0x6f,
+ 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x69,
+ 0x74, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
+ 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c,
+ 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69,
+ 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
+ 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
+ 0x22, 0x5c, 0x0a, 0x16, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e,
+ 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x6c, 0x69,
+ 0x6e, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6c, 0x6f, 0x67, 0x73,
+ 0x2e, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65, 0x52, 0x05, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x12,
+ 0x1d, 0x0a, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20,
+ 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0x9d,
0x01, 0x0a, 0x07, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x0b, 0x41, 0x64,
0x64, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x18, 0x2e, 0x6c, 0x6f, 0x67, 0x73,
0x2e, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75,
@@ -285,15 +329,15 @@ var file_logs_api_proto_rawDesc = []byte{
0x1b, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c, 0x6f, 0x67,
0x4c, 0x69, 0x6e, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6c,
0x6f, 0x67, 0x73, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x4c, 0x69, 0x6e,
- 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x71,
+ 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x72,
0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x2e, 0x6c, 0x6f, 0x67, 0x73, 0x42, 0x08, 0x41, 0x70, 0x69, 0x50,
- 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
+ 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x63, 0x34, 0x74, 0x2d, 0x62, 0x75, 0x74, 0x2d, 0x73, 0x34, 0x64, 0x2f, 0x6e,
- 0x65, 0x6f, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x6c,
- 0x6f, 0x67, 0x73, 0xa2, 0x02, 0x03, 0x4c, 0x58, 0x58, 0xaa, 0x02, 0x04, 0x4c, 0x6f, 0x67, 0x73,
- 0xca, 0x02, 0x04, 0x4c, 0x6f, 0x67, 0x73, 0xe2, 0x02, 0x10, 0x4c, 0x6f, 0x67, 0x73, 0x5c, 0x47,
- 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x04, 0x4c, 0x6f, 0x67,
- 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x65, 0x6f, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f,
+ 0x6c, 0x6f, 0x67, 0x73, 0xa2, 0x02, 0x03, 0x4c, 0x58, 0x58, 0xaa, 0x02, 0x04, 0x4c, 0x6f, 0x67,
+ 0x73, 0xca, 0x02, 0x04, 0x4c, 0x6f, 0x67, 0x73, 0xe2, 0x02, 0x10, 0x4c, 0x6f, 0x67, 0x73, 0x5c,
+ 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x04, 0x4c, 0x6f,
+ 0x67, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -314,20 +358,22 @@ var file_logs_api_proto_goTypes = []interface{}{
(*AddLogLinesRequest)(nil), // 1: logs.AddLogLinesRequest
(*SearchLogLinesRequest)(nil), // 2: logs.SearchLogLinesRequest
(*SearchLogLinesResponse)(nil), // 3: logs.SearchLogLinesResponse
- (*emptypb.Empty)(nil), // 4: google.protobuf.Empty
+ (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp
+ (*emptypb.Empty)(nil), // 5: google.protobuf.Empty
}
var file_logs_api_proto_depIdxs = []int32{
- 0, // 0: logs.AddLogLinesRequest.lines:type_name -> logs.LogLine
- 0, // 1: logs.SearchLogLinesResponse.lines:type_name -> logs.LogLine
- 1, // 2: logs.Service.AddLogLines:input_type -> logs.AddLogLinesRequest
- 2, // 3: logs.Service.SearchLogLines:input_type -> logs.SearchLogLinesRequest
- 4, // 4: logs.Service.AddLogLines:output_type -> google.protobuf.Empty
- 3, // 5: logs.Service.SearchLogLines:output_type -> logs.SearchLogLinesResponse
- 4, // [4:6] is the sub-list for method output_type
- 2, // [2:4] is the sub-list for method input_type
- 2, // [2:2] is the sub-list for extension type_name
- 2, // [2:2] is the sub-list for extension extendee
- 0, // [0:2] is the sub-list for field type_name
+ 4, // 0: logs.LogLine.timestamp:type_name -> google.protobuf.Timestamp
+ 0, // 1: logs.AddLogLinesRequest.lines:type_name -> logs.LogLine
+ 0, // 2: logs.SearchLogLinesResponse.lines:type_name -> logs.LogLine
+ 1, // 3: logs.Service.AddLogLines:input_type -> logs.AddLogLinesRequest
+ 2, // 4: logs.Service.SearchLogLines:input_type -> logs.SearchLogLinesRequest
+ 5, // 5: logs.Service.AddLogLines:output_type -> google.protobuf.Empty
+ 3, // 6: logs.Service.SearchLogLines:output_type -> logs.SearchLogLinesResponse
+ 5, // [5:7] is the sub-list for method output_type
+ 3, // [3:5] is the sub-list for method input_type
+ 3, // [3:3] is the sub-list for extension type_name
+ 3, // [3:3] is the sub-list for extension extendee
+ 0, // [0:3] is the sub-list for field type_name
}
func init() { file_logs_api_proto_init() }
diff --git a/proto/go/logs/api_grpc.pb.go b/pkg/proto/logs/api_grpc.pb.go
similarity index 100%
rename from proto/go/logs/api_grpc.pb.go
rename to pkg/proto/logs/api_grpc.pb.go
diff --git a/pkg/pubsub/pubsub_test.go b/pkg/pubsub/pubsub_test.go
index f2726bf..c4c5a99 100644
--- a/pkg/pubsub/pubsub_test.go
+++ b/pkg/pubsub/pubsub_test.go
@@ -113,7 +113,7 @@ func TestPubSub_slowpoke(t *testing.T) {
wgSlow.Wait()
}()
- slowSub := p.Subscribe(func(msg string) error {
+ slowSub := p.Subscribe(func(string) error {
defer wgSlow.Done()
select {
@@ -153,7 +153,7 @@ func TestPubSub_slowpoke(t *testing.T) {
func TestPubSub_unsubscribe(t *testing.T) {
p := NewPubSub[string]()
- sub1 := p.Subscribe(func(msg string) error {
+ sub1 := p.Subscribe(func(string) error {
t.Error("first subscriber must not be called")
return nil
})
diff --git a/pkg/testutils/testutils.go b/pkg/testutils/testutils.go
index c5b25f4..d764db1 100644
--- a/pkg/testutils/testutils.go
+++ b/pkg/testutils/testutils.go
@@ -46,9 +46,8 @@ func RandomString(length int) string {
return RandomStringWithCharset(length, charset)
}
-func RandomInt(min, max int) int {
- n := seededRand.Intn(max - min)
- return min + n
+func RandomInt(mn, mx int) int {
+ return mn + seededRand.Intn(mx-mn)
}
func RandomIP() string {
diff --git a/proto/buf.gen.yaml b/proto/buf.gen.yaml
index 9d3ad12..d17b9fa 100644
--- a/proto/buf.gen.yaml
+++ b/proto/buf.gen.yaml
@@ -2,14 +2,29 @@ version: v1
managed:
enabled: true
go_package_prefix:
- default: github.com/c4t-but-s4d/neo/v2/proto/go
+ default: github.com/c4t-but-s4d/neo/v2/pkg/proto
plugins:
# Go
- name: go
- out: go
+ out: ../pkg/proto
opt:
- paths=source_relative
- name: go-grpc
- out: go
+ out: ../pkg/proto
opt:
- paths=source_relative
+
+ # Typescript
+ - plugin: buf.build/community/stephenh-ts-proto
+ out: ../front/src/proto
+ opt:
+ - enumsAsLiterals=true
+ - env=browser
+ - esModuleInterop=true
+ - forceLong=long
+ - oneof=unions
+ - outputServices=nice-grpc,outputServices=generic-definitions
+ - unrecognizedEnum=false
+ - removeEnumPrefix=true
+ - useDate=false
+ - useMapType=true
diff --git a/proto/buf.yaml b/proto/buf.yaml
index 310c5fd..2119a15 100644
--- a/proto/buf.yaml
+++ b/proto/buf.yaml
@@ -4,7 +4,7 @@ breaking:
- WIRE_JSON
lint:
use:
- - DEFAULT
+ - STANDARD
except:
- PACKAGE_VERSION_SUFFIX
- RPC_REQUEST_STANDARD_NAME
diff --git a/proto/logs/api.proto b/proto/logs/api.proto
index 0dafb6a..8c96692 100644
--- a/proto/logs/api.proto
+++ b/proto/logs/api.proto
@@ -4,29 +4,34 @@ package logs;
option go_package = "logs";
import "google/protobuf/empty.proto";
+import "google/protobuf/timestamp.proto";
message LogLine {
- string exploit = 1;
- int64 version = 2;
- string message = 3;
- string level = 4;
- string team = 5;
+ string exploit = 1;
+ int64 version = 2;
+ string message = 3;
+ string level = 4;
+ string team = 5;
+ google.protobuf.Timestamp timestamp = 6;
}
message AddLogLinesRequest {
- repeated LogLine lines = 1;
+ repeated LogLine lines = 1;
}
message SearchLogLinesRequest {
- string exploit = 1;
- int64 version = 2;
+ string exploit = 1;
+ int64 version = 2;
+ int64 limit = 3;
+ string last_token = 4;
}
message SearchLogLinesResponse {
- repeated LogLine lines = 1;
+ repeated LogLine lines = 1;
+ string last_token = 2;
}
service Service {
- rpc AddLogLines(AddLogLinesRequest) returns (google.protobuf.Empty) {}
- rpc SearchLogLines(SearchLogLinesRequest) returns (stream SearchLogLinesResponse) {}
+ rpc AddLogLines(AddLogLinesRequest) returns (google.protobuf.Empty) {}
+ rpc SearchLogLines(SearchLogLinesRequest) returns (stream SearchLogLinesResponse) {}
}