aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorinnokentii <innokentii@yandex-team.com>2022-12-08 23:14:28 +0300
committerinnokentii <innokentii@yandex-team.com>2022-12-08 23:14:28 +0300
commit9323fe28059355bafc517301a3e7a00fc0367f08 (patch)
tree1d606af915ed01d3f7609a9c7861063c56976910
parent8fdf497e036a8b96b30dcc1c0db56822d4e9244f (diff)
downloadydb-9323fe28059355bafc517301a3e7a00fc0367f08.tar.gz
Implement audit in Console
add basic web-interface for Console audit add basic TEvConfigure logging
-rw-r--r--ydb/core/client/server/msgbus_server_console.cpp1
-rw-r--r--ydb/core/cms/CMakeLists.txt6
-rw-r--r--ydb/core/cms/console/CMakeLists.txt3
-rw-r--r--ydb/core/cms/console/config_index.cpp2
-rw-r--r--ydb/core/cms/console/config_index.h2
-rw-r--r--ydb/core/cms/console/console.h9
-rw-r--r--ydb/core/cms/console/console__configure.cpp63
-rw-r--r--ydb/core/cms/console/console__get_log_tail.cpp63
-rw-r--r--ydb/core/cms/console/console__log_cleanup.cpp40
-rw-r--r--ydb/core/cms/console/console__scheme.h12
-rw-r--r--ydb/core/cms/console/console_configs_manager.cpp38
-rw-r--r--ydb/core/cms/console/console_configs_manager.h15
-rw-r--r--ydb/core/cms/console/console_configs_provider.cpp2
-rw-r--r--ydb/core/cms/console/console_impl.h3
-rw-r--r--ydb/core/cms/console/logger.cpp196
-rw-r--r--ydb/core/cms/console/logger.h46
-rw-r--r--ydb/core/cms/console/modifications_validator.cpp8
-rw-r--r--ydb/core/cms/console/modifications_validator_ut.cpp30
-rw-r--r--ydb/core/cms/http.cpp5
-rw-r--r--ydb/core/cms/json_proxy.h33
-rw-r--r--ydb/core/cms/json_proxy_console_log.h76
-rw-r--r--ydb/core/cms/ui/cms.css191
-rw-r--r--ydb/core/cms/ui/cms.js12
-rw-r--r--ydb/core/cms/ui/console_log.js488
-rw-r--r--ydb/core/cms/ui/index.html74
-rw-r--r--ydb/core/protos/console.proto32
-rw-r--r--ydb/core/protos/console_config.proto3
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ 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 {