Fossil SCM

Add the administrative log capability.

drh 2014-12-01 17:22 trunk merge
Commit f3455a56eef0f73036eda651ddf0b036b833deb0
--- src/codecheck1.c
+++ src/codecheck1.c
@@ -309,10 +309,11 @@
309309
struct {
310310
const char *zFName; /* Name of the function */
311311
int iFmtArg; /* Index of format argument. Leftmost is 1. */
312312
unsigned fmtFlags; /* Processing flags */
313313
} aFmtFunc[] = {
314
+ { "admin_log", 1, 0 },
314315
{ "blob_append_sql", 2, FMT_NO_S },
315316
{ "blob_appendf", 2, 0 },
316317
{ "cgi_panic", 1, 0 },
317318
{ "cgi_redirectf", 1, 0 },
318319
{ "db_blob", 2, FMT_NO_S },
319320
--- src/codecheck1.c
+++ src/codecheck1.c
@@ -309,10 +309,11 @@
309 struct {
310 const char *zFName; /* Name of the function */
311 int iFmtArg; /* Index of format argument. Leftmost is 1. */
312 unsigned fmtFlags; /* Processing flags */
313 } aFmtFunc[] = {
 
314 { "blob_append_sql", 2, FMT_NO_S },
315 { "blob_appendf", 2, 0 },
316 { "cgi_panic", 1, 0 },
317 { "cgi_redirectf", 1, 0 },
318 { "db_blob", 2, FMT_NO_S },
319
--- src/codecheck1.c
+++ src/codecheck1.c
@@ -309,10 +309,11 @@
309 struct {
310 const char *zFName; /* Name of the function */
311 int iFmtArg; /* Index of format argument. Leftmost is 1. */
312 unsigned fmtFlags; /* Processing flags */
313 } aFmtFunc[] = {
314 { "admin_log", 1, 0 },
315 { "blob_append_sql", 2, FMT_NO_S },
316 { "blob_appendf", 2, 0 },
317 { "cgi_panic", 1, 0 },
318 { "cgi_redirectf", 1, 0 },
319 { "db_blob", 2, FMT_NO_S },
320
+48 -2
--- src/db.c
+++ src/db.c
@@ -2243,10 +2243,11 @@
22432243
const char *def; /* Default value */
22442244
};
22452245
#endif /* INTERFACE */
22462246
struct stControlSettings const ctrlSettings[] = {
22472247
{ "access-log", 0, 0, 0, 0, "off" },
2248
+ { "admin-log", 0, 0, 0, 0, "off" },
22482249
{ "allow-symlinks", 0, 0, 1, 0, "off" },
22492250
{ "auto-captcha", "autocaptcha", 0, 0, 0, "on" },
22502251
{ "auto-hyperlink", 0, 0, 0, 0, "on", },
22512252
{ "auto-shun", 0, 0, 0, 0, "on" },
22522253
{ "autosync", 0, 0, 0, 0, "on" },
@@ -2323,10 +2324,13 @@
23232324
** The "unset" command clears a property setting.
23242325
**
23252326
**
23262327
** access-log If enabled, record successful and failed login attempts
23272328
** in the "accesslog" table. Default: off
2329
+**
2330
+** admin-log If enabled, record configuration changes in the
2331
+** "admin_log" table. Default: off
23282332
**
23292333
** allow-symlinks If enabled, don't follow symlinks, and instead treat
23302334
** (versionable) them as symlinks on Unix. Has no effect on Windows
23312335
** (existing links in repository created on Unix become
23322336
** plain-text files with link destination path inside).
@@ -2699,21 +2703,63 @@
26992703
"%s WITHOUT ROWID;\n"
27002704
"INSERT INTO \"%w\" SELECT * FROM \"x_%w\";\n"
27012705
"DROP TABLE \"x_%w\";\n",
27022706
zTName, zTName, blob_sql_text(&newSql), zTName, zTName, zTName
27032707
);
2704
- fossil_print("Converting table %s of %s to WITHOUT ROWID.\n", zTName, g.argv[i]);
2708
+ fossil_print("Converting table %s of %s to WITHOUT ROWID.\n",
2709
+ zTName, g.argv[i]);
27052710
blob_reset(&newSql);
27062711
}
27072712
blob_append_sql(&allSql, "COMMIT;\n");
27082713
db_finalize(&q);
27092714
if( dryRun ){
27102715
fossil_print("SQL that would have been evaluated:\n");
2711
- fossil_print("-------------------------------------------------------------\n");
2716
+ fossil_print("%.78c\n", '-');
27122717
fossil_print("%s", blob_sql_text(&allSql));
27132718
}else{
27142719
db_multi_exec("%s", blob_sql_text(&allSql));
27152720
}
27162721
blob_reset(&allSql);
27172722
db_close(1);
27182723
}
27192724
}
2725
+
2726
+/*
2727
+** Make sure the adminlog table exists. Create it if it does not
2728
+*/
2729
+void create_admin_log_table(void){
2730
+ static int once = 0;
2731
+ if( once ) return;
2732
+ once = 1;
2733
+ db_multi_exec(
2734
+ "CREATE TABLE IF NOT EXISTS \"%w\".admin_log(\n"
2735
+ " id INTEGER PRIMARY KEY,\n"
2736
+ " time INTEGER, -- Seconds since 1970\n"
2737
+ " page TEXT, -- path of page\n"
2738
+ " who TEXT, -- User who made the change\n "
2739
+ " what TEXT -- What changed\n"
2740
+ ")", db_name("repository")
2741
+ );
2742
+}
2743
+
2744
+/*
2745
+** Write a message into the admin_event table, if admin logging is
2746
+** enabled via the admin-log configuration option.
2747
+*/
2748
+void admin_log(const char *zFormat, ...){
2749
+ Blob what = empty_blob;
2750
+ va_list ap;
2751
+ if( !db_get_boolean("admin-log", 0) ){
2752
+ /* Potential leak here (on %z params) but
2753
+ the alternative is to let blob_vappendf()
2754
+ do it below. */
2755
+ return;
2756
+ }
2757
+ create_admin_log_table();
2758
+ va_start(ap,zFormat);
2759
+ blob_vappendf( &what, zFormat, ap );
2760
+ va_end(ap);
2761
+ db_multi_exec("INSERT INTO admin_log(time,page,who,what)"
2762
+ " VALUES(now(), %Q, %Q, %B)",
2763
+ g.zPath, g.zLogin, &what);
2764
+ blob_reset(&what);
2765
+}
27202766
--- src/db.c
+++ src/db.c
@@ -2243,10 +2243,11 @@
2243 const char *def; /* Default value */
2244 };
2245 #endif /* INTERFACE */
2246 struct stControlSettings const ctrlSettings[] = {
2247 { "access-log", 0, 0, 0, 0, "off" },
 
2248 { "allow-symlinks", 0, 0, 1, 0, "off" },
2249 { "auto-captcha", "autocaptcha", 0, 0, 0, "on" },
2250 { "auto-hyperlink", 0, 0, 0, 0, "on", },
2251 { "auto-shun", 0, 0, 0, 0, "on" },
2252 { "autosync", 0, 0, 0, 0, "on" },
@@ -2323,10 +2324,13 @@
2323 ** The "unset" command clears a property setting.
2324 **
2325 **
2326 ** access-log If enabled, record successful and failed login attempts
2327 ** in the "accesslog" table. Default: off
 
 
 
2328 **
2329 ** allow-symlinks If enabled, don't follow symlinks, and instead treat
2330 ** (versionable) them as symlinks on Unix. Has no effect on Windows
2331 ** (existing links in repository created on Unix become
2332 ** plain-text files with link destination path inside).
@@ -2699,21 +2703,63 @@
2699 "%s WITHOUT ROWID;\n"
2700 "INSERT INTO \"%w\" SELECT * FROM \"x_%w\";\n"
2701 "DROP TABLE \"x_%w\";\n",
2702 zTName, zTName, blob_sql_text(&newSql), zTName, zTName, zTName
2703 );
2704 fossil_print("Converting table %s of %s to WITHOUT ROWID.\n", zTName, g.argv[i]);
 
