collavre 0.3.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/collavre/actiontext.css +73 -71
- data/app/assets/stylesheets/collavre/activity_logs.css +18 -45
- data/app/assets/stylesheets/collavre/comments_popup.css +197 -35
- data/app/assets/stylesheets/collavre/creatives.css +101 -51
- data/app/assets/stylesheets/collavre/dark_mode.css +221 -88
- data/app/assets/stylesheets/collavre/design_tokens.css +334 -0
- data/app/assets/stylesheets/collavre/mention_menu.css +13 -9
- data/app/assets/stylesheets/collavre/popup.css +57 -27
- data/app/assets/stylesheets/collavre/slide_view.css +6 -6
- data/app/assets/stylesheets/collavre/user_menu.css +4 -5
- data/app/components/collavre/plans_timeline_component.html.erb +2 -2
- data/app/controllers/collavre/admin/orchestration_controller.rb +9 -2
- data/app/controllers/collavre/admin/settings_controller.rb +199 -0
- data/app/controllers/collavre/comments/reactions_controller.rb +1 -9
- data/app/controllers/collavre/comments_controller.rb +39 -162
- data/app/controllers/collavre/creatives_controller.rb +18 -58
- data/app/controllers/collavre/users_controller.rb +31 -3
- data/app/helpers/collavre/application_helper.rb +97 -0
- data/app/helpers/collavre/creatives_helper.rb +10 -202
- data/app/javascript/collavre.js +0 -1
- data/app/javascript/components/creative_tree_row.js +3 -2
- data/app/javascript/controllers/comment_controller.js +309 -4
- data/app/javascript/controllers/comments/form_controller.js +52 -0
- data/app/javascript/controllers/comments/presence_controller.js +13 -0
- data/app/javascript/controllers/creatives/tree_controller.js +2 -1
- data/app/javascript/controllers/link_creative_controller.js +29 -3
- data/app/javascript/lib/__tests__/html_code_block_wrapper.test.js +201 -0
- data/app/javascript/lib/html_code_block_wrapper.js +168 -0
- data/app/javascript/lib/utils/markdown.js +2 -1
- data/app/javascript/modules/creative_row_editor.js +5 -1
- data/app/javascript/utils/emoji_parser.js +21 -0
- data/app/jobs/collavre/ai_agent_job.rb +6 -2
- data/app/jobs/collavre/cron_action_job.rb +18 -6
- data/app/jobs/collavre/cron_scheduler_job.rb +112 -0
- data/app/models/collavre/comment/approvable.rb +50 -0
- data/app/models/collavre/comment/broadcastable.rb +119 -0
- data/app/models/collavre/comment/notifiable.rb +111 -0
- data/app/models/collavre/comment.rb +13 -258
- data/app/models/collavre/comment_reaction.rb +15 -0
- data/app/models/collavre/creative/describable.rb +86 -0
- data/app/models/collavre/creative/linkable.rb +77 -0
- data/app/models/collavre/creative/permissible.rb +103 -0
- data/app/models/collavre/creative.rb +3 -289
- data/app/models/collavre/orchestrator_policy.rb +1 -1
- data/app/models/collavre/system_setting.rb +27 -1
- data/app/models/collavre/user.rb +42 -0
- data/app/models/collavre/user_theme.rb +10 -0
- data/app/services/collavre/ai_agent/approval_handler.rb +110 -0
- data/app/services/collavre/ai_agent/message_builder.rb +129 -0
- data/app/services/collavre/ai_agent/review_handler.rb +70 -0
- data/app/services/collavre/ai_agent_service.rb +93 -150
- data/app/services/collavre/ai_client.rb +23 -4
- data/app/services/collavre/auto_theme_generator.rb +168 -50
- data/app/services/collavre/command_menu_service.rb +70 -0
- data/app/services/collavre/comment_move_service.rb +94 -0
- data/app/services/collavre/comments/action_executor.rb +10 -0
- data/app/services/collavre/comments/mcp_command.rb +1 -2
- data/app/services/collavre/creatives/create_service.rb +86 -0
- data/app/services/collavre/creatives/destroy_service.rb +41 -0
- data/app/services/collavre/creatives/index_query.rb +3 -0
- data/app/services/collavre/markdown_converter.rb +240 -0
- data/app/services/collavre/mention_parser.rb +63 -0
- data/app/services/collavre/orchestration/agent_context_builder.rb +24 -8
- data/app/services/collavre/orchestration/agent_orchestrator.rb +59 -10
- data/app/services/collavre/orchestration/loop_breaker.rb +12 -7
- data/app/services/collavre/orchestration/policy_resolver.rb +16 -2
- data/app/services/collavre/orchestration/scheduler.rb +4 -3
- data/app/services/collavre/orchestration/stuck_detector.rb +1 -1
- data/app/services/collavre/system_events/context_builder.rb +1 -6
- data/app/services/collavre/tools/creative_batch_service.rb +107 -0
- data/app/services/collavre/tools/creative_update_service.rb +17 -12
- data/app/services/collavre/tools/cron_create_service.rb +17 -5
- data/app/views/admin/shared/_tabs.html.erb +2 -1
- data/app/views/collavre/admin/orchestration/show.html.erb +11 -0
- data/app/views/collavre/admin/settings/_system_tab.html.erb +138 -0
- data/app/views/collavre/admin/settings/_uiux_tab.html.erb +44 -0
- data/app/views/collavre/admin/settings/index.html.erb +11 -0
- data/app/views/collavre/admin/settings/uiux.html.erb +11 -0
- data/app/views/collavre/comments/_comment.html.erb +15 -5
- data/app/views/collavre/comments/_comments_popup.html.erb +9 -2
- data/app/views/collavre/creatives/_mobile_actions_menu.html.erb +0 -3
- data/app/views/collavre/creatives/_share_button.html.erb +0 -52
- data/app/views/collavre/creatives/_share_modal.html.erb +52 -0
- data/app/views/collavre/creatives/index.html.erb +5 -8
- data/app/views/collavre/shared/navigation/_panels.html.erb +2 -2
- data/app/views/collavre/user_themes/index.html.erb +7 -9
- data/app/views/collavre/users/_contact_management.html.erb +2 -1
- data/app/views/collavre/users/edit_ai.html.erb +7 -0
- data/app/views/collavre/users/index.html.erb +16 -1
- data/app/views/collavre/users/new_ai.html.erb +18 -8
- data/app/views/collavre/users/passkeys.html.erb +1 -1
- data/app/views/collavre/users/show.html.erb +1 -1
- data/app/views/layouts/collavre/slide.html.erb +8 -1
- data/config/locales/admin.en.yml +88 -0
- data/config/locales/admin.ko.yml +88 -0
- data/config/locales/ai_agent.en.yml +5 -1
- data/config/locales/ai_agent.ko.yml +5 -1
- data/config/locales/comments.en.yml +5 -1
- data/config/locales/comments.ko.yml +5 -1
- data/config/locales/orchestration.en.yml +8 -0
- data/config/locales/orchestration.ko.yml +8 -0
- data/config/locales/users.en.yml +12 -0
- data/config/locales/users.ko.yml +12 -0
- data/config/routes.rb +7 -1
- data/db/migrate/20260212011655_add_quoted_comment_to_comments.rb +7 -0
- data/db/migrate/20260213044247_add_agent_conf_to_users.rb +5 -0
- data/lib/collavre/engine.rb +25 -0
- data/lib/collavre/version.rb +1 -1
- metadata +32 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
.common-popup {
|
|
3
3
|
position: absolute;
|
|
4
|
-
z-index:
|
|
5
|
-
background: var(--
|
|
6
|
-
border: 1px solid var(--color
|
|
7
|
-
box-shadow:
|
|
4
|
+
z-index: var(--layer-modal);
|
|
5
|
+
background: var(--surface-section);
|
|
6
|
+
border: 1px solid var(--border-color);
|
|
7
|
+
box-shadow: var(--shadow-2);
|
|
8
8
|
border-radius: 6px;
|
|
9
9
|
padding: 0.35em;
|
|
10
10
|
max-width: min(420px, 90vw);
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
overflow-y: auto;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
.common-popup-list li {
|
|
22
|
+
list-style: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
21
25
|
.mention-popup ul,
|
|
22
26
|
.common-popup ul {
|
|
23
27
|
list-style: none;
|
|
@@ -34,7 +38,7 @@
|
|
|
34
38
|
.mention-item.active,
|
|
35
39
|
.common-popup-item:hover,
|
|
36
40
|
.common-popup-item.active {
|
|
37
|
-
background: var(--
|
|
41
|
+
background: var(--border-drag-over);
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
.common-popup-item {
|
|
@@ -49,21 +53,21 @@
|
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
.command-label {
|
|
52
|
-
font-weight:
|
|
56
|
+
font-weight: var(--weight-6);
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
.command-args {
|
|
56
60
|
font-size: 0.85em;
|
|
57
|
-
color: var(--
|
|
61
|
+
color: var(--text-muted);
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
.command-aliases {
|
|
61
65
|
font-size: 0.85em;
|
|
62
|
-
color: var(--
|
|
66
|
+
color: var(--text-muted);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
.command-description {
|
|
66
70
|
margin-top: 0.2em;
|
|
67
71
|
font-size: 0.85em;
|
|
68
|
-
color: var(--
|
|
72
|
+
color: var(--text-muted);
|
|
69
73
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
.popup-box {
|
|
2
|
-
background: var(--
|
|
3
|
-
border: 1px solid var(--color
|
|
2
|
+
background: var(--surface-bg);
|
|
3
|
+
border: 1px solid var(--border-color);
|
|
4
4
|
padding: 1em;
|
|
5
|
-
box-shadow:
|
|
6
|
-
border-radius:
|
|
5
|
+
box-shadow: var(--shadow-2);
|
|
6
|
+
border-radius: var(--radius-3);
|
|
7
7
|
position: relative;
|
|
8
8
|
display: flex;
|
|
9
9
|
flex-direction: column;
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
|
|
44
44
|
.common-popup {
|
|
45
45
|
position: absolute;
|
|
46
|
-
z-index:
|
|
47
|
-
background: var(--
|
|
48
|
-
border: 1px solid var(--color
|
|
46
|
+
z-index: var(--layer-modal);
|
|
47
|
+
background: var(--surface-section);
|
|
48
|
+
border: 1px solid var(--border-color);
|
|
49
49
|
padding: 1em;
|
|
50
|
-
box-shadow:
|
|
51
|
-
border-radius:
|
|
50
|
+
box-shadow: var(--shadow-3);
|
|
51
|
+
border-radius: var(--radius-3);
|
|
52
52
|
display: flex;
|
|
53
53
|
flex-direction: column;
|
|
54
54
|
max-width: 90vw;
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
margin-bottom: 0.75em;
|
|
61
61
|
padding: 0.5em;
|
|
62
62
|
border-radius: 6px;
|
|
63
|
-
border: 1px solid var(--color
|
|
64
|
-
background: var(--
|
|
65
|
-
color: var(--
|
|
63
|
+
border: 1px solid var(--border-color);
|
|
64
|
+
background: var(--surface-bg);
|
|
65
|
+
color: var(--text-primary);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
.common-popup h3 {
|
|
@@ -71,25 +71,55 @@
|
|
|
71
71
|
font-size: 1.1em;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
.popup-box input:not([type="radio"]):not([type="checkbox"]),
|
|
75
|
+
.popup-box select {
|
|
76
|
+
width: 100%;
|
|
77
|
+
box-sizing: border-box;
|
|
78
|
+
}
|
|
79
|
+
|
|
74
80
|
.popup-close-btn {
|
|
75
81
|
position: absolute;
|
|
76
82
|
top: 0.75em;
|
|
77
83
|
right: 0.75em;
|
|
78
|
-
background:
|
|
84
|
+
background: transparent;
|
|
79
85
|
border: none;
|
|
86
|
+
padding: 0;
|
|
80
87
|
font-size: 1.2em;
|
|
81
88
|
line-height: 1;
|
|
82
89
|
cursor: pointer;
|
|
83
|
-
color: var(--
|
|
90
|
+
color: var(--text-primary);
|
|
91
|
+
opacity: 0.6;
|
|
92
|
+
appearance: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.popup-close-btn:hover {
|
|
96
|
+
opacity: 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.delete-share-btn {
|
|
100
|
+
background: transparent;
|
|
101
|
+
border: none;
|
|
102
|
+
padding: 0;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
color: var(--text-primary);
|
|
105
|
+
appearance: none;
|
|
106
|
+
font-size: 1.1em;
|
|
107
|
+
opacity: 0.6;
|
|
108
|
+
padding: 0 0.5em;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.delete-share-btn:hover {
|
|
112
|
+
opacity: 1;
|
|
113
|
+
color: var(--danger);
|
|
84
114
|
}
|
|
85
115
|
|
|
86
116
|
#github-integration-modal .github-modal-status {
|
|
87
|
-
color: var(--
|
|
117
|
+
color: var(--text-muted);
|
|
88
118
|
margin-bottom: 1em;
|
|
89
119
|
}
|
|
90
120
|
|
|
91
121
|
#github-integration-modal .github-modal-subtext {
|
|
92
|
-
color: var(--
|
|
122
|
+
color: var(--text-muted);
|
|
93
123
|
}
|
|
94
124
|
|
|
95
125
|
#github-integration-modal .github-existing-connections {
|
|
@@ -99,19 +129,19 @@
|
|
|
99
129
|
#github-integration-modal .github-modal-list {
|
|
100
130
|
padding-left: 1.2em;
|
|
101
131
|
margin-bottom: 0.75em;
|
|
102
|
-
color: var(--
|
|
132
|
+
color: var(--text-primary);
|
|
103
133
|
}
|
|
104
134
|
|
|
105
135
|
#github-integration-modal .github-modal-list-box {
|
|
106
|
-
border: 1px solid var(--color
|
|
136
|
+
border: 1px solid var(--border-color);
|
|
107
137
|
padding: 0.5em;
|
|
108
|
-
border-radius:
|
|
109
|
-
background: var(--
|
|
110
|
-
color: var(--
|
|
138
|
+
border-radius: var(--radius-1);
|
|
139
|
+
background: var(--surface-section);
|
|
140
|
+
color: var(--text-primary);
|
|
111
141
|
}
|
|
112
142
|
|
|
113
143
|
#github-integration-modal .github-modal-empty {
|
|
114
|
-
color: var(--
|
|
144
|
+
color: var(--text-muted);
|
|
115
145
|
}
|
|
116
146
|
|
|
117
147
|
.popup-menu-wrapper {
|
|
@@ -122,11 +152,11 @@
|
|
|
122
152
|
.popup-menu {
|
|
123
153
|
display: none;
|
|
124
154
|
position: absolute;
|
|
125
|
-
z-index:
|
|
126
|
-
background: var(--
|
|
127
|
-
border: 1px solid var(--color
|
|
155
|
+
z-index: var(--layer-modal);
|
|
156
|
+
background: var(--surface-section);
|
|
157
|
+
border: 1px solid var(--border-color);
|
|
128
158
|
padding: 0.5em;
|
|
129
|
-
box-shadow: 0 2px 8px var(--color
|
|
159
|
+
box-shadow: 0 2px 8px var(--border-color);
|
|
130
160
|
min-width: 220px;
|
|
131
161
|
top: calc(100% + 4px);
|
|
132
162
|
left: 0;
|
|
@@ -164,4 +194,4 @@
|
|
|
164
194
|
display: block;
|
|
165
195
|
min-width: 100%;
|
|
166
196
|
}
|
|
167
|
-
}
|
|
197
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
height: 100vh;
|
|
3
3
|
display: flex;
|
|
4
4
|
overflow: auto;
|
|
5
|
-
padding:
|
|
5
|
+
padding: var(--space-7);
|
|
6
6
|
box-sizing: border-box;
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
|
|
18
18
|
#slide-controls {
|
|
19
19
|
position: fixed;
|
|
20
|
-
bottom:
|
|
20
|
+
bottom: var(--space-2);
|
|
21
21
|
left: 50%;
|
|
22
22
|
transform: translateX(-50%);
|
|
23
23
|
z-index: 10;
|
|
24
24
|
display: flex;
|
|
25
|
-
gap:
|
|
25
|
+
gap: var(--space-2);
|
|
26
26
|
align-items: center;
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
left: 0;
|
|
59
59
|
right: 0;
|
|
60
60
|
height: 20vh;
|
|
61
|
-
background: var(--color
|
|
61
|
+
background: var(--border-color);
|
|
62
62
|
align-items: center;
|
|
63
63
|
justify-content: center;
|
|
64
64
|
}
|
|
@@ -66,14 +66,14 @@
|
|
|
66
66
|
#slide-caption {
|
|
67
67
|
display: block;
|
|
68
68
|
pointer-events: none;
|
|
69
|
-
padding:
|
|
69
|
+
padding: var(--space-2);
|
|
70
70
|
text-align: center;
|
|
71
71
|
white-space: pre-wrap;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
#slide-timer {
|
|
75
75
|
display: block;
|
|
76
|
-
margin-top:
|
|
76
|
+
margin-top: var(--space-2);
|
|
77
77
|
font-variant-numeric: tabular-nums;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.nav-avatar {
|
|
2
|
-
width:
|
|
3
|
-
height:
|
|
2
|
+
width: var(--space-7);
|
|
3
|
+
height: var(--space-7);
|
|
4
4
|
border-radius: 50%;
|
|
5
5
|
}
|
|
6
6
|
|
|
@@ -27,8 +27,7 @@
|
|
|
27
27
|
display: flex;
|
|
28
28
|
align-items: center;
|
|
29
29
|
justify-content: center;
|
|
30
|
-
font-weight:
|
|
31
|
-
color: var(--
|
|
30
|
+
font-weight: var(--weight-7);
|
|
31
|
+
color: var(--text-primary);
|
|
32
32
|
pointer-events: none;
|
|
33
33
|
}
|
|
34
|
-
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div class="plans-timeline-wrapper">
|
|
2
|
-
<button id="timeline-today-btn" type="button"><%= t('collavre.plans.today', default: '오늘') %></button>
|
|
2
|
+
<button id="timeline-today-btn" class="btn btn-xs" type="button"><%= t('collavre.plans.today', default: '오늘') %></button>
|
|
3
3
|
<div id="plans-timeline" class="horizontal-timeline" data-plans='<%= raw plan_data.to_json %>' data-start-date="<%= @start_date %>" data-end-date="<%= @end_date %>" data-delete-confirm="<%= t('collavre.plans.delete_confirm', default: 'Are you sure?') %>"></div>
|
|
4
4
|
</div>
|
|
5
5
|
<hr>
|
|
@@ -9,6 +9,6 @@
|
|
|
9
9
|
<input type="text" id="plan-select-creative-input" placeholder="<%= t('collavre.plans.select_creative', default: 'Select Creative') %>" autocomplete="off">
|
|
10
10
|
<%= form.date_field :start_date, placeholder: t('collavre.plans.start_date'), id: 'plan-start-date' %>
|
|
11
11
|
<%= form.date_field :target_date, placeholder: t('collavre.plans.target_date'), id: 'plan-target-date' %>
|
|
12
|
-
<%= form.submit t('collavre.plans.add_plan'), id: 'add-plan-btn', disabled: true %>
|
|
12
|
+
<%= form.submit t('collavre.plans.add_plan'), id: 'add-plan-btn', class: 'btn btn-sm btn-primary', disabled: true %>
|
|
13
13
|
<% end %>
|
|
14
14
|
</div>
|
|
@@ -39,7 +39,8 @@ module Collavre
|
|
|
39
39
|
# Group by type for readable YAML structure
|
|
40
40
|
structure = {
|
|
41
41
|
"arbitration" => { "global" => nil, "overrides" => [] },
|
|
42
|
-
"scheduling" => { "global" => nil, "overrides" => [] }
|
|
42
|
+
"scheduling" => { "global" => nil, "overrides" => [] },
|
|
43
|
+
"collaboration" => { "global" => nil, "overrides" => [] }
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
policies.each do |policy|
|
|
@@ -89,6 +90,12 @@ module Collavre
|
|
|
89
90
|
"backoff_strategy" => "exponential",
|
|
90
91
|
"topic_max_concurrent_jobs" => 1
|
|
91
92
|
}
|
|
93
|
+
},
|
|
94
|
+
"collaboration" => {
|
|
95
|
+
"global" => {
|
|
96
|
+
"a2a_completion_instruction" => nil,
|
|
97
|
+
"mention_rule" => nil
|
|
98
|
+
}
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
end
|
|
@@ -97,7 +104,7 @@ module Collavre
|
|
|
97
104
|
raise PolicyValidationError, t("admin.orchestration.invalid_format") unless parsed.is_a?(Hash)
|
|
98
105
|
|
|
99
106
|
parsed.each do |type, data|
|
|
100
|
-
unless %w[arbitration scheduling].include?(type)
|
|
107
|
+
unless %w[arbitration scheduling collaboration].include?(type)
|
|
101
108
|
raise PolicyValidationError, t("admin.orchestration.unknown_policy_type", type: type)
|
|
102
109
|
end
|
|
103
110
|
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Collavre
|
|
4
|
+
module Admin
|
|
5
|
+
class SettingsController < ApplicationController
|
|
6
|
+
before_action :require_system_admin!
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@help_link = SystemSetting.find_by(key: "help_menu_link")&.value
|
|
10
|
+
@mcp_tool_approval = SystemSetting.find_by(key: "mcp_tool_approval_required")&.value == "true"
|
|
11
|
+
@creatives_login_required = SystemSetting.creatives_login_required?
|
|
12
|
+
@home_page_path = SystemSetting.home_page_path
|
|
13
|
+
|
|
14
|
+
# Account lockout settings
|
|
15
|
+
@max_login_attempts = SystemSetting.max_login_attempts
|
|
16
|
+
@lockout_duration_minutes = SystemSetting.lockout_duration_minutes
|
|
17
|
+
|
|
18
|
+
# Password policy settings
|
|
19
|
+
@password_min_length = SystemSetting.password_min_length
|
|
20
|
+
|
|
21
|
+
# Session timeout settings
|
|
22
|
+
@session_timeout_minutes = SystemSetting.session_timeout_minutes
|
|
23
|
+
|
|
24
|
+
# Rate limiting settings
|
|
25
|
+
@password_reset_rate_limit = SystemSetting.password_reset_rate_limit
|
|
26
|
+
@password_reset_rate_period_minutes = SystemSetting.password_reset_rate_period_minutes
|
|
27
|
+
@api_rate_limit = SystemSetting.api_rate_limit
|
|
28
|
+
@api_rate_period_minutes = SystemSetting.api_rate_period_minutes
|
|
29
|
+
|
|
30
|
+
# Storage is "disabled" list. View expects "enabled" list.
|
|
31
|
+
all_provider_keys = Rails.application.config.auth_providers.map { |p| p[:key].to_s }
|
|
32
|
+
disabled_providers = SystemSetting.find_by(key: "auth_providers_disabled")&.value&.split(",") || []
|
|
33
|
+
@enabled_auth_providers = all_provider_keys - disabled_providers
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def uiux
|
|
37
|
+
@default_light_theme_id = SystemSetting.default_light_theme_id
|
|
38
|
+
@default_dark_theme_id = SystemSetting.default_dark_theme_id
|
|
39
|
+
@available_themes = Collavre::UserTheme.all.order(:name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def update_uiux
|
|
43
|
+
SystemSetting.transaction do
|
|
44
|
+
light_theme_id = params[:default_light_theme_id].to_s.strip
|
|
45
|
+
light_theme_setting = SystemSetting.find_or_initialize_by(key: "default_light_theme_id")
|
|
46
|
+
light_theme_setting.value = light_theme_id.present? ? light_theme_id : nil
|
|
47
|
+
light_theme_setting.save!
|
|
48
|
+
|
|
49
|
+
dark_theme_id = params[:default_dark_theme_id].to_s.strip
|
|
50
|
+
dark_theme_setting = SystemSetting.find_or_initialize_by(key: "default_dark_theme_id")
|
|
51
|
+
dark_theme_setting.value = dark_theme_id.present? ? dark_theme_id : nil
|
|
52
|
+
dark_theme_setting.save!
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
redirect_to collavre.admin_uiux_path, notice: t("admin.settings.updated")
|
|
56
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
57
|
+
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
|
58
|
+
@default_light_theme_id = params[:default_light_theme_id]
|
|
59
|
+
@default_dark_theme_id = params[:default_dark_theme_id]
|
|
60
|
+
@available_themes = Collavre::UserTheme.all.order(:name)
|
|
61
|
+
render :uiux, status: :unprocessable_entity
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def update
|
|
65
|
+
SystemSetting.transaction do
|
|
66
|
+
# Help Link
|
|
67
|
+
help_link_setting = SystemSetting.find_or_initialize_by(key: "help_menu_link")
|
|
68
|
+
help_link_setting.value = params[:help_link].to_s.strip
|
|
69
|
+
help_link_setting.save!
|
|
70
|
+
|
|
71
|
+
# MCP Tool Approval
|
|
72
|
+
mcp_setting = SystemSetting.find_or_initialize_by(key: "mcp_tool_approval_required")
|
|
73
|
+
mcp_setting.value = params[:mcp_tool_approval] == "1" ? "true" : "false"
|
|
74
|
+
mcp_setting.save!
|
|
75
|
+
|
|
76
|
+
# Creatives Login Required
|
|
77
|
+
creatives_login_setting = SystemSetting.find_or_initialize_by(key: "creatives_login_required")
|
|
78
|
+
creatives_login_setting.value = params[:creatives_login_required] == "1" ? "true" : "false"
|
|
79
|
+
creatives_login_setting.save!
|
|
80
|
+
|
|
81
|
+
# Home Page Path
|
|
82
|
+
home_page_path_input = params[:home_page_path].to_s.strip
|
|
83
|
+
if home_page_path_input.present?
|
|
84
|
+
normalized_path, error = validate_and_normalize_home_page_path(home_page_path_input)
|
|
85
|
+
if error
|
|
86
|
+
home_page_setting = SystemSetting.new(key: "home_page_path")
|
|
87
|
+
home_page_setting.errors.add(:base, error)
|
|
88
|
+
raise ActiveRecord::RecordInvalid, home_page_setting
|
|
89
|
+
end
|
|
90
|
+
home_page_setting = SystemSetting.find_or_initialize_by(key: "home_page_path")
|
|
91
|
+
home_page_setting.value = normalized_path
|
|
92
|
+
home_page_setting.save!
|
|
93
|
+
else
|
|
94
|
+
home_page_setting = SystemSetting.find_or_initialize_by(key: "home_page_path")
|
|
95
|
+
home_page_setting.value = nil
|
|
96
|
+
home_page_setting.save!
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Account Lockout Settings
|
|
100
|
+
max_attempts = params[:max_login_attempts].to_i
|
|
101
|
+
max_attempts = SystemSetting::DEFAULT_MAX_LOGIN_ATTEMPTS if max_attempts < 1
|
|
102
|
+
SystemSetting.find_or_initialize_by(key: "max_login_attempts").tap { |s| s.value = max_attempts.to_s; s.save! }
|
|
103
|
+
|
|
104
|
+
lockout_duration = params[:lockout_duration_minutes].to_i
|
|
105
|
+
lockout_duration = SystemSetting::DEFAULT_LOCKOUT_DURATION_MINUTES if lockout_duration < 1
|
|
106
|
+
SystemSetting.find_or_initialize_by(key: "lockout_duration_minutes").tap { |s| s.value = lockout_duration.to_s; s.save! }
|
|
107
|
+
|
|
108
|
+
# Password Policy Settings
|
|
109
|
+
password_min_length = [ [ params[:password_min_length].to_i, SystemSetting::DEFAULT_PASSWORD_MIN_LENGTH ].max, 72 ].min
|
|
110
|
+
SystemSetting.find_or_initialize_by(key: "password_min_length").tap { |s| s.value = password_min_length.to_s; s.save! }
|
|
111
|
+
|
|
112
|
+
# Session Timeout Settings
|
|
113
|
+
session_timeout = [ params[:session_timeout_minutes].to_i, 0 ].max
|
|
114
|
+
SystemSetting.find_or_initialize_by(key: "session_timeout_minutes").tap { |s| s.value = session_timeout.to_s; s.save! }
|
|
115
|
+
|
|
116
|
+
# Rate Limiting - Password Reset
|
|
117
|
+
pw_reset_limit = params[:password_reset_rate_limit].to_i
|
|
118
|
+
pw_reset_limit = SystemSetting::DEFAULT_PASSWORD_RESET_RATE_LIMIT if pw_reset_limit < 1
|
|
119
|
+
SystemSetting.find_or_initialize_by(key: "password_reset_rate_limit").tap { |s| s.value = pw_reset_limit.to_s; s.save! }
|
|
120
|
+
|
|
121
|
+
pw_reset_period = params[:password_reset_rate_period_minutes].to_i
|
|
122
|
+
pw_reset_period = SystemSetting::DEFAULT_PASSWORD_RESET_RATE_PERIOD_MINUTES if pw_reset_period < 1
|
|
123
|
+
SystemSetting.find_or_initialize_by(key: "password_reset_rate_period_minutes").tap { |s| s.value = pw_reset_period.to_s; s.save! }
|
|
124
|
+
|
|
125
|
+
# Rate Limiting - API
|
|
126
|
+
api_limit = params[:api_rate_limit].to_i
|
|
127
|
+
api_limit = SystemSetting::DEFAULT_API_RATE_LIMIT if api_limit < 1
|
|
128
|
+
SystemSetting.find_or_initialize_by(key: "api_rate_limit").tap { |s| s.value = api_limit.to_s; s.save! }
|
|
129
|
+
|
|
130
|
+
api_period = params[:api_rate_period_minutes].to_i
|
|
131
|
+
api_period = SystemSetting::DEFAULT_API_RATE_PERIOD_MINUTES if api_period < 1
|
|
132
|
+
SystemSetting.find_or_initialize_by(key: "api_rate_period_minutes").tap { |s| s.value = api_period.to_s; s.save! }
|
|
133
|
+
|
|
134
|
+
# Auth Providers
|
|
135
|
+
auth_providers = Array(params[:auth_providers]).reject(&:blank?)
|
|
136
|
+
if auth_providers.empty?
|
|
137
|
+
auth_setting = SystemSetting.new(key: "auth_providers_enabled")
|
|
138
|
+
auth_setting.errors.add(:base, t("admin.settings.auth_provider_required"))
|
|
139
|
+
raise ActiveRecord::RecordInvalid, auth_setting
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
all_provider_keys = Rails.application.config.auth_providers.map { |p| p[:key].to_s }
|
|
143
|
+
disabled_providers = all_provider_keys - auth_providers
|
|
144
|
+
SystemSetting.find_or_initialize_by(key: "auth_providers_disabled").tap { |s| s.value = disabled_providers.join(","); s.save! }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
redirect_to collavre.admin_settings_path, notice: t("admin.settings.updated")
|
|
148
|
+
rescue ActiveRecord::RecordInvalid => e
|
|
149
|
+
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
|
150
|
+
@help_link = params[:help_link]
|
|
151
|
+
@mcp_tool_approval = params[:mcp_tool_approval] == "1"
|
|
152
|
+
@creatives_login_required = params[:creatives_login_required] == "1"
|
|
153
|
+
@home_page_path = params[:home_page_path]
|
|
154
|
+
@max_login_attempts = params[:max_login_attempts].to_i.positive? ? params[:max_login_attempts].to_i : SystemSetting::DEFAULT_MAX_LOGIN_ATTEMPTS
|
|
155
|
+
@lockout_duration_minutes = params[:lockout_duration_minutes].to_i.positive? ? params[:lockout_duration_minutes].to_i : SystemSetting::DEFAULT_LOCKOUT_DURATION_MINUTES
|
|
156
|
+
@password_min_length = [ [ params[:password_min_length].to_i, SystemSetting::DEFAULT_PASSWORD_MIN_LENGTH ].max, 72 ].min
|
|
157
|
+
@session_timeout_minutes = [ params[:session_timeout_minutes].to_i, 0 ].max
|
|
158
|
+
@password_reset_rate_limit = params[:password_reset_rate_limit].to_i.positive? ? params[:password_reset_rate_limit].to_i : SystemSetting::DEFAULT_PASSWORD_RESET_RATE_LIMIT
|
|
159
|
+
@password_reset_rate_period_minutes = params[:password_reset_rate_period_minutes].to_i.positive? ? params[:password_reset_rate_period_minutes].to_i : SystemSetting::DEFAULT_PASSWORD_RESET_RATE_PERIOD_MINUTES
|
|
160
|
+
@api_rate_limit = params[:api_rate_limit].to_i.positive? ? params[:api_rate_limit].to_i : SystemSetting::DEFAULT_API_RATE_LIMIT
|
|
161
|
+
@api_rate_period_minutes = params[:api_rate_period_minutes].to_i.positive? ? params[:api_rate_period_minutes].to_i : SystemSetting::DEFAULT_API_RATE_PERIOD_MINUTES
|
|
162
|
+
@enabled_auth_providers = params[:auth_providers] || []
|
|
163
|
+
render :index, status: :unprocessable_entity
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def validate_and_normalize_home_page_path(value)
|
|
169
|
+
path = value.to_s.strip
|
|
170
|
+
|
|
171
|
+
if path.match?(%r{\A[a-z][a-z0-9+.-]*://}i)
|
|
172
|
+
return [ nil, t("admin.settings.home_page_path_invalid_url") ]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
path = path.split(/[?#]/).first
|
|
176
|
+
path = "/#{path}" unless path.start_with?("/")
|
|
177
|
+
path = path.gsub(%r{/+}, "/")
|
|
178
|
+
return [ nil, nil ] if path == "/"
|
|
179
|
+
|
|
180
|
+
begin
|
|
181
|
+
route_info = Rails.application.routes.recognize_path(path, method: :get)
|
|
182
|
+
|
|
183
|
+
if route_info[:format].present? && route_info[:format] != "html"
|
|
184
|
+
return [ nil, t("admin.settings.home_page_path_not_html", path: path) ]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
non_html_paths = %w[/service-worker /manifest /up]
|
|
188
|
+
if non_html_paths.any? { |p| path.start_with?(p) }
|
|
189
|
+
return [ nil, t("admin.settings.home_page_path_not_html", path: path) ]
|
|
190
|
+
end
|
|
191
|
+
rescue ActionController::RoutingError
|
|
192
|
+
return [ nil, t("admin.settings.home_page_path_not_routable", path: path) ]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
[ path, nil ]
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -45,15 +45,7 @@ module Collavre
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def broadcast_reaction_update
|
|
48
|
-
|
|
49
|
-
Turbo::StreamsChannel.broadcast_action_to(
|
|
50
|
-
[ @creative, :comments ],
|
|
51
|
-
action: "update_reactions",
|
|
52
|
-
target: view_context.dom_id(@comment),
|
|
53
|
-
attributes: {
|
|
54
|
-
data: payload.to_json
|
|
55
|
-
}
|
|
56
|
-
)
|
|
48
|
+
CommentReaction.broadcast_reaction_update(@comment)
|
|
57
49
|
end
|
|
58
50
|
|
|
59
51
|
def set_creative
|