├── .gitignore ├── .gitattributes ├── foot.php ├── .htaccess ├── index.php ├── status.php ├── send.php ├── functions.php ├── insert.php ├── README.md ├── head.php ├── config.dist.php ├── login.php ├── outbox.php ├── inbox.php └── stylesheet.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore config file 2 | /config.php -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /foot.php: -------------------------------------------------------------------------------- 1 |

2 | 3 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | order deny,allow 2 | #deny from all 3 | allow from 127.0.0.1 4 | # Uncomment/edit to match LAN subnet 5 | allow from 192.168.99 -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /status.php: -------------------------------------------------------------------------------- 1 |
2 | prepare("SELECT UpdatedInDB FROM phones"); 7 | $sql->execute(); 8 | $UpdatedInDB = $sql->fetchColumn(); 9 | $UIDB = strtotime($UpdatedInDB." ".$TimeZone); 10 | 11 | if ((time() - $UIDB) > 90) { 12 | echo "STATUS: DISCONNECTED"; 13 | } else { 14 | echo "STATUS: CONNECTED"; 15 | } 16 | ?> 17 |
18 | -------------------------------------------------------------------------------- /send.php: -------------------------------------------------------------------------------- 1 |
2 | SEND MESSAGE:

3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Mobile Number:
Date & Time:
Format: YYYY-MM-DD HH:MM:SS for sending at a future date/time. Leave blank to send now.
Message:
(Max length: 999 char)
29 |
30 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | 40 | -------------------------------------------------------------------------------- /insert.php: -------------------------------------------------------------------------------- 1 | prepare( 20 | 'INSERT INTO outbox SET '. 21 | 'DestinationNumber = "'.$num.'", '. 22 | 'UDH = "050003'.$ref.$nb.'01", '. 23 | 'SendingDateTime = "'.$SendAt.'", '. 24 | 'MultiPart = "'.(count($messages) > 0 ? 'true' : 'false').'", '. 25 | 'TextDecoded = "'.$message.'", '. 26 | 'CreatorID = "", '. 27 | 'Class = "-1"' 28 | ); 29 | $sql->execute(); 30 | 31 | $id = $pdo->lastInsertId(); 32 | $i = 1; 33 | foreach ($messages as $message) { 34 | $sql = $pdo->prepare( 35 | 'INSERT INTO outbox_multipart SET '. 36 | 'SequencePosition = "'.++$i.'", '. 37 | 'UDH = "050003'.$ref.$nb.sprintf("%02x", $i).'", '. 38 | 'TextDecoded = "'.$message.'", '. 39 | 'ID = "'.$id.'", '. 40 | 'Class = "-1"' 41 | ); 42 | $sql->execute(); 43 | } 44 | die(header('Location: ./')); 45 | 46 | ?> -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## PHP Gammu Frontend 2 | 3 | Just a simple PHP frontend for Gammu SMS that allows you to view outbox & inbox, search messages and send messages. 4 | 5 | Nothing fancy here - no Restful API, no contact database, no mass-SMS. This frontend uses the unaltered Gammu database only. Other Gammu functions should be scripted using gammu-smsd-inject. Frequent contact names (instead of numbers) can be displayed by editing name variables in config.php. 6 | 7 | I just wanted a simple viewer to see messages generated programmatically by other scripts. That's all this is, with a couple of extra very minor features. 8 | 9 | 10 | ## Features 11 | 12 | * Uses unaltered Gammu database (no new tables/columns/etc) 13 | * Send short/long messages (up to 999 char) immediately or at future time 14 | * Long messages are properly sequenced to arrive as a single long message 15 | * Displays outbox (including long message outbox in sequence) 16 | * Displays inbox/sent items 17 | * Displays connection status: CONNECTED or DISCONNECTED (StatusFrequency in SMSDRC must be enabled and < 90) 18 | * Search inbox/sent items by keyword or phone number 19 | * Displays names instead of numbers if configured (from PHP array - no database entries) 20 | * Mobile browser support (turns wide tables into cards) 21 | 22 | ## Prerequisites 23 | 24 | * Working Gammu 25 | * Webserver with PHP 26 | * MySQL database for Gammu 27 | 28 | 29 | ## Instructions 30 | 31 | * Drop files into web server folder 32 | * Rename config.php.dist to config.php and edit variables 33 | * Edit .htaccess to allow your LAN subnet (or delete if you don't want any security) 34 | -------------------------------------------------------------------------------- /head.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | SMS Gateway 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 44 |
45 | 46 |
47 | -------------------------------------------------------------------------------- /config.dist.php: -------------------------------------------------------------------------------- 1 | 'supersecretpassword', 8 | 'userdude' => 'othersecretpassword', 9 | 'otherdude' => 'anotherpassword' 10 | ); 11 | 12 | /* Cookie Duration in days */ 13 | $cookie_duration = 90; 14 | 15 | /* Database Variables (MySQL databases only) 16 | 17 | 'driver' = connection type 18 | 19 | For MySQL use driver = 'mysql' 20 | For ODBC use driver = 'odbc' 21 | 22 | * When opting for ODBC use correct DSN! * 23 | * Example: "MariaDB ODBC 3.0 Driver" * 24 | * Exact spelling is critical! * 25 | */ 26 | $Database = array ( 27 | 'host' => 'localhost', 28 | 'username' => 'gammu', 29 | 'password' => 'supersecretpassword', 30 | 'dbname' => 'gammu', 31 | 'port' => '3306', 32 | 'driver' => 'mysql', 33 | 'dsn' => 'MariaDB ODBC 3.1 Driver' 34 | ); 35 | 36 | /* Location Variables */ 37 | $ServerLocation = array ( 38 | 'CountryCode' => '+1' // telephone country code prefix 39 | ); 40 | 41 | /* TimeZone required to determine if gammu is connected or not - status.php 42 | TimeZone offset required to compare last update in database against current time 43 | https://www.php.net/manual/en/timezones.php 44 | */ 45 | $TimeZone = 'America/New_York'; 46 | 47 | 48 | /* Contact Variables */ 49 | /* Optional - change if you want to use real names/numbers - leaving the examples will not harm anything */ 50 | $Contacts = array ( 51 | '2125551212' => 'Jim', 52 | '5615559876' => 'Bill', 53 | '4045555463' => 'Jean', 54 | '1235558888' => 'Sandy', 55 | '9999999999' => 'Frank', 56 | '' => '' 57 | ); 58 | 59 | 60 | /* Custom URL shortner service - converts to http link */ 61 | /* Enable this if you receive shortlinks without http(s):// prefix */ 62 | $UseShortURL = False; // Only use this if your short link service does NOT prefix URLs in text messages with http:// 63 | $ShortURLDomain = 'url.domain.tld'; // URL your short link service uses for short links (not the name of the service) - works great with yourls 64 | $ShortURLSSL = True; // True prefixes https://, False prefixes http:// 65 | 66 | 67 | /* MESSAGE HISTORY: Number of records per page */ 68 | $no_of_records_per_page = 20; 69 | 70 | /* HIDE OUTBOX: True = do not display outbox if no messages. False = display outbox all the time. */ 71 | $hideOutbox = True; 72 | 73 | ?> 74 | -------------------------------------------------------------------------------- /login.php: -------------------------------------------------------------------------------- 1 | 32 | 33 | 34 | 35 | 36 | 37 | Log In 38 | 39 | 40 | 44 | 45 | 46 |
47 |

Log In

48 |
" method="post"> 49 |
50 | 51 | 52 |
53 |
54 | 55 | 56 |
57 |
58 | 59 | 60 |
61 |
62 | 63 |
64 |
65 | "; 68 | echo "alert('Username/Password Invalid');"; 69 | echo ""; 70 | } 71 | ?> 72 |
73 | 74 | -------------------------------------------------------------------------------- /outbox.php: -------------------------------------------------------------------------------- 1 | prepare("SELECT COUNT(id) FROM outbox"); 6 | $ismsg_sql->execute(); 7 | $outbox_records = $ismsg_sql->fetchColumn(); 8 | 9 | $sql = $pdo->prepare(" 10 | (SELECT 11 | a.ID, 12 | a.Status, 13 | a.SendingDateTime, 14 | a.DestinationNumber AS Number, 15 | a.TextDecoded, 16 | a.MultiPart AS Sequence 17 | FROM outbox a ) 18 | UNION ALL 19 | (SELECT 20 | a.ID, 21 | a.DestinationNumber AS Number, 22 | a.Status, 23 | a.SendingDateTime, 24 | b.TextDecoded, 25 | b.SequencePosition AS Sequence 26 | FROM outbox a 27 | JOIN outbox_multipart b on a.ID = b.ID ) 28 | ORDER BY ID ASC, Sequence ASC 29 | "); 30 | $sql->execute(); 31 | 32 | if (($outbox_records > 0) || (!$hideOutbox)) { 33 | echo " 34 |
35 | CURRENT OUTBOX:
36 |
37 |
38 |
O = OK
39 |
E = Error
40 |
41 |
42 |
43 |
44 |
TimeStamp
45 |
To
46 |
Message
47 |
48 | "; 49 | 50 | while($row = $sql->fetch(PDO::FETCH_ASSOC)){ 51 | echo " 52 |
53 | "; 54 | 55 | if (($row['Status']=="SendingOK") || ($row['Status']=="SendingOKNoReport") || ($row['Status']=="Reserved")) { 56 | echo " 57 |
O
"; 58 | } else { 59 | echo " 60 |
E
"; 61 | } 62 | 63 | echo "
".date("y/m/d H:i:s", strtotime($row['SendingDateTime']))."
"; 64 | 65 | $num = str_replace($ServerLocation['CountryCode'], '', $row['Number']); 66 | if (array_key_exists($num,$Contacts)) { 67 | echo " 68 |
".$Contacts[$num]."
"; 69 | } else { 70 | echo " 71 |
".displayMobileNumber($num)."
"; 72 | } 73 | 74 | if ($UseShortURL) { 75 | if ($ShortURLSSL){$HTTP = "https://";} else {$HTTP = "http://";} 76 | $ShortURLRegex = "((".$ShortURLDomain.")\/\S+)"; 77 | $textMsg = preg_replace($ShortURLRegex, '$0', $row['TextDecoded']); 78 | } else { 79 | $textMsg = $row['TextDecoded']; 80 | } 81 | echo "
".$textMsg."
"; 82 | 83 | echo " 84 |
"; // End of div-table-row 85 | } 86 | 87 | echo " 88 |
"; // End of div-table 89 | 90 | echo " 91 |
"; // End of overborder 92 | 93 | echo " 94 |
"; // End of section 95 | 96 | } 97 | 98 | 99 | ?> -------------------------------------------------------------------------------- /inbox.php: -------------------------------------------------------------------------------- 1 | prepare(" 19 | SELECT 20 | Sum( a.count ) 21 | FROM( 22 | SELECT 23 | Count( * ) AS count 24 | FROM inbox 25 | ".$search_inbox_sql." 26 | UNION ALL 27 | SELECT 28 | Count( * ) AS count 29 | FROM sentitems 30 | ".$search_outbox_sql." 31 | ) a 32 | "); 33 | $total_pages_sql->execute(); 34 | $total_rows = $total_pages_sql->fetchColumn(); 35 | $total_pages = ceil($total_rows / $no_of_records_per_page); 36 | 37 | $sql = $pdo->prepare(" 38 | SELECT 39 | Status, 40 | DATE_FORMAT(ReceivingDateTime, '%y/%m/%d %H:%i:%s') as TimeStamp, 41 | SenderNumber as Number, 42 | TextDecoded 43 | FROM inbox 44 | ".$search_inbox_sql." 45 | UNION ALL 46 | SELECT 47 | Status, 48 | DATE_FORMAT(SendingDateTime, '%y/%m/%d %H:%i:%s') as TimeStamp, 49 | DestinationNumber as Number, 50 | TextDecoded 51 | FROM sentitems 52 | ".$search_outbox_sql." 53 | ORDER BY TimeStamp DESC 54 | LIMIT ".$offset.", ".$no_of_records_per_page 55 | ); 56 | $sql->execute(); 57 | 58 | if ($total_rows == 1) {$singular = '';} else {$singular= 's';} 59 | 60 | echo " 61 |
"; 62 | 63 | if ($search == ""){ 64 | echo "MESSAGE HISTORY: ".$total_rows." Messages (Page: ".$page." of ".$total_pages.")"; 65 | } else { 66 | if ($total_rows == 0){ 67 | echo "SEARCH RESULTS: No results for \"".$search."\""; 68 | } else { 69 | echo "SEARCH RESULTS: ".$total_rows." Result".$singular." for \"".$search."\" (Page: ".$page." of ".$total_pages.")"; 70 | } 71 | } 72 | 73 | echo " 74 |
75 |
76 |
S = Sent Items
77 |
I = Inbox
78 |
E = Error
79 |
80 |
81 |
82 |
83 |
TimeStamp
84 |
Number
85 |
Message
86 |
87 | "; 88 | 89 | while($row = $sql->fetch(PDO::FETCH_ASSOC)){ 90 | echo " 91 |
92 | "; 93 | 94 | if($row['Status']=="SendingOKNoReport") { 95 | echo "
S
"; 96 | $tofrom = "To"; 97 | } else if ($row['Status']=="SendingError") { 98 | echo "
E
"; 99 | $tofrom = "To"; 100 | } else { 101 | echo "
I
"; 102 | $tofrom = "From"; 103 | } 104 | 105 | echo "
".$row['TimeStamp']."
"; 106 | 107 | $num = str_replace($ServerLocation['CountryCode'], '', $row['Number']); 108 | if (array_key_exists($num,$Contacts)) { 109 | echo "
".$Contacts[$num]."
"; 110 | } else { 111 | echo "
".displayMobileNumber($num)."
"; 112 | } 113 | 114 | if ($UseShortURL) { 115 | if ($ShortURLSSL){$HTTP = "https://";} else {$HTTP = "http://";} 116 | $ShortURLRegex = "((".$ShortURLDomain.")\/\S+)"; 117 | $textMsg = preg_replace($ShortURLRegex, '$0', $row['TextDecoded']); 118 | } else { 119 | $textMsg = $row['TextDecoded']; 120 | } 121 | echo "
".$textMsg."
"; 122 | 123 | echo " 124 |
"; // End of div-table-row 125 | } 126 | 127 | 128 | echo " 129 |
"; //End of div-table 130 | 131 | echo " 132 |
133 | "; 134 | 135 | if ($page <= 1){echo "
  • First
  • ";} else {echo "
  • First
  • ";} 136 | if ($page <= 1){echo "
  • Prev
  • ";} else {echo "
  • Prev
  • ";} 137 | if ($page >= $total_pages){echo "
  • Next
  • ";} else {echo "
  • Next
  • ";} 138 | if ($page >= $total_pages){echo "
  • Last
  • ";} else {echo "
  • Last
  • ";} 139 | 140 | echo " 141 |
    142 |
    143 |
    144 | 145 |
    146 |
    147 |
    "; 148 | 149 | echo " 150 |
    "; //End of overborder 151 | 152 | echo " 153 |
    "; //End of section 154 | 155 | ?> 156 | -------------------------------------------------------------------------------- /stylesheet.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Super Simple Gammu Frontend 4 | 5 | */ 6 | 7 | 8 | body { 9 | background: #fefefe; 10 | font-family: "Roboto"; 11 | font-size: 12pt; 12 | } 13 | 14 | a:link, a:active, a:visited { 15 | color: #FF0000; 16 | text-transform: underline; 17 | } 18 | 19 | a:hover { 20 | color: #FF0000; 21 | text-transform: none; 22 | } 23 | 24 | .header { 25 | position: fixed; 26 | top: 0; 27 | left: 0; 28 | width: 100%; 29 | color: #000; 30 | background: #fefefe; 31 | z-index: 1; 32 | overflow: hidden; 33 | text-align:center; 34 | } 35 | 36 | .header h1 { 37 | font-size:25px; 38 | font-weight:normal; 39 | margin:0 auto; 40 | } 41 | 42 | .header h2 { 43 | font-size:15px; 44 | font-weight:normal; 45 | margin:0 auto; 46 | } 47 | 48 | .wrapper { 49 | max-width: 1200px; 50 | position: relative; 51 | margin: 20px auto 30px auto; 52 | padding-top: 10px; 53 | } 54 | 55 | .clear { 56 | clear: both; 57 | } 58 | 59 | .banner { 60 | width: 100%; 61 | background-color: #666666; 62 | } 63 | 64 | .banner-left { 65 | float: left; 66 | width: 67%; 67 | } 68 | 69 | .banner-left-left { 70 | float: left; 71 | width: 50%; 72 | text-align: center; 73 | color: white; 74 | } 75 | 76 | .banner-left-right { 77 | float: right; 78 | width: 50%; 79 | text-align: center; 80 | color: white; 81 | } 82 | 83 | .banner-right { 84 | float: right; 85 | width: 33%; 86 | color: white; 87 | } 88 | 89 | .headlinks { 90 | max-width: 720px; 91 | position:relative; 92 | margin: 0 auto; 93 | } 94 | 95 | .headlinkwidth { 96 | width: 100%; 97 | position:relative; 98 | margin: 0 auto; 99 | } 100 | 101 | .headlinks ul li { 102 | float: left; 103 | width: 33%; 104 | display: inline-block; 105 | text-align: center; 106 | margin: 0 auto; 107 | } 108 | 109 | .headlinks ul li a { 110 | } 111 | 112 | .headlinks a:link, a:active, a:visited { 113 | color: #FF0000; 114 | text-transform: underline; 115 | } 116 | 117 | .headlinks a:hover { 118 | color: #FF0000; 119 | text-transform: none; 120 | } 121 | 122 | .section { 123 | padding: 5px 0 25px 0; 124 | } 125 | 126 | table.section { 127 | border-collapse: collapse; 128 | border: 1px solid black; 129 | width: 100%; 130 | font-size: 10pt; 131 | } 132 | 133 | table.section th, table.section td { 134 | border: 1px solid black; 135 | } 136 | 137 | .section h1 { 138 | font-size: 30px; 139 | text-align: center; 140 | font-family: "Oswald", sans-serif; 141 | padding: 5px; 142 | } 143 | 144 | .section h2 { 145 | font-size: 20px; 146 | text-align: center; 147 | } 148 | 149 | .sectitle { 150 | font-weight: bold; 151 | font-style: normal; 152 | } 153 | 154 | .secleft { 155 | float: left; 156 | /* width: 50%; */ 157 | font-weight: bold; 158 | padding: 10px; 159 | } 160 | 161 | .secright { 162 | float: right; 163 | /* width: 50%; */ 164 | text-align: right; 165 | padding: 10px; 166 | } 167 | 168 | .footer { 169 | width: 100%; 170 | text-align: center; 171 | } 172 | 173 | ul { 174 | list-style-type: none; 175 | padding: 0; 176 | } 177 | 178 | li { 179 | padding: 0; 180 | display: inline; 181 | font-size: 10pt; 182 | } 183 | 184 | 185 | /* ### NEW TABLE CSS ### */ 186 | 187 | .overborder { 188 | outline: 1px solid black; 189 | border-radius: 5px; 190 | } 191 | 192 | .div-table { 193 | display: table; 194 | width: 100%; 195 | font-size: 0.8em; 196 | border-left: 1px solid #ccc; 197 | border-bottom: 1px solid #ccc; 198 | } 199 | 200 | .div-table-row-header { 201 | display: table-row; 202 | font-weight: bold; 203 | text-align: center; 204 | } 205 | 206 | .div-table-row { 207 | display: table-row; 208 | } 209 | 210 | .div-table-row:nth-of-type(even) { 211 | background: #eee; 212 | border: 1px solid #ccc; 213 | } 214 | 215 | .center { 216 | text-align: center; 217 | } 218 | 219 | .tall { 220 | padding: 5px 0; 221 | } 222 | 223 | .narrow { 224 | width: 5px; 225 | } 226 | 227 | .green { 228 | color: white; 229 | background-color: green; 230 | } 231 | 232 | .blue { 233 | color: white; 234 | background-color: blue; 235 | } 236 | 237 | .red { 238 | color: white; 239 | background-color: red; 240 | } 241 | 242 | .greenbox { 243 | color: white; 244 | } 245 | 246 | .bluebox { 247 | color: white; 248 | } 249 | 250 | .redbox { 251 | color: white; 252 | } 253 | 254 | .timestamp { 255 | width: 110px; 256 | } 257 | 258 | .number { 259 | width: 90px; 260 | } 261 | 262 | .files-col { 263 | max-width: 400px; 264 | word-wrap: break-word; /* IE 5+ */ 265 | } 266 | 267 | .whitespace { 268 | white-space: pre; /* CSS 2.0 */ 269 | white-space: pre-wrap; /* CSS 2.1 */ 270 | white-space: pre-line; /* CSS 3.0 */ 271 | white-space: -pre-wrap; /* Opera 4-6 */ 272 | white-space: -o-pre-wrap; /* Opera 7 */ 273 | white-space: -moz-pre-wrap; /* Mozilla */ 274 | white-space: -hp-pre-wrap; /* HP Printers */ 275 | } 276 | 277 | .div-table-col { 278 | display: table-cell; 279 | padding: 3px; 280 | border-right: 1px solid #ccc; 281 | border-top: 1px solid #ccc; 282 | } 283 | 284 | /* ### BAR CHART ### */ 285 | #barchart { 286 | display: grid; 287 | overflow: hidden; 288 | width: 100%; 289 | /* grid-template-columns: 1fr 1fr 1fr; */ 290 | } 291 | 292 | #barchart div { 293 | color: white; 294 | font-weight: bold; 295 | font-size: 0.7em; 296 | display: flex; 297 | align-items: center; 298 | justify-content: center; 299 | padding: 0 5px; 300 | } 301 | 302 | /* ### MOBILE DEVICES ### */ 303 | @media only screen and (max-width: 629px) { 304 | 305 | /* ### NEW TABLE CSS ### */ 306 | .div-table, .div-table-row-header, .div-table-row, .div-table-col { 307 | display: block; 308 | } 309 | 310 | .div-table { 311 | border: none; 312 | } 313 | 314 | /* Hide table headers (but not display: none;, for accessibility) */ 315 | .div-table-row-header { 316 | position: absolute; 317 | top: -9999px; 318 | left: -9999px; 319 | } 320 | 321 | .white:before { 322 | color: white; 323 | } 324 | 325 | .div-table-row { 326 | border: none; 327 | border-left: 1px solid #ccc; 328 | border-right: 1px solid #ccc; 329 | } 330 | 331 | .div-table-col { 332 | /* Behave like a "row" */ 333 | border: none; 334 | border-bottom: 1px solid #e6e6e6; 335 | position: relative; 336 | padding-left: 100px; 337 | text-align: left; 338 | word-wrap: break-word; /* IE 5+ */ 339 | } 340 | 341 | .div-table-col:before { 342 | /* Now like a table header */ 343 | border: none; 344 | position: absolute; 345 | top: 3px; 346 | left: 6px; 347 | padding-right: 10px; 348 | /* Label the data */ 349 | content: attr(data-column); 350 | font-weight: bold; 351 | } 352 | 353 | .narrow { 354 | max-width: 10px; 355 | } 356 | 357 | .description-col { 358 | min-width: 0px; 359 | } 360 | 361 | .timestamp { 362 | width: calc(100% - 102px); 363 | } 364 | 365 | .number { 366 | width: calc(100% - 102px); 367 | } 368 | 369 | ul { 370 | list-style-type: none; 371 | padding: 0; 372 | } 373 | 374 | li { 375 | padding: 0; 376 | display: inline; 377 | } 378 | 379 | .secleft { 380 | display: block; 381 | float: none; 382 | width: 100%; 383 | font-weight: bold; 384 | padding: 5px; 385 | } 386 | 387 | .secright { 388 | display: block; 389 | float: none; 390 | width: 100%; 391 | text-align: left; 392 | padding: 5px; 393 | } 394 | 395 | .greenbox { 396 | color: green; 397 | } 398 | 399 | .bluebox { 400 | color: blue; 401 | } 402 | 403 | .redbox { 404 | color: white; 405 | } 406 | 407 | 408 | } --------------------------------------------------------------------------------