2705 blob_reset(&newSql);
2706 }
2707 blob_append_sql(&allSql, "COMMIT;\n");
2708 db_finalize(&q);
2709 if( dryRun ){
2710 fossil_print("SQL that would have been evaluated:\n");
2711 fossil_print("-------------------------------------------------------------\n");
2712 fossil_print("%s", blob_sql_text(&allSql));
2713 }else{
2714 db_multi_exec("%s", blob_sql_text(&allSql));
2715 }
2716 blob_reset(&allSql);
2717 db_close(1);
2718 }
2719 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2720
--- src/db.c
+++ src/db.c
@@ -2243,10 +2243,11 @@
2243 const char *def; /* Default value */
2244 };
2245 #endif /* INTERFACE */
2246 struct stControlSettings const ctrlSettings[] = {
2247 { "access-log", 0, 0, 0, 0, "off" },
2248 { "admin-log", 0, 0, 0, 0, "off" },
2249 { "allow-symlinks", 0, 0, 1, 0, "off" },
2250 { "auto-captcha", "autocaptcha", 0, 0, 0, "on" },
2251 { "auto-hyperlink", 0, 0, 0, 0, "on", },
2252 { "auto-shun", 0, 0, 0, 0, "on" },
2253 { "autosync", 0, 0, 0, 0, "on" },
@@ -2323,10 +2324,13 @@
2324 ** The "unset" command clears a property setting.
2325 **
2326 **
2327 ** access-log If enabled, record successful and failed login attempts
2328 ** in the "accesslog" table. Default: off
2329 **
2330 ** admin-log If enabled, record configuration changes in the
2331 ** "admin_log" table. Default: off
2332 **
2333 ** allow-symlinks If enabled, don't follow symlinks, and instead treat
2334 ** (versionable) them as symlinks on Unix. Has no effect on Windows
2335 ** (existing links in repository created on Unix become
2336 ** plain-text files with link destination path inside).
@@ -2699,21 +2703,63 @@
2703 "%s WITHOUT ROWID;\n"
2704 "INSERT INTO \"%w\" SELECT * FROM \"x_%w\";\n"
2705 "DROP TABLE \"x_%w\";\n",
2706 zTName, zTName, blob_sql_text(&newSql), zTName, zTName, zTName
2707 );
2708 fossil_print("Converting table %s of %s to WITHOUT ROWID.\n",
2709 zTName, g.argv[i]);
2710 blob_reset(&newSql);
2711 }
2712 blob_append_sql(&allSql, "COMMIT;\n");
2713 db_finalize(&q);
2714 if( dryRun ){
2715 fossil_print("SQL that would have been evaluated:\n");
2716 fossil_print("%.78c\n", '-');
2717 fossil_print("%s", blob_sql_text(&allSql));
2718 }else{
2719 db_multi_exec("%s", blob_sql_text(&allSql));
2720 }
2721 blob_reset(&allSql);
2722 db_close(1);
2723 }
2724 }
2725
2726 /*
2727 ** Make sure the adminlog table exists. Create it if it does not
2728 */
2729 void create_admin_log_table(void){
2730 static int once = 0;
2731 if( once ) return;
2732 once = 1;
2733 db_multi_exec(
2734 "CREATE TABLE IF NOT EXISTS \"%w\".admin_log(\n"
2735 " id INTEGER PRIMARY KEY,\n"
2736 " time INTEGER, -- Seconds since 1970\n"
2737 " page TEXT, -- path of page\n"
2738 " who TEXT, -- User who made the change\n "
2739 " what TEXT -- What changed\n"
2740 ")", db_name("repository")
2741 );
2742 }
2743
2744 /*
2745 ** Write a message into the admin_event table, if admin logging is
2746 ** enabled via the admin-log configuration option.
2747 */
2748 void admin_log(const char *zFormat, ...){
2749 Blob what = empty_blob;
2750 va_list ap;
2751 if( !db_get_boolean("admin-log", 0) ){
2752 /* Potential leak here (on %z params) but
2753 the alternative is to let blob_vappendf()
2754 do it below. */
2755 return;
2756 }
2757 create_admin_log_table();
2758 va_start(ap,zFormat);
2759 blob_vappendf( &what, zFormat, ap );
2760 va_end(ap);
2761 db_multi_exec("INSERT INTO admin_log(time,page,who,what)"
2762 " VALUES(now(), %Q, %Q, %B)",
2763 g.zPath, g.zLogin, &what);
2764 blob_reset(&what);
2765 }
2766
--- src/moderate.c
+++ src/moderate.c
@@ -116,10 +116,11 @@
116116
attachRid = db_int(0, "SELECT attachRid FROM modreq WHERE objid=%d", rid);
117117
if( rid==objid ){
118118
db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
119119
}
120120
if( attachRid && object_used(attachRid) ) attachRid = 0;
121
+ admin_log("Disapproved moderation of rid %d.", rid);
121122
rid = attachRid;
122123
}
123124
db_end_transaction(0);
124125
}
125126
@@ -134,10 +135,11 @@
134135
"INSERT OR IGNORE INTO unclustered VALUES(%d);"
135136
"INSERT OR IGNORE INTO unsent VALUES(%d);",
136137
rid, rid, rid
137138
);
138139
db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
140
+ admin_log("Approved moderation of rid %d.", rid);
139141
db_end_transaction(0);
140142
}
141143
142144
/*
143145
** WEBPAGE: modreq
144146
--- src/moderate.c
+++ src/moderate.c
@@ -116,10 +116,11 @@
116 attachRid = db_int(0, "SELECT attachRid FROM modreq WHERE objid=%d", rid);
117 if( rid==objid ){
118 db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
119 }
120 if( attachRid && object_used(attachRid) ) attachRid = 0;
 
121 rid = attachRid;
122 }
123 db_end_transaction(0);
124 }
125
@@ -134,10 +135,11 @@
134 "INSERT OR IGNORE INTO unclustered VALUES(%d);"
135 "INSERT OR IGNORE INTO unsent VALUES(%d);",
136 rid, rid, rid
137 );
138 db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
 
139 db_end_transaction(0);
140 }
141
142 /*
143 ** WEBPAGE: modreq
144
--- src/moderate.c
+++ src/moderate.c
@@ -116,10 +116,11 @@
116 attachRid = db_int(0, "SELECT attachRid FROM modreq WHERE objid=%d", rid);
117 if( rid==objid ){
118 db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
119 }
120 if( attachRid && object_used(attachRid) ) attachRid = 0;
121 admin_log("Disapproved moderation of rid %d.", rid);
122 rid = attachRid;
123 }
124 db_end_transaction(0);
125 }
126
@@ -134,10 +135,11 @@
135 "INSERT OR IGNORE INTO unclustered VALUES(%d);"
136 "INSERT OR IGNORE INTO unsent VALUES(%d);",
137 rid, rid, rid
138 );
139 db_multi_exec("DELETE FROM modreq WHERE objid=%d", rid);
140 admin_log("Approved moderation of rid %d.", rid);
141 db_end_transaction(0);
142 }
143
144 /*
145 ** WEBPAGE: modreq
146
+1 -1
--- src/rebuild.c
+++ src/rebuild.c
@@ -346,11 +346,11 @@
346346
rebuild_update_schema();
347347
for(;;){
348348
zTable = db_text(0,
349349
"SELECT name FROM sqlite_master /*scan*/"
350350
" WHERE type='table'"
351
- " AND name NOT IN ('blob','delta','rcvfrom','user',"
351
+ " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user',"
352352
"'config','shun','private','reportfmt',"
353353
"'concealed','accesslog','modreq')"
354354
" AND name NOT GLOB 'sqlite_*'"
355355
" AND name NOT GLOB 'fx_*'"
356356
);
357357
--- src/rebuild.c
+++ src/rebuild.c
@@ -346,11 +346,11 @@
346 rebuild_update_schema();
347 for(;;){
348 zTable = db_text(0,
349 "SELECT name FROM sqlite_master /*scan*/"
350 " WHERE type='table'"
351 " AND name NOT IN ('blob','delta','rcvfrom','user',"
352 "'config','shun','private','reportfmt',"
353 "'concealed','accesslog','modreq')"
354 " AND name NOT GLOB 'sqlite_*'"
355 " AND name NOT GLOB 'fx_*'"
356 );
357
--- src/rebuild.c
+++ src/rebuild.c
@@ -346,11 +346,11 @@
346 rebuild_update_schema();
347 for(;;){
348 zTable = db_text(0,
349 "SELECT name FROM sqlite_master /*scan*/"
350 " WHERE type='table'"
351 " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user',"
352 "'config','shun','private','reportfmt',"
353 "'concealed','accesslog','modreq')"
354 " AND name NOT GLOB 'sqlite_*'"
355 " AND name NOT GLOB 'fx_*'"
356 );
357
+98 -1
--- src/setup.c
+++ src/setup.c
@@ -109,10 +109,12 @@
109109
"Show artifacts that are shunned by this repository");
110110
setup_menu_entry("Log", "rcvfromlist",
111111
"A record of received artifacts and their sources");
112112
setup_menu_entry("User-Log", "access_log",
113113
"A record of login attempts");
114
+ setup_menu_entry("Admin-Log", "admin_log",
115
+ "View the admin_log entries");
114116
setup_menu_entry("Stats", "stat",
115117
"Display repository statistics");
116118
setup_menu_entry("SQL", "admin_sql",
117119
"Enter raw SQL commands");
118120
setup_menu_entry("TH1", "admin_th1",
@@ -380,12 +382,14 @@
380382
}
381383
login_verify_csrf_secret();
382384
db_multi_exec(
383385
"REPLACE INTO user(uid,login,info,pw,cap,mtime) "
384386
"VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
385
- uid, P("login"), P("info"), zPw, zCap
387
+ uid, zLogin, P("info"), zPw, zCap
386388
);
389
+ admin_log( "Updated user [%q] with capabilities [%q].",
390
+ zLogin, zCap );
387391
if( atoi(PD("all","0"))>0 ){
388392
Blob sql;
389393
char *zErr = 0;
390394
blob_zero(&sql);
391395
if( zOldLogin==0 ){
@@ -407,12 +411,16 @@
407411
zLogin, P("pw"), zLogin, P("info"), zCap,
408412
zOldLogin
409413
);
410414
login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
411415
blob_reset(&sql);
416
+ admin_log( "Updated user [%q] in all login groups "
417
+ "with capabilities [%q].",
418
+ zLogin, zCap );
412419
if( zErr ){
413420
style_header("User Change Error");
421
+ admin_log( "Error updating user '%q': %s'.", zLogin, zErr );
414422
@ <span class="loginError">%s(zErr)</span>
415423
@
416424
@ <p><a href="setup_uedit?id=%d(uid)">[Bummer]</a></p>
417425
style_footer();
418426
return;
@@ -862,10 +870,12 @@
862870
if( zQ ){
863871
int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
864872
if( iQ!=iVal ){
865873
login_verify_csrf_secret();
866874
db_set(zVar, iQ ? "1" : "0", 0);
875
+ admin_log("Set option [%q] to [%q].",
876
+ zVar, iQ ? "on" : "off");
867877
iVal = iQ;
868878
}
869879
}
870880
@ <input type="checkbox" name="%s(zQParm)"
871881
if( iVal ){
@@ -889,12 +899,15 @@
889899
int disabled /* 1 if disabled */
890900
){
891901
const char *zVal = db_get(zVar, zDflt);
892902
const char *zQ = P(zQParm);
893903
if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
904
+ const int nZQ = (int)strlen(zQ);
894905
login_verify_csrf_secret();
895906
db_set(zVar, zQ, 0);
907
+ admin_log("Set entry_attribute %Q to: %.*s%s",
908
+ zVar, 20, zQ, (nZQ>20 ? "..." : ""));
896909
zVal = zQ;
897910
}
898911
@ <input type="text" id="%s(zQParm)" name="%s(zQParm)" value="%h(zVal)" size="%d(width)"
899912
if( disabled ){
900913
@ disabled="disabled"
@@ -915,12 +928,15 @@
915928
int disabled /* 1 if the textarea should not be editable */
916929
){
917930
const char *z = db_get(zVar, (char*)zDflt);
918931
const char *zQ = P(zQP);
919932
if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
933
+ const int nZQ = (int)strlen(zQ);
920934
login_verify_csrf_secret();
921935
db_set(zVar, zQ, 0);
936
+ admin_log("Set textarea_attribute %Q to: %.*s%s",
937
+ zVar, 20, zQ, (nZQ>20 ? "..." : ""));
922938
z = zQ;
923939
}
924940
if( rows>0 && cols>0 ){
925941
@ <textarea id="id%s(zQP)" name="%s(zQP)" rows="%d(rows)"
926942
if( disabled ){
@@ -946,12 +962,15 @@
946962
){
947963
const char *z = db_get(zVar, (char*)zDflt);
948964
const char *zQ = P(zQP);
949965
int i;
950966
if( zQ && fossil_strcmp(zQ,z)!=0){
967
+ const int nZQ = (int)strlen(zQ);
951968
login_verify_csrf_secret();
952969
db_set(zVar, zQ, 0);
970
+ admin_log("Set multiple_choice_attribute %Q to: %.*s%s",
971
+ zVar, 20, zQ, (nZQ>20 ? "..." : ""));
953972
z = zQ;
954973
}
955974
@ <select size="1" name="%s(zQP)" id="id%s(zQP)">
956975
for(i=0; i<nChoice*2; i+=2){
957976
const char *zSel = fossil_strcmp(azChoice[i],z)==0 ? " selected" : "";
@@ -2023,5 +2042,83 @@
20232042
@ <pre class="th1error">%h(zR)</pre>
20242043
}
20252044
}
20262045
style_footer();
20272046
}
2047
+
2048
+static void admin_log_render_limits(){
2049
+ int const count = db_int(0,"SELECT COUNT(*) FROM admin_log");
2050
+ int i;
2051
+ int limits[] = {
2052
+ 10, 20, 50, 100, 250, 500, 0
2053
+ };
2054
+ for(i = 0; limits[i]; ++i ){
2055
+ cgi_printf("%s<a href='?n=%d'>%d</a>",
2056
+ i ? " " : "",
2057
+ limits[i], limits[i]);
2058
+ if(limits[i]>count) break;
2059
+ }
2060
+}
2061
+
2062
+/*
2063
+** WEBPAGE: admin_log
2064
+**
2065
+** Shows the contents of the admin_log table, which is only created if
2066
+** the admin-log setting is enabled. Requires Admin or Setup ('a' or
2067
+** 's') permissions.
2068
+*/
2069
+void page_admin_log(){
2070
+ Stmt stLog = empty_Stmt;
2071
+ Blob qLog = empty_blob;
2072
+ int limit;
2073
+ int fLogEnabled;
2074
+ int counter = 0;
2075
+ login_check_credentials();
2076
+ if( !g.perm.Setup && !g.perm.Admin ){
2077
+ login_needed();
2078
+ }
2079
+ style_header("Admin Log");
2080
+ create_admin_log_table();
2081
+ limit = atoi(PD("n","20"));
2082
+ fLogEnabled = db_get_boolean("admin-log", 0);
2083
+ @ <div>Admin logging is %s(fLogEnabled?"on":"off").</div>
2084
+
2085
+
2086
+ @ <div>Limit results to: <span>
2087
+ admin_log_render_limits();
2088
+ @ </span></div>
2089
+
2090
+ blob_append_sql(&qLog,
2091
+ "SELECT datetime(time,'unixepoch'), who, page, what "
2092
+ "FROM admin_log "
2093
+ "ORDER BY time DESC ");
2094
+ if(limit>0){
2095
+ @ %d(limit) Most recent entries:
2096
+ blob_append_sql(&qLog, "LIMIT %d", limit);
2097
+ }
2098
+ db_prepare(&stLog, "%s", blob_sql_text(&qLog));
2099
+ blob_reset(&qLog);
2100
+ @ <table id="adminLogTable" class="adminLogTable" width="100%%">
2101
+ @ <thead>
2102
+ @ <th>Time</th>
2103
+ @ <th>User</th>
2104
+ @ <th>Page</th>
2105
+ @ <th width="60%%">Message</th>
2106
+ @ </thead><tbody>
2107
+ while( SQLITE_ROW == db_step(&stLog) ){
2108
+ char const * zTime = db_column_text(&stLog, 0);
2109
+ char const * zUser = db_column_text(&stLog, 1);
2110
+ char const * zPage = db_column_text(&stLog, 2);
2111
+ char const * zMessage = db_column_text(&stLog, 3);
2112
+ @ <tr class="row%d(counter++%2)">
2113
+ @ <td class="adminTime">%s(zTime)</td>
2114
+ @ <td>%s(zUser)</td>
2115
+ @ <td>%s(zPage)</td>
2116
+ @ <td>%h(zMessage)</td>
2117
+ @ </tr>
2118
+ }
2119
+ @ </tbody></table>
2120
+ if(limit>0 && counter<limit){
2121
+ @ <div>%d(counter) entries shown.</div>
2122
+ }
2123
+ style_footer();
2124
+}
20282125
--- src/setup.c
+++ src/setup.c
@@ -109,10 +109,12 @@
109 "Show artifacts that are shunned by this repository");
110 setup_menu_entry("Log", "rcvfromlist",
111 "A record of received artifacts and their sources");
112 setup_menu_entry("User-Log", "access_log",
113 "A record of login attempts");
 
 
114 setup_menu_entry("Stats", "stat",
115 "Display repository statistics");
116 setup_menu_entry("SQL", "admin_sql",
117 "Enter raw SQL commands");
118 setup_menu_entry("TH1", "admin_th1",
@@ -380,12 +382,14 @@
380 }
381 login_verify_csrf_secret();
382 db_multi_exec(
383 "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
384 "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
385 uid, P("login"), P("info"), zPw, zCap
386 );
 
 
387 if( atoi(PD("all","0"))>0 ){
388 Blob sql;
389 char *zErr = 0;
390 blob_zero(&sql);
391 if( zOldLogin==0 ){
@@ -407,12 +411,16 @@
407 zLogin, P("pw"), zLogin, P("info"), zCap,
408 zOldLogin
409 );
410 login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
411 blob_reset(&sql);
 
 
 
412 if( zErr ){
413 style_header("User Change Error");
 
414 @ <span class="loginError">%s(zErr)</span>
415 @
416 @ <p><a href="setup_uedit?id=%d(uid)">[Bummer]</a></p>
417 style_footer();
418 return;
@@ -862,10 +870,12 @@
862 if( zQ ){
863 int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
864 if( iQ!=iVal ){
865 login_verify_csrf_secret();
866 db_set(zVar, iQ ? "1" : "0", 0);
 
 
867 iVal = iQ;
868 }
869 }
870 @ <input type="checkbox" name="%s(zQParm)"
871 if( iVal ){
@@ -889,12 +899,15 @@
889 int disabled /* 1 if disabled */
890 ){
891 const char *zVal = db_get(zVar, zDflt);
892 const char *zQ = P(zQParm);
893 if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
 
894 login_verify_csrf_secret();
895 db_set(zVar, zQ, 0);
 
 
896 zVal = zQ;
897 }
898 @ <input type="text" id="%s(zQParm)" name="%s(zQParm)" value="%h(zVal)" size="%d(width)"
899 if( disabled ){
900 @ disabled="disabled"
@@ -915,12 +928,15 @@
915 int disabled /* 1 if the textarea should not be editable */
916 ){
917 const char *z = db_get(zVar, (char*)zDflt);
918 const char *zQ = P(zQP);
919 if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
 
920 login_verify_csrf_secret();
921 db_set(zVar, zQ, 0);
 
 
922 z = zQ;
923 }
924 if( rows>0 && cols>0 ){
925 @ <textarea id="id%s(zQP)" name="%s(zQP)" rows="%d(rows)"
926 if( disabled ){
@@ -946,12 +962,15 @@
946 ){
947 const char *z = db_get(zVar, (char*)zDflt);
948 const char *zQ = P(zQP);
949 int i;
950 if( zQ && fossil_strcmp(zQ,z)!=0){
 
951 login_verify_csrf_secret();
952 db_set(zVar, zQ, 0);
 
 
953 z = zQ;
954 }
955 @ <select size="1" name="%s(zQP)" id="id%s(zQP)">
956 for(i=0; i<nChoice*2; i+=2){
957 const char *zSel = fossil_strcmp(azChoice[i],z)==0 ? " selected" : "";
@@ -2023,5 +2042,83 @@
2023 @ <pre class="th1error">%h(zR)</pre>
2024 }
2025 }
2026 style_footer();
2027 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2028
--- src/setup.c
+++ src/setup.c
@@ -109,10 +109,12 @@
109 "Show artifacts that are shunned by this repository");
110 setup_menu_entry("Log", "rcvfromlist",
111 "A record of received artifacts and their sources");
112 setup_menu_entry("User-Log", "access_log",
113 "A record of login attempts");
114 setup_menu_entry("Admin-Log", "admin_log",
115 "View the admin_log entries");
116 setup_menu_entry("Stats", "stat",
117 "Display repository statistics");
118 setup_menu_entry("SQL", "admin_sql",
119 "Enter raw SQL commands");
120 setup_menu_entry("TH1", "admin_th1",
@@ -380,12 +382,14 @@
382 }
383 login_verify_csrf_secret();
384 db_multi_exec(
385 "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
386 "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
387 uid, zLogin, P("info"), zPw, zCap
388 );
389 admin_log( "Updated user [%q] with capabilities [%q].",
390 zLogin, zCap );
391 if( atoi(PD("all","0"))>0 ){
392 Blob sql;
393 char *zErr = 0;
394 blob_zero(&sql);
395 if( zOldLogin==0 ){
@@ -407,12 +411,16 @@
411 zLogin, P("pw"), zLogin, P("info"), zCap,
412 zOldLogin
413 );
414 login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
415 blob_reset(&sql);
416 admin_log( "Updated user [%q] in all login groups "
417 "with capabilities [%q].",
418 zLogin, zCap );
419 if( zErr ){
420 style_header("User Change Error");
421 admin_log( "Error updating user '%q': %s'.", zLogin, zErr );
422 @ <span class="loginError">%s(zErr)</span>
423 @
424 @ <p><a href="setup_uedit?id=%d(uid)">[Bummer]</a></p>
425 style_footer();
426 return;
@@ -862,10 +870,12 @@
870 if( zQ ){
871 int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
872 if( iQ!=iVal ){
873 login_verify_csrf_secret();
874 db_set(zVar, iQ ? "1" : "0", 0);
875 admin_log("Set option [%q] to [%q].",
876 zVar, iQ ? "on" : "off");
877 iVal = iQ;
878 }
879 }
880 @ <input type="checkbox" name="%s(zQParm)"
881 if( iVal ){
@@ -889,12 +899,15 @@
899 int disabled /* 1 if disabled */
900 ){
901 const char *zVal = db_get(zVar, zDflt);
902 const char *zQ = P(zQParm);
903 if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
904 const int nZQ = (int)strlen(zQ);
905 login_verify_csrf_secret();
906 db_set(zVar, zQ, 0);
907 admin_log("Set entry_attribute %Q to: %.*s%s",
908 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
909 zVal = zQ;
910 }
911 @ <input type="text" id="%s(zQParm)" name="%s(zQParm)" value="%h(zVal)" size="%d(width)"
912 if( disabled ){
913 @ disabled="disabled"
@@ -915,12 +928,15 @@
928 int disabled /* 1 if the textarea should not be editable */
929 ){
930 const char *z = db_get(zVar, (char*)zDflt);
931 const char *zQ = P(zQP);
932 if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
933 const int nZQ = (int)strlen(zQ);
934 login_verify_csrf_secret();
935 db_set(zVar, zQ, 0);
936 admin_log("Set textarea_attribute %Q to: %.*s%s",
937 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
938 z = zQ;
939 }
940 if( rows>0 && cols>0 ){
941 @ <textarea id="id%s(zQP)" name="%s(zQP)" rows="%d(rows)"
942 if( disabled ){
@@ -946,12 +962,15 @@
962 ){
963 const char *z = db_get(zVar, (char*)zDflt);
964 const char *zQ = P(zQP);
965 int i;
966 if( zQ && fossil_strcmp(zQ,z)!=0){
967 const int nZQ = (int)strlen(zQ);
968 login_verify_csrf_secret();
969 db_set(zVar, zQ, 0);
970 admin_log("Set multiple_choice_attribute %Q to: %.*s%s",
971 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
972 z = zQ;
973 }
974 @ <select size="1" name="%s(zQP)" id="id%s(zQP)">
975 for(i=0; i<nChoice*2; i+=2){
976 const char *zSel = fossil_strcmp(azChoice[i],z)==0 ? " selected" : "";
@@ -2023,5 +2042,83 @@
2042 @ <pre class="th1error">%h(zR)</pre>
2043 }
2044 }
2045 style_footer();
2046 }
2047
2048 static void admin_log_render_limits(){
2049 int const count = db_int(0,"SELECT COUNT(*) FROM admin_log");
2050 int i;
2051 int limits[] = {
2052 10, 20, 50, 100, 250, 500, 0
2053 };
2054 for(i = 0; limits[i]; ++i ){
2055 cgi_printf("%s<a href='?n=%d'>%d</a>",
2056 i ? " " : "",
2057 limits[i], limits[i]);
2058 if(limits[i]>count) break;
2059 }
2060 }
2061
2062 /*
2063 ** WEBPAGE: admin_log
2064 **
2065 ** Shows the contents of the admin_log table, which is only created if
2066 ** the admin-log setting is enabled. Requires Admin or Setup ('a' or
2067 ** 's') permissions.
2068 */
2069 void page_admin_log(){
2070 Stmt stLog = empty_Stmt;
2071 Blob qLog = empty_blob;
2072 int limit;
2073 int fLogEnabled;
2074 int counter = 0;
2075 login_check_credentials();
2076 if( !g.perm.Setup && !g.perm.Admin ){
2077 login_needed();
2078 }
2079 style_header("Admin Log");
2080 create_admin_log_table();
2081 limit = atoi(PD("n","20"));
2082 fLogEnabled = db_get_boolean("admin-log", 0);
2083 @ <div>Admin logging is %s(fLogEnabled?"on":"off").</div>
2084
2085
2086 @ <div>Limit results to: <span>
2087 admin_log_render_limits();
2088 @ </span></div>
2089
2090 blob_append_sql(&qLog,
2091 "SELECT datetime(time,'unixepoch'), who, page, what "
2092 "FROM admin_log "
2093 "ORDER BY time DESC ");
2094 if(limit>0){
2095 @ %d(limit) Most recent entries:
2096 blob_append_sql(&qLog, "LIMIT %d", limit);
2097 }
2098 db_prepare(&stLog, "%s", blob_sql_text(&qLog));
2099 blob_reset(&qLog);
2100 @ <table id="adminLogTable" class="adminLogTable" width="100%%">
2101 @ <thead>
2102 @ <th>Time</th>
2103 @ <th>User</th>
2104 @ <th>Page</th>
2105 @ <th width="60%%">Message</th>
2106 @ </thead><tbody>
2107 while( SQLITE_ROW == db_step(&stLog) ){
2108 char const * zTime = db_column_text(&stLog, 0);
2109 char const * zUser = db_column_text(&stLog, 1);
2110 char const * zPage = db_column_text(&stLog, 2);
2111 char const * zMessage = db_column_text(&stLog, 3);
2112 @ <tr class="row%d(counter++%2)">
2113 @ <td class="adminTime">%s(zTime)</td>
2114 @ <td>%s(zUser)</td>
2115 @ <td>%s(zPage)</td>
2116 @ <td>%h(zMessage)</td>
2117 @ </tr>
2118 }
2119 @ </tbody></table>
2120 if(limit>0 && counter<limit){
2121 @ <div>%d(counter) entries shown.</div>
2122 }
2123 style_footer();
2124 }
2125
+3
--- src/shun.c
+++ src/shun.c
@@ -56,10 +56,11 @@
5656
if( P("rebuild") ){
5757
db_close(1);
5858
db_open_repository(g.zRepositoryName);
5959
db_begin_transaction();
6060
rebuild_db(0, 0, 0);
61
+ admin_log("Rebuilt database.");
6162
db_end_transaction(0);
6263
}
6364
if( zUuid ){
6465
char *p;
6566
int i = 0;
@@ -101,10 +102,11 @@
101102
while( *p ){
102103
db_multi_exec("DELETE FROM shun WHERE uuid=%Q", p);
103104
if( !db_exists("SELECT 1 FROM blob WHERE uuid=%Q", p) ){
104105
allExist = 0;
105106
}
107
+ admin_log("Unshunned %Q", p);
106108
p += UUID_SIZE+1;
107109
}
108110
if( allExist ){
109111
@ <p class="noMoreShun">Artifact(s)<br />
110112
for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
@@ -139,10 +141,11 @@
139141
if( tagid ){
140142
db_multi_exec("DELETE FROM ticket WHERE tkt_uuid=%Q", p);
141143
db_multi_exec("DELETE FROM tag WHERE tagid=%d", tagid);
142144
db_multi_exec("DELETE FROM tagxref WHERE tagid=%d", tagid);
143145
}
146
+ admin_log("Shunned %Q", p);
144147
p += UUID_SIZE+1;
145148
}
146149
@ <p class="shunned">Artifact(s)<br />
147150
for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
148151
@ <a href="%s(g.zTop)/artifact/%s(p)">%s(p)</a><br />
149152
--- src/shun.c
+++ src/shun.c
@@ -56,10 +56,11 @@
56 if( P("rebuild") ){
57 db_close(1);
58 db_open_repository(g.zRepositoryName);
59 db_begin_transaction();
60 rebuild_db(0, 0, 0);
 
61 db_end_transaction(0);
62 }
63 if( zUuid ){
64 char *p;
65 int i = 0;
@@ -101,10 +102,11 @@
101 while( *p ){
102 db_multi_exec("DELETE FROM shun WHERE uuid=%Q", p);
103 if( !db_exists("SELECT 1 FROM blob WHERE uuid=%Q", p) ){
104 allExist = 0;
105 }
 
106 p += UUID_SIZE+1;
107 }
108 if( allExist ){
109 @ <p class="noMoreShun">Artifact(s)<br />
110 for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
@@ -139,10 +141,11 @@
139 if( tagid ){
140 db_multi_exec("DELETE FROM ticket WHERE tkt_uuid=%Q", p);
141 db_multi_exec("DELETE FROM tag WHERE tagid=%d", tagid);
142 db_multi_exec("DELETE FROM tagxref WHERE tagid=%d", tagid);
143 }
 
144 p += UUID_SIZE+1;
145 }
146 @ <p class="shunned">Artifact(s)<br />
147 for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
148 @ <a href="%s(g.zTop)/artifact/%s(p)">%s(p)</a><br />
149
--- src/shun.c
+++ src/shun.c
@@ -56,10 +56,11 @@
56 if( P("rebuild") ){
57 db_close(1);
58 db_open_repository(g.zRepositoryName);
59 db_begin_transaction();
60 rebuild_db(0, 0, 0);
61 admin_log("Rebuilt database.");
62 db_end_transaction(0);
63 }
64 if( zUuid ){
65 char *p;
66 int i = 0;
@@ -101,10 +102,11 @@
102 while( *p ){
103 db_multi_exec("DELETE FROM shun WHERE uuid=%Q", p);
104 if( !db_exists("SELECT 1 FROM blob WHERE uuid=%Q", p) ){
105 allExist = 0;
106 }
107 admin_log("Unshunned %Q", p);
108 p += UUID_SIZE+1;
109 }
110 if( allExist ){
111 @ <p class="noMoreShun">Artifact(s)<br />
112 for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
@@ -139,10 +141,11 @@
141 if( tagid ){
142 db_multi_exec("DELETE FROM ticket WHERE tkt_uuid=%Q", p);
143 db_multi_exec("DELETE FROM tag WHERE tagid=%d", tagid);
144 db_multi_exec("DELETE FROM tagxref WHERE tagid=%d", tagid);
145 }
146 admin_log("Shunned %Q", p);
147 p += UUID_SIZE+1;
148 }
149 @ <p class="shunned">Artifact(s)<br />
150 for( p = zUuid ; *p ; p += UUID_SIZE+1 ){
151 @ <a href="%s(g.zTop)/artifact/%s(p)">%s(p)</a><br />
152
+10
--- src/style.c
+++ src/style.c
@@ -1208,10 +1208,20 @@
12081208
@ font-weight: bold;
12091209
},
12101210
{ "#canvas", "timeline graph node colors",
12111211
@ color: black;
12121212
@ background-color: white;
1213
+ },
1214
+ { "table.adminLogTable",
1215
+ "Class for the /admin_log table",
1216
+ @ text-align: left
1217
+ },
1218
+ { ".adminLogTable .adminTime",
1219
+ "Class for the /admin_log table",
1220
+ @ text-align: left
1221
+ @ vertical-align: top;
1222
+ @ white-space: nowrap;
12131223
},
12141224
{ 0,
12151225
0,
12161226
0
12171227
}
12181228
--- src/style.c
+++ src/style.c
@@ -1208,10 +1208,20 @@
1208 @ font-weight: bold;
1209 },
1210 { "#canvas", "timeline graph node colors",
1211 @ color: black;
1212 @ background-color: white;
 
 
 
 
 
 
 
 
 
 
1213 },
1214 { 0,
1215 0,
1216 0
1217 }
1218
--- src/style.c
+++ src/style.c
@@ -1208,10 +1208,20 @@
1208 @ font-weight: bold;
1209 },
1210 { "#canvas", "timeline graph node colors",
1211 @ color: black;
1212 @ background-color: white;
1213 },
1214 { "table.adminLogTable",
1215 "Class for the /admin_log table",
1216 @ text-align: left
1217 },
1218 { ".adminLogTable .adminTime",
1219 "Class for the /admin_log table",
1220 @ text-align: left
1221 @ vertical-align: top;
1222 @ white-space: nowrap;
1223 },
1224 { 0,
1225 0,
1226 0
1227 }
1228

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button