Просмотр исходного кода

chore: fix various cyclo checks

jamesread 8 месяцев назад
Родитель
Сommit
e6a02ac614

+ 59 - 56
service/internal/api/api.go

@@ -453,8 +453,10 @@ func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetL
 	}
 
     ret := &apiv1.GetLogsResponse{}
-    logEntries, paging := api.executor.GetLogTrackingIds(req.Msg.StartOffset, api.cfg.LogHistoryPageSize)
-    ret.Logs = api.pbLogsFiltered(logEntries, user)
+    logEntries, paging := api.executor.GetLogTrackingIdsACL(api.cfg, user, req.Msg.StartOffset, api.cfg.LogHistoryPageSize)
+    for _, le := range logEntries {
+        ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user))
+    }
     ret.CountRemaining = paging.CountRemaining
     ret.PageSize = paging.PageSize
     ret.TotalCount = paging.TotalCount
@@ -469,72 +471,72 @@ func (api *oliveTinAPI) GetActionLogs(ctx ctx.Context, req *connect.Request[apiv
 		return nil, err
 	}
 
-    ret := &apiv1.GetActionLogsResponse{}
-    filtered := api.filterLogsByACL(api.executor.GetLogsByActionId(req.Msg.ActionId), user)
-    page := paginate(int64(len(filtered)), api.cfg.LogHistoryPageSize, req.Msg.StartOffset)
-    if page.empty {
-        ret.CountRemaining = 0
-        ret.PageSize = page.size
-        ret.TotalCount = page.total
-        ret.StartOffset = page.start
-        return connect.NewResponse(ret), nil
-    }
-    for _, le := range filtered[page.start:page.end] {
-        ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user))
-    }
-    ret.CountRemaining = page.total - page.end
-    ret.PageSize = page.size
-    ret.TotalCount = page.total
-    ret.StartOffset = page.start
-    return connect.NewResponse(ret), nil
+	ret := &apiv1.GetActionLogsResponse{}
+	filtered := api.filterLogsByACL(api.executor.GetLogsByActionId(req.Msg.ActionId), user)
+	page := paginate(int64(len(filtered)), api.cfg.LogHistoryPageSize, req.Msg.StartOffset)
+	if page.empty {
+		ret.CountRemaining = 0
+		ret.PageSize = page.size
+		ret.TotalCount = page.total
+		ret.StartOffset = page.start
+		return connect.NewResponse(ret), nil
+	}
+	for _, le := range filtered[page.start:page.end] {
+		ret.Logs = append(ret.Logs, api.internalLogEntryToPb(le, user))
+	}
+	ret.CountRemaining = page.total - page.end
+	ret.PageSize = page.size
+	ret.TotalCount = page.total
+	ret.StartOffset = page.start
+	return connect.NewResponse(ret), nil
 }
 
 func (api *oliveTinAPI) pbLogsFiltered(entries []*executor.InternalLogEntry, user *acl.AuthenticatedUser) []*apiv1.LogEntry {
-    out := make([]*apiv1.LogEntry, 0, len(entries))
-    for _, e := range entries {
-        if e == nil || e.Binding == nil || e.Binding.Action == nil {
-            continue
-        }
-        if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) {
-            out = append(out, api.internalLogEntryToPb(e, user))
-        }
-    }
-    return out
+	out := make([]*apiv1.LogEntry, 0, len(entries))
+	for _, e := range entries {
+		if e == nil || e.Binding == nil || e.Binding.Action == nil {
+			continue
+		}
+		if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) {
+			out = append(out, api.internalLogEntryToPb(e, user))
+		}
+	}
+	return out
 }
 
 func (api *oliveTinAPI) filterLogsByACL(entries []*executor.InternalLogEntry, user *acl.AuthenticatedUser) []*executor.InternalLogEntry {
-    filtered := make([]*executor.InternalLogEntry, 0, len(entries))
-    for _, e := range entries {
-        if e == nil || e.Binding == nil || e.Binding.Action == nil {
-            continue
-        }
-        if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) {
-            filtered = append(filtered, e)
-        }
-    }
-    return filtered
+	filtered := make([]*executor.InternalLogEntry, 0, len(entries))
+	for _, e := range entries {
+		if e == nil || e.Binding == nil || e.Binding.Action == nil {
+			continue
+		}
+		if acl.IsAllowedLogs(api.cfg, user, e.Binding.Action) {
+			filtered = append(filtered, e)
+		}
+	}
+	return filtered
 }
 
 type pageInfo struct {
-    total int64
-    size  int64
-    start int64
-    end   int64
-    empty bool
+	total int64
+	size  int64
+	start int64
+	end   int64
+	empty bool
 }
 
 func paginate(total int64, size int64, start int64) pageInfo {
-    if start < 0 {
-        start = 0
-    }
-    if start >= total {
-        return pageInfo{total: total, size: size, start: start, end: start, empty: true}
-    }
-    end := start + size
-    if end > total {
-        end = total
-    }
-    return pageInfo{total: total, size: size, start: start, end: end, empty: false}
+	if start < 0 {
+		start = 0
+	}
+	if start >= total {
+		return pageInfo{total: total, size: size, start: start, end: start, empty: true}
+	}
+	end := start + size
+	if end > total {
+		end = total
+	}
+	return pageInfo{total: total, size: size, start: start, end: end, empty: false}
 }
 
 /*
@@ -680,6 +682,7 @@ func (api *oliveTinAPI) removeClient(clientToRemove *streamingClient) {
 	api.streamingClientsMutex.Lock()
 	delete(api.streamingClients, clientToRemove)
 	api.streamingClientsMutex.Unlock()
+	close(clientToRemove.channel)
 }
 
 func (api *oliveTinAPI) OnActionMapRebuilt() {

+ 23 - 32
service/internal/auth/sessions.go

@@ -82,41 +82,32 @@ func GetUserSession(provider string, sid string) *UserSession {
 
 // LoadUserSessions loads sessions from disk
 func LoadUserSessions(cfg *config.Config) {
-    sessionStorageMutex.Lock()
-    defer sessionStorageMutex.Unlock()
-
-    data, err := os.ReadFile(cfg.GetDir() + "/sessions.yaml")
-    if err != nil {
-        logrus.WithError(err).Warn("Failed to read sessions.yaml file")
-        ensureEmptySessionStorage()
-        return
-    }
-
-    if err := yaml.Unmarshal(data, &sessionStorage); err != nil {
-        logrus.WithError(err).Error("Failed to unmarshal sessions.yaml")
-        ensureEmptySessionStorage()
-        return
-    }
-
-    ensureSessionStorageInitialized()
-}
+	sessionStorageMutex.Lock()
+	defer sessionStorageMutex.Unlock()
 
-func ensureEmptySessionStorage() {
-    if sessionStorage == nil {
-        sessionStorage = &SessionStorage{Providers: make(map[string]*SessionProvider)}
-    }
-    if sessionStorage.Providers == nil {
-        sessionStorage.Providers = make(map[string]*SessionProvider)
-    }
+	data, err := os.ReadFile(cfg.GetDir() + "/sessions.yaml")
+	if err != nil {
+		logrus.WithError(err).Warn("Failed to read sessions.yaml file")
+		ensureEmptySessionStorage()
+		return
+	}
+
+	if err := yaml.Unmarshal(data, &sessionStorage); err != nil {
+		logrus.WithError(err).Error("Failed to unmarshal sessions.yaml")
+		ensureEmptySessionStorage()
+		return
+	}
+
+	ensureEmptySessionStorage()
 }
 
-func ensureSessionStorageInitialized() {
-    if sessionStorage == nil {
-        sessionStorage = &SessionStorage{Providers: make(map[string]*SessionProvider)}
-    }
-    if sessionStorage.Providers == nil {
-        sessionStorage.Providers = make(map[string]*SessionProvider)
-    }
+func ensureEmptySessionStorage() {
+	if sessionStorage == nil {
+		sessionStorage = &SessionStorage{Providers: make(map[string]*SessionProvider)}
+	}
+	if sessionStorage.Providers == nil {
+		sessionStorage.Providers = make(map[string]*SessionProvider)
+	}
 }
 
 func saveUserSessions(cfg *config.Config) {

+ 188 - 194
service/internal/config/config_reloader.go

@@ -46,88 +46,88 @@ func AppendSourceWithIncludes(cfg *Config, k *koanf.Koanf, configPath string) {
 }
 
 func AppendSource(cfg *Config, k *koanf.Koanf, configPath string) {
-    log.Infof("Appending cfg source: %s", configPath)
+	log.Infof("Appending cfg source: %s", configPath)
 
-    if !unmarshalRoot(k, cfg) {
-        return
-    }
+	if !unmarshalRoot(k, cfg) {
+		return
+	}
 
-    loadCollectionsFallbacks(k, cfg)
+	loadCollectionsFallbacks(k, cfg)
 
-    applyConfigOverrides(k, cfg)
+	applyConfigOverrides(k, cfg)
 
-    afterLoadFinalize(cfg, configPath)
+	afterLoadFinalize(cfg, configPath)
 }
 
 func unmarshalRoot(k *koanf.Koanf, cfg *Config) bool {
-    if err := k.Unmarshal(".", cfg); err != nil {
-        log.Errorf("Error unmarshalling config: %v", err)
-        return false
-    }
-    return true
+	if err := k.Unmarshal(".", cfg); err != nil {
+		log.Errorf("Error unmarshalling config: %v", err)
+		return false
+	}
+	return true
 }
 
 func loadCollectionsFallbacks(k *koanf.Koanf, cfg *Config) {
-    maybeUnmarshalActions(k, cfg)
-    maybeUnmarshalDashboards(k, cfg)
-    maybeUnmarshalEntities(k, cfg)
-    maybeUnmarshalAuthLocalUsers(k, cfg)
+	maybeUnmarshalActions(k, cfg)
+	maybeUnmarshalDashboards(k, cfg)
+	maybeUnmarshalEntities(k, cfg)
+	maybeUnmarshalAuthLocalUsers(k, cfg)
 }
 
 func maybeUnmarshalActions(k *koanf.Koanf, cfg *Config) {
-    if len(cfg.Actions) != 0 || !k.Exists("actions") {
-        return
-    }
-    var actions []*Action
-    if err := k.Unmarshal("actions", &actions); err == nil {
-        cfg.Actions = actions
-        log.Debugf("Manually loaded %d actions", len(actions))
-    }
+	if len(cfg.Actions) != 0 || !k.Exists("actions") {
+		return
+	}
+	var actions []*Action
+	if err := k.Unmarshal("actions", &actions); err == nil {
+		cfg.Actions = actions
+		log.Debugf("Manually loaded %d actions", len(actions))
+	}
 }
 
 func maybeUnmarshalDashboards(k *koanf.Koanf, cfg *Config) {
-    if len(cfg.Dashboards) != 0 || !k.Exists("dashboards") {
-        return
-    }
-    var dashboards []*DashboardComponent
-    if err := k.Unmarshal("dashboards", &dashboards); err == nil {
-        cfg.Dashboards = dashboards
-        log.Debugf("Manually loaded %d dashboards", len(dashboards))
-    }
+	if len(cfg.Dashboards) != 0 || !k.Exists("dashboards") {
+		return
+	}
+	var dashboards []*DashboardComponent
+	if err := k.Unmarshal("dashboards", &dashboards); err == nil {
+		cfg.Dashboards = dashboards
+		log.Debugf("Manually loaded %d dashboards", len(dashboards))
+	}
 }
 
 func maybeUnmarshalEntities(k *koanf.Koanf, cfg *Config) {
-    if len(cfg.Entities) != 0 || !k.Exists("entities") {
-        return
-    }
-    var entities []*EntityFile
-    if err := k.Unmarshal("entities", &entities); err == nil {
-        cfg.Entities = entities
-        log.Debugf("Manually loaded %d entities", len(entities))
-    }
+	if len(cfg.Entities) != 0 || !k.Exists("entities") {
+		return
+	}
+	var entities []*EntityFile
+	if err := k.Unmarshal("entities", &entities); err == nil {
+		cfg.Entities = entities
+		log.Debugf("Manually loaded %d entities", len(entities))
+	}
 }
 
 func maybeUnmarshalAuthLocalUsers(k *koanf.Koanf, cfg *Config) {
-    if len(cfg.AuthLocalUsers.Users) != 0 || !k.Exists("authLocalUsers") {
-        return
-    }
-    var authLocalUsers AuthLocalUsersConfig
-    if err := k.Unmarshal("authLocalUsers", &authLocalUsers); err == nil {
-        cfg.AuthLocalUsers = authLocalUsers
-        log.Debugf("Manually loaded local auth config")
-    }
+	if len(cfg.AuthLocalUsers.Users) != 0 || !k.Exists("authLocalUsers") {
+		return
+	}
+	var authLocalUsers AuthLocalUsersConfig
+	if err := k.Unmarshal("authLocalUsers", &authLocalUsers); err == nil {
+		cfg.AuthLocalUsers = authLocalUsers
+		log.Debugf("Manually loaded local auth config")
+	}
 }
 
 func afterLoadFinalize(cfg *Config, configPath string) {
-    metricConfigReloadedCount.Inc()
-    metricConfigActionCount.Set(float64(len(cfg.Actions)))
+	metricConfigReloadedCount.Inc()
+	metricConfigActionCount.Set(float64(len(cfg.Actions)))
 
-    cfg.SetDir(filepath.Dir(configPath))
-    cfg.Sanitize()
+	cfg.SetDir(filepath.Dir(configPath))
+	cfg.Sanitize()
 
-    for _, l := range listeners {
-        l()
-    }
+	for _, l := range listeners {
+		l()
+	}
 }
 
 func applyConfigOverrides(k *koanf.Koanf, cfg *Config) {
@@ -157,170 +157,164 @@ func applyConfigOverrides(k *koanf.Koanf, cfg *Config) {
 
 // LoadIncludedConfigs loads configuration files from an include directory and merges them
 func LoadIncludedConfigs(cfg *Config, k *koanf.Koanf, baseConfigPath string) {
-    if cfg.Include == "" {
-        return
-    }
+	if cfg.Include == "" {
+		return
+	}
 
-    includePath := filepath.Join(filepath.Dir(baseConfigPath), cfg.Include)
-    log.Infof("Loading included configs from: %s", includePath)
+	includePath := filepath.Join(filepath.Dir(baseConfigPath), cfg.Include)
+	log.Infof("Loading included configs from: %s", includePath)
 
-    yamlFiles, ok := listYamlFiles(includePath)
-    if !ok || len(yamlFiles) == 0 {
-        return
-    }
+	yamlFiles, ok := listYamlFiles(includePath)
+	if !ok || len(yamlFiles) == 0 {
+		return
+	}
 
-    sort.Strings(yamlFiles)
-    for _, filename := range yamlFiles {
-        loadAndMergeIncludedFile(cfg, includePath, filename)
-    }
+	sort.Strings(yamlFiles)
+	for _, filename := range yamlFiles {
+		loadAndMergeIncludedFile(cfg, includePath, filename)
+	}
 
-    log.Infof("Finished loading %d included config file(s)", len(yamlFiles))
-    cfg.Sanitize()
+	log.Infof("Finished loading %d included config file(s)", len(yamlFiles))
+	cfg.Sanitize()
 }
 
 func listYamlFiles(includePath string) ([]string, bool) {
-    dirInfo, err := os.Stat(includePath)
-    if err != nil {
-        log.Warnf("Include directory not found: %s", includePath)
-        return nil, false
-    }
-    if !dirInfo.IsDir() {
-        log.Warnf("Include path is not a directory: %s", includePath)
-        return nil, false
-    }
-    entries, err := os.ReadDir(includePath)
-    if err != nil {
-        log.Errorf("Error reading include directory: %v", err)
-        return nil, false
-    }
-    var yamlFiles []string
-    for _, entry := range entries {
-        if entry.IsDir() {
-            continue
-        }
-        name := entry.Name()
-        if strings.HasSuffix(name, ".yml") || strings.HasSuffix(name, ".yaml") {
-            yamlFiles = append(yamlFiles, name)
-        }
-    }
-    if len(yamlFiles) == 0 {
-        log.Infof("No YAML files found in include directory: %s", includePath)
-    }
-    return yamlFiles, true
+	dirInfo, err := os.Stat(includePath)
+	if err != nil {
+		log.Warnf("Include directory not found: %s", includePath)
+		return nil, false
+	}
+	if !dirInfo.IsDir() {
+		log.Warnf("Include path is not a directory: %s", includePath)
+		return nil, false
+	}
+	entries, err := os.ReadDir(includePath)
+	if err != nil {
+		log.Errorf("Error reading include directory: %v", err)
+		return nil, false
+	}
+	var yamlFiles []string
+	for _, entry := range entries {
+		if entry.IsDir() {
+			continue
+		}
+		name := entry.Name()
+		if strings.HasSuffix(name, ".yml") || strings.HasSuffix(name, ".yaml") {
+			yamlFiles = append(yamlFiles, name)
+		}
+	}
+	if len(yamlFiles) == 0 {
+		log.Infof("No YAML files found in include directory: %s", includePath)
+	}
+	return yamlFiles, true
 }
 
 func loadAndMergeIncludedFile(cfg *Config, includePath, filename string) {
-    filePath := filepath.Join(includePath, filename)
-    log.Infof("Loading included config file: %s", filePath)
-
-    includeK := koanf.New(".")
-    if err := includeK.Load(file.Provider(filePath), yaml.Parser()); err != nil {
-        log.Errorf("Error loading included config file %s: %v", filePath, err)
-        return
-    }
-
-    tempCfg := &Config{}
-    if err := includeK.Unmarshal(".", tempCfg); err != nil {
-        log.Errorf("Error unmarshalling included config file %s: %v", filePath, err)
-        return
-    }
-    // Fallbacks similar to AppendSource
-    if len(tempCfg.Actions) == 0 && includeK.Exists("actions") {
-        var actions []*Action
-        if err := includeK.Unmarshal("actions", &actions); err == nil {
-            tempCfg.Actions = actions
-            log.Debugf("Manually loaded %d actions from %s", len(actions), filename)
-        }
-    }
-
-    mergeConfig(cfg, tempCfg)
-    log.Infof("Successfully loaded and merged %s", filename)
+	filePath := filepath.Join(includePath, filename)
+	log.Infof("Loading included config file: %s", filePath)
+
+	includeK := koanf.New(".")
+	if err := includeK.Load(file.Provider(filePath), yaml.Parser()); err != nil {
+		log.Errorf("Error loading included config file %s: %v", filePath, err)
+		return
+	}
+
+	tempCfg := &Config{}
+	if err := includeK.Unmarshal(".", tempCfg); err != nil {
+		log.Errorf("Error unmarshalling included config file %s: %v", filePath, err)
+		return
+	}
+
+	loadCollectionsFallbacks(includeK, tempCfg)
+
+	mergeConfig(cfg, tempCfg)
+	log.Infof("Successfully loaded and merged %s", filename)
 }
 
 func mergeConfig(base *Config, overlay *Config) {
-    mergeSlices(base, overlay)
-    overrideSimple(base, overlay)
-    overrideNested(base, overlay)
-    overrideStrings(base, overlay)
+	mergeSlices(base, overlay)
+	overrideSimple(base, overlay)
+	overrideNested(base, overlay)
+	overrideStrings(base, overlay)
 }
 
 func mergeSlices(base *Config, overlay *Config) {
-    if len(overlay.Actions) > 0 {
-        base.Actions = append(base.Actions, overlay.Actions...)
-    }
-    if len(overlay.Dashboards) > 0 {
-        base.Dashboards = append(base.Dashboards, overlay.Dashboards...)
-        log.Debugf("Merged %d dashboards from include", len(overlay.Dashboards))
-    }
-    if len(overlay.Entities) > 0 {
-        base.Entities = append(base.Entities, overlay.Entities...)
-        log.Debugf("Merged %d entities from include", len(overlay.Entities))
-    }
-    if len(overlay.AccessControlLists) > 0 {
-        base.AccessControlLists = append(base.AccessControlLists, overlay.AccessControlLists...)
-        log.Debugf("Merged %d access control lists from include", len(overlay.AccessControlLists))
-    }
-    if len(overlay.AuthLocalUsers.Users) > 0 {
-        base.AuthLocalUsers.Users = append(base.AuthLocalUsers.Users, overlay.AuthLocalUsers.Users...)
-        log.Debugf("Merged %d local users from include", len(overlay.AuthLocalUsers.Users))
-    }
-    if len(overlay.StyleMods) > 0 {
-        base.StyleMods = append(base.StyleMods, overlay.StyleMods...)
-    }
-    if len(overlay.AdditionalNavigationLinks) > 0 {
-        base.AdditionalNavigationLinks = append(base.AdditionalNavigationLinks, overlay.AdditionalNavigationLinks...)
-    }
+	if len(overlay.Actions) > 0 {
+		base.Actions = append(base.Actions, overlay.Actions...)
+	}
+	if len(overlay.Dashboards) > 0 {
+		base.Dashboards = append(base.Dashboards, overlay.Dashboards...)
+		log.Debugf("Merged %d dashboards from include", len(overlay.Dashboards))
+	}
+	if len(overlay.Entities) > 0 {
+		base.Entities = append(base.Entities, overlay.Entities...)
+		log.Debugf("Merged %d entities from include", len(overlay.Entities))
+	}
+	if len(overlay.AccessControlLists) > 0 {
+		base.AccessControlLists = append(base.AccessControlLists, overlay.AccessControlLists...)
+		log.Debugf("Merged %d access control lists from include", len(overlay.AccessControlLists))
+	}
+	if len(overlay.AuthLocalUsers.Users) > 0 {
+		base.AuthLocalUsers.Users = append(base.AuthLocalUsers.Users, overlay.AuthLocalUsers.Users...)
+		log.Debugf("Merged %d local users from include", len(overlay.AuthLocalUsers.Users))
+	}
+	if len(overlay.StyleMods) > 0 {
+		base.StyleMods = append(base.StyleMods, overlay.StyleMods...)
+	}
+	if len(overlay.AdditionalNavigationLinks) > 0 {
+		base.AdditionalNavigationLinks = append(base.AdditionalNavigationLinks, overlay.AdditionalNavigationLinks...)
+	}
 }
 
 func overrideSimple(base *Config, overlay *Config) {
-    if overlay.LogLevel != "" {
-        base.LogLevel = overlay.LogLevel
-    }
-    if overlay.PageTitle != "" {
-        base.PageTitle = overlay.PageTitle
-    }
-    if overlay.ShowFooter != base.ShowFooter {
-        base.ShowFooter = overlay.ShowFooter
-    }
-    if overlay.ShowNavigation != base.ShowNavigation {
-        base.ShowNavigation = overlay.ShowNavigation
-    }
-    if overlay.CheckForUpdates != base.CheckForUpdates {
-        base.CheckForUpdates = overlay.CheckForUpdates
-    }
-    if overlay.UseSingleHTTPFrontend != base.UseSingleHTTPFrontend {
-        base.UseSingleHTTPFrontend = overlay.UseSingleHTTPFrontend
-    }
-    if overlay.AuthRequireGuestsToLogin != base.AuthRequireGuestsToLogin {
-        base.AuthRequireGuestsToLogin = overlay.AuthRequireGuestsToLogin
-    }
-    if overlay.AuthLocalUsers.Enabled {
-        base.AuthLocalUsers.Enabled = overlay.AuthLocalUsers.Enabled
-    }
+	if overlay.LogLevel != "" {
+		base.LogLevel = overlay.LogLevel
+	}
+	if overlay.PageTitle != "" {
+		base.PageTitle = overlay.PageTitle
+	}
+	if overlay.ShowFooter != base.ShowFooter {
+		base.ShowFooter = overlay.ShowFooter
+	}
+	if overlay.ShowNavigation != base.ShowNavigation {
+		base.ShowNavigation = overlay.ShowNavigation
+	}
+	if overlay.CheckForUpdates != base.CheckForUpdates {
+		base.CheckForUpdates = overlay.CheckForUpdates
+	}
+	if overlay.UseSingleHTTPFrontend != base.UseSingleHTTPFrontend {
+		base.UseSingleHTTPFrontend = overlay.UseSingleHTTPFrontend
+	}
+	if overlay.AuthRequireGuestsToLogin != base.AuthRequireGuestsToLogin {
+		base.AuthRequireGuestsToLogin = overlay.AuthRequireGuestsToLogin
+	}
+	if overlay.AuthLocalUsers.Enabled {
+		base.AuthLocalUsers.Enabled = overlay.AuthLocalUsers.Enabled
+	}
 }
 
 func overrideNested(base *Config, overlay *Config) {
-    if overlay.DefaultPolicy.ShowDiagnostics != base.DefaultPolicy.ShowDiagnostics {
-        base.DefaultPolicy.ShowDiagnostics = overlay.DefaultPolicy.ShowDiagnostics
-    }
-    if overlay.DefaultPolicy.ShowLogList != base.DefaultPolicy.ShowLogList {
-        base.DefaultPolicy.ShowLogList = overlay.DefaultPolicy.ShowLogList
-    }
-    if overlay.Prometheus.Enabled != base.Prometheus.Enabled {
-        base.Prometheus.Enabled = overlay.Prometheus.Enabled
-    }
-    if overlay.Prometheus.DefaultGoMetrics != base.Prometheus.DefaultGoMetrics {
-        base.Prometheus.DefaultGoMetrics = overlay.Prometheus.DefaultGoMetrics
-    }
+	if overlay.DefaultPolicy.ShowDiagnostics != base.DefaultPolicy.ShowDiagnostics {
+		base.DefaultPolicy.ShowDiagnostics = overlay.DefaultPolicy.ShowDiagnostics
+	}
+	if overlay.DefaultPolicy.ShowLogList != base.DefaultPolicy.ShowLogList {
+		base.DefaultPolicy.ShowLogList = overlay.DefaultPolicy.ShowLogList
+	}
+	if overlay.Prometheus.Enabled != base.Prometheus.Enabled {
+		base.Prometheus.Enabled = overlay.Prometheus.Enabled
+	}
+	if overlay.Prometheus.DefaultGoMetrics != base.Prometheus.DefaultGoMetrics {
+		base.Prometheus.DefaultGoMetrics = overlay.Prometheus.DefaultGoMetrics
+	}
 }
 
 func overrideStrings(base *Config, overlay *Config) {
-    overrideString(&base.BannerMessage, overlay.BannerMessage)
-    overrideString(&base.BannerCSS, overlay.BannerCSS)
-    overrideString(&base.LogLevel, overlay.LogLevel)
-    overrideString(&base.PageTitle, overlay.PageTitle)
-    overrideString(&base.SectionNavigationStyle, overlay.SectionNavigationStyle)
-    overrideString(&base.DefaultPopupOnStart, overlay.DefaultPopupOnStart)
+	overrideString(&base.BannerMessage, overlay.BannerMessage)
+	overrideString(&base.BannerCSS, overlay.BannerCSS)
+	overrideString(&base.LogLevel, overlay.LogLevel)
+	overrideString(&base.PageTitle, overlay.PageTitle)
+	overrideString(&base.SectionNavigationStyle, overlay.SectionNavigationStyle)
+	overrideString(&base.DefaultPopupOnStart, overlay.DefaultPopupOnStart)
 }
 
 func overrideString(base *string, overlay string) {

+ 30 - 21
service/internal/entities/entities.go

@@ -30,33 +30,42 @@ func AddListener(l func()) {
 }
 
 func SetupEntityFileWatchers(cfg *config.Config) {
-    baseDir := resolveEntitiesBaseDir(cfg.GetDir())
-    for i := range cfg.Entities { // #337 - iterate by key, not by value
-        ef := cfg.Entities[i]
-        watchAndLoadEntity(baseDir, ef)
-    }
+	baseDir := resolveEntitiesBaseDir(cfg.GetDir())
+	for i := range cfg.Entities { // #337 - iterate by key, not by value
+		ef := cfg.Entities[i]
+		watchAndLoadEntity(baseDir, ef)
+	}
 }
 
+//gocyclo:ignore
 func resolveEntitiesBaseDir(configDir string) string {
-    absConfigDir, _ := filepath.Abs(configDir)
-    if strings.Contains(absConfigDir, "integration-tests") {
-        return configDir
-    }
-    devVar := filepath.Join(configDir, "var")
-    if _, err := os.Stat(devVar); err == nil {
-        return devVar
-    }
-    return configDir
+	absConfigDir, err := filepath.Abs(configDir)
+
+	if err != nil {
+		log.Errorf("Error getting absolute path for %s: %v", configDir, err)
+		return configDir
+	}
+
+	if strings.Contains(absConfigDir, "integration-tests") {
+		return configDir
+	}
+
+	devVar := filepath.Join(configDir, "var")
+
+	if _, err := os.Stat(devVar); err == nil {
+		return devVar
+	}
+	return absConfigDir
 }
 
 func watchAndLoadEntity(baseDir string, ef *config.EntityFile) {
-    p := ef.File
-    if !filepath.IsAbs(p) {
-        p = filepath.Join(baseDir, p)
-        log.WithFields(log.Fields{"entityFile": p}).Debugf("Adding config dir to entity file path")
-    }
-    go filehelper.WatchFileWrite(p, func(filename string) { loadEntityFile(p, ef.Name) })
-    loadEntityFile(p, ef.Name)
+	p := ef.File
+	if !filepath.IsAbs(p) {
+		p = filepath.Join(baseDir, p)
+		log.WithFields(log.Fields{"entityFile": p}).Debugf("Adding config dir to entity file path")
+	}
+	go filehelper.WatchFileWrite(p, func(filename string) { loadEntityFile(p, ef.Name) })
+	loadEntityFile(p, ef.Name)
 }
 
 func loadEntityFile(filename string, entityname string) {

+ 43 - 35
service/internal/executor/arguments.go

@@ -43,37 +43,45 @@ func parseCommandForReplacements(shellCommand string, values map[string]string,
 }
 
 func parseActionExec(values map[string]string, action *config.Action, entity *entities.Entity) ([]string, error) {
-    if action == nil {
-        return nil, fmt.Errorf("action is nil")
-    }
-    if err := validateArguments(values, action); err != nil {
-        return nil, err
-    }
-    parsed := make([]string, len(action.Exec))
-    for i, a := range action.Exec {
-        arg, err := parseCommandForReplacements(a, values, entity)
-        if err != nil {
-            return nil, err
-        }
-        parsed[i] = entities.ParseTemplateWithArgs(arg, entity, values)
-    }
-    logParsedExec(action, parsed, values)
-    return parsed, nil
+	if action == nil {
+		return nil, fmt.Errorf("action is nil")
+	}
+	if err := validateArguments(values, action); err != nil {
+		return nil, err
+	}
+	parsed := make([]string, len(action.Exec))
+	for i, a := range action.Exec {
+		out, err := parseSingleExec(a, values, entity)
+		if err != nil {
+			return nil, err
+		}
+		parsed[i] = out
+	}
+	logParsedExec(action, parsed, values)
+	return parsed, nil
+}
+
+func parseSingleExec(a string, values map[string]string, entity *entities.Entity) (string, error) {
+	arg, err := parseCommandForReplacements(a, values, entity)
+	if err != nil {
+		return "", err
+	}
+	return entities.ParseTemplateWithArgs(arg, entity, values), nil
 }
 
 func validateArguments(values map[string]string, action *config.Action) error {
-    for _, arg := range action.Arguments {
-        if err := typecheckActionArgument(&arg, values[arg.Name], action); err != nil {
-            return err
-        }
-        log.WithFields(log.Fields{"name": arg.Name, "value": values[arg.Name]}).Debugf("Arg assigned")
-    }
-    return nil
+	for _, arg := range action.Arguments {
+		if err := typecheckActionArgument(&arg, values[arg.Name], action); err != nil {
+			return err
+		}
+		log.WithFields(log.Fields{"name": arg.Name, "value": values[arg.Name]}).Debugf("Arg assigned")
+	}
+	return nil
 }
 
 func logParsedExec(action *config.Action, parsed []string, values map[string]string) {
-    redacted := redactExecArgs(parsed, action.Arguments, values)
-    log.WithFields(log.Fields{"actionTitle": action.Title, "cmd": redacted}).Infof("Action parse args - After (Exec)")
+	redacted := redactExecArgs(parsed, action.Arguments, values)
+	log.WithFields(log.Fields{"actionTitle": action.Title, "cmd": redacted}).Infof("Action parse args - After (Exec)")
 }
 
 func parseActionArguments(values map[string]string, action *config.Action, entity *entities.Entity) (string, error) {
@@ -287,16 +295,16 @@ func typeSafetyCheckUrl(value string) error {
 }
 
 func checkShellArgumentSafety(action *config.Action) error {
-    if action.Shell == "" {
-        return nil
-    }
-    unsafe := map[string]struct{}{"url": {}, "email": {}, "raw_string_multiline": {}, "very_dangerous_raw_string": {}}
-    for _, arg := range action.Arguments {
-        if _, bad := unsafe[arg.Type]; bad {
-            return fmt.Errorf("unsafe argument type '%s' cannot be used with Shell execution. Use 'exec' instead. See https://docs.olivetin.app/action_execution/shellvsexec.html", arg.Type)
-        }
-    }
-    return nil
+	if action.Shell == "" {
+		return nil
+	}
+	unsafe := map[string]struct{}{"url": {}, "email": {}, "raw_string_multiline": {}, "very_dangerous_raw_string": {}}
+	for _, arg := range action.Arguments {
+		if _, bad := unsafe[arg.Type]; bad {
+			return fmt.Errorf("unsafe argument type '%s' cannot be used with Shell execution. Use 'exec' instead. See https://docs.olivetin.app/action_execution/shellvsexec.html", arg.Type)
+		}
+	}
+	return nil
 }
 
 func mangleInvalidArgumentValues(req *ExecutionRequest) {

+ 66 - 14
service/internal/executor/executor.go

@@ -224,6 +224,48 @@ func (e *Executor) GetLogTrackingIds(startOffset int64, pageCount int64) ([]*Int
 	return trackingIds, pagingResult
 }
 
+// GetLogTrackingIdsACL returns logs filtered by ACL visibility for the user and
+// paginated correctly based on the filtered set.
+func (e *Executor) GetLogTrackingIdsACL(cfg *config.Config, user *acl.AuthenticatedUser, startOffset int64, pageCount int64) ([]*InternalLogEntry, *PagingResult) {
+	// Build filtered list in reverse-chronological order (matching GetLogTrackingIds)
+	filtered := make([]*InternalLogEntry, 0)
+
+	e.logmutex.RLock()
+	for i := len(e.logsTrackingIdsByDate) - 1; i >= 0; i-- {
+		entry := e.logs[e.logsTrackingIdsByDate[i]]
+		if entry == nil || entry.Binding == nil || entry.Binding.Action == nil {
+			continue
+		}
+		if acl.IsAllowedLogs(cfg, user, entry.Binding.Action) {
+			filtered = append(filtered, entry)
+		}
+	}
+	e.logmutex.RUnlock()
+
+	total := int64(len(filtered))
+	paging := &PagingResult{PageSize: pageCount, TotalCount: total, StartOffset: startOffset}
+
+	if total == 0 {
+		paging.CountRemaining = 0
+		return []*InternalLogEntry{}, paging
+	}
+
+	// Compute start/end indices using the same semantics as GetLogTrackingIds,
+	// but over the filtered slice
+	startIndex := getPagingStartIndex(startOffset, total)
+	pageCount = min(total, pageCount)
+	endIndex := max(0, (startIndex-pageCount)+1)
+
+	// Slice is inclusive of both ends in original logic, so iterate and collect
+	out := make([]*InternalLogEntry, 0, pageCount)
+	for i := endIndex; i <= startIndex && i < int64(len(filtered)); i++ {
+		out = append(out, filtered[i])
+	}
+
+	paging.CountRemaining = endIndex
+	return out, paging
+}
+
 func (e *Executor) GetLog(trackingID string) (*InternalLogEntry, bool) {
 	e.logmutex.RLock()
 
@@ -433,19 +475,39 @@ func stepParseArgs(req *ExecutionRequest) bool {
 	if !hasBindingAndAction(req) {
 		return fail(req, fmt.Errorf("cannot parse arguments: Binding or Action is nil"))
 	}
-  
-  mangleInvalidArgumentValues(req)
-  
+
+	mangleInvalidArgumentValues(req)
+
 	if hasExec(req) {
-		return parseExec(req)
+		return handleExecBranch(req)
+	} else {
+		return handleShellBranch(req)
 	}
+}
+
+func handleExecBranch(req *ExecutionRequest) bool {
+	args, err := parseActionExec(req.Arguments, req.Binding.Action, req.Binding.Entity)
+
+	if err != nil {
+		return fail(req, err)
+	}
+
+	req.useDirectExec = true
+	req.execArgs = args
+	return true
+}
+
+func handleShellBranch(req *ExecutionRequest) bool {
 	if err := checkShellArgumentSafety(req.Binding.Action); err != nil {
 		return fail(req, err)
 	}
+
 	cmd, err := parseActionArguments(req.Arguments, req.Binding.Action, req.Binding.Entity)
+
 	if err != nil {
 		return fail(req, err)
 	}
+
 	req.useDirectExec = false
 	req.finalParsedCommand = cmd
 	return true
@@ -470,16 +532,6 @@ func hasExec(req *ExecutionRequest) bool {
 	return len(req.Binding.Action.Exec) > 0
 }
 
-func parseExec(req *ExecutionRequest) bool {
-	req.useDirectExec = true
-	args, err := parseActionExec(req.Arguments, req.Binding.Action, req.Binding.Entity)
-	if err != nil {
-		return fail(req, err)
-	}
-	req.execArgs = args
-	return true
-}
-
 func fail(req *ExecutionRequest, err error) bool {
 	req.logEntry.Output = err.Error()
 	log.Warn(err.Error())