2024-02-26

【Kotlin】disable/enable ImageButton

參考資料 ----

雖然在 layout.xml 中定義 enabled="false"AndroidStudio 並不會報錯,但在 app 執行時並沒有產生作用;官網的 ImageButton 參考頁也沒有找到 enabled 的屬性,似乎只能在 app 執行時設定。
 
...
...

<ImageButton
    android:id="@+id/Button1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/圖檔"    本次採用 向量圖檔(vector asset), 所以可在程式中改變圖檔的顏色
    android:background="@android:color/transparent"    指定按鈕的背景為透明
    android:enabled="true" />    這個屬性在 app 執行時並沒有產生作用
 


 
 
... 
...

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        var button1 = findViewById<ImageButton>(R.id.Button1)
        button1.isEnabled = false
        // 即使被 disabled 了, button1 所引用的圖檔顏色並沒有任何變化
        // 所以也要自行改變圖檔的顏色
        button1.setColorFilter(getColor(R.color.black))
    }
 


2024-02-13

【MSSQLserver】日期的運算(適 MSSQL2016(含)後版本)

參考資料 ----
DATEPART (Transact-SQL)

 
-- 假設給予日期:2024-2-2(五)


-- 求得 2024-2-2 是當年的第幾週
SELECT DATEPART(week, '2024-2-2')    -- 第 5 週


-- 求得 2024-2-2 是星期幾
SELECT DATEPART(weekday, '2024-2-2')    -- 星期五


-- 當月首日:2024-2-1
-- 方法一
-- EOMONTH 求得上個月的最末日
-- DATEADD 再運算加 1 日
SELECT DATEADD(day, 1, EOMONTH('2024-2-2',-1))

-- 方法二
SELECT DATEFROMPARTS(YEAR('2024-2-2'),MONTH('2024-2-2'),1)


-- 當月最末日:2024-2-29
SELECT EOMONTH('2024-2-2')


-- 當年首日:2024-1-1
SELECT DATEFROMPARTS(YEAR('2024-2-2'),1,1)


-- 當年最末日:2024-12-31
SELECT DATEFROMPARTS(YEAR('2024-2-2'), 12, 31)
 


2024-02-07

【Kotlin】設為 深色/夜晚 模式

Android Studio 精靈建立新專案,若看到有 /res/values-night/ 目錄,且目錄下有 themes.xml,就表示精靈有幫您建立 深色/夜晚 模式的主題配置。
 
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        ...
        ...

        // 設為 深色/夜晚 模式
        AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)

        ...
        ...
}
 

2024-02-01

【LinuxMint20.3】以 docker 安裝 Apache Superset -- 免費,開源的 BI

參考資料 ----


Superset 的安裝方式有:
  • Linux 套件安裝
  • 以 Docker Compose 佈署
  • 以 Kubernetes(K8S) 佈署
  • 在 Dockerhub 執行
  • 以 Pypi 下載安裝
  • 自 GitHub 下載安裝
  • 自 Apache 基金會官網下載安裝


本筆記採用 Docker Compose 佈署

安裝必要的相依套件
 
[user]$ sudo  apt  install  git  docker  docker-compose
 


複製資源庫
 
# 預定裝在 /usr/share
[user]$ cd  /usr/share
[user]$ sudo  git  clone  https://github.com/apache/superset.git

# 會看到生成 /usr/share/superset/ 目錄
 


執行 docker daemon
 
[user]$ cd  superset
# 執行 docker daemon
[user]$ sudo  systemctl  start  docker

# 檢查確認 docker daemon 執行狀態
[user]$ sudo  systemctl  status  docker
 


啟動 superset
 
# 第一次執行, 下傳 container, 一次性指令
[user]$ sudo  docker-compose  -f  docker-compose-non-dev.yml  pull

# 之後只要執行這指令
[user]$ sudo  docker-compose  -f  docker-compose-non-dev.yml  up
 


若要停止執行 superset,則 CTRL + C
這時會看到螢幕顯示
Stopping superset_app...
Stopping superset_worker_beat...
...
等正在停止程序的訊息

superset 內建並不支援 Microsoft SQL Server,若要連接 mssql,則要安裝驅動程式,官方建議的驅動為 pymssql
 
[user]$ sudo  vim  /usr/share/superset/docker/requirements-local.txt
# 輸入
pymssql

:x 存檔離開

# 再執行
[user]$ sudo  docker-compose  -f  docker-compose-non-dev.yml  up
# 就會自動安裝 pymssql 了
 


開啟瀏覽器,輸入網址
http://localhost:8088
登入帳/密:admin / admin

建立資料庫連線

點擊右上角的 "Settings"
Database Connections



+DATABASE
→ 可以看到列出的預設支援資料庫沒有 MS SQL Server
→ 下拉下方的 Supported databases,選擇最末的 Other



DISPLAY NAME (必填) 命名您的資料庫連線
SQLALCHEMY URI(必填) 輸入連線字串,格式為
 
mssql+pymssql://username:password@hostname:port/database_name

* 若 password 內含有 @ 字元,要改為 URL編碼(percent-encoding),改成 %40
* port 的部份,若您的 mssql 採用預設的 port,則可不填
 
→ 點擊 TEST CONNECTION 鈕確認連線正常
→ CONNECT 建立連線





設定為開機自動執行 superset

 
[user]$ sudo  vim  /etc/systemd/system/superset.service
# 輸入下述指令
[Unit]
Description=Apache  Superset
After=docker.service

[Service]
Type=simple
ExecStart=/usr/bin/docker-compose  -f  /usr/share/superset/docker-compose-non-dev.yml  up
ExecStop=/usr/bin/docker-compose  -f  /usr/share/superset/docker-compose-non-dev.yml  down

[Install]
WantedBy=multi-user.target

:x 存檔離開

# 啟用 docker.service 開機自動執行
[user]$ sudo  systemctl  enable  docker.service

# 啟用 superset.service 開機自動執行
[user]$ sudo  systemctl  enable  superset.service
 
重新開機,驗證 superset 確實開機自動執行


2024-01-31

【Metabase】設定 Metabase 為開機即啟動服務

參考資料 ----

metabase.jar 移至 /usr/share/metabase/ 目錄


 
[user]$ sudo  su
[root]# groupadd  -r  metabase
[root]# useradd  -r  -s  /bin/false  -g  metabase  metabase
[root]# chown  -R  metabase:metabase  /usr/share/metabase
[root]# touch  /var/log/metabase.log
[root]# chown  syslog:adm  /var/log/metabase.log
[root]# touch  /etc/default/metabase
[root]# chmod  640  /etc/default/metabase
 


建立 Metabase Service 服務檔
 
[root]# cd  /etc/systemd/system/
[root]# vim metabase.service
# 輸入下述內容
[Unit]
Description=Metabase server
After=syslog.target
After=network.target

[Service]
WorkingDirectory=/usr/share/metabase/
ExecStart=/usr/bin/java  -jar  /usr/share/metabase/metabase.jar
EnvironmentFile=/etc/default/metabase
User=metabase
Type=simple
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=metabase
SuccessExitStatus=143
TimeoutStopSec=120
Restart=always

[Install]
WantedBy=multi-user.target

:x 存檔離開
 


建立 syslog conf
 
[root]# vim  /etc/rsyslog.d/metabase.conf
# 輸入下述內容
if $programname == 'metabase' then /var/log/metabase.log
& stop

:x 存檔離開

# 重啟 rsyslog.service 以使 metabase.conf 生效
[root]# systemctl  restart  rsyslog.service
 


編輯 Metabase 的環境變數
 
[root]# vim  /etc/default/metabase
MB_PASSWORD_COMPLEXITY=strong
MB_PASSWORD_LENGTH=密碼字串長度
MB_JETTY_HOST=0.0.0.0
MB_JETTY_PORT=3000
MB_DB_TYPE=mysql
MB_DB_DBNAME=metabase
MB_DB_PORT=3306
MB_DB_USER=metabase
MB_DB_PASS=MySQL的metabase帳戶密碼
MB_DB_HOST=localhost
MB_EMOJI_IN_LOGS=true    # log 要不要有顏文字...這還蠻有趣的...若不要就填 false
# any other env vars you want available to Metabase
 


設定 metabase 為開機啟動
 
# 先手動啟動 metabase.service
[root]# systemctl  daemon-reload
[root]# systemctl  start  metabase.service
[root]# systemctl  status  metabase.service

# 確定執行沒問題了, 就改為開機自動啟動
[root]# systemctl  enable  metabase.service
 



相關筆記 ----

2024-01-29

【Metabase】社群版(Community) 將系統資料移至大型資料庫

參考資料 ----


Metabase 自帶的系統資料庫為 H2,僅用於測試試用期間,若要轉為正式應用(production),官方強烈將系統資料移至大型資料庫。

建議版本為 MySQL 5.7.7(含)MariaDB 10.2.2(含) 或 PostgreSQL 9.4(含) 以上;本筆記為 MySQL 8.0

注意:避免同時做 Metabase 更新升級 及 資料庫移植


關閉 Metabase,目前我的練功主機是開終端機視窗,在家目錄以
 
[user]~$ java  -jar  metabase.jar
 
的指令啟動 Metabase
所以只要在瀏覽器的 Metabase 頁面登出,然後在終端機視窗 Ctrl+C 中斷 Metabase 即可。


備份 H2︰複製 metabase.db.mv.db 到另一個安全的目錄存放。


操作 phpMyAdmin
MySQL 新增 metabase 帳戶


建立 metabase 空白資料庫...我其實對資料庫的字集沒有概念,所以隨便選。


授予 metabase 帳戶對 metabase 資料庫具有全部的權限










執行移植指令
 
# 切換到 metabase.jar 所在目錄, 在本例為 [user]~/metabase/
[user]~/metabase$  export MB_DB_TYPE=mysql
[user]~/metabase$  export MB_DB_CONNECTION_URI="jdbc:mysql://主機IP:3306/metabase?user=metabase帳戶&password=密碼"
[user]~/metabase$  java  -DMB_DB_TYPE=mysql  -DMB_DB_CONNECTION_URI="jdbc:mysql://主機IP:3306/metabase?user=帳號&password=密碼;"  -jar  metabase.jar  load-from-h2  metabase.db    # 注意檔名
 


因為有備份 H2,所以可以放心刪除 metabase 所在目錄下的 H2 檔案,以確認 metabase 存取的是 MySQL。

重新啟動 metabase
 
[user]~/metabase$  export MB_DB_TYPE=mysql
[user]~/metabase$  export MB_DB_CONNECTION_URI="jdbc:mysql://主機IP:3306/metabase?user=metabase帳戶&password=密碼"
[user]~/metabase$  java  -DMB_DB_TYPE=mysql  -DMB_DB_CONNECTION_URI="jdbc:mysql://主機IP:3306/metabase?user=metabase帳號&password=密碼"  -jar  metabase.jar
 
重登入 metabase,可以看到之前製作的儀表板都在。


相關筆記 ----


2024-01-20

【CentOS 7.x】安裝 Filebeat 集中收集 防火牆 Fortigate 的 log

參考資料 ----

 
[root]#  rpm  --import  https://packages.elastic.co/GPG-KEY-elasticsearch
 

打開 CentOS 防火牆 9004 port


編寫 filebeat repo
 
[root]#  vi  /etc/yum.repos.d/filebeat.repo
# 寫入下列內容
[elastic-8.x]
name=Elastic repository for 8.x packages
baseurl=https://artifacts.elastic.co/packages/8.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
# 存檔離開
 


安裝 filebeat
 
[root]# yum  install  -y  filebeat

# 安裝後, 建議關閉 filebeat.repo 自動更新功能
# enabled=0
 


設定為開機啟動
 
[root]# systemctl  enable  filebeat
 


啟用收集 Fortinet 防火牆 Fortigatelog 功能
 
[root]# filebeat  modules  enable  fortinet
 
切換到 /etc/filebeat/modules.d/ 下,會看到 fortinet.yml,其他模組因為未啟用,所以會看到其他的檔名皆為 《模組名.yml.disabled》


修改設定檔 /etc/filebeat/modules.d/fortinet.yml
 
- module: fortinet
  firewall:
    enabled: true    改成 true
    var.input: udp   解除註解
    var.syslog_host: 0.0.0.0 或 localhost
    var.syslog_port: 9004    解除註解
    var.internal_interfaces: [ "LAN" ]    解除註解
# 存檔離開
 


由於 Filebeat 屬於 ELK 系統的其中一個套件,預設是將收集到的 log 再往 ElasticsearchLogstash 送,所以要修改 /etc/filebeat/filebeat.yml, 將 log 存成檔案
 
[root]# vi /etc/filebeat/filebeat.yml
# 必須將其他 output 的設定註解
# 亦即同一時間只能有一個 output 設定
output.file:
  path: "/var/log/filebeat"
  filename: filebeat        # 檔名規則, 在本例:filebeat-西元年月日.ndjson, 內容為 json 格式
  rotate_every_kb: 10240    # 每個 log 檔的 size, 若達設定值,
                            # 則換存新的 log 檔, 在本例, 產生的檔名依序為 filebeat-西元年月日.ndjson, filebeat-西元年月日-1.ndjson, filebeat-西元年月日-2.ndjson, ...
                            # 預設為 10240 KB
  number_of_files: 7        # 保留的檔案數, 若達設定值, 則刪除最舊的檔案, 預設值為 7, 設定值範圍為 2~1024
  permissions: 0600         # 檔案權限, 設定值 0600
  rotate_on_startup: true   # 是否啟動時就存新的 log 檔, 預設值 true
# 存檔離開
 
要注意檔名雖然為 filebeat-西元年月日.ndjson,但換檔的規則是當檔案達設定值才換,並不會每天生出一個新的檔,
所以我想到變通的做法 -- 在 crontab 指定每天重啟 filebeat
只是若同一天重啟 filebeat 一次以上,仍會產生 filebeat-西元年月日-1.ndjson, ...


立即啟動
 
[root]# systemctl  start  filebeat
 


確認 filebeat 在運行中
 
[root]# systemctl  status  filebeat
 
切換到 /var/log/filebeat/ 目錄下, 發現已生出檔案 filebeat-西元年月日.ndjson,打開檔案觀察,格式 json


Fortigate 防火牆,設定將 log 往 CentOS 送,由於防火牆不歸老人家管,只聽說此功能為進階設定,在 Fortigate 的圖形界面達不到此目的,必須下指令,陽春的指令如下:
 
config log syslogd setting
set status enable
set format default
set server <安裝 Filebeat 的 CentOS 主機的 IP>
set port 9004

 
防火牆的 log 數量非常驚人,不到 1 小時就超過 1G 了!!
管防火牆的高手夥伴還啟用了過濾條件,只將 critical、alert、emergency 三個等級的 log 往 CentOS 送,這樣 log 的數量大幅減少。

log 的等級如下:










2024-01-14

【LINE】解除 LIFF 連動程式

點擊 主頁 → 右上角 (設定)齒輪










2023-12-07

【LINE Messaging API】主動傳送訊息給指定使用者

 
<php?
date_default_timezone_set('Asia/Taipei');
$sRootDir = $_SERVER['DOCUMENT_ROOT'];
$channelAccessToken = '您的 channel Access Token';
$bodyMsg = file_get_contents('php://input');

// LINE 不會幫我們記錄, 所以要自己寫 log
// 這段不是必要, 只是方便自己除錯
$sLogfile = 'log_'.date('Ymd').'.log';    // 指定 log 檔名, 一天一個檔, 檔名格式為: log_yyyymmdd.log
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', Recive: '.$bodyMsg."\n", true));
fclose($fp);

...
...
// 取得 user id, 管道有很多, 此處不贅述
$sUserId = ...;

$payload = [ 'to' => $sUserId,
             'messages' => [
                               ['type' => 'text',
                                'text' => '恭喜! 您中了特獎!',
                               ]
                              ]
           ];
SendMsg($payload);


// 傳送訊息給指定使用者
function SendMsg($payload)
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/v2/bot/message/push');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
    curl_setopt($ch, CURLOPT_HTTPHEADER, [  'Content-Type: application/json',
                                            'Authorization: Bearer ' . $GLOBALS['channelAccessToken']
                                        ]);
    $result = curl_exec($ch);
    curl_close($ch);
    // 寫 log
    $sLogfile = 'log_'.date('Ymd').'.log';
    $fp = fopen($sLogfile, "a+");
    fwrite($fp , print_r(date('Y-m-d H:i:s').', send message result: '.$result."\n", true));
    fclose($fp);
}
?>
 

【LINE Messaging API】以 user id 查詢 user name

 
<php?
date_default_timezone_set('Asia/Taipei');
$sRootDir = $_SERVER['DOCUMENT_ROOT'];
$channelAccessToken = '您的 channel Access Token';
$bodyMsg = file_get_contents('php://input');

// LINE 不會幫我們記錄, 所以要自己寫 log
// 這段不是必要, 只是方便自己除錯
$sLogfile = 'log_'.date('Ymd').'.log';    // 指定 log 檔名, 一天一個檔, 檔名格式為: log_yyyymmdd.log
$fp = fopen($sLogfile, "a+");
fwrite($fp , print_r(date('Y-m-d H:i:s').', Recive: '.$bodyMsg."\n", true));
fclose($fp);

$obj = json_decode($bodyMsg, true);
$payload = null;
$event = null;

foreach ($obj['events'] as &$event)
{
    switch($event['type'])
    {
        // 如果您有在 LINE 官方帳號後台設定 歡迎詞, 
        // user 加入好友後也會收到
        case 'follow':
            // user 加入好友後, 程式自己要做的事
            $userId = $event['source']['userId'];
            $sUsername = getUsername($sUser);
            break;
            
        ...
        ...
    }        
}

function getUsername($user_id) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, "https://api.line.me/v2/bot/profile/$user_id");
    curl_setopt($ch, CURLOPT_HTTPHEADER, [  'Content-Type: application/json',
                                            'Authorization: Bearer ' . $GLOBALS['channelAccessToken']
                                        ]);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    $result = curl_exec($ch);
    curl_close($ch);

    if ($result===false) {
		// 查詢失敗, 
        return 'NG';
    }

    $data = json_decode($result, true);
    return $data["displayName"];
}
?>