diff options
author | innokentii <innokentii@yandex-team.com> | 2022-12-08 23:14:28 +0300 |
---|---|---|
committer | innokentii <innokentii@yandex-team.com> | 2022-12-08 23:14:28 +0300 |
commit | 9323fe28059355bafc517301a3e7a00fc0367f08 (patch) | |
tree | 1d606af915ed01d3f7609a9c7861063c56976910 | |
parent | 8fdf497e036a8b96b30dcc1c0db56822d4e9244f (diff) | |
download | ydb-9323fe28059355bafc517301a3e7a00fc0367f08.tar.gz |
Implement audit in Console
add basic web-interface for Console audit
add basic TEvConfigure logging
27 files changed, 1410 insertions, 43 deletions
diff --git a/ydb/core/client/server/msgbus_server_console.cpp b/ydb/core/client/server/msgbus_server_console.cpp index a7c1effc2c..0da13ed4a1 100644 --- a/ydb/core/client/server/msgbus_server_console.cpp +++ b/ydb/core/client/server/msgbus_server_console.cpp @@ -109,6 +109,7 @@ public: } else if (Request.HasConfigureRequest()) { auto request = MakeHolder<TEvConsole::TEvConfigureRequest>(); request->Record.CopyFrom(Request.GetConfigureRequest()); + request->Record.SetUserToken(TBase::GetSerializedToken()); NTabletPipe::SendData(ctx, ConsolePipe, request.Release()); } else if (Request.HasGetConfigItemsRequest()) { auto request = MakeHolder<TEvConsole::TEvGetConfigItemsRequest>(); diff --git a/ydb/core/cms/CMakeLists.txt b/ydb/core/cms/CMakeLists.txt index 9f9a311e12..b5c8767f45 100644 --- a/ydb/core/cms/CMakeLists.txt +++ b/ydb/core/cms/CMakeLists.txt @@ -93,15 +93,16 @@ target_link_libraries(ydb-core-cms.global PUBLIC tools-enum_parser-enum_serialization_runtime ) target_sources(ydb-core-cms.global PRIVATE - ${CMAKE_BINARY_DIR}/ydb/core/cms/117417182f73c245478c9903130ed8f2.cpp + ${CMAKE_BINARY_DIR}/ydb/core/cms/d9287e52aed5bc947231449c57c31fe9.cpp ) resources(ydb-core-cms.global - ${CMAKE_BINARY_DIR}/ydb/core/cms/117417182f73c245478c9903130ed8f2.cpp + ${CMAKE_BINARY_DIR}/ydb/core/cms/d9287e52aed5bc947231449c57c31fe9.cpp INPUTS ${CMAKE_SOURCE_DIR}/ydb/core/cms/ui/index.html ${CMAKE_SOURCE_DIR}/ydb/core/cms/ui/cms.css ${CMAKE_SOURCE_DIR}/ydb/core/cms/ui/cms.js ${CMAKE_SOURCE_DIR}/ydb/core/cms/ui/cms_log.js + ${CMAKE_SOURCE_DIR}/ydb/core/cms/ui/console_log.js ${CMAKE_SOURCE_DIR}/ydb/core/cms/ui/common.css ${CMAKE_SOURCE_DIR}/ydb/core/cms/ui/common.js ${CMAKE_SOURCE_DIR}/ydb/core/cms/ui/configs.js @@ -131,6 +132,7 @@ resources(ydb-core-cms.global cms/ui/cms.css cms/ui/cms.js cms/ui/cms_log.js + cms/ui/console_log.js cms/ui/common.css cms/ui/common.js cms/ui/configs.js diff --git a/ydb/core/cms/console/CMakeLists.txt b/ydb/core/cms/console/CMakeLists.txt index feec894070..e56e5dc033 100644 --- a/ydb/core/cms/console/CMakeLists.txt +++ b/ydb/core/cms/console/CMakeLists.txt @@ -51,6 +51,8 @@ target_sources(core-cms-console PRIVATE ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/console__create_tenant.cpp ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/console__init_scheme.cpp ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/console__load_state.cpp + ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/console__get_log_tail.cpp + ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/console__log_cleanup.cpp ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/console__remove_computational_units.cpp ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/console__remove_config_subscription.cpp ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/console__remove_config_subscriptions.cpp @@ -70,6 +72,7 @@ target_sources(core-cms-console PRIVATE ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/http.cpp ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/immediate_controls_configurator.cpp ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/log_settings_configurator.cpp + ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/logger.cpp ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/modifications_validator.cpp ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/net_classifier_updater.cpp ${CMAKE_SOURCE_DIR}/ydb/core/cms/console/shared_cache_configurator.cpp diff --git a/ydb/core/cms/console/config_index.cpp b/ydb/core/cms/console/config_index.cpp index 98ede70c6f..56186d478e 100644 --- a/ydb/core/cms/console/config_index.cpp +++ b/ydb/core/cms/console/config_index.cpp @@ -583,7 +583,7 @@ void TConfigModifications::DeepCopyFrom(const TConfigModifications &other) void TConfigModifications::ApplyTo(TConfigIndex &index) const { - for (auto id : RemovedItems) + for (auto &[id, _] : RemovedItems) index.RemoveItem(id); for (auto &pr : ModifiedItems) index.RemoveItem(pr.first); diff --git a/ydb/core/cms/console/config_index.h b/ydb/core/cms/console/config_index.h index 83f5d38426..726c30bd12 100644 --- a/ydb/core/cms/console/config_index.h +++ b/ydb/core/cms/console/config_index.h @@ -548,7 +548,7 @@ struct TConfigModifications { TVector<TConfigItem::TPtr> AddedItems; THashMap<ui64, TConfigItem::TPtr> ModifiedItems; - THashSet<ui64> RemovedItems; + THashMap<ui64, TConfigItem::TPtr> RemovedItems; }; struct TSubscription : public TThrRefBase { diff --git a/ydb/core/cms/console/console.h b/ydb/core/cms/console/console.h index d0fa7724b5..7370d21b20 100644 --- a/ydb/core/cms/console/console.h +++ b/ydb/core/cms/console/console.h @@ -41,6 +41,7 @@ struct TEvConsole { EvConfigSubscriptionCanceled, EvConfigSubscriptionNotification, EvUpdateTenantPoolConfig, + EvGetLogTailRequest, // responses EvCreateTenantResponse = EvCreateTenantRequest + 1024, @@ -70,6 +71,7 @@ struct TEvConsole { EvToggleConfigValidatorResponse, EvConfigSubscriptionResponse, EvConfigSubscriptionError, + EvGetLogTailResponse, EvEnd }; @@ -270,6 +272,13 @@ struct TEvConsole { struct TEvToggleConfigValidatorRequest : public TEventShortDebugPB<TEvToggleConfigValidatorRequest, NKikimrConsole::TToggleConfigValidatorRequest, EvToggleConfigValidatorRequest> {}; struct TEvToggleConfigValidatorResponse : public TEventShortDebugPB<TEvToggleConfigValidatorResponse, NKikimrConsole::TToggleConfigValidatorResponse, EvToggleConfigValidatorResponse> {}; + + ////////////////////////////////////////////////// + // AUDIT + ////////////////////////////////////////////////// + struct TEvGetLogTailRequest : public TEventPB<TEvGetLogTailRequest, NKikimrConsole::TGetLogTailRequest, EvGetLogTailRequest> {}; + + struct TEvGetLogTailResponse : public TEventPB<TEvGetLogTailResponse, NKikimrConsole::TGetLogTailResponse, EvGetLogTailResponse> {}; }; IActor *CreateConsole(const TActorId &tablet, TTabletStorageInfo *info); diff --git a/ydb/core/cms/console/console__configure.cpp b/ydb/core/cms/console/console__configure.cpp index f28417e760..5a238d7533 100644 --- a/ydb/core/cms/console/console__configure.cpp +++ b/ydb/core/cms/console/console__configure.cpp @@ -1,6 +1,8 @@ #include "console_configs_manager.h" #include "modifications_validator.h" +#include <ydb/library/aclib/aclib.h> + namespace NKikimr { namespace NConsole { @@ -196,7 +198,7 @@ public: return false; } - Self->PendingConfigModifications.RemovedItems.insert(item->Id); + Self->PendingConfigModifications.RemovedItems.emplace(item->Id, item); return true; } @@ -208,7 +210,7 @@ public: for (auto &cookie : cookies.GetCookies()) Self->ConfigIndex.CollectItemsByCookie(cookie, THashSet<ui32>(), items); for (auto &item : items) - Self->PendingConfigModifications.RemovedItems.insert(item->Id); + Self->PendingConfigModifications.RemovedItems.emplace(item->Id, item); return true; } @@ -487,6 +489,8 @@ public: Response->Record.MutableStatus()->SetCode(Ydb::StatusIds::SUCCESS); // Fill affected configs. + auto LogData = NKikimrConsole::TLogRecordData{}; + TConfigsConfig config(Self->Config); config.ValidationLevel = NKikimrConsole::VALIDATE_TENANTS_AND_NODE_TYPES; TModificationsValidator affectedChecker(Self->ConfigIndex, @@ -508,6 +512,9 @@ public: *entry.MutableNewConfig()); } + // Collect actually affected config kinds + THashSet<ui32> AffectedKinds; + // Dry run stops here. Further processing modifies internal state. if (rec.GetDryRun()) { Self->PendingConfigModifications.Clear(); @@ -519,16 +526,64 @@ public: item->Id = Self->NextConfigItemId++; item->Generation = 1; Response->Record.AddAddedItemIds(item->Id); + + AffectedKinds.insert(item->Kind); } // Increment generation for modified items. - for (auto &pr : Self->PendingConfigModifications.ModifiedItems) - ++pr.second->Generation; + for (auto &[_, item] : Self->PendingConfigModifications.ModifiedItems) { + ++item->Generation; + // + AffectedKinds.insert(item->Kind); + } + + for (auto &[_, item] : Self->PendingConfigModifications.RemovedItems) { + AffectedKinds.insert(item->Kind); + } + + // Get user sid for audit and cleanup message + TString userSID; + if (Request->Get()->Record.HasUserToken()) { + NACLib::TUserToken userToken(Request->Get()->Record.GetUserToken()); + userSID = userToken.GetUserSID(); + Request->Get()->Record.ClearUserToken(); + } + + + // Calculate actually affected configs for logging + TModificationsValidator actualAffectedChecker(Self->ConfigIndex, + Self->PendingConfigModifications, + config); + + TDynBitMap kinds; + + for (auto kind : AffectedKinds) { + kinds.Set(kind); + LogData.AddAffectedKinds(kind); + } + + auto actualAffected = actualAffectedChecker.ComputeAffectedConfigs(kinds, true); + + for (auto &item : actualAffected) { + auto &logEntry = *LogData.AddAffectedConfigs(); + logEntry.SetTenant(item.Tenant); + logEntry.SetNodeType(item.NodeType); + + actualAffectedChecker.BuildConfigs(kinds, + item.Tenant, + item.NodeType, + *logEntry.MutableOldConfig(), + *logEntry.MutableNewConfig()); + } // Update database. Self->DbApplyPendingConfigModifications(txc, ctx); Self->DbUpdateNextConfigItemId(txc, ctx); + // Log command and changes + LogData.MutableAction()->Swap(&Request->Get()->Record); + Self->Logger.DbLogData(userSID, LogData, txc, ctx); + return true; } diff --git a/ydb/core/cms/console/console__get_log_tail.cpp b/ydb/core/cms/console/console__get_log_tail.cpp new file mode 100644 index 0000000000..7ed8141a3e --- /dev/null +++ b/ydb/core/cms/console/console__get_log_tail.cpp @@ -0,0 +1,63 @@ +#include "console_configs_manager.h" + +namespace NKikimr { +namespace NConsole { + +using namespace NKikimrConsole; + +class TConfigsManager::TTxGetLogTail : public TTransactionBase<TConfigsManager> { +public: + TTxGetLogTail(TConfigsManager *self, + TEvConsole::TEvGetLogTailRequest::TPtr &ev) + : TBase(self) + , Request(std::move(ev)) + { + } + + bool Execute(TTransactionContext &txc, const TActorContext &ctx) override + { + auto &req = Request->Get()->Record; + + LOG_DEBUG_S(ctx, NKikimrServices::CMS_CONFIGS, + "TTxGetLogTail Execute " << req.ShortDebugString()); + + TVector<NKikimrConsole::TLogRecord> records; + if (!Self->Logger.DbLoadLogTail(req.GetLogFilter(), records, txc)) + return false; + + LOG_DEBUG_S(ctx, NKikimrServices::CMS_CONFIGS, + "TTxGetLogTail found " << records.size() + << " matching log records"); + + Response = MakeHolder<TEvConsole::TEvGetLogTailResponse>(); + auto &rec = Response->Record; + rec.MutableStatus()->SetCode(Ydb::StatusIds::SUCCESS); + for (auto it = records.rbegin(); it != records.rend(); ++it) { + auto &entry = *rec.AddLogRecords(); + entry.Swap(&*it); + } + + return true; + } + + void Complete(const TActorContext &ctx) override + { + LOG_DEBUG(ctx, NKikimrServices::CMS_CONFIGS, "TTxGetLogTail Complete"); + + ctx.Send(Request->Sender, Response.Release()); + + Self->TxProcessor->TxCompleted(this, ctx); + } + +private: + TEvConsole::TEvGetLogTailRequest::TPtr Request; + THolder<TEvConsole::TEvGetLogTailResponse> Response; +}; + +ITransaction *TConfigsManager::CreateTxGetLogTail(TEvConsole::TEvGetLogTailRequest::TPtr &ev) +{ + return new TTxGetLogTail(this, ev); +} + +} // NConsole +} // NKikimr diff --git a/ydb/core/cms/console/console__log_cleanup.cpp b/ydb/core/cms/console/console__log_cleanup.cpp new file mode 100644 index 0000000000..821807e554 --- /dev/null +++ b/ydb/core/cms/console/console__log_cleanup.cpp @@ -0,0 +1,40 @@ +#include "console_configs_manager.h" + +namespace NKikimr { +namespace NConsole { + +class TConfigsManager::TTxLogCleanup : public TTransactionBase<TConfigsManager> { +public: + TTxLogCleanup(TConfigsManager *self) + : TBase(self) + { + } + + bool Execute(TTransactionContext &txc, const TActorContext &ctx) override + { + LOG_DEBUG_S(ctx, NKikimrServices::CMS_CONFIGS, + "TTxLogCleanup Execute"); + + const ui32 maxConsoleLogEntries = 25000; + + return Self->Logger.DbCleanupLog(maxConsoleLogEntries, txc, ctx); + } + + void Complete(const TActorContext &ctx) override + { + LOG_DEBUG(ctx, NKikimrServices::CMS_CONFIGS, + "TTxLogCleanup Complete"); + + Self->ScheduleLogCleanup(ctx); + + Self->TxProcessor->TxCompleted(this, ctx); + } +}; + +ITransaction *TConfigsManager::CreateTxLogCleanup() +{ + return new TTxLogCleanup(this); +} + +} // NConsole +} // NKikimr diff --git a/ydb/core/cms/console/console__scheme.h b/ydb/core/cms/console/console__scheme.h index c4b32b5294..ea9d275d84 100644 --- a/ydb/core/cms/console/console__scheme.h +++ b/ydb/core/cms/console/console__scheme.h @@ -101,6 +101,16 @@ struct Schema : NIceDb::Schema { using TColumns = TableColumns<Tenant, Host, Port, Kind>; }; + struct LogRecords : Table<7> { + struct Id : Column<1, NScheme::NTypeIds::Uint64> {}; + struct Timestamp : Column<2, NScheme::NTypeIds::Uint64> {}; + struct UserSID : Column<3, NScheme::NTypeIds::String> {}; + struct Data : Column<4, NScheme::NTypeIds::String> {}; + + using TKey = TableKey<Id>; + using TColumns = TableColumns<Id, Timestamp, UserSID, Data>; + }; + struct ConfigItems : Table<100> { struct Id : Column<1, NScheme::NTypeIds::Uint64> {}; struct Generation : Column<2, NScheme::NTypeIds::Uint64> {}; @@ -143,7 +153,7 @@ struct Schema : NIceDb::Schema { }; using TTables = SchemaTables<Config, Tenants, TenantPools, TenantUnits, RemovedTenants, - RegisteredUnits, ConfigItems, ConfigSubscriptions, DisabledValidators>; + RegisteredUnits, LogRecords, ConfigItems, ConfigSubscriptions, DisabledValidators>; using TSettings = SchemaSettings<ExecutorLogBatching<true>, ExecutorLogFlushPeriod<TDuration::MicroSeconds(512).GetValue()>>; }; diff --git a/ydb/core/cms/console/console_configs_manager.cpp b/ydb/core/cms/console/console_configs_manager.cpp index fdf490870f..90288dac9e 100644 --- a/ydb/core/cms/console/console_configs_manager.cpp +++ b/ydb/core/cms/console/console_configs_manager.cpp @@ -71,8 +71,8 @@ void TConfigsManager::ApplyPendingConfigModifications(const TActorContext &ctx, { LOG_DEBUG(ctx, NKikimrServices::CMS_CONFIGS, "Applying pending config modifications"); - for (auto id : PendingConfigModifications.RemovedItems) - LOG_DEBUG_S(ctx, NKikimrServices::CMS_CONFIGS, "Remove " << ConfigIndex.GetItem(id)->ToString()); + for (auto &pr : PendingConfigModifications.RemovedItems) + LOG_DEBUG_S(ctx, NKikimrServices::CMS_CONFIGS, "Remove " << ConfigIndex.GetItem(pr.first)->ToString()); for (auto &pr : PendingConfigModifications.ModifiedItems) LOG_DEBUG_S(ctx, NKikimrServices::CMS_CONFIGS, "Remove modified " << pr.second->ToString()); for (auto &pr : PendingConfigModifications.ModifiedItems) @@ -272,7 +272,7 @@ void TConfigsManager::DbApplyPendingConfigModifications(TTransactionContext &txc DbUpdateItem(item, txc, ctx); for (auto &pr : PendingConfigModifications.ModifiedItems) DbUpdateItem(pr.second, txc, ctx); - for (auto id : PendingConfigModifications.RemovedItems) + for (auto &[id, _] : PendingConfigModifications.RemovedItems) DbRemoveItem(id, txc, ctx); } @@ -295,6 +295,8 @@ bool TConfigsManager::DbLoadState(TTransactionContext &txc, NIceDb::TNiceDb db(txc.DB); auto nextConfigItemIdRow = db.Table<Schema::Config>().Key(TConsole::ConfigKeyNextConfigItemId).Select<Schema::Config::Value>(); auto nextSubscriptionIdRow = db.Table<Schema::Config>().Key(TConsole::ConfigKeyNextSubscriptionId).Select<Schema::Config::Value>(); + auto nextLogItemIdRow = db.Table<Schema::Config>().Key(TConsole::ConfigKeyNextLogItemId).Select<Schema::Config::Value>(); + auto minLogItemIdRow = db.Table<Schema::Config>().Key(TConsole::ConfigKeyMinLogItemId).Select<Schema::Config::Value>(); auto configItemRowset = db.Table<Schema::ConfigItems>().Range().Select<Schema::ConfigItems::TColumns>(); auto subscriptionRowset = db.Table<Schema::ConfigSubscriptions>().Range().Select<Schema::ConfigSubscriptions::TColumns>(); auto validatorsRowset = db.Table<Schema::DisabledValidators>().Range().Select<Schema::DisabledValidators::TColumns>(); @@ -320,6 +322,16 @@ bool TConfigsManager::DbLoadState(TTransactionContext &txc, NextSubscriptionId = 1; } + if (nextLogItemIdRow.IsValid()) { + TString value = nextLogItemIdRow.GetValue<Schema::Config::Value>(); + Logger.SetNextLogItemId(FromString<ui64>(value)); + } + + if (minLogItemIdRow.IsValid()) { + TString value = minLogItemIdRow.GetValue<Schema::Config::Value>(); + Logger.SetMinLogItemId(FromString<ui64>(value)); + } + while (!configItemRowset.EndOfSet()) { ui64 id = configItemRowset.GetValue<Schema::ConfigItems::Id>(); ui64 generation = configItemRowset.GetValue<Schema::ConfigItems::Generation>(); @@ -508,6 +520,11 @@ void TConfigsManager::DbUpdateSubscriptionLastProvidedConfig(ui64 id, .Update(NIceDb::TUpdate<Schema::ConfigSubscriptions::LastProvidedConfig>(configId.ItemIds)); } +void TConfigsManager::Handle(TEvConsole::TEvGetLogTailRequest::TPtr &ev, const TActorContext &ctx) +{ + TxProcessor->ProcessTx(CreateTxGetLogTail(ev), ctx); +} + void TConfigsManager::Handle(TEvConsole::TEvAddConfigSubscriptionRequest::TPtr &ev, const TActorContext &ctx) { TxProcessor->ProcessTx(CreateTxAddConfigSubscription(ev), ctx); @@ -574,6 +591,7 @@ void TConfigsManager::Handle(TEvPrivate::TEvStateLoaded::TPtr &/*ev*/, const TAc ctx.Send(ConfigsProvider, new TConfigsProvider::TEvPrivate::TEvSetConfigs(ConfigIndex.GetConfigItems())); ctx.Send(ConfigsProvider, new TConfigsProvider::TEvPrivate::TEvSetSubscriptions(SubscriptionIndex.GetSubscriptions())); ctx.Send(GetNameserviceActorId(), new TEvInterconnect::TEvListNodes()); + ScheduleLogCleanup(ctx); } void TConfigsManager::Handle(TEvPrivate::TEvCleanupSubscriptions::TPtr &/*ev*/, const TActorContext &ctx) @@ -596,5 +614,19 @@ void TConfigsManager::ScheduleSubscriptionsCleanup(const TActorContext &ctx) SubscriptionsCleanupTimerCookieHolder.Get()); } +void TConfigsManager::CleanupLog(const TActorContext &ctx) +{ + TxProcessor->ProcessTx(CreateTxLogCleanup(), ctx); +} + +void TConfigsManager::ScheduleLogCleanup(const TActorContext &ctx) +{ + LogCleanupTimerCookieHolder.Reset(ISchedulerCookie::Make2Way()); + CreateLongTimer(ctx, TDuration::Minutes(15), + new IEventHandle(SelfId(), SelfId(), new TEvPrivate::TEvCleanupLog), + AppData(ctx)->SystemPoolId, + LogCleanupTimerCookieHolder.Get()); +} + } // namespace NConsole } // namespace NKikimr diff --git a/ydb/core/cms/console/console_configs_manager.h b/ydb/core/cms/console/console_configs_manager.h index 2dca13be26..650ce47441 100644 --- a/ydb/core/cms/console/console_configs_manager.h +++ b/ydb/core/cms/console/console_configs_manager.h @@ -5,6 +5,7 @@ #include "config_index.h" #include "configs_config.h" #include "console.h" +#include "logger.h" #include "tx_processor.h" #include <ydb/core/actorlib_impl/long_timer.h> @@ -32,6 +33,7 @@ public: enum EEv { EvStateLoaded = EventSpaceBegin(TKikimrEvents::ES_PRIVATE), EvCleanupSubscriptions, + EvCleanupLog, EvEnd }; @@ -40,6 +42,8 @@ public: struct TEvStateLoaded : public TEventLocal<TEvStateLoaded, EvStateLoaded> {}; struct TEvCleanupSubscriptions : public TEventLocal<TEvCleanupSubscriptions, EvCleanupSubscriptions> {}; + + struct TEvCleanupLog : public TEventLocal<TEvCleanupLog, EvCleanupLog> {}; }; public: @@ -103,6 +107,8 @@ private: class TTxReplaceConfigSubscriptions; class TTxToggleConfigValidator; class TTxUpdateLastProvidedConfig; + class TTxGetLogTail; + class TTxLogCleanup; ITransaction *CreateTxAddConfigSubscription(TEvConsole::TEvAddConfigSubscriptionRequest::TPtr &ev); ITransaction *CreateTxCleanupSubscriptions(TEvInterconnect::TEvNodesInfo::TPtr &ev); @@ -112,6 +118,8 @@ private: ITransaction *CreateTxReplaceConfigSubscriptions(TEvConsole::TEvReplaceConfigSubscriptionsRequest::TPtr &ev); ITransaction *CreateTxToggleConfigValidator(TEvConsole::TEvToggleConfigValidatorRequest::TPtr &ev); ITransaction *CreateTxUpdateLastProvidedConfig(TEvConsole::TEvConfigNotificationResponse::TPtr &ev); + ITransaction *CreateTxGetLogTail(TEvConsole::TEvGetLogTailRequest::TPtr &ev); + ITransaction *CreateTxLogCleanup(); void Handle(TEvConsole::TEvAddConfigSubscriptionRequest::TPtr &ev, const TActorContext &ctx); void Handle(TEvConsole::TEvConfigNotificationResponse::TPtr &ev, const TActorContext &ctx); @@ -121,6 +129,7 @@ private: void Handle(TEvConsole::TEvRemoveConfigSubscriptionsRequest::TPtr &ev, const TActorContext &ctx); void Handle(TEvConsole::TEvReplaceConfigSubscriptionsRequest::TPtr &ev, const TActorContext &ctx); void Handle(TEvConsole::TEvToggleConfigValidatorRequest::TPtr &ev, const TActorContext &ctx); + void Handle(TEvConsole::TEvGetLogTailRequest::TPtr &ev, const TActorContext &ctx); void Handle(TEvInterconnect::TEvNodesInfo::TPtr &ev, const TActorContext &ctx); void Handle(TEvPrivate::TEvStateLoaded::TPtr &ev, const TActorContext &ctx); void Handle(TEvPrivate::TEvCleanupSubscriptions::TPtr &ev, const TActorContext &ctx); @@ -128,6 +137,8 @@ private: void ForwardToConfigsProvider(TAutoPtr<IEventHandle> &ev, const TActorContext &ctx); void ScheduleSubscriptionsCleanup(const TActorContext &ctx); + void ScheduleLogCleanup(const TActorContext &ctx); + void CleanupLog(const TActorContext &ctx); STFUNC(StateWork) { @@ -135,6 +146,7 @@ private: switch (ev->GetTypeRewrite()) { HFuncTraced(TEvConsole::TEvAddConfigSubscriptionRequest, Handle); FFunc(TEvConsole::EvCheckConfigUpdatesRequest, ForwardToConfigsProvider); + HFunc(TEvConsole::TEvGetLogTailRequest, Handle); HFuncTraced(TEvConsole::TEvConfigNotificationResponse, Handle); HFuncTraced(TEvConsole::TEvConfigureRequest, Handle); FFunc(TEvConsole::EvGetConfigItemsRequest, ForwardToConfigsProvider); @@ -152,6 +164,7 @@ private: HFuncTraced(TEvPrivate::TEvStateLoaded, Handle); FFunc(TEvConsole::EvConfigSubscriptionRequest, ForwardToConfigsProvider); FFunc(TEvConsole::EvConfigSubscriptionCanceled, ForwardToConfigsProvider); + CFunc(TEvPrivate::EvCleanupLog, CleanupLog); default: Y_FAIL("TConfigsManager::StateWork unexpected event type: %" PRIx32 " event: %s", @@ -196,6 +209,8 @@ private: TActorId ConfigsProvider; TTxProcessor::TPtr TxProcessor; + TLogger Logger; + TSchedulerCookieHolder LogCleanupTimerCookieHolder; }; } // namespace NConsole diff --git a/ydb/core/cms/console/console_configs_provider.cpp b/ydb/core/cms/console/console_configs_provider.cpp index 4ba6f46033..3a3578d2e4 100644 --- a/ydb/core/cms/console/console_configs_provider.cpp +++ b/ydb/core/cms/console/console_configs_provider.cpp @@ -410,7 +410,7 @@ void TConfigsProvider::ApplyConfigModifications(const TConfigModifications &modi TSubscriptionSet subscriptions; TInMemorySubscriptionSet inMemorySubscriptions; - for (auto id : modifications.RemovedItems) { + for (auto &[id, _] : modifications.RemovedItems) { auto item = ConfigIndex.GetItem(id); LOG_TRACE_S(ctx, NKikimrServices::CMS_CONFIGS, "TConfigsProvider: remove " << item->ToString()); ConfigIndex.RemoveItem(id); diff --git a/ydb/core/cms/console/console_impl.h b/ydb/core/cms/console/console_impl.h index 59b02a24b7..8d42fd6c06 100644 --- a/ydb/core/cms/console/console_impl.h +++ b/ydb/core/cms/console/console_impl.h @@ -42,6 +42,8 @@ public: ConfigKeyNextConfigItemId, ConfigKeyNextTxId, ConfigKeyNextSubscriptionId, + ConfigKeyNextLogItemId, + ConfigKeyMinLogItemId, }; private: @@ -90,6 +92,7 @@ private: FFunc(TEvConsole::EvCheckConfigUpdatesRequest, ForwardToConfigsManager); FFunc(TEvConsole::EvConfigNotificationResponse, ForwardToConfigsManager); FFunc(TEvConsole::EvConfigureRequest, ForwardToConfigsManager); + FFunc(TEvConsole::EvGetLogTailRequest, ForwardToConfigsManager); FFunc(TEvConsole::EvCreateTenantRequest, ForwardToTenantsManager); FFunc(TEvConsole::EvDescribeTenantOptionsRequest, ForwardToTenantsManager); FFunc(TEvConsole::EvGetConfigItemsRequest, ForwardToConfigsManager); diff --git a/ydb/core/cms/console/logger.cpp b/ydb/core/cms/console/logger.cpp new file mode 100644 index 0000000000..646c89852f --- /dev/null +++ b/ydb/core/cms/console/logger.cpp @@ -0,0 +1,196 @@ +#include "logger.h" +#include "console_impl.h" + +#include <util/generic/utility.h> + +namespace NKikimr { +namespace NConsole { + +using namespace NKikimrConsole; + +TLogger::TLogger() {} + +bool TLogger::DbCleanupLog(ui32 remainEntries, + TTransactionContext &txc, + const TActorContext &ctx) +{ + if (remainEntries > NextLogItemId) { + return true; + } + + ui64 fromId = NextLogItemId - remainEntries; + + LOG_DEBUG_S(ctx, NKikimrServices::CMS_CONFIGS, + "Cleanup log records until " << fromId); + + NIceDb::TNiceDb db(txc.DB); + + LOG_DEBUG_S(ctx, NKikimrServices::CMS_CONFIGS, + "Removing " << (fromId - MinLogItemId + 1) << " log records"); + + for (ui64 id = fromId; id >= MinLogItemId; --id) + db.Table<Schema::LogRecords>().Key(id).Delete(); + + MinLogItemId = fromId + 1; + + db.Table<Schema::Config>().Key(TConsole::ConfigKeyMinLogItemId) + .Update<Schema::Config::Value>(ToString(MinLogItemId)); + + return true; +} + +bool TLogger::DbLoadLogTail(const NKikimrConsole::TLogFilter &filter, + TVector<NKikimrConsole::TLogRecord> &result, + TTransactionContext &txc) +{ + result.clear(); + + ui64 remain = Min<ui32>(filter.GetLimit(), 10000); + + ui64 timestamp = Max<ui64>(); + + if (filter.HasReverse() && filter.GetReverse()) { + timestamp = Min<ui64>(); + } + + if (filter.HasFromTimestamp()) { + timestamp = filter.GetFromTimestamp(); + } + + bool reverse = filter.HasReverse() && filter.GetReverse(); + + auto checkTimestamp = [&](ui64 ts) { + if (reverse) { + return ts >= timestamp; + } else { + return ts <= timestamp; + } + }; + + THashSet<TString> users; + + for (size_t i = 0; i < filter.UsersSize(); i++) { + users.insert(filter.GetUsers(i)); + } + + THashSet<TString> excludeUsers; + + for (size_t i = 0; i < filter.ExcludeUsersSize(); i++) { + excludeUsers.insert(filter.GetExcludeUsers(i)); + } + + auto checkUser = [&](TString& user) { + if (!users.empty()) { + return users.contains(user); + } else if (!excludeUsers.empty()) { + return !excludeUsers.contains(user); + } + + return true; + }; + + THashSet<ui32> affectedKinds; + + for (size_t i = 0; i < filter.AffectedKindsSize(); i++) { + affectedKinds.insert(filter.GetAffectedKinds(i)); + } + + auto checkAffected = [&](const NKikimrConsole::TLogRecordData& data) { + if (affectedKinds.size() != 0) { + for (size_t i = 0; i < data.AffectedKindsSize(); i++) { + if(affectedKinds.contains(data.GetAffectedKinds(i))) { + return true; + } + } + + return false; + } + + return true; + }; + + NIceDb::TNiceDb db(txc.DB); + + auto processRowset = [&](auto&& rowset) { + if (!rowset.IsReady()) + return false; + + while (remain && !rowset.EndOfSet()) { + auto ts = rowset.template GetValue<Schema::LogRecords::Timestamp>(); + auto id = rowset.template GetValue<Schema::LogRecords::Id>(); + auto user = rowset.template GetValue<Schema::LogRecords::UserSID>(); + NKikimrConsole::TLogRecordData data; + Y_PROTOBUF_SUPPRESS_NODISCARD data.ParseFromString(rowset.template GetValue<Schema::LogRecords::Data>()); + + if (checkTimestamp(ts) && checkUser(user) && checkAffected(data)) { + result.push_back(NKikimrConsole::TLogRecord()); + result.back().SetId(id); + result.back().SetTimestamp(ts); + result.back().SetUser(user); + result.back().MutableData()->Swap(&data); + --remain; + } + + if (remain && !rowset.Next()) { + return false; + } + } + + return true; + }; + + auto table = db.Table<Schema::LogRecords>(); + + if (reverse && filter.HasFromId()) { + return processRowset(table + .GreaterOrEqual(filter.GetFromId()) + .Select<Schema::LogRecords::TColumns>()); + } else if (reverse && !filter.HasFromId()) { + return processRowset(table + .Select<Schema::LogRecords::TColumns>()); + } else if (!reverse && filter.HasFromId()) { + return processRowset(table + .Reverse() + .LessOrEqual(filter.GetFromId()) + .Select<Schema::LogRecords::TColumns>()); + } else { + return processRowset(table + .Reverse() + .Select<Schema::LogRecords::TColumns>()); + } + } + +void TLogger::DbLogData(const TString &userSID, + const TLogRecordData &data, + TTransactionContext &txc, + const TActorContext &ctx) +{ + ui64 timestamp = ctx.Now().GetValue(); + + TString serializedData = data.SerializeAsString(); + + LOG_TRACE_S(ctx, NKikimrServices::CMS_CONFIGS, + "Add log record to local DB" + << " timestamp=" << timestamp + << " data=" << data.ShortDebugString()); + + NIceDb::TNiceDb db(txc.DB); + db.Table<Schema::LogRecords>().Key(NextLogItemId) + .Update<Schema::LogRecords::Timestamp>(timestamp) + .Update<Schema::LogRecords::UserSID>(userSID) + .Update<Schema::LogRecords::Data>(serializedData); + + db.Table<Schema::Config>().Key(TConsole::ConfigKeyNextLogItemId) + .Update<Schema::Config::Value>(ToString(++NextLogItemId)); +} + +void TLogger::SetNextLogItemId(ui64 id) { + NextLogItemId = id; +} + +void TLogger::SetMinLogItemId(ui64 id) { + MinLogItemId = id; +} + +} // NConsole +} // NKikimr diff --git a/ydb/core/cms/console/logger.h b/ydb/core/cms/console/logger.h new file mode 100644 index 0000000000..c87bbbde9e --- /dev/null +++ b/ydb/core/cms/console/logger.h @@ -0,0 +1,46 @@ +#pragma once +#include "defs.h" + +#include "console__scheme.h" + +#include <ydb/core/protos/console.pb.h> +#include <ydb/core/tablet_flat/tablet_flat_executed.h> + +#include <util/datetime/base.h> +#include <util/generic/set.h> + +namespace NKikimr { +namespace NConsole { + +using NTabletFlatExecutor::TTransactionContext; + +/** + * Class for log manipulation in local Console DB. + */ +class TLogger { +public: + TLogger(); + + bool DbCleanupLog(ui32 remainEntries, + TTransactionContext &txc, + const TActorContext &ctx); + + bool DbLoadLogTail(const NKikimrConsole::TLogFilter &filter, + TVector<NKikimrConsole::TLogRecord> &result, + TTransactionContext &txc); + + void DbLogData(const TString &userSID, + const NKikimrConsole::TLogRecordData &data, + TTransactionContext &txc, + const TActorContext &ctx); + + void SetNextLogItemId(ui64 id); + void SetMinLogItemId(ui64 id); + +private: + ui64 NextLogItemId = 0; + ui64 MinLogItemId = 0; +}; + +} // NConsole +} // NKikimr diff --git a/ydb/core/cms/console/modifications_validator.cpp b/ydb/core/cms/console/modifications_validator.cpp index b3125be585..f6c29f8a3e 100644 --- a/ydb/core/cms/console/modifications_validator.cpp +++ b/ydb/core/cms/console/modifications_validator.cpp @@ -86,9 +86,9 @@ TConfigModifications TModificationsValidator::BuildModificationsForValidation(co { TConfigModifications result; - for (auto id : diff.RemovedItems) { + for (auto &[id, item] : diff.RemovedItems) { if (Index.GetItem(id)) - result.RemovedItems.insert(id); + result.RemovedItems.emplace(id, item); } ui64 newId = Max<ui64>(); @@ -99,7 +99,7 @@ TConfigModifications TModificationsValidator::BuildModificationsForValidation(co ++newItem->Generation; result.ModifiedItems.emplace(pr.first, newItem); } else { - result.RemovedItems.insert(pr.first); + result.RemovedItems.emplace(pr.first, pr.second); } } else if (IsValidationRequired(pr.second)) { TConfigItem::TPtr newItem = new TConfigItem(*pr.second); @@ -122,7 +122,7 @@ TConfigModifications TModificationsValidator::BuildModificationsForValidation(co void TModificationsValidator::CollectModifiedItems(const TConfigModifications &diff) { - for (auto id : diff.RemovedItems) + for (auto &[id, _] : diff.RemovedItems) ModifiedItems.insert(Index.GetItem(id)); for (auto &pr : diff.ModifiedItems) { ModifiedItems.insert(Index.GetItem(pr.first)); diff --git a/ydb/core/cms/console/modifications_validator_ut.cpp b/ydb/core/cms/console/modifications_validator_ut.cpp index c29d8c563e..7f27bf9429 100644 --- a/ydb/core/cms/console/modifications_validator_ut.cpp +++ b/ydb/core/cms/console/modifications_validator_ut.cpp @@ -281,12 +281,18 @@ TConfigModifications MakeDiffAddItems() TConfigModifications MakeDiffRemoveItems() { TConfigModifications result; - result.RemovedItems.insert(ITEM_DOMAIN_LOG_1.GetId().GetId()); - result.RemovedItems.insert(ITEM_NODE1_LOG_1.GetId().GetId()); - result.RemovedItems.insert(ITEM_HOST1_LOG_1.GetId().GetId()); - result.RemovedItems.insert(ITEM_TENANT1_LOG_1.GetId().GetId()); - result.RemovedItems.insert(ITEM_TYPE1_LOG_1.GetId().GetId()); - result.RemovedItems.insert(ITEM_TENANT1_TYPE1_LOG_1.GetId().GetId()); + result.RemovedItems[ITEM_DOMAIN_LOG_1.GetId().GetId()] + = new TConfigItem(ITEM_DOMAIN_LOG_1); + result.RemovedItems[ITEM_NODE1_LOG_1.GetId().GetId()] + = new TConfigItem(ITEM_NODE1_LOG_1); + result.RemovedItems[ITEM_HOST1_LOG_1.GetId().GetId()] + = new TConfigItem(ITEM_HOST1_LOG_1); + result.RemovedItems[ITEM_TENANT1_LOG_1.GetId().GetId()] + = new TConfigItem(ITEM_TENANT1_LOG_1); + result.RemovedItems[ITEM_TYPE1_LOG_1.GetId().GetId()] + = new TConfigItem(ITEM_TYPE1_LOG_1); + result.RemovedItems[ITEM_TENANT1_TYPE1_LOG_1.GetId().GetId()] + = new TConfigItem(ITEM_TENANT1_TYPE1_LOG_1); return result; } @@ -367,15 +373,19 @@ TConfigModifications MakeDiffForAllAffected(bool domainAffected) TConfigModifications result; if (domainAffected) { result.AddedItems.push_back(new TConfigItem(ITEM_DOMAIN_POOL_1)); - result.RemovedItems.insert(ITEM_DOMAIN_LOG_1.GetId().GetId()); + result.RemovedItems[ITEM_DOMAIN_LOG_1.GetId().GetId()] + = new TConfigItem(ITEM_DOMAIN_LOG_1); } AddScopeModification(result, ITEM_TENANT2_LOG_1, ITEM_TYPE2_LOG_1); AddScopeModification(result, ITEM_TENANT3_TYPE3_LOG_1, ITEM_TYPE2_LOG_1); result.AddedItems.push_back(new TConfigItem(ITEM_TENANT3_POOL_1)); result.AddedItems.push_back(new TConfigItem(ITEM_TYPE3_POOL_1)); - result.RemovedItems.insert(ITEM_TYPE1_LOG_1.GetId().GetId()); - result.RemovedItems.insert(ITEM_TENANT1_LOG_1.GetId().GetId()); - result.RemovedItems.insert(ITEM_TENANT2_TYPE2_LOG_1.GetId().GetId()); + result.RemovedItems[ITEM_TYPE1_LOG_1.GetId().GetId()] + = new TConfigItem(ITEM_TYPE1_LOG_1); + result.RemovedItems[ITEM_TENANT1_LOG_1.GetId().GetId()] + = new TConfigItem(ITEM_TENANT1_LOG_1); + result.RemovedItems[ITEM_TENANT2_TYPE2_LOG_1.GetId().GetId()] + = new TConfigItem(ITEM_TENANT2_TYPE2_LOG_1); return result; } diff --git a/ydb/core/cms/http.cpp b/ydb/core/cms/http.cpp index c383c71b3d..82efbc9833 100644 --- a/ydb/core/cms/http.cpp +++ b/ydb/core/cms/http.cpp @@ -4,6 +4,7 @@ #include "json_proxy_config_updates.h" #include "json_proxy_config_validators.h" #include "json_proxy_log.h" +#include "json_proxy_console_log.h" #include "json_proxy_operations.h" #include "json_proxy_proto.h" #include "json_proxy_toggle_config_validator.h" @@ -66,8 +67,10 @@ public: TEvCms::TEvManageNotificationResponse>>; ApiHandlers["/api/console/configure"] = new TApiMethodHandler<TJsonProxyConsole<NConsole::TEvConsole::TEvConfigureRequest, - NConsole::TEvConsole::TEvConfigureResponse>>; + NConsole::TEvConsole::TEvConfigureResponse, + true>>; ApiHandlers["/api/json/log"] = new TApiMethodHandler<TJsonProxyLog>; + ApiHandlers["/api/json/console/log"] = new TApiMethodHandler<TJsonProxyConsoleLog>; ApiHandlers["/api/json/configitems"] = new TApiMethodHandler<TJsonProxyConfigItems>; ApiHandlers["/api/json/configvalidators"] = new TApiMethodHandler<TJsonProxyConfigValidators>; ApiHandlers["/api/json/toggleconfigvalidator"] = new TApiMethodHandler<TJsonProxyToggleConfigValidator>; diff --git a/ydb/core/cms/json_proxy.h b/ydb/core/cms/json_proxy.h index 185d0ce01b..d827e04a39 100644 --- a/ydb/core/cms/json_proxy.h +++ b/ydb/core/cms/json_proxy.h @@ -23,10 +23,10 @@ namespace NKikimr { namespace NCms { -template <typename TRequestEvent, typename TResponseEvent> -class TJsonProxyBase : public TActorBootstrapped<TJsonProxyBase<TRequestEvent, TResponseEvent>> { +template <typename TRequestEvent, typename TResponseEvent, bool ForwardToken = false> +class TJsonProxyBase : public TActorBootstrapped<TJsonProxyBase<TRequestEvent, TResponseEvent, ForwardToken>> { private: - using TBase = TActorBootstrapped<TJsonProxyBase<TRequestEvent, TResponseEvent>>; + using TBase = TActorBootstrapped<TJsonProxyBase<TRequestEvent, TResponseEvent, ForwardToken>>; protected: using TRequest = TRequestEvent; @@ -61,6 +61,11 @@ public: return; } + if constexpr (ForwardToken) { + NMon::TEvHttpInfo *msg = RequestEvent->Get(); + request->Record.SetUserToken(msg->UserToken); + } + ui64 tid = GetTabletId(ctx); if (!tid) { ReplyWithErrorAndDie(TString(NMonitoring::HTTPNOTFOUND) + " unknown tablet ID", ctx); @@ -185,14 +190,14 @@ protected: TActorId Pipe; }; -template <typename TRequestEvent, typename TResponseEvent> -class TJsonProxy : public TJsonProxyBase<TRequestEvent, TResponseEvent> { +template <typename TRequestEvent, typename TResponseEvent, bool ForwardToken = false> +class TJsonProxy : public TJsonProxyBase<TRequestEvent, TResponseEvent, ForwardToken> { private: - using TBase = TJsonProxyBase<TRequestEvent, TResponseEvent>; + using TBase = TJsonProxyBase<TRequestEvent, TResponseEvent, ForwardToken>; public: TJsonProxy(NMon::TEvHttpInfo::TPtr &event) - : TJsonProxyBase<TRequestEvent, TResponseEvent>(event) + : TJsonProxyBase<TRequestEvent, TResponseEvent, ForwardToken>(event) { } @@ -214,11 +219,11 @@ public: } }; -template <typename TRequestEvent, typename TResponseEvent, bool useConsole> -class TJsonProxyCmsBase : public TJsonProxy<TRequestEvent, TResponseEvent> { +template <typename TRequestEvent, typename TResponseEvent, bool useConsole, bool ForwardToken = false> +class TJsonProxyCmsBase : public TJsonProxy<TRequestEvent, TResponseEvent, ForwardToken> { public: TJsonProxyCmsBase(NMon::TEvHttpInfo::TPtr &event) - : TJsonProxy<TRequestEvent, TResponseEvent>(event) + : TJsonProxy<TRequestEvent, TResponseEvent, ForwardToken>(event) { } @@ -235,11 +240,11 @@ public: } }; -template <typename TRequestEvent, typename TResponseEvent> -using TJsonProxyCms = TJsonProxyCmsBase<TRequestEvent, TResponseEvent, false>; +template <typename TRequestEvent, typename TResponseEvent, bool ForwardToken = false> +using TJsonProxyCms = TJsonProxyCmsBase<TRequestEvent, TResponseEvent, false, ForwardToken>; -template <typename TRequestEvent, typename TResponseEvent> -using TJsonProxyConsole = TJsonProxyCmsBase<TRequestEvent, TResponseEvent, true>; +template <typename TRequestEvent, typename TResponseEvent, bool ForwardToken = false> +using TJsonProxyConsole = TJsonProxyCmsBase<TRequestEvent, TResponseEvent, true, ForwardToken>; } // NCms } // NKikimr diff --git a/ydb/core/cms/json_proxy_console_log.h b/ydb/core/cms/json_proxy_console_log.h new file mode 100644 index 0000000000..78f581b341 --- /dev/null +++ b/ydb/core/cms/json_proxy_console_log.h @@ -0,0 +1,76 @@ +#pragma once + +#include "json_proxy.h" + +namespace NKikimr { +namespace NCms { + +using namespace NConsole; + +class TJsonProxyConsoleLog : public TJsonProxyConsole<TEvConsole::TEvGetLogTailRequest, TEvConsole::TEvGetLogTailResponse> { +private: + +public: + TJsonProxyConsoleLog(NMon::TEvHttpInfo::TPtr &event) + : TJsonProxyConsole<TEvConsole::TEvGetLogTailRequest, TEvConsole::TEvGetLogTailResponse>(event) + { + } + + TAutoPtr<TRequest> PrepareRequest(const TActorContext &) override + { + TAutoPtr<TRequest> request = new TRequest; + const TCgiParameters& cgi = RequestEvent->Get()->Request.GetParams(); + + if (cgi.Has("limit")) { + ui32 limit = 0; + if (TryFromString<ui32>(cgi.Get("limit"), limit)) + request->Record.MutableLogFilter()->SetLimit(limit); + } + + if (cgi.Has("from-timestamp")) { + ui64 val = 0; + if (TryFromString<ui64>(cgi.Get("from-timestamp"), val)) + request->Record.MutableLogFilter()->SetFromTimestamp(val * 1000); + } else if (cgi.Has("from-id")) { + ui64 val = 0; + if (TryFromString<ui64>(cgi.Get("from-id"), val)) + request->Record.MutableLogFilter()->SetFromId(val); + } + + if (cgi.Has("reverse")) { + bool reverse = false; + if (TryFromString<bool>(cgi.Get("reverse"), reverse)) + request->Record.MutableLogFilter()->SetReverse(reverse); + } + + if (cgi.Has("users")) { + TVector<TString> users; + StringSplitter(cgi.Get("users")).Split(',').Collect(&users); + for (auto& user : users) { + request->Record.MutableLogFilter()->AddUsers(user); + } + } else if (cgi.Has("exclude-users")) { + TVector<TString> users; + StringSplitter(cgi.Get("exclude-users")).Split(',').Collect(&users); + for (auto& user : users) { + request->Record.MutableLogFilter()->AddExcludeUsers(user); + } + } + + if (cgi.Has("affected-kinds")) { + TVector<TString> affectedStrs; + StringSplitter(cgi.Get("affected-kinds")).Split(',').Collect(&affectedStrs); + for (auto& affectedStr : affectedStrs) { + ui32 kind = 0; + if (TryFromString<ui32>(affectedStr, kind)) + request->Record.MutableLogFilter()->AddAffectedKinds(kind); + } + } + + return request; + } +}; + + +} // NCms +} // NKikimr diff --git a/ydb/core/cms/ui/cms.css b/ydb/core/cms/ui/cms.css index b8b1105295..fbc0e2d59e 100644 --- a/ydb/core/cms/ui/cms.css +++ b/ydb/core/cms/ui/cms.css @@ -37,7 +37,6 @@ label.cfg-form { .pending-update { background: #F08080 url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='100px' width='300px'><text x='0' y='50' fill='red' font-size='30'>Pending Update</text></svg>") no-repeat center; - } pre.cfg-item-hdr { @@ -69,3 +68,193 @@ td { padding-left: 10px; padding-right: 10px; } + +.pointer { + cursor: pointer; +} + +#popup-header { + margin-right: 32px; +} + +#popup { + position: fixed; + top: 0%; + left: 0%; + height: 100%; + width: 100%; + background: #ffffff00; + z-index: 99; + display: none; +} + + +.popup-content { + position: fixed; + box-shadow: 0px 0px 4px 2px rgba(0, 0, 0, 0.3); + border-radius: 8px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + min-width: 500px; + max-width: 1000px; + min-height: 200px; + max-height: 85%; + overflow: scroll; + text-align: center; + background-color: #e8eae6; + box-sizing: border-box; + padding: 10px; + z-index: 100; +} + +.popup-close-btn { + position: absolute; + right: 15px; + top: 15px; + cursor: pointer; +} + +.crosssign { + display: inline-block; + width: 22px; + height: 22px; + position: relative; + transform: rotate(45deg); +} + +.crosssign_circle { + position: absolute; + width: 22px; + height: 22px; + background-color: rgb(0, 86, 179); + border-radius: 11px; + left: 0; + top: 0; +} + +.crosssign_stem, +.crosssign_stem2 { + position: absolute; + background-color: #fff; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +.crosssign_stem { + width: 3px; + height: 9px; +} + +.crosssign_stem2 { + width: 9px; + height: 3px; +} + +#popup pre { + text-align: left; + outline: 1px solid #ccc; padding: 5px; margin: 5px; +} + +#popup .string { + color: green; +} + +#popup .number { + color: darkorange; +} + +#popup .boolean { + color: blue; +} + +#popup .null { + color: magenta; +} + +#popup .key { + color: red; +} + +.spoiler-wrap { + background: #fff; + margin: 0 0 8px; + border: 1px solid rgb(0, 86, 179) +} + +.spoiler-head { + background: #efefef; + cursor: pointer; + padding: 5px; +} + +.spoiler-body { + padding: 10px +} + +.spoiler-wrap.disabled .spoiler-body { + display:none +} + +.spoiler-wrap.active { + border-color: rgb(0, 86, 179); +} + +.spoiler-wrap.active .spoiler-head { + background: rgb(0, 86, 179); + color: #fff +} + +.icon-copy { + box-sizing: border-box; + position: relative; + display: block; + transform: scale(var(--ggs,1)); + width: 14px; + height: 18px; + border: 2px solid; + margin-left: -5px; + margin-top: -4px +} + +.icon-copy::after, +.icon-copy::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute +} + +.icon-copy::before { + background: + linear-gradient( to left, + currentColor 5px, transparent 0) + no-repeat right top/5px 2px, + linear-gradient( to left, + currentColor 5px, transparent 0) + no-repeat left bottom/ 2px 5px; + box-shadow: inset -4px -4px 0 -2px; + bottom: -6px; + right: -6px; + width: 14px; + height: 18px +} + +.icon-copy::after { + width: 6px; + height: 2px; + background: currentColor; + left: 2px; + top: 2px; + box-shadow: 0 4px 0,0 8px 0 +} + +#console-log a { + cursor: pointer; + color: rgb(0, 123, 255); +} + +#console-log a:hover { + color: rgb(0, 86, 179); +} diff --git a/ydb/core/cms/ui/cms.js b/ydb/core/cms/ui/cms.js index a90e9bbc44..e93eb1ff1b 100644 --- a/ydb/core/cms/ui/cms.js +++ b/ydb/core/cms/ui/cms.js @@ -29,7 +29,19 @@ function main() { initConfigsTab(); initValidatorsTab(); initCmsLogTab(); + initConsoleLogTab(); initCmsSentinelTab(); + + $('#popup').on('click', function(e) { + if (e.target !== this) + return; + + togglePopup(); + }); +} + +function togglePopup() { + $("#popup").toggle(); } $(document).ready(main); diff --git a/ydb/core/cms/ui/console_log.js b/ydb/core/cms/ui/console_log.js new file mode 100644 index 0000000000..79df4379c9 --- /dev/null +++ b/ydb/core/cms/ui/console_log.js @@ -0,0 +1,488 @@ +'use strict'; + +var ConsoleLogState = { + fromId: undefined, + fromTimestamp: undefined, + limit: 100, + totalRecords: 0, + filterByKind: false, + usersExclude: false, +}; + +function RenderScope(content) { + var result = "{" + if (content.hasOwnProperty("TenantAndNodeTypeFilter")) { + var filter = content["TenantAndNodeTypeFilter"]; + var filters = []; + if (filter.hasOwnProperty("Tenant")) { + filters.push("Tenant:" + filter["Tenant"]); + } + if (filter.hasOwnProperty("NodeType")) { + filters.push("NodeType:" + filter["NodeType"]); + } + result += filters.join(","); + } + if (content.hasOwnProperty("HostFilter")) { + result += "Hosts:[" + content["HostFilter"]["Hosts"].join(",") + "]"; + } + if (content.hasOwnProperty("NodeFilter")) { + result += "Hosts:[" + content["NodeFilter"]["Nodes"].join(",") + "]"; + } + result += "}" + return result; +} + +function RenderId(content) { + var id = content["Id"]; + var gen = content["Generation"]; + return id + "." + gen; +} + +function RenderKind(content) { + return cmsEnums.get('ItemKinds', content); +} + +const MergeStrategies = { + 1: "OVERWRITE", + 2: "MERGE", + 3: "MERGE_OVERWRITE_REPEATED", +} + +function RenderMergeStrategy(content) { + return MergeStrategies[content]; +} + +function RenderAffectedConfigs(content) { + return "\n\t\t" + Object.keys(content).join("\n\t\t") +} + +function RenderBasicConfigItem(ci) { + var result = ""; + + if (ci.hasOwnProperty("Id")) { + var cid = ci["Id"]; + result += "\n\tId:" + RenderId(cid); + } + + if (ci.hasOwnProperty("Kind")) { + var kind = ci["Kind"]; + result += "\n\tKind:" + RenderKind(kind); + } + + if (ci.hasOwnProperty("UsageScope")) { + var scope = ci["UsageScope"]; + result += "\n\tScope:" + RenderScope(scope); + } + + if (ci.hasOwnProperty("Order")) { + result += "\n\tOrder:" + ci["Order"]; + } + + if (ci.hasOwnProperty("MergeStrategy")) { + result += "\n\tMergeStrategy:" + RenderMergeStrategy(ci["MergeStrategy"]); + } else { + result += "\n\tMergeStrategy:MERGE"; + } + + if (ci.hasOwnProperty("Cookie")) { + result += "\n\tCookie:" + ci["Cookie"]; + } + + if (ci.hasOwnProperty("Config")) { + result += "\n\tAffectedConfigs:" + RenderAffectedConfigs(ci["Config"]); + } + + return result; +} + +function RenderAddConfigItem(content) { + var ci = content["ConfigItem"]; + var result = "<b>AddItem:</b>" + + result += RenderBasicConfigItem(ci); + + return result; +} + +function RenderRemoveConfigItem(content) { + var cid = content["ConfigItemId"]; + return "<b>RemoveItem:</b>\n\tId:" + RenderId(cid); +} + +function RenderModifyConfigItem(content) { + var ci = content["ConfigItem"]; + var result = "<b>ModifyItem:</b>" + + result += RenderBasicConfigItem(ci); + + return result; +} + +function RenderRemoveConfigItems(content) { + var cf = content["CookieFilter"]["Cookies"]; + return "<b>RemoveItems:</b>\n\tCookies:\n\t\t" + cf.join("\n\t\t"); +} + +const ActionRenderers = { + "AddConfigItem": RenderAddConfigItem, + "RemoveConfigItem": RenderRemoveConfigItem, + "ModifyConfigItem": RenderModifyConfigItem, + "RemoveConfigItems": RenderRemoveConfigItems, +} + +function onConsoleLogLoaded(data) { + if (data['Status']['Code'] != 'SUCCESS') { + onConsoleLogFailed(data); + return; + } + + $('#console-log-error').html(''); + + var recs = data['LogRecords']; + if (recs === undefined) + recs = []; + + if (recs.length > 0) { + if (ConsoleLogState.reverse) { + ConsoleLogState.fromId = Math.min(recs[recs.length - 1]['Id'], recs[0]['Id']); + } else { + ConsoleLogState.fromId = Math.max(recs[recs.length - 1]['Id'], recs[0]['Id']); + } + ConsoleLogState.totalRecords = recs.length; + } + $("#console-from-id").val(ConsoleLogState.fromId); + + for (var i = 0; i < recs.length; ++i) { + var rec = recs[i]; + var line = document.createElement('tr'); + line.className = "pointer"; + + var cell0 = document.createElement('td'); + var cell1 = document.createElement('td'); + var cell2 = document.createElement('td'); + var cell3 = document.createElement('td'); + var cell4 = document.createElement('td'); + + if (rec['Data'].hasOwnProperty('AffectedKinds')) { + cell3.innerHTML = "<pre>" + rec['Data']['AffectedKinds'].map(x => cmsEnums.get('ItemKinds', x)).join("\n") + "</pre>"; + } + + cell0.textContent = rec['Id']; + cell0.dataset.ordervalue = -rec['Id']; // hack for rev order + var timestamp = new Date(rec['Timestamp'] / 1000); + cell1.textContent = timestamp.toLocaleString('en-GB', { timeZone: 'UTC' }); + cell2.textContent = rec['User']; + var contents = []; + for (var action of rec['Data']['Action']['Actions']) { + var key = (Object.keys(action)[0]); + var content = action[key]; + contents.push(ActionRenderers[key](content)); + } + cell4.innerHTML = "<pre>" + contents.join("<br/>") + "</pre>"; + cell4.title = JSON.stringify(rec['Data']['Action'], null, 2); + cell4.setAttribute('data', JSON.stringify(rec['Data'], null, 2)); + + line.appendChild(cell0); + line.appendChild(cell1); + line.appendChild(cell2); + line.appendChild(cell3); + line.appendChild(cell4); + + document.getElementById('console-log-body').appendChild(line); + } + + if (recs.length > 0) { + $("#console-log-table").trigger("update", [true]); + } +} + +function onConsoleLogFailed(data) { + if (data && data['Status'] && data['Status']['Reason']) + $('#console-log-error').html(data['Status']['Reason']); + else + $('#console-log-error').html("Cannot get CONSOLE log update"); + setTimeout(loadConsoleLog, ConsoleLogState.retryInterval); +} + +function loadConsoleLog(reverse) { + $("#console-log-body").empty(); + + $("#console-limit").text(ConsoleLogState.limit); + + var users = $("#console-user-filter").val(); + if (users !== "") { + ConsoleLogState.users = users.split(','); + } else { + ConsoleLogState.users = undefined; + } + + var affected = $("#console-affected-filter").val(); + if (affected !== "") { + ConsoleLogState.affected = affected.split(',').map(x => cmsEnums.parse('ItemKinds', x)); + } else { + ConsoleLogState.affected = undefined; + } + + var url = 'cms/api/json/console/log?limit=' + ConsoleLogState.limit; + + if (ConsoleLogState.fromTimestamp !== undefined) { + url += '&from-timestamp=' + ConsoleLogState.fromTimestamp; + ConsoleLogState.fromTimestamp = undefined; + } else if (ConsoleLogState.fromId !== undefined) { + url += '&from-id=' + ConsoleLogState.fromId; + } + + if (ConsoleLogState.users !== undefined) { + if (ConsoleLogState.usersExclude) { + url += '&exclude-users='; + } else { + url += '&users='; + } + url += ConsoleLogState.users; + } + + if (ConsoleLogState.affected !== undefined) { + url += '&affected=' + ConsoleLogState.affected; + } + + if (reverse) { + url += '&reverse=true'; + } + + $.get(url).done(onConsoleLogLoaded).fail(onConsoleLogFailed); +} + +function syntaxHighlight(json) { + json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { + var cls = 'number'; + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'key'; + } else { + cls = 'string'; + } + } else if (/true|false/.test(match)) { + cls = 'boolean'; + } else if (/null/.test(match)) { + cls = 'null'; + } + return '<span class="' + cls + '">' + match + '</span>'; + }); +} + +function renderSpoiler(header, bodyElem) { + var spoilerHead = $("<div></div>"); + + spoilerHead.text(header); + spoilerHead.addClass("spoiler-head"); + spoilerHead.click(function() { + $(this) + .parents('.spoiler-wrap') + .toggleClass("active") + .find('.spoiler-body') + .slideToggle(); + }); + + var spoilerBody = $("<div></div>"); + + spoilerBody.append(bodyElem); + spoilerBody.addClass("spoiler-body"); + + var spoiler = $("<div></div>"); + + spoiler.append(spoilerHead); + spoiler.append(spoilerBody); + spoiler.addClass("spoiler-wrap"); + spoiler.addClass("disabled"); + + return spoiler; +} + +function filterUnaffected(inp, filter) { + if (!ConsoleLogState.filterByKind) { + return inp; + } + var result = {}; + var affected = filter.map(x => cmsEnums.get('ItemKinds', x).slice(0, -4)); + for (var key of Object.keys(inp)) { + if (affected.includes(key) || affected.length == 0) { + result[key] = inp[key]; + } + } + return result; +} + +function copyToClipboard(ev) { + var element = ev.data.elem; + var temp = $("<input>"); + $("body").append(temp); + temp.val($(element).text()).select(); + document.execCommand("copy"); + temp.remove(); +} + +function renderConsolePopup(data, user, time) { + // set header + $("#popup-header").text("Change by \"" + user + "\" at " + time); + + var div = $("<div></div>"); + + // create action view + var cmdDiv = $("<div></div>") + var cmd = $("<pre></pre>"); + cmd.html(syntaxHighlight(JSON.stringify(data['Action'], null, 2))); + var copyCmd = $("<div class=\"icon-copy\" style=\"float:right; cursor:pointer;\"></div>"); + copyCmd.click({elem: cmd}, copyToClipboard); + cmdDiv.append(copyCmd); + cmdDiv.append(cmd); + div.append(renderSpoiler("Command", cmdDiv)); + + if (!data.hasOwnProperty('AffectedKinds')) { + data['AffectedKinds'] = []; + } + + if (data.hasOwnProperty("AffectedConfigs")) { + for (var i in data['AffectedConfigs']) { + var oldConfigDiv = $("<div></div>") + var oldConfig = $("<pre></pre>"); + var oldConfigData = data['AffectedConfigs'][i]['OldConfig']; + oldConfigData = filterUnaffected(oldConfigData, data['AffectedKinds']); + oldConfig.html(syntaxHighlight(JSON.stringify(oldConfigData, null, 2))); + var copyOldConfig = $("<div class=\"icon-copy\" style=\"float:right; cursor:pointer;\"></div>"); + copyOldConfig.click({elem: oldConfig}, copyToClipboard); + oldConfigDiv.append(copyOldConfig); + oldConfigDiv.append(oldConfig); + div.append(renderSpoiler( + "Old Config for Tenant:\"" + data['AffectedConfigs'][i]['Tenant'] + + "\" NodeType:\"" + data['AffectedConfigs'][i]['NodeType'] + "\"", oldConfigDiv)); + + var newConfigDiv = $("<div></div>") + var newConfig = $("<pre></pre>"); + var newConfigData = data['AffectedConfigs'][i]['NewConfig']; + newConfigData = filterUnaffected(newConfigData, data['AffectedKinds']); + newConfig.html(syntaxHighlight(JSON.stringify(newConfigData, null, 2))); + var copyNewConfig = $("<div class=\"icon-copy\" style=\"float:right; cursor:pointer;\"></div>"); + copyNewConfig.click({elem: newConfig}, copyToClipboard); + newConfigDiv.append(copyNewConfig); + newConfigDiv.append(newConfig); + div.append(renderSpoiler( + "New Config for Tenant:\"" + data['AffectedConfigs'][i]['Tenant'] + + "\" NodeType:\"" + data['AffectedConfigs'][i]['NodeType'] + "\"", newConfigDiv)); + } + } + + // clear and fill popup + var content = $("#popup-content"); + content.empty(); + content.append(div); + + // actually show popup + $("#popup").show(); +} + +function initConsoleLogTab() { + $("#console-log-table") + .on('click', 'tbody tr', function() { + // load data + var data = JSON.parse($(this).closest('tr').children().eq(4).attr('data')); + var user = $(this).closest('tr').children().eq(2).text(); + var time = $(this).closest('tr').children().eq(1).text(); + + renderConsolePopup(data, user, time); + }) + .tablesorter({ + theme: 'blue', + sortList: [[0,0]], + headers: { + 0: { + sorter: 'numeric-ordervalue', + }, + 1: { + sorter: false, + }, + 2: { + sorter: false, + }, + 3: { + sorter: false, + }, + 4: { + sorter: false, + } + }, + widgets : ['zebra'], + }); + + $("#console-first-page") + .click(function() { + ConsoleLogState.limit = parseInt($("#console-limit").val(), 10); + ConsoleLogState.fromId = undefined; + loadConsoleLog(false); + }); + + $("#console-prev-page") + .click(function() { + ConsoleLogState.limit = parseInt($("#console-limit").val(), 10); + ConsoleLogState.fromId += ConsoleLogState.limit; + loadConsoleLog(ConsoleLogState.reverse); + }); + + $("#console-next-page") + .click(function() { + ConsoleLogState.limit = parseInt($("#console-limit").val(), 10); + if (!ConsoleLogState.reverse) { + if (ConsoleLogState.fromId - ConsoleLogState.limit >= ConsoleLogState.limit - 1) { + ConsoleLogState.fromId -= ConsoleLogState.limit; + } else { + ConsoleLogState.fromId = ConsoleLogState.limit - 1; + } + } else { + if (ConsoleLogState.fromId - ConsoleLogState.limit >= 0) { + ConsoleLogState.fromId -= ConsoleLogState.limit; + } else { + ConsoleLogState.fromId = 0; + } + } + loadConsoleLog(ConsoleLogState.reverse); + }); + + $("#console-last-page") + .click(function() { + ConsoleLogState.limit = parseInt($("#console-limit").val(), 10); + ConsoleLogState.fromId = undefined; + loadConsoleLog(true); + }); + + $("#console-from-id-fetch") + .click(function() { + ConsoleLogState.limit = $("#console-limit").val(); + ConsoleLogState.fromId = $("#console-from-id").val(); + loadConsoleLog(ConsoleLogState.reverse); + }); + + $('input[type=radio][name=console-order]').change(function() { + if (this.value === 'normal') { + ConsoleLogState.reverse = false; + } + else if (this.value === 'reverse') { + ConsoleLogState.reverse = true; + } + loadConsoleLog(ConsoleLogState.reverse); + }); + + $('input[type=checkbox][name=console-user-filter-exclude]').change(function() { + ConsoleLogState.usersExclude = this.checked; + }) + + $('#console-datetime').val(new Date(Date.now()).toISOString().slice(0, 19)); + + $("#console-datetime-search") + .click(function() { + ConsoleLogState.limit = parseInt($("#console-limit").val(), 10); + ConsoleLogState.fromTimestamp = Date.parse($('#console-datetime').val() + "Z"); + loadConsoleLog(ConsoleLogState.reverse); + }); + + loadConsoleLog(); +} diff --git a/ydb/core/cms/ui/index.html b/ydb/core/cms/ui/index.html index f5a26485e2..7e47d73157 100644 --- a/ydb/core/cms/ui/index.html +++ b/ydb/core/cms/ui/index.html @@ -15,6 +15,7 @@ <script language="javascript" type="text/javascript" src="cms/enums.js"></script> <script language="javascript" type="text/javascript" src="cms/proto_types.js"></script> <script language="javascript" type="text/javascript" src="cms/cms_log.js"></script> + <script language="javascript" type="text/javascript" src="cms/console_log.js"></script> <script language="javascript" type="text/javascript" src="cms/sentinel_state.js"></script> <script language="javascript" type="text/javascript" src="cms/configs.js"></script> <script language="javascript" type="text/javascript" src="cms/config_forms.js"></script> @@ -49,6 +50,9 @@ <a class="nav-link" href="#cms-log" data-toggle="tab">CMS Log</a> </li> <li class="nav-item"> + <a class="nav-link" href="#console-log" data-toggle="tab">Console Log</a> + </li> + <li class="nav-item"> <a class="nav-link" href="#sentinel-state" data-toggle="tab"> Sentinel state <svg width="25px" height="25px" id="sentinel-loader"> @@ -92,6 +96,63 @@ </tbody> </table> </div> + <div id="console-log" class="tab-pane fade"> + <div id="console-log-error" class="error"></div> + Show values + <input autocomplete="off" type="radio" id="console-normal" + name="console-order" value="normal" checked /> + <label for="console-normal">Less than id</label> + <input autocomplete="off" type="radio" id="console-reverse" + name="console-order" value="reverse" /> + <label for="console-reverse">Greater than id</label> + <br/> + Per page: + <input autocomplete="off" type="text" id="console-limit" + name="console-limit" required value="100"/> + <br/> + UserFilter: + <input autocomplete="off" type="text" id="console-user-filter" + name="console-user-filter" placeholder="root@builtin,innokentii@staff" + required value=""/> + <input autocomplete="off" type="checkbox" id="console-user-filter-exclude" + name="console-user-filter-exclude" value="console-user-filter-exclude" /> + <label for="console-user-filter-exclude">Exclude</label> + <br/>AffectedFilter: + <input autocomplete="off" type="text" id="console-affected-filter" + name="console-affected-filter" placeholder="KQPConfigItem,CmsConfigItem" + required value=""/> + <br/> + Search time (UTC): + <input type="datetime-local" id="console-datetime" + name="console-datetime" autocomplete="off"/> + <input type="button" id="console-datetime-search" value="Search"/> + <br/> + <a id="console-first-page">First Page</a> + | + <a id="console-prev-page">Prev. Page</a> + | + From id: + <input autocomplete="off" type="text" id="console-from-id" + name="console-from-id" required value="first"/> + <input type="button" id="console-from-id-fetch" value="Fetch"/> + | + <a id="console-next-page">Next Page</a> + | + <a id="console-last-page">Last Page</a> + <table id="console-log-table" class="tablesorter"> + <thead> + <tr> + <th>Id</th> + <th>Timestamp (UTC)</th> + <th>UserSID</th> + <th>AffectedKinds</th> + <th>Action</th> + </tr> + </thead> + <tbody id="console-log-body"> + </tbody> + </table> + </div> <div id="sentinel-state" class="tab-pane fade"> <div id="sentinel-error" class="error"></div> <table id="sentinel-config"></table> @@ -116,5 +177,18 @@ </div> </div> </div> + <div id="popup"> + <div class="popup-content"> + <div onclick="togglePopup()" class="popup-close-btn"> + <span class="crosssign"> + <div class="crosssign_circle"></div> + <div class="crosssign_stem"></div> + <div class="crosssign_stem2"></div> + </span> + </div> + <h3 id="popup-header">Popup header</h3> + <p id="popup-content">Popup content</p> + </div> + </div> </body> </html> diff --git a/ydb/core/protos/console.proto b/ydb/core/protos/console.proto index aa089db889..41e1cf3c52 100644 --- a/ydb/core/protos/console.proto +++ b/ydb/core/protos/console.proto @@ -25,3 +25,35 @@ message TSetConfigRequest { message TSetConfigResponse { optional TStatus Status = 1; } + +message TLogRecordData { + optional TConfigureRequest Action = 1; + repeated TConfigureResponse.TAffectedConfig AffectedConfigs = 2; + repeated uint32 AffectedKinds = 3; +} + +message TLogRecord { + optional uint64 Id = 1; + optional uint64 Timestamp = 2; + optional string User = 3; + optional TLogRecordData Data = 4; +} + +message TLogFilter { + optional uint64 FromId = 1; + optional uint64 FromTimestamp = 2; + optional uint32 Limit = 3 [default = 100]; + repeated uint32 AffectedKinds = 4; + repeated string Users = 5; + repeated string ExcludeUsers = 6; + optional bool Reverse = 7; +} + +message TGetLogTailRequest { + optional TLogFilter LogFilter = 1; +} + +message TGetLogTailResponse { + optional TStatus Status = 1; + repeated TLogRecord LogRecords = 4; +} diff --git a/ydb/core/protos/console_config.proto b/ydb/core/protos/console_config.proto index da590e23a5..0d3ef8c80c 100644 --- a/ydb/core/protos/console_config.proto +++ b/ydb/core/protos/console_config.proto @@ -204,6 +204,9 @@ message TConfigureRequest { // (option is false) affected configs in response // have only Tenant and NodeType fields filled. optional bool FillAffectedConfigs = 3; + // For internal use only. + // Used for logging user requests + optional bytes UserToken = 4; } message TConfigureResponse { |