diff --git a/.env b/.env
new file mode 100644
index 000000000..d4e31d19a
--- /dev/null
+++ b/.env
@@ -0,0 +1,10 @@
+DBDRIVER=mysql
+MYSQLHOST=localhost
+MYSQLUSER=root
+MYSQLPASSWORD=
+MYSQLDATABASE=mlite
+MYSQLPORT=3306
+APPURL=http://localhost:8000/uploads
+MULTIAPP=
+MULTIAPP_REDIRECT=
+DEV_MODE=false
diff --git a/.gitignore b/.gitignore
index e9fad2793..de38332b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.DS_Store
+.trae/
config.php
admin/tmp
admin/pla.php
@@ -7,11 +8,17 @@ websocket
uploads
vendor
__vendor
-systems/data/database.sdb
+app_amati/
+systems/data/*.sdb
webapps
backup/
plugins/apotek_online
plugins/simrsp
+# plugins/mini_pacs/*.sh
+# plugins/mini_pacs/*.json
+# plugins/mini_pacs/worklist_monitor.php
+# plugins/mini_pacs/receiver.php
+# plugins/mini_pacs/systemd/*
config.php
themes/mlite
systems/lib/jasperphp
@@ -22,7 +29,6 @@ plugins/lisensi
assets/easyui
plugins/desktop
plugins/sia
-plugins/crud_generator
plugins/kasir
plugins/dokter_radiologi
plugins/dokter_ulun
@@ -37,9 +43,18 @@ plugins/utd/Site.php
plugins/utd/view/adonan.html
websocket
plugins/website_ausy
+plugins/rsgmgha
+plugins/verifikasi_klaim
plugins/pasien_galleries
# plugins/log_antrian_taskid
+mobile-react/node_modules
+frontend-react/node_modules
+frontend-react/dist
+frontend-react/node_modules/*
+frontend-react/dist/*
+
+#docker/
docker/mysql*
docker/orthanc/orthanc-db/*
docker/orthanc/dummy-dicom/*
@@ -58,3 +73,4 @@ themes/atilamedika
.vercel
api/
docker/.env
+mobile-react/node_modules/.package-lock.json
\ No newline at end of file
diff --git a/.htaccess b/.htaccess
index 9728d1073..30c608345 100644
--- a/.htaccess
+++ b/.htaccess
@@ -2,7 +2,7 @@
Options -Indexes
# Prevent visitors from viewing files directly
-
+
Order allow,deny
Deny from all
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..481ba9ccf
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,25 @@
+# Changelog
+
+## [2026-04-07]
+
+### Added
+- VClaim: Tambah alur pembaruan tanggal-pulang SEP (endpoint baru `updatetglplg2`) + modal UI `plugins/vclaim/view/admin/sep_pulang.html`.
+- VClaim: Tambah endpoint `spridata` untuk mengambil detail SPRI berdasarkan `no_surat` (dipakai untuk memuat data ke form).
+- VClaim: Tambah aksi Update/Delete SPRI dari form SPRI (menggunakan endpoint update & delete VClaim).
+- VClaim: Tambah aksi Update/Delete Kontrol dari form Kontrol (menggunakan endpoint update & delete VClaim).
+- VClaim: Tambah menu SEP Internal di menu.
+
+### Changed
+- VClaim: Tambah tombol “Pulang” di riwayat SEP (ditampilkan untuk `jnsPelayanan == 1`) dan muat modal tanggal-pulang.
+- VClaim: Perbaiki alur input SEP (isi otomatis No. MR dari data peserta, buat otomatis No. Rujukan saat kosong, dan izinkan klik No. SEP di riwayat untuk mengisi No. Rujukan).
+- VClaim: Perbarui permintaan tanggal-pulang menggunakan HTTP `PUT` (bukan `DELETE`) dan terima nilai dari input.
+- VClaim: Tingkatkan UX SPRI (tambah field No Surat + mode Update/Delete setelah memilih item, dan buat baris No Rawat bisa diklik untuk memuat data ke form).
+
+### Fixed
+- VClaim: Perkuat penanganan pesan kesalahan saat respons BPJS null/tidak valid (fallback ke “Unknown error”).
+- VClaim: Perbaiki kesalahan kecil dan pengutipan token sesi di beberapa URL AJAX (mis. label “Faskes” dan pengutipan token).
+
+## [2026-04-08]
+
+### Changed
+- VClaim: Perubahan header dan body request pada aproval SEP
\ No newline at end of file
diff --git a/MLITE.postman_collection.json b/MLITE.postman_collection.json
index c03341ade..c0cead3d9 100644
--- a/MLITE.postman_collection.json
+++ b/MLITE.postman_collection.json
@@ -1,326 +1,7371 @@
{
- "info": {
- "name": "mLITE API",
- "description": "Collection for mLITE API",
- "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
- },
- "item": [
- {
- "name": "Login",
- "event": [
- {
- "listen": "test",
- "script": {
- "exec": [
- "var jsonData = pm.response.json();",
- "if (jsonData.token) {",
- " pm.collectionVariables.set(\"token\", jsonData.token);",
- "}"
- ],
- "type": "text/javascript"
- }
- }
- ],
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "Content-Type",
- "value": "application/json",
- "type": "text"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\n \"username\": \"{{username}}\",\n \"password\": \"{{password}}\"\n}"
- },
- "url": {
- "raw": "{{baseUrl}}/admin/api/login",
- "host": [
- "{{baseUrl}}"
- ],
- "path": [
- "admin",
- "api",
- "login"
- ]
- }
- },
- "response": []
- },
- {
- "name": "Pasien",
- "item": [
- {
- "name": "List Pasien",
- "request": {
- "method": "GET",
- "header": [
- {
- "key": "X-Api-Key",
- "value": "{{apiKey}}",
- "type": "text"
- },
- {
- "key": "Authorization",
- "value": "Bearer {{token}}",
- "type": "text"
- }
- ],
- "url": {
- "raw": "{{baseUrl}}/admin/api/pasien/list?page={{page}}&per_page={{per_page}}&s={{s}}",
- "host": [
- "{{baseUrl}}"
- ],
- "path": [
- "admin",
- "api",
- "pasien",
- "list"
- ],
- "query": [
- {
- "key": "page",
- "value": "{{page}}"
- },
- {
- "key": "per_page",
- "value": "{{per_page}}"
- },
- {
- "key": "s",
- "value": "{{s}}"
- }
- ]
- }
- },
- "response": []
- },
- {
- "name": "Show Pasien",
- "request": {
- "method": "GET",
- "header": [
- {
- "key": "X-Api-Key",
- "value": "{{apiKey}}",
- "type": "text"
- },
- {
- "key": "Authorization",
- "value": "Bearer {{token}}",
- "type": "text"
- }
- ],
- "url": {
- "raw": "{{baseUrl}}/admin/api/pasien/show/{{no_rkm_medis}}",
- "host": [
- "{{baseUrl}}"
- ],
- "path": [
- "admin",
- "api",
- "pasien",
- "show",
- "{{no_rkm_medis}}"
- ]
- }
- },
- "response": []
- },
- {
- "name": "Riwayat Perawatan",
- "request": {
- "method": "GET",
- "header": [
- {
- "key": "X-Api-Key",
- "value": "{{apiKey}}",
- "type": "text"
- },
- {
- "key": "Authorization",
- "value": "Bearer {{token}}",
- "type": "text"
- }
- ],
- "url": {
- "raw": "{{baseUrl}}/admin/api/pasien/riwayatperawatan/{{no_rkm_medis}}",
- "host": [
- "{{baseUrl}}"
- ],
- "path": [
- "admin",
- "api",
- "pasien",
- "riwayatperawatan",
- "{{no_rkm_medis}}"
- ]
- }
- },
- "response": []
- },
- {
- "name": "Create Pasien",
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "X-Api-Key",
- "value": "{{apiKey}}",
- "type": "text"
- },
- {
- "key": "Content-Type",
- "value": "application/json",
- "type": "text"
- },
- {
- "key": "Authorization",
- "value": "Bearer {{token}}",
- "type": "text"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\n \"nm_pasien\": \"Pasien Baru\",\n \"no_ktp\": \"1234567890123456\",\n \"jk\": \"L\",\n \"tgl_lahir\": \"1990-01-01\",\n \"alamat\": \"Jl. Contoh No. 1\",\n \"no_tlp\": \"08123456789\",\n \"kd_pj\": \"UMUM\",\n \"no_rkm_medis\": \"{{no_rkm_medis}}\"\n}"
- },
- "url": {
- "raw": "{{baseUrl}}/admin/api/pasien/create",
- "host": [
- "{{baseUrl}}"
- ],
- "path": [
- "admin",
- "api",
- "pasien",
- "create"
- ]
- }
- },
- "response": []
- },
- {
- "name": "Update Pasien",
- "request": {
- "method": "POST",
- "header": [
- {
- "key": "X-Api-Key",
- "value": "{{apiKey}}",
- "type": "text"
- },
- {
- "key": "Content-Type",
- "value": "application/json",
- "type": "text"
- },
- {
- "key": "Authorization",
- "value": "Bearer {{token}}",
- "type": "text"
- }
- ],
- "body": {
- "mode": "raw",
- "raw": "{\n \"nm_pasien\": \"Pasien Update\",\n \"alamat\": \"Jl. Update No. 2\"\n}"
- },
- "url": {
- "raw": "{{baseUrl}}/admin/api/pasien/update/{{no_rkm_medis}}",
- "host": [
- "{{baseUrl}}"
- ],
- "path": [
- "admin",
- "api",
- "pasien",
- "update",
- "{{no_rkm_medis}}"
- ]
- }
- },
- "response": []
- },
- {
- "name": "Delete Pasien",
- "request": {
- "method": "DELETE",
- "header": [
- {
- "key": "X-Api-Key",
- "value": "{{apiKey}}",
- "type": "text"
- },
- {
- "key": "Authorization",
- "value": "Bearer {{token}}",
- "type": "text"
- }
- ],
- "url": {
- "raw": "{{baseUrl}}/admin/api/pasien/delete/{{no_rkm_medis}}",
- "host": [
- "{{baseUrl}}"
- ],
- "path": [
- "admin",
- "api",
- "pasien",
- "delete",
- "{{no_rkm_medis}}"
- ]
- }
- },
- "response": []
- }
- ]
- }
- ],
- "variable": [
- {
- "key": "baseUrl",
- "value": "http://mlite.loc",
- "type": "string"
- },
- {
- "key": "apiKey",
- "value": "YOUR_API_KEY_HERE",
- "type": "string"
- },
- {
- "key": "username",
- "value": "admin",
- "type": "string"
- },
- {
- "key": "password",
- "value": "",
- "type": "string"
- },
- {
- "key": "token",
- "value": "",
- "type": "string"
- },
- {
- "key": "s",
- "value": "",
- "type": "string"
- },
- {
- "key": "page",
- "value": "1",
- "type": "string"
- },
- {
- "key": "per_page",
- "value": "10",
- "type": "string"
- },
- {
- "key": "no_rkm_medis",
- "value": "000001",
- "type": "string"
- }
- ]
-}
\ No newline at end of file
+ "info": {
+ "name": "mLITE API",
+ "description": "Collection for mLITE API",
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ },
+ "item": [
+ {
+ "name": "Login",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "var jsonData = pm.response.json();",
+ "if (jsonData.token) {",
+ " pm.collectionVariables.set(\"token\", jsonData.token);",
+ "}"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text",
+ "description": "API Key wajib disertakan untuk validasi akses Layer 1"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"username\": \"{{username}}\",\n \"password\": \"{{password}}\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/login",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "login"
+ ]
+ },
+ "description": "Mendapatkan Token JWT untuk Frontend/Mobile App. \n\nKeamanan Layer 1: Aplikasi diverifikasi via X-Api-Key.\nKeamanan Layer 2: End-User diverifikasi via Username & Password di body.\n(API Key bebas digunakan untuk meloginkan End-User mana saja sesuai tabel mlite_users)."
+ },
+ "response": []
+ },
+ {
+ "name": "Dashboard",
+ "item": [
+ {
+ "name": "Doctor Dashboard",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/dashboard/stats",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "dashboard",
+ "stats"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Pasien",
+ "item": [
+ {
+ "name": "List Pasien",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/pasien/list?page={{page}}&per_page={{per_page}}&s={{s}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "pasien",
+ "list"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "{{page}}"
+ },
+ {
+ "key": "per_page",
+ "value": "{{per_page}}"
+ },
+ {
+ "key": "s",
+ "value": "{{s}}"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Pasien",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/pasien/show/{{no_rkm_medis}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "pasien",
+ "show",
+ "{{no_rkm_medis}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Riwayat Perawatan",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/pasien/riwayatperawatan/{{no_rkm_medis}}?no_rawat={{no_rawat}}&detail=false",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "pasien",
+ "riwayatperawatan",
+ "{{no_rkm_medis}}"
+ ],
+ "query": [
+ {
+ "key": "no_rawat",
+ "value": "{{no_rawat}}"
+ },
+ {
+ "key": "detail",
+ "value": "false"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create Pasien",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"nm_pasien\": \"Pasien Baru\",\n \"no_ktp\": \"1234567890123456\",\n \"jk\": \"L\",\n \"tgl_lahir\": \"1990-01-01\",\n \"alamat\": \"Jl. Contoh No. 1\",\n \"no_tlp\": \"08123456789\",\n \"kd_pj\": \"UMUM\",\n \"no_rkm_medis\": \"{{no_rkm_medis}}\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/pasien/create",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "pasien",
+ "create"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Update Pasien",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"nm_pasien\": \"Pasien Update\",\n \"alamat\": \"Jl. Update No. 2\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/pasien/update/{{no_rkm_medis}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "pasien",
+ "update",
+ "{{no_rkm_medis}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Pasien",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/pasien/delete/{{no_rkm_medis}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "pasien",
+ "delete",
+ "{{no_rkm_medis}}"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Rawat Jalan",
+ "item": [
+ {
+ "name": "List Rawat Jalan",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/list?draw=1&start=0&length=10&tgl_awal=2025-01-01&tgl_akhir=2025-12-31",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "list"
+ ],
+ "query": [
+ {
+ "key": "draw",
+ "value": "1"
+ },
+ {
+ "key": "start",
+ "value": "0"
+ },
+ {
+ "key": "length",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2025-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2025-12-31"
+ },
+ {
+ "key": "search",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Rawat Jalan",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/show/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "show",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create Rawat Jalan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rkm_medis\": \"000001\",\n \"kd_poli\": \"IGD\",\n \"kd_dokter\": \"D00001\",\n \"kd_pj\": \"UMU\",\n \"tgl_registrasi\": \"2025-01-01\",\n \"jam_reg\": \"08:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/create",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "create"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Update Rawat Jalan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kd_poli\": \"POLI01\",\n \"kd_dokter\": \"D00002\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/update/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "update",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Rawat Jalan",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/delete/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "delete",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show SOAP Rawat Jalan",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/showsoap/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "showsoap",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save SOAP Rawat Jalan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\",\n \"suhu_tubuh\": \"36.5\",\n \"tensi\": \"120/80\",\n \"nadi\": \"80\",\n \"respirasi\": \"20\",\n \"tinggi\": \"170\",\n \"berat\": \"60\",\n \"gcs\": \"E4V5M6\",\n \"keluhan\": \"Demam\",\n \"pemeriksaan\": \"Suhu badan tinggi\",\n \"alergi\": \"-\",\n \"lingkar_perut\": \"80\",\n \"rtl\": \"Istirahat\",\n \"penilaian\": \"Demam berdarah\",\n \"instruksi\": \"Minum obat\",\n \"evaluasi\": \"Baik\",\n \"nip\": \"D00001\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/savesoap",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "savesoap"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete SOAP Rawat Jalan",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/deletesoap",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "deletesoap"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Detail Rawat Jalan",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/showdetail/tindakan/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "showdetail",
+ "tindakan",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Detail Rawat Jalan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kat\": \"tindakan\",\n \"jml_tindakan\": \"1\",\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_jenis_prw\": \"T001\",\n \"provider\": \"rawat_jl_dr\",\n \"kode_provider\": \"D00001\",\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/savedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "savedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Detail Rawat Jalan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kat\": \"tindakan\",\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_jenis_prw\": \"T001\",\n \"provider\": \"rawat_jl_dr\",\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/deletedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "deletedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Diagnosa",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kd_penyakit\": \"A00.0\",\n \"status\": \"Ralan\",\n \"prioritas\": 1\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/savediagnosa",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "savediagnosa"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Diagnosa",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kd_penyakit\": \"A00.0\",\n \"prioritas\": 1\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/deletediagnosa",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "deletediagnosa"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Prosedur",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kode\": \"89.01\",\n \"status\": \"Ralan\",\n \"prioritas\": 1\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/saveprosedur",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "saveprosedur"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Prosedur",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kode\": \"89.01\",\n \"prioritas\": 1\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/deleteprosedur",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "deleteprosedur"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Catatan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"catatan\": \"Pasien perlu istirahat cukup\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/savecatatan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "savecatatan"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Catatan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"catatan\": \"Pasien perlu istirahat cukup\",\n \"tanggal\": \"2025-01-01\",\n \"jam\": \"12:00:00\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/deletecatatan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "deletecatatan"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Berkas",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"judul\": \"Hasil Lab Luar\",\n \"deskripsi\": \"http://link-to-file.com/file.pdf\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/saveberkas",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "saveberkas"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Berkas",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"judul\": \"Hasil Lab Luar\",\n \"deskripsi\": \"http://link-to-file.com/file.pdf\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/deleteberkas",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "deleteberkas"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Resume",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"diagnosa_utama\": \"Demam Berdarah\",\n \"diagnosa_sekunder\": \"Tifus\",\n \"jalannya_penyakit\": \"Demam 3 hari...\",\n \"terapi\": \"Paracetamol 3x1\",\n \"kondisi_pulang\": \"Hidup\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/saveresume",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "saveresume"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Resume",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kd_dokter\": \"D001\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/deleteresume",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "deleteresume"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Rujukan Internal",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kd_poli\": \"P001\",\n \"kd_dokter\": \"D001\",\n \"catatan\": \"Mohon konsul bedah\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/saverujukaninternal",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "saverujukaninternal"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Rujukan Internal",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kd_poli\": \"P001\",\n \"kd_dokter\": \"D001\",\n \"catatan\": \"Mohon konsul bedah\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/deleterujukaninternal",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "deleterujukaninternal"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Laporan Operasi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"tanggal\": \"2023-01-01\",\n \"jam\": \"10:00:00\",\n \"operator\": \"D001\",\n \"laporan\": \"Operasi berjalan lancar\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/savelaporanoperasi",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "savelaporanoperasi"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Laporan Operasi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"tanggal\": \"2023-01-01\",\n \"jam\": \"10:00:00\",\n \"operator\": \"D001\",\n \"laporan\": \"Operasi berjalan lancar\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_jalan/deletelaporanoperasi",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_jalan",
+ "deletelaporanoperasi"
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Website",
+ "item": [
+ {
+ "name": "List Website News",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/website/list?draw=1&start=0&length=10&search=",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "website",
+ "list"
+ ],
+ "query": [
+ {
+ "key": "draw",
+ "value": "1"
+ },
+ {
+ "key": "start",
+ "value": "0"
+ },
+ {
+ "key": "length",
+ "value": "10"
+ },
+ {
+ "key": "search",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Website News",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/website/show/{{news_id}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "website",
+ "show",
+ "{{news_id}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create Website News",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"title\": \"Judul Berita API\",\n \"content\": \"Isi berita dari API\",\n \"intro\": \"Ringkasan berita\",\n \"status\": 2,\n \"comments\": 1,\n \"markdown\": 0,\n \"published_at\": \"2026-05-01 10:00:00\",\n \"tags\": [\"api\", \"website\"]\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/website/create",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "website",
+ "create"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Update Website News",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"title\": \"Judul Berita API (Update)\",\n \"intro\": \"Ringkasan update\",\n \"status\": 2,\n \"tags\": [\"api\", \"update\"]\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/website/update/{{news_id}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "website",
+ "update",
+ "{{news_id}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Website News",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/website/delete/{{news_id}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "website",
+ "delete",
+ "{{news_id}}"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Presensi",
+ "item": [
+ {
+ "name": "List Presensi",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/presensi/list",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "presensi",
+ "list"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Show Presensi",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/presensi/show?tanggal={{tanggal}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "presensi",
+ "show"
+ ],
+ "query": [
+ {
+ "key": "tanggal",
+ "value": "{{tanggal}}"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Clock In",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"shift\": \"Pagi\",\n \"photo\": \"\",\n \"lat\": \"-6.200000\",\n \"lng\": \"106.816666\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/presensi/create",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "presensi",
+ "create"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Clock Out",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/presensi/update",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "presensi",
+ "update"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Presensi",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"jam_datang\": \"2023-01-01 08:00:00\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/presensi/delete",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "presensi",
+ "delete"
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "IGD",
+ "item": [
+ {
+ "name": "List IGD",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/list?tgl_awal={{tgl_awal}}&tgl_akhir={{tgl_akhir}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "list"
+ ],
+ "query": [
+ {
+ "key": "draw",
+ "value": "1"
+ },
+ {
+ "key": "start",
+ "value": "0"
+ },
+ {
+ "key": "length",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2025-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2025-12-31"
+ },
+ {
+ "key": "search",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Detail IGD",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/show/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "show",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create IGD",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rkm_medis\": \"123456\",\n \"kd_poli\": \"IGD\",\n \"kd_dokter\": \"D001\",\n \"kd_pj\": \"A01\",\n \"tgl_registrasi\": \"2024-01-01\",\n \"jam_reg\": \"08:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/create",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "create"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Update IGD",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"stts\": \"Sudah\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/update/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "update",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete IGD",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/delete/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "delete",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show SOAP",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/showsoap/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "showsoap",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save SOAP",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_perawatan\": \"2024-01-01\",\n \"jam_rawat\": \"10:00:00\",\n \"suhu_tubuh\": \"36.5\",\n \"tensi\": \"120/80\",\n \"nadi\": \"80\",\n \"respirasi\": \"20\",\n \"tinggi\": \"170\",\n \"berat\": \"65\",\n \"gcs\": \"E4V5M6\",\n \"keluhan\": \"Sakit kepala\",\n \"pemeriksaan\": \"Keadaan umum baik\",\n \"alergi\": \"-\",\n \"lingkar_perut\": \"-\",\n \"rtl\": \"Istirahat\",\n \"penilaian\": \"-\",\n \"instruksi\": \"-\",\n \"evaluasi\": \"-\",\n \"nip\": \"-\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/savesoap",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "savesoap"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete SOAP",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_perawatan\": \"2024-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/deletesoap",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "deletesoap"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Tindakan",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/showdetail/tindakan/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "showdetail",
+ "tindakan",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Tindakan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kat\": \"tindakan\",\n \"jml_tindakan\": \"1\",\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_perawatan\": \"2024-01-01\",\n \"jam_rawat\": \"10:00:00\",\n \"kd_jenis_prw\": \"T001\",\n \"provider\": \"rawat_jl_dr\",\n \"kode_provider\": \"D001\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/savedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "savedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Tindakan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"provider\": \"rawat_jl_dr\",\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_jenis_prw\": \"T001\",\n \"tgl_perawatan\": \"2024-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/deletedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "deletedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Obat",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/showdetail/obat/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "showdetail",
+ "obat",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Obat",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kat\": \"obat\",\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_perawatan\": \"2024-01-01\",\n \"jam_rawat\": \"10:00:00\",\n \"kd_jenis_prw\": \"OB001\",\n \"jml\": \"10\",\n \"aturan_pakai\": \"3x1\",\n \"kode_provider\": \"D001\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/savedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "savedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Obat",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"kode_brng\": \"OB001\",\n \"tgl_peresepan\": \"2024-01-01\",\n \"jam_peresepan\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/deletedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "deletedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Diagnosa",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_penyakit\": \"A00.1\",\n \"status\": \"Ralan\",\n \"prioritas\": \"1\",\n \"status_penyakit\": \"Lama\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/savediagnosa",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "savediagnosa"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Diagnosa",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_penyakit\": \"A00.1\",\n \"prioritas\": \"1\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/deletediagnosa",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "deletediagnosa"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Prosedur",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kode\": \"89.01\",\n \"status\": \"Ralan\",\n \"prioritas\": 1\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/saveprosedur",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "saveprosedur"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Prosedur",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kode\": \"89.01\",\n \"prioritas\": 1\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/deleteprosedur",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "deleteprosedur"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Catatan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"catatan\": \"Pasien perlu istirahat cukup\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/savecatatan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "savecatatan"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Catatan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"catatan\": \"Pasien perlu istirahat cukup\",\n \"tanggal\": \"2025-01-01\",\n \"jam\": \"12:00:00\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/deletecatatan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "deletecatatan"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Berkas",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"judul\": \"Hasil Lab Luar\",\n \"deskripsi\": \"http://link-to-file.com/file.pdf\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/saveberkas",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "saveberkas"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Berkas",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"judul\": \"Hasil Lab Luar\",\n \"deskripsi\": \"http://link-to-file.com/file.pdf\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/deleteberkas",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "deleteberkas"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Resume",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"diagnosa_utama\": \"Demam Berdarah\",\n \"diagnosa_sekunder\": \"Tifus\",\n \"jalannya_penyakit\": \"Demam 3 hari...\",\n \"terapi\": \"Paracetamol 3x1\",\n \"kondisi_pulang\": \"Hidup\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/saveresume",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "saveresume"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Laporan Operasi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"tanggal\": \"2023-01-01\",\n \"jam\": \"10:00:00\",\n \"operator\": \"D001\",\n \"laporan\": \"Operasi berjalan lancar\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/savelaporanoperasi",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "savelaporanoperasi"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Laporan Operasi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"tanggal\": \"2023-01-01\",\n \"jam\": \"10:00:00\",\n \"operator\": \"D001\",\n \"laporan\": \"Operasi berjalan lancar\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/igd/deletelaporanoperasi",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "igd",
+ "deletelaporanoperasi"
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Rawat Inap",
+ "item": [
+ {
+ "name": "List Rawat Inap",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/list?draw=1&start=0&length=10&tgl_awal=2025-01-01&tgl_akhir=2025-12-31",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "list"
+ ],
+ "query": [
+ {
+ "key": "draw",
+ "value": "1"
+ },
+ {
+ "key": "start",
+ "value": "0"
+ },
+ {
+ "key": "length",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2025-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2025-12-31"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Rawat Inap",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/show/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "show",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create Rawat Inap",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_kamar\": \"K001\",\n \"kd_dokter\": \"D00001\",\n \"lama\": \"1\",\n \"diagnosa_awal\": \"Demam\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/create",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "create"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Update Rawat Inap",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kd_kamar\": \"K002\",\n \"stts_pulang\": \"-\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/update/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "update",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Rawat Inap",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/delete/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "delete",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Detail Kamar Inap",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kat\": \"tindakan\",\n \"jml_tindakan\": \"1\",\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_jenis_prw\": \"T001\",\n \"provider\": \"rawat_inap_dr\",\n \"kode_provider\": \"D00001\",\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/savedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "savedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Detail Kamar Inap",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/showdetail/tindakan/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "showdetail",
+ "tindakan",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Detail Kamar Inap",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kat\": \"tindakan\",\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_jenis_prw\": \"T001\",\n \"provider\": \"rawat_inap_dr\",\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/deletedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "deletedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show SOAP Kamar Inap",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/showsoap/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "showsoap",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save SOAP Kamar Inap",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_perawatan\": \"2025-12-29\",\n \"jam_rawat\": \"18:30:00\",\n \"suhu_tubuh\": \"36\",\n \"tensi\": \"120/80\",\n \"nadi\": \"80\",\n \"respirasi\": \"20\",\n \"tinggi\": \"170\",\n \"berat\": \"60\",\n \"spo2\": \"98\",\n \"gcs\": \"12\",\n \"kesadaran\": \"Sopor\",\n \"keluhan\": \"Demam\",\n \"pemeriksaan\": \"Suhu badan tinggi\",\n \"alergi\": \"-\",\n \"rtl\": \"Istirahat\",\n \"instruksi\": \"Minum obat\",\n \"evaluasi\": \"Baik\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/savesoap",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "savesoap"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete SOAP Kamar Inap",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/deletesoap",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "deletesoap"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Diagnosa",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kd_penyakit\": \"A00.0\",\n \"status\": \"Ranap\",\n \"prioritas\": 1\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/savediagnosa",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "savediagnosa"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Diagnosa",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kd_penyakit\": \"A00.0\",\n \"prioritas\": 1\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/deletediagnosa",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "deletediagnosa"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Prosedur",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kode\": \"89.01\",\n \"status\": \"Ranap\",\n \"prioritas\": 1\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/saveprosedur",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "saveprosedur"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Prosedur",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"kode\": \"89.01\",\n \"prioritas\": 1\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/deleteprosedur",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "deleteprosedur"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Catatan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"catatan\": \"Pasien perlu istirahat cukup\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/savecatatan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "savecatatan"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Catatan",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"catatan\": \"Pasien perlu istirahat cukup\",\n \"tanggal\": \"2025-01-01\",\n \"jam\": \"12:00:00\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/deletecatatan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "deletecatatan"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Berkas",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"judul\": \"Hasil Lab Luar\",\n \"deskripsi\": \"http://link-to-file.com/file.pdf\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/saveberkas",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "saveberkas"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Berkas",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"judul\": \"Hasil Lab Luar\",\n \"deskripsi\": \"http://link-to-file.com/file.pdf\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/deleteberkas",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "deleteberkas"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Resume",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"diagnosa_utama\": \"Demam Berdarah\",\n \"diagnosa_sekunder\": \"Tifus\",\n \"jalannya_penyakit\": \"Demam 3 hari...\",\n \"terapi\": \"Paracetamol 3x1\",\n \"kondisi_pulang\": \"Hidup\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/saveresume",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "saveresume"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Save Laporan Operasi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"tanggal\": \"2023-01-01\",\n \"jam\": \"10:00:00\",\n \"operator\": \"D001\",\n \"laporan\": \"Operasi berjalan lancar\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/savelaporanoperasi",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "savelaporanoperasi"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Delete Laporan Operasi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"2023/01/01/000001\",\n \"tanggal\": \"2023-01-01\",\n \"jam\": \"10:00:00\",\n \"operator\": \"D001\",\n \"laporan\": \"Operasi berjalan lancar\"\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/rawat_inap/deletelaporanoperasi",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "rawat_inap",
+ "deletelaporanoperasi"
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Laboratorium",
+ "item": [
+ {
+ "name": "List Laboratorium",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/list?draw=1&start=0&length=10&tgl_awal=2025-01-01&tgl_akhir=2025-12-31",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "list"
+ ],
+ "query": [
+ {
+ "key": "draw",
+ "value": "1"
+ },
+ {
+ "key": "start",
+ "value": "0"
+ },
+ {
+ "key": "length",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2025-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2025-12-31"
+ },
+ {
+ "key": "search",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Laboratorium",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/show/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "show",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create Laboratorium",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rkm_medis\": \"000001\",\n \"tgl_registrasi\": \"2025-01-01\",\n \"jam_reg\": \"08:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/save",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "save"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Update Laboratorium",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kd_dokter\": \"D00002\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/update/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "update",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Laboratorium",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/delete/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "delete",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Detail Laboratorium",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kat\": \"laboratorium\",\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_jenis_prw\": \"PK0001\",\n \"jml_tindakan\": 1,\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\",\n \"kode_provider\": \"D00001\",\n \"status\": \"Ralan\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/savedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "savedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Detail Laboratorium",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_jenis_prw\": \"PK0001\",\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/deletedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "deletedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Detail Laboratorium",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/showdetail/{{status}}/{{no_rawat}}/{{noorder}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "showdetail",
+ "{{status}}",
+ "{{no_rawat}}",
+ "{{noorder}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Nilai Laboratorium",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_periksa\": \"2025-01-01\",\n \"jam\": \"10:00:00\",\n \"id_template\": \"1\",\n \"value\": \"Normal\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/savenilai",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "savenilai"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Keterangan Laboratorium",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_periksa\": \"2025-01-01\",\n \"jam\": \"10:00:00\",\n \"id_template\": \"1\",\n \"value\": \"Catat tambahan\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/saveketerangan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "saveketerangan"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Validasi Laboratorium",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"noorder\": \"PR0001\",\n \"tgl_permintaan\": \"2025-01-01\",\n \"jam_permintaan\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/laboratorium/savevalidasi",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "laboratorium",
+ "savevalidasi"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Radiologi",
+ "item": [
+ {
+ "name": "List Radiologi",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/list?draw=1&start=0&length=10&tgl_awal=2025-01-01&tgl_akhir=2025-12-31",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "list"
+ ],
+ "query": [
+ {
+ "key": "draw",
+ "value": "1"
+ },
+ {
+ "key": "start",
+ "value": "0"
+ },
+ {
+ "key": "length",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2025-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2025-12-31"
+ },
+ {
+ "key": "search",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Radiologi",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/show/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "show",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create Radiologi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rkm_medis\": \"000001\",\n \"tgl_registrasi\": \"2025-01-01\",\n \"jam_reg\": \"08:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/save",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "save"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Update Radiologi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kd_dokter\": \"D00002\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/update/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "update",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Radiologi",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/delete/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "delete",
+ "{{no_rawat}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Detail Radiologi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kat\": \"radiologi\",\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_jenis_prw\": \"PR0001\",\n \"jml_tindakan\": 1,\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\",\n \"kode_provider\": \"D00001\",\n \"status\": \"Ralan\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/savedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "savedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Detail Radiologi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"kd_jenis_prw\": \"PR0001\",\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/deletedetail",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "deletedetail"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Detail Radiologi",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/showdetail/{{status}}/{{no_rawat}}/{{noorder}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "showdetail",
+ "{{status}}",
+ "{{no_rawat}}",
+ "{{noorder}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Hasil Radiologi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_periksa\": \"2025-01-01\",\n \"jam\": \"10:00:00\",\n \"hasil\": \"Tampak normal batas jantung dan paru\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/savehasil",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "savehasil"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Hasil Radiologi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"tgl_perawatan\": \"2025-01-01\",\n \"jam_rawat\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/deletehasil",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "deletehasil"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Validasi Radiologi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"noorder\": \"PR0001\",\n \"tgl_permintaan\": \"2025-01-01\",\n \"jam_permintaan\": \"10:00:00\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/radiologi/savevalidasi",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "radiologi",
+ "savevalidasi"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Vedika",
+ "item": [
+ {
+ "name": "List Vedika",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/vedika/list?draw=1&start=0&length=10&tgl_awal=2025-01-01&tgl_akhir=2025-12-31&type=ralan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "vedika",
+ "list"
+ ],
+ "query": [
+ {
+ "key": "draw",
+ "value": "1"
+ },
+ {
+ "key": "start",
+ "value": "0"
+ },
+ {
+ "key": "length",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2025-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2025-12-31"
+ },
+ {
+ "key": "type",
+ "value": "ralan",
+ "description": "ralan / ranap"
+ },
+ {
+ "key": "search",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "List Vedika Lengkap",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/vedika/lengkap?draw=1&start=0&length=10&tgl_awal=2025-01-01&tgl_akhir=2025-12-31&type=ralan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "vedika",
+ "lengkap"
+ ],
+ "query": [
+ {
+ "key": "draw",
+ "value": "1"
+ },
+ {
+ "key": "start",
+ "value": "0"
+ },
+ {
+ "key": "length",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2025-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2025-12-31"
+ },
+ {
+ "key": "type",
+ "value": "ralan",
+ "description": "ralan / ranap"
+ },
+ {
+ "key": "search",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "List Vedika Pengajuan",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/vedika/pengajuan?draw=1&start=0&length=10&tgl_awal=2025-01-01&tgl_akhir=2025-12-31&type=ralan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "vedika",
+ "pengajuan"
+ ],
+ "query": [
+ {
+ "key": "draw",
+ "value": "1"
+ },
+ {
+ "key": "start",
+ "value": "0"
+ },
+ {
+ "key": "length",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2025-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2025-12-31"
+ },
+ {
+ "key": "type",
+ "value": "ralan",
+ "description": "ralan / ranap"
+ },
+ {
+ "key": "search",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "List Vedika Perbaikan",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/vedika/perbaikan?draw=1&start=0&length=10&tgl_awal=2025-01-01&tgl_akhir=2025-12-31&type=ralan",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "vedika",
+ "perbaikan"
+ ],
+ "query": [
+ {
+ "key": "draw",
+ "value": "1"
+ },
+ {
+ "key": "start",
+ "value": "0"
+ },
+ {
+ "key": "length",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2025-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2025-12-31"
+ },
+ {
+ "key": "type",
+ "value": "ralan",
+ "description": "ralan / ranap"
+ },
+ {
+ "key": "search",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Vedika",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/vedika/show/{{nosep}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "vedika",
+ "show",
+ "{{nosep}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create Vedika",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"nosep\": \"00001\",\n \"no_rawat\": \"2023/01/01/000001\",\n \"no_rkm_medis\": \"000001\",\n \"catatan\": \"Berkas lengkap\",\n \"status\": \"Verifikasi\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/vedika/create",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "vedika",
+ "create"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Update Vedika",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"catatan\": \"Revisi berkas\",\n \"status\": \"Pending\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/vedika/update/{{nosep}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "vedika",
+ "update",
+ "{{nosep}}"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Vedika",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/vedika/delete/{{nosep}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "vedika",
+ "delete",
+ "{{nosep}}"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Master",
+ "description": "\n\n#### Modul Master menyediakan endpoint list/simpan/hapus data master.\n\n### Endpoint\n- **List Data (GET)**: `{{baseUrl}}/admin/api/master/list/{table}`\n- **Simpan Data (POST)**: `{{baseUrl}}/admin/api/master/save/{table}`\n- **Hapus Data (DELETE)**: `{{baseUrl}}/admin/api/master/delete/{table}`\n\n### Perilaku Endpoint\n- `list/{table}`: bersifat generic untuk tabel yang ada di database (kecuali tabel sensitif yang diblokir).\n- `save/{table}` dan `delete/{table}`: hanya bisa untuk tabel yang sudah terdaftar handler-nya di modul `plugins/master`.\n- Jika tabel belum didukung handler, API mengembalikan: `Table not allowed or not found`.\n\n### Parameter Pencarian (GET)\n- `page`: Nomor halaman (default: 1)\n- `per_page`: Jumlah record per halaman (default: 10)\n- `s`: Kata kunci pencarian (string)\n- `col`: Nama kolom spesifik untuk pencarian (opsional)\n\n### Catatan Keamanan\n- Tabel sensitif seperti `mlite_api_key` diblokir.\n\n### Contoh `{table}`\n- Data dasar: `dokter`, `petugas`, `poliklinik`, `bangsal`, `kamar`, `databarang`\n- Layanan: `jns_perawatan`, `jns_perawatan_inap`, `jns_perawatan_lab`, `jns_perawatan_radiologi`\n- Biodata: `bahasa`, `suku_bangsa`, `cacat_fisik`, `pendidikan`, `personal_pasien`\n- Diagnosis/prosedur: `penyakit` (ICD-10), `icd9` (ICD-9)",
+ "item": [
+ {
+ "name": "List Master Data",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/master/list/dokter?page={{page}}&per_page={{per_page}}&s={{s}}&col=",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "master",
+ "list",
+ "dokter"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "{{page}}"
+ },
+ {
+ "key": "per_page",
+ "value": "{{per_page}}"
+ },
+ {
+ "key": "s",
+ "value": "{{s}}"
+ },
+ {
+ "key": "col",
+ "value": ""
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Create Master Data",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kd_dokter\": \"D00001\",\n \"nm_dokter\": \"Dr. Contoh\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/master/save/dokter",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "master",
+ "save",
+ "dokter"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete Master Data",
+ "request": {
+ "method": "DELETE",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"kd_dokter\": \"D00001\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/master/delete/dokter",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "master",
+ "delete",
+ "dokter"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Apotek Ralan",
+ "item": [
+ {
+ "name": "Resep List",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ralan/reseplist?page={{page}}&per_page={{per_page}}&s={{s}}&tgl_awal={{tgl_awal}}&tgl_akhir={{tgl_akhir}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ralan",
+ "reseplist"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "{{page}}"
+ },
+ {
+ "key": "per_page",
+ "value": "{{per_page}}"
+ },
+ {
+ "key": "s",
+ "value": "{{s}}"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "{{tgl_awal}}"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "{{tgl_akhir}}"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Detail",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ralan/showdetail/{{category}}/{{no_rawat}}?no_resep={{no_resep}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ralan",
+ "showdetail",
+ "{{category}}",
+ "{{no_rawat}}"
+ ],
+ "query": [
+ {
+ "key": "no_resep",
+ "value": "{{no_resep}}"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Validasi Resep",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ralan/validasi/{{no_rawat}}?no_resep={{no_resep}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ralan",
+ "validasi",
+ "{{no_rawat}}"
+ ],
+ "query": [
+ {
+ "key": "no_resep",
+ "value": "{{no_resep}}"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Validasi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"no_resep\": \"{{no_resep}}\",\n \"jumlah\": {},\n \"aturan_pakai\": {}\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ralan/savevalidasi",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ralan",
+ "savevalidasi"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Simpan Obat Resep",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_resep\": \"{{no_resep}}\",\n \"kode_brng\": \"{{kode_brng}}\",\n \"jml\": \"10\",\n \"aturan_pakai\": \"3x1\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ralan/simpanobatresep",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ralan",
+ "simpanobatresep"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Simpan Racikan Resep",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_resep\": \"{{no_resep}}\",\n \"no_racik\": \"1\",\n \"nama_racik\": \"Racikan 1\",\n \"kd_racik\": \"R01\",\n \"jml_dr\": \"10\",\n \"aturan_pakai\": \"3x1\",\n \"keterangan\": \"-\",\n \"items\": [\n {\n \"kode_brng\": \"B01\",\n \"kandungan\": \"10mg\",\n \"jml\": \"5\"\n }\n ]\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ralan/simpanracikanresep",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ralan",
+ "simpanracikanresep"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Apotek Ranap",
+ "item": [
+ {
+ "name": "Resep List",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ranap/reseplist?page={{page}}&per_page={{per_page}}&s={{s}}&tgl_awal={{tgl_awal}}&tgl_akhir={{tgl_akhir}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ranap",
+ "reseplist"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "{{page}}"
+ },
+ {
+ "key": "per_page",
+ "value": "{{per_page}}"
+ },
+ {
+ "key": "s",
+ "value": "{{s}}"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "{{tgl_awal}}"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "{{tgl_akhir}}"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Show Detail",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ranap/showdetail/{{category}}/{{no_rawat}}?no_resep={{no_resep}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ranap",
+ "showdetail",
+ "{{category}}",
+ "{{no_rawat}}"
+ ],
+ "query": [
+ {
+ "key": "no_resep",
+ "value": "{{no_resep}}"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Validasi Resep",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ranap/validasi/{{no_rawat}}?no_resep={{no_resep}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ranap",
+ "validasi",
+ "{{no_rawat}}"
+ ],
+ "query": [
+ {
+ "key": "no_resep",
+ "value": "{{no_resep}}"
+ }
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Validasi",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_rawat\": \"{{no_rawat}}\",\n \"no_resep\": \"{{no_resep}}\",\n \"jumlah\": {},\n \"aturan_pakai\": {}\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ranap/savevalidasi",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ranap",
+ "savevalidasi"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Simpan Obat Resep",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_resep\": \"{{no_resep}}\",\n \"kode_brng\": \"{{kode_brng}}\",\n \"jml\": \"10\",\n \"aturan_pakai\": \"3x1\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ranap/simpanobatresep",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ranap",
+ "simpanobatresep"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Simpan Racikan Resep",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"no_resep\": \"{{no_resep}}\",\n \"no_racik\": \"1\",\n \"nama_racik\": \"Racikan 1\",\n \"kd_racik\": \"R01\",\n \"jml_dr\": \"10\",\n \"aturan_pakai\": \"3x1\",\n \"keterangan\": \"-\",\n \"items\": [\n {\n \"kode_brng\": \"B01\",\n \"kandungan\": \"10mg\",\n \"jml\": \"5\"\n }\n ]\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/apotek_ranap/simpanracikanresep",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "apotek_ranap",
+ "simpanracikanresep"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Kasir",
+ "item": [
+ {
+ "name": "Billing List (Rawat Jalan)",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/kasir_rawat_jalan/billinglist?page=1&per_page=10&tgl_awal=2024-01-01&tgl_akhir=2024-12-31",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "kasir_rawat_jalan",
+ "billinglist"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "1"
+ },
+ {
+ "key": "per_page",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2024-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2024-12-31"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Billing Detail (Rawat Jalan)",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/kasir_rawat_jalan/billingdetail/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "kasir_rawat_jalan",
+ "billingdetail",
+ "{{no_rawat}}"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Billing (Rawat Jalan)",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/kasir_rawat_jalan/billing/{{no_rawat}}?all=false",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "kasir_rawat_jalan",
+ "billing",
+ "{{no_rawat}}"
+ ],
+ "query": [
+ {
+ "key": "all",
+ "value": "false",
+ "description": "true/false"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Billing List (Rawat Inap)",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/kasir_rawat_inap/billinglist?page=1&per_page=10&tgl_awal=2024-01-01&tgl_akhir=2024-12-31",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "kasir_rawat_inap",
+ "billinglist"
+ ],
+ "query": [
+ {
+ "key": "page",
+ "value": "1"
+ },
+ {
+ "key": "per_page",
+ "value": "10"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2024-01-01"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2024-12-31"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "name": "Billing Detail (Rawat Inap)",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/kasir_rawat_inap/billingdetail/{{no_rawat}}",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "kasir_rawat_inap",
+ "billingdetail",
+ "2024/01/01/000001"
+ ]
+ }
+ }
+ },
+ {
+ "name": "Billing (Rawat Inap)",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Username-Permission",
+ "value": "{{username_permission}}",
+ "type": "text"
+ },
+ {
+ "key": "X-Password-Permission",
+ "value": "{{password_permission}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/kasir_rawat_inap/billing/{{no_rawat}}?all=false",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "kasir_rawat_inap",
+ "billing",
+ "{{no_rawat}}"
+ ],
+ "query": [
+ {
+ "key": "all",
+ "value": "false",
+ "description": "true/false"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {
+ "name": "Users",
+ "item": [
+ {
+ "name": "Save User",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"username\": \"testuser\",\n \"fullname\": \"Test User\",\n \"password\": \"password123\",\n \"email\": \"test@example.com\",\n \"role\": \"staff\",\n \"cap\": \"ANG,APT\",\n \"access\": \"pasien,rawat_jalan\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/users/save",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "users",
+ "save"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Delete User",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"id\": \"3\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/users/delete",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "users",
+ "delete"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Settings",
+ "item": [
+ {
+ "name": "Get Settings",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/settings/settings",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "settings",
+ "settings"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "Save Settings",
+ "request": {
+ "method": "POST",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}",
+ "type": "text"
+ },
+ {
+ "key": "Content-Type",
+ "value": "application/json",
+ "type": "text"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}",
+ "type": "text"
+ }
+ ],
+ "body": {
+ "mode": "raw",
+ "raw": "{\n \"nama_instansi\": \"RS Baru\",\n \"alamat\": \"Jl. Baru No. 1\"\n}"
+ },
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/settings/saveSettings",
+ "host": [
+ "{{baseUrl}}"
+ ],
+ "path": [
+ "admin",
+ "api",
+ "settings",
+ "saveSettings"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Mini PACS",
+ "item": [
+ {
+ "name": "Study List",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/mini_pacs/studylist?page=1&per_page=10&s="
+ }
+ }
+ },
+ {
+ "name": "Study Detail",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/mini_pacs/studydetail/{{study_id}}"
+ }
+ }
+ },
+ {
+ "name": "Series Detail",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/mini_pacs/seriesdetail/{{series_id}}"
+ }
+ }
+ },
+ {
+ "name": "Download DICOM",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/mini_pacs/instancedicom/{{instance_id}}"
+ }
+ }
+ },
+ {
+ "name": "Get Thumbnail",
+ "request": {
+ "method": "GET",
+ "header": [
+ {
+ "key": "X-Api-Key",
+ "value": "{{apiKey}}"
+ },
+ {
+ "key": "Authorization",
+ "value": "Bearer {{token}}"
+ }
+ ],
+ "url": {
+ "raw": "{{baseUrl}}/admin/api/mini_pacs/instancejpg/{{instance_id}}"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "variable": [
+ {
+ "key": "baseUrl",
+ "value": "https://mlite.up.railway.app",
+ "type": "string"
+ },
+ {
+ "key": "apiKey",
+ "value": "YOUR_API_KEY_HERE",
+ "type": "string"
+ },
+ {
+ "key": "username",
+ "value": "admin",
+ "type": "string"
+ },
+ {
+ "key": "password",
+ "value": "admin",
+ "type": "string"
+ },
+ {
+ "key": "token",
+ "value": "",
+ "type": "string"
+ },
+ {
+ "key": "s",
+ "value": "",
+ "type": "string"
+ },
+ {
+ "key": "page",
+ "value": "1",
+ "type": "string"
+ },
+ {
+ "key": "per_page",
+ "value": "10",
+ "type": "string"
+ },
+ {
+ "key": "no_rkm_medis",
+ "value": "000001",
+ "type": "string"
+ },
+ {
+ "key": "no_rawat",
+ "value": "",
+ "type": "string"
+ },
+ {
+ "key": "username_permission",
+ "value": "DR001",
+ "type": "string"
+ },
+ {
+ "key": "password_permission",
+ "value": "12345678",
+ "type": "string"
+ },
+ {
+ "key": "tgl_awal",
+ "value": "2025-01-01",
+ "type": "string"
+ },
+ {
+ "key": "tgl_akhir",
+ "value": "2027-01-01",
+ "type": "string"
+ }
+ ]
+}
diff --git a/README.md b/README.md
index 9c35861ca..58a1938be 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
- Codename: Kirana
+ Codename: Sabrina
# mLITE
@@ -12,98 +12,93 @@ Panel kontrol dan tampilan default sepenuhnya responsif, yang membuatnya dapat d
Masih banyak fitur-fitur tersembunyi untuk kebutuhan pengembangan. Silahkan jelajahi!!
-Kebutuhan Sistem
-----------------
+## Kebutuhan Sistem
Persyaratan sistem untuk mLITE sangat sederhana, sehingga setiap server modern sudah cukup. Berikut persyaratan minimal yang diperlukan
-+ Apache 2.2+ dengan `mod_rewrite` atau Nginx
-+ PHP versi 7.4 - 8.3+
-+ MySQL atau MariaDB
+- Apache 2.2+ dengan `mod_rewrite` atau Nginx
+- PHP versi 7.4 - 8.3+
+- MySQL atau MariaDB
Konfigurasi PHP harus memiliki ekstensi berikut:
-+ dom
-+ gd
-+ mbstring
-+ pdo
-+ zip
-+ cURL
+- dom
+- gd
+- mbstring
+- pdo
+- zip
+- cURL
-Pemasangan
-----------
+## Pemasangan
### Pemasangan menggunakan composer.
+
1. Install composer di server/PC dan jalankan perintah composer untuk pemasangan paket utama dan independensi
```
$ composer create-project basoro/mlite
```
-2. Buat folder `uploads`, `tmp/` dan `admin/tmp`. Beberapa server mungkin memerlukan izin tambahan `chmod 777` untuk direktori dan file tersebut.
-
-3. Sesuaikan pengaturan di config.php
-
-4. Buat database baru di MySQL/MariaDB dan import file `mlite_db.sql`
-
-5. Buka browser Anda dan navigasikan ke alamat tempat file mLITE berada.
-
-6. Silahkan login dengan Username: admin dan Password: admin
+1. Buat folder `uploads`, `tmp/` dan `admin/tmp`. Beberapa server mungkin memerlukan izin tambahan `chmod 777` untuk direktori dan file tersebut.
+2. Sesuaikan pengaturan di .env
+3. Buat database baru di MySQL/MariaDB dan import file `mlite_db.sql`
+4. Buka browser Anda dan navigasikan ke alamat tempat file mLITE berada.
+5. Silahkan login dengan Username: admin dan Password: admin
### Pemasangan Manual
-1. Unduh versi terbaru [mLITE] (https://github.com/basoro/mlite).
-
-2. Ekstrak semua file dari paket terkompresi dan kemudian transfer ke direktori lokal atau server. Biasanya, file diunggah ke `www`,` htdocs` atau `public_html`.
+1. Unduh versi terbaru \[mLITE] ().
+2. Ekstrak semua file dari paket terkompresi dan kemudian transfer ke direktori lokal atau server. Biasanya, file diunggah ke `www`, ` htdocs` atau `public_html`.
3. Install composer di server/PC dan jalankan perintah composer untuk pemasangan independensi
+
```
$ composer install
```
-4. Buat folder `uploads`, `tmp/` dan `admin/tmp`. Beberapa server mungkin memerlukan izin tambahan `chmod 777` untuk direktori dan file tersebut.
-
-5. Sesuaikan pengaturan di config.php
-
-6. Buat database baru di MySQL/MariaDB dan import file `mlite_db.sql` (tambahkan sql-mode = '' pada my.cnf atau jalankan perintah berikut)
+1. Buat folder `uploads`, `tmp/` dan `admin/tmp`. Beberapa server mungkin memerlukan izin tambahan `chmod 777` untuk direktori dan file tersebut.
+2. Sesuaikan pengaturan di .env
+3. Buat database baru di MySQL/MariaDB dan import file `mlite_db.sql` (tambahkan sql-mode = '' pada my.cnf atau jalankan perintah berikut)
```
$ sed -i "/user=mysql/a sql-mode = ''" /etc/my.cnf
```
-7. Buka browser Anda dan navigasikan ke alamat tempat file mLITE berada.
-
-8. Silahkan login dengan Username: admin dan Password: admin
+1. Buka browser Anda dan navigasikan ke alamat tempat file mLITE berada.
+2. Silahkan login dengan Username: admin dan Password: admin
### Pemasangan menggunakan docker.
-1. Download file release terbaru [mLITE] atau lakukan git clone.
+
+1. Download file release terbaru \[mLITE] atau lakukan git clone.
```
$ git clone https://github.com/basoro/mlite.git
```
-Sesuaikan port di bagian Nginx Service dan MySQL Service dari default ke port yang anda inginkan (atau biarkan defautl saja).
-2. Jalankan perintah docker-compose
+Sesuaikan environment variable di .env seperti port APP dan MySQL dari default ke port yang anda inginkan (atau biarkan defautl saja).
+
+1. Jalankan perintah docker-compose
+
```
$ cd mlite/docker && docker-compose build && docker-compose up -d
```
-3. Buka browser Anda dan navigasikan ke alamat URL_ADDRESS:8088.
-
-4. Silahkan login dengan Username: admin dan Password: admin
-
+1. Buka browser Anda dan navigasikan ke alamat URL\_ADDRESS:8088.
+2. Silahkan login dengan Username: admin dan Password: admin
## Catatan:
+
Untuk setiap kali update, silahkan jalankan perintah
+
```
$ composer update
```
-Ini untuk menambahkan dependensi baru pada aplikasi
+Ini untuk menambahkan dependensi baru pada aplikasi
## Peringatan!
-+ Untuk pengguna Apache, pastikan file `.htaccess` juga ada di server. Tanpanya mLITE tidak akan berfungsi.
-+ Untuk pengguna Nginx, tambahkan konfigurasi berikut di pengaturan nginx.conf (atau sejenisnya)
+- Untuk pengguna Apache, pastikan file `.htaccess` juga ada di server. Tanpanya mLITE tidak akan berfungsi.
+- Untuk pengguna Nginx, tambahkan konfigurasi berikut di pengaturan nginx.conf (atau sejenisnya)
```bash
location / {
@@ -118,6 +113,11 @@ location ^~ /systems/data/ {
return 403;
}
+location ~* \.(sdb|txt|env|md)$ {
+ deny all;
+ return 403;
+}
+
location /admin {
index index.php;
try_files $uri $uri/ /admin/index.php?$args;
@@ -139,6 +139,11 @@ location ^~ /lite/systems/data/ {
return 403;
}
+location ~* \.(sdb|txt|env|md)$ {
+ deny all;
+ return 403;
+}
+
location /lite/admin {
index index.php;
try_files $uri $uri/ /lite/admin/index.php?$args;
@@ -146,8 +151,11 @@ location /lite/admin {
```
Untuk masuk ke panel administrasi, tambahkan `/admin/` di akhir URL.
+
#### Login: `admin` Kata sandi: `admin`
+
Ini harus diubah segera setelah login untuk alasan keamanan. Juga dapat mengganti nama direktori dengan panel administrasi. (Anda perlu mengubahnya pada `config.php`)
## Demo
-Demo dan Info lebih lanjut di https://mlite.id
+
+Untuk Demo dan Info lebih lanjut di
diff --git a/admin/index.php b/admin/index.php
index a03a05d95..5ccd2ce51 100644
--- a/admin/index.php
+++ b/admin/index.php
@@ -11,20 +11,28 @@
// CORS Headers
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS, DELETE, PUT");
- header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Api-Key, X-Requested-With");
+ header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Api-Key, X-Requested-With, X-Username-Permission, X-Password-Permission");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
- if (ob_get_level() > 0) ob_end_clean();
+ if (ob_get_level() > 0)
+ ob_end_clean();
http_response_code(200);
exit;
}
-
+
// Handle HTTPS forwarding with strict comparison
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
-define('BASE_DIR', __DIR__.'/..');
-require_once('../config.php');
+ if (!defined('BASE_DIR')) {
+ define('BASE_DIR', __DIR__ . '/..');
+ }
+ require_once('../config.php');
+
+ // if (!file_exists(BASE_DIR . '/.env')) {
+ // header('Location: /install.php');
+ // exit;
+ // }
if (DEV_MODE) {
error_reporting(E_ALL);
@@ -34,187 +42,347 @@
ini_set('display_errors', '0');
}
-require_once('../systems/lib/Autoloader.php');
-//ob_start(base64_decode('XFN5c3RlbXNcTWFpbjo6dmVyaWZ5TGljZW5zZQ=='));
-
-$core = new Systems\Admin;
-
-// Register API routes available regardless of login state
-$core->router->set('api/login', function () use ($core) {
- if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- $input = json_decode(file_get_contents('php://input'), true);
- $username = $input['username'] ?? $_POST['username'] ?? '';
- $password = $input['password'] ?? $_POST['password'] ?? '';
- $user = $core->db('mlite_users')->where('username', $username)->oneArray();
- if ($user && password_verify($password, $user['password'])) {
- $payload = [
- 'iss' => url(),
- 'sub' => $user['id'],
- 'username' => $user['username']
- ];
- $token = \Systems\Lib\Jwt::encode($payload, JWT_SECRET);
+ require_once('../systems/lib/Autoloader.php');
+ //ob_start(base64_decode('XFN5c3RlbXNcTWFpbjo6dmVyaWZ5TGljZW5zZQ=='));
+
+ $core = new Systems\Admin;
+
+ // Register API routes available regardless of login state
+ $core->router->set('api/login', function () use ($core) {
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $apiUser = $core->checkAuth('POST');
+ $input = json_decode(file_get_contents('php://input'), true);
+ $username = $input['username'] ?? $_POST['username'] ?? '';
+ $password = $input['password'] ?? $_POST['password'] ?? '';
+
+ $user = $core->db('mlite_users')->where('username', $username)->oneArray();
+ if ($user && password_verify($password, $user['password'])) {
+ $payload = [
+ 'iss' => url(),
+ 'sub' => $user['id'],
+ 'username' => $user['username']
+ ];
+ $token = \Systems\Lib\Jwt::encode($payload, JWT_SECRET);
+ header('Content-Type: application/json');
+ echo json_encode([
+ 'token' => $token,
+ 'fullname' => htmlspecialchars($user['fullname'] ?? $user['username'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
+ ]);
+ } else {
+ header('Content-Type: application/json');
+ http_response_code(401);
+ echo json_encode(['error' => 'Invalid credentials']);
+ }
+ exit;
+ }
+ });
+
+ $core->router->set('api/(:str)/(:str)(:any)', function ($module, $method, $params) use ($core) {
+ // 1. Verifikasi kredensial aplikasi klien via API Key atau Sesi
+ $apiUser = $core->checkAuth(strtoupper($_SERVER['REQUEST_METHOD']));
+
+ $token = null;
+ if (isset($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/Bearer\s(\S+)/', $_SERVER['HTTP_AUTHORIZATION'], $m)) {
+ $token = $m[1];
+ } elseif (function_exists('getallheaders')) {
+ $headers = getallheaders();
+ $auth = $headers['Authorization'] ?? $headers['authorization'] ?? null;
+ if ($auth && preg_match('/Bearer\s(\S+)/', $auth, $m)) {
+ $token = $m[1];
+ }
+ }
+ $payload = $token ? \Systems\Lib\Jwt::verify($token, JWT_SECRET) : false;
+ $loggedIn = $core->loginCheck();
+
+ $core->loadModules();
+ $moduleObj = $core->module->{$module};
+ if (!$moduleObj) {
header('Content-Type: application/json');
- echo json_encode(['token' => $token]);
+ http_response_code(404);
+ echo json_encode(['error' => 'Module not found']);
+ exit;
+ }
+ $access = 'all';
+ if ($loggedIn) {
+ $access = $core->getUserInfo('access');
+ } elseif ($payload) {
+ $uid = $payload['sub'] ?? 0;
+ $urow = $core->db('mlite_users')->where('id', $uid)->oneArray();
+ $access = $urow['access'] ?? '';
} else {
+ $urow = $core->db('mlite_users')->where('username', $apiUser)->oneArray();
+ $access = $urow['access'] ?? '';
+ }
+ if (!($access == 'all' || in_array($module, explode(',', (string) $access)))) {
header('Content-Type: application/json');
- http_response_code(401);
- echo json_encode(['error' => 'Invalid credentials']);
+ http_response_code(403);
+ echo json_encode(['error' => 'Forbidden']);
+ exit;
}
- exit;
- }
-});
-
-$core->router->set('api/(:str)/(:str)(:any)', function ($module, $method, $params) use ($core) {
- $token = null;
- if (isset($_SERVER['HTTP_AUTHORIZATION']) && preg_match('/Bearer\s(\S+)/', $_SERVER['HTTP_AUTHORIZATION'], $m)) {
- $token = $m[1];
- } elseif (function_exists('getallheaders')) {
- $headers = getallheaders();
- $auth = $headers['Authorization'] ?? $headers['authorization'] ?? null;
- if ($auth && preg_match('/Bearer\s(\S+)/', $auth, $m)) {
- $token = $m[1];
+ $args = $params ? explode('/', trim($params, '/')) : [];
+ $apiMethod = 'api' . ucfirst($method);
+ $anyMethod = 'any' . ucfirst($method);
+ $httpMethod = strtolower($_SERVER['REQUEST_METHOD']) . ucfirst($method);
+ if (method_exists($moduleObj, $apiMethod)) {
+ $result = $moduleObj->{$apiMethod}(...array_values($args));
+ } elseif (method_exists($moduleObj, $httpMethod)) {
+ $result = $moduleObj->{$httpMethod}(...array_values($args));
+ } elseif (method_exists($moduleObj, $anyMethod)) {
+ $result = $moduleObj->{$anyMethod}(...array_values($args));
+ } else {
+ header('Content-Type: application/json');
+ http_response_code(404);
+ echo json_encode(['error' => 'Method not found']);
+ exit;
}
- }
- $payload = $token ? \Systems\Lib\Jwt::verify($token, JWT_SECRET) : false;
- $loggedIn = $core->loginCheck();
- if (!$payload && !$loggedIn) {
- header('Content-Type: application/json');
- http_response_code(401);
- echo json_encode(['error' => 'Unauthorized']);
- exit;
- }
- $core->loadModules();
- $moduleObj = $core->module->{$module};
- if (!$moduleObj) {
- header('Content-Type: application/json');
- http_response_code(404);
- echo json_encode(['error' => 'Module not found']);
- exit;
- }
- $access = 'all';
- if ($loggedIn) {
- $access = $core->getUserInfo('access');
- } elseif ($payload) {
- $uid = $payload['sub'] ?? 0;
- $urow = $core->db('mlite_users')->where('id', $uid)->oneArray();
- $access = $urow['access'] ?? '';
- }
- if (!($access == 'all' || in_array($module, explode(',', (string)$access)))) {
- header('Content-Type: application/json');
- http_response_code(403);
- echo json_encode(['error' => 'Forbidden']);
- exit;
- }
- $args = $params ? explode('/', trim($params, '/')) : [];
- $apiMethod = 'api'.ucfirst($method);
- $anyMethod = 'any'.ucfirst($method);
- $httpMethod = strtolower($_SERVER['REQUEST_METHOD']).ucfirst($method);
- if (method_exists($moduleObj, $apiMethod)) {
- $result = $moduleObj->{$apiMethod}(...array_values($args));
- } elseif (method_exists($moduleObj, $httpMethod)) {
- $result = $moduleObj->{$httpMethod}(...array_values($args));
- } elseif (method_exists($moduleObj, $anyMethod)) {
- $result = $moduleObj->{$anyMethod}(...array_values($args));
- } else {
header('Content-Type: application/json');
- http_response_code(404);
- echo json_encode(['error' => 'Method not found']);
+ if (is_array($result) || is_object($result)) {
+ echo json_encode($result);
+ } else {
+ echo json_encode(['data' => $result]);
+ }
exit;
+ });
+
+ // Execute API route early if requested
+ $__path = $core->router->execute(true);
+
+ // daftar API yang TIDAK lewat API umum
+ $excludedApiPrefixes = [
+ 'api/manage',
+ 'api/notifikasi',
+ 'api/settingsapam',
+ 'api/paymentduitku',
+ 'api/settingskey',
+ 'api/saveSettingsApam',
+ 'api/saveSettingsKey',
+ ];
+
+ $skipApi = false;
+ foreach ($excludedApiPrefixes as $prefix) {
+ if (strpos($__path, $prefix) === 0) {
+ $skipApi = true;
+ break;
+ }
}
- header('Content-Type: application/json');
- if (is_array($result) || is_object($result)) {
- echo json_encode($result);
- } else {
- echo json_encode(['data' => $result]);
+
+ if (!$skipApi && strpos($__path, 'api/') === 0) {
+ $core->router->execute();
+ return;
}
- exit;
-});
-
-// Execute API route early if requested
-$__path = $core->router->execute(true);
-if (strpos($__path, 'api/') === 0) {
- $core->router->execute();
- return;
-}
-if ($core->loginCheck()) {
- $core->loadModules();
- $core->router->set('(:str)/(:str)(:any)', function ($module, $method, $params) use ($core) {
- $core->createNav($module, $method);
- if ($params) {
- $core->loadModule($module, $method, explode('/', trim($params, '/')));
- } else {
- $core->loadModule($module, $method);
- }
- });
+ if ($core->loginCheck()) {
+ $core->loadModules();
- $core->router->execute();
- $core->drawTheme('index.html');
- $core->module->finishLoop();
-} else {
- if (isset($_POST['login'])) {
- if ($core->login($_POST['username'], $_POST['password'], isset($_POST['remember_me']))) {
- $arrayURL = parseURL();
- if ($arrayURL && count($arrayURL) > 1) {
- $url = array_merge([ADMIN], $arrayURL);
- redirect(url($url));
- }
- if (!empty($_SESSION['mlite_force_change'])) {
- redirect(url([ADMIN, 'profil', 'ganti_pass']));
+ $core->router->set('(:str)/(:str)(:any)', function ($module, $method, $params) use ($core) {
+ $core->createNav($module, $method);
+ if ($params) {
+ $core->loadModule($module, $method, explode('/', trim($params, '/')));
+ } else {
+ $core->loadModule($module, $method);
}
- if(MULTI_APP) {
- if(!empty(MULTI_APP_REDIRECT)) {
- redirect(url([ADMIN, MULTI_APP_REDIRECT, 'main']));
+ });
+
+ $core->router->execute();
+ $core->drawTheme('index.html');
+ $core->module->finishLoop();
+ } else {
+ if (isset($_GET['cancel_otp'])) {
+ unset($_SESSION['mlite_otp_pending']);
+ unset($_SESSION['mlite_otp_user_id']);
+ unset($_SESSION['mlite_otp_remember_me']);
+ redirect(url([ADMIN]));
+ }
+
+ if (isset($_POST['verify_otp']) && isset($_SESSION['mlite_otp_pending'])) {
+ $otp_code = trim($_POST['otp_code'] ?? '');
+ $uid = $_SESSION['mlite_otp_user_id'] ?? 0;
+ $userRow = $core->db('mlite_users')->where('id', $uid)->oneArray();
+
+ if ($userRow && $userRow['otp_code'] === $otp_code && strtotime($userRow['otp_expires']) >= time()) {
+ // OTP Valid
+ unset($_SESSION['mlite_otp_pending']);
+ unset($_SESSION['mlite_otp_user_id']);
+
+ $_SESSION['mlite_user']= $userRow['id'];
+ $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(6));
+ $_SESSION['userAgent'] = $_SERVER['HTTP_USER_AGENT'];
+ $_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR'];
+
+ if (isset($_SESSION['mlite_otp_remember_me'])) {
+ $token = str_gen(64, "1234567890qwertyuiop[]asdfghjkl;zxcvbnm,./");
+ $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
+ || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower((string) $_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https');
+ $core->db('mlite_remember_me')->save(['user_id' => $userRow['id'], 'token' => $token, 'expiry' => time()+60*60*24*30]);
+ setcookie('mlite_remember', $userRow['id'] . ':' . $token, [
+ 'expires' => time() + 60 * 60 * 24 * 365,
+ 'path' => '/',
+ 'secure' => $isHttps,
+ 'httponly' => true,
+ 'samesite' => 'Lax',
+ ]);
+ unset($_SESSION['mlite_otp_remember_me']);
+ }
+
+ $arrayURL = parseURL();
+ if ($arrayURL && count($arrayURL) > 1) {
+ $url = array_merge([ADMIN], $arrayURL);
+ redirect(url($url));
+ }
+ if (!empty($_SESSION['mlite_force_change'])) {
+ redirect(url([ADMIN, 'profil', 'ganti_pass']));
+ }
+ if (MULTI_APP) {
+ if (!empty(MULTI_APP_REDIRECT)) {
+ redirect(url([ADMIN, MULTI_APP_REDIRECT, 'main']));
+ } else {
+ redirect(url([ADMIN, 'dashboard', 'main']));
+ }
} else {
redirect(url([ADMIN, 'dashboard', 'main']));
}
} else {
- redirect(url([ADMIN, 'dashboard', 'main']));
+ $core->setNotify('failure', 'Kode OTP tidak valid atau telah kedaluwarsa.');
}
}
- }
- if(MULTI_APP) {
- if(!empty(MULTI_APP_REDIRECT)) {
- echo $core->tpl->draw(MODULES.'/'.MULTI_APP_REDIRECT.'/view/login.html', true);
- } else {
- $core->drawTheme('login.html');
- }
- } else {
- $core->drawTheme('login.html');
- }
-}
+ if (isset($_POST['login'])) {
+ if ($core->login($_POST['username'], $_POST['password'], isset($_POST['remember_me']))) {
+ if (isset($_SESSION['mlite_otp_pending'])) {
+ $core->setNotify('success', 'Harap masukkan 6-digit kode OTP yang telah dikirim ke WhatsApp Anda.');
+ } else {
+ $arrayURL = parseURL();
+ if ($arrayURL && count($arrayURL) > 1) {
+ $url = array_merge([ADMIN], $arrayURL);
+ redirect(url($url));
+ }
+ if (!empty($_SESSION['mlite_force_change'])) {
+ redirect(url([ADMIN, 'profil', 'ganti_pass']));
+ }
+ if (MULTI_APP) {
+ if (!empty(MULTI_APP_REDIRECT)) {
+ redirect(url([ADMIN, MULTI_APP_REDIRECT, 'main']));
+ } else {
+ redirect(url([ADMIN, 'dashboard', 'main']));
+ }
+ } else {
+ redirect(url([ADMIN, 'dashboard', 'main']));
+ }
+ }
+ }
+ }
+ if (MULTI_APP) {
+ if (!empty(MULTI_APP_REDIRECT)) {
+ echo $core->tpl->draw(MODULES . '/' . MULTI_APP_REDIRECT . '/view/login.html', true);
+ } else {
+ $core->drawTheme('login.html');
+ }
+ } else {
+ $core->drawTheme('login.html');
+ }
+
+ }
} catch (Throwable $e) {
+ // Tangkap khusus error ketidakadaan tabel untuk tidak di redirect melainkan dimunculkan sebagai notifikasi
+ $errMsg = $e->getMessage();
+ $isMissingTable = (strpos($errMsg, 'Base table or view not found') !== false || strpos($errMsg, 'no such table') !== false || strpos($errMsg, 'Unknown database') !== false);
+
// Check if this is an AJAX request expecting JSON
$isAjaxRequest = (
(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') ||
(isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) ||
(isset($_SERVER['CONTENT_TYPE']) && strpos($_SERVER['CONTENT_TYPE'], 'application/json') !== false)
);
-
+
if ($isAjaxRequest) {
+ $error = 'Database connection failed';
$message = 'Please check database configuration and ensure MySQL server is running';
- if ($e->getCode() == 23000) {
+ $debug = [];
+
+ if ($isMissingTable) {
+ $error = 'Table / Database Not Found';
+ $message = $errMsg;
+ } elseif ($e->getCode() == 23000) {
$message = $e->getMessage();
}
- // Return JSON error response for AJAX requests
- header('Content-Type: application/json');
- echo json_encode([
- 'error' => 'Database connection failed',
- 'message' => $message,
- 'details' => DEV_MODE ? $e->getMessage() : 'System error occurred'
- ]);
+ if (defined('DEV_MODE') && DEV_MODE) {
+ $error = 'System Error';
+ $message = $e->getMessage();
+ $debug = [
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'trace' => explode("\n", $e->getTraceAsString())
+ ];
+ }
+
+ // Return HTML error response for AJAX requests (e.g. for modal content or alerts)
+ header('Content-Type: text/html');
+ $debug_html = '';
+ if (!empty($debug)) {
+ $debug_html .= ' ';
+ $debug_html .= 'File: ' . htmlspecialchars($debug['file']) . ' on line ' . $debug['line'] . ' ';
+ $debug_html .= '';
+ $debug_html .= '
' . htmlspecialchars(implode("\n", $debug['trace'])) . ' ';
+ $debug_html .= '
';
+ }
+
+ echo '
+
×
+
' . htmlspecialchars($error) . '
+
' . htmlspecialchars($message) . '
+ ' . $debug_html . '
+
';
} else {
+
// Return HTML error for regular requests
- if (DEV_MODE) {
- echo 'Error ' . htmlspecialchars($e->getMessage()) . ' ';
- echo '' . htmlspecialchars($e->getTraceAsString()) . ' ';
- } else {
- echo 'System Error Please contact administrator.
';
+ $title = 'System Error';
+ $content = 'Please contact administrator.
';
+ $debug_info = '';
+
+ if ($isMissingTable) {
+ $title = 'Database Error';
+ $content = 'Warning: ' . htmlspecialchars($errMsg) . '
';
+ $content .= 'Sepertinya tabel yang hendak diakses tidak tersedia di database. Anda mungkin tidak menginstal plugin secara menyeluruh, atau modul belum menyediakan tabel tersebut.
';
}
+
+ if (defined('DEV_MODE') && DEV_MODE) {
+ $content .= 'Error: ' . htmlspecialchars($e->getMessage()) . '
';
+ $debug_info = '';
+ $debug_info .= '
File: ' . htmlspecialchars($e->getFile()) . ' on line ' . $e->getLine() . '
';
+ $debug_info .= '
Stack Trace: ';
+ $debug_info .= '
' . htmlspecialchars($e->getTraceAsString()) . ' ';
+ $debug_info .= '
';
+ }
+
+ echo '
+
+
+ ' . $title . '
+
+
+
+
+
' . $title . '
+ ' . $content . '
+ ' . $debug_info . '
+
+
mLITE Healthcare System © ' . date('Y') . '
+
+
+
+ ';
}
}
diff --git a/assets/css/atilamedika.min.css b/assets/css/atilamedika.min.css
new file mode 100644
index 000000000..93f83666b
--- /dev/null
+++ b/assets/css/atilamedika.min.css
@@ -0,0 +1 @@
+@import url("mlite.min.css");body{background-color:#fff7f2;color:#14332d}a{color:#f08a54}a:hover,a:focus{color:#d9733d}.text-primary{color:#0f3d34}a.text-primary:hover,a.text-primary:focus{color:#0b2f29}.bg-primary{background-color:#0f3d34}a.bg-primary:hover,a.bg-primary:focus{background-color:#0b2f29}.btn-primary{background-color:#0f3d34;border-color:#0f3d34}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-color:#0b2f29;border-color:#0b2f29}.btn-warning{background-color:#f08a54;border-color:#f08a54}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-color:#d9733d;border-color:#d9733d}.label-primary{background-color:#0f3d34}.label-warning{background-color:#f08a54}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{background-color:#0f3d34;border-color:#0f3d34}.navbar-default{background-color:#0f3d34;border-color:#0f3d34}.navbar-default .navbar-brand,.navbar-default .navbar-nav>li>a{color:#fff}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus,.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#f08a54}.panel-primary>.panel-heading{background-color:#0f3d34;border-color:#0f3d34;color:#fff}.panel-primary{border-color:#0f3d34}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{border-top-color:#0f3d34}.form-control:focus{border-color:#f08a54;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(240,138,84,.35);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(240,138,84,.35)}
diff --git a/assets/img/atilamedika.png b/assets/img/atilamedika.png
new file mode 100644
index 000000000..7a4f9db9f
Binary files /dev/null and b/assets/img/atilamedika.png differ
diff --git a/composer.json b/composer.json
index fa04837ad..d3d4e3ef1 100644
--- a/composer.json
+++ b/composer.json
@@ -8,12 +8,14 @@
}
},
"require": {
- "php": ">=7.4",
+ "php": ">=8.0",
"nullpunkt/lz-string-php": "^1.3",
"phpseclib/phpseclib": "~3.0.36",
"phpmailer/phpmailer": "^6.9.1",
- "mpdf/mpdf": "^8.2.4",
- "workerman/workerman": "^4.0"
+ "mpdf/mpdf": "^8.2",
+ "workerman/workerman": "^4.0",
+ "mpdf/qrcode": "^1.2",
+ "halaxa/json-machine": "^1.2"
},
"license": "MIT",
"authors": [
diff --git a/config.php b/config.php
index e17cc9e9f..bb344fe37 100644
--- a/config.php
+++ b/config.php
@@ -1,22 +1,75 @@
=')) {
- exit("mLITE requires at least PHP 7.4 (Current: " . PHP_VERSION . ")");
+if (!version_compare(PHP_VERSION, '8.0.0', '>=')) {
+ exit("mLITE requires at least PHP 8.0.0 (Current: " . PHP_VERSION . ")");
}
-define('DBHOST', 'localhost');
-define('DBPORT', '3306');
-define('DBUSER', 'root');
-define('DBPASS', '');
-define('DBNAME', 'mlite');
+function env(string $key, $default = null)
+{
+ return $_ENV[$key] ?? $_SERVER[$key] ?? $default;
+}
+
+// Simple .env loader (PHP 8.3 safe, no putenv)
+if (file_exists(BASE_DIR . '/.env')) {
+ $lines = file(BASE_DIR . '/.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+
+ foreach ($lines as $line) {
+ $line = trim($line);
+
+ // Skip komentar & baris kosong
+ if ($line === '' || str_starts_with($line, '#')) {
+ continue;
+ }
+
+ if (!str_contains($line, '=')) {
+ continue;
+ }
+
+ [$name, $value] = explode('=', $line, 2);
+ $name = trim($name);
+ $value = trim($value);
+
+ // Hapus tanda kutip jika ada
+ $value = trim($value, "\"'");
+
+ if (!isset($_ENV[$name]) && !isset($_SERVER[$name])) {
+ $_ENV[$name] = $value;
+ $_SERVER[$name] = $value;
+ }
+ }
+}
+
+
+// Database Driver: 'mysql' or 'sqlite'
+define('DBDRIVER', env('DBDRIVER') ?: '');
+
+if (DBDRIVER == 'sqlite') {
+ $db_host = '';
+ $db_user = '';
+ $db_pass = '';
+ $db_name = BASE_DIR . '/systems/data/mlite.sdb';
+ $db_port = '';
+} else {
+ $db_host = env('MYSQLHOST') ?: 'localhost';
+ $db_user = env('MYSQLUSER') ?: 'root';
+ $db_pass = env('MYSQLPASSWORD') ?: '';
+ $db_name = env('MYSQLDATABASE') ?: 'mlite';
+ $db_port = env('MYSQLPORT') ?: '3306';
+}
+
+define('DBHOST', $db_host);
+define('DBPORT', $db_port);
+define('DBUSER', $db_user);
+define('DBPASS', $db_pass);
+define('DBNAME', $db_name);
// URL Webapps
-define('WEBAPPS_URL', 'http://mlite.loc/uploads'); // Sesuaikan http://mlite.loc dengan domain atau IP Address server
+define('WEBAPPS_URL', env('APPURL') ?: 'http://localhost:8000/uploads'); // Sesuaikan http://mlite.loc dengan domain atau IP Address server
define('WEBAPPS_PATH', BASE_DIR . '/uploads');
// Multi APP
-define('MULTI_APP', false);
-define('MULTI_APP_REDIRECT', '');
+define('MULTI_APP', env('MULTIAPP') ?: 'false');
+define('MULTI_APP_REDIRECT', env('MULTIAPP_REDIRECT') ?: '');
// Admin cat name
define('ADMIN', 'admin');
@@ -34,7 +87,7 @@
define('FILE_LOCK', false);
// Basic modules
-define('BASIC_MODULES', serialize([
+define('BASIC_MODULES', json_encode([
9 => 'settings',
0 => 'dashboard',
1 => 'master',
@@ -45,10 +98,12 @@
6 => 'farmasi',
8 => 'users',
7 => 'modules',
- 10 => 'wagateway'
+ 10 => 'wagateway'
]));
-// Developer mode
-define('DEV_MODE', true);
+// Developer mode, mendukung `DEV_MODE` dan key lama `DEVMODE` di .env
+define('DEV_MODE', filter_var(env('DEV_MODE', env('DEVMODE', true)), FILTER_VALIDATE_BOOLEAN));
+
+define('JWT_SECRET', 'mlite_secret_key_change_me');
?>
diff --git a/docker/.env b/docker/.env
index ba8adb468..2518c38e0 100644
--- a/docker/.env
+++ b/docker/.env
@@ -1,11 +1,22 @@
-HOST_PROJECT_DIR=../
-HOST_PROJECT_PORT=7788
+# Database Configuration
+DB_DRIVER=mysql
+DB_HOST=mysql
+DB_PORT=3306
+DB_ROOT_PASSWORD=rootpassword123
+DB_DATABASE=mlite_db
+DB_USERNAME=mlite
+DB_PASSWORD=mlite
+# Application Configuration
+# APP_PORT=8080 # gunakan jika ingin mengakses nginx dari luar container
+# DB_EXTERNAL_PORT=3308 # gunakan jika ingin mengakses mysql dari luar container
-# Default admin credentials (can be overridden)
-PANEL_ADMIN_USER=admin
-PANEL_ADMIN_PASS=admin
+APPURL=http://localhost:8000/uploads
+MULTIAPP=
+MULTIAPP_REDIRECT=
+DEVMODE=
-# DICOM Router
-ORG_ID=
-CLIENT=
-SECRET=
+
+
+# Docker Compose Profiles
+# Biarkan kosong jika menggunakan SQLite. Isi dengan 'mysql_enabled' jika ingin menggunakan container MySQL
+COMPOSE_PROFILES=mysql_enabled
diff --git a/docker/Makefile b/docker/Makefile
deleted file mode 100644
index bb42498e6..000000000
--- a/docker/Makefile
+++ /dev/null
@@ -1,179 +0,0 @@
-# mLITE Docker Makefile
-# Compatible with bash/zsh/macOS/Linux
-
-PROJECT_NAME = mlite
-COMPOSE_FILE = docker-compose.yml
-DB_CONTAINER = mlite_mysql
-PHP_CONTAINER = mlite_php
-ORTHANC_CONTAINER = mlite_orthanc
-NGINX_CONTAINER = mlite_nginx
-FLASK_APP_CONTAINER = mlite_panel
-PHPMYADMIN_CONTAINER = mlite_phpmyadmin
-
-DB_NAME = mlite_db
-DB_USER = mlite
-DB_PASSWORD = mlite123
-BACKUP_DIR = ./backups
-
-ORTHANC_URL=http://localhost:8042/instances
-ORTHANC_API=http://localhost:8042
-ORTHANC_USER=orthanc
-ORTHANC_PASS=orthanc
-
-# Colors
-RED = \033[0;31m
-GREEN = \033[0;32m
-YELLOW = \033[1;33m
-BLUE = \033[0;34m
-NC = \033[0m
-
-.PHONY: build up down restart logs logs-follow ps status php mysql orthanc nginx panel phpmyadmin db-backup db-restore clean help orthanc-test orthanc-upload-dummy orthanc-status nginx-status nginx-reload nginx-logs
-
-## Build all services
-build:
- @echo "$(BLUE)Building all services...$(NC)"
- @DOCKER_BUILDKIT=1 docker compose -f $(COMPOSE_FILE) build --progress=plain
- @echo "$(GREEN)Build completed!$(NC)"
-
-## Start all containers
-up:
- @echo "$(BLUE)Starting all containers...$(NC)"
- @docker compose -f $(COMPOSE_FILE) up -d
- @echo "$(GREEN)All containers started!$(NC)"
- @make status
-
-## Stop and remove containers
-down:
- @echo "$(YELLOW)Stopping and removing containers...$(NC)"
- @docker compose -f $(COMPOSE_FILE) down
- @echo "$(GREEN)All containers stopped!$(NC)"
-
-## Restart containers
-restart:
- @echo "$(YELLOW)Restarting containers...$(NC)"
- @docker compose -f $(COMPOSE_FILE) restart
- @echo "$(GREEN)Containers restarted!$(NC)"
-
-## Logs
-logs:
- @docker compose -f $(COMPOSE_FILE) logs
-
-logs-follow:
- @docker compose -f $(COMPOSE_FILE) logs -f
-
-## Show container status
-status:
- @docker compose -f $(COMPOSE_FILE) ps
-
-## Shell access
-php:
- @docker compose -f $(COMPOSE_FILE) exec $(PHP_CONTAINER) /bin/bash
-
-mysql:
- @docker compose -f $(COMPOSE_FILE) exec $(DB_CONTAINER) mysql -u$(DB_USER) -p$(DB_PASSWORD) $(DB_NAME)
-
-orthanc:
- @docker compose -f $(COMPOSE_FILE) exec $(ORTHANC_CONTAINER) /bin/bash
-
-nginx:
- @docker compose -f $(COMPOSE_FILE) exec $(NGINX_CONTAINER) /bin/bash
-
-panel:
- @docker compose -f $(COMPOSE_FILE) exec panel /bin/bash
-
-phpmyadmin:
- @docker compose -f $(COMPOSE_FILE) exec $(PHPMYADMIN_CONTAINER) /bin/bash
-
-## Nginx Manager Commands
-nginx-logs:
- @echo "$(BLUE)Nginx Manager logs:$(NC)"
- @docker compose -f $(COMPOSE_FILE) logs panel
-
-## Database backup
-db-backup:
- @echo "$(BLUE)Creating database backup...$(NC)"
- @mkdir -p $(BACKUP_DIR)
- @BACKUP_FILE="$(BACKUP_DIR)/$(DB_NAME)_$$(date +%Y%m%d_%H%M%S).sql"; \
- docker compose -f $(COMPOSE_FILE) exec -T $(DB_CONTAINER) mysqldump -u$(DB_USER) -p$(DB_PASSWORD) $(DB_NAME) > $$BACKUP_FILE && \
- echo "$(GREEN)Backup created: $$BACKUP_FILE$(NC)" || echo "$(RED)Backup failed!$(NC)"
-
-## Database restore
-db-restore:
- @if [ -z "$(FILE)" ]; then \
- echo "$(RED)Usage: make db-restore FILE=backup.sql$(NC)"; \
- exit 1; \
- fi; \
- if [ ! -f "$(FILE)" ]; then \
- echo "$(RED)Error: $(FILE) not found$(NC)"; \
- exit 1; \
- fi; \
- echo "$(YELLOW)Restoring database from $(FILE)...$(NC)"; \
- docker compose -f $(COMPOSE_FILE) exec -T $(DB_CONTAINER) mysql -u$(DB_USER) -p$(DB_PASSWORD) $(DB_NAME) < $(FILE); \
- echo "$(GREEN)Database restored successfully!$(NC)"
-
-## Clean up Docker images and containers
-clean:
- @echo "$(YELLOW)Cleaning up containers and images...$(NC)"
- @docker compose -f $(COMPOSE_FILE) down --rmi all
- @docker system prune -f --volumes
- @echo "$(GREEN)Cleanup completed!$(NC)"
-
-## Orthanc Testing
-orthanc-test:
- @echo "$(BLUE)Testing Orthanc connection...$(NC)"
- @if docker compose -f $(COMPOSE_FILE) ps $(ORTHANC_CONTAINER) | grep -q "Up"; then \
- echo "$(GREEN)Orthanc container is running$(NC)"; \
- curl -s -u "$(ORTHANC_USER):$(ORTHANC_PASS)" $(ORTHANC_API)/system | jq .Version || echo "$(RED)Orthanc API not reachable$(NC)"; \
- else \
- echo "$(RED)Orthanc container is not running$(NC)"; \
- fi
-
-orthanc-upload-dummy:
- @echo "📤 Uploading dummy DICOM files to Orthanc..."
- @find ./orthanc/dummy-dicom -type f -name "*.dcm" -print0 | while IFS= read -r -d '' f; do \
- echo "➡️ Upload $$f"; \
- curl -s -X POST "$(ORTHANC_URL)" --user "$(ORTHANC_USER):$(ORTHANC_PASS)" --data-binary @"$$f" > /dev/null || echo "❌ Failed: $$f"; \
- done; \
- echo "$(GREEN)✅ Done. Listing studies:$(NC)"; \
- curl -s -u "$(ORTHANC_USER):$(ORTHANC_PASS)" "$(ORTHANC_API)/studies" | jq .
-
-orthanc-status:
- @echo "$(YELLOW)Checking Orthanc system status...$(NC)"
- @curl -s -u "$(ORTHANC_USER):$(ORTHANC_PASS)" "$(ORTHANC_API)/system" | jq .
-
-## Panel rebuild
-panel-rebuild:
- @echo "$(YELLOW)Rebuilding panel service...$(NC)"
- @DOCKER_BUILDKIT=1 docker compose -f $(COMPOSE_FILE) build --progress=plain panel
- @docker compose -f $(COMPOSE_FILE) up -d --force-recreate panel
- @echo "$(GREEN)Panel rebuilt and restarted!$(NC)"
- @echo "$(BLUE)Starting nginx service...$(NC)"
- @docker compose -f $(COMPOSE_FILE) up -d --no-deps nginx
- @echo "$(BLUE)Starting php83 service...$(NC)"
- @docker compose -f $(COMPOSE_FILE) up -d --no-deps php83
- @echo "$(BLUE)Starting mysql service...$(NC)"
- @docker compose -f $(COMPOSE_FILE) up -d --no-deps mysql
- @make status
-
-## Help
-help:
- @echo "$(GREEN)mLITE Docker Commands$(NC)"
- @echo " make build - Build all services"
- @echo " make up - Start containers"
- @echo " make down - Stop containers"
- @echo " make restart - Restart containers"
- @echo " make status - Show running containers"
- @echo " make logs - Show logs"
- @echo " make logs-follow - Follow logs"
- @echo " make php/mysql/nginx/... - Access containers"
- @echo " make phpmyadmin - Access phpMyAdmin container"
- @echo " make db-backup - Backup database"
- @echo " make db-restore FILE=x - Restore from backup"
- @echo " make orthanc-test - Test Orthanc connection"
- @echo " make orthanc-upload-dummy- Upload dummy DICOM files"
- @echo " make orthanc-status - Show Orthanc system info"
- @echo " make panel - Access Panel container"
- @echo " make nginx-status - Check Nginx Manager status"
- @echo " make nginx-reload - Reload Nginx via Manager"
- @echo " make nginx-logs - Show Nginx Manager logs"
- @echo " make clean - Remove all containers/images"
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
new file mode 100644
index 000000000..4e50311c3
--- /dev/null
+++ b/docker/docker-compose.yaml
@@ -0,0 +1,73 @@
+version: "3"
+services:
+ nginx:
+ build:
+ context: .
+ dockerfile: nginx.Dockerfile
+ image: nginx:alpine
+ # ports:
+ # - ${APP_PORT}:80 # gunakan jika ingin mengakses nginx dari luar container
+ networks:
+ - internal
+ volumes:
+ - ../:/var/www/html/
+ - php_backups:/var/www/html/backups
+ - php_uploads:/var/www/html/uploads
+ - php_systems_data:/var/www/html/systems/data
+ - ./logs/nginx:/var/log/nginx/
+ depends_on:
+ - php
+
+ php:
+ image: php:8.3-fpm-alpine
+ user: root
+ build:
+ context: .
+ dockerfile: php.Dockerfile
+ networks:
+ - internal
+ environment:
+ - DBDRIVER=${DB_DRIVER}
+ - MYSQLHOST=${DB_HOST}
+ - MYSQLPORT=${DB_PORT}
+ - MYSQLUSER=${DB_USERNAME}
+ - MYSQLPASSWORD=${DB_PASSWORD}
+ - MYSQLDATABASE=${DB_DATABASE}
+ volumes:
+ - ../:/var/www/html/
+ - php_backups:/var/www/html/backups
+ - php_uploads:/var/www/html/uploads
+ - php_systems_data:/var/www/html/systems/data
+ command: sh -c "composer install && mkdir -p /var/www/html/backups /var/www/html/uploads /var/www/html/systems/data /var/www/html/tmp /var/www/html/admin/tmp && chmod -R 777 /var/www/html/backups /var/www/html/uploads /var/www/html/systems/data /var/www/html/tmp /var/www/html/admin/tmp && php-fpm"
+
+ mysql:
+ image: mysql:8.0
+ command: --sql-mode=""
+ restart: always
+ profiles:
+ - mysql_enabled
+ environment:
+ MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
+ MYSQL_DATABASE: ${DB_DATABASE}
+ MYSQL_USER: ${DB_USERNAME}
+ MYSQL_PASSWORD: ${DB_PASSWORD}
+ # ports:
+ # - ${DB_EXTERNAL_PORT}:3306 #gunakan jika ingin mengakses mysql dari luar container
+ expose:
+ - 3306
+ networks:
+ - internal
+ volumes:
+ - mysql_data:/var/lib/mysql
+ - ../mlite_db.sql:/docker-entrypoint-initdb.d/init.sql
+
+networks:
+ internal:
+ driver: bridge
+
+volumes:
+ php_backups:
+ php_uploads:
+ php_systems_data:
+ php_plugins:
+ mysql_data:
\ No newline at end of file
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
deleted file mode 100644
index 727362b22..000000000
--- a/docker/docker-compose.yml
+++ /dev/null
@@ -1,282 +0,0 @@
-services:
- php74:
- build:
- context: .
- dockerfile: php/Dockerfile-7.4
- container_name: mlite_php74
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- depends_on:
- - mysql
- networks:
- - app-network
- environment:
- PHP_IDE_CONFIG: serverName=Docker
- restart: unless-stopped
-
- php80:
- build:
- context: .
- dockerfile: php/Dockerfile-8.0
- container_name: mlite_php80
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- depends_on:
- - mysql
- networks:
- - app-network
- environment:
- PHP_IDE_CONFIG: serverName=Docker
- restart: unless-stopped
-
- php81:
- build:
- context: .
- dockerfile: php/Dockerfile-8.1
- container_name: mlite_php81
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- depends_on:
- - mysql
- networks:
- - app-network
- environment:
- PHP_IDE_CONFIG: serverName=Docker
- restart: unless-stopped
-
- php82:
- build:
- context: .
- dockerfile: php/Dockerfile-8.2
- container_name: mlite_php82
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- depends_on:
- - mysql
- networks:
- - app-network
- environment:
- PHP_IDE_CONFIG: serverName=Docker
- restart: unless-stopped
-
- php83:
- build:
- context: .
- dockerfile: php/Dockerfile
- container_name: mlite_php83
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- depends_on:
- - mysql
- networks:
- - app-network
- environment:
- PHP_IDE_CONFIG: serverName=Docker
- restart: unless-stopped
-
- panel:
- build:
- context: ./panel
- dockerfile: Dockerfile
- container_name: mlite_panel
- ports:
- - ${HOST_PROJECT_PORT}:5000
- volumes:
- - ./panel:/app
- - ../:/workspace
- - ./nginx/conf.d:/etc/nginx/conf.d
- - ./nginx:/workspace/docker/nginx
- - ./logs/modsecurity:/var/log/modsecurity:ro
- - /var/run/docker.sock:/var/run/docker.sock
- env_file:
- - .env
- environment:
- - NGINX_CONTAINER_NAME=mlite_nginx
- - FLASK_ENV=development
- - FLASK_DEBUG=1
- - NGINX_CONF_DIR=/workspace/docker/nginx/conf.d
- command: python app.py
- networks:
- - app-network
- restart: unless-stopped
-
- php56:
- build:
- context: .
- dockerfile: php/Dockerfile-5.6
- platform: linux/amd64
- container_name: mlite_php56
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- depends_on:
- - mysql
- networks:
- - app-network
- environment:
- PHP_IDE_CONFIG: serverName=Docker
- restart: unless-stopped
-
- php70:
- build:
- context: .
- dockerfile: php/Dockerfile-7.0
- platform: linux/amd64
- container_name: mlite_php70
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- depends_on:
- - mysql
- networks:
- - app-network
- environment:
- PHP_IDE_CONFIG: serverName=Docker
- restart: unless-stopped
-
- php71:
- build:
- context: .
- dockerfile: php/Dockerfile-7.1
- platform: linux/amd64
- container_name: mlite_php71
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- depends_on:
- - mysql
- networks:
- - app-network
- environment:
- PHP_IDE_CONFIG: serverName=Docker
- restart: unless-stopped
-
- php72:
- build:
- context: .
- dockerfile: php/Dockerfile-7.2
- platform: linux/amd64
- container_name: mlite_php72
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- depends_on:
- - mysql
- networks:
- - app-network
- environment:
- PHP_IDE_CONFIG: serverName=Docker
- restart: unless-stopped
-
- php73:
- build:
- context: .
- dockerfile: php/Dockerfile-7.3
- platform: linux/amd64
- container_name: mlite_php73
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- depends_on:
- - mysql
- networks:
- - app-network
- environment:
- PHP_IDE_CONFIG: serverName=Docker
- restart: unless-stopped
-
- nginx:
- build:
- context: ./nginx
- dockerfile: Dockerfile
- container_name: mlite_nginx
- ports:
- - "80:80"
- - "443:443"
- volumes:
- - ${HOST_PROJECT_DIR}:/var/www/public
- - ./nginx/conf.d:/etc/nginx/conf.d
- - ./nginx/blocked_ips.conf:/etc/nginx/blocked_ips.conf
- - ./certbot/conf:/etc/letsencrypt
- - ./certbot/www:/var/www/certbot
- - ./logs/modsecurity:/var/log/modsecurity
- - ./backups:/backups
- depends_on:
- - php56
- - php70
- - php71
- - php72
- - php73
- - php74
- - php80
- - php81
- - php82
- - php83
- - panel
- networks:
- - app-network
- restart: unless-stopped
-
- mysql:
- image: mysql:8.0
- container_name: mlite_mysql
- restart: always
- environment:
- MYSQL_ALLOW_EMPTY_PASSWORD: true
- MYSQL_DATABASE: mlite
- MYSQL_ROOT_HOST: localhost
- ports:
- - "3308:3306"
- volumes:
- - ./mysql:/var/lib/mysql
- - ../mlite_db.sql:/docker-entrypoint-initdb.d/00-mlite_db.sql
- networks:
- - app-network
-
- phpmyadmin:
- image: phpmyadmin/phpmyadmin
- container_name: mlite_phpmyadmin
- environment:
- PMA_HOST: mysql
- PMA_PORT: 3306
- ports:
- - "888:80"
- depends_on:
- - mysql
- networks:
- - app-network
-
- orthanc:
- build:
- context: .
- dockerfile: orthanc/Dockerfile
- container_name: mlite_orthanc
- restart: unless-stopped
- ports:
- - "8042:8042"
- - "4242:4242"
- volumes:
- - ./orthanc/orthanc-db:/var/lib/orthanc/db
- - ./orthanc/orthanc.json:/etc/orthanc/orthanc.json:ro
- environment:
- - ORTHANC_NAME=Orthanc-Headless
- networks:
- - app-network
-
- dicom:
- image: registry.dto.kemkes.go.id/pub/dicom-router:latest
- container_name: dicom-router
- restart: always
- ports:
- - "11112:11112"
- - "8080:8080"
- environment:
- - AE_TITLE=DCMROUTER
- - ORG_ID=${ORG_ID}
- - CLIENT=${CLIENT}
- - SECRET=${SECRET}
- - WEBHOOK_URL=https://api-satusehat.kemkes.go.id
- - WEBHOOK_USER=
- - WEBHOOK_PASSWORD=
- - URL=https://api-satusehat.kemkes.go.id
- networks:
- - app-network
-
-networks:
- app-network:
- driver: bridge
diff --git a/docker/install.sh b/docker/install.sh
deleted file mode 100644
index 6990a25ce..000000000
--- a/docker/install.sh
+++ /dev/null
@@ -1,192 +0,0 @@
-#!/usr/bin/env bash
-# ==========================================================
-# Universal Docker Static Binary Installer (with --skip-compose)
-# ==========================================================
-
-set -e
-
-green() { echo -e "\033[1;32m$*\033[0m"; }
-yellow() { echo -e "\033[1;33m$*\033[0m"; }
-red() { echo -e "\033[1;31m$*\033[0m"; }
-
-if [ "$EUID" -ne 0 ]; then
- red "❌ Please run as root or with sudo."
- exit 1
-fi
-
-VERSION="latest"
-SKIP_COMPOSE=false
-
-for arg in "$@"; do
- case "$arg" in
- --skip-compose) SKIP_COMPOSE=true ;;
- *) VERSION="$arg" ;;
- esac
-done
-
-ARCH=$(uname -m)
-case "$ARCH" in
- x86_64) DOCKER_ARCH="x86_64" ;;
- aarch64) DOCKER_ARCH="aarch64" ;;
- armv7l) DOCKER_ARCH="armhf" ;;
- *) red "❌ Unsupported architecture: $ARCH"; exit 1 ;;
-esac
-
-green "Detected architecture: $DOCKER_ARCH"
-
-if [ "$VERSION" = "latest" ]; then
- yellow "Fetching latest Docker version..."
- VERSION=$(curl -fsSL https://download.docker.com/linux/static/stable/$DOCKER_ARCH/ \
- | grep -Eo 'docker-[0-9]+\.[0-9]+\.[0-9]+' \
- | sort -V | tail -n1 | sed 's/docker-//')
-fi
-green "Installing Docker version: $VERSION"
-
-URL="https://download.docker.com/linux/static/stable/$DOCKER_ARCH/docker-$VERSION.tgz"
-yellow "Downloading from: $URL"
-curl -fsSL "$URL" -o /tmp/docker.tgz
-
-tar -xzf /tmp/docker.tgz -C /tmp/
-install -m 0755 /tmp/docker/* /usr/local/bin/
-
-echo
-echo '🚀 Starting Docker daemon in background...'
-nohup dockerd > /var/log/dockerd.log 2>&1 &
-sleep 5
-
-echo
-echo '📦 Installing Docker Compose plugin...'
-mkdir -p ~/.docker/cli-plugins/
-curl -sSL https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m) -o ~/.docker/cli-plugins/docker-compose
-chmod +x ~/.docker/cli-plugins/docker-compose
-docker compose version && echo '✅ Docker Compose installed successfully!'
-
-echo
-echo "🔍 Detecting architecture..."
-ARCH=$(uname -m)
-case "$ARCH" in
- x86_64|amd64) BX_ARCH="amd64" ;;
- aarch64|arm64) BX_ARCH="arm64" ;;
- armv7l|armv7) BX_ARCH="arm-v7" ;;
- *) echo "❌ Unsupported architecture: $ARCH"; exit 1 ;;
-esac
-echo "Detected architecture: $BX_ARCH"
-
-echo
-echo "⬇️ Fetching latest Buildx release info..."
-BUILDX_VERSION=$(curl -s https://api.github.com/repos/docker/buildx/releases/latest | grep tag_name | cut -d '"' -f 4)
-BUILDX_URL="https://github.com/docker/buildx/releases/download/${BUILDX_VERSION}/buildx-${BUILDX_VERSION}.linux-${BX_ARCH}"
-
-echo "📦 Downloading Buildx binary: ${BUILDX_URL}"
-mkdir -p ~/.docker/cli-plugins/
-curl -fsSL "$BUILDX_URL" -o ~/.docker/cli-plugins/docker-buildx
-chmod +x ~/.docker/cli-plugins/docker-buildx
-
-echo
-echo "🔎 Checking binary type..."
-file ~/.docker/cli-plugins/docker-buildx
-
-echo
-echo "🧩 Testing Buildx..."
-docker buildx version || echo "⚠️ Buildx still not executable – check architecture."
-
-if command -v systemctl >/dev/null 2>&1; then
- yellow "Setting up systemd service..."
- cat >/etc/systemd/system/docker.service <<'EOF'
-[Unit]
-Description=Docker Application Container Engine
-Documentation=https://docs.docker.com
-After=network-online.target firewalld.service
-Wants=network-online.target
-
-[Service]
-ExecStart=/usr/local/bin/dockerd -H fd://
-ExecReload=/bin/kill -s HUP $MAINPID
-LimitNOFILE=1048576
-LimitNPROC=infinity
-LimitCORE=infinity
-TasksMax=infinity
-TimeoutStartSec=0
-Restart=on-failure
-
-[Install]
-WantedBy=multi-user.target
-EOF
-
- systemctl daemon-reload
- systemctl enable docker --now
-else
- yellow "Systemd not found. You can start Docker manually with:"
- echo " dockerd &"
-fi
-
-groupadd -f docker
-usermod -aG docker "${SUDO_USER:-$USER}" || true
-
-rm -rf /tmp/docker /tmp/docker.tgz
-green "✅ Docker static binaries installed successfully!"
-
-if [ "$SKIP_COMPOSE" = "true" ]; then
- echo "⏭️ Skipping Docker Compose build/up step (--skip-compose used)."
- exit 0
-fi
-
-echo
-echo '🚀 Running compose commands...'
-docker compose -f docker-compose.yml build panel
-docker compose -f docker-compose.yml up -d panel
-echo '✅ Docker Compose panel service started successfully!'
-
-# ==========================================================
-# 🎉 Final Info Section (revisi anti-hang)
-# ==========================================================
-
-# Ambil alamat IP server
-echo
-echo "🌐 Detecting server IP address..."
-address=$(hostname -I 2>/dev/null | awk '{print $1}')
-
-# Jika hostname -I gagal, coba fallback ke ifconfig.me (timeout 3 detik)
-if [ -z "$address" ]; then
- address=$(curl -s --max-time 3 ifconfig.me || echo "")
-fi
-
-# Jika tetap kosong, beri default
-if [ -z "$address" ]; then
- address="SERVER_IP"
-fi
-
-# ==========================================================
-# 🔍 Baca variabel dari docker/.env
-# ==========================================================
-ENV_FILE=".env"
-if [ -f "$ENV_FILE" ]; then
- yellow "Loading environment variables from $ENV_FILE..."
- # Hindari error jika .env kosong
- set +e
- export $(grep -v '^#' "$ENV_FILE" | xargs) 2>/dev/null || true
- set -e
-else
- red "⚠️ File $ENV_FILE not found. Using default values."
-fi
-
-# Ambil nilai dari env atau gunakan default
-port=${PORT:-7788}
-username=${USERNAME:-admin}
-password=${PASSWORD:-admin}
-
-# ==========================================================
-# ✅ Tampilkan hasil akhir
-# ==========================================================
-echo
-echo -e "=================================================================="
-echo -e "\033[32m🎉 Congratulations! Install succeeded!\033[0m"
-echo -e "=================================================================="
-echo "🌐 mLITE-Panel: http://$address:$port"
-echo -e "👤 username: $username"
-echo -e "🔑 password: $password"
-echo
-echo -e "\033[33m⚠️ Warning:\033[0m"
-echo -e "\033[33mIf you cannot access the panel,\033[0m"
-echo -e "\033[33mrelease the following ports (7788|80|443) in the firewall.\033[0m"
-echo -e "=================================================================="
diff --git a/docker/nginx.Dockerfile b/docker/nginx.Dockerfile
new file mode 100644
index 000000000..eb39f7ed3
--- /dev/null
+++ b/docker/nginx.Dockerfile
@@ -0,0 +1,6 @@
+FROM nginx:alpine
+
+COPY nginx/default.conf /etc/nginx/conf.d
+
+# COPY . /var/www/html
+
diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile
deleted file mode 100644
index 0bf96453a..000000000
--- a/docker/nginx/Dockerfile
+++ /dev/null
@@ -1,104 +0,0 @@
-FROM nginx:latest
-
-# Install build dependencies for ModSecurity and NGINX
-RUN apt-get update && apt-get install -y \
- bison \
- build-essential \
- ca-certificates \
- curl \
- dh-autoreconf \
- doxygen \
- git \
- zip \
- unzip \
- libcurl4-openssl-dev \
- liblmdb-dev \
- libxml2-dev \
- libyajl-dev \
- pkg-config \
- wget \
- zlib1g-dev \
- libpcre2-dev \
- libtool \
- automake \
- autoconf \
- certbot \
- cron \
- mariadb-client \
- && rm -rf /var/lib/apt/lists/*
-
-# Prepare ACME webroot
-RUN mkdir -p /var/www/certbot/.well-known/acme-challenge
-
-# Configure cron tasks for certbot renew and backups
-RUN mkdir -p /backups/mysql /backups/web && \
- cat > /etc/cron.d/mlite <<'EOF'
-# mLITE scheduled tasks
-SHELL=/bin/sh
-PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
-# Certbot renew daily at 02:00
-0 2 * * * root certbot renew --quiet && nginx -s reload
-# MySQL backup daily at 03:00
-0 3 * * * root mysqldump -h mysql -P 3306 -umlite -pmlite123 mlite_db > /backups/mysql/mysql_$(date +\%Y\%m\%d_\%H\%M\%S).sql
-# Website backup daily at 03:10
-10 3 * * * root tar --exclude=./docker/backups -czf /backups/web/web_$(date +\%Y\%m\%d_\%H\%M\%S).tar.gz -C /var/www/public .
-EOF
-RUN chmod 0644 /etc/cron.d/mlite
-
-# Build and install libmodsecurity (ModSecurity v3)
-RUN set -eux; \
- cd /tmp; \
- git clone --depth 1 https://github.com/owasp-modsecurity/ModSecurity; \
- cd ModSecurity; \
- git submodule init; \
- git submodule update; \
- ./build.sh; \
- ./configure; \
- make; \
- make install; \
- ldconfig
-
-# Download ModSecurity NGINX connector
-RUN set -eux; \
- cd /tmp; \
- git clone --depth 1 https://github.com/SpiderLabs/ModSecurity-nginx.git
-
-# Build dynamic module against the exact NGINX version from the base image
-RUN set -eux; \
- ver=$(nginx -v 2>&1 | sed -E 's#.*nginx/([0-9.]+).*#\1#'); \
- cd /tmp; \
- wget "https://nginx.org/download/nginx-${ver}.tar.gz"; \
- tar -xzf "nginx-${ver}.tar.gz"; \
- cd "nginx-${ver}"; \
- ./configure --with-compat --add-dynamic-module=/tmp/ModSecurity-nginx; \
- make modules; \
- cp objs/ngx_http_modsecurity_module.so /etc/nginx/modules/
-
-# Configure ModSecurity and OWASP CRS
-RUN set -eux; \
- mkdir -p /etc/nginx/modsec /var/log/modsecurity; \
- cd /etc/nginx/modsec; \
- git clone --depth 1 https://github.com/coreruleset/coreruleset owasp-crs; \
- cp owasp-crs/crs-setup.conf.example owasp-crs/crs-setup.conf; \
- sed -i 's/^\s*SecDefaultAction/# SecDefaultAction (disabled by image build)/' /etc/nginx/modsec/owasp-crs/crs-setup.conf; \
- printf '%s\n' \
- 'SecRuleEngine On' \
- 'SecRequestBodyAccess On' \
- 'SecResponseBodyAccess Off' \
- 'SecAuditEngine RelevantOnly' \
- 'SecAuditLog /var/log/modsecurity/audit.log' \
- 'Include /etc/nginx/modsec/modsecurity.conf' \
- 'Include /etc/nginx/modsec/owasp-crs/crs-setup.conf' \
- 'Include /etc/nginx/modsec/owasp-crs/rules/*.conf' \
- > /etc/nginx/modsec/main.conf
-
-# Copy ModSecurity base config
-COPY modsecurity.conf /etc/nginx/modsec/modsecurity.conf
-
-# Copy custom nginx.conf that loads the ModSecurity module
-COPY nginx.conf /etc/nginx/nginx.conf
-
-# Expose ports (already mapped via docker-compose)
-EXPOSE 80 8080
-
-CMD ["sh", "-lc", "/usr/sbin/cron && exec nginx -g 'daemon off;'"]
diff --git a/docker/nginx/blocked_ips.conf b/docker/nginx/blocked_ips.conf
deleted file mode 100644
index e69de29bb..000000000
diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf
new file mode 100644
index 000000000..1272754b6
--- /dev/null
+++ b/docker/nginx/default.conf
@@ -0,0 +1,33 @@
+server {
+ listen 0.0.0.0:80;
+
+ root /var/www/html;
+ client_max_body_size 100M;
+
+
+ location / {
+ index index.php;
+ if (!-e $request_filename) {
+ rewrite / /index.php last;
+ }
+ }
+
+ location ^~ /systems/data/ {
+ deny all;
+ return 403;
+ }
+
+ location /admin {
+ index index.php;
+ try_files $uri $uri/ /admin/index.php?$args;
+ }
+
+ location ~ \.php$ {
+ include fastcgi_params;
+ fastcgi_pass php:9000;
+ fastcgi_index index.php;
+ fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
+ }
+
+
+}
\ No newline at end of file
diff --git a/docker/nginx/modsecurity.conf b/docker/nginx/modsecurity.conf
deleted file mode 100644
index a21e00919..000000000
--- a/docker/nginx/modsecurity.conf
+++ /dev/null
@@ -1,32 +0,0 @@
-# Base ModSecurity configuration (Nginx-compatible)
-
-# Core engine settings
-SecRuleEngine On
-SecRequestBodyAccess On
-SecResponseBodyAccess Off
-
-# Body limits (tune as needed)
-SecRequestBodyLimit 13107200
-SecRequestBodyNoFilesLimit 131072
-SecResponseBodyLimit 524288
-
-# Temporary and persistent storage
-SecTmpDir /tmp
-SecDataDir /tmp
-
-# Debug logging (set level 0-9; 0 disables debug)
-SecDebugLog /var/log/modsecurity/debug.log
-SecDebugLogLevel 0
-
-# Audit logging
-SecAuditEngine On
-SecAuditLog /var/log/modsecurity/audit.log
-SecAuditLogFormat JSON
-SecAuditLogParts ABIJDEFHKZ
-SecAuditLogType Serial
-
-# Status engine (basic runtime info)
-SecStatusEngine On
-
-# Note: OWASP CRS includes are loaded via /etc/nginx/modsec/main.conf
-# This file focuses on baseline engine and logging configuration only.
diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf
deleted file mode 100644
index a872aff53..000000000
--- a/docker/nginx/nginx.conf
+++ /dev/null
@@ -1,38 +0,0 @@
-# Load ModSecurity dynamic module
-load_module modules/ngx_http_modsecurity_module.so;
-
-user root;
-worker_processes auto;
-
-error_log /var/log/nginx/error.log warn;
-pid /var/run/nginx.pid;
-
-events {
- worker_connections 1024;
-}
-
-http {
- include /etc/nginx/mime.types;
- default_type application/octet-stream;
-
- # Logging
- log_format main '$realip_remote_addr - $remote_user [$time_local] "$request" '
- '$status $body_bytes_sent "$http_referer" '
- '"$http_user_agent" "$http_x_forwarded_for"';
-
- access_log /var/log/nginx/access.log main;
- access_log /dev/null combined;
-
- error_log /var/log/nginx/error.log warn;
-
- # Enable ModSecurity globally and load rules
- modsecurity on;
- modsecurity_rules_file /etc/nginx/modsec/main.conf;
-
- sendfile on;
- keepalive_timeout 65;
- server_tokens off;
-
- # Load site configs
- include /etc/nginx/conf.d/*.conf;
-}
\ No newline at end of file
diff --git a/docker/orthanc/Dockerfile b/docker/orthanc/Dockerfile
deleted file mode 100644
index 193bddf0d..000000000
--- a/docker/orthanc/Dockerfile
+++ /dev/null
@@ -1,3 +0,0 @@
-FROM orthancteam/orthanc:latest
-
-COPY orthanc/orthanc.json /etc/orthanc/orthanc.json
diff --git a/docker/orthanc/orthanc.json b/docker/orthanc/orthanc.json
deleted file mode 100644
index 359172ebb..000000000
--- a/docker/orthanc/orthanc.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "Name": "OrthancHeadless",
- "HttpServerEnabled": true,
- "HttpPort": 8042,
- "RemoteAccessAllowed": true,
- "AuthenticationEnabled": true,
- "RegisteredUsers": {
- "orthanc": "orthanc"
- },
- "DicomAet": "ORTHANC",
- "DicomPort": 4242,
- "DicomModalities": {
- "DCMROUTER": [ "DCMROUTER", "172.18.0.8", 11112 ]
- },
- "DicomWeb": {
- "Enable": true,
- "Root": "/dicom-web/",
- "StowRS": true,
- "WadoRS": true,
- "QidoRS": true
- },
- "HttpServer": {
- "EnableCors": true
- },
- "OrthancExplorer": {
- "Enable": false
- },
- "StorageDirectory": "/var/lib/orthanc/db"
-}
diff --git a/docker/panel/Dockerfile b/docker/panel/Dockerfile
deleted file mode 100644
index a3140bb38..000000000
--- a/docker/panel/Dockerfile
+++ /dev/null
@@ -1,30 +0,0 @@
-FROM python:3.11-slim
-
-WORKDIR /app
-
-# Install Docker CLI and Compose plugin from official Docker repo
-RUN apt-get update && apt-get install -y \
- ca-certificates curl gnupg \
- && install -m 0755 -d /etc/apt/keyrings \
- && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
- && chmod a+r /etc/apt/keyrings/docker.gpg \
- && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list \
- && apt-get update \
- && apt-get install -y docker-ce-cli docker-compose-plugin \
- && rm -rf /var/lib/apt/lists/*
-
-# Copy requirements and install Python dependencies
-COPY requirements.txt .
-RUN pip install --no-cache-dir -r requirements.txt
-
-# Copy application code
-COPY . .
-
-# Create nginx conf directory
-RUN mkdir -p /etc/nginx/conf.d
-
-# Expose port
-EXPOSE 5000
-
-# Run the application
-CMD ["python", "app.py"]
\ No newline at end of file
diff --git a/docker/panel/app.py b/docker/panel/app.py
deleted file mode 100644
index 985af8efb..000000000
--- a/docker/panel/app.py
+++ /dev/null
@@ -1,6693 +0,0 @@
-import os
-import subprocess
-import requests
-import shlex
-import re
-import shutil
-import yaml
-import json
-from datetime import datetime, timezone, timedelta
-from flask import Flask, render_template, request, redirect, url_for, flash, jsonify, Response, session
-from flask import stream_with_context
-import logging
-import time
-from functools import wraps
-from collections import deque
-
-logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s in %(module)s: %(message)s')
-logger = logging.getLogger(__name__)
-
-# MySQL (Docker-based) helpers
-# Use docker exec to run mysql commands inside the mlite_mysql container.
-# Provide minimal compatibility with cursor/connection interface used by routes.
-try:
- import mysql.connector # optional, only for Error types referenced in except blocks
-except Exception:
- class _DummyMySQLError(Exception):
- pass
- class _DummyPoolError(Exception):
- pass
- class _DummyPooling:
- class MySQLConnectionPool:
- def __init__(self, **kwargs):
- pass
- def _remove_connections(self):
- pass
- class _DummyConnector:
- Error = _DummyMySQLError
- PoolError = _DummyPoolError
- pooling = _DummyPooling
- class mysql:
- connector = _DummyConnector()
-
-MYSQL_CONTAINER_NAME = os.environ.get('MYSQL_CONTAINER_NAME', 'mlite_mysql')
-
-def get_mysql_config_path():
- """Return path to persisted MySQL config JSON for root access."""
- try:
- base_dir = os.path.dirname(os.path.abspath(__file__))
- return os.path.join(base_dir, 'mysql_config.json')
- except Exception:
- return 'mysql_config.json'
-
-
-def load_mysql_config():
- """Load MySQL access config from JSON (root user/password)."""
- default_cfg = {
- 'user': 'root',
- 'password': '',
- 'host': 'localhost', # within container
- }
- cfg_path = get_mysql_config_path()
- try:
- if os.path.exists(cfg_path):
- with open(cfg_path, 'r') as f:
- data = json.load(f)
- if isinstance(data, dict):
- # ensure keys
- for k in default_cfg:
- data.setdefault(k, default_cfg[k])
- return data
- except Exception as e:
- logger.warning(f"Failed to load MySQL config: {e}")
- return default_cfg
-
-
-def save_mysql_config(cfg: dict) -> bool:
- """Persist MySQL access config to JSON file."""
- try:
- cfg_path = get_mysql_config_path()
- with open(cfg_path, 'w') as f:
- json.dump(cfg or {}, f)
- return True
- except Exception as e:
- logger.error(f"Failed to save MySQL config: {e}")
- return False
-
-# Global configuration used by routes
-_db_cfg_loaded = load_mysql_config()
-# Expose as the name expected by routes
-db_config = {
- 'user': _db_cfg_loaded.get('user', 'root'),
- 'password': _db_cfg_loaded.get('password', ''),
- # The following keys are kept for compatibility when routes pass db_config to pooling
- 'host': _db_cfg_loaded.get('host', 'localhost'),
- 'database': None,
-}
-connection_pool = None # not used with docker-based access, kept for compatibility
-
-
-def _build_mysql_cli_args(user: str, password: str, database: str = None, batch: bool = True, skip_column_names: bool = False):
- args = ['mysql', f'-u{user}']
- if password:
- args.append(f'-p{password}')
- if database:
- args.extend(['-D', database])
- if batch:
- args.append('--batch')
- args.append('--raw')
- if skip_column_names:
- args.append('--skip-column-names')
- return args
-
-
-def _exec_mysql(sql: str, db: str = None, expect_output: bool = True, dictionary: bool = False):
- """Run given SQL inside MySQL container and parse output.
- - When dictionary=True, keep column names in first line and return list of dicts.
- - When dictionary=False, skip column names and return list of lists.
- """
- # Prefer latest credentials from mysql_config.json if available
- try:
- cfg = load_mysql_config()
- user = cfg.get('user', db_config.get('user', 'root'))
- pwd = cfg.get('password', db_config.get('password', ''))
- except Exception:
- user = db_config.get('user', 'root')
- pwd = db_config.get('password', '')
- skip_cols = not dictionary
- cli_args = _build_mysql_cli_args(user, pwd, database=db, batch=True, skip_column_names=skip_cols)
- cmd = ['docker', 'exec', MYSQL_CONTAINER_NAME] + cli_args + ['-e', sql]
- try:
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
- if result.returncode != 0:
- stderr = (result.stderr or '').strip()
- stdout = (result.stdout or '').strip()
- raise RuntimeError(stderr or stdout or 'mysql cli error')
- out = (result.stdout or '').strip()
- if not expect_output:
- return []
- if not out:
- return []
- lines = out.splitlines()
- # Parse tab-separated output
- if dictionary:
- if not lines:
- return []
- headers = lines[0].split('\t')
- rows = []
- for line in lines[1:]:
- cols = line.split('\t')
- row = {headers[i]: (cols[i] if i < len(cols) else None) for i in range(len(headers))}
- rows.append(row)
- return rows
- else:
- rows = []
- for line in lines:
- cols = line.split('\t')
- rows.append(cols)
- return rows
- except Exception as e:
- logger.error(f"MySQL exec error: {e}")
- raise
-
-
-def _format_sql_with_params(sql: str, params: tuple or list or None):
- if not params:
- return sql
- def esc(val):
- s = str(val)
- s = s.replace("\\", "\\\\").replace("'", "\\'")
- return f"'{s}'"
- out = sql
- for p in params:
- out = out.replace('%s', esc(p), 1)
- return out
-
-
-class DockerMySQLCursor:
- def __init__(self, dictionary: bool = False, database: str or None = None):
- self.dictionary = dictionary
- self.database = database
- self._last_result = []
- self.rowcount = 0
- def execute(self, sql: str, params: tuple or list or None = None):
- formatted = _format_sql_with_params(sql, params)
- # Decide if we expect output: SELECT/SHOW/DESCRIBE returns rows
- is_select_like = bool(re.match(r"\s*(SELECT|SHOW|DESCRIBE|EXPLAIN)\b", formatted, re.IGNORECASE))
- if is_select_like:
- self._last_result = _exec_mysql(formatted, db=self.database, expect_output=True, dictionary=self.dictionary)
- # For select-like, rowcount equals number of returned rows
- try:
- self.rowcount = len(self._last_result)
- except Exception:
- self.rowcount = 0
- else:
- # Execute DML/DDL, then query ROW_COUNT() to emulate affected rows
- rows = _exec_mysql(f"{formatted}; SELECT ROW_COUNT();", db=self.database, expect_output=True, dictionary=False)
- # ROW_COUNT() result should be last line
- try:
- if rows:
- last = rows[-1]
- # rows are list of columns; take first value
- val = int((last[0] if isinstance(last, (list, tuple)) and last else last))
- self.rowcount = val
- else:
- self.rowcount = 0
- except Exception:
- self.rowcount = 0
- # Non-select queries don't have result sets
- self._last_result = []
- def fetchall(self):
- return self._last_result
- def fetchone(self):
- try:
- return self._last_result[0] if self._last_result else None
- except Exception:
- return None
- def close(self):
- pass
-
-
-class DockerMySQLConnection:
- def __init__(self, database: str or None = None):
- self.database = database
- self._autocommit = True
- def cursor(self, dictionary: bool = False):
- return DockerMySQLCursor(dictionary=dictionary, database=self.database)
- def commit(self):
- # mysql CLI executes in autocommit by default, nothing needed
- pass
- def close(self):
- pass
- # Provide minimal compatibility with connector API
- @property
- def autocommit(self):
- return self._autocommit
- @autocommit.setter
- def autocommit(self, value):
- # No effect; mysql CLI runs statements independently
- self._autocommit = bool(value)
- def cmd_query(self, sql: str):
- # Handle USE to switch default database; otherwise run as no-output command
- m = re.match(r"\s*USE\s+`?([a-zA-Z0-9_]+)`?\s*;?\s*$", sql, re.IGNORECASE)
- if m:
- self.database = m.group(1)
- try:
- _exec_mysql(sql, db=self.database, expect_output=False, dictionary=False)
- except Exception as e:
- # Surface error via exception to mimic connector behavior
- raise e
-
-
-def create_mysql_connection(database: str or None = None):
- """Return a docker-backed MySQL connection shim compatible with route usage."""
- return DockerMySQLConnection(database=database)
-
-
-# Helper untuk memformat timestamp Docker menjadi "YYYY-MM-DD H:i:s" tanpa zona waktu
-def format_created_field(s: str) -> str:
- try:
- if not s:
- return ''
- m = re.search(r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})', s)
- return m.group(1) if m else s
- except Exception:
- return s
-
-app = Flask(__name__)
-app.secret_key = 'nginx-manager-secret-key'
-# Default admin credentials (can be overridden via env PANEL_ADMIN_USER/PANEL_ADMIN_PASS)
-app.config['ADMIN_USER'] = os.environ.get('PANEL_ADMIN_USER', 'admin')
-app.config['ADMIN_PASS'] = os.environ.get('PANEL_ADMIN_PASS', 'admin123')
-
-@app.context_processor
-def inject_config():
- return dict(config=app.config)
-
-# Configuration
-NGINX_CONF_DIR = os.environ.get('NGINX_CONF_DIR', os.path.join(os.environ.get('HOST_PROJECT_DIR', '/Users/basoro/Slemp/data/www/mlite.loc'), 'docker', 'nginx', 'conf.d'))
-NGINX_CONTAINER_NAME = os.environ.get('NGINX_CONTAINER_NAME', 'mlite_nginx')
-PHP_CONTAINER_NAME = os.environ.get('PHP_CONTAINER_NAME', 'mlite_php')
-# Base web root inside containers (mounted from host ../)
-PHP_WEBROOT_BASE = '/var/www/public'
-# Optional toggle to create default index.html after directory creation (env CREATE_DEFAULT_INDEX=1/0)
-CREATE_DEFAULT_INDEX = os.environ.get('CREATE_DEFAULT_INDEX', '1') == '1'
-
-# Simple in-memory cache for Nginx network stats
-NETWORK_STATS_CACHE = {
- 'history': deque(maxlen=60), # keep last 60 samples
- 'last_fetch_at': 0,
- 'last_value': None
-}
-NETWORK_STATS_POLL_INTERVAL_SEC = 3
-
-def parse_size_to_bytes(s: str) -> int:
- try:
- s = s.strip()
- parts = s.split()
- if len(parts) == 1:
- val_unit = parts[0]
- num_str = ''.join(ch for ch in val_unit if (ch.isdigit() or ch == '.' or ch == ','))
- unit = ''.join(ch for ch in val_unit if ch.isalpha())
- else:
- num_str, unit = parts[0], parts[1]
- num = float(num_str.replace(',', '')) if num_str else 0.0
- unit = unit.upper() if unit else 'B'
- if unit in ['B']:
- mult = 1
- elif unit in ['KB', 'KIB', 'K']:
- mult = 1024
- elif unit in ['MB', 'MIB', 'M']:
- mult = 1024**2
- elif unit in ['GB', 'GIB', 'G']:
- mult = 1024**3
- elif unit in ['TB', 'TIB', 'T']:
- mult = 1024**4
- else:
- mult = 1
- return int(num * mult)
- except Exception:
- return 0
-
-def get_nginx_netio_once():
- """Sample rx/tx bytes for the nginx container using `docker stats --no-stream`.
- Returns (rx_bytes, tx_bytes)."""
- try:
- result = subprocess.run(
- ['docker', 'stats', '--no-stream', '--format', '{{json .}}', NGINX_CONTAINER_NAME],
- capture_output=True, text=True, timeout=8
- )
- if result.returncode != 0:
- raise RuntimeError(result.stderr or 'docker stats failed')
- line = (result.stdout or '').strip().splitlines()
- if not line:
- raise RuntimeError('no stats output')
- obj = json.loads(line[0])
- netio = obj.get('NetIO', '')
- parts = [p.strip() for p in netio.split('/')]
- rx_str = parts[0] if len(parts) > 0 else '0 B'
- tx_str = parts[1] if len(parts) > 1 else '0 B'
- rx = parse_size_to_bytes(rx_str)
- tx = parse_size_to_bytes(tx_str)
- return rx, tx
- except Exception as e:
- logger.error(f"Error sampling nginx net stats: {e}")
- return 0, 0
-
-def get_container_network_stats():
- """Return time series of nginx container network stats (rx/tx total bytes).
- Uses simple caching to avoid frequent docker calls."""
- now = time.time()
- try:
- if now - (NETWORK_STATS_CACHE['last_fetch_at'] or 0) >= NETWORK_STATS_POLL_INTERVAL_SEC:
- rx, tx = get_nginx_netio_once()
- NETWORK_STATS_CACHE['last_fetch_at'] = now
- NETWORK_STATS_CACHE['last_value'] = (rx, tx)
- NETWORK_STATS_CACHE['history'].append({
- 'timestamp': datetime.now().strftime('%H:%M'),
- 'rx': rx,
- 'tx': tx
- })
- if not NETWORK_STATS_CACHE['history']:
- rx, tx = get_nginx_netio_once()
- NETWORK_STATS_CACHE['last_fetch_at'] = now
- NETWORK_STATS_CACHE['last_value'] = (rx, tx)
- NETWORK_STATS_CACHE['history'].append({
- 'timestamp': datetime.now().strftime('%H:%M'),
- 'rx': rx,
- 'tx': tx
- })
- return list(NETWORK_STATS_CACHE['history'])
- except Exception as e:
- logger.error(f"Error building network stats: {e}")
- return []
-
-def get_sites():
- """Get list of configured sites with detected type and relevant info"""
- sites = []
- try:
- if os.path.exists(NGINX_CONF_DIR):
- for filename in os.listdir(NGINX_CONF_DIR):
- if filename.endswith('.conf'):
- domain = filename.replace('.conf', '')
- filepath = os.path.join(NGINX_CONF_DIR, filename)
-
- site_type = None
- port = None
- root_dir = None
- try:
- with open(filepath, 'r') as f:
- content = f.read()
- # Detect type and extract information
- # PHP-FPM detection: parse fastcgi_pass host:9000 to infer version
- m_fcgi = re.search(r"fastcgi_pass\s+([a-zA-Z0-9_-]+):9000", content)
- if m_fcgi:
- site_type = 'php'
- php_host = m_fcgi.group(1)
- php_version = None
- # Map host to version (supports php56–php83)
- m_ver = re.search(r"php(\d{2})", php_host)
- if m_ver:
- code = m_ver.group(1)
- if code == '56':
- php_version = '5.6'
- elif code == '70':
- php_version = '7.0'
- elif code == '71':
- php_version = '7.1'
- elif code == '72':
- php_version = '7.2'
- elif code == '73':
- php_version = '7.3'
- elif code == '74':
- php_version = '7.4'
- elif code == '80':
- php_version = '8.0'
- elif code == '81':
- php_version = '8.1'
- elif code == '82':
- php_version = '8.2'
- elif code == '83':
- php_version = '8.3'
- # extract root directive
- m = re.search(r"^\s*root\s+([^;]+);", content, re.MULTILINE)
- if m:
- root_dir = m.group(1).strip()
- elif 'proxy_pass http://localhost:' in content:
- site_type = 'proxy'
- for line in content.split('\n'):
- if 'proxy_pass http://localhost:' in line:
- port = line.split('proxy_pass http://localhost:')[1].split(';')[0].strip()
- break
- else:
- # Detect static site by try_files fallback to index.html and absence of PHP fastcgi
- if re.search(r"try_files\s+\$uri\s+\$uri/\s+/index\.html", content) and 'fastcgi_pass' not in content:
- site_type = 'static'
- m = re.search(r"^\s*root\s+([^;]+);", content, re.MULTILINE)
- if m:
- root_dir = m.group(1).strip()
- except Exception as e:
- print(f"Error reading config file {filename}: {e}")
-
- sites.append({
- 'domain': domain,
- 'type': site_type or 'proxy',
- 'port': port,
- 'root': root_dir,
- 'php_version': php_version if site_type == 'php' else None,
- 'config_file': filename,
- 'created_at': datetime.fromtimestamp(os.path.getctime(filepath)).strftime('%Y-%m-%d %H:%M:%S')
- })
- except Exception as e:
- print(f"Error getting sites: {e}")
-
- return sites
-
-def safe_join(base, path):
- """Safely join and normalize a path ensuring it stays within base."""
- if not path:
- return base
- if not path.startswith('/'):
- path = '/' + path
- # Normalize path
- normalized = os.path.normpath(path)
- # Map any /var/www/public* to container base
- if normalized.startswith('/var/www/public'):
- target = normalized
- else:
- # Constrain to base directory
- target = os.path.normpath(os.path.join(PHP_WEBROOT_BASE, normalized.lstrip('/')))
- # Ensure target stays within base
- base_norm = os.path.normpath(PHP_WEBROOT_BASE)
- if not target.startswith(base_norm):
- raise ValueError('Invalid root directory: must be under /var/www/public')
- return target
-
-
-def ensure_directory(path):
- """Create directory via Nginx container (shared mount) if not exists with 0755 permissions and root ownership."""
- try:
- quoted = shlex.quote(path)
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"mkdir -p {quoted} && chmod 755 {quoted} && chown root:root {quoted}"]
- result = subprocess.run(cmd, capture_output=True, text=True)
- if result.returncode == 0:
- return True, 'Directory ensured via container'
- return False, f'Failed to create directory via container: {result.stderr.strip() or result.stdout.strip()}'
- except Exception as e:
- return False, f'Failed to create directory: {str(e)}'
-
-# Create a default index.html for PHP-FPM site root
-# The file is written inside the container (nginx/php) since panel container doesn't mount webroot
-# Includes site name and current timestamp with simple styling
-
-def create_default_index(root_dir, domain):
- try:
- now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- index_path = os.path.join(root_dir, 'index.html')
- title = f"Welcome to {domain}"
- content = f"""
-
-
-
-
- {title}
-
-
-
-
-
{title}
-
Generated on {now}
-
Root directory: {root_dir}
-
Replace this page with your app or index.php.
-
-
-
-"""
- quoted_index = shlex.quote(index_path)
- last_err = None
- # Write via Nginx container (shared mount). Fallbacks are unnecessary with unified mount.
- try:
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"cat > {quoted_index} << 'EOF'\n{content}\nEOF\nchmod 644 {quoted_index} && chown root:root {quoted_index}"]
- res = subprocess.run(cmd, capture_output=True, text=True)
- if res.returncode == 0:
- return True, 'Default index.html created'
- last_err = res.stderr.strip() or res.stdout.strip()
- except Exception as e:
- last_err = str(e)
- return False, f'Failed to create index.html: {last_err or "unknown error"}'
- except Exception as e:
- return False, f'Failed to prepare index content: {str(e)}'
-
-
-def create_nginx_config(domain, site_type='proxy', port=None, root_dir='/var/www/public', php_version='8.3'):
- """Create nginx configuration file for either reverse proxy or PHP-FPM"""
- if site_type == 'php':
- # Map php_version to container host name
- version_map = {
- '5.6': 'php56',
- '7.0': 'php70',
- '7.1': 'php71',
- '7.2': 'php72',
- '7.3': 'php73',
- '7.4': 'php74',
- '8.0': 'php80',
- '8.1': 'php81',
- '8.2': 'php82',
- '8.3': 'php83',
- }
- php_host = version_map.get(php_version, 'php83')
- config_content = f"""server {{
- listen 80;
- server_name {domain};
-
- root {root_dir};
- index index.php index.html;
-
- access_log /var/log/nginx/access.log;
- error_log /var/log/nginx/error.log;
-
- location / {{
- try_files $uri $uri/ /index.php?$args;
- }}
-
- location /admin {{
- try_files $uri $uri/ /admin/index.php?$args;
- }}
-
- location ~ \\.php$ {{
- include fastcgi_params;
- fastcgi_pass {php_host}:9000;
- fastcgi_index index.php;
- fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-
- fastcgi_buffer_size 128k;
- fastcgi_buffers 4 256k;
- fastcgi_busy_buffers_size 256k;
- }}
-
- # Izinkan hanya untuk ACME challenge
- location ^~ /.well-known/acme-challenge/ {{
- modsecurity off;
- root /var/www/certbot;
- allow all;
- default_type "text/plain";
- try_files $uri =404;
- }}
-
- location ~ /\\. {{
- deny all;
- }}
-}}
-"""
- elif site_type == 'static':
- config_content = f"""server {{
- listen 80;
- server_name {domain};
-
- root {root_dir};
- index index.html;
-
- access_log /var/log/nginx/access.log;
- error_log /var/log/nginx/error.log;
-
- location / {{
- try_files $uri $uri/ /index.html;
- }}
-
- # Izinkan hanya untuk ACME challenge
- location ^~ /.well-known/acme-challenge/ {{
- modsecurity off;
- root /var/www/certbot;
- allow all;
- default_type "text/plain";
- try_files $uri =404;
- }}
-
- location ~ /\\. {{
- deny all;
- }}
-}}
-"""
- else:
- config_content = f"""server {{
- listen 80;
- server_name {domain};
-
- location / {{
- proxy_pass http://localhost:{port};
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- }}
-
- # Izinkan hanya untuk ACME challenge
- location ^~ /.well-known/acme-challenge/ {{
- modsecurity off;
- root /var/www/certbot;
- allow all;
- default_type "text/plain";
- try_files $uri =404;
- }}
-}}
-"""
-
- config_filename = f"{domain}.conf"
- config_path = os.path.join(NGINX_CONF_DIR, config_filename)
-
- try:
- with open(config_path, 'w') as f:
- f.write(config_content)
- return True
- except Exception as e:
- print(f"Error creating config file: {e}")
- return False
-
-def delete_nginx_config(domain):
- """Delete nginx configuration file"""
- config_filename = f"{domain}.conf"
- config_path = os.path.join(NGINX_CONF_DIR, config_filename)
-
- try:
- if os.path.exists(config_path):
- os.remove(config_path)
- return True
- return False
- except Exception as e:
- print(f"Error deleting config file: {e}")
- return False
-
-def reload_nginx():
- """Reload nginx configuration"""
- try:
- # Test nginx configuration first
- test_result = subprocess.run([
- 'docker', 'exec', NGINX_CONTAINER_NAME, 'nginx', '-t'
- ], capture_output=True, text=True)
-
- if test_result.returncode != 0:
- return False, f"Configuration test failed: {test_result.stderr}"
-
- # If test passes, reload nginx
- result = subprocess.run([
- 'docker', 'exec', NGINX_CONTAINER_NAME, 'nginx', '-s', 'reload'
- ], capture_output=True, text=True)
-
- if result.returncode == 0:
- return True, "Nginx reloaded successfully"
- else:
- return False, f"Nginx reload failed: {result.stderr}"
- except Exception as e:
- return False, f"Error reloading nginx: {str(e)}"
-
-
-def ensure_compose_env():
- """Ensure /workspace/docker/.env contains HOST_PROJECT_DIR without overwriting other variables."""
- try:
- proj_dir = get_project_dir()
- if not proj_dir:
- return False
- env_path = '/workspace/docker/.env'
-
- # Read existing .env content if available
- existing_lines = []
- try:
- with open(env_path, 'r') as f:
- existing_lines = f.readlines()
- except FileNotFoundError:
- existing_lines = []
-
- # Prepare the updated HOST_PROJECT_DIR line
- host_line = f"HOST_PROJECT_DIR={proj_dir}\n"
- updated = False
- for idx, line in enumerate(existing_lines):
- stripped = line.strip()
- if stripped.startswith('HOST_PROJECT_DIR='):
- existing_lines[idx] = host_line
- updated = True
- break
-
- # Append if not present
- if not updated:
- existing_lines.append(host_line)
-
- # Write back, preserving other variables
- with open(env_path, 'w') as f:
- f.writelines(existing_lines)
- return True
- except Exception:
- return False
-
-def get_project_dir():
- """Return the host project directory backing /workspace mount of mlite_panel."""
- try:
- res = subprocess.run(
- ['docker','inspect','-f','{{range .Mounts}}{{if eq .Destination "/workspace"}}{{.Source}}{{end}}{{end}}','mlite_panel'],
- capture_output=True, text=True, timeout=5
- )
- if res.returncode == 0:
- src = (res.stdout or '').strip()
- if src:
- return src
- except Exception:
- pass
- # Fallback to env if provided
- return os.environ.get('HOST_PROJECT_DIR', '')
-
-def get_services_from_compose():
- """Get all services from docker-compose.yml"""
- print("[DEBUG] === get_services_from_compose() STARTED ===")
-
- try:
- # Resolve candidate paths (prefer container-mounted first)
- current_file = os.path.abspath(__file__)
- current_dir = os.path.dirname(current_file)
- parent_dir = os.path.dirname(current_dir)
- candidates = [
- ('/app/docker-compose.yml', '/app'),
- (os.path.join(parent_dir, 'docker-compose.yml'), parent_dir),
- ('/workspace/docker/docker-compose.yml', '/workspace/docker'),
- ('/Users/basoro/Slemp/data/www/mlite.loc/docker/docker-compose.yml', '/Users/basoro/Slemp/data/www/mlite.loc/docker')
- ]
- print(f"[DEBUG] Current file: {current_file}")
- print(f"[DEBUG] Current dir: {current_dir}")
- print(f"[DEBUG] Parent dir: {parent_dir}")
- for path, _ in candidates:
- print(f"[DEBUG] Candidate compose path exists? {path}: {os.path.exists(path)}")
-
- compose_path = None
- for path, _ in candidates:
- if os.path.exists(path):
- compose_path = path
- break
- if not compose_path:
- print("[ERROR] No docker-compose.yml found in any checked path")
- print("[DEBUG] === get_services_from_compose() COMPLETED (no file) ===")
- return []
-
- print(f"[DEBUG] Reading compose file: {compose_path}")
- with open(compose_path, 'r') as f:
- content = f.read()
- print(f"[DEBUG] File content length: {len(content)} characters")
- print(f"[DEBUG] First 200 chars:\n{content[:200]}")
- compose_data = yaml.safe_load(content)
-
- print(f"[DEBUG] Parsed compose data type: {type(compose_data)}")
- if not compose_data or not isinstance(compose_data, dict):
- print(f"[ERROR] Compose data invalid or empty: {compose_data}")
- print("[DEBUG] === get_services_from_compose() COMPLETED (invalid data) ===")
- return []
- print(f"[DEBUG] Parsed compose data keys: {list(compose_data.keys())}")
-
- services_data = compose_data.get('services', {})
- print(f"[DEBUG] Found {len(services_data)} services: {list(services_data.keys())}")
-
- services = []
- for service_name, service_config in services_data.items():
- print(f"[DEBUG] Processing service: {service_name}")
- service_info = {
- 'name': service_name,
- 'container_name': service_config.get('container_name', f"mlite_{service_name}"),
- 'image': service_config.get('image', 'custom-build'),
- 'build': 'build' in service_config,
- 'ports': service_config.get('ports', []),
- 'volumes': len(service_config.get('volumes', [])),
- 'environment': len(service_config.get('environment', {})),
- 'depends_on': service_config.get('depends_on', []),
- 'restart': service_config.get('restart', 'no')
- }
- services.append(service_info)
- print(f"[DEBUG] Added service info: {service_info}")
-
- print(f"[DEBUG] Returning {len(services)} services")
- print("[DEBUG] === get_services_from_compose() COMPLETED ===")
- return services
-
- except Exception as e:
- print(f"[ERROR] Exception in get_services_from_compose(): {str(e)}")
- print(f"[ERROR] Exception type: {type(e)}")
- import traceback
- print(f"[ERROR] Traceback: {traceback.format_exc()}")
- print("[DEBUG] === get_services_from_compose() COMPLETED (exception) ===")
- return []
-
-def get_php_containers():
- """Get PHP container data for PHP-FPM page, consistent with docker ps logic."""
- print("[DEBUG] === get_php_containers() STARTED ===")
- try:
- # Use docker ps JSON per-line for reliable status/ports
- cmd = ['docker', 'ps', '-a', '--format', '{{json .}}']
- print(f"[DEBUG] Running: {' '.join(cmd)}")
- result = subprocess.run(cmd, capture_output=True, text=True, check=True)
-
- containers = []
- lines = result.stdout.strip().split('\n') if result.stdout.strip() else []
- print(f"[DEBUG] Found {len(lines)} containers total")
-
- php_versions = ['php56', 'php70', 'php71', 'php72', 'php73', 'php74', 'php80', 'php81', 'php82', 'php83']
- version_map = {
- '56': '5.6', '70': '7.0', '71': '7.1', '72': '7.2', '73': '7.3',
- '74': '7.4', '80': '8.0', '81': '8.1', '82': '8.2', '83': '8.3'
- }
-
- for line in lines:
- if not line.strip():
- continue
- try:
- obj = json.loads(line)
- container_name = obj.get('Names') or obj.get('Name') or ''
- matched_ver = None
- for php_ver in php_versions:
- if php_ver in container_name:
- matched_ver = php_ver
- break
- if not matched_ver:
- continue
-
- print(f"[DEBUG] Found PHP container: {container_name}")
- status_text = obj.get('Status', '') or ''
- running = status_text.lower().startswith('up')
- ports_parsed = parse_ports_string(obj.get('Ports', '') or '')
- # Convert to display strings like "host:container" or just "container"
- ports_display = []
- for p in deduplicate_ports(ports_parsed):
- pub = str(p.get('PublishedPort') or '')
- tgt = str(p.get('TargetPort') or '')
- if pub and tgt:
- ports_display.append(f"{pub}:{tgt}")
- elif tgt:
- ports_display.append(tgt)
-
- # Derive PHP version code (e.g., 83 -> 8.3)
- code = matched_ver.replace('php', '')
- php_version = version_map.get(code, code)
-
- containers.append({
- 'name': container_name,
- 'running': running,
- 'status': 'Running' if running else 'Stopped',
- 'php_version': php_version,
- 'ports': ports_display,
- 'created': format_created_field(obj.get('CreatedAt', '')),
- 'image': obj.get('Image', '')
- })
- except (json.JSONDecodeError, KeyError, IndexError) as e:
- print(f"[WARN] Error parsing container data: {e}")
- continue
-
- print(f"[DEBUG] Found {len(containers)} PHP containers")
- print("[DEBUG] === get_php_containers() COMPLETED ===")
- return containers
-
- except subprocess.CalledProcessError as e:
- print(f"[ERROR] Error getting PHP containers: {e}")
- print("[DEBUG] === get_php_containers() COMPLETED (error) ===")
- return []
- except Exception as e:
- print(f"[ERROR] Unexpected error getting PHP containers: {e}")
- print("[DEBUG] === get_php_containers() COMPLETED (exception) ===")
- return []
-
-def get_mysql_containers():
- """Get MySQL container data for MySQL management page, consistent with docker ps logic."""
- print("[DEBUG] === get_mysql_containers() STARTED ===")
- try:
- # Use docker ps JSON per-line for reliable status/ports
- cmd = ['docker', 'ps', '-a', '--format', '{{json .}}']
- print(f"[DEBUG] Running: {' '.join(cmd)}")
- result = subprocess.run(cmd, capture_output=True, text=True, check=True)
-
- containers = []
- lines = result.stdout.strip().split('\n') if result.stdout.strip() else []
- print(f"[DEBUG] Found {len(lines)} containers total")
-
- # MySQL container identifiers
- mysql_identifiers = ['mysql', 'mariadb', 'percona']
-
- for line in lines:
- if not line.strip():
- continue
- try:
- obj = json.loads(line)
- container_name = obj.get('Names') or obj.get('Name') or ''
-
- # Check if this is a MySQL container
- is_mysql = False
- for identifier in mysql_identifiers:
- if identifier in container_name.lower():
- is_mysql = True
- break
-
- if not is_mysql:
- continue
-
- print(f"[DEBUG] Found MySQL container: {container_name}")
- status_text = obj.get('Status', '') or ''
- running = status_text.lower().startswith('up')
- ports_parsed = parse_ports_string(obj.get('Ports', '') or '')
-
- # Convert to display strings like "host:container" or just "container"
- ports_display = []
- for p in deduplicate_ports(ports_parsed):
- pub = str(p.get('PublishedPort') or '')
- tgt = str(p.get('TargetPort') or '')
- if pub and tgt:
- ports_display.append(f"{pub}:{tgt}")
- elif tgt:
- ports_display.append(tgt)
-
- containers.append({
- 'name': container_name,
- 'running': running,
- 'status': 'Running' if running else 'Stopped',
- 'ports': ports_display,
- 'created': obj.get('CreatedAt', ''),
- 'image': obj.get('Image', '')
- })
- except (json.JSONDecodeError, KeyError, IndexError) as e:
- print(f"[WARN] Error parsing container data: {e}")
- continue
-
- print(f"[DEBUG] Found {len(containers)} MySQL containers")
- print("[DEBUG] === get_mysql_containers() COMPLETED ===")
- return containers
-
- except subprocess.CalledProcessError as e:
- print(f"[ERROR] Error getting MySQL containers: {e}")
- print("[DEBUG] === get_mysql_containers() COMPLETED (error) ===")
- return []
- except Exception as e:
- print(f"[ERROR] Unexpected error getting MySQL containers: {e}")
- print("[DEBUG] === get_mysql_containers() COMPLETED (exception) ===")
- return []
-
-def parse_ports_string(ports_str: str):
- """Convert docker ps Ports string into list of dicts with PublishedPort/TargetPort."""
- if not ports_str:
- return []
- parts = [p.strip() for p in ports_str.split(',') if p.strip()]
- results = []
- for p in parts:
- try:
- if '->' in p:
- left, right = p.split('->', 1)
- # PublishedPort: take last number on the left side
- m_pub = re.findall(r"(\d+)", left)
- published = m_pub[-1] if m_pub else ''
- # TargetPort: take number before / on the right side
- target = right.split('/')[0]
- # If right contains host:port mapping like 80,80/tcp ensure only port digits
- m_target = re.findall(r"(\d+)", target)
- target = m_target[-1] if m_target else ''
- results.append({'PublishedPort': published, 'TargetPort': target})
- else:
- # Format like "80/tcp" — only target port exposed
- target = p.split('/')[0]
- m_target = re.findall(r"(\d+)", target)
- target = m_target[-1] if m_target else ''
- results.append({'PublishedPort': '', 'TargetPort': target})
- except Exception:
- # Fallback: skip malformed segment
- continue
- return results
-
-def deduplicate_ports(ports: list):
- """Remove duplicate port mappings based on (PublishedPort, TargetPort).
- Ensures consistent dict shape {PublishedPort, TargetPort}.
- """
- seen = set()
- deduped = []
- for p in ports or []:
- try:
- if isinstance(p, dict):
- pub = str(p.get('PublishedPort', '') or '')
- tgt = str(p.get('TargetPort', '') or '')
- else:
- # Unknown shape, skip
- continue
- key = (pub, tgt)
- if key not in seen:
- seen.add(key)
- deduped.append({'PublishedPort': pub, 'TargetPort': tgt})
- except Exception:
- continue
- return deduped
-
-def get_container_status_via_docker_ps():
- """Get status of containers using `docker ps` (no compose plugin required)."""
- status_by_name = {}
- try:
- cmd = ['docker', 'ps', '-a', '--format', '{{json .}}']
- print(f"[DEBUG] Running: {' '.join(cmd)}")
- result = subprocess.run(cmd, capture_output=True, text=True)
- print(f"[DEBUG] docker ps exit: {result.returncode}")
- if result.stderr:
- print(f"[DEBUG] stderr: {result.stderr.strip()}")
- if result.returncode == 0 and result.stdout.strip():
- lines = result.stdout.strip().split('\n')
- print(f"[DEBUG] docker ps returned {len(lines)} JSON lines")
- for line in lines:
- if not line.strip():
- continue
- try:
- obj = json.loads(line)
- except json.JSONDecodeError as e:
- print(f"[WARN] JSON decode error in docker ps line: {e}")
- continue
- name = obj.get('Names') or obj.get('Name') or ''
- status_text = obj.get('Status', '')
- running = status_text.lower().startswith('up')
- ports_list = parse_ports_string(obj.get('Ports', '') or '')
- status_by_name[name] = {
- 'running': running,
- 'status': status_text or ('running' if running else 'stopped'),
- 'created': format_created_field(obj.get('CreatedAt', '')),
- 'image': obj.get('Image', ''),
- 'ports': ports_list
- }
- else:
- print("[ERROR] docker ps failed or output empty")
- except Exception as e:
- print(f"Error running docker ps: {e}")
- return status_by_name
-
-def get_container_status():
- """Get status of all containers from docker-compose"""
- container_data = {}
-
- try:
- # Prefer container-mounted compose file, fallback to parent and host paths
- current_file = os.path.abspath(__file__)
- current_dir = os.path.dirname(current_file)
- parent_dir = os.path.dirname(current_dir)
- candidates = [
- ('/app/docker-compose.yml', '/app'),
- (os.path.join(parent_dir, 'docker-compose.yml'), parent_dir),
- ('/workspace/docker/docker-compose.yml', '/workspace/docker'),
- ('/Users/basoro/Slemp/data/www/mlite.loc/docker/docker-compose.yml', '/Users/basoro/Slemp/data/www/mlite.loc/docker')
- ]
-
- compose_path = None
- compose_cwd = None
- for path, cwd in candidates:
- if os.path.exists(path):
- compose_path = path
- compose_cwd = cwd
- break
-
- if not compose_path:
- print("[ERROR] No docker-compose.yml found for container status")
- return container_data
-
- base = get_compose_cmd_base()
- project = get_compose_project()
- cmd = base + ['-f', compose_path] + (['--project-name', project] if project else []) + ['ps', '--format', 'json']
- print(f"[DEBUG] Running: {' '.join(cmd)} (cwd={compose_cwd})")
- result = subprocess.run(cmd, capture_output=True, text=True, cwd=compose_cwd)
- print(f"[DEBUG] docker compose ps exit: {result.returncode}")
- if result.stderr:
- print(f"[DEBUG] stderr: {result.stderr.strip()}")
-
- if result.returncode == 0 and result.stdout.strip():
- try:
- lines = result.stdout.strip().split('\n')
- print(f"[DEBUG] ps returned {len(lines)} JSON lines")
- containers = []
- for line in lines:
- if line.strip():
- try:
- containers.append(json.loads(line))
- except json.JSONDecodeError as e:
- print(f"[WARN] JSON decode error: {e}")
- for container in containers:
- service_name = container.get('Service', '')
- container_data[service_name] = {
- 'running': 'running' in (container.get('State', '') or '').lower(),
- 'status': container.get('State', 'unknown'),
- 'created': container.get('CreatedAt', ''),
- 'image': container.get('Image', ''),
- 'ports': container.get('Publishers', []) or []
- }
- except json.JSONDecodeError as e:
- print(f"JSON parsing error: {e}")
- except Exception as e:
- print(f"Error getting container status: {e}")
-
- return container_data
-
-def build_container(service_name):
- """Build specific container"""
- if service_name == 'panel':
- logger.warning("Attempted to build panel container; operation blocked")
- return False, "Cannot build panel container"
- try:
- project = get_compose_project()
- base = get_compose_cmd_base()
- resolved_file = get_resolved_compose_file()
- cmd = base + ['-f', resolved_file] + (['--project-name', project] if project else []) + ['build', service_name]
- logger.info(f"Building container: service={service_name}, cmd={' '.join(cmd)}")
- start_time = datetime.now()
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=300, cwd='/workspace/docker') # 5 minutes timeout
- duration = (datetime.now() - start_time).total_seconds()
- logger.info(f"Build result: rc={result.returncode}, duration={duration}s, stdout='{result.stdout.strip()}', stderr='{result.stderr.strip()}'")
-
- if result.returncode == 0:
- return True, f"Container {service_name} built successfully"
- else:
- return False, f"Build failed: {result.stderr or result.stdout}"
- except subprocess.TimeoutExpired as te:
- logger.error(f"Build timeout for service={service_name}: {str(te)}")
- return False, "Build timeout (5 minutes)"
- except Exception as e:
- logger.exception(f"Build error for service={service_name}")
- return False, f"Build error: {str(e)}"
-
-# Helper: detect compose project name from panel container labels
-COMPOSE_CMD_BASE = None
-COMPOSE_PROJECT_NAME = None
-
-def get_compose_cmd_base():
- """Return the base compose command, preferring plugin, falling back to docker-compose."""
- global COMPOSE_CMD_BASE
- if COMPOSE_CMD_BASE:
- return COMPOSE_CMD_BASE
- # Try Docker Compose plugin: `docker compose`
- try:
- res = subprocess.run(['docker', 'compose', 'version'], capture_output=True, text=True, timeout=5)
- if res.returncode == 0:
- COMPOSE_CMD_BASE = ['docker', 'compose']
- logger.info("Using Docker Compose plugin: 'docker compose'")
- return COMPOSE_CMD_BASE
- except Exception as e:
- logger.info(f"'docker compose' not available: {e}")
- # Try docker-compose binary
- try:
- if shutil.which('docker-compose'):
- res2 = subprocess.run(['docker-compose', 'version'], capture_output=True, text=True, timeout=5)
- if res2.returncode == 0:
- COMPOSE_CMD_BASE = ['docker-compose']
- logger.info("Using docker-compose binary")
- return COMPOSE_CMD_BASE
- except Exception as e:
- logger.info(f"'docker-compose' not available: {e}")
- # Fallback to plugin even if version check failed
- COMPOSE_CMD_BASE = ['docker', 'compose']
- logger.warning("Compose not detected; defaulting to 'docker compose' which may fail")
- return COMPOSE_CMD_BASE
-
-def get_compose_project():
- global COMPOSE_PROJECT_NAME
- if COMPOSE_PROJECT_NAME:
- return COMPOSE_PROJECT_NAME
- try:
- result = subprocess.run(
- ['docker', 'inspect', '-f', '{{ index .Config.Labels "com.docker.compose.project" }}', 'mlite_panel'],
- capture_output=True, text=True, timeout=5
- )
- if result.returncode == 0:
- name = result.stdout.strip()
- if name:
- COMPOSE_PROJECT_NAME = name
- logger.info(f"Detected compose project: {COMPOSE_PROJECT_NAME}")
- return COMPOSE_PROJECT_NAME
- except Exception as e:
- logger.warning(f"Failed to detect compose project name: {e}")
- return None
-
-def start_container(service_name):
- """Start specific container"""
- if service_name == 'panel':
- logger.warning("Attempted to start panel container; operation blocked")
- return False, "Cannot control panel container"
- try:
- project = get_compose_project()
- base = get_compose_cmd_base()
- cmd = base + ['--env-file', '/workspace/docker/.env', '-f', '/workspace/docker/docker-compose.yml'] + (['--project-name', project] if project else []) + ['up', '-d', '--no-deps', service_name]
- logger.info(f"Starting container: service={service_name}, cmd={' '.join(cmd)}")
- start_time = datetime.now()
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=120, cwd='/workspace') # 2 minutes timeout
- duration = (datetime.now() - start_time).total_seconds()
- logger.info(f"Start result: rc={result.returncode}, duration={duration}s, stdout='{result.stdout.strip()}', stderr='{result.stderr.strip()}'")
-
- if result.returncode == 0:
- return True, f"Container {service_name} started successfully"
- # Fallback: directly start container(s) by label
- logger.info(f"Compose up failed for service={service_name}, attempting docker start by labels")
- label_filters = ['--filter', f'label=com.docker.compose.service={service_name}']
- if project:
- label_filters += ['--filter', f'label=com.docker.compose.project={project}']
- ps_cmd = ['docker', 'ps', '-a', '-q'] + label_filters
- ps_res = subprocess.run(ps_cmd, capture_output=True, text=True, timeout=10)
- ids = [line.strip() for line in ps_res.stdout.splitlines() if line.strip()]
- logger.info(f"Fallback discovered {len(ids)} container(s) for service={service_name}: {ids}")
- if ids:
- start_cmd = ['docker', 'start'] + ids
- start_res = subprocess.run(start_cmd, capture_output=True, text=True, timeout=30)
- logger.info(f"Fallback start result: rc={start_res.returncode}, stdout='{start_res.stdout.strip()}', stderr='{start_res.stderr.strip()}'")
- if start_res.returncode == 0:
- return True, f"Container(s) for {service_name} started"
- else:
- return False, f"Fallback start failed: {start_res.stderr or start_res.stdout}"
- else:
- return False, f"Start failed: {result.stderr or result.stdout}"
- except subprocess.TimeoutExpired as te:
- logger.error(f"Start timeout for service={service_name}: {str(te)}")
- return False, "Start timeout (2 minutes)"
- except Exception as e:
- logger.exception(f"Start error for service={service_name}")
- return False, f"Start error: {str(e)}"
-
-def stop_container(service_name):
- """Stop specific container"""
- if service_name == 'panel':
- logger.warning("Attempted to stop panel container; operation blocked")
- return False, "Cannot control panel container"
- try:
- project = get_compose_project()
- base = get_compose_cmd_base()
- cmd = base + ['--env-file', '/workspace/docker/.env', '-f', '/workspace/docker/docker-compose.yml'] + (['--project-name', project] if project else []) + ['stop', service_name]
- logger.info(f"Stopping container: service={service_name}, cmd={' '.join(cmd)}")
- start_time = datetime.now()
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60, cwd='/workspace/docker') # 1 minute timeout
- duration = (datetime.now() - start_time).total_seconds()
- logger.info(f"Stop result: rc={result.returncode}, duration={duration}s, stdout='{result.stdout.strip()}', stderr='{result.stderr.strip()}'")
-
- if result.returncode == 0:
- return True, f"Container {service_name} stopped successfully"
- # Fallback: directly stop container(s) by label
- logger.info(f"Compose stop failed for service={service_name}, attempting docker stop by labels")
- label_filters = ['--filter', f'label=com.docker.compose.service={service_name}']
- if project:
- label_filters += ['--filter', f'label=com.docker.compose.project={project}']
- ps_cmd = ['docker', 'ps', '-q'] + label_filters
- ps_res = subprocess.run(ps_cmd, capture_output=True, text=True, timeout=10)
- ids = [line.strip() for line in ps_res.stdout.splitlines() if line.strip()]
- logger.info(f"Fallback discovered {len(ids)} container(s) for service={service_name}: {ids}")
- if ids:
- stop_cmd = ['docker', 'stop'] + ids
- stop_res = subprocess.run(stop_cmd, capture_output=True, text=True, timeout=30)
- logger.info(f"Fallback stop result: rc={stop_res.returncode}, stdout='{stop_res.stdout.strip()}', stderr='{stop_res.stderr.strip()}'")
- if stop_res.returncode == 0:
- return True, f"Container(s) for {service_name} stopped"
- else:
- return False, f"Fallback stop failed: {stop_res.stderr or stop_res.stdout}"
- else:
- return False, f"Stop failed: {result.stderr or result.stdout}"
- except subprocess.TimeoutExpired as te:
- logger.error(f"Stop timeout for service={service_name}: {str(te)}")
- return False, "Stop timeout (1 minute)"
- except Exception as e:
- logger.exception(f"Stop error for service={service_name}")
- return False, f"Stop error: {str(e)}"
-
-def restart_container(service_name):
- """Restart specific container"""
- try:
- project = get_compose_project()
- base = get_compose_cmd_base()
- cmd = base + ['--env-file', '/workspace/docker/.env', '-f', '/workspace/docker/docker-compose.yml'] + (['--project-name', project] if project else []) + ['restart', service_name]
- logger.info(f"Restarting container: service={service_name}, cmd={' '.join(cmd)}")
- start_time = datetime.now()
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=120, cwd='/workspace/docker') # 2 minutes timeout
- duration = (datetime.now() - start_time).total_seconds()
- logger.info(f"Restart result: rc={result.returncode}, duration={duration}s, stdout='{result.stdout.strip()}', stderr='{result.stderr.strip()}'")
-
- if result.returncode == 0:
- return True, f"Container {service_name} restarted successfully"
- else:
- return False, f"Restart failed: {result.stderr or result.stdout}"
- except subprocess.TimeoutExpired as te:
- logger.error(f"Restart timeout for service={service_name}: {str(te)}")
- return False, "Restart timeout (2 minutes)"
- except Exception as e:
- logger.exception(f"Restart error for service={service_name}")
- return False, f"Restart error: {str(e)}"
-
-
-def remove_container(service_name):
- """Remove (delete) container(s) for a specific service"""
- if service_name == 'panel':
- logger.warning("Attempted to remove panel container; operation blocked")
- return False, "Cannot control panel container"
- try:
- project = get_compose_project()
- base = get_compose_cmd_base()
- resolved_file = get_resolved_compose_file()
- cmd = base + ['-f', resolved_file] + (['--project-name', project] if project else []) + ['rm', '-f', '-s', service_name]
- logger.info(f"Removing container: service={service_name}, cmd={' '.join(cmd)}")
- start_time = datetime.now()
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
- duration = (datetime.now() - start_time).total_seconds()
- logger.info(f"Remove result: rc={result.returncode}, duration={duration}s, stdout='{result.stdout.strip()}', stderr='{result.stderr.strip()}'")
-
- if result.returncode == 0:
- return True, f"Container {service_name} removed successfully"
- # Fallback: remove by container IDs filtered by labels
- logger.info(f"Compose rm failed for service={service_name}, attempting docker rm by labels")
- label_filters = ['--filter', f'label=com.docker.compose.service={service_name}']
- if project:
- label_filters += ['--filter', f'label=com.docker.compose.project={project}']
- ps_cmd = ['docker', 'ps', '-a', '-q'] + label_filters
- ps_res = subprocess.run(ps_cmd, capture_output=True, text=True, timeout=10)
- ids = [line.strip() for line in ps_res.stdout.splitlines() if line.strip()]
- logger.info(f"Fallback discovered {len(ids)} container(s) for service={service_name}: {ids}")
- if ids:
- rm_cmd = ['docker', 'rm', '-f'] + ids
- rm_res = subprocess.run(rm_cmd, capture_output=True, text=True, timeout=30)
- logger.info(f"Fallback rm result: rc={rm_res.returncode}, stdout='{rm_res.stdout.strip()}', stderr='{rm_res.stderr.strip()}'")
- if rm_res.returncode == 0:
- return True, f"Container(s) for {service_name} removed"
- else:
- return False, f"Fallback remove failed: {rm_res.stderr or rm_res.stdout}"
- else:
- return False, f"Remove failed: {result.stderr or result.stdout}"
- except subprocess.TimeoutExpired as te:
- logger.error(f"Remove timeout for service={service_name}: {str(te)}")
- return False, "Remove timeout (1 minute)"
- except Exception as e:
- logger.exception(f"Remove error for service={service_name}")
- return False, f"Remove error: {str(e)}"
-
-
-def remove_image(service_name):
- """Remove built image(s) for a specific service (build-capable)."""
- if service_name == 'panel':
- logger.warning("Attempted to remove panel image; operation blocked")
- return False, "Cannot control panel container"
- try:
- project = get_compose_project()
- images_to_remove = set()
- # Prefer discovering images via existing containers
- label_filters = ['--filter', f'label=com.docker.compose.service={service_name}']
- if project:
- label_filters += ['--filter', f'label=com.docker.compose.project={project}']
- ps_cmd = ['docker', 'ps', '-a', '-q'] + label_filters
- ps_res = subprocess.run(ps_cmd, capture_output=True, text=True, timeout=10)
- ids = [line.strip() for line in ps_res.stdout.splitlines() if line.strip()]
- for cid in ids:
- insp = subprocess.run(['docker', 'inspect', '-f', '{{.Image}}|{{.Config.Image}}', cid], capture_output=True, text=True, timeout=5)
- if insp.returncode == 0:
- out = (insp.stdout or '').strip()
- parts = out.split('|') if out else []
- img_id = parts[0] if len(parts) > 0 else ''
- if img_id:
- images_to_remove.add(img_id)
- # If no containers found, try image repository patterns
- if not images_to_remove:
- patterns = []
- if project:
- patterns.append(f"{project}-{service_name}")
- patterns.append(f"{project}_{service_name}")
- patterns.append(f"mlite_{service_name}")
- img_res = subprocess.run(['docker', 'images', '--format', '{{.Repository}} {{.ID}}'], capture_output=True, text=True, timeout=10)
- for line in (img_res.stdout or '').splitlines():
- try:
- repo, imgid = line.strip().split()
- except ValueError:
- continue
- if any(p in repo for p in patterns):
- images_to_remove.add(imgid)
- if not images_to_remove:
- return False, "No images found for service"
- rm_cmd = ['docker', 'rmi', '-f'] + list(images_to_remove)
- rm_res = subprocess.run(rm_cmd, capture_output=True, text=True, timeout=60)
- if rm_res.returncode == 0:
- return True, f"Image(s) for {service_name} removed"
- else:
- return False, f"Remove image failed: {rm_res.stderr or rm_res.stdout}"
- except subprocess.TimeoutExpired as te:
- logger.error(f"Remove image timeout for service={service_name}: {str(te)}")
- return False, "Remove image timeout (1 minute)"
- except Exception as e:
- logger.exception(f"Remove image error for service={service_name}")
- return False, f"Remove image error: {str(e)}"
-
-def check_nginx_status():
- """Check if nginx container is running (via docker inspect), fallback to process check"""
- try:
- result = subprocess.run(
- ['docker', 'inspect', '-f', '{{.State.Running}}', NGINX_CONTAINER_NAME],
- capture_output=True, text=True
- )
- if result.returncode == 0:
- return result.stdout.strip().lower() == 'true'
- except Exception:
- pass
- # Fallback: try to detect nginx process inside the container without requiring pgrep
- try:
- result2 = subprocess.run(
- ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', "ps | grep nginx | grep -v grep"],
- capture_output=True, text=True
- )
- return result2.returncode == 0 and result2.stdout.strip() != ''
- except Exception:
- return False
-
-# Simple login-required decorator
-def login_required(f):
- @wraps(f)
- def decorated_function(*args, **kwargs):
- if not session.get('logged_in'):
- # For API endpoints, return JSON instead of HTML login redirect
- try:
- is_api = request.path.startswith('/api/')
- except Exception:
- is_api = False
- if is_api:
- return jsonify({'success': False, 'error': 'Unauthorized'}), 401
- next_url = request.path if request.method == 'GET' else None
- return redirect(url_for('login', next=next_url))
- return f(*args, **kwargs)
- return decorated_function
-
-@app.route('/login', methods=['GET', 'POST'])
-def login():
- if request.method == 'POST':
- username = request.form.get('username', '').strip()
- password = request.form.get('password', '').strip()
- if not username or not password:
- flash('Username dan password wajib diisi', 'error')
- return render_template('login.html')
- if username == app.config.get('ADMIN_USER') and password == app.config.get('ADMIN_PASS'):
- session['logged_in'] = True
- session['username'] = username
- next_url = request.args.get('next')
- if next_url and next_url.startswith('/'):
- return redirect(next_url)
- return redirect(url_for('dashboard'))
- else:
- flash('Username atau password salah', 'error')
- return render_template('login.html')
- if session.get('logged_in'):
- return redirect(url_for('dashboard'))
- return render_template('login.html')
-
-@app.route('/logout')
-def logout():
- session.clear()
- flash('Anda telah logout', 'success')
- return redirect(url_for('login'))
-
-@app.route('/')
-@login_required
-def dashboard():
- """Dashboard page showing all sites - HOT RELOAD TEST"""
- sites = get_sites()
- nginx_status = check_nginx_status()
-
- # MySQL status and total databases
- mysql_containers = get_mysql_containers()
- mysql_running = any(c.get('running') for c in mysql_containers) if mysql_containers else False
- total_databases = 0
- if mysql_running:
- try:
- rows = _exec_mysql('SELECT COUNT(*) FROM information_schema.schemata WHERE schema_name NOT IN ("mysql","information_schema","performance_schema","sys")', expect_output=True, dictionary=False)
- if rows and rows[0] and rows[0][0].isdigit():
- total_databases = int(rows[0][0])
- else:
- # Fallback: SHOW DATABASES and count non-system schemas
- dbs = _exec_mysql('SHOW DATABASES', expect_output=True, dictionary=False)
- if dbs:
- non_system = [r[0] for r in dbs if r and r[0] not in ['mysql','information_schema','performance_schema','sys']]
- total_databases = len(non_system)
- except Exception:
- total_databases = 0
-
- return render_template('dashboard.html', sites=sites, nginx_status=nginx_status, mysql_running=mysql_running, total_databases=total_databases)
-
-@app.route('/add-site', methods=['GET', 'POST'])
-@login_required
-def add_site():
- nginx_status = check_nginx_status()
- """Add new site page"""
- if request.method == 'POST':
- domain = request.form.get('domain', '').strip()
- site_type = request.form.get('site_type', 'proxy').strip()
- port = request.form.get('port', '').strip()
- root_dir = request.form.get('root_dir', '/var/www/public').strip()
- php_version = request.form.get('php_version', '8.3').strip()
-
- # Validation
- if not domain:
- flash('Domain is required', 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
-
- if site_type == 'proxy':
- if not port:
- flash('Port is required for proxy sites', 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
- try:
- port_int = int(port)
- if port_int < 1 or port_int > 65535:
- flash('Port must be between 1 and 65535', 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
- except ValueError:
- flash('Port must be a valid number', 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
- elif site_type == 'php':
- if not root_dir:
- flash('Root directory is required for PHP-FPM sites', 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
- # Validate and ensure directory within /var/www/public
- try:
- safe_root = safe_join(PHP_WEBROOT_BASE, root_dir)
- except ValueError as ve:
- flash(str(ve), 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
- ok, msg = ensure_directory(safe_root)
- if not ok:
- flash(msg, 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
- # Use the safe normalized root in config
- root_dir = safe_root
- # Optionally create default index.html (non-blocking if fails)
- if CREATE_DEFAULT_INDEX:
- ok_idx, msg_idx = create_default_index(root_dir, domain)
- if not ok_idx:
- flash(f'Peringatan: {msg_idx}', 'warning')
- elif site_type == 'static':
- if not root_dir:
- flash('Root directory is required for Static sites', 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
- try:
- safe_root = safe_join(PHP_WEBROOT_BASE, root_dir)
- except ValueError as ve:
- flash(str(ve), 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
- ok, msg = ensure_directory(safe_root)
- if not ok:
- flash(msg, 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
- root_dir = safe_root
- if CREATE_DEFAULT_INDEX:
- ok_idx, msg_idx = create_default_index(root_dir, domain)
- if not ok_idx:
- flash(f'Peringatan: {msg_idx}', 'warning')
- else:
- flash('Invalid site type', 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
-
- # Check if domain already exists
- existing_sites = get_sites()
- if any(site['domain'] == domain for site in existing_sites):
- flash('Domain already exists', 'error')
- return render_template('add_site.html', nginx_status=nginx_status)
-
- # Create nginx config
- created = False
- if site_type == 'php':
- created = create_nginx_config(domain, site_type='php', root_dir=root_dir, php_version=php_version)
- elif site_type == 'static':
- created = create_nginx_config(domain, site_type='static', root_dir=root_dir)
- else:
- created = create_nginx_config(domain, site_type='proxy', port=port)
-
- if created:
- # Reload nginx
- success, message = reload_nginx()
- if success:
- flash('Site added successfully', 'success')
- return redirect(url_for('dashboard'))
- else:
- # If reload fails, delete the config file
- delete_nginx_config(domain)
- flash(f'Failed to reload nginx: {message}', 'error')
- else:
- flash('Failed to create configuration file', 'error')
- # Fallthrough to final render
-
- return render_template('add_site.html', nginx_status=nginx_status)
-
-@app.route('/delete-site/')
-@login_required
-def delete_site(domain):
- """Delete site configuration"""
- if delete_nginx_config(domain):
- success, message = reload_nginx()
- if success:
- flash('Site deleted successfully', 'success')
- else:
- flash(f'Site deleted but nginx reload failed: {message}', 'warning')
- else:
- flash('Failed to delete site configuration', 'error')
-
- return redirect(url_for('dashboard'))
-
-@app.route('/api/sites')
-@login_required
-def api_sites():
- """API endpoint to get all sites"""
- sites = get_sites()
- return jsonify({
- 'sites': sites,
- 'total': len(sites)
- })
-
-@app.route('/api/nginx-status')
-@login_required
-def api_nginx_status():
- """API endpoint for nginx status"""
- sites = get_sites()
- nginx_running = check_nginx_status()
-
- return jsonify({
- 'status': 'running' if nginx_running else 'stopped',
- 'total_sites': len(sites),
- 'sites': sites
- })
-
-@app.route('/api/network-stats')
-@login_required
-def api_network_stats():
- """API endpoint to get network stats for nginx container (mlite_nginx)."""
- try:
- series = get_container_network_stats()
- return jsonify({
- 'success': True,
- 'network': series
- })
- except Exception as e:
- logger.error(f"Error in /api/network-stats: {e}")
- return jsonify({
- 'success': False,
- 'error': str(e),
- 'network': []
- }), 500
-
-@app.route('/api/reload-nginx')
-@login_required
-def api_reload_nginx():
- """API endpoint to reload nginx"""
- success, message = reload_nginx()
- return jsonify({
- 'success': success,
- 'message': message
- })
-
-# Helpers for vhost .conf editing
-CONF_MAX_SIZE = 200 * 1024 # 200KB safety limit
-SITE_NAME_REGEX = re.compile(r'^[a-zA-Z0-9.-]+$')
-
-def is_valid_site_name(name: str) -> bool:
- return bool(name) and SITE_NAME_REGEX.match(name) is not None and '/' not in name and '\\' not in name
-
-def get_conf_path(site_name: str) -> str:
- filename = f"{site_name}.conf"
- path = os.path.normpath(os.path.join(NGINX_CONF_DIR, filename))
- base_norm = os.path.normpath(NGINX_CONF_DIR)
- if not path.startswith(base_norm):
- raise ValueError('Invalid path resolved')
- return path
-
-def read_conf_file(site_name: str) -> tuple[bool, str, str]:
- try:
- if not is_valid_site_name(site_name):
- return False, '', 'Invalid site name'
- path = get_conf_path(site_name)
- if not os.path.exists(path):
- return False, '', 'Configuration file not found'
- with open(path, 'r') as f:
- content = f.read()
- return True, content, ''
- except Exception as e:
- return False, '', str(e)
-
-def backup_conf_file(site_name: str) -> tuple[bool, str]:
- try:
- path = get_conf_path(site_name)
- ts = datetime.now().strftime('%Y%m%d_%H%M%S')
- backup_path = f"{path}.bak_{ts}"
- shutil.copyfile(path, backup_path)
- return True, backup_path
- except Exception as e:
- return False, str(e)
-
-def write_conf_file(site_name: str, content: str) -> tuple[bool, str]:
- try:
- if len(content.encode('utf-8')) > CONF_MAX_SIZE:
- return False, 'Configuration too large'
- path = get_conf_path(site_name)
- with open(path, 'w') as f:
- f.write(content)
- return True, ''
- except Exception as e:
- return False, str(e)
-
-@app.route('/api/vhost/', methods=['GET'])
-@login_required
-def api_vhost(site_name):
- if not is_valid_site_name(site_name):
- return jsonify({'error': 'Invalid site name'}), 400
- ok, content, err = read_conf_file(site_name)
- if not ok:
- return jsonify({'error': err}), 404
- return jsonify({'site_name': site_name, 'content': content})
-
-@app.route('/edit_vhost/', methods=['GET', 'POST'])
-@login_required
-def edit_vhost(site_name):
- nginx_status = check_nginx_status()
- if not is_valid_site_name(site_name):
- flash('Invalid site name', 'error')
- return redirect(url_for('dashboard'))
- if request.method == 'GET':
- ok, content, err = read_conf_file(site_name)
- if not ok:
- flash(err, 'error')
- return redirect(url_for('dashboard'))
- return render_template('edit_vhost.html', site_name=site_name, content=content, nginx_status=nginx_status)
- # POST: save changes with backup and reload nginx
- new_content = request.form.get('content', '')
- if not new_content:
- flash('Content cannot be empty', 'error')
- return redirect(url_for('edit_vhost', site_name=site_name))
- # Backup first
- b_ok, backup_path_or_err = backup_conf_file(site_name)
- if not b_ok:
- flash(f'Backup failed: {backup_path_or_err}', 'error')
- return redirect(url_for('edit_vhost', site_name=site_name))
- # Write file
- w_ok, w_err = write_conf_file(site_name, new_content)
- if not w_ok:
- flash(f'Write failed: {w_err}', 'error')
- return redirect(url_for('edit_vhost', site_name=site_name))
- # Test and reload
- success, message = reload_nginx()
- if success:
- flash('Vhost configuration saved and Nginx reloaded successfully', 'success')
- return redirect(url_for('dashboard'))
- else:
- # Revert from backup on failure
- try:
- shutil.copyfile(backup_path_or_err, get_conf_path(site_name))
- reload_nginx()
- except Exception:
- pass
- flash(f'Nginx test/reload failed: {message}. Changes reverted.', 'error')
- return redirect(url_for('edit_vhost', site_name=site_name))
- return redirect(url_for('dashboard'))
-
-@app.route('/containers')
-@login_required
-def containers():
- """Container management page"""
- print("[DEBUG] === CONTAINERS ROUTE STARTED ===")
- nginx_status = check_nginx_status()
-
- services = get_services_from_compose()
- print(f"[DEBUG] get_services_from_compose() returned {len(services)} services")
-
- # Prefer docker ps-based status (works without compose plugin)
- docker_ps_status = get_container_status_via_docker_ps()
- print(f"[DEBUG] get_container_status_via_docker_ps() returned {len(docker_ps_status)} items")
-
- containers_data = []
- # Build local Docker images catalog once
- images_catalog = {}
- project_name = None
- try:
- project_name = get_compose_project()
- except Exception:
- project_name = None
- try:
- img_res = subprocess.run(['docker', 'images', '--format', '{{.Repository}}|{{.Tag}}|{{.ID}}'], capture_output=True, text=True, timeout=10)
- for line in (img_res.stdout or '').splitlines():
- parts = line.strip().split('|')
- if len(parts) >= 3:
- repo = parts[0] or ''
- tag = parts[1] or ''
- img_id = parts[2] or ''
- full = f"{repo}:{tag}" if repo and tag else repo
- if full:
- images_catalog[full] = img_id
- except Exception as e:
- print(f"[WARN] Failed to list docker images: {e}")
-
- print(f"[DEBUG] Processing {len(services)} services...")
- for service in services:
- service_name = service['name']
- container_name = service['container_name']
- container_info = docker_ps_status.get(container_name, {
- 'running': False,
- 'status': 'not created',
- 'created': '',
- 'image': service['image'],
- 'ports': []
- })
- # Determine if an image exists for this service
- container_exists = container_name in docker_ps_status
- has_image = False
- if container_exists:
- has_image = True
- elif service.get('build'):
- patterns = []
- if project_name:
- patterns.append(f"{project_name}-{service_name}")
- patterns.append(f"{project_name}_{service_name}")
- patterns.append(f"mlite_{service_name}")
- for repo in images_catalog.keys():
- if any(p in repo for p in patterns):
- has_image = True
- break
- containers_data.append({
- 'name': container_name,
- 'service': service_name,
- 'running': container_info['running'],
- 'status': container_info['status'],
- 'created': container_info['created'],
- 'image': container_info['image'],
- 'ports': deduplicate_ports(container_info['ports']),
- 'build': service['build'],
- 'volumes': service['volumes'],
- 'environment': service['environment'],
- 'depends_on': service['depends_on'],
- 'exists': container_exists,
- 'has_container': container_exists,
- 'has_image': has_image
- })
-
- print(f"[DEBUG] Final containers_data: {len(containers_data)} items")
-
- categories = categorize_services(containers_data)
- print(f"[DEBUG] Created {len(categories)} categories: {list(categories.keys())}")
- for cat_name, cat_items in categories.items():
- print(f"[DEBUG] Category '{cat_name}': {len(cat_items)} items")
-
- print("[DEBUG] === CONTAINERS ROUTE COMPLETED ===")
- return render_template('containers.html', categories=categories, containers=containers_data, nginx_status=nginx_status)
-
-@app.route('/container/build/', methods=['POST'])
-@login_required
-def container_build(service_name):
- """Build specific container"""
- # Security: validate service name
- if not re.match(r'^[a-zA-Z0-9_-]+$', service_name):
- flash('Invalid service name', 'error')
- return redirect(url_for('containers'))
-
- success, message = build_container(service_name)
- if success:
- flash(message, 'success')
- else:
- flash(message, 'error')
-
- return redirect(url_for('containers'))
-
-@app.route('/container/build-log/')
-@login_required
-def container_build_log(service_name):
- """Stream build logs for specific service via SSE and persist to temp file"""
- # Validate service name
- if not re.match(r'^[a-zA-Z0-9_-]+$', service_name):
- return Response("data: Invalid service name\n\n", mimetype='text/event-stream')
- if service_name == 'panel':
- return Response("data: Cannot build panel container\n\n", mimetype='text/event-stream')
-
- project = get_compose_project()
- base = get_compose_cmd_base()
- resolved_file = get_resolved_compose_file()
- cmd = base + ['-f', resolved_file] + (['--project-name', project] if project else []) + ['build', service_name]
-
- # Prepare temp log file path
- log_dir = '/app/tmp'
- try:
- os.makedirs(log_dir, exist_ok=True)
- except Exception:
- pass
- log_path = os.path.join(log_dir, f"build_{service_name}_{int(time.time())}.log")
-
- def generate():
- yield f"data: Running: {' '.join(cmd)}\n\n"
- try:
- with open(log_path, 'w') as lf:
- lf.write(f"Running: {' '.join(cmd)}\n")
- ensure_compose_env()
- env = os.environ.copy()
- proj_dir = get_project_dir()
- if proj_dir:
- env['HOST_PROJECT_DIR'] = proj_dir
- proc = subprocess.Popen(
- cmd,
- cwd='/workspace/docker',
- env=env,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- bufsize=1
- )
- for line in proc.stdout:
- clean = line.rstrip()
- lf.write(clean + "\n")
- lf.flush()
- yield f"data: {clean}\n\n"
- proc.wait()
- lf.write(f"Build finished with code {proc.returncode}\n")
- lf.flush()
- yield f"data: Build finished with code {proc.returncode}\n\n"
- yield "event: done\n" + ("data: success\n\n" if proc.returncode == 0 else "data: failed\n\n")
- except Exception as e:
- try:
- with open(log_path, 'a') as lf:
- lf.write(f"Build error: {str(e)}\n")
- except Exception:
- pass
- yield f"data: Build error: {str(e)}\n\n"
- yield "event: done\ndata: error\n\n"
-
- return Response(stream_with_context(generate()), mimetype='text/event-stream')
-
-@app.route('/container/up/', methods=['POST'])
-@login_required
-def container_up(service_name):
- """Start specific container"""
- if not re.match(r'^[a-zA-Z0-9_-]+$', service_name):
- flash('Invalid service name', 'error')
- return redirect(url_for('containers'))
-
- success, message = start_container(service_name)
- if success:
- flash(message, 'success')
- else:
- flash(message, 'error')
-
- return redirect(url_for('containers'))
-
-@app.route('/container/down/', methods=['POST'])
-@login_required
-def container_down(service_name):
- """Stop specific container"""
- logger.info(f"[container_down] POST received for service='{service_name}'")
- if not re.match(r'^[a-zA-Z0-9_-]+$', service_name):
- logger.warning(f"[container_down] Invalid service name: '{service_name}'")
- flash('Invalid service name', 'error')
- return redirect(url_for('containers'))
-
- success, message = stop_container(service_name)
- logger.info(f"[container_down] stop_container returned success={success}, message='{message}'")
- if success:
- flash(message, 'success')
- else:
- # Tampilkan pesan error dan arahkan user untuk melihat logs
- if 'Cannot control panel container' in message:
- flash('Tidak dapat menghentikan container panel dari UI.', 'error')
- else:
- flash(f"Gagal stop: {message}. Lihat logs untuk detail.", 'error')
-
- return redirect(url_for('containers'))
-
-@app.route('/container/remove/', methods=['POST'])
-@login_required
-def container_remove(service_name):
- """Remove specific container"""
- logger.info(f"[container_remove] POST received for service='{service_name}'")
- if not re.match(r'^[a-zA-Z0-9_-]+$', service_name):
- logger.warning(f"[container_remove] Invalid service name: '{service_name}'")
- flash('Invalid service name', 'error')
- return redirect(url_for('containers'))
-
- success, message = remove_container(service_name)
- logger.info(f"[container_remove] remove_container returned success={success}, message='{message}'")
- if success:
- flash(message, 'success')
- else:
- if 'Cannot control panel container' in message:
- flash('Tidak dapat menghapus container panel dari UI.', 'error')
- else:
- flash(f"Gagal hapus container: {message}.", 'error')
-
- return redirect(url_for('containers'))
-
-@app.route('/container/remove-image/', methods=['POST'])
-@login_required
-def container_remove_image(service_name):
- """Remove image for specific service"""
- logger.info(f"[container_remove_image] POST received for service='{service_name}'")
- if not re.match(r'^[a-zA-Z0-9_-]+$', service_name):
- logger.warning(f"[container_remove_image] Invalid service name: '{service_name}'")
- flash('Invalid service name', 'error')
- return redirect(url_for('containers'))
-
- success, message = remove_image(service_name)
- logger.info(f"[container_remove_image] remove_image returned success={success}, message='{message}'")
- if success:
- flash(message, 'success')
- else:
- if 'Cannot control panel container' in message:
- flash('Tidak dapat menghapus image panel dari UI.', 'error')
- else:
- flash(f"Gagal hapus image: {message}.", 'error')
-
- return redirect(url_for('containers'))
-
-
-
-def categorize_services(containers):
- """Categorize services into groups"""
- categories = {
- 'PHP Containers': [],
- 'Web Services': [],
- 'Database & Services': []
- }
- php_services = {'php56','php70','php71','php72','php73','php74','php80','php81','php82','php83'}
- for container in containers:
- service_name = container['service']
- if service_name in php_services:
- categories['PHP Containers'].append(container)
- elif service_name in ['nginx', 'panel']:
- categories['Web Services'].append(container)
- else:
- categories['Database & Services'].append(container)
- return categories
-
-# Add SSE start-log route
-@app.route('/container/start-log/')
-@login_required
-def container_start_log(service_name):
- """Stream start (compose up) logs for specific service via SSE."""
- if not re.match(r'^[a-zA-Z0-9_-]+$', service_name):
- return Response("data: Invalid service name\n\n", mimetype='text/event-stream')
- if service_name == 'panel':
- return Response("data: Cannot control panel container\n\n", mimetype='text/event-stream')
-
- project = get_compose_project()
- base = get_compose_cmd_base()
- resolved_file = get_resolved_compose_file()
- cmd = base + ['-f', resolved_file] + (['--project-name', project] if project else []) + ['up', '-d', '--no-deps', service_name]
-
- log_dir = '/app/tmp'
- try:
- os.makedirs(log_dir, exist_ok=True)
- except Exception:
- pass
- log_path = os.path.join(log_dir, f"start_{service_name}_{int(time.time())}.log")
-
- def generate():
- yield f"data: Running: {' '.join(cmd)}\n\n"
- try:
- with open(log_path, 'w') as lf:
- lf.write(f"Running: {' '.join(cmd)}\n")
- env = os.environ.copy()
- proj_dir = get_project_dir()
- if proj_dir:
- env['HOST_PROJECT_DIR'] = proj_dir
- proc = subprocess.Popen(
- cmd,
- cwd='/workspace/docker',
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- bufsize=1
- )
- for line in proc.stdout:
- if line:
- lf.write(line)
- lf.flush()
- yield f"data: {line.strip()}\n\n"
- rc = proc.wait()
- lf.write(f"\nStart finished with code {rc}\n")
- yield "event: done\n"
- yield f"data: {'success' if rc == 0 else 'error'}\n\n"
- except Exception as e:
- try:
- with open(log_path, 'a') as lf:
- lf.write(f"\nStart error: {str(e)}\n")
- except Exception:
- pass
- yield f"data: Start error: {str(e)}\n\n"
- yield "event: done\n"
- yield "data: error\n\n"
-
- return Response(stream_with_context(generate()), mimetype='text/event-stream')
-
-@app.route('/nginx')
-@login_required
-def nginx_page():
- """Nginx management page"""
- try:
- # Get nginx container info
- docker_ps_status = get_container_status_via_docker_ps()
- nginx_info = docker_ps_status.get(NGINX_CONTAINER_NAME, {
- 'running': False,
- 'status': 'not found',
- 'image': 'unknown',
- 'ports': [],
- 'created': ''
- })
- # Format ports for display
- try:
- ports_list = deduplicate_ports(nginx_info.get('ports', []) or [])
- formatted = []
- for p in ports_list:
- pub = str(p.get('PublishedPort')) if p.get('PublishedPort') is not None else ''
- tgt = str(p.get('TargetPort')) if p.get('TargetPort') is not None else ''
- if pub and tgt:
- formatted.append(f"{pub}:{tgt}")
- elif tgt:
- formatted.append(tgt)
- nginx_ports_display = ', '.join(formatted)
- except Exception:
- nginx_ports_display = ''
-
- # Get nginx configuration files (enabled and disabled)
- config_files = []
- try:
- logger.info(f"[nginx_page] NGINX_CONF_DIR={NGINX_CONF_DIR}")
- except Exception:
- pass
- if os.path.exists(NGINX_CONF_DIR):
- try:
- dir_list = sorted(os.listdir(NGINX_CONF_DIR))
- logger.info(f"[nginx_page] conf.d list: {dir_list}")
- siti_path = os.path.join(NGINX_CONF_DIR, 'siti.loc.conf')
- logger.info(f"[nginx_page] siti.loc.conf path: {siti_path} exists={os.path.exists(siti_path)}")
- except Exception as e:
- logger.warning(f"[nginx_page] Failed to list conf dir: {e}")
- for filename in os.listdir(NGINX_CONF_DIR):
- if filename.endswith('.conf') or filename.endswith('.conf.disabled'):
- filepath = os.path.join(NGINX_CONF_DIR, filename)
- try:
- with open(filepath, 'r') as f:
- content = f.read()
- # Parse config info
- enabled = filename.endswith('.conf')
- domain = filename.replace('.conf.disabled', '').replace('.conf', '')
- try:
- logger.info(f"[nginx_page] Found config file {filename}, domain={domain}, enabled={enabled}")
- except Exception:
- pass
- site_type = 'proxy'
- port = None
- root_dir = None
- if 'fastcgi_pass' in content:
- site_type = 'php'
- elif 'proxy_pass' in content:
- site_type = 'proxy'
- for line in content.split('\n'):
- if 'proxy_pass http://localhost:' in line:
- port = line.split('proxy_pass http://localhost:')[1].split(';')[0].strip()
- break
- else:
- site_type = 'static'
- # SSL status parsing
- ssl_enabled = ('listen 443' in content) and ('ssl_certificate' in content)
- https_redirect = ('return 301 https://$host$request_uri' in content) or ('return 301 https://' in content)
- config_files.append({
- 'domain': domain,
- 'type': site_type,
- 'port': port,
- 'root_dir': root_dir,
- 'filename': filename,
- 'created': datetime.fromtimestamp(os.path.getctime(filepath)).strftime('%Y-%m-%d %H:%M:%S'),
- 'enabled': enabled,
- 'ssl_enabled': ssl_enabled,
- 'https_redirect': https_redirect
- })
- except Exception as e:
- print(f"Error reading config file {filename}: {e}")
-
- # Get nginx logs
- logs = []
- try:
- # Get last 50 lines of nginx logs
- result = subprocess.run([
- 'docker', 'logs', '--tail', '50', NGINX_CONTAINER_NAME
- ], capture_output=True, text=True, timeout=10)
-
- if result.returncode == 0:
- logs = result.stdout.strip().split('\n')[-20:] # Last 20 lines
- except Exception as e:
- print(f"Error getting nginx logs: {e}")
-
- return render_template('nginx.html',
- nginx_info=nginx_info,
- nginx_ports_display=nginx_ports_display,
- config_files=config_files,
- logs=logs,
- nginx_container_name=NGINX_CONTAINER_NAME)
- except Exception as e:
- flash(f'Error loading nginx page: {str(e)}', 'error')
- return redirect(url_for('dashboard'))
-
-@app.route('/container/restart/', methods=['POST'])
-@login_required
-def container_restart(service_name):
- """Restart specific container"""
- if not re.match(r'^[a-zA-Z0-9_-]+$', service_name):
- flash('Invalid service name', 'error')
- return redirect(url_for('containers'))
-
- success, message = restart_container(service_name)
- if success:
- flash(message, 'success')
- else:
- flash(message, 'error')
-
- return redirect(url_for('containers'))
-
-def categorize_services(containers):
- """Categorize services into groups"""
- categories = {
- 'PHP Containers': [],
- 'Web Services': [],
- 'Database & Services': []
- }
- php_services = {'php56','php70','php71','php72','php73','php74','php80','php81','php82','php83'}
- for container in containers:
- service_name = container['service']
- if service_name in php_services:
- categories['PHP Containers'].append(container)
- elif service_name in ['nginx', 'panel']:
- categories['Web Services'].append(container)
- else:
- categories['Database & Services'].append(container)
- return categories
-
-def get_resolved_compose_file():
- """Generate a resolved compose file by parsing YAML and converting
- ${HOST_PROJECT_DIR}, ./ and ../ host paths in volumes to absolute host paths.
- Returns path to the temporary resolved compose file.
- """
- try:
- src_path = '/workspace/docker/docker-compose.yml'
- if not os.path.exists(src_path):
- return src_path
- with open(src_path, 'r') as f:
- data = yaml.safe_load(f)
- host_dir = get_project_dir()
- if not isinstance(data, dict):
- raise Exception('compose data invalid')
- services = data.get('services', {})
- compose_dir_host = os.path.join(host_dir, 'docker') if host_dir else None
- for svc_name, svc in (services or {}).items():
- vols = svc.get('volumes')
- if not vols:
- continue
- new_vols = []
- for v in vols:
- if isinstance(v, str):
- parts = v.split(':')
- if len(parts) >= 2:
- src = parts[0]
- rest = ':'.join(parts[1:])
- # Convert only host-side src
- if host_dir:
- if src == '${HOST_PROJECT_DIR}':
- src = host_dir
- elif src.startswith('./') and compose_dir_host:
- src = os.path.join(compose_dir_host, src[2:])
- elif src.startswith('../'):
- src = os.path.join(host_dir, src[3:])
- new_v = f"{src}:{rest}"
- new_vols.append(new_v)
- else:
- new_vols.append(v)
- else:
- new_vols.append(v)
- svc['volumes'] = new_vols
- out_path = '/workspace/docker/.compose-resolved.yml'
- with open(out_path, 'w') as f:
- f.write(yaml.safe_dump(data, sort_keys=False))
- return out_path
- except Exception:
- return '/workspace/docker/docker-compose.yml'
-
-@app.route('/nginx/toggle/', methods=['POST'])
-@login_required
-def toggle_site(domain):
- """Enable/Disable a site's nginx config by renaming .conf <-> .conf.disabled"""
- try:
- if not is_valid_site_name(domain):
- flash('Invalid domain name', 'error')
- return redirect(url_for('nginx_page'))
- conf_path = get_conf_path(domain)
- disabled_path = os.path.normpath(os.path.join(NGINX_CONF_DIR, f"{domain}.conf.disabled"))
- # Determine current state
- enabled = os.path.exists(conf_path)
- if enabled:
- # Disable: move to .conf.disabled
- try:
- shutil.move(conf_path, disabled_path)
- except Exception as e:
- flash(f'Failed to disable site: {str(e)}', 'error')
- return redirect(url_for('nginx_page'))
- success, message = reload_nginx()
- if not success:
- # revert
- try:
- shutil.move(disabled_path, conf_path)
- except Exception:
- pass
- flash(f'Failed to reload nginx: {message}', 'error')
- else:
- flash('Site disabled and nginx reloaded', 'success')
- else:
- # Enable: move .conf.disabled back to .conf
- if not os.path.exists(disabled_path):
- flash('Disabled config not found', 'error')
- return redirect(url_for('nginx_page'))
- try:
- shutil.move(disabled_path, conf_path)
- except Exception as e:
- flash(f'Failed to enable site: {str(e)}', 'error')
- return redirect(url_for('nginx_page'))
- success, message = reload_nginx()
- if not success:
- # revert
- try:
- shutil.move(conf_path, disabled_path)
- except Exception:
- pass
- flash(f'Failed to reload nginx: {message}', 'error')
- else:
- flash('Site enabled and nginx reloaded', 'success')
- except Exception as e:
- flash(f'Error toggling site: {str(e)}', 'error')
- return redirect(url_for('nginx_page'))
-
-@app.route('/nginx/delete/', methods=['POST'])
-@login_required
-def nginx_delete_site(domain):
- """Delete a site's nginx config (.conf or .conf.disabled) and reload nginx"""
- try:
- if not is_valid_site_name(domain):
- flash('Invalid domain name', 'error')
- return redirect(url_for('nginx_page'))
- conf_path = get_conf_path(domain)
- disabled_path = os.path.normpath(os.path.join(NGINX_CONF_DIR, f"{domain}.conf.disabled"))
- removed = False
- # Try remove enabled .conf
- if os.path.exists(conf_path):
- try:
- os.remove(conf_path)
- removed = True
- except Exception as e:
- flash(f'Failed to delete config: {str(e)}', 'error')
- return redirect(url_for('nginx_page'))
- # Or remove disabled file
- elif os.path.exists(disabled_path):
- try:
- os.remove(disabled_path)
- removed = True
- except Exception as e:
- flash(f'Failed to delete disabled config: {str(e)}', 'error')
- return redirect(url_for('nginx_page'))
- else:
- flash('Config file not found', 'error')
- return redirect(url_for('nginx_page'))
- # Reload nginx if removed
- if removed:
- success, message = reload_nginx()
- if success:
- flash('Config deleted and nginx reloaded', 'success')
- else:
- flash(f'Config deleted but reload failed: {message}', 'warning')
- except Exception as e:
- flash(f'Error deleting site: {str(e)}', 'error')
- return redirect(url_for('nginx_page'))
-
-# --- SSL Helpers & API ---
-
-def _ssl_paths(domain: str):
- ssl_dir = os.path.join(NGINX_CONF_DIR, 'ssl', domain)
- return ssl_dir, os.path.join(ssl_dir, 'fullchain.pem'), os.path.join(ssl_dir, 'privkey.pem')
-
-@app.route('/api/nginx/ssl/status/', methods=['GET'])
-@login_required
-def api_ssl_status(domain):
- try:
- if not is_valid_site_name(domain):
- return jsonify({'success': False, 'error': 'Invalid domain'}), 400
- conf_path = get_conf_path(domain)
- disabled_path = os.path.normpath(os.path.join(NGINX_CONF_DIR, f"{domain}.conf.disabled"))
- target_path = conf_path if os.path.exists(conf_path) else (disabled_path if os.path.exists(disabled_path) else None)
- if not target_path:
- return jsonify({'success': False, 'error': 'Config not found'}), 404
- with open(target_path, 'r') as f:
- content = f.read()
- ssl_enabled = ('listen 443' in content) and ('ssl_certificate' in content)
- https_redirect = ('return 301 https://$host$request_uri' in content) or ('return 301 https://' in content)
- ssl_dir, cert_path, key_path = _ssl_paths(domain)
- # Check certs inside nginx container as primary source
- container_fullchain = f"/etc/letsencrypt/live/{domain}/fullchain.pem"
- container_privkey = f"/etc/letsencrypt/live/{domain}/privkey.pem"
- container_cert_exists = False
- try:
- check_cmd = [
- 'docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c',
- f"test -f {shlex.quote(container_fullchain)} && test -f {shlex.quote(container_privkey)} && echo OK || echo MISSING"
- ]
- chk = subprocess.run(check_cmd, capture_output=True, text=True, timeout=10)
- container_cert_exists = (chk.returncode == 0 and 'OK' in (chk.stdout or ''))
- except Exception:
- container_cert_exists = False
- host_cert_exists = os.path.exists(cert_path) and os.path.exists(key_path)
- return jsonify({
- 'success': True,
- 'ssl_enabled': ssl_enabled,
- 'https_redirect': https_redirect,
- 'cert_exists': container_cert_exists or host_cert_exists,
- 'cert_path': f'/etc/letsencrypt/live/{domain}/fullchain.pem',
- 'key_path': f'/etc/letsencrypt/live/{domain}/privkey.pem',
- 'host_cert_path': f'/etc/nginx/conf.d/ssl/{domain}/fullchain.pem',
- 'host_key_path': f'/etc/nginx/conf.d/ssl/{domain}/privkey.pem',
- 'source': 'container' if container_cert_exists else ('host' if host_cert_exists else 'none')
- })
- except Exception as e:
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/nginx/ssl/configure', methods=['POST'])
-@login_required
-def api_ssl_configure():
- try:
- payload = request.json if request.is_json else request.form
- domain = payload.get('domain')
- enable_ssl = str(payload.get('enable_ssl', 'true')).lower() in ['true', '1', 'yes']
- force_redirect = str(payload.get('force_redirect', 'true')).lower() in ['true', '1', 'yes']
- if not domain or not is_valid_site_name(domain):
- return jsonify({'success': False, 'error': 'Invalid domain'}), 400
- conf_path = get_conf_path(domain)
- disabled_path = os.path.normpath(os.path.join(NGINX_CONF_DIR, f"{domain}.conf.disabled"))
- target_path = conf_path if os.path.exists(conf_path) else (disabled_path if os.path.exists(disabled_path) else None)
- if not target_path:
- return jsonify({'success': False, 'error': 'Config not found'}), 404
- with open(target_path, 'r') as f:
- content = f.read()
- ssl_dir, cert_path, key_path = _ssl_paths(domain)
- # Try Let's Encrypt via certbot container (webroot) if email provided
- le_live_dir = os.path.join('/etc', 'letsencrypt', 'live', domain)
- if (not os.path.exists(cert_path) or not os.path.exists(key_path)) and (payload.get('email') or os.environ.get('LE_EMAIL')):
- try:
- email_addr = (payload.get('email') or os.environ.get('LE_EMAIL'))
- # Ensure ACME webroot exists inside Nginx container
- try:
- subprocess.run(['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', 'mkdir -p /var/www/certbot/.well-known/acme-challenge'], capture_output=True, text=True, timeout=20)
- except Exception:
- pass
- # Run certbot inside Nginx container using webroot challenge
- cmd = [
- 'docker', 'exec', NGINX_CONTAINER_NAME, 'certbot', 'certonly',
- '--webroot', '-w', '/var/www/certbot',
- '-d', domain, '--email', email_addr,
- '--agree-tos', '--non-interactive', '--preferred-challenges', 'http'
- ]
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=240)
- if result.returncode == 0:
- # Verify certs exist inside container only; no copy to host
- src_fullchain_abs = f"/etc/letsencrypt/live/{domain}/fullchain.pem"
- src_privkey_abs = f"/etc/letsencrypt/live/{domain}/privkey.pem"
- try:
- check_cmd = [
- 'docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c',
- f"test -f {shlex.quote(src_fullchain_abs)} && test -f {shlex.quote(src_privkey_abs)} && echo OK || echo MISSING"
- ]
- check = subprocess.run(check_cmd, capture_output=True, text=True, timeout=20)
- if check.returncode == 0 and 'OK' in (check.stdout or ''):
- logger.info(f"Let's Encrypt certs present in container for {domain}")
- else:
- logger.warning(
- f"Certbot succeeded but certs not found in container for {domain}: "
- f"stdout={(check.stdout or '').strip()} stderr={(check.stderr or '').strip()}"
- )
- except Exception as e:
- logger.warning(f"Failed to verify certs in container for {domain}: {e}")
- else:
- logger.warning(f"Certbot (nginx) failed for {domain}: {result.stderr or result.stdout}")
- except Exception as ce:
- logger.error(f"Certbot (nginx) error for {domain}: {ce}")
- # Determine certificate availability from container or host
- container_fullchain = f"/etc/letsencrypt/live/{domain}/fullchain.pem"
- container_privkey = f"/etc/letsencrypt/live/{domain}/privkey.pem"
- container_cert_exists = False
- try:
- check_cmd = [
- 'docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c',
- f"test -f {shlex.quote(container_fullchain)} && test -f {shlex.quote(container_privkey)} && echo OK || echo MISSING"
- ]
- chk = subprocess.run(check_cmd, capture_output=True, text=True, timeout=10)
- container_cert_exists = (chk.returncode == 0 and 'OK' in (chk.stdout or ''))
- except Exception:
- container_cert_exists = False
- host_cert_exists = os.path.exists(cert_path) and os.path.exists(key_path)
- if enable_ssl and not (container_cert_exists or host_cert_exists):
- return jsonify({'success': False, 'error': "Let's Encrypt certificate not available. Ensure port 80 is open, DNS points to host, ACME challenge is served at '/.well-known/acme-challenge', and a valid email is provided. Check panel logs for certbot output."}), 400
- ssl_enabled = ('listen 443' in content) and ('ssl_certificate' in content)
- https_redirect_present = ('return 301 https://$host$request_uri' in content) or ('return 301 https://' in content)
- if enable_ssl and not ssl_enabled:
- # Build SSL server block based on site type
- site_type = 'proxy'
- port = None
- root_dir = None
- php_fastcgi_line = None
- # Helper: extract root from main server (listen 80) block, ignoring location blocks
- def _extract_root_from_main_server(c: str):
- lines = c.splitlines()
- depth = 0
- in_server = False
- saw_listen80 = False
- root_val = None
- for line in lines:
- stripped = line.strip()
- if not in_server and re.search(r'\bserver\s*\{', line) and depth == 0:
- in_server = True
- saw_listen80 = False
- root_val = None
- depth += line.count('{') - line.count('}')
- continue
- if in_server:
- if depth == 1 and re.search(r'\blisten\s+(?:\S+:)?80\b', stripped):
- saw_listen80 = True
- if depth == 1 and stripped.startswith('root '):
- if root_val is None:
- m = re.search(r'root\s+([^;]+);', stripped)
- if m:
- root_val = m.group(1).strip()
- depth += line.count('{') - line.count('}')
- if depth <= 0:
- if saw_listen80:
- return root_val
- in_server = False
- else:
- depth += line.count('{') - line.count('}')
- return None
-
- if 'fastcgi_pass' in content:
- site_type = 'php'
- # Use root from main server block, ignore ACME location root
- root_dir = _extract_root_from_main_server(content)
- # Capture fastcgi_pass line as-is
- for line in content.split('\n'):
- if 'fastcgi_pass' in line:
- php_fastcgi_line = line.strip()
- break
- elif 'proxy_pass' in content:
- site_type = 'proxy'
- for line in content.split('\n'):
- if 'proxy_pass http://localhost:' in line:
- try:
- port = line.split('proxy_pass http://localhost:')[1].split(';')[0].strip()
- except Exception:
- pass
- break
- else:
- site_type = 'static'
- # Use root from main server block, ignore ACME location root
- root_dir = _extract_root_from_main_server(content)
- # Choose cert paths: prefer host copies if present, else container paths
- use_host_paths = os.path.exists(cert_path) and os.path.exists(key_path)
- cert_ref = cert_path if use_host_paths else f"/etc/letsencrypt/live/{domain}/fullchain.pem"
- key_ref = key_path if use_host_paths else f"/etc/letsencrypt/live/{domain}/privkey.pem"
- ssl_block = [
- 'server {',
- ' listen 443 ssl;',
- f' server_name {domain};',
- f' ssl_certificate {cert_ref};',
- f' ssl_certificate_key {key_ref};',
- ' ssl_protocols TLSv1.2 TLSv1.3;',
- ' ssl_prefer_server_ciphers on;',
- ]
- if site_type == 'php':
- if root_dir:
- ssl_block.append(f' root {root_dir};')
- ssl_block.extend([
- ' index index.php index.html;',
- '',
- ' location / {',
- ' try_files $uri $uri/ /index.php?$args;',
- ' }',
- ' location /admin {',
- ' try_files $uri $uri/ /admin/index.php?$args;',
- ' }',
- ' location ~ \\.php$ {',
- ' include fastcgi_params;',
- php_fastcgi_line or ' fastcgi_pass php83:9000;',
- ' fastcgi_index index.php;',
- ' fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;',
- ' fastcgi_buffer_size 128k;',
- ' fastcgi_buffers 4 256k;',
- ' fastcgi_busy_buffers_size 256k;',
- ' }',
- ' location ~ /\\. {',
- ' deny all;',
- ' }',
- ])
- elif site_type == 'proxy':
- ssl_block.extend([
- ' location / {',
- f' proxy_pass http://localhost:{port or "80"};',
- ' proxy_set_header Host $host;',
- ' proxy_set_header X-Real-IP $remote_addr;',
- ' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;',
- ' proxy_set_header X-Forwarded-Proto $scheme;',
- ' }',
- ])
- else:
- if root_dir:
- ssl_block.append(f' root {root_dir};')
- ssl_block.extend([
- ' index index.html;',
- ' location / {',
- ' try_files $uri $uri/ /index.html;',
- ' }',
- ' location ~ /\\ . {',
- ' deny all;',
- ' }',
- ])
- ssl_block.append('}')
- content = content + '\n' + '\n'.join(ssl_block) + '\n'
- if force_redirect and not https_redirect_present:
- redirect_block = (
- 'server {\n'
- ' listen 80;\n'
- f' server_name {domain};\n'
- ' location /.well-known/acme-challenge/ {\n'
- ' root /var/www/certbot;\n'
- ' try_files $uri =404;\n'
- ' }\n'
- ' location / {\n'
- ' return 301 https://$host$request_uri;\n'
- ' }\n'
- '}\n'
- )
- content = redirect_block + '\n' + content
- ok_write, err_write = write_conf_file(domain, content)
- if not ok_write:
- return jsonify({'success': False, 'error': err_write}), 500
- success, message = reload_nginx()
- return jsonify({'success': success, 'message': message})
- except Exception as e:
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/nginx/ssl/disable', methods=['POST'])
-@login_required
-def api_ssl_disable():
- try:
- payload = request.json if request.is_json else request.form
- domain = payload.get('domain')
- if not domain or not is_valid_site_name(domain):
- return jsonify({'success': False, 'error': 'Invalid domain'}), 400
- conf_path = get_conf_path(domain)
- disabled_path = os.path.normpath(os.path.join(NGINX_CONF_DIR, f"{domain}.conf.disabled"))
- target_path = conf_path if os.path.exists(conf_path) else (disabled_path if os.path.exists(disabled_path) else None)
- if not target_path:
- return jsonify({'success': False, 'error': 'Config not found'}), 404
- with open(target_path, 'r') as f:
- content = f.read()
- content = re.sub(r"server\s*\{.*?listen\s+443\s+ssl;.*?\}", "", content, flags=re.DOTALL)
- content = re.sub(r"server\s*\{[^}]*listen\s+80;[^}]*?(?:return\s+301\s+https://|\.well-known/acme-challenge)[^}]*\}", "", content, flags=re.DOTALL)
- ok_write, err_write = write_conf_file(domain, content)
- if not ok_write:
- return jsonify({'success': False, 'error': err_write}), 500
- success, message = reload_nginx()
- return jsonify({'success': success, 'message': message})
- except Exception as e:
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/nginx/ssl/letsencrypt-log/')
-@login_required
-def api_letsencrypt_log(domain):
- """Stream Let's Encrypt generation logs via SSE while running certbot inside nginx container."""
- try:
- if not is_valid_site_name(domain):
- return Response("data: Invalid domain\n\n", mimetype='text/event-stream')
- email = request.args.get('email') or os.environ.get('LE_EMAIL')
- if not email:
- return Response("data: Email is required for Let's Encrypt\n\n", mimetype='text/event-stream')
- try:
- subprocess.run(['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', 'mkdir -p /var/www/certbot/.well-known/acme-challenge'], capture_output=True, text=True, timeout=20)
- except Exception:
- pass
- cmd = [
- 'docker', 'exec', NGINX_CONTAINER_NAME, 'certbot', 'certonly',
- '--webroot', '-w', '/var/www/certbot',
- '-d', domain, '--email', email,
- '--agree-tos', '--non-interactive', '--preferred-challenges', 'http',
- '--debug'
- ]
- log_dir = '/app/tmp'
- try:
- os.makedirs(log_dir, exist_ok=True)
- except Exception:
- pass
- log_path = os.path.join(log_dir, f"letsencrypt_{domain}_{int(time.time())}.log")
- ssl_dir, cert_path, key_path = _ssl_paths(domain)
- os.makedirs(ssl_dir, exist_ok=True)
- def generate():
- yield f"data: Running: {' '.join(cmd)}\n\n"
- try:
- with open(log_path, 'w') as lf:
- lf.write(f"Running: {' '.join(cmd)}\n")
- proc = subprocess.Popen(
- cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- bufsize=1
- )
- for line in proc.stdout:
- if line:
- lf.write(line)
- lf.flush()
- yield f"data: {line.strip()}\n\n"
- rc = proc.wait()
- lf.write(f"\nCertbot finished with code {rc}\n")
- status = 'error'
- if rc == 0:
- try:
- src_fullchain = f"{NGINX_CONTAINER_NAME}:/etc/letsencrypt/live/{domain}/fullchain.pem"
- src_privkey = f"{NGINX_CONTAINER_NAME}:/etc/letsencrypt/live/{domain}/privkey.pem"
- cp_cert = subprocess.run(['docker', 'cp', src_fullchain, cert_path], capture_output=True, text=True, timeout=20)
- yield f"data: Copying fullchain.pem: rc={cp_cert.returncode} err={(cp_cert.stderr or '').strip()}\n\n"
- cp_key = subprocess.run(['docker', 'cp', src_privkey, key_path], capture_output=True, text=True, timeout=20)
- yield f"data: Copying privkey.pem: rc={cp_key.returncode} err={(cp_key.stderr or '').strip()}\n\n"
- if cp_cert.returncode == 0 and cp_key.returncode == 0:
- status = 'success'
- else:
- # If copy failed, check if certs are present in container and allow success
- check_cmd = [
- 'docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c',
- f"test -f /etc/letsencrypt/live/{domain}/fullchain.pem && test -f /etc/letsencrypt/live/{domain}/privkey.pem && echo OK || echo MISSING"
- ]
- chk = subprocess.run(check_cmd, capture_output=True, text=True, timeout=10)
- if chk.returncode == 0 and 'OK' in (chk.stdout or ''):
- status = 'success'
- yield f"data: Certs exist in container; proceeding without host copy.\n\n"
- else:
- status = 'error'
- except Exception as ce:
- yield f"data: Error copying certs: {str(ce)}\n\n"
- status = 'error'
- yield "event: done\n"
- yield f"data: {status}\n\n"
- except Exception as e:
- try:
- with open(log_path, 'a') as lf:
- lf.write(f"\nCertbot error: {str(e)}\n")
- except Exception:
- pass
- yield f"data: Certbot error: {str(e)}\n\n"
- yield "event: done\n"
- return Response(generate(), mimetype='text/event-stream')
- except Exception as e:
- return Response(f"data: Internal error: {str(e)}\n\n", mimetype='text/event-stream')
-
-# PHP-FPM Routes
-@app.route('/php-fpm')
-@login_required
-def php_fpm():
- """PHP-FPM management page"""
- nginx_status = check_nginx_status()
- php_containers = get_php_containers()
- return render_template('php_fpm.html', php_containers=php_containers, nginx_status=nginx_status)
-
-@app.route('/api/php-containers')
-@login_required
-def api_php_containers():
- """API endpoint to get PHP containers status"""
- try:
- containers = get_php_containers()
- return jsonify({
- 'success': True,
- 'containers': containers
- })
- except Exception as e:
- logger.error(f"Error getting PHP containers: {e}")
- return jsonify({
- 'success': False,
- 'error': str(e)
- }), 500
-
-# MySQL Routes
-@app.route('/mysql')
-@login_required
-def mysql():
- """MySQL management page"""
- nginx_status = check_nginx_status()
- mysql_containers = get_mysql_containers()
- return render_template('mysql.html', mysql_containers=mysql_containers, nginx_status=nginx_status)
-
-
-# --- MySQL Configuration Endpoints ---
-@app.route('/api/mysql/get-config', methods=['GET'])
-@login_required
-def get_mysql_config_file():
- """Read MySQL configuration from inside the Docker container."""
- try:
- # Prefer the primary my.cnf if present (effective config), then common defaults
- candidate_files = [
- '/etc/my.cnf',
- '/etc/mysql/my.cnf',
- '/etc/mysql/mysql.conf.d/mysqld.cnf',
- '/etc/mysql/mariadb.conf.d/50-server.cnf',
- '/etc/mysql/conf.d/99-custom.cnf'
- ]
- chosen_path = None
- for path in candidate_files:
- try:
- check = subprocess.run(['docker', 'exec', MYSQL_CONTAINER_NAME, 'test', '-f', path], capture_output=True, text=True)
- if check.returncode == 0:
- chosen_path = path
- break
- except Exception:
- continue
- if not chosen_path:
- # If no file, pick a writable directory for future saves
- dir_candidates = ['/etc/mysql/conf.d', '/etc/mysql/mysql.conf.d', '/etc/mysql/mariadb.conf.d', '/etc']
- for d in dir_candidates:
- check = subprocess.run(['docker', 'exec', MYSQL_CONTAINER_NAME, 'test', '-d', d], capture_output=True, text=True)
- if check.returncode == 0:
- chosen_path = os.path.join(d, '99-custom.cnf')
- break
- if not chosen_path:
- return jsonify({'success': False, 'error': 'No suitable MySQL config location found in container'}), 404
- # Read content if file exists; otherwise provide a minimal template
- read_cmd = ['docker', 'exec', MYSQL_CONTAINER_NAME, 'bash', '-lc', f"if [ -f '{chosen_path}' ]; then cat '{chosen_path}'; fi"]
- read_result = subprocess.run(read_cmd, capture_output=True, text=True)
- content = read_result.stdout if read_result.returncode == 0 else ''
- if not content.strip():
- content = '[mysqld]\n'
- return jsonify({'success': True, 'path': chosen_path, 'config': content})
- except Exception as e:
- logger.error(f"Error reading MySQL config: {e}")
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/mysql/save-config', methods=['POST'])
-@login_required
-def save_mysql_config_file():
- """Save MySQL configuration into the container and restart MySQL."""
- try:
- data = request.get_json(silent=True) or {}
- config_content = data.get('config')
- if not isinstance(config_content, str) or not config_content.strip():
- return jsonify({'success': False, 'error': 'Config content is required'}), 400
- # Determine target path: prefer /etc/my.cnf if present
- target_path = None
- check_my = subprocess.run(['docker', 'exec', MYSQL_CONTAINER_NAME, 'test', '-f', '/etc/my.cnf'], capture_output=True, text=True)
- if check_my.returncode == 0:
- target_path = '/etc/my.cnf'
- else:
- # Fallback to conf directories
- dir_candidates = ['/etc/mysql/conf.d', '/etc/mysql/mysql.conf.d', '/etc/mysql/mariadb.conf.d', '/etc']
- for d in dir_candidates:
- check = subprocess.run(['docker', 'exec', MYSQL_CONTAINER_NAME, 'test', '-d', d], capture_output=True, text=True)
- if check.returncode == 0:
- target_path = f"{d}/99-custom.cnf"
- break
- if not target_path:
- return jsonify({'success': False, 'error': 'MySQL config location not found in container'}), 404
- ts = int(time.time())
- backup_and_write = (
- f"if [ -f '{target_path}' ]; then cp '{target_path}' '{target_path}.backup.{ts}'; fi; "
- f"cat > '{target_path}' <<'EOF'\n{config_content}\nEOF"
- )
- write_cmd = ['docker', 'exec', '-i', MYSQL_CONTAINER_NAME, 'bash', '-lc', backup_and_write]
- write_result = subprocess.run(write_cmd, capture_output=True, text=True)
- if write_result.returncode != 0:
- return jsonify({'success': False, 'error': write_result.stderr or write_result.stdout}), 500
- # Restart MySQL by restarting the container
- restart_result = subprocess.run(['docker', 'restart', MYSQL_CONTAINER_NAME], capture_output=True, text=True)
- if restart_result.returncode != 0:
- return jsonify({'success': False, 'error': f"Failed to restart MySQL container: {restart_result.stderr or restart_result.stdout}"}), 500
- time.sleep(3)
- # Optional verification
- try:
- connection = create_mysql_connection()
- cursor = connection.cursor(dictionary=True)
- cursor.execute('SHOW VARIABLES LIKE "log_bin"')
- lb = cursor.fetchone()
- cursor.execute('SHOW VARIABLES LIKE "server_id"')
- sid = cursor.fetchone()
- logger.info(f"Post-save config: log_bin={lb}, server_id={sid}")
- cursor.close()
- connection.close()
- except Exception as ve:
- logger.warning(f"MySQL verification failed after config save: {ve}")
- return jsonify({'success': True, 'message': f"Configuration saved to {target_path} and MySQL restarted"})
- except Exception as e:
- logger.error(f"Error saving MySQL config: {e}")
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/mysql-containers')
-@login_required
-def api_mysql_containers():
- """API endpoint to get MySQL containers status"""
- try:
- containers = get_mysql_containers()
- return jsonify({
- 'success': True,
- 'containers': containers
- })
- except Exception as e:
- logger.error(f"Error getting MySQL containers: {e}")
- return jsonify({
- 'success': False,
- 'error': str(e)
- }), 500
-
-@app.route('/files')
-@login_required
-def files():
- """Files management page"""
- nginx_status = check_nginx_status()
- return render_template('files.html', nginx_status=nginx_status)
-
-@app.route('/api/files', methods=['GET'])
-@login_required
-def list_files():
- base_path = request.args.get('path', '/')
- # Normalize path to prevent directory traversal and ensure absolute path inside container
- base_path = os.path.normpath(base_path)
- if base_path.startswith('..') or not base_path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- try:
- # Use docker exec to list files inside the Nginx container with detailed metadata
- quoted = shlex.quote(base_path)
- cmd = [
- 'docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c',
- (
- 'p=' + quoted + ' ; '\
- 'if [ ! -d "$p" ]; then echo "__ERROR__|Not a directory"; exit 2; fi; '\
- 'for f in "$p"/.* "$p"/*; do '\
- ' bn="$(basename "$f")"; '\
- ' [ "$bn" = "." ] && continue; [ "$bn" = ".." ] && continue; '\
- ' [ ! -e "$f" ] && continue; '\
- ' stat -c "%n|%F|%s|%Y|%a|%A|%U|%G" "$f" || true; '\
- 'done'
- )
- ]
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
- if result.returncode != 0:
- err = (result.stderr or result.stdout or '').strip()
- return jsonify({'error': f'Container access failed: {err}'}), 400
-
- files = []
- lines = (result.stdout or '').splitlines()
- for ln in lines:
- ln = ln.strip()
- if not ln:
- continue
- if ln.startswith('__ERROR__|'):
- # Path not a directory
- return jsonify({'error': ln.split('|', 1)[1]}), 400
- parts = ln.split('|')
- if len(parts) != 8:
- continue
- full_path, ftype, size, modified, mode, permissions, owner, group = parts
- name = os.path.basename(full_path)
- if name in ('.', '..'):
- continue
- is_dir = ftype.lower().startswith('directory')
- file_info = {
- 'name': name,
- 'path': os.path.normpath(os.path.join(base_path, name)),
- 'is_dir': is_dir,
- 'size': None if is_dir else int(size),
- 'modified': int(modified),
- 'permissions': permissions,
- 'owner': owner,
- 'group': group,
- 'mode': mode
- }
- files.append(file_info)
- return jsonify(files)
- except Exception as e:
- return jsonify({'error': f'Could not access directory in container: {str(e)}'}), 400
-
-# Backup and restore endpoints removed
-
-@app.route('/api/files/upload', methods=['POST'])
-@login_required
-def upload_file():
- logger.info('File upload attempt')
- from werkzeug.utils import secure_filename
- import tempfile
-
- # Handle both single and multiple file uploads
- files = request.files.getlist('files') if 'files' in request.files else []
- if not files and 'file' in request.files:
- files = [request.files['file']]
-
- if not files:
- return jsonify({'error': 'No files selected'}), 400
-
- upload_path = request.form.get('path', '/')
- upload_path = os.path.normpath(upload_path)
- if upload_path.startswith('..') or not upload_path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- try:
- uploaded_files = []
- failed_files = []
-
- for file in files:
- if file.filename == '':
- continue
-
- try:
- filename = secure_filename(file.filename)
- target_path = os.path.normpath(os.path.join(upload_path, filename))
-
- # Handle duplicate filenames inside container
- counter = 1
- original_filename = filename
- while True:
- q = shlex.quote(target_path)
- check_cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"[ -e {q} ] && echo EXISTS || true"]
- chk = subprocess.run(check_cmd, capture_output=True, text=True)
- if 'EXISTS' in (chk.stdout or ''):
- name, ext = os.path.splitext(original_filename)
- filename = f"{name}_{counter}{ext}"
- target_path = os.path.normpath(os.path.join(upload_path, filename))
- counter += 1
- else:
- break
-
- # Save to temp file in panel container
- temp_dir = tempfile.mkdtemp(prefix='panel_upload_')
- temp_path = os.path.join(temp_dir, filename)
- file.save(temp_path)
-
- # Ensure destination directory exists inside container
- dest_dir = os.path.dirname(target_path) or '/'
- dest_q = shlex.quote(dest_dir)
- mkcmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"mkdir -p {dest_q}"]
- mk = subprocess.run(mkcmd, capture_output=True, text=True)
- if mk.returncode != 0:
- raise Exception((mk.stderr or mk.stdout or 'mkdir failed').strip())
-
- # Copy file into container
- cp_cmd = ['docker', 'cp', temp_path, f"{NGINX_CONTAINER_NAME}:{target_path}"]
- cp = subprocess.run(cp_cmd, capture_output=True, text=True)
-
- # Cleanup temp
- try:
- os.remove(temp_path)
- os.rmdir(temp_dir)
- except Exception:
- pass
-
- if cp.returncode != 0:
- raise Exception((cp.stderr or cp.stdout or 'docker cp failed').strip())
-
- uploaded_files.append({
- 'original_name': file.filename,
- 'saved_name': filename,
- 'path': target_path
- })
- logger.info(f'File uploaded successfully: {target_path}')
-
- except Exception as e:
- failed_files.append({
- 'filename': file.filename,
- 'error': str(e)
- })
- logger.error(f'Failed to upload {file.filename}: {str(e)}')
-
- response_data = {
- 'uploaded_files': uploaded_files,
- 'failed_files': failed_files,
- 'total_uploaded': len(uploaded_files),
- 'total_failed': len(failed_files)
- }
-
- if uploaded_files:
- response_data['message'] = f'Successfully uploaded {len(uploaded_files)} file(s)'
- if failed_files:
- response_data['message'] += f', {len(failed_files)} file(s) failed'
- else:
- response_data['message'] = 'No files were uploaded'
-
- return jsonify(response_data)
-
- except Exception as e:
- logger.error(f'Upload error: {str(e)}')
- return jsonify({'error': f'Could not upload files: {str(e)}'}), 400
-
-@app.route('/api/files/delete', methods=['POST'])
-@login_required
-def delete_file():
- logger.info('File deletion attempt')
- path = request.json.get('path')
- if not path:
- return jsonify({'error': 'No path specified'}), 400
-
- path = os.path.normpath(path)
- if path.startswith('..') or not path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- try:
- q = shlex.quote(path)
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"if [ -e {q} ]; then if [ -d {q} ]; then rm -rf {q}; else rm -f {q}; fi; else echo NOTFOUND; fi"]
- res = subprocess.run(cmd, capture_output=True, text=True)
- out = (res.stdout or '').strip()
- if 'NOTFOUND' in out:
- return jsonify({'error': 'File or directory not found'}), 404
- if res.returncode != 0:
- return jsonify({'error': (res.stderr or res.stdout or 'Delete failed').strip()}), 400
- logger.info(f'Deleted successfully in container: {path}')
- return jsonify({'message': 'File/directory deleted successfully'})
- except Exception as e:
- return jsonify({'error': f'Could not delete: {str(e)}'}), 400
-
-@app.route('/api/files/chmod', methods=['POST'])
-@login_required
-def chmod_file():
- logger.info('File chmod attempt')
- path = request.json.get('path')
- mode = request.json.get('mode')
- if not path or mode is None:
- return jsonify({'error': 'Path and mode must be specified'}), 400
-
- path = os.path.normpath(path)
- if path.startswith('..') or not path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- try:
- # Expect mode as octal string like "755" or integer
- if isinstance(mode, str):
- if not re.fullmatch(r'[0-7]{3,4}', mode):
- return jsonify({'error': 'Invalid mode format'}), 400
- mode_str = mode
- elif isinstance(mode, int):
- mode_str = format(mode, 'o')
- else:
- return jsonify({'error': 'Invalid mode type'}), 400
-
- qpath = shlex.quote(path)
- qmode = shlex.quote(mode_str)
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"chmod {qmode} {qpath} || echo __ERR__"]
- res = subprocess.run(cmd, capture_output=True, text=True)
- if res.returncode != 0 or '__ERR__' in (res.stdout or ''):
- return jsonify({'error': (res.stderr or res.stdout or 'chmod failed').strip()}), 400
- logger.info(f'Permissions changed in container: {path} to {mode_str}')
- return jsonify({'message': 'Permissions changed successfully'})
- except Exception as e:
- return jsonify({'error': f'Could not change permissions: {str(e)}'}), 400
-
-@app.route('/api/files/chown', methods=['POST'])
-@login_required
-def chown_file():
- logger.info('File chown attempt')
- path = request.json.get('path')
- owner = request.json.get('owner')
- group = request.json.get('group')
- if not path:
- return jsonify({'error': 'Path must be specified'}), 400
-
- path = os.path.normpath(path)
- if path.startswith('..') or not path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- try:
- # Build chown target string (owner[:group])
- target = ''
- if owner and group:
- target = f"{owner}:{group}"
- elif owner and not group:
- target = str(owner)
- elif group and not owner:
- target = f":{group}"
- else:
- return jsonify({'error': 'Owner or group must be specified'}), 400
-
- qpath = shlex.quote(path)
- qtarget = shlex.quote(target)
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"chown {qtarget} {qpath} || echo __ERR__"]
- res = subprocess.run(cmd, capture_output=True, text=True)
- if res.returncode != 0 or '__ERR__' in (res.stdout or ''):
- return jsonify({'error': (res.stderr or res.stdout or 'chown failed').strip()}), 400
- logger.info(f'Ownership changed in container: {path} to {target}')
- return jsonify({'message': 'Ownership changed successfully'})
- except Exception as e:
- return jsonify({'error': f'Could not change ownership: {str(e)}'}), 400
-
-@app.route('/api/files/rename', methods=['POST'])
-@login_required
-def rename_file():
- logger.info('File rename attempt')
- old_path = request.json.get('old_path')
- new_path = request.json.get('new_path')
- if not old_path or not new_path:
- return jsonify({'error': 'Both old and new paths must be specified'}), 400
-
- old_path = os.path.normpath(old_path)
- new_path = os.path.normpath(new_path)
- if old_path.startswith('..') or new_path.startswith('..') or not old_path.startswith('/') or not new_path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- try:
- qold = shlex.quote(old_path)
- qnew = shlex.quote(new_path)
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"if [ ! -e {qold} ]; then echo __SRC_NOT_FOUND__; exit 1; fi; if [ -e {qnew} ]; then echo __DST_EXISTS__; exit 1; fi; mv {qold} {qnew} || echo __ERR__"]
- res = subprocess.run(cmd, capture_output=True, text=True)
- out = (res.stdout or '')
- if '__SRC_NOT_FOUND__' in out:
- return jsonify({'error': 'Source file or directory not found'}), 404
- if '__DST_EXISTS__' in out:
- return jsonify({'error': 'Destination already exists'}), 400
- if res.returncode != 0 or '__ERR__' in out:
- return jsonify({'error': (res.stderr or res.stdout or 'rename failed').strip()}), 400
- logger.info(f'File/directory renamed in container from {old_path} to {new_path}')
- return jsonify({'message': 'File/directory renamed successfully'})
- except Exception as e:
- return jsonify({'error': f'Could not rename: {str(e)}'}), 400
-
-@app.route('/api/files/download', methods=['GET'])
-@login_required
-def download_file():
- logger.info('File download attempt')
- path = request.args.get('path')
- if not path:
- return jsonify({'error': 'No path specified'}), 400
-
- path = os.path.normpath(path)
- if path.startswith('..') or not path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- try:
- q = shlex.quote(path)
- # Ensure it is a regular file inside container
- check_cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"[ -f {q} ] || echo __NOT_FILE__"]
- chk = subprocess.run(check_cmd, capture_output=True, text=True)
- if '__NOT_FILE__' in (chk.stdout or ''):
- return jsonify({'error': 'File not found or is a directory'}), 404
- if chk.returncode != 0:
- return jsonify({'error': (chk.stderr or chk.stdout or 'Check failed').strip()}), 400
-
- # Stream file content via docker exec cat
- def generate():
- cat_cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"cat {q}"]
- proc = subprocess.Popen(cat_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- try:
- while True:
- chunk = proc.stdout.read(8192)
- if not chunk:
- break
- yield chunk
- finally:
- proc.stdout.close()
- proc.wait()
-
- basename = os.path.basename(path)
- headers = {
- 'Content-Type': 'application/octet-stream',
- 'Content-Disposition': f'attachment; filename="{basename}"'
- }
- logger.info(f'File downloaded from container: {path}')
- return Response(generate(), headers=headers)
- except Exception as e:
- return jsonify({'error': f'Could not download file: {str(e)}'}), 400
-
-@app.route('/api/files/compress', methods=['POST'])
-@login_required
-def compress_files():
- logger.info('File compression attempt')
- data = request.json
- paths = data.get('paths', [])
- archive_name = data.get('archive_name', 'archive.zip')
-
- if not paths:
- return jsonify({'error': 'No files specified'}), 400
-
- # Validate paths inside container
- safe_paths = []
- for p in paths:
- p = os.path.normpath(p)
- if p.startswith('..') or not p.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
- safe_paths.append(p)
-
- try:
- # Determine archive directory (use dirname of first path)
- first_path = os.path.normpath(safe_paths[0])
- archive_dir = os.path.dirname(first_path) or '/'
-
- # Ensure archive name ends with .zip
- if not archive_name.endswith('.zip'):
- archive_name += '.zip'
-
- # Build zip command inside container
- qdir = shlex.quote(archive_dir)
- qarchive = shlex.quote(os.path.join(archive_dir, archive_name))
- qfiles = ' '.join(shlex.quote(p) for p in safe_paths)
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"cd {qdir} && zip -r {qarchive} {qfiles} || echo __ERR__"]
- res = subprocess.run(cmd, capture_output=True, text=True)
- if res.returncode != 0 or '__ERR__' in (res.stdout or ''):
- return jsonify({'error': (res.stderr or res.stdout or 'zip failed').strip()}), 400
-
- logger.info(f'Files compressed in container to: {os.path.join(archive_dir, archive_name)}')
- return jsonify({'message': 'Files compressed successfully', 'archive_path': os.path.join(archive_dir, archive_name)})
- except Exception as e:
- return jsonify({'error': f'Could not compress files: {str(e)}'}), 400
-
-@app.route('/api/files/uncompress', methods=['POST'])
-@login_required
-def uncompress_file():
- logger.info('File uncompression attempt')
- data = request.json
- archive_path = data.get('path')
- extract_to = data.get('extract_to')
-
- if not archive_path:
- return jsonify({'error': 'No archive path specified'}), 400
-
- archive_path = os.path.normpath(archive_path)
- if archive_path.startswith('..') or not archive_path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- # If extract_to is not specified, extract to the same directory as the archive
- if not extract_to:
- extract_to = os.path.dirname(archive_path)
- else:
- extract_to = os.path.normpath(extract_to)
- if extract_to.startswith('..') or not extract_to.startswith('/'):
- return jsonify({'error': 'Invalid extraction path'}), 400
-
- try:
- qarchive = shlex.quote(archive_path)
- qextract = shlex.quote(extract_to)
- # Determine archive type and extract via container tools
- if archive_path.endswith('.zip'):
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"unzip -o {qarchive} -d {qextract} || echo __ERR__"]
- elif archive_path.endswith(('.tar', '.tar.gz', '.tgz', '.tar.bz2')):
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"mkdir -p {qextract} && tar -xf {qarchive} -C {qextract} || echo __ERR__"]
- else:
- return jsonify({'error': 'Unsupported archive format. Supported: .zip, .tar, .tar.gz, .tgz, .tar.bz2'}), 400
-
- res = subprocess.run(cmd, capture_output=True, text=True)
- if res.returncode != 0 or '__ERR__' in (res.stdout or ''):
- return jsonify({'error': (res.stderr or res.stdout or 'extract failed').strip()}), 400
-
- logger.info(f'Archive extracted in container: {archive_path} to {extract_to}')
- return jsonify({'message': 'Archive extracted successfully', 'extract_path': extract_to})
- except Exception as e:
- return jsonify({'error': f'Could not extract archive: {str(e)}'}), 400
-
-@app.route('/api/files/create-directory', methods=['POST'])
-@login_required
-def create_directory():
- logger.info('Directory creation attempt')
- path = request.json.get('path')
- if not path:
- return jsonify({'error': 'No path specified'}), 400
-
- path = os.path.normpath(path)
- if path.startswith('..') or not path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- try:
- q = shlex.quote(path)
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"mkdir -p {q} || echo __ERR__"]
- res = subprocess.run(cmd, capture_output=True, text=True)
- if res.returncode != 0 or '__ERR__' in (res.stdout or ''):
- return jsonify({'error': (res.stderr or res.stdout or 'mkdir failed').strip()}), 400
- logger.info(f'Directory created successfully in container: {path}')
- return jsonify({'message': 'Directory created successfully'})
- except Exception as e:
- return jsonify({'error': f'Could not create directory: {str(e)}'}), 400
-
-@app.route('/api/files/read', methods=['POST'])
-@login_required
-def read_file():
- path = request.json.get('path')
- if not path:
- return jsonify({'error': 'No path specified'}), 400
-
- path = os.path.normpath(path)
- if path.startswith('..') or not path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- try:
- q = shlex.quote(path)
- # Ensure file and not directory
- check_cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"[ -f {q} ] || echo __NOT_FILE__"]
- chk = subprocess.run(check_cmd, capture_output=True, text=True)
- if '__NOT_FILE__' in (chk.stdout or ''):
- return jsonify({'error': 'File not found or is a directory'}), 404
- if chk.returncode != 0:
- return jsonify({'error': (chk.stderr or chk.stdout or 'Check failed').strip()}), 400
-
- # Read content via docker exec cat
- cat_cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"cat {q}"]
- res = subprocess.run(cat_cmd, capture_output=True, text=True)
- if res.returncode != 0:
- return jsonify({'error': (res.stderr or res.stdout or 'Read failed').strip()}), 400
- content = res.stdout
- logger.info(f'File read from container: {path}, content length: {len(content)}')
- return jsonify({
- 'content': content,
- 'path': path
- })
- except Exception as e:
- logger.error(f'Error reading file {path}: {str(e)}')
- return jsonify({'error': f'Could not read file: {str(e)}'}), 400
-
-@app.route('/api/files/new', methods=['POST'])
-@login_required
-def create_new_file():
- try:
- data = request.json
- file_path = data.get('path')
- file_name = data.get('name')
-
- if not file_path or not file_name:
- return jsonify({'error': 'Path and filename are required'}), 400
-
- # Normalize path to prevent directory traversal
- full_path = os.path.normpath(os.path.join(file_path, file_name))
- if full_path.startswith('..') or not full_path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- # Create empty file inside container if not exists
- qpath = shlex.quote(full_path)
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"[ -e {qpath} ] && echo __EXISTS__ || (mkdir -p $(dirname {qpath}) && touch {qpath}) || echo __ERR__"]
- res = subprocess.run(cmd, capture_output=True, text=True)
- out = (res.stdout or '')
- if '__EXISTS__' in out:
- return jsonify({'error': 'File already exists'}), 400
- if res.returncode != 0 or '__ERR__' in out:
- return jsonify({'error': (res.stderr or res.stdout or 'create failed').strip()}), 400
-
- logger.info(f'New file created in container: {full_path}')
- return jsonify({'message': 'File created successfully'})
- except Exception as e:
- logger.error(f'Error creating new file: {str(e)}')
- return jsonify({'error': str(e)}), 500
-
-@app.route('/api/files/save', methods=['POST'])
-@login_required
-def save_file():
- path = request.json.get('path')
- content = request.json.get('content')
- if not path or content is None:
- return jsonify({'error': 'Both path and content must be specified'}), 400
-
- path = os.path.normpath(path)
- if path.startswith('..') or not path.startswith('/'):
- return jsonify({'error': 'Invalid path'}), 400
-
- try:
- qpath = shlex.quote(path)
- # Ensure file exists and is not a directory
- check_cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"[ -f {qpath} ] || echo __NOT_FILE__"]
- chk = subprocess.run(check_cmd, capture_output=True, text=True)
- if '__NOT_FILE__' in (chk.stdout or ''):
- return jsonify({'error': 'File not found or is a directory'}), 404
- if chk.returncode != 0:
- return jsonify({'error': (chk.stderr or chk.stdout or 'Check failed').strip()}), 400
-
- # Write content using docker exec and tee (safe for most text)
- # We will base64-encode content to avoid shell quoting issues
- import base64
- b64 = base64.b64encode(content.encode('utf-8')).decode('ascii')
- qb64 = shlex.quote(b64)
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"echo {qb64} | base64 -d > {qpath} || echo __ERR__"]
- res = subprocess.run(cmd, capture_output=True, text=True)
- if res.returncode != 0 or '__ERR__' in (res.stdout or ''):
- return jsonify({'error': (res.stderr or res.stdout or 'write failed').strip()}), 400
- return jsonify({'message': 'File saved successfully'})
- except Exception as e:
- return jsonify({'error': f'Could not save file: {str(e)}'}), 400
-
-@app.route('/api/container/start-log/')
-@login_required
-def api_container_start_log(container_name):
- """API endpoint to stream start logs for containers (PHP/MySQL) via compose."""
- # Resolve compose service name from container name
- service_name, err = resolve_service_from_container_name(container_name)
- if not service_name:
- return Response(f"data: Invalid container: {err or 'unknown'}\n\n", mimetype='text/event-stream')
- if not re.match(r'^[a-zA-Z0-9_-]+$', service_name):
- return Response("data: Invalid service name\n\n", mimetype='text/event-stream')
- if service_name == 'panel':
- return Response("data: Cannot control panel container\n\n", mimetype='text/event-stream')
-
- project = get_compose_project()
- base = get_compose_cmd_base()
- resolved_file = get_resolved_compose_file()
- cmd = base + ['-f', resolved_file] + (['--project-name', project] if project else []) + ['up', '-d', '--no-deps', service_name]
-
- log_dir = '/app/tmp'
- try:
- os.makedirs(log_dir, exist_ok=True)
- except Exception:
- pass
- log_path = os.path.join(log_dir, f"start_{service_name}_{int(time.time())}.log")
-
- def generate():
- yield f"data: Running: {' '.join(cmd)}\n\n"
- try:
- with open(log_path, 'w') as lf:
- lf.write(f"Running: {' '.join(cmd)}\n")
- env = os.environ.copy()
- proj_dir = get_project_dir()
- if proj_dir:
- env['HOST_PROJECT_DIR'] = proj_dir
- proc = subprocess.Popen(
- cmd,
- cwd='/workspace/docker',
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- bufsize=1
- )
- for line in proc.stdout:
- if line:
- lf.write(line)
- lf.flush()
- yield f"data: {line.strip()}\n\n"
- rc = proc.wait()
- lf.write(f"\nStart finished with code {rc}\n")
- yield "event: done\n"
- yield f"data: {'success' if rc == 0 else 'error'}\n\n"
- except Exception as e:
- try:
- with open(log_path, 'a') as lf:
- lf.write(f"\nStart error: {str(e)}\n")
- except Exception:
- pass
- yield f"data: Start error: {str(e)}\n\n"
- yield "event: done\n"
-
- return Response(generate(), mimetype='text/event-stream')
-
-# Helper to resolve compose service name from a container name
-def resolve_service_from_container_name(container_name):
- try:
- res = subprocess.run(
- ['docker', 'inspect', '-f', '{{ index .Config.Labels "com.docker.compose.service" }}|{{ index .Config.Labels "com.docker.compose.project" }}', container_name],
- capture_output=True, text=True, timeout=5
- )
- if res.returncode != 0:
- return None, "Container not found"
- out = res.stdout.strip()
- parts = out.split('|') if out else []
- service = parts[0] if len(parts) > 0 else ''
- project = parts[1] if len(parts) > 1 else ''
- expected_project = get_compose_project()
- if expected_project and project and project != expected_project:
- return None, "Container belongs to different project"
- if not service:
- if container_name.startswith('mlite_'):
- return container_name.replace('mlite_', ''), None
- return None, "Service label missing"
- return service, None
- except Exception as e:
- return None, str(e)
-
-@app.route('/api/container//', methods=['POST'])
-@login_required
-def api_container_control(container_name, action):
- """API endpoint to control containers (PHP/MySQL)"""
- if action not in ['start', 'stop', 'restart']:
- return jsonify({
- 'success': False,
- 'error': 'Invalid action'
- }), 400
-
- service_name, err = resolve_service_from_container_name(container_name)
- if not service_name:
- return jsonify({
- 'success': False,
- 'error': f'Invalid container: {err or "unknown"}'
- }), 400
-
- if service_name == 'panel':
- return jsonify({
- 'success': False,
- 'error': 'Cannot control panel container'
- }), 400
-
- if action == 'start':
- success, message = start_container(service_name)
- elif action == 'stop':
- success, message = stop_container(service_name)
- else:
- success, message = restart_container(service_name)
-
- if success:
- return jsonify({
- 'success': True,
- 'message': message
- })
- else:
- return jsonify({
- 'success': False,
- 'error': message
- }), 500
-
-@app.route('/api/mysql-info')
-@login_required
-def mysql_info():
- try:
- connection = create_mysql_connection()
- connection.autocommit = True
- connection.cmd_query('USE mysql')
- cursor = connection.cursor()
- cursor.execute('SHOW VARIABLES LIKE "%version%"')
- version = cursor.fetchall()
- cursor.execute('SHOW STATUS')
- status = cursor.fetchall()
- cursor.close()
- connection.close()
- # Safely convert list-of-lists to dict, only using pairs
- def pairs_to_dict(rows):
- out = {}
- for r in rows:
- try:
- if isinstance(r, (list, tuple)) and len(r) >= 2:
- out[str(r[0])] = r[1]
- except Exception:
- continue
- return out
- return jsonify({
- 'version': pairs_to_dict(version),
- 'status': pairs_to_dict(status)
- })
- except Exception as e:
- logger.error(f'MySQL info error: {e}')
- return jsonify({'error': str(e)}), 500
-
-# MySQL Database Management
-@app.route('/api/mysql/execute-query', methods=['POST'])
-@login_required
-def execute_query():
- logger.info('Database query execution requested')
- try:
- data = request.get_json()
- database = data.get('database')
- query = data.get('query')
-
- if not database or not query:
- return jsonify({'error': 'Database and query are required'}), 400
-
- connection = create_mysql_connection()
- connection.cmd_query(f'USE `{database}`')
-
- cursor = connection.cursor(dictionary=True)
- cursor.execute(query)
-
- if query.strip().upper().startswith(('SELECT', 'SHOW', 'DESCRIBE', 'EXPLAIN')):
- result = cursor.fetchall()
- else:
- connection.commit()
- result = [{'affected_rows': cursor.rowcount}]
-
- cursor.close()
- connection.close()
-
- return jsonify(result)
-
- except Exception as err:
- logger.error(f'MySQL error executing query: {err}')
- return jsonify({'error': str(err)}), 500
- except Exception as e:
- logger.error(f'Error executing query: {e}')
- return jsonify({'error': 'Internal server error'}), 500
-
-@app.route('/api/mysql/tables')
-@login_required
-def list_tables():
- logger.info('Table list requested')
- connection = None
- cursor = None
- try:
- db_name = request.args.get('database')
- if not db_name:
- logger.warning('Database name not provided')
- return jsonify({'error': 'Database name is required'}), 400
- # Validate database name (prevent SQL injection)
- if not re.match(r'^[a-zA-Z0-9_]+$', db_name):
- logger.warning(f'Invalid database name format: {db_name}')
- return jsonify({'error': 'Invalid database name format'}), 400
- try:
- connection = create_mysql_connection()
- connection.cmd_query(f'USE `{db_name}`')
- cursor = connection.cursor(dictionary=True)
- # Test connection
- cursor.execute('SELECT 1')
- cursor.fetchone()
- except Exception as e:
- logger.error(f'Database connection error: {str(e)}')
- return jsonify({'error': 'Database connection error'}), 500
- try:
- # Get list of tables with information
- cursor.execute("""
- SELECT
- table_name as name,
- `table_rows` as `rows`,
- ROUND((data_length + index_length) / 1024 / 1024, 2) as size
- FROM information_schema.tables
- WHERE table_schema = %s
- AND table_type = 'BASE TABLE'
- """, (db_name,))
- table_list = cursor.fetchall()
- # Handle null values
- for table in table_list:
- table['rows'] = table['rows'] if table['rows'] is not None else 0
- table['size'] = table['size'] if table['size'] is not None else 0
- except Exception as e:
- logger.error(f'Error listing tables: {str(e)}')
- raise
- return jsonify(table_list)
- except Exception as e:
- errno = getattr(e, 'errno', None)
- msg = getattr(e, 'msg', str(e))
- error_msg = f'MySQL Error ({errno}): {msg}' if errno is not None else f'Unexpected error: {str(e)}'
- logger.error(f'Error listing tables: {error_msg}')
- return jsonify({'error': error_msg}), 500
- except Exception as e:
- error_msg = f'Unexpected error: {str(e)}'
- logger.error(f'Error listing tables: {error_msg}')
- return jsonify({'error': error_msg}), 500
- finally:
- if cursor:
- try:
- cursor.close()
- except Exception as e:
- logger.error(f'Error closing cursor: {str(e)}')
- if connection:
- try:
- connection.close()
- except Exception as e:
- logger.error(f'Error closing connection: {str(e)}')
-
-@app.route('/api/mysql/databases')
-@login_required
-def list_databases():
- logger.info('Database list requested')
- try:
- connection = create_mysql_connection()
- cursor = connection.cursor()
- # Get list of databases
- cursor.execute('SHOW DATABASES')
- databases = cursor.fetchall()
- # Get size and tables count of each database
- db_list = []
- for row in databases:
- # Support both list-of-lists and list-of-tuples
- db_name = row[0] if isinstance(row, (list, tuple)) and row else row
- if db_name not in ['information_schema', 'performance_schema', 'mysql', 'sys']:
- cursor.execute(f"""
- SELECT
- ROUND(SUM(data_length + index_length) / 1024 / 1024, 2)
- FROM information_schema.tables
- WHERE table_schema = '{db_name}'
- GROUP BY table_schema;
- """)
- size_row = cursor.fetchone()
- size_val = None
- if isinstance(size_row, dict):
- # dictionary cursor case
- size_val = list(size_row.values())[0] if size_row else None
- elif isinstance(size_row, (list, tuple)):
- size_val = size_row[0] if size_row else None
- else:
- size_val = size_row
- # Tables count
- cursor.execute(f"""
- SELECT COUNT(*)
- FROM information_schema.tables
- WHERE table_schema = '{db_name}';
- """)
- cnt_row = cursor.fetchone()
- if isinstance(cnt_row, dict):
- tables_cnt = list(cnt_row.values())[0] if cnt_row else 0
- elif isinstance(cnt_row, (list, tuple)):
- tables_cnt = cnt_row[0] if cnt_row else 0
- else:
- tables_cnt = cnt_row or 0
- db_list.append({
- 'name': db_name,
- 'size': float(size_val) if size_val is not None else 0,
- 'tables': int(tables_cnt) if isinstance(tables_cnt, (int, float)) else int(tables_cnt or 0)
- })
- cursor.close()
- connection.close()
- return jsonify(db_list)
- except Exception as e:
- logger.error(f'List databases error: {e}')
- return jsonify({'error': str(e)}), 500
-
-@app.route('/api/mysql/create-db', methods=['POST'])
-@login_required
-def create_database():
- logger.info('Database creation attempt')
- try:
- data = request.json
- db_name = data.get('name')
- db_user = data.get('user')
- db_password = data.get('password')
-
- if not db_name:
- logger.warning('Database name not provided')
- return jsonify({'error': 'Nama database diperlukan'}), 400
-
- # Validate database name (prevent SQL injection)
- if not re.match(r'^[a-zA-Z0-9_]+$', db_name):
- logger.warning(f'Invalid database name format: {db_name}')
- return jsonify({'error': 'Format nama database tidak valid. Gunakan hanya huruf, angka, dan underscore'}), 400
-
- # Validate username if provided
- if db_user and not re.match(r'^[a-zA-Z0-9_]+$', db_user):
- logger.warning(f'Invalid username format: {db_user}')
- return jsonify({'error': 'Format username tidak valid. Gunakan hanya huruf, angka, dan underscore'}), 400
-
- connection = create_mysql_connection()
- cursor = connection.cursor()
-
- # Create database
- cursor.execute(f'CREATE DATABASE `{db_name}`')
-
- # Create user and grant privileges if username and password provided
- if db_user and db_password:
- try:
- # Check if user already exists
- cursor.execute("SELECT User FROM mysql.user WHERE User = %s AND Host = 'localhost'", (db_user,))
- user_exists = cursor.fetchone()
-
- if user_exists:
- # User exists, just grant privileges
- cursor.execute(f"GRANT ALL PRIVILEGES ON `{db_name}`.* TO '{db_user}'@'localhost'")
- cursor.execute("FLUSH PRIVILEGES")
- logger.info(f'Granted privileges on {db_name} to existing user {db_user}')
- else:
- # Create new user
- cursor.execute(f"CREATE USER '{db_user}'@'localhost' IDENTIFIED BY %s", (db_password,))
- # Grant all privileges on the database to the user
- cursor.execute(f"GRANT ALL PRIVILEGES ON `{db_name}`.* TO '{db_user}'@'localhost'")
- cursor.execute("FLUSH PRIVILEGES")
- logger.info(f'Created new user {db_user} and granted privileges on {db_name}')
- except Exception as user_err:
- logger.warning(f'Error handling user {db_user}: {user_err}')
- # Continue without failing the database creation
- pass
-
- # Check if user exists before closing connection
- user_created_or_exists = False
- if db_user:
- try:
- cursor.execute("SELECT User FROM mysql.user WHERE User = %s AND Host = 'localhost'", (db_user,))
- user_created_or_exists = cursor.fetchone() is not None
- except Exception:
- user_created_or_exists = False
-
- connection.commit()
- cursor.close()
- connection.close()
-
- if db_user:
- if user_created_or_exists:
- logger.info(f'Database {db_name} created and privileges granted to user {db_user}')
- return jsonify({'message': f'Database "{db_name}" berhasil dibuat dan privileges diberikan ke user "{db_user}"'})
- else:
- logger.info(f'Database {db_name} created successfully')
- return jsonify({'message': f'Database "{db_name}" berhasil dibuat (user gagal dibuat)'})
- else:
- logger.info(f'Database {db_name} created successfully')
- return jsonify({'message': f'Database "{db_name}" berhasil dibuat'})
- except Exception as err:
- logger.error(f'MySQL error during database creation: {err}')
- errno = getattr(err, 'errno', None)
- if errno == 1007: # Database already exists
- return jsonify({'error': f'Database "{db_name}" sudah ada'}), 400
- elif errno == 1045: # Access denied
- return jsonify({'error': 'Akses ditolak. Periksa konfigurasi MySQL'}), 500
- else:
- return jsonify({'error': f'Error MySQL: {str(err)}'}), 500
- except Exception as e:
- logger.error(f'Unexpected error during database creation: {e}')
- return jsonify({'error': f'Terjadi kesalahan: {str(e)}'}), 500
-
-@app.route('/api/mysql/delete-db', methods=['POST'])
-@login_required
-def delete_database():
- logger.info('Database deletion attempt')
- try:
- db_name = request.json.get('name')
- if not db_name:
- logger.warning('Database name not provided for deletion')
- return jsonify({'error': 'Nama database diperlukan'}), 400
-
- # Validate database name (prevent SQL injection)
- if not re.match(r'^[a-zA-Z0-9_]+$', db_name):
- logger.warning(f'Invalid database name format for deletion: {db_name}')
- return jsonify({'error': 'Format nama database tidak valid'}), 400
-
- connection = create_mysql_connection()
- cursor = connection.cursor()
-
- # Drop database
- cursor.execute(f'DROP DATABASE `{db_name}`')
- connection.commit()
- cursor.close()
- connection.close()
-
- logger.info(f'Database {db_name} deleted successfully')
- return jsonify({'message': f'Database "{db_name}" berhasil dihapus'})
- except Exception as err:
- logger.error(f'MySQL error during database deletion: {err}')
- errno = getattr(err, 'errno', None)
- if errno == 1008: # Database doesn't exist
- return jsonify({'error': f'Database "{db_name}" tidak ditemukan'}), 400
- elif errno == 1045: # Access denied
- return jsonify({'error': 'Akses ditolak. Periksa konfigurasi MySQL'}), 500
- else:
- return jsonify({'error': f'Error MySQL: {str(err)}'}), 500
- except Exception as e:
- logger.error(f'Unexpected error during database deletion: {e}')
- return jsonify({'error': f'Terjadi kesalahan: {str(e)}'}), 500
-
-@app.route('/api/mysql/delete-table', methods=['POST'])
-@login_required
-def delete_table():
- logger.info('Table deletion requested')
- try:
- data = request.get_json()
- db_name = data.get('database')
- table_name = data.get('table')
-
- if not db_name or not table_name:
- logger.warning('Database name or table name not provided')
- return jsonify({'error': 'Nama database dan tabel diperlukan'}), 400
-
- # Validate database and table names (prevent SQL injection)
- if not re.match(r'^[a-zA-Z0-9_]+$', db_name):
- logger.warning(f'Invalid database name format for table deletion: {db_name}')
- return jsonify({'error': 'Format nama database tidak valid'}), 400
-
- if not re.match(r'^[a-zA-Z0-9_]+$', table_name):
- logger.warning(f'Invalid table name format for deletion: {table_name}')
- return jsonify({'error': 'Format nama tabel tidak valid'}), 400
-
- connection = create_mysql_connection()
- cursor = connection.cursor()
-
- # Use the specified database
- cursor.execute(f'USE `{db_name}`')
-
- # Drop table
- cursor.execute(f'DROP TABLE `{table_name}`')
- connection.commit()
- cursor.close()
- connection.close()
-
- logger.info(f'Table {table_name} deleted successfully from database {db_name}')
- return jsonify({'message': f'Tabel "{table_name}" berhasil dihapus dari database "{db_name}"'})
- except Exception as err:
- logger.error(f'MySQL error during table deletion: {err}')
- errno = getattr(err, 'errno', None)
- if errno == 1051: # Table doesn't exist
- return jsonify({'error': f'Tabel "{table_name}" tidak ditemukan dalam database "{db_name}"'}), 400
- elif errno == 1049: # Database doesn't exist
- return jsonify({'error': f'Database "{db_name}" tidak ditemukan'}), 400
- elif errno == 1045: # Access denied
- return jsonify({'error': 'Akses ditolak. Periksa konfigurasi MySQL'}), 500
- else:
- return jsonify({'error': f'Error MySQL: {str(err)}'}), 500
- except Exception as e:
- logger.error(f'Unexpected error during table deletion: {e}')
- return jsonify({'error': f'Terjadi kesalahan: {str(e)}'}), 500
-
-@app.route('/api/mysql/get-root-password', methods=['GET'])
-@login_required
-def get_root_password():
- """Return current root password from db_config (do not query MySQL)."""
- try:
- pwd = db_config.get('password', '')
- return jsonify({'password': pwd or ''})
- except Exception as e:
- logger.error(f'Error getting root password: {e}')
- return jsonify({'error': 'Failed to get root password'}), 500
-
-@app.route('/api/mysql/set-root-password', methods=['POST'])
-@login_required
-def set_root_password():
- logger.info('Root password change requested')
- try:
- data = request.get_json()
- new_password = data.get('password', '')
-
- # Connect to MySQL as root with current password from db_config
- connection = create_mysql_connection()
- cursor = connection.cursor()
-
- # Set new password for root user (localhost and %)
- if new_password:
- cursor.execute("ALTER USER 'root'@'localhost' IDENTIFIED BY %s", (new_password,))
- try:
- cursor.execute("ALTER USER IF EXISTS 'root'@'%' IDENTIFIED BY %s", (new_password,))
- except Exception as e:
- logger.warning(f"Failed to update password for root@%: {e}")
- else:
- cursor.execute("ALTER USER 'root'@'localhost' IDENTIFIED BY ''")
- try:
- cursor.execute("ALTER USER IF EXISTS 'root'@'%' IDENTIFIED BY ''")
- except Exception as e:
- logger.warning(f"Failed to update empty password for root@%: {e}")
-
- connection.commit()
- cursor.close()
- connection.close()
-
- # Update the global db_config with new password
- global db_config
- old_password = db_config['password']
- db_config['password'] = new_password
- logger.info(f'Password updated in db_config: from "{old_password}" to "{new_password}"')
- logger.info(f'Current db_config password: {db_config["password"]}')
-
- # Save configuration to JSON file for persistence
- if save_mysql_config(db_config):
- logger.info('Password configuration saved to file successfully')
- else:
- logger.warning('Failed to save password configuration to file')
-
- # Reinitialize connection pool with new password
- global connection_pool
- try:
- if connection_pool:
- connection_pool._remove_connections()
- connection_pool = mysql.connector.pooling.MySQLConnectionPool(**db_config)
- logger.info('Connection pool reinitialized with new password')
- except Exception as e:
- logger.warning(f'Failed to reinitialize connection pool: {str(e)}')
- connection_pool = None
-
- # Restart SLEMP to reload database configuration
- try:
- logger.info('Restarting SLEMP to reload database configuration')
- restart_result = subprocess.run(['supervisorctl', 'restart', 'slemp'], capture_output=True, text=True, timeout=30)
- if restart_result.returncode == 0:
- logger.info('SLEMP restarted successfully')
- else:
- logger.warning(f'Failed to restart SLEMP: {restart_result.stderr}')
- except Exception as e:
- logger.warning(f'Error restarting SLEMP: {str(e)}')
-
- logger.info('Root password changed successfully')
- return jsonify({'message': 'Password root MySQL berhasil diubah'})
-
- except Exception as err:
- logger.error(f'MySQL error during password change: {err}')
- errno = getattr(err, 'errno', None)
- if errno == 1045: # Access denied
- return jsonify({'error': 'Akses ditolak. Password root saat ini mungkin sudah berubah'}), 500
- else:
- return jsonify({'error': f'Error MySQL: {str(err)}'}), 500
- except Exception as e:
- logger.error(f'Unexpected error during password change: {e}')
- return jsonify({'error': f'Terjadi kesalahan: {str(e)}'}), 500
-
-@app.route('/api/mysql/users', methods=['GET'])
-@login_required
-def get_mysql_users():
- """Get list of MySQL users"""
- try:
- connection = create_mysql_connection()
- cursor = connection.cursor(dictionary=True)
-
- # Get all users (excluding system and root accounts)
- logger.info('Filtering out system MySQL users from display: root, mysql, mysql.sys, mysql.session, mysql.infoschema')
- cursor.execute("SELECT User, Host FROM mysql.user WHERE User != '' AND User NOT IN ('root','mysql','mysql.sys','mysql.session','mysql.infoschema') ORDER BY User, Host")
- users = cursor.fetchall()
-
- # Get privileges for each user
- for user in users:
- try:
- cursor.execute(f"SHOW GRANTS FOR '{user['User']}'@'{user['Host']}'")
- grants = cursor.fetchall()
- privileges = []
- for grant in grants:
- grant_text = list(grant.values())[0]
- if 'ALL PRIVILEGES' in grant_text:
- privileges.append('ALL PRIVILEGES')
- elif 'GRANT' in grant_text:
- # Extract specific privileges
- start = grant_text.find('GRANT ') + 6
- end = grant_text.find(' ON')
- if start < end:
- privileges.append(grant_text[start:end])
- user['privileges'] = ', '.join(privileges) if privileges else 'No privileges'
- except Exception:
- user['privileges'] = 'Unable to determine'
-
- cursor.close()
- connection.close()
-
- return jsonify({'users': users})
-
- except Exception as err:
- logger.error(f'MySQL error getting users: {err}')
- return jsonify({'error': f'Error MySQL: {str(err)}'}), 500
- except Exception as e:
- logger.error(f'Error getting MySQL users: {str(e)}')
- return jsonify({'error': f'Terjadi kesalahan: {str(e)}'}), 500
-
-@app.route('/api/mysql/create-user', methods=['POST'])
-@login_required
-def create_mysql_user():
- """Create a new MySQL user"""
- try:
- data = request.get_json()
- username = data.get('username')
- password = data.get('password')
- host = data.get('host', '%')
- privileges = data.get('privileges', 'ALL')
-
- if not username or not password:
- return jsonify({'error': 'Username dan password harus diisi'}), 400
-
- connection = create_mysql_connection()
- cursor = connection.cursor()
-
- # Create user
- cursor.execute(f"CREATE USER '{username}'@'{host}' IDENTIFIED BY %s", (password,))
-
- # Grant privileges
- if privileges == 'ALL':
- cursor.execute(f"GRANT ALL PRIVILEGES ON *.* TO '{username}'@'{host}'")
- else:
- cursor.execute(f"GRANT {privileges} ON *.* TO '{username}'@'{host}'")
-
- # Flush privileges
- cursor.execute("FLUSH PRIVILEGES")
-
- connection.commit()
- cursor.close()
- connection.close()
-
- logger.info(f'MySQL user created: {username}@{host}')
- return jsonify({'message': f'User {username}@{host} berhasil dibuat'})
-
- except Exception as err:
- logger.error(f'MySQL error creating user: {err}')
- errno = getattr(err, 'errno', None)
- if errno == 1396: # User already exists
- return jsonify({'error': f'User {username}@{host} sudah ada'}), 400
- else:
- return jsonify({'error': f'Error MySQL: {str(err)}'}), 500
- except Exception as e:
- logger.error(f'Error creating MySQL user: {str(e)}')
- return jsonify({'error': f'Terjadi kesalahan: {str(e)}'}), 500
-
-@app.route('/api/mysql/delete-user', methods=['DELETE'])
-@login_required
-def delete_mysql_user():
- """Delete a MySQL user"""
- try:
- data = request.get_json()
- username = data.get('username')
- host = data.get('host')
-
- if not username or not host:
- return jsonify({'error': 'Username dan host harus diisi'}), 400
-
- # Prevent deletion of root user
- if username == 'root':
- return jsonify({'error': 'User root tidak dapat dihapus'}), 400
-
- connection = create_mysql_connection()
- cursor = connection.cursor()
-
- # Drop user
- cursor.execute(f"DROP USER '{username}'@'{host}'")
-
- # Flush privileges
- cursor.execute("FLUSH PRIVILEGES")
-
- connection.commit()
- cursor.close()
- connection.close()
-
- logger.info(f'MySQL user deleted: {username}@{host}')
- return jsonify({'message': f'User {username}@{host} berhasil dihapus'})
-
- except Exception as err:
- logger.error(f'MySQL error deleting user: {err}')
- errno = getattr(err, 'errno', None)
- if errno == 1396: # User doesn't exist
- return jsonify({'error': f'User {username}@{host} tidak ditemukan'}), 404
- else:
- return jsonify({'error': f'Error MySQL: {str(err)}'}), 500
- except Exception as e:
- logger.error(f'Error deleting MySQL user: {str(e)}')
- return jsonify({'error': f'Terjadi kesalahan: {str(e)}'}), 500
-
-@app.route('/api/mysql/update-user', methods=['PUT'])
-@login_required
-def update_mysql_user():
- """Update a MySQL user's password, privileges, and host"""
- try:
- data = request.get_json()
- username = data.get('username')
- host = data.get('host')
- new_host = data.get('new_host', host) # Use original host if new_host not provided
- password = data.get('password')
- privileges = data.get('privileges', 'ALL PRIVILEGES')
-
- if not username or not host or not password or not new_host:
- return jsonify({'error': 'Username, host, new_host, dan password harus diisi'}), 400
-
- connection = create_mysql_connection()
- cursor = connection.cursor()
-
- # If host is changing, we need to create a new user and drop the old one
- if host != new_host:
- # Create new user with new host
- cursor.execute(f"CREATE USER '{username}'@'{new_host}' IDENTIFIED BY %s", (password,))
-
- # Grant privileges to new user
- if privileges == 'ALL PRIVILEGES':
- cursor.execute(f"GRANT ALL PRIVILEGES ON *.* TO '{username}'@'{new_host}'")
- else:
- cursor.execute(f"GRANT {privileges} ON *.* TO '{username}'@'{new_host}'")
-
- # Drop old user
- try:
- cursor.execute(f"DROP USER '{username}'@'{host}'")
- except Exception as err:
- errno = getattr(err, 'errno', None)
- if errno != 1396: # Ignore if user doesn't exist
- raise
-
- logger.info(f'MySQL user host changed: {username}@{host} -> {username}@{new_host}')
- message = f'User {username}@{new_host} berhasil diupdate (host diubah dari {host})'
- else:
- # Just update password and privileges for existing user
- cursor.execute(f"ALTER USER '{username}'@'{host}' IDENTIFIED BY %s", (password,))
-
- # Update privileges if specified
- if privileges and privileges != 'CURRENT':
- # First revoke all privileges
- cursor.execute(f"REVOKE ALL PRIVILEGES ON *.* FROM '{username}'@'{host}'")
-
- # Grant new privileges
- if privileges == 'ALL PRIVILEGES':
- cursor.execute(f"GRANT ALL PRIVILEGES ON *.* TO '{username}'@'{host}'")
- else:
- cursor.execute(f"GRANT {privileges} ON *.* TO '{username}'@'{host}'")
-
- logger.info(f'MySQL user updated: {username}@{host}')
- message = f'User {username}@{host} berhasil diupdate'
-
- # Flush privileges
- cursor.execute("FLUSH PRIVILEGES")
-
- connection.commit()
- cursor.close()
- connection.close()
-
- return jsonify({'message': message})
-
- except Exception as err:
- logger.error(f'MySQL error updating user: {err}')
- errno = getattr(err, 'errno', None)
- if errno == 1396: # User doesn't exist
- return jsonify({'error': f'User {username}@{host} tidak ditemukan'}), 404
- elif errno == 1007: # User already exists
- return jsonify({'error': f'User {username}@{new_host} sudah ada'}), 409
- else:
- return jsonify({'error': f'Error MySQL: {str(err)}'}), 500
- except Exception as e:
- logger.error(f'Error updating MySQL user: {str(e)}')
- return jsonify({'error': f'Terjadi kesalahan: {str(e)}'}), 500
-
-# MySQL Replication Management
-@app.route('/api/mysql/replication/status', methods=['GET'])
-@login_required
-def get_replication_status():
- """Get MySQL replication status"""
- try:
- connection = create_mysql_connection()
- if not connection:
- return jsonify({'success': False, 'error': 'Failed to connect to MySQL'})
-
- cursor = connection.cursor(dictionary=True)
- status = {'master': None, 'slave': []}
-
- # Check master status
- try:
- cursor.execute("SHOW MASTER STATUS")
- master_result = cursor.fetchone()
- if master_result:
- status['master'] = {
- 'file': master_result.get('File'),
- 'position': master_result.get('Position'),
- 'binlog_do_db': master_result.get('Binlog_Do_DB'),
- 'binlog_ignore_db': master_result.get('Binlog_Ignore_DB')
- }
- except Exception as e:
- logger.debug(f"Master status check failed: {e}")
- pass # Master not configured
-
- # Check slave status
- try:
- cursor.execute("SHOW SLAVE STATUS")
- slave_result = cursor.fetchone()
- if slave_result:
- status['slave'] = [{
- 'master_host': slave_result.get('Master_Host'),
- 'master_user': slave_result.get('Master_User'),
- 'master_port': slave_result.get('Master_Port'),
- 'slave_io_running': slave_result.get('Slave_IO_Running'),
- 'slave_sql_running': slave_result.get('Slave_SQL_Running'),
- 'seconds_behind_master': slave_result.get('Seconds_Behind_Master'),
- 'master_log_file': slave_result.get('Master_Log_File'),
- 'read_master_log_pos': slave_result.get('Read_Master_Log_Pos'),
- 'relay_log_file': slave_result.get('Relay_Log_File'),
- 'relay_log_pos': slave_result.get('Relay_Log_Pos'),
- 'last_errno': slave_result.get('Last_Errno'),
- 'last_error': slave_result.get('Last_Error')
- }]
- except Exception as e:
- logger.debug(f"Slave status check failed: {e}")
- pass # Slave not configured
-
- cursor.close()
- connection.close()
-
- return jsonify({'success': True, 'status': status})
-
- except Exception as e:
- logger.error(f'Error getting replication status: {str(e)}')
- return jsonify({'success': False, 'error': str(e)})
-
-@app.route('/api/mysql/replication/setup-master', methods=['POST'])
-@login_required
-def setup_mysql_master():
- """Setup MySQL as master for replication (inside Docker container)."""
- try:
- data = request.get_json()
- server_id = data.get('server_id', 1)
- log_bin = data.get('log_bin', 'mysql-bin')
-
- if not isinstance(server_id, int) or server_id < 1 or server_id > 4294967295:
- return jsonify({'success': False, 'error': 'Invalid server ID. Must be between 1 and 4294967295'})
-
- # Prefer editing /etc/my.cnf to ensure effective config, else fallback to conf.d drop-in
- use_mycnf = subprocess.run(['docker', 'exec', MYSQL_CONTAINER_NAME, 'test', '-f', '/etc/my.cnf'], capture_output=True, text=True).returncode == 0
- cfg_name = '99-replication.cnf'
- cfg_content = (
- "[mysqld]\n"
- f"server-id = {server_id}\n"
- f"log-bin = {log_bin}\n"
- "binlog-format = ROW\n"
- "expire-logs-days = 7\n"
- )
-
- if use_mycnf:
- # Ensure conf.d exists and is included by my.cnf; also remove disabling directives
- shell_script = (
- "set -e; "
- # Backup my.cnf
- "ts=$(date +%s); cp /etc/my.cnf /etc/my.cnf.backup.$ts; "
- # Ensure conf.d directory exists
- "mkdir -p /etc/mysql/conf.d; "
- # Remove disabling directives and conflicting lines
- "sed -i -E '/^\\s*(disable-log-bin|skip-log-bin)\\b/d' /etc/my.cnf; "
- "sed -i -E '/^\\s*server-id\\s*=.*/d' /etc/my.cnf; "
- "sed -i -E '/^\\s*log-bin\\s*=.*/d' /etc/my.cnf; "
- # Ensure !includedir directive present
- "grep -qE '^\\s*!includedir\\s+/etc/mysql/conf.d' /etc/my.cnf || echo '\n!includedir /etc/mysql/conf.d' >> /etc/my.cnf; "
- # Write drop-in replication config
- f"cat > /etc/mysql/conf.d/{cfg_name} <<'EOF'\n{cfg_content}EOF"
- )
- write_result = subprocess.run(['docker', 'exec', '-i', MYSQL_CONTAINER_NAME, 'bash', '-lc', shell_script], capture_output=True, text=True)
- if write_result.returncode != 0:
- return jsonify({'success': False, 'error': f"Failed to update /etc/my.cnf and write replication config: {write_result.stderr or write_result.stdout}"})
- cfg_path = f"/etc/mysql/conf.d/{cfg_name}"
- else:
- # Fallback: write drop-in to an available conf directory
- candidate_dirs = [
- '/etc/mysql/mariadb.conf.d',
- '/etc/mysql/mysql.conf.d',
- '/etc/mysql/conf.d'
- ]
- target_dir = None
- for d in candidate_dirs:
- try:
- check = subprocess.run(['docker', 'exec', MYSQL_CONTAINER_NAME, 'test', '-d', d], capture_output=True, text=True)
- if check.returncode == 0:
- target_dir = d
- break
- except Exception:
- continue
- if not target_dir:
- return jsonify({'success': False, 'error': 'MySQL config directory not found in container'})
- cfg_path = f"{target_dir}/{cfg_name}"
- write_cmd = ['docker', 'exec', '-i', MYSQL_CONTAINER_NAME, 'bash', '-lc', f"cat > '{cfg_path}' <<'EOF'\n{cfg_content}EOF"]
- write_result = subprocess.run(write_cmd, capture_output=True, text=True)
- if write_result.returncode != 0:
- return jsonify({'success': False, 'error': f"Failed to write replication config: {write_result.stderr or write_result.stdout}"})
-
- # Restart MySQL by restarting the container
- restart_result = subprocess.run(['docker', 'restart', MYSQL_CONTAINER_NAME], capture_output=True, text=True)
- if restart_result.returncode != 0:
- return jsonify({'success': False, 'error': f"Failed to restart MySQL container: {restart_result.stderr}"})
- # Wait a bit for MySQL to come up
- time.sleep(3)
-
- # Verify variables before master status
- connection = create_mysql_connection()
- cursor = connection.cursor(dictionary=True)
- try:
- cursor.execute('SHOW VARIABLES LIKE "log_bin"')
- lb = cursor.fetchone() or {}
- cursor.execute('SHOW VARIABLES LIKE "server_id"')
- sid = cursor.fetchone() or {}
- lb_val = (lb.get('Value') or lb.get('value') or '').upper()
- sid_val = int((sid.get('Value') or sid.get('value') or 0))
- logger.info(f"After setup-master: log_bin={lb_val}, server_id={sid_val}, cfg_path={cfg_path}")
- if lb_val != 'ON' or sid_val == 0:
- cursor.close(); connection.close()
- return jsonify({'success': False, 'error': f'Master not active: log_bin={lb_val}, server_id={sid_val}. Config at {cfg_path}.'})
- except Exception as e:
- logger.warning(f"Variable check failed: {e}")
-
- # Check master status
- try:
- cursor.execute("SHOW MASTER STATUS")
- master_status = cursor.fetchone()
- except Exception as e:
- master_status = None
- logger.error(f"Error checking master status: {e}")
-
- if not master_status:
- cursor.close()
- connection.close()
- return jsonify({'success': False, 'error': 'Master setup failed - no master status found (ensure binlog is enabled)'});
-
- # Create replication user if provided
- replication_user = data.get('replication_user')
- replication_password = data.get('replication_password')
- logger.info(f'Received replication_user: {replication_user}, has_password: {bool(replication_password)}')
- if replication_user and replication_password:
- try:
- # Check if user exists first
- cursor.execute("SELECT User FROM mysql.user WHERE User = %s AND Host = '%'", (replication_user,))
- user_exists = cursor.fetchone() is not None
- if user_exists:
- cursor.execute(f"DROP USER '{replication_user}'@'%'")
- cursor.execute(f"CREATE USER '{replication_user}'@'%' IDENTIFIED BY '{replication_password}'")
- cursor.execute(f"GRANT REPLICATION SLAVE ON *.* TO '{replication_user}'@'%'")
- cursor.execute("FLUSH PRIVILEGES")
- logger.info(f'Replication user {replication_user} prepared')
- except Exception as user_error:
- logger.error(f'Failed to create replication user {replication_user}: {str(user_error)}')
- # Don't fail the entire setup if user creation fails
- else:
- logger.warning(f'Replication user not created - user: {replication_user}, password provided: {bool(replication_password)}')
-
- cursor.close()
- connection.close()
-
- logger.info(f'MySQL master setup completed inside container with server-id {server_id}')
- return jsonify({
- 'success': True,
- 'message': 'Master setup completed successfully',
- 'master_status': master_status,
- 'replication_user_created': bool(replication_user and replication_password)
- })
-
- except Exception as e:
- logger.error(f'Error setting up MySQL master: {str(e)}')
- return jsonify({'success': False, 'error': str(e)})
-
-@app.route('/api/mysql/replication/setup-slave', methods=['POST'])
-@login_required
-def setup_mysql_slave():
- """Setup MySQL as slave for replication"""
- try:
- data = request.get_json()
- master_host = data.get('master_host')
- master_user = data.get('master_user')
- master_password = data.get('master_password')
- master_log_file = data.get('master_log_file', '')
- master_log_pos = data.get('master_log_pos', 0)
- server_id = data.get('server_id', 2)
-
- if not all([master_host, master_user, master_password]):
- return jsonify({'success': False, 'error': 'Master host, user, and password are required'})
-
- if not isinstance(server_id, int) or server_id < 1 or server_id > 4294967295:
- return jsonify({'success': False, 'error': 'Invalid server ID. Must be between 1 and 4294967295'})
-
- # Write slave config inside container and restart via Docker
- container = MYSQL_CONTAINER_NAME
- script_template = r"""
-set -euo pipefail
-TS=$(date +%s)
-FILES=( "/etc/my.cnf" "/etc/mysql/my.cnf" "/etc/mysql/mysql.conf.d/mysqld.cnf" )
-DIRS=( "/etc/mysql/conf.d" "/etc/mysql/mysql.conf.d" )
-sanitize_file() {
- local f="$1"
- [ -f "$f" ] || return 0
- cp "$f" "$f.backup.$TS" || true
- sed -E '/^[[:space:]]*(server[-_]?id|relay[-_]?log|read[-_]?only)[[:space:]]*(=|$)/d' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
-}
-for f in "${FILES[@]}"; do sanitize_file "$f"; done
-for d in "${DIRS[@]}"; do
- if [ -d "$d" ]; then
- for cf in "$d"/*.cnf; do [ -f "$cf" ] || continue; sanitize_file "$cf"; done
- fi
-done
-mkdir -p /etc/mysql/conf.d || true
-cat > /etc/mysql/conf.d/99-slave.cnf </tmp/setup_slave.sh <<'SCRIPT_EOF'\n{script}\nSCRIPT_EOF\nbash /tmp/setup_slave.sh"],
- capture_output=True, text=True
- )
- if exec_res.returncode != 0:
- return jsonify({'success': False, 'error': f"Failed to update slave config in container: {exec_res.stderr or exec_res.stdout}"})
- restart_res = subprocess.run(['docker','restart',container], capture_output=True, text=True)
- if restart_res.returncode != 0:
- return jsonify({'success': False, 'error': f"Failed to restart container: {restart_res.stderr or restart_res.stdout}"})
- time.sleep(3)
-
- # Tunggu MySQL siap setelah restart dengan retry
- max_attempts = 10
- attempt = 0
- connection = None
- while attempt < max_attempts:
- connection = create_mysql_connection()
- if connection:
- break
- attempt += 1
- time.sleep(1)
- if not connection:
- return jsonify({'success': False, 'error': 'Failed to connect to MySQL after restart (timeout waiting for server ready)'})
-
- cursor = connection.cursor()
-
- # Stop slave jika sedang berjalan (abaikan error jika sudah berhenti)
- try:
- cursor.execute("STOP SLAVE")
- except Exception as stop_error:
- if "Slave already has been stopped" not in str(stop_error):
- logger.warning(f'Error stopping slave (ignored): {str(stop_error)}')
-
- # Validasi master host dapat dijangkau dari dalam container
- try:
- subprocess.run(['docker','exec',container,'bash','-lc', f"nc -z -w2 {master_host} 3306"], capture_output=True, text=True, timeout=5)
- except Exception as host_check_err:
- logger.warning(f"Master host reachability check failed (ignored): {host_check_err}")
-
- # Bangun SQL CHANGE MASTER TO (sertakan MASTER_PORT)
- change_master_sql = f"""
- CHANGE MASTER TO
- MASTER_HOST='{master_host}',
- MASTER_USER='{master_user}',
- MASTER_PASSWORD='{master_password}',
- MASTER_PORT=3306
- """
-
- if master_log_file and master_log_pos > 0:
- change_master_sql += f",\nMASTER_LOG_FILE='{master_log_file}',\nMASTER_LOG_POS={master_log_pos}"
-
- # Eksekusi CHANGE MASTER TO dengan penanganan error detail
- # Tambah delay 10 detik sebelum CHANGE MASTER TO untuk memastikan MySQL siap
- time.sleep(10)
- try:
- cursor.execute(change_master_sql)
- except Exception as cm_err:
- logger.error(f"CHANGE MASTER TO failed: {cm_err}; sql={change_master_sql}")
- cursor.close()
- connection.close()
- return jsonify({'success': False, 'error': f'CHANGE MASTER TO failed: {cm_err}'})
-
- # Start slave dengan penanganan error
- try:
- cursor.execute("START SLAVE")
- except Exception as start_err:
- logger.warning(f"START SLAVE error: {start_err}")
-
- # Beri waktu lebih lama agar slave benar-benar mulai
- time.sleep(5)
-
- # Check status slave
- cursor.close()
- connection.close()
-
- conn2 = create_mysql_connection()
- if not conn2:
- logger.warning('Slave setup completed, but cannot reconnect to verify status')
- return jsonify({'success': True, 'message': 'Slave setup completed successfully (verification skipped: reconnect failed)'})
- try:
- cur2 = conn2.cursor(dictionary=True)
- cur2.execute("SHOW SLAVE STATUS")
- status = cur2.fetchone()
- cur2.close()
- finally:
- conn2.close()
-
- if not status:
- logger.info('Slave setup completed, but SHOW SLAVE STATUS returns empty (not configured)')
- return jsonify({'success': False, 'error': 'Slave is not configured after setup', 'status': None})
-
- io = str(status.get('Slave_IO_Running') or status.get('slave_io_running') or '')
- sql = str(status.get('Slave_SQL_Running') or status.get('slave_sql_running') or '')
- is_running = (io == 'Yes' and sql == 'Yes')
-
- if not is_running:
- last_error = status.get('Last_Error') or status.get('Last_SQL_Error') or status.get('Last_IO_Error')
- logger.warning(f"Slave setup verification failed: IO={io}, SQL={sql}, error={last_error}")
- return jsonify({'success': False, 'error': f"Slave not running after setup (IO={io}, SQL={sql})", 'last_error': last_error, 'status': status})
-
- logger.info(f'MySQL slave setup completed and verified running for master {master_host}')
- return jsonify({'success': True, 'message': 'Slave setup completed successfully', 'status': status})
-
- except Exception as e:
- logger.error(f'Error setting up MySQL slave: {str(e)}')
- return jsonify({'success': False, 'error': str(e)})
-
-@app.route('/api/mysql/replication/start-slave', methods=['POST'])
-@login_required
-def start_mysql_slave():
- """Start MySQL slave replication and verify status"""
- try:
- connection = create_mysql_connection()
- if not connection:
- return jsonify({'success': False, 'error': 'Failed to connect to MySQL'})
-
- cursor = connection.cursor()
- cursor.execute("START SLAVE")
- cursor.close()
- connection.close()
-
- # Beri waktu agar slave benar-benar mulai
- time.sleep(2)
-
- # Cek status slave
- conn2 = create_mysql_connection()
- if not conn2:
- logger.warning('Started slave but cannot reconnect to verify status')
- return jsonify({'success': True, 'message': 'Slave started successfully (verification skipped: reconnect failed)'})
- try:
- cur2 = conn2.cursor(dictionary=True)
- cur2.execute("SHOW SLAVE STATUS")
- status = cur2.fetchone()
- cur2.close()
- finally:
- conn2.close()
-
- if not status:
- logger.info('Slave started, but SHOW SLAVE STATUS returns empty (not configured)')
- return jsonify({'success': False, 'error': 'Slave not configured or status unavailable after start', 'status': None})
-
- io = str(status.get('Slave_IO_Running') or status.get('slave_io_running') or '')
- sql = str(status.get('Slave_SQL_Running') or status.get('slave_sql_running') or '')
- is_running = (io == 'Yes' and sql == 'Yes')
-
- if not is_running:
- last_error = status.get('Last_Error') or status.get('Last_SQL_Error') or status.get('Last_IO_Error')
- logger.warning(f"Slave start verification failed: IO={io}, SQL={sql}, error={last_error}")
- return jsonify({'success': False, 'error': f"Slave not running after START SLAVE (IO={io}, SQL={sql})", 'last_error': last_error, 'status': status})
-
- logger.info('MySQL slave started and verified running')
- return jsonify({'success': True, 'message': 'Slave started successfully', 'status': status})
-
- except Exception as e:
- logger.error(f'Error starting MySQL slave: {str(e)}')
- return jsonify({'success': False, 'error': str(e)})
-
-@app.route('/api/mysql/replication/stop-slave', methods=['POST'])
-@login_required
-def stop_mysql_slave():
- """Stop MySQL slave replication"""
- try:
- connection = create_mysql_connection()
- if not connection:
- return jsonify({'success': False, 'error': 'Failed to connect to MySQL'})
-
- cursor = connection.cursor()
- try:
- cursor.execute("STOP SLAVE")
- logger.info('MySQL slave stopped')
- message = 'Slave stopped successfully'
- except Exception as stop_error:
- if "Slave already has been stopped" in str(stop_error):
- logger.info('MySQL slave was already stopped')
- message = 'Slave was already stopped'
- else:
- raise stop_error
-
- cursor.close()
- connection.close()
-
- return jsonify({'success': True, 'message': message})
-
- except Exception as e:
- logger.error(f'Error stopping MySQL slave: {str(e)}')
- return jsonify({'success': False, 'error': str(e)})
-
-@app.route('/api/mysql/replication/reset-slave', methods=['POST'])
-@login_required
-def reset_mysql_slave():
- """Reset MySQL slave configuration and clean slave-related config inside Docker container"""
- try:
- # First reset the slave inside MySQL
- connection = create_mysql_connection()
- if not connection:
- return jsonify({'success': False, 'error': 'Failed to connect to MySQL'})
-
- cursor = connection.cursor()
- try:
- cursor.execute("STOP SLAVE")
- except Exception as stop_error:
- if "Slave already has been stopped" not in str(stop_error):
- logger.warning(f'Error stopping slave during reset (ignored): {str(stop_error)}')
-
- cursor.execute("RESET SLAVE ALL")
- cursor.close()
- connection.close()
-
- # Then clean slave-related configuration files inside the container and restart MySQL
- container = MYSQL_CONTAINER_NAME
- script = r"""
-set -euo pipefail
-TS=$(date +%s)
-FILES=( "/etc/my.cnf" "/etc/mysql/my.cnf" "/etc/mysql/mysql.conf.d/mysqld.cnf" "/etc/mysql/mariadb.conf.d/50-server.cnf" )
-DIRS=( "/etc/mysql/conf.d" "/etc/mysql/mysql.conf.d" "/etc/mysql/mariadb.conf.d" )
-sanitize_file() {
- local f="$1"
- [ -f "$f" ] || return 0
- cp "$f" "$f.backup.$TS" || true
- sed -E '/^[[:space:]]*(server[-_]?id|relay[-_]?log|read[-_]?only)[[:space:]]*(=|$)/d' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
-}
-for f in "${FILES[@]}"; do sanitize_file "$f"; done
-for d in "${DIRS[@]}"; do
- if [ -d "$d" ]; then
- rm -f "$d"/*slave*.cnf || true
- rm -f "$d"/99-slave.cnf || true
- rm -f "$d"/*replication*.cnf || true
- for cf in "$d"/*.cnf; do [ -f "$cf" ] || continue; sanitize_file "$cf"; done
- fi
-done
-"""
- exec_res = subprocess.run(
- ['docker','exec',container,'bash','-lc', f"cat >/tmp/reset_slave.sh <<'SCRIPT_EOF'\n{script}\nSCRIPT_EOF\nbash /tmp/reset_slave.sh"],
- capture_output=True, text=True
- )
- if exec_res.returncode != 0:
- return jsonify({'success': False, 'error': f"Failed to clean slave config in container: {exec_res.stderr or exec_res.stdout}"})
- restart_res = subprocess.run(['docker','restart',container], capture_output=True, text=True)
- if restart_res.returncode != 0:
- return jsonify({'success': False, 'error': f"Failed to restart container: {restart_res.stderr or restart_res.stdout}"})
- time.sleep(3)
-
- # Verify that slave status is cleared (optional)
- connection2 = create_mysql_connection()
- slave_status = None
- if connection2:
- try:
- cur2 = connection2.cursor(dictionary=True)
- cur2.execute("SHOW SLAVE STATUS")
- slave_status = cur2.fetchone()
- cur2.close()
- finally:
- connection2.close()
-
- logger.info('MySQL slave reset and configuration cleaned')
- return jsonify({'success': True, 'message': 'Slave reset and configuration cleaned successfully', 'slave_status': slave_status})
-
- except Exception as e:
- logger.error(f'Error resetting MySQL slave: {str(e)}')
- return jsonify({'success': False, 'error': str(e)})
-
-@app.route('/api/mysql/replication/skip-error', methods=['POST'])
-@login_required
-def skip_mysql_replication_error():
- """Skip current MySQL replication error by advancing one transaction.
- Intended for errors like 1062 (duplicate entry).
- Steps: STOP SLAVE; SET GLOBAL sql_slave_skip_counter = 1; START SLAVE; verify.
- """
- try:
- connection = create_mysql_connection()
- if not connection:
- return jsonify({'success': False, 'error': 'Failed to connect to MySQL'})
-
- # Perform skip sequence
- cur = connection.cursor()
- try:
- try:
- cur.execute("STOP SLAVE")
- except Exception as e_stop:
- # If already stopped, continue
- if "already has been stopped" not in str(e_stop):
- logger.warning(f"STOP SLAVE returned: {e_stop}")
-
- # Advance one transaction
- cur.execute("SET GLOBAL sql_slave_skip_counter = 1")
-
- # Restart slave
- cur.execute("START SLAVE")
- finally:
- cur.close()
- connection.close()
-
- # Small delay to let slave apply
- time.sleep(2)
-
- # Verify status
- conn2 = create_mysql_connection()
- status = None
- last_error = None
- if conn2:
- try:
- c2 = conn2.cursor(dictionary=True)
- c2.execute("SHOW SLAVE STATUS")
- status = c2.fetchone()
- c2.close()
- finally:
- conn2.close()
- if status:
- io = str(status.get('Slave_IO_Running') or status.get('slave_io_running') or '')
- sql = str(status.get('Slave_SQL_Running') or status.get('slave_sql_running') or '')
- last_error = status.get('Last_Error') or status.get('Last_SQL_Error') or status.get('Last_IO_Error')
- is_running = (io == 'Yes' and sql == 'Yes')
- if not is_running:
- logger.warning(f"Skip error attempted, but slave not running: IO={io}, SQL={sql}, error={last_error}")
- return jsonify({'success': False, 'error': 'Slave still not running after skip', 'last_error': last_error, 'status': status})
-
- logger.info('Replication error skipped and slave running (if configured)')
- return jsonify({'success': True, 'message': 'Replication error skipped. Slave restarted.', 'status': status})
- except Exception as e:
- logger.error(f'Error skipping replication error: {str(e)}')
- return jsonify({'success': False, 'error': str(e)})
-
-@app.route('/api/mysql/replication/disable-master', methods=['POST'])
-@login_required
-def disable_mysql_master():
- """Disable MySQL/MariaDB master (turn off binary logging) inside Docker container"""
- try:
- container = MYSQL_CONTAINER_NAME
- # Shell script to sanitize all relevant config files and ensure binlog off
- script = r"""
-set -euo pipefail
-TS=$(date +%s)
-# Detect flavor (MariaDB vs MySQL)
-FLAVOR="mysql"
-if mysql --version 2>/dev/null | grep -qi mariadb; then FLAVOR="mariadb"; fi
-# Candidate files and directories
-FILES=(
- "/etc/my.cnf"
- "/etc/mysql/my.cnf"
- "/etc/mysql/mysql.conf.d/mysqld.cnf"
- "/etc/mysql/mariadb.conf.d/50-server.cnf"
-)
-DIRS=("/etc/mysql/conf.d" "/etc/mysql/mysql.conf.d" "/etc/mysql/mariadb.conf.d")
-# Function to sanitize config files by removing replication/binlog directives
-sanitize_file() {
- local f="$1"
- [ -f "$f" ] || return 0
- cp "$f" "$f.backup.$TS" || true
- sed -E '/^[[:space:]]*(server[-_]?id|log[-_]?bin|binlog[-_]?format|expire[-_]?logs[-_]?days|binlog[-_]?do[-_]?db|binlog[-_]?ignore[-_]?db)[[:space:]]*(=|$)/d' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
-}
-# Process candidate files
-for f in "${FILES[@]}"; do sanitize_file "$f"; done
-# Process drop-in directories: remove replication-related cnf files and scrub others
-for d in "${DIRS[@]}"; do
- if [ -d "$d" ]; then
- # Remove specific replication config files if any
- rm -f "$d"/*replication*.cnf || true
- rm -f "$d"/99-replication.cnf || true
- # Scrub all *.cnf under this dir
- for cf in "$d"/*.cnf; do
- [ -f "$cf" ] || continue
- sanitize_file "$cf"
- done
- fi
-done
-# For MariaDB, explicitly disable binlog to be safe
-if [ "$FLAVOR" = "mariadb" ]; then
- TARGET="/etc/my.cnf"
- [ -f "$TARGET" ] || TARGET="/etc/mysql/my.cnf"
- if [ -f "$TARGET" ]; then
- cp "$TARGET" "$TARGET.backup.$TS" || true
- sed -E '/^[[:space:]]*(log[-_]?bin|binlog[-_]?format)[[:space:]]*(=|$)/d' "$TARGET" > "$TARGET.tmp" && mv "$TARGET.tmp" "$TARGET"
- if ! grep -qi '^\[mysqld\]' "$TARGET"; then
- printf '[mysqld]\n' >> "$TARGET"
- fi
- printf 'disable-log-bin\n' >> "$TARGET"
- else
- cat > "$TARGET" < "$TARGET.tmp" && mv "$TARGET.tmp" "$TARGET"
- fi
- mkdir -p /etc/mysql/conf.d || true
- cat > /etc/mysql/conf.d/99-disable-binlog.cnf </tmp/disable_master.sh <<'SCRIPT_EOF'\n{script}\nSCRIPT_EOF\nbash /tmp/disable_master.sh"],
- capture_output=True, text=True
- )
- if exec_res.returncode != 0:
- return jsonify({'success': False, 'error': f"Failed to update config in container: {exec_res.stderr or exec_res.stdout}"})
- # Restart MySQL container to apply changes
- restart_res = subprocess.run(['docker', 'restart', container], capture_output=True, text=True)
- if restart_res.returncode != 0:
- return jsonify({'success': False, 'error': f"Failed to restart container: {restart_res.stderr or restart_res.stdout}"})
- time.sleep(3)
- # Verify that binary logging is OFF
- connection = create_mysql_connection()
- if not connection:
- return jsonify({'success': False, 'error': 'Failed to connect to MySQL after restart'})
- cursor = connection.cursor(dictionary=True)
- try:
- cursor.execute('SHOW VARIABLES LIKE "log_bin"')
- lb = cursor.fetchone()
- log_bin_state = (lb or {}).get('Value') or (lb or {}).get('value')
- except Exception as e:
- log_bin_state = None
- # Also check master status emptiness
- master_empty = True
- try:
- cursor.execute("SHOW MASTER STATUS")
- mr = cursor.fetchone()
- if mr:
- master_empty = False
- except Exception:
- master_empty = True
- # Attempt reset if still enabled
- if (log_bin_state and str(log_bin_state).upper() == 'ON') or (not master_empty):
- try:
- cursor.execute("RESET MASTER")
- cursor.execute('SHOW VARIABLES LIKE "log_bin"')
- lb2 = cursor.fetchone()
- log_bin_state = (lb2 or {}).get('Value') or (lb2 or {}).get('value')
- cursor.execute("SHOW MASTER STATUS")
- mr2 = cursor.fetchone()
- master_empty = not bool(mr2)
- except Exception:
- pass
- cursor.close()
- connection.close()
- if (log_bin_state and str(log_bin_state).upper() == 'ON') or (not master_empty):
- return jsonify({'success': False, 'error': 'Binary logging is still ON; check other config files enabling log-bin.'})
- logger.info('MySQL master replication disabled successfully (log_bin=OFF)')
- return jsonify({'success': True, 'message': 'Master replication disabled successfully'})
-
- except Exception as e:
- logger.error(f'Error disabling MySQL master: {str(e)}')
- return jsonify({'success': False, 'error': str(e)})
-
-@app.route('/api/mysql/error-logs', methods=['GET'])
-def get_mysql_error_logs():
- """Get MySQL error logs"""
- try:
- # Common MySQL error log paths
- error_log_paths = [
- '/var/log/mysql/error.log',
- '/var/log/mysqld.log',
- '/var/log/mysql.err',
- '/usr/local/var/mysql/*.err'
- ]
-
- logs_content = ""
- log_found = False
-
- for log_path in error_log_paths:
- if '*' in log_path:
- # Handle wildcard paths
- import glob
- matching_files = glob.glob(log_path)
- for file_path in matching_files:
- if os.path.exists(file_path):
- try:
- with open(file_path, 'r') as f:
- # Get last 100 lines
- lines = f.readlines()
- recent_lines = lines[-100:] if len(lines) > 100 else lines
- logs_content += f"\n=== {file_path} ===\n"
- logs_content += ''.join(recent_lines)
- log_found = True
- except Exception as e:
- logs_content += f"\nError reading {file_path}: {str(e)}\n"
- else:
- if os.path.exists(log_path):
- try:
- with open(log_path, 'r') as f:
- # Get last 100 lines
- lines = f.readlines()
- recent_lines = lines[-100:] if len(lines) > 100 else lines
- logs_content += f"\n=== {log_path} ===\n"
- logs_content += ''.join(recent_lines)
- log_found = True
- except Exception as e:
- logs_content += f"\nError reading {log_path}: {str(e)}\n"
-
- if not log_found:
- logs_content = "No MySQL error logs found in common locations."
-
- # Return plain text logs (no HTML formatting)
- return jsonify({
- 'success': True,
- 'logs': logs_content
- })
-
- except Exception as e:
- logger.error(f'Error getting MySQL error logs: {str(e)}')
- return jsonify({'success': False, 'message': str(e)})
-
-@app.route('/api/mysql/replication-logs', methods=['GET'])
-def get_mysql_replication_logs():
- """Get MySQL replication-related logs"""
- try:
- connection = create_mysql_connection()
- if not connection:
- return jsonify({'success': False, 'message': 'Failed to connect to MySQL'})
-
- cursor = connection.cursor(dictionary=True)
- logs_content = ""
-
- # Get replication status for error information
- try:
- cursor.execute("SHOW SLAVE STATUS")
- slave_status = cursor.fetchone()
-
- if slave_status:
- logs_content += "=== Slave Status Information ===\n"
- logs_content += f"Slave_IO_Running: {slave_status.get('Slave_IO_Running', 'N/A')}\n"
- logs_content += f"Slave_SQL_Running: {slave_status.get('Slave_SQL_Running', 'N/A')}\n"
- logs_content += f"Last_IO_Error: {slave_status.get('Last_IO_Error', 'None')}\n"
- logs_content += f"Last_SQL_Error: {slave_status.get('Last_SQL_Error', 'None')}\n"
- logs_content += f"Seconds_Behind_Master: {slave_status.get('Seconds_Behind_Master', 'N/A')}\n"
- logs_content += f"Master_Log_File: {slave_status.get('Master_Log_File', 'N/A')}\n"
- logs_content += f"Read_Master_Log_Pos: {slave_status.get('Read_Master_Log_Pos', 'N/A')}\n\n"
- else:
- logs_content += "=== No Slave Configuration Found ===\n\n"
- except Exception as e:
- logs_content += f"Error getting slave status: {str(e)}\n\n"
-
- # Get binary log information
- try:
- cursor.execute("SHOW BINARY LOGS")
- binary_logs = cursor.fetchall()
-
- if binary_logs:
- logs_content += "=== Binary Logs ===\n"
- for log in binary_logs:
- logs_content += f"{log.get('Log_name', 'N/A')} - Size: {log.get('File_size', 'N/A')} bytes\n"
- logs_content += "\n"
- else:
- logs_content += "=== No Binary Logs Found ===\n\n"
- except Exception as e:
- logs_content += f"Binary logs not available: {str(e)}\n\n"
-
- # Get master status
- try:
- cursor.execute("SHOW MASTER STATUS")
- master_status = cursor.fetchone()
-
- if master_status:
- logs_content += "=== Master Status ===\n"
- logs_content += f"File: {master_status.get('File', 'N/A')}\n"
- logs_content += f"Position: {master_status.get('Position', 'N/A')}\n"
- logs_content += f"Binlog_Do_DB: {master_status.get('Binlog_Do_DB', 'N/A')}\n"
- logs_content += f"Binlog_Ignore_DB: {master_status.get('Binlog_Ignore_DB', 'N/A')}\n\n"
- else:
- logs_content += "=== No Master Configuration Found ===\n\n"
- except Exception as e:
- logs_content += f"Master status not available: {str(e)}\n\n"
-
- cursor.close()
- connection.close()
-
- if not logs_content.strip():
- logs_content = "No replication information available."
-
- # Return plain text logs
- return jsonify({
- 'success': True,
- 'logs': logs_content
- })
-
- except Exception as e:
- logger.error(f'Error getting MySQL replication logs: {str(e)}')
- return jsonify({'success': False, 'message': str(e)})
-
-@app.route('/api/mysql/general-logs', methods=['GET'])
-def get_mysql_general_logs():
- """Get MySQL general logs"""
- try:
- # Common MySQL general log paths
- general_log_paths = [
- '/var/log/mysql/mysql.log',
- '/var/log/mysql/general.log',
- '/usr/local/var/mysql/general.log'
- ]
-
- logs_content = ""
- log_found = False
-
- for log_path in general_log_paths:
- if os.path.exists(log_path):
- try:
- with open(log_path, 'r') as f:
- # Get last 100 lines
- lines = f.readlines()
- recent_lines = lines[-100:] if len(lines) > 100 else lines
- logs_content += f"\n=== {log_path} ===\n"
- logs_content += ''.join(recent_lines)
- log_found = True
- except Exception as e:
- logs_content += f"\nError reading {log_path}: {str(e)}\n"
-
- if not log_found:
- logs_content = "No MySQL general logs found. General logging may be disabled.\n\n"
- logs_content += "To enable general logging, add the following to your MySQL configuration:\n"
- logs_content += "general_log = 1\n"
- logs_content += "general_log_file = /var/log/mysql/general.log"
-
- # Return plain text logs
- return jsonify({
- 'success': True,
- 'logs': logs_content
- })
-
- except Exception as e:
- logger.error(f'Error getting MySQL general logs: {str(e)}')
- return jsonify({'success': False, 'message': str(e)})
-
-@app.route('/api/mysql/slow-logs', methods=['GET'])
-def get_mysql_slow_logs():
- """Get MySQL slow query logs"""
- try:
- # Common MySQL slow log paths
- slow_log_paths = [
- '/var/log/mysql/mysql-slow.log',
- '/var/log/mysql/slow.log',
- '/usr/local/var/mysql/slow.log'
- ]
-
- logs_content = ""
- log_found = False
-
- for log_path in slow_log_paths:
- if os.path.exists(log_path):
- try:
- with open(log_path, 'r') as f:
- # Get last 50 entries (slow logs can be verbose)
- lines = f.readlines()
- recent_lines = lines[-200:] if len(lines) > 200 else lines
- logs_content += f"\n=== {log_path} ===\n"
- logs_content += ''.join(recent_lines)
- log_found = True
- except Exception as e:
- logs_content += f"\nError reading {log_path}: {str(e)}\n"
-
- if not log_found:
- logs_content = "No MySQL slow query logs found. Slow query logging may be disabled.\n\n"
- logs_content += "To enable slow query logging, add the following to your MySQL configuration:\n"
- logs_content += "slow_query_log = 1\n"
- logs_content += "slow_query_log_file = /var/log/mysql/mysql-slow.log\n"
- logs_content += "long_query_time = 2"
-
- # Return plain text logs
- return jsonify({
- 'success': True,
- 'logs': logs_content
- })
-
- except Exception as e:
- logger.error(f'Error getting MySQL slow logs: {str(e)}')
- return jsonify({'success': False, 'message': str(e)})
-
-@app.route('/api/mysql/analyze-slow-logs', methods=['GET'])
-def analyze_mysql_slow_logs():
- """Analyze MySQL slow query logs"""
- try:
- connection = create_mysql_connection()
- if not connection:
- return jsonify({'success': False, 'message': 'Failed to connect to MySQL'})
-
- cursor = connection.cursor(dictionary=True)
- analysis_content = ""
-
- # Get slow query log status
- try:
- cursor.execute("SHOW VARIABLES LIKE 'slow_query_log'")
- slow_log_status = cursor.fetchone()
-
- cursor.execute("SHOW VARIABLES LIKE 'long_query_time'")
- long_query_time = cursor.fetchone()
-
- cursor.execute("SHOW VARIABLES LIKE 'slow_query_log_file'")
- slow_log_file = cursor.fetchone()
-
- analysis_content += "=== Slow Query Log Configuration ===\n"
- analysis_content += f"Slow Query Log: {slow_log_status.get('Value', 'N/A') if slow_log_status else 'N/A'}\n"
- analysis_content += f"Long Query Time: {long_query_time.get('Value', 'N/A') if long_query_time else 'N/A'} seconds\n"
- analysis_content += f"Slow Log File: {slow_log_file.get('Value', 'N/A') if slow_log_file else 'N/A'}\n\n"
-
- except Exception as e:
- analysis_content += f"Error getting slow log configuration: {str(e)}\n\n"
-
- # Get process list for currently running queries
- try:
- cursor.execute("SHOW PROCESSLIST")
- processes = cursor.fetchall()
-
- long_running = [p for p in processes if p.get('Time', 0) > 5 and p.get('Command') not in ['Sleep', 'Binlog Dump']]
-
- if long_running:
- analysis_content += "=== Currently Long Running Queries ===\n"
- for process in long_running:
- analysis_content += f"ID: {process.get('Id', 'N/A')}, User: {process.get('User', 'N/A')}, "
- analysis_content += f"Time: {process.get('Time', 'N/A')}s, State: {process.get('State', 'N/A')}\n"
- analysis_content += f"Info: {process.get('Info', 'N/A')[:100]}...\n\n"
- else:
- analysis_content += "=== No Long Running Queries Found ===\n\n"
-
- except Exception as e:
- analysis_content += f"Error getting process list: {str(e)}\n\n"
-
- # Get query cache statistics
- try:
- cursor.execute("SHOW STATUS LIKE 'Qcache%'")
- qcache_stats = cursor.fetchall()
-
- if qcache_stats:
- analysis_content += "=== Query Cache Statistics ===\n"
- for stat in qcache_stats:
- analysis_content += f"{stat.get('Variable_name', 'N/A')}: {stat.get('Value', 'N/A')}\n"
- analysis_content += "\n"
-
- except Exception as e:
- analysis_content += f"Query cache statistics not available: {str(e)}\n\n"
-
- # Get table lock statistics
- try:
- cursor.execute("SHOW STATUS LIKE 'Table_locks%'")
- lock_stats = cursor.fetchall()
-
- if lock_stats:
- analysis_content += "=== Table Lock Statistics ===\n"
- for stat in lock_stats:
- analysis_content += f"{stat.get('Variable_name', 'N/A')}: {stat.get('Value', 'N/A')}\n"
- analysis_content += "\n"
-
- except Exception as e:
- analysis_content += f"Table lock statistics not available: {str(e)}\n\n"
-
- cursor.close()
- connection.close()
-
- if not analysis_content.strip():
- analysis_content = "No slow query analysis data available."
-
- # Return plain text analysis
- return jsonify({
- 'success': True,
- 'analysis': analysis_content
- })
-
- except Exception as e:
- logger.error(f'Error analyzing MySQL slow logs: {str(e)}')
- return jsonify({'success': False, 'message': str(e)})
-
-# ModSecurity per-site helpers
-
-def _parse_modsecurity_status_from_content(content: str) -> tuple[str, bool]:
- """Parse ModSecurity status from vhost content.
- Only consider directives at server scope for server blocks that listen on 80 or 443.
- Ignore directives inside location blocks (e.g., ACME challenge should remain off).
-
- Returns (configured, effective_enabled):
- - configured: 'on'|'off'|'inherit' representing server-level configuration across 80/443 blocks
- - effective_enabled: True if ModSecurity effectively enabled (global default assumed on for 'inherit')
- """
- try:
- lines = content.splitlines()
- depth = 0
- in_server = False
- server_listen_ports = set()
- server_modsec = None # 'on' | 'off' | None
- configs = [] # per targeted server block ('on'|'off'|'inherit')
- for line in lines:
- stripped = line.strip()
- # Enter server block
- if not in_server and re.search(r"\bserver\s*\{", line) and depth == 0:
- in_server = True
- server_listen_ports = set()
- server_modsec = None
- depth += line.count('{') - line.count('}')
- continue
- if in_server:
- # Detect listen ports only at server scope
- if depth == 1 and re.search(r"\blisten\s+(?:\S+:)?(80|443)\b", stripped):
- try:
- m = re.search(r"\blisten\s+(?:\S+:)?(80|443)\b", stripped)
- if m:
- server_listen_ports.add(m.group(1))
- except Exception:
- pass
- # Capture modsecurity directive only at server scope (ignore comments)
- if depth == 1 and (not stripped.startswith('#')):
- if re.search(r"\bmodsecurity\s+on\s*;", stripped, re.IGNORECASE):
- server_modsec = 'on'
- elif re.search(r"\bmodsecurity\s+off\s*;", stripped, re.IGNORECASE):
- server_modsec = 'off'
- # Update depth and finalize server block when closing
- depth += line.count('{') - line.count('}')
- if depth <= 0:
- # End of server block; consider only if it listens on 80 or 443
- if ('80' in server_listen_ports) or ('443' in server_listen_ports):
- if server_modsec is None:
- configs.append('inherit')
- else:
- configs.append(server_modsec)
- in_server = False
- else:
- depth += line.count('{') - line.count('}')
- # Determine overall configured status across targeted server blocks
- if not configs:
- return 'inherit', True
- if all(c == 'on' for c in configs):
- return 'on', True
- if all(c == 'off' for c in configs):
- return 'off', False
- # Mixed or partial -> treat as inherit to avoid misleading UI
- return 'inherit', True
- except Exception:
- return 'inherit', True
-
-
-def get_modsecurity_status(site_name: str) -> dict:
- ok, content, err = read_conf_file(site_name)
- if not ok:
- return {
- 'site_name': site_name,
- 'configured': 'inherit',
- 'effective_enabled': True,
- 'error': err
- }
- configured, effective = _parse_modsecurity_status_from_content(content)
- return {
- 'site_name': site_name,
- 'configured': configured,
- 'effective_enabled': effective
- }
-
-
-def _set_modsecurity_status_in_content(content: str, enabled: bool) -> str:
- """Update vhost content to set modsecurity on/off only at server scope; keep ACME location off."""
- lines = content.splitlines()
- directive = "modsecurity on;" if enabled else "modsecurity off;"
- new_lines = []
- depth = 0
- in_server = False
- for line in lines:
- stripped = line.strip()
- # Enter server block
- if not in_server and re.search(r"\bserver\s*\{", line) and depth == 0:
- in_server = True
- new_lines.append(line)
- depth += line.count('{') - line.count('}')
- # Insert directive at top-level of server block
- new_lines.append(" " + directive)
- continue
- if in_server:
- # Only remove existing modsecurity directive at server scope (depth==1)
- if depth == 1 and (not stripped.startswith('#')) and re.search(r"\bmodsecurity\s+(on|off)\s*;", stripped, flags=re.IGNORECASE):
- # Skip this line; we've inserted new directive already
- pass
- else:
- new_lines.append(line)
- depth += line.count('{') - line.count('}')
- if depth <= 0:
- in_server = False
- else:
- new_lines.append(line)
- depth += line.count('{') - line.count('}')
- return "\n".join(new_lines)
-
-
-def update_modsecurity_status(site_name: str, enabled: bool) -> tuple[bool, str]:
- ok, content, err = read_conf_file(site_name)
- if not ok:
- return False, err
- backup_ok, backup_path = backup_conf_file(site_name)
- if not backup_ok:
- logger.warning(f"Failed to backup vhost for {site_name}: {backup_path}")
- new_content = _set_modsecurity_status_in_content(content, enabled)
- ok_w, err_w = write_conf_file(site_name, new_content)
- if not ok_w:
- return False, err_w
- success, message = reload_nginx()
- if not success:
- return False, f"Updated file but failed to reload nginx: {message}"
- return True, "ModSecurity status updated and nginx reloaded"
-
-
-@app.route('/api/modsecurity/', methods=['GET', 'POST'])
-@login_required
-def api_modsecurity_site(site_name):
- if not is_valid_site_name(site_name):
- return jsonify({'success': False, 'message': 'Invalid site name'}), 400
- if request.method == 'GET':
- status = get_modsecurity_status(site_name)
- return jsonify({'success': True, **status})
- else:
- data = request.get_json(silent=True) or {}
- enabled = bool(data.get('enabled', True))
- ok, msg = update_modsecurity_status(site_name, enabled)
- return jsonify({'success': ok, 'message': msg})
-
-
-
-
-# --- ModSecurity Dashboard Helpers & API ---
-
-MODSEC_AUDIT_PATH = '/var/log/modsecurity/audit.log'
-
-def _parse_time(ts_str):
- try:
- if ts_str is None:
- return None
- # Numeric epoch (int/float)
- if isinstance(ts_str, (int, float)):
- return datetime.fromtimestamp(float(ts_str), tz=timezone.utc)
- s = str(ts_str).strip()
- # Numeric epoch string
- try:
- if s.replace('.', '', 1).isdigit():
- return datetime.fromtimestamp(float(s), tz=timezone.utc)
- except Exception:
- pass
- # Normalize Zulu and ensure timezone awareness
- s = s.replace('Z', '+00:00')
- # Try ISO formats with offset
- try:
- dt = datetime.fromisoformat(s)
- if dt.tzinfo is None:
- dt = dt.replace(tzinfo=timezone.utc)
- return dt.astimezone(timezone.utc)
- except Exception:
- pass
- # Try common ModSecurity format: 'Mon, 21 Aug 2023 10:14:23'
- try:
- dt = datetime.strptime(s[:19], '%a, %d %b %Y %H:%M:%S')
- return dt.replace(tzinfo=timezone.utc)
- except Exception:
- pass
- # Try ModSecurity ctime-like format: 'Tue Oct 14 18:18:15 2025'
- try:
- dt = datetime.strptime(s, '%a %b %d %H:%M:%S %Y')
- return dt.replace(tzinfo=timezone.utc)
- except Exception:
- pass
- # Try alternative ModSecurity format e.g. '2023-08-21 10:14:23'
- try:
- dt = datetime.strptime(s[:19], '%Y-%m-%d %H:%M:%S')
- return dt.replace(tzinfo=timezone.utc)
- except Exception:
- pass
- return None
- except Exception:
- return None
-
-def read_modsec_audit_lines(max_lines: int = 5000):
- """Read ModSecurity audit lines with validation and container fallback.
- Returns a list of raw JSON lines. If both methods fail, returns [].
- """
- # Try local mounted file first
- try:
- if os.path.isfile(MODSEC_AUDIT_PATH):
- if not os.access(MODSEC_AUDIT_PATH, os.R_OK):
- logger.error(f"ModSec audit file not readable: {MODSEC_AUDIT_PATH}")
- else:
- try:
- size = os.path.getsize(MODSEC_AUDIT_PATH)
- except Exception:
- size = None
- logger.info(f"Reading local ModSec audit: path={MODSEC_AUDIT_PATH} size={size}")
- with open(MODSEC_AUDIT_PATH, 'r', encoding='utf-8', errors='ignore') as f:
- lines = f.readlines()[-max_lines:]
- return [ln.rstrip('\n') for ln in lines]
- else:
- logger.warning(f"ModSec audit file not found locally: {MODSEC_AUDIT_PATH}")
- except Exception as e:
- logger.warning(f"ModSec audit local read failed: {e}")
- # Fallback to docker exec into nginx container
- try:
- cmd = ['docker', 'exec', NGINX_CONTAINER_NAME, 'sh', '-c', f"tail -n {max_lines} {shlex.quote(MODSEC_AUDIT_PATH)} 2>/dev/null || true"]
- logger.info(f"Reading ModSec audit via docker exec: container={NGINX_CONTAINER_NAME} path={MODSEC_AUDIT_PATH}")
- res = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
- if res.returncode != 0:
- logger.warning(f"docker exec returned code {res.returncode}: {res.stderr}")
- return (res.stdout or '').splitlines()
- except Exception as e:
- logger.error(f"Error reading ModSecurity audit log via docker exec: {e}")
- return []
-
-def _parse_modsec_event(obj: dict) -> dict:
- tx = obj.get('transaction', {}) if isinstance(obj, dict) else {}
- req = tx.get('request', {})
- resp = tx.get('response', {})
- msgs = obj.get('messages', []) if isinstance(obj, dict) else []
- rule_ids = []
- severity = None
- try:
- for m in msgs:
- det = m.get('details', {})
- rid = det.get('rule_id') or det.get('id') or det.get('ruleId')
- if rid:
- rule_ids.append(str(rid))
- else:
- # Fallback: try parse from message text patterns like id "942100"
- try:
- msgtxt = ''
- if isinstance(m, dict):
- msgtxt = m.get('message') or m.get('msg') or ''
- if isinstance(msgtxt, str) and msgtxt:
- import re
- m1 = re.search(r'id\s*"?(\d{3,7})"?', msgtxt, re.IGNORECASE)
- if m1:
- rule_ids.append(m1.group(1))
- except Exception:
- pass
- sev = det.get('severity') or m.get('severity') or det.get('severity_str')
- if sev and not severity:
- severity = sev
- except Exception:
- pass
- # Additional fallback: parse from "intervention.log" text
- try:
- interv = obj.get('intervention', {}) or {}
- logtxt = interv.get('log') or ''
- if isinstance(logtxt, str) and logtxt:
- import re
- m_id = re.search(r'id\s*"?(\d{3,7})"?', logtxt, re.IGNORECASE)
- if m_id:
- rid = m_id.group(1)
- if rid and rid not in rule_ids:
- rule_ids.append(rid)
- m_sev = re.search(r'severity\s*"?([A-Z]+)"?', logtxt, re.IGNORECASE)
- if m_sev and not severity:
- severity = m_sev.group(1)
- except Exception:
- pass
- ts = tx.get('time_stamp') or obj.get('timestamp')
- ip = tx.get('client_ip') or obj.get('client_ip')
- method = req.get('method') or obj.get('method')
- uri = req.get('uri') or obj.get('uri')
- status = resp.get('status') or obj.get('status')
- # Map disruptive flag to status text if available
- try:
- interv = obj.get('intervention', {}) or {}
- if isinstance(interv.get('disruptive'), bool):
- status = ('Blocked' if interv.get('disruptive') else 'Detected')
- except Exception:
- pass
- try:
- if (not severity) and rule_ids:
- sev_map = get_crs_severity_map()
- for rid in rule_ids:
- s = sev_map.get(str(rid))
- if s:
- severity = s
- break
- except Exception:
- pass
- # Extract domain from absolute URI or Host header
- domain = None
- try:
- if isinstance(uri, str) and (uri.startswith('http://') or uri.startswith('https://')):
- from urllib.parse import urlparse
- parsed = urlparse(uri)
- domain = parsed.hostname or parsed.netloc or None
- if not domain:
- headers = req.get('headers') or tx.get('request_headers') or {}
- host_val = None
- if isinstance(headers, dict):
- host_val = headers.get('Host') or headers.get('host') or headers.get('HOST')
- if not host_val:
- host_val = req.get('host') or tx.get('host')
- domain = host_val or domain
- except Exception:
- domain = None
- dt = _parse_time(ts)
- bucket = None
- try:
- if dt:
- bucket = dt.strftime('%Y-%m-%d %H:%M')
- elif isinstance(ts, str) and len(ts) >= 16:
- bucket = ts[:16]
- except Exception:
- bucket = 'unknown'
- # Debug logging (limited)
- try:
- print(f"[modsec] _parse_modsec_event ts_raw={ts} parsed={dt.isoformat() if dt else None} bucket={bucket}")
- except Exception:
- pass
- return {
- 'timestamp': ts,
- 'bucket': bucket or 'unknown',
- 'datetime': dt.isoformat() if dt else None,
- 'src_ip': ip,
- 'rule_ids': rule_ids,
- 'severity': severity,
- 'method': method,
- 'uri': uri,
- 'domain': domain,
- 'status': status,
- }
-
-def get_modsec_events(limit=1000, since=None, until=None, ip=None, rule_id=None):
- lines = read_modsec_audit_lines(max_lines=max(1000, limit * 10))
- events = []
- for ln in lines:
- ln = ln.strip()
- if not ln:
- continue
- try:
- obj = json.loads(ln)
- except Exception:
- continue
- ev = _parse_modsec_event(obj)
- events.append(ev)
- # Filter
- def in_range(ev):
- dt = None
- if ev.get('datetime'):
- try:
- dt = datetime.fromisoformat(ev['datetime'])
- except Exception:
- dt = None
- ok = True
- if since:
- try:
- since_dt = datetime.fromisoformat(since)
- ok = ok and (dt is None or dt >= since_dt)
- except Exception:
- pass
- if until:
- try:
- until_dt = datetime.fromisoformat(until)
- ok = ok and (dt is None or dt <= until_dt)
- except Exception:
- pass
- if ip:
- ok = ok and (ev.get('src_ip') == ip)
- if rule_id:
- ok = ok and (rule_id in (ev.get('rule_ids') or []))
- return ok
- events = [e for e in events if in_range(e)]
- events = events[-limit:] if limit and len(events) > limit else events
- return events
-
-_crs_severity_cache = {'loaded': False, 'map': {}, 'ts': 0}
-
-def get_crs_severity_map() -> dict:
- try:
- import time, re
- now = time.time()
- if _crs_severity_cache['loaded'] and (now - _crs_severity_cache['ts'] < 600):
- return _crs_severity_cache['map']
- res = subprocess.run(
- ['docker','exec',NGINX_CONTAINER_NAME,'sh','-lc','grep -R "id:\|severity:" -n /etc/nginx/modsec/owasp-crs/rules/*.conf || true'],
- capture_output=True, text=True, timeout=10
- )
- lines = (res.stdout or '').splitlines()
- m = {}
- current_id = None
- for ln in lines:
- ln = ln.strip()
- idm = re.search(r'id:\s*(\d{3,8})', ln, re.IGNORECASE)
- if idm:
- current_id = idm.group(1)
- sev = None
- sm = re.search(r'severity:\s*"?([A-Za-z]+)"?', ln, re.IGNORECASE)
- if sm:
- sev = sm.group(1).upper()
- if current_id and sev:
- m[current_id] = sev
- current_id = None
- _crs_severity_cache['map'] = m
- _crs_severity_cache['loaded'] = True
- _crs_severity_cache['ts'] = now
- return m
- except Exception:
- return _crs_severity_cache.get('map') or {}
-
-def aggregate_modsec_stats(events):
- series = {}
- rule_counts = {}
- ip_counts = {}
- total = len(events)
- today = week = month = 0
- now = datetime.now().astimezone() # use server local timezone
- debug_samples = 0
- for ev in events:
- b = ev.get('bucket', 'unknown')
- series[b] = series.get(b, 0) + 1
- for rid in (ev.get('rule_ids') or []):
- rule_counts[rid] = rule_counts.get(rid, 0) + 1
- ip = ev.get('src_ip')
- if ip:
- ip_counts[ip] = ip_counts.get(ip, 0) + 1
- # totals by time ranges
- try:
- dt_str = ev.get('datetime') or ev.get('timestamp') or None
- dt = _parse_time(dt_str) if dt_str else None
- if dt:
- dt_local = dt.astimezone(now.tzinfo)
- if debug_samples < 5:
- try:
- diff_days = (now - dt_local).days
- print(f"[modsec] dt_str={dt_str} parsed_utc={dt.isoformat()} parsed_local={dt_local.isoformat()} diff_days={diff_days}", flush=True)
- debug_samples += 1
- except Exception:
- pass
- if dt_local.date() == now.date():
- today += 1
- if (now - dt_local) <= timedelta(days=7):
- week += 1
- if dt_local.year == now.year and dt_local.month == now.month:
- month += 1
- except Exception as e:
- try:
- if debug_samples < 5:
- print(f"[modsec] aggregate error: {e}", flush=True)
- debug_samples += 1
- except Exception:
- pass
- pass
- series_list = sorted([{'bucket': k, 'count': v} for k, v in series.items()], key=lambda x: x['bucket'])
- top_rules = sorted([{'rule_id': k, 'count': v} for k, v in rule_counts.items()], key=lambda x: x['count'], reverse=True)[:10]
- top_ips = sorted([{'ip': k, 'count': v} for k, v in ip_counts.items()], key=lambda x: x['count'], reverse=True)[:10]
- unique_ips = len(ip_counts)
- return {
- 'total': total,
- 'unique_ips': unique_ips,
- 'today': today,
- 'week': week,
- 'month': month,
- 'series': series_list,
- 'top_rules': top_rules,
- 'top_ips': top_ips,
- }
-
-@app.route('/api/modsecurity/stats')
-@login_required
-def api_modsec_stats():
- limit = int(request.args.get('limit', '2000') or '2000')
- events = get_modsec_events(limit=limit)
- stats = aggregate_modsec_stats(events)
- # Hitung jumlah IP terblokir dari blocked_ips.conf
- try:
- blocked_ips_file = os.path.join(os.path.dirname(NGINX_CONF_DIR), 'blocked_ips.conf')
- blocked_ips_count = 0
- if os.path.exists(blocked_ips_file):
- with open(blocked_ips_file, 'r') as f:
- for line in f:
- line = line.strip()
- if line.startswith('deny ') and line.endswith(';'):
- blocked_ips_count += 1
- stats['blocked_ips_count'] = blocked_ips_count
- except Exception:
- stats['blocked_ips_count'] = 0
- return jsonify({'success': True, **stats})
-
-@app.route('/api/modsecurity/attacks')
-@login_required
-def api_modsec_attacks():
- limit = int(request.args.get('limit', '500') or '500')
- since = request.args.get('since')
- until = request.args.get('until')
- ip = request.args.get('ip')
- rule_id = request.args.get('rule_id')
- events = get_modsec_events(limit=limit, since=since, until=until, ip=ip, rule_id=rule_id)
- return jsonify({'success': True, 'events': events, 'count': len(events)})
-
-# === ModSecurity exclusions management ===
-def ensure_modsec_exclusions_include() -> bool:
- try:
- inc_line = 'Include /etc/nginx/modsec/exclusions.conf'
- subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'sh','-lc','touch /etc/nginx/modsec/exclusions.conf'],capture_output=True, text=True)
- chk = subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'sh','-lc',f"grep -qF '{inc_line}' /etc/nginx/modsec/main.conf || echo MISSING"],capture_output=True, text=True)
- if 'MISSING' in (chk.stdout or ''):
- subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'sh','-lc',f"echo '{inc_line}' >> /etc/nginx/modsec/main.conf"],capture_output=True, text=True)
- return True
- except Exception:
- return False
-
-def reload_nginx_quiet() -> bool:
- try:
- t = subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'nginx','-t'],capture_output=True,text=True)
- if t.returncode != 0:
- return False
- r = subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'nginx','-s','reload'],capture_output=True,text=True)
- return r.returncode == 0
- except Exception:
- return False
-
-@app.route('/api/modsecurity/exclusions', methods=['GET'])
-@login_required
-def api_modsec_exclusions():
- try:
- ensure_modsec_exclusions_include()
- res = subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'sh','-lc','cat /etc/nginx/modsec/exclusions.conf || true'],capture_output=True,text=True)
- lines = (res.stdout or '').splitlines()
- ids = []
- for ln in lines:
- ln = ln.strip()
- if ln.lower().startswith('secruleremovebyid'):
- parts = ln.split()
- if len(parts)>=2:
- ids.append(parts[1])
- return jsonify({'success': True, 'exclusions': ids, 'raw': lines})
- except Exception as e:
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/modsecurity/exclude-rule', methods=['POST','DELETE'])
-@login_required
-def api_modsec_exclude_rule():
- try:
- data = request.get_json(silent=True) or {}
- rule_id = str(data.get('rule_id') or '').strip()
- scope = str(data.get('scope') or 'global').strip().lower()
- domain = str(data.get('domain') or '').strip()
- if not rule_id.isdigit():
- return jsonify({'success': False, 'error': 'Invalid rule_id'}), 400
- if request.method == 'POST':
- if scope == 'global':
- ensure_modsec_exclusions_include()
- cmd = f"grep -q 'SecRuleRemoveById {rule_id}' /etc/nginx/modsec/exclusions.conf || echo 'SecRuleRemoveById {rule_id}' >> /etc/nginx/modsec/exclusions.conf"
- subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'sh','-lc',cmd],capture_output=True,text=True)
- ok = reload_nginx_quiet()
- return jsonify({'success': ok})
- elif scope == 'domain' and domain:
- conf_disabled = os.path.join(NGINX_CONF_DIR, f"{domain}.conf.disabled")
- conf_enabled = os.path.join(NGINX_CONF_DIR, f"{domain}.conf")
- target_path = conf_enabled if os.path.exists(conf_enabled) else conf_disabled
- if not os.path.exists(target_path):
- return jsonify({'success': False, 'error': 'Domain config not found'}), 404
- with open(target_path,'r') as f:
- content = f.read()
- if f"SecRuleRemoveById {rule_id}" in content or f"modsecurity_rules 'SecRuleRemoveById {rule_id}'" in content:
- ok = reload_nginx_quiet()
- return jsonify({'success': ok})
- new_lines = []
- inserted = False
- for line in content.splitlines():
- new_lines.append(line)
- if (not inserted) and line.strip().startswith('server') and '{' in line:
- new_lines.append(f" modsecurity_rules 'SecRuleRemoveById {rule_id}'")
- inserted = True
- if not inserted:
- new_lines.insert(0, f"modsecurity_rules 'SecRuleRemoveById {rule_id}'")
- with open(target_path,'w') as f:
- f.write('\n'.join(new_lines))
- ok = reload_nginx_quiet()
- return jsonify({'success': ok})
- else:
- return jsonify({'success': False, 'error': 'Invalid scope/domain'}), 400
- else:
- if scope == 'global':
- ensure_modsec_exclusions_include()
- cmd = f"sed -i '/SecRuleRemoveById {rule_id}/d' /etc/nginx/modsec/exclusions.conf || true"
- subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'sh','-lc',cmd],capture_output=True,text=True)
- ok = reload_nginx_quiet()
- return jsonify({'success': ok})
- elif scope == 'domain' and domain:
- conf_disabled = os.path.join(NGINX_CONF_DIR, f"{domain}.conf.disabled")
- conf_enabled = os.path.join(NGINX_CONF_DIR, f"{domain}.conf")
- target_path = conf_enabled if os.path.exists(conf_enabled) else conf_disabled
- if not os.path.exists(target_path):
- return jsonify({'success': False, 'error': 'Domain config not found'}), 404
- with open(target_path,'r') as f:
- content = f.read()
- content = content.replace(f"modsecurity_rules 'SecRuleRemoveById {rule_id}'\n", '').replace(f"modsecurity_rules 'SecRuleRemoveById {rule_id}'", '')
- with open(target_path,'w') as f:
- f.write(content)
- ok = reload_nginx_quiet()
- return jsonify({'success': ok})
- else:
- return jsonify({'success': False, 'error': 'Invalid scope/domain'}), 400
- except Exception as e:
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/modsecurity/top-rules')
-@login_required
-def api_modsec_top_rules():
- limit = int(request.args.get('limit', '2000') or '2000')
- events = get_modsec_events(limit=limit)
- stats = aggregate_modsec_stats(events)
- return jsonify({'success': True, 'top_rules': stats['top_rules']})
-
-@app.route('/api/modsecurity/top-ips')
-@login_required
-def api_modsec_top_ips():
- limit = int(request.args.get('limit', '2000') or '2000')
- events = get_modsec_events(limit=limit)
- stats = aggregate_modsec_stats(events)
- return jsonify({'success': True, 'top_ips': stats['top_ips']})
-
-@app.route('/modsecurity-dashboard')
-@login_required
-def modsecurity_dashboard_page():
- nginx_status = check_nginx_status()
- return render_template('modsecurity_dashboard.html', nginx_status=nginx_status)
-
-@app.route('/api/modsecurity-logs/')
-@login_required
-def api_modsec_logs_by_domain(domain):
- """Get ModSecurity logs filtered by domain with date range support"""
- try:
- if not is_valid_site_name(domain):
- return jsonify({'success': False, 'error': 'Invalid domain name'}), 400
-
- # Get date range parameters
- start_date = request.args.get('start_date')
- end_date = request.args.get('end_date')
-
- # Convert date strings to datetime objects
- since = None
- until = None
-
- if start_date:
- try:
- since = datetime.fromisoformat(start_date).replace(tzinfo=timezone.utc)
- except ValueError:
- return jsonify({'success': False, 'error': 'Invalid start_date format'}), 400
-
- if end_date:
- try:
- # Add one day to include the entire end date
- until = datetime.fromisoformat(end_date).replace(tzinfo=timezone.utc) + timedelta(days=1)
- except ValueError:
- return jsonify({'success': False, 'error': 'Invalid end_date format'}), 400
-
- # Get events with domain filtering
- events = get_modsec_events_by_domain(domain, since=since, until=until)
-
- # Format logs for frontend
- logs = []
- for event in events:
- # Get the most relevant rule ID and message
- rule_ids = event.get('rule_ids', [])
- rule_id = rule_ids[0] if rule_ids else None
-
- # Extract message from event or use default
- message = 'ModSecurity rule triggered'
- if event.get('severity'):
- message = f"{event['severity']} severity rule triggered"
-
- logs.append({
- 'time': event.get('bucket', 'unknown'),
- 'ip': event.get('src_ip', 'unknown'),
- 'method': event.get('method', 'unknown'),
- 'uri': event.get('uri', 'unknown'),
- 'rule_id': rule_id,
- 'severity': event.get('severity', 'unknown'),
- 'action': 'blocked' if event.get('status') and str(event.get('status')).startswith('4') else 'detected',
- 'message': message,
- 'domain': event.get('domain', domain)
- })
-
- return jsonify({
- 'success': True,
- 'domain': domain,
- 'logs': logs,
- 'count': len(logs),
- 'date_range': {
- 'start_date': start_date,
- 'end_date': end_date
- }
- })
- except Exception as e:
- logger.exception("Error in api_modsec_logs_by_domain")
- return jsonify({'success': False, 'error': 'Failed to fetch ModSecurity logs', 'details': str(e)}), 500
-
-@app.route('/api/block-ip', methods=['POST'])
-@login_required
-def api_block_ip():
- """Block an IP address using nginx configuration"""
- try:
- data = request.get_json()
- if not data or 'ip' not in data:
- return jsonify({'success': False, 'error': 'IP address is required'}), 400
-
- ip = data['ip'].strip()
-
- # Validate IP address format
- import ipaddress
- try:
- ipaddress.ip_address(ip)
- except ValueError:
- return jsonify({'success': False, 'error': 'Invalid IP address format'}), 400
-
- # Path to blocked IPs file
- blocked_ips_file = os.path.join(os.path.dirname(NGINX_CONF_DIR), 'blocked_ips.conf')
-
- # Read existing blocked IPs
- blocked_ips = set()
- if os.path.exists(blocked_ips_file):
- with open(blocked_ips_file, 'r') as f:
- for line in f:
- line = line.strip()
- if line.startswith('deny ') and line.endswith(';'):
- blocked_ip = line[5:-1].strip()
- blocked_ips.add(blocked_ip)
-
- # Check if IP is already blocked
- if ip in blocked_ips:
- return jsonify({'success': True, 'message': f'IP {ip} is already blocked'})
-
- # Add IP to blocked list
- blocked_ips.add(ip)
-
- # Write updated blocked IPs file
- with open(blocked_ips_file, 'w') as f:
- f.write('# Blocked IPs for ModSecurity\n')
- for blocked_ip in sorted(blocked_ips):
- f.write(f'deny {blocked_ip};\n')
-
- # Ensure server configs include blocked_ips.conf
- update_server_configs_with_blocked_ips()
-
- # Reload nginx configuration
- reload_result = subprocess.run(['docker', 'exec', 'mlite_nginx', 'nginx', '-s', 'reload'],
- capture_output=True, text=True, timeout=30)
-
- if reload_result.returncode != 0:
- return jsonify({'success': False, 'error': f'Failed to reload nginx: {reload_result.stderr}'}), 500
-
- return jsonify({'success': True, 'message': f'IP {ip} has been blocked successfully'})
-
- except Exception as e:
- logger.error(f"Error blocking IP: {e}")
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/unblock-ip', methods=['POST'])
-@login_required
-def api_unblock_ip():
- """Unblock an IP address from nginx configuration"""
- try:
- data = request.get_json()
- if not data or 'ip' not in data:
- return jsonify({'success': False, 'error': 'IP address is required'}), 400
-
- ip = data['ip'].strip()
-
- # Validate IP address format
- import ipaddress
- try:
- ipaddress.ip_address(ip)
- except ValueError:
- return jsonify({'success': False, 'error': 'Invalid IP address format'}), 400
-
- # Path to blocked IPs file
- blocked_ips_file = os.path.join(os.path.dirname(NGINX_CONF_DIR), 'blocked_ips.conf')
-
- # Read existing blocked IPs
- blocked_ips = set()
- if os.path.exists(blocked_ips_file):
- with open(blocked_ips_file, 'r') as f:
- for line in f:
- line = line.strip()
- if line.startswith('deny ') and line.endswith(';'):
- blocked_ip = line[5:-1].strip()
- blocked_ips.add(blocked_ip)
-
- # Check if IP is blocked
- if ip not in blocked_ips:
- return jsonify({'success': True, 'message': f'IP {ip} is not blocked'})
-
- # Remove IP from blocked list
- blocked_ips.discard(ip)
-
- # Write updated blocked IPs file
- with open(blocked_ips_file, 'w') as f:
- f.write('# Blocked IPs for ModSecurity\n')
- for blocked_ip in sorted(blocked_ips):
- f.write(f'deny {blocked_ip};\n')
-
- # Ensure server configs include blocked_ips.conf
- update_server_configs_with_blocked_ips()
-
- # Reload nginx configuration
- reload_result = subprocess.run(['docker', 'exec', 'mlite_nginx', 'nginx', '-s', 'reload'],
- capture_output=True, text=True, timeout=30)
-
- if reload_result.returncode != 0:
- return jsonify({'success': False, 'error': f'Failed to reload nginx: {reload_result.stderr}'}), 500
-
- return jsonify({'success': True, 'message': f'IP {ip} has been unblocked successfully'})
-
- except Exception as e:
- logger.error(f"Error unblocking IP: {e}")
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/blocked-ips', methods=['GET'])
-@login_required
-def api_blocked_ips():
- """Get list of blocked IP addresses"""
- try:
- # Path to blocked IPs file
- blocked_ips_file = os.path.join(os.path.dirname(NGINX_CONF_DIR), 'blocked_ips.conf')
-
- # Read existing blocked IPs
- blocked_ips = []
- if os.path.exists(blocked_ips_file):
- with open(blocked_ips_file, 'r') as f:
- for line in f:
- line = line.strip()
- if line.startswith('deny ') and line.endswith(';'):
- blocked_ip = line[5:-1].strip()
- blocked_ips.append(blocked_ip)
-
- return jsonify({'success': True, 'blocked_ips': sorted(blocked_ips)})
-
- except Exception as e:
- logger.error(f"Error getting blocked IPs: {e}")
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/restart-panel', methods=['POST'])
-@login_required
-def api_restart_panel():
- """Restart or recreate the panel container via Docker Compose"""
- try:
- data = request.get_json(silent=True) or {}
- mode = str(data.get('mode') or 'restart').lower()
- compose_file = '/workspace/docker/docker-compose.yml'
-
- if mode == 'recreate':
- cmd = ['docker', 'compose', '-f', compose_file, 'up', '-d', '--no-deps', '--force-recreate', 'panel']
- action = 'recreate'
- else:
- cmd = ['docker', 'compose', '-f', compose_file, 'restart', 'panel']
- action = 'restart'
-
- # Start asynchronously to avoid killing the current request mid-flight
- subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
- logger.info(f"Initiated panel {action} via docker compose")
- return jsonify({'success': True, 'message': f'Panel {action} initiated'})
- except Exception as e:
- logger.exception(f"Error initiating panel restart: {e}")
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/cron')
-@login_required
-def cron_page():
- return render_template('cron.html')
-
-def _read_cron_file() -> str:
- try:
- res = subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'sh','-lc','cat /etc/cron.d/mlite'],capture_output=True,text=True,timeout=10)
- if res.returncode == 0:
- return res.stdout
- except Exception:
- pass
- return ''
-
-def _parse_cron_settings(cron_text: str) -> dict:
- data = {'certbot_enabled': False,'certbot_time': '02:00','mysql_enabled': False,'mysql_time': '03:00','mysql_user': db_config.get('user','root'),'mysql_password': db_config.get('password',''),'mysql_database': db_config.get('database','mlite_db'),'web_enabled': False,'web_time': '03:10'}
- for line in (cron_text or '').splitlines():
- s = line.strip()
- if not s or s.startswith('#'):
- continue
- parts = s.split()
- if len(parts) < 7:
- continue
- mm = parts[0]
- hh = parts[1]
- cmd = ' '.join(parts[6:])
- if 'certbot renew' in cmd:
- data['certbot_enabled'] = True
- data['certbot_time'] = f"{hh.zfill(2)}:{mm.zfill(2)}"
- elif 'mysqldump' in cmd:
- data['mysql_enabled'] = True
- data['mysql_time'] = f"{hh.zfill(2)}:{mm.zfill(2)}"
- try:
- for token in cmd.split():
- if token.startswith('-u') and len(token) > 2:
- data['mysql_user'] = token[2:].strip().strip('"').strip("'")
- if token.startswith('-p') and len(token) > 2:
- data['mysql_password'] = token[2:].strip().strip('"').strip("'")
- import re
- if '--all-databases' in cmd:
- data['mysql_database'] = ''
- else:
- m = re.search(r"mysqldump[^\n>]*?\s([A-Za-z0-9_\-]+)\s*(?:>|$)", cmd)
- if m:
- db_tok = m.group(1)
- if db_tok and not db_tok.startswith('-'):
- data['mysql_database'] = db_tok
- except Exception:
- pass
- elif 'tar ' in cmd and '/backups/web/' in cmd:
- data['web_enabled'] = True
- data['web_time'] = f"{hh.zfill(2)}:{mm.zfill(2)}"
- return data
-
-def _build_cron(settings: dict) -> str:
- shell = 'SHELL=/bin/sh\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n'
- lines = [shell]
- def to_fields(t):
- hh, mm = (t or '00:00').split(':')
- return f"{int(mm)} {int(hh)} * * *"
- if settings.get('certbot_enabled'):
- t = settings.get('certbot_time','02:00')
- lines.append(f"{to_fields(t)} root certbot renew --quiet && nginx -s reload # mlite:certbot")
- if settings.get('mysql_enabled'):
- t = settings.get('mysql_time','03:00')
- u = settings.get('mysql_user', db_config.get('user','root'))
- p = settings.get('mysql_password', db_config.get('password',''))
- d = settings.get('mysql_database', db_config.get('database','mlite_db'))
- pass_arg = f" -p{p}" if p else ""
- lines.append(f"{to_fields(t)} root mkdir -p /backups/mysql && mysqldump -h mysql -P 3306 -u{u}{pass_arg} {d} > /backups/mysql/mysql_$(date +%Y%m%d_%H%M%S).sql # mlite:mysql")
- if settings.get('web_enabled'):
- t = settings.get('web_time','03:10')
- lines.append(f"{to_fields(t)} root mkdir -p /backups/web && tar --exclude=./docker/backups -czf /backups/web/web_$(date +%Y%m%d_%H%M%S).tar.gz -C /var/www/public . # mlite:web")
- return '\n'.join(lines) + '\n'
-
-def _write_cron(cron_text: str) -> tuple[bool,str]:
- try:
- cmd = f"mkdir -p /backups/cron && cat > /etc/cron.d/mlite <<'EOF'\n{cron_text}\nEOF && chmod 0644 /etc/cron.d/mlite"
- res = subprocess.run(['docker','exec','-i',NGINX_CONTAINER_NAME,'sh','-lc',cmd],capture_output=True,text=True,timeout=15)
- if res.returncode == 0:
- return True, 'ok'
- return False, res.stderr or res.stdout
- except Exception as e:
- return False, str(e)
-
-def get_cron_jobs_path():
- base = os.environ.get('HOST_PROJECT_DIR') or '/workspace'
- p = os.path.join(base, 'docker', 'cron', 'jobs.json')
- try:
- os.makedirs(os.path.dirname(p), exist_ok=True)
- except Exception:
- pass
- return p
-
-def load_cron_jobs() -> list:
- try:
- p = get_cron_jobs_path()
- if os.path.exists(p):
- with open(p, 'r') as f:
- return json.load(f) or []
- except Exception:
- pass
- return []
-
-def save_cron_jobs(jobs: list) -> bool:
- try:
- with open(get_cron_jobs_path(), 'w') as f:
- json.dump(jobs or [], f)
- return True
- except Exception:
- return False
-
-def _job_to_cron(job: dict) -> str:
- user = (job.get('user') or 'root').strip() or 'root'
- script = (job.get('script') or '').strip()
- jid = job.get('id') or 'job'
- log_path = f"/backups/cron/{jid}.log"
- schedule = job.get('schedule', {})
- t = (schedule.get('time') or '00:00')
- hh, mm = (t.split(':') + ['00'])[:2]
- kind = (schedule.get('type') or 'daily')
- if kind == 'daily':
- fields = f"{int(mm)} {int(hh)} * * *"
- elif kind == 'every_minutes':
- n = max(1, int(schedule.get('every', 5)))
- fields = f"*/{n} * * * *"
- elif kind == 'every_hours':
- n = max(1, int(schedule.get('every', 1)))
- fields = f"0 */{n} * * *"
- else:
- fields = f"{int(mm)} {int(hh)} * * *"
- return f"{fields} {user} bash -lc '{script}' >> {log_path} 2>&1"
-
-def _build_full_cron(settings: dict, jobs: list) -> str:
- base = _build_cron(settings)
- extra = []
- for j in jobs:
- if j.get('enabled'):
- extra.append(_job_to_cron(j))
- return base + ("\n".join(extra) + ("\n" if extra else ""))
-
-def _job_log_host_path(jid: str) -> str:
- base = os.environ.get('HOST_PROJECT_DIR') or '/workspace'
- p = os.path.join(base, 'docker', 'backups', 'cron', f"{jid}.log")
- try:
- os.makedirs(os.path.dirname(p), exist_ok=True)
- except Exception:
- pass
- return p
-
-@app.route('/api/cron/jobs', methods=['GET'])
-@login_required
-def api_cron_jobs_list():
- jobs = load_cron_jobs()
- enriched = []
- for j in jobs:
- jid = j.get('id')
- lp = _job_log_host_path(jid)
- last = None
- try:
- if os.path.exists(lp):
- last = datetime.fromtimestamp(os.path.getmtime(lp)).isoformat()
- except Exception:
- last = None
- enriched.append({**j, 'last_exec': last})
- return jsonify({'success': True, 'jobs': enriched})
-
-@app.route('/api/cron/jobs', methods=['POST'])
-@login_required
-def api_cron_jobs_add():
- data = request.get_json(silent=True) or {}
- job = {
- 'id': data.get('id') or str(int(datetime.now().timestamp()*1000)),
- 'name': data.get('name') or 'Task',
- 'enabled': bool(data.get('enabled', True)),
- 'user': data.get('user') or 'root',
- 'task_type': data.get('task_type') or 'shell',
- 'script': data.get('script') or '',
- 'schedule': data.get('schedule') or {'type':'daily','time':'01:00'}
- }
- jobs = load_cron_jobs()
- jobs.append(job)
- save_cron_jobs(jobs)
- settings = _parse_cron_settings(_read_cron_file())
- text = _build_full_cron(settings, jobs)
- ok, err = _write_cron(text)
- if not ok:
- return jsonify({'success': False, 'error': err}), 500
- return jsonify({'success': True, 'job': job})
-
-@app.route('/api/cron/jobs/', methods=['PUT'])
-@login_required
-def api_cron_jobs_update(jid):
- data = request.get_json(silent=True) or {}
- jobs = load_cron_jobs()
- found = False
- for i, j in enumerate(jobs):
- if str(j.get('id')) == str(jid):
- jobs[i] = {**j, **data, 'id': j['id']}
- found = True
- break
- if not found:
- return jsonify({'success': False, 'error': 'Job not found'}), 404
- save_cron_jobs(jobs)
- settings = _parse_cron_settings(_read_cron_file())
- text = _build_full_cron(settings, jobs)
- ok, err = _write_cron(text)
- if not ok:
- return jsonify({'success': False, 'error': err}), 500
- return jsonify({'success': True})
-
-@app.route('/api/cron/jobs/', methods=['DELETE'])
-@login_required
-def api_cron_jobs_delete(jid):
- jobs = [j for j in load_cron_jobs() if str(j.get('id')) != str(jid)]
- save_cron_jobs(jobs)
- settings = _parse_cron_settings(_read_cron_file())
- text = _build_full_cron(settings, jobs)
- ok, err = _write_cron(text)
- if not ok:
- return jsonify({'success': False, 'error': err}), 500
- return jsonify({'success': True})
-
-@app.route('/api/cron/jobs//execute', methods=['POST'])
-@login_required
-def api_cron_jobs_execute(jid):
- jobs = load_cron_jobs()
- job = next((j for j in jobs if str(j.get('id')) == str(jid)), None)
- if not job:
- return jsonify({'success': False, 'error': 'Job not found'}), 404
- lp = _job_log_host_path(jid)
- raw_script = (job.get('script') or '').strip()
- if not raw_script:
- return jsonify({'success': False, 'error': 'Empty script'}), 400
- script = raw_script.replace("'","'\\''")
- setup = "mkdir -p /backups/cron /backups/mysql /backups/web"
- cmd = f"{setup} && sh -lc '{script}' >> /backups/cron/{jid}.log 2>&1"
- res = subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'sh','-lc',cmd],capture_output=True,text=True,timeout=120)
- if res.returncode != 0:
- return jsonify({'success': False, 'error': (res.stderr or res.stdout or 'command failed'), 'cmd': cmd}), 500
- try:
- # touch host log to update mtime if needed
- with open(lp, 'a') as f:
- f.write('')
- except Exception:
- pass
- return jsonify({'success': True})
-
-@app.route('/api/cron/jobs//log', methods=['GET'])
-@login_required
-def api_cron_jobs_log(jid):
- try:
- lp = _job_log_host_path(jid)
- content = ''
- if os.path.exists(lp):
- with open(lp, 'r') as f:
- content = f.read()
- else:
- # try read in container
- res = subprocess.run(['docker','exec',NGINX_CONTAINER_NAME,'sh','-lc',f'cat /backups/cron/{jid}.log || true'],capture_output=True,text=True,timeout=10)
- content = res.stdout
- return jsonify({'success': True, 'log': content})
- except Exception as e:
- return jsonify({'success': False, 'error': str(e)}), 500
-
-@app.route('/api/cron/settings', methods=['GET'])
-@login_required
-def api_cron_get():
- cur = _read_cron_file()
- data = _parse_cron_settings(cur)
- return jsonify({'success': True, 'settings': data})
-
-@app.route('/api/cron/settings', methods=['POST'])
-@login_required
-def api_cron_set():
- payload = request.get_json(silent=True) or {}
- cur = _parse_cron_settings(_read_cron_file())
- cur.update({
- 'certbot_enabled': bool(payload.get('certbot_enabled', cur.get('certbot_enabled'))),
- 'certbot_time': payload.get('certbot_time', cur.get('certbot_time')),
- 'mysql_enabled': bool(payload.get('mysql_enabled', cur.get('mysql_enabled'))),
- 'mysql_time': payload.get('mysql_time', cur.get('mysql_time')),
- 'mysql_user': payload.get('mysql_user', cur.get('mysql_user')),
- 'mysql_password': payload.get('mysql_password', cur.get('mysql_password')),
- 'mysql_database': payload.get('mysql_database', cur.get('mysql_database')),
- 'web_enabled': bool(payload.get('web_enabled', cur.get('web_enabled'))),
- 'web_time': payload.get('web_time', cur.get('web_time'))
- })
- text = _build_cron(cur)
- ok, err = _write_cron(text)
- if not ok:
- return jsonify({'success': False, 'error': err}), 500
- return jsonify({'success': True, 'settings': cur})
-
-def get_modsec_events_by_domain(domain, limit=1000, since=None, until=None):
- """Get ModSecurity events filtered by specific domain"""
- # Validate domain input to avoid NoneType errors
- if not isinstance(domain, str) or not domain.strip():
- logger.warning(f"get_modsec_events_by_domain called with invalid domain: {domain!r}")
- return []
- target_domain = domain.strip().lower()
-
- lines = read_modsec_audit_lines(max_lines=max(1000, limit * 10))
- events = []
-
- for ln in lines:
- ln = ln.strip()
- if not ln:
- continue
- try:
- obj = json.loads(ln)
- except Exception:
- continue
-
- ev = _parse_modsec_event(obj)
- if not isinstance(ev, dict):
- continue
-
- # Filter by domain - case insensitive comparison with null-safety
- event_domain = (ev.get('domain') or '').lower()
-
- # Check if this event belongs to the target domain
- if not event_domain or target_domain not in event_domain:
- continue
-
- # Apply date range filter
- def in_range(ev):
- dt = None
- if ev.get('datetime'):
- try:
- dt = datetime.fromisoformat(ev['datetime'])
- except Exception:
- dt = None
- ok = True
- if since:
- ok = ok and (dt is None or dt >= since)
- if until:
- ok = ok and (dt is None or dt <= until)
- return ok
-
- if in_range(ev):
- events.append(ev)
-
- # Limit results and return most recent first
- events = events[-limit:] if limit and len(events) > limit else events
- return events
-
-def ensure_blocked_ips_config():
- """Ensure blocked IPs configuration is included in nginx main config"""
- try:
- # Path to main nginx config
- main_config_path = os.path.join(os.path.dirname(NGINX_CONF_DIR), 'nginx.conf')
- include_line = f" include /etc/nginx/blocked_ips.conf;"
-
- # Check if main config exists and read it
- if os.path.exists(main_config_path):
- with open(main_config_path, 'r') as f:
- config_content = f.read()
-
- # Check if blocked_ips.conf is already included
- if "include /etc/nginx/blocked_ips.conf" in config_content:
- return True # Already included
-
- # Add include directive to http block
- if 'http {' in config_content:
- # Find the http block and add include directive
- lines = config_content.split('\n')
- new_lines = []
- in_http_block = False
- http_brace_count = 0
-
- for line in lines:
- new_lines.append(line)
-
- if 'http {' in line:
- in_http_block = True
- http_brace_count = 1
- elif in_http_block:
- if '{' in line:
- http_brace_count += line.count('{')
- if '}' in line:
- http_brace_count -= line.count('}')
-
- # Add include directive before the closing brace of http block
- if http_brace_count == 1 and line.strip() == '}':
- # Remove the closing brace we just added
- new_lines.pop()
- # Add our include directive
- new_lines.append(include_line)
- # Add the closing brace back
- new_lines.append(line)
- in_http_block = False
-
- # Write updated config
- with open(main_config_path, 'w') as f:
- f.write('\n'.join(new_lines))
-
- return True
-
- # If main config doesn't exist or we couldn't modify it,
- # we'll rely on per-server include directives
- return False
-
- except Exception as e:
- logger.error(f"Error ensuring blocked IPs config: {e}")
- return False
-
-def update_server_configs_with_blocked_ips():
- """Update all server configurations to include blocked_ips.conf"""
- try:
- blocked_ips_file = os.path.join(os.path.dirname(NGINX_CONF_DIR), 'blocked_ips.conf')
- include_directive = f" include /etc/nginx/blocked_ips.conf;"
-
- # Process all .conf files in nginx conf directory
- for filename in os.listdir(NGINX_CONF_DIR):
- if filename.endswith('.conf') and filename != 'blocked_ips.conf':
- config_path = os.path.join(NGINX_CONF_DIR, filename)
-
- try:
- with open(config_path, 'r') as f:
- content = f.read()
-
- # Update or add blocked_ips include
- if 'include /etc/nginx/conf.d/blocked_ips.conf' in content:
- content = content.replace('/etc/nginx/conf.d/blocked_ips.conf', '/etc/nginx/blocked_ips.conf')
- with open(config_path, 'w') as f:
- f.write(content)
- logger.info(f"Replaced old include path in {filename}")
- continue
-
- if 'include /etc/nginx/blocked_ips.conf' in content:
- continue
-
- # Add include directive to server block
- if 'server {' in content:
- lines = content.split('\n')
- new_lines = []
- in_server_block = False
- server_brace_count = 0
- include_added = False
-
- for line in lines:
- new_lines.append(line)
-
- if 'server {' in line and not include_added:
- in_server_block = True
- server_brace_count = 1
- elif in_server_block and not include_added:
- if '{' in line:
- server_brace_count += line.count('{')
- if '}' in line:
- server_brace_count -= line.count('}')
-
- # Add include directive after the opening brace of server block
- if server_brace_count >= 1 and line.strip() == '':
- # Find a good place to add the include (after server_name or listen)
- prev_lines = [l.strip() for l in new_lines[-5:] if l.strip()]
- if any('server_name' in l or 'listen' in l for l in prev_lines):
- new_lines.append(include_directive)
- include_added = True
-
- # Write updated config
- with open(config_path, 'w') as f:
- f.write('\n'.join(new_lines))
-
- logger.info(f"Updated {filename} with blocked_ips include")
-
- except Exception as e:
- logger.error(f"Error updating {filename}: {e}")
- continue
-
- return True
-
- except Exception as e:
- logger.error(f"Error updating server configs: {e}")
- return False
-
-if __name__ == '__main__':
- # Ensure nginx conf directory exists
- os.makedirs(NGINX_CONF_DIR, exist_ok=True)
-
- # Ensure blocked IPs configuration is included in nginx
- ensure_blocked_ips_config()
-
- port = int(os.environ.get('PORT') or os.environ.get('FLASK_RUN_PORT') or 5000)
- app.run(host='0.0.0.0', port=port, debug=True)
diff --git a/docker/panel/requirements.txt b/docker/panel/requirements.txt
deleted file mode 100644
index 7ff66c80b..000000000
--- a/docker/panel/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-Flask==2.3.3
-Werkzeug==2.3.7
-requests==2.31.0
-PyYAML==6.0.2
\ No newline at end of file
diff --git a/docker/panel/static/logo.png b/docker/panel/static/logo.png
deleted file mode 100644
index 7522e7b72..000000000
Binary files a/docker/panel/static/logo.png and /dev/null differ
diff --git a/docker/panel/static/mysql.js b/docker/panel/static/mysql.js
deleted file mode 100644
index a46b88c69..000000000
--- a/docker/panel/static/mysql.js
+++ /dev/null
@@ -1,1876 +0,0 @@
-// MySQL Management JavaScript Functions
-
-// Global variables
-let currentDatabase = '';
-let currentTable = '';
-let databases = [];
-let users = [];
-
-// Tab switching function
-function switchTab(tabName) {
- // Hide all tab panels
- document.querySelectorAll('.tab-panel').forEach(panel => {
- panel.classList.add('hidden');
- });
-
- // Remove active class from all tab buttons
- document.querySelectorAll('.tab-button').forEach(button => {
- button.classList.remove('active', 'border-blue-500', 'text-blue-600', 'dark:text-blue-400');
- button.classList.add('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'dark:text-gray-400', 'dark:hover:text-gray-300');
- });
-
- // Show selected tab panel
- document.getElementById(tabName + '-tab').classList.remove('hidden');
-
- // Add active class to selected tab button
- const activeTab = document.getElementById('tab-' + tabName);
- activeTab.classList.add('active', 'border-blue-500', 'text-blue-600', 'dark:text-blue-400');
- activeTab.classList.remove('border-transparent', 'text-gray-500', 'hover:text-gray-700', 'dark:text-gray-400', 'dark:hover:text-gray-300');
-
- // Load data for the tab if needed
- switch(tabName) {
- case 'databases':
- loadDatabases();
- break;
- case 'tables':
- loadTables();
- break;
- case 'users':
- loadUsers();
- break;
- case 'replication':
- loadReplicationStatus();
- break;
- case 'logs':
- loadErrorLogs();
- break;
- }
-}
-
-// Modal functions
-function openCreateDatabaseModal() {
- document.getElementById('createDatabaseModal').classList.remove('hidden');
- document.getElementById('createDatabaseModal').classList.add('flex');
- document.getElementById('newDatabaseName').focus();
-}
-
-function closeCreateDatabaseModal() {
- document.getElementById('createDatabaseModal').classList.add('hidden');
- document.getElementById('createDatabaseModal').classList.remove('flex');
- document.getElementById('newDatabaseName').value = '';
-}
-
-function openCreateUserModal() {
- document.getElementById('createUserModal').classList.remove('hidden');
- document.getElementById('createUserModal').classList.add('flex');
- document.getElementById('newUsername').focus();
-}
-
-function closeCreateUserModal() {
- document.getElementById('createUserModal').classList.add('hidden');
- document.getElementById('createUserModal').classList.remove('flex');
- document.getElementById('newUsername').value = '';
- document.getElementById('newUserPassword').value = '';
- document.getElementById('newUserHost').value = '%';
- document.getElementById('newUserPrivileges').value = 'ALL PRIVILEGES';
-}
-
-function loadCurrentRootPassword() {
- const currentLabel = document.getElementById('currentRootPassword');
- if (!currentLabel) return;
- currentLabel.textContent = '(loading...)';
- fetch('/api/mysql/get-root-password')
- .then(async (response) => {
- const text = await response.text();
- let data;
- try { data = text ? JSON.parse(text) : null; } catch { throw new Error('Invalid JSON response'); }
- if (!response.ok) throw new Error(data?.error || 'Failed to get root password');
- return data;
- })
- .then(data => {
- const pwd = data?.password || '';
- if (pwd) {
- // Obfuscate display but still set input value
- const obfuscated = '*'.repeat(Math.min(pwd.length, 12));
- currentLabel.textContent = obfuscated;
- const input = document.getElementById('rootPassword');
- if (input) input.value = pwd;
- } else {
- currentLabel.textContent = '(empty)';
- }
- })
- .catch(err => {
- console.error('Error getting root password:', err);
- currentLabel.textContent = '(error)';
- });
-}
-
-function openSetRootPasswordModal() {
- document.getElementById('setRootPasswordModal').classList.remove('hidden');
- document.getElementById('setRootPasswordModal').classList.add('flex');
- loadCurrentRootPassword();
- const input = document.getElementById('rootPassword');
- if (input) input.focus();
- const toggleBtn = document.getElementById('toggleRootPasswordBtn');
- if (toggleBtn) {
- toggleBtn.onclick = function() {
- const inp = document.getElementById('rootPassword');
- if (!inp) return;
- const icon = this.querySelector('i');
- const toText = inp.type === 'password';
- inp.type = toText ? 'text' : 'password';
- if (icon) icon.className = toText ? 'fas fa-eye-slash' : 'fas fa-eye';
- };
- }
-}
-
-function closeSetRootPasswordModal() {
- document.getElementById('setRootPasswordModal').classList.add('hidden');
- document.getElementById('setRootPasswordModal').classList.remove('flex');
- document.getElementById('rootPassword').value = '';
- const currentLabel = document.getElementById('currentRootPassword');
- if (currentLabel) currentLabel.textContent = '';
- const toggleBtn = document.getElementById('toggleRootPasswordBtn');
- if (toggleBtn) {
- const icon = toggleBtn.querySelector('i');
- const inp = document.getElementById('rootPassword');
- if (inp) inp.type = 'password';
- if (icon) icon.className = 'fas fa-eye';
- }
-}
-
-function openEditConfigModal() {
- document.getElementById('editConfigModal').classList.remove('hidden');
- document.getElementById('editConfigModal').classList.add('flex');
- loadMySQLConfig();
-}
-
-function closeEditConfigModal() {
- document.getElementById('editConfigModal').classList.add('hidden');
- document.getElementById('editConfigModal').classList.remove('flex');
-}
-
-// MySQL Info functions
-function getMySQLInfo() {
- fetch('/api/mysql-info')
- .then(response => {
- if (!response.ok) {
- throw new Error('Failed to get MySQL info');
- }
- return response.json();
- })
- .then(data => {
- const content = document.getElementById('mysqlInfoContent');
- const statusBadge = document.getElementById('mysqlStatusBadge');
-
- const version = data?.version?.version ?? data?.version ?? 'N/A';
- const uptime = data?.status?.Uptime ?? 'N/A';
- const connections = data?.status?.Threads_connected ?? 'N/A';
- const queries = data?.status?.Questions ?? 'N/A';
-
- content.innerHTML = `
-
-
Version: ${version}
-
Uptime: ${uptime}
-
Connections: ${connections}
-
Queries: ${queries}
-
- `;
-
- statusBadge.innerHTML = ' Connected';
- statusBadge.className = 'px-3 py-1 bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 rounded-full text-sm';
- })
- .catch(error => {
- console.error('Error getting MySQL info:', error);
- const content = document.getElementById('mysqlInfoContent');
- const statusBadge = document.getElementById('mysqlStatusBadge');
-
- content.innerHTML = `
-
-
- Error: ${error.message}
-
- `;
-
- statusBadge.innerHTML = ' Error';
- statusBadge.className = 'px-3 py-1 bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 rounded-full text-sm';
- });
-}
-
-function openStartLog(containerName) {
- try {
- const button = (typeof event !== 'undefined' && event) ? event.target.closest('button') : null;
- const originalContent = button ? button.innerHTML : '';
- if (!containerName) {
- throw new Error('Invalid container name');
- }
-
- // Try to use SSE modal if available
- const modal = document.getElementById('startLogModal');
- const content = document.getElementById('startLogContent');
- const useSSE = !!(modal && content && typeof EventSource !== 'undefined');
-
- if (button) {
- button.disabled = true;
- button.dataset.originalContent = originalContent;
- button.innerHTML = ' Starting...';
- }
-
- if (useSSE) {
- modal.classList.remove('hidden');
- modal.classList.add('flex');
- content.textContent = '';
- window.startShouldReload = false;
- if (window.startSource) {
- try { window.startSource.close(); } catch (_) {}
- }
- window.startSource = new EventSource(`/api/container/start-log/${encodeURIComponent(containerName)}`);
- window.startSource.onmessage = (e) => {
- content.textContent += (e.data || '') + "\n";
- content.scrollTop = content.scrollHeight;
- };
- window.startSource.addEventListener('done', (e) => {
- const status = (e.data || '').toLowerCase();
- content.textContent += `\n[Start ${status}]\n`;
- content.scrollTop = content.scrollHeight;
- window.startShouldReload = status === 'success';
- try { window.startSource.close(); } catch (_) {}
- if (button) {
- button.disabled = false;
- button.innerHTML = button.dataset.originalContent || originalContent || button.innerHTML;
- delete button.dataset.originalContent;
- }
- });
- window.startSource.onerror = () => {
- content.textContent += "\n[Error streaming logs]\n";
- content.scrollTop = content.scrollHeight;
- window.startShouldReload = false;
- try { window.startSource.close(); } catch (_) {}
- if (button) {
- button.disabled = false;
- button.innerHTML = button.dataset.originalContent || originalContent || button.innerHTML;
- delete button.dataset.originalContent;
- }
- };
- } else {
- // Fallback: POST start
- const url = `/api/container/${encodeURIComponent(containerName)}/start`;
- fetch(url, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- })
- .then(async (response) => {
- const text = await response.text();
- let data = null;
- try { data = text ? JSON.parse(text) : null; } catch (_) {}
- if (!response.ok) {
- const msg = (data && (data.error || data.message)) ? (data.error || data.message) : 'Failed to start container';
- throw new Error(msg);
- }
- return data;
- })
- .then((data) => {
- const msg = (data && (data.message || (data.success ? 'Container started successfully' : ''))) || 'Container started';
- showNotification(msg, 'success');
- setTimeout(() => { window.location.reload(); }, 600);
- })
- .catch((error) => {
- console.error('Error starting container:', error);
- showNotification(error.message || 'Error starting container', 'error');
- alert(`Error: ${error.message}`);
- })
- .finally(() => {
- if (button) {
- button.disabled = false;
- button.innerHTML = button.dataset.originalContent || originalContent || button.innerHTML;
- delete button.dataset.originalContent;
- }
- });
- }
- } catch (e) {
- console.error('openStartLog error:', e);
- showNotification(e.message || 'Unexpected error', 'error');
- }
-}
-
-function closeStartLog() {
- try {
- const modal = document.getElementById('startLogModal');
- if (modal) {
- modal.classList.add('hidden');
- modal.classList.remove('flex');
- }
- if (window.startSource) { try { window.startSource.close(); } catch (_) {} window.startSource = null; }
- if (window.startShouldReload) { window.location.reload(); }
- } catch (e) {
- console.error('closeStartLog error:', e);
- }
-}
-
-function controlMySQLContainer(containerName, action) {
- try {
- const button = (typeof event !== 'undefined' && event) ? event.target.closest('button') : null;
- const originalContent = button ? button.innerHTML : '';
-
- if (!containerName || !action) {
- throw new Error('Invalid parameters');
- }
- if (!['stop', 'restart'].includes(action)) {
- throw new Error('Unsupported action');
- }
-
- if (button) {
- button.disabled = true;
- button.dataset.originalContent = originalContent;
- button.innerHTML = ' ' + (action === 'stop' ? 'Stopping...' : 'Restarting...');
- }
-
- const url = `/api/container/${encodeURIComponent(containerName)}/${encodeURIComponent(action)}`;
- fetch(url, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- })
- .then(async (response) => {
- const text = await response.text();
- let data = null;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- // Non-JSON response
- }
- if (!response.ok) {
- const msg = (data && (data.error || data.message)) ? (data.error || data.message) : `Failed to ${action} container`;
- throw new Error(msg);
- }
- return data;
- })
- .then((data) => {
- const msg = (data && (data.message || (data.success ? `Container ${action}ed successfully` : ''))) || `Container ${action}ed`;
- showNotification(msg, 'success');
- setTimeout(() => { window.location.reload(); }, 600);
- })
- .catch((error) => {
- console.error(`Error ${action} container:`, error);
- showNotification(error.message || `Error ${action} container`, 'error');
- alert(`Error: ${error.message}`);
- })
- .finally(() => {
- if (button) {
- button.disabled = false;
- button.innerHTML = button.dataset.originalContent || originalContent || button.innerHTML;
- delete button.dataset.originalContent;
- }
- });
- } catch (e) {
- console.error('controlMySQLContainer error:', e);
- showNotification(e.message || 'Unexpected error', 'error');
- }
-}
-
-// Database functions
-function loadDatabases() {
- fetch('/api/mysql/databases')
- .then(response => {
- if (!response.ok) {
- throw new Error('Failed to load databases');
- }
- return response.json();
- })
- .then(data => {
- if (!Array.isArray(data)) {
- throw new Error('Invalid databases response');
- }
- databases = data;
- displayDatabases(databases);
- updateDatabaseSelectors();
- })
- .catch(error => {
- console.error('Error loading databases:', error);
- document.getElementById('databasesTableBody').innerHTML = `
-
-
-
- Error: ${error.message}
-
-
- `;
- });
-}
-
-function displayDatabases(databases) {
- const tbody = document.getElementById('databasesTableBody');
-
- if (databases.length === 0) {
- tbody.innerHTML = `
-
-
-
- No databases found
- Create your first database to get started
-
-
- `;
- return;
- }
-
- tbody.innerHTML = databases.map(db => `
-
-
-
-
- ${db.name}
-
-
-
- ${db.tables || 0}
-
-
- ${formatBytes(db.size || 0)}
-
-
-
-
- View Tables
-
-
- Delete
-
-
-
-
- `).join('');
-}
-
-function createDatabase() {
- const dbName = document.getElementById('newDatabaseName').value.trim();
-
- if (!dbName) {
- alert('Please enter a database name');
- return;
- }
-
- if (!/^[a-zA-Z0-9_]+$/.test(dbName)) {
- alert('Database name can only contain letters, numbers, and underscores');
- return;
- }
-
- fetch('/api/mysql/create-db', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ name: dbName })
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to create database';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Database created successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Database created successfully', 'success');
- closeCreateDatabaseModal();
- loadDatabases();
- } else {
- throw new Error(data?.error || 'Failed to create database');
- }
- })
- .catch(error => {
- console.error('Error creating database:', error);
- alert('Error creating database: ' + error.message);
- });
-}
-
-function deleteDatabase(dbName) {
- if (!confirm(`Are you sure you want to delete the database "${dbName}"? This action cannot be undone.`)) {
- return;
- }
-
- fetch('/api/mysql/delete-db', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ name: dbName })
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && (data.error || data.message)) ? (data.error || data.message) : 'Failed to delete database';
- throw new Error(errMsg);
- }
- return data;
- })
- .then((data) => {
- const msg = data?.message || data?.success || 'Database deleted successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Database deleted successfully', 'success');
- loadDatabases();
- } else {
- throw new Error(data?.error || 'Failed to delete database');
- }
- })
- .catch(error => {
- console.error('Error deleting database:', error);
- alert('Error deleting database: ' + error.message);
- });
-}
-
-function viewTables(dbName) {
- currentDatabase = dbName;
- document.getElementById('databaseSelector').value = dbName;
- switchTab('tables');
-}
-
-// Table functions
-function loadTables() {
- const dbName = document.getElementById('databaseSelector').value;
-
- if (!dbName) {
- document.getElementById('tablesTableBody').innerHTML = `
-
-
- Please select a database first
-
-
- `;
- return;
- }
-
- fetch(`/api/mysql/tables?database=${encodeURIComponent(dbName)}`)
- .then(async (response) => {
- if (!response.ok) {
- // Could be redirected to login or server error
- const text = await response.text().catch(() => '');
- throw new Error('Failed to load tables');
- }
- // Parse JSON safely
- let data;
- try {
- data = await response.json();
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- // Backend returns array directly; fallback to data.tables if present
- const tables = Array.isArray(data) ? data : (Array.isArray(data?.tables) ? data.tables : null);
- if (!tables) {
- const msg = (data && typeof data === 'object' && data.error) ? data.error : 'Invalid tables response';
- throw new Error(msg);
- }
- displayTables(tables);
- })
- .catch(error => {
- console.error('Error loading tables:', error);
- document.getElementById('tablesTableBody').innerHTML = `
-
-
-
- Error: ${error.message}
-
-
- `;
- });
-}
-
-function displayTables(tables) {
- const tbody = document.getElementById('tablesTableBody');
-
- if (tables.length === 0) {
- tbody.innerHTML = `
-
-
-
- No tables found
- Create tables in this database to see them here
-
-
- `;
- return;
- }
-
- tbody.innerHTML = tables.map(table => `
-
-
-
-
- ${table.name}
-
-
-
- ${table.rows || 0}
-
-
- ${formatBytes(table.size || 0)}
-
-
-
-
- View Data
-
-
- Delete
-
-
-
-
- `).join('');
-}
-
-function deleteTable(tableName) {
- const dbName = document.getElementById('databaseSelector').value;
-
- if (!confirm(`Are you sure you want to delete the table "${tableName}" from database "${dbName}"? This action cannot be undone.`)) {
- return;
- }
-
- fetch('/api/mysql/delete-table', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- database: dbName,
- table: tableName
- })
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to delete table';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Table deleted successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Table deleted successfully', 'success');
- loadTables();
- } else {
- throw new Error(data?.error || 'Failed to delete table');
- }
- })
- .catch(error => {
- console.error('Error deleting table:', error);
- alert('Error deleting table: ' + error.message);
- });
-}
-
-function viewTableData(tableName) {
- currentTable = tableName;
- // Switch to queries tab and load table data
- switchTab('queries');
- document.getElementById('sqlQuery').value = `SELECT * FROM \`${tableName}\` LIMIT 100;`;
-}
-
-// User functions
-function loadUsers() {
- fetch('/api/mysql/users')
- .then(response => {
- if (!response.ok) {
- throw new Error('Failed to load users');
- }
- return response.json();
- })
- .then(data => {
- const list = Array.isArray(data) ? data : (data && Array.isArray(data.users) ? data.users : null);
- if (!list) {
- throw new Error('Invalid users response');
- }
- // Normalize keys to match UI expectations: user, host, privileges
- users = list.map(u => ({
- user: u.user ?? u.User ?? '',
- host: u.host ?? u.Host ?? '%',
- privileges: u.privileges ?? 'No privileges'
- }));
- displayUsers(users);
- })
- .catch(error => {
- console.error('Error loading users:', error);
- document.getElementById('usersTableBody').innerHTML = `
-
-
-
- Error: ${error.message}
-
-
- `;
- });
-}
-
-function displayUsers(users) {
- const tbody = document.getElementById('usersTableBody');
-
- if (users.length === 0) {
- tbody.innerHTML = `
-
-
-
- No users found
- Create MySQL users to see them here
-
-
- `;
- return;
- }
-
- tbody.innerHTML = users.map(user => `
-
-
-
-
- ${user.user}
-
-
-
- ${user.host}
-
-
-
- ${user.privileges || 'No privileges'}
-
-
-
-
-
- Edit
-
-
- Delete
-
-
-
-
- `).join('');
-}
-
-function createUser() {
- const username = document.getElementById('newUsername').value.trim();
- const password = document.getElementById('newUserPassword').value;
- const host = document.getElementById('newUserHost').value.trim();
- const privileges = document.getElementById('newUserPrivileges').value;
-
- if (!username) {
- alert('Please enter a username');
- return;
- }
-
- if (!password) {
- alert('Please enter a password');
- return;
- }
-
- if (!/^[a-zA-Z0-9_]+$/.test(username)) {
- alert('Username can only contain letters, numbers, and underscores');
- return;
- }
-
- fetch('/api/mysql/create-user', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- username: username,
- password: password,
- host: host,
- privileges: privileges
- })
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to create user';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'User created successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'User created successfully', 'success');
- closeCreateUserModal();
- loadUsers();
- } else {
- throw new Error(data?.error || 'Failed to create user');
- }
- })
- .catch(error => {
- console.error('Error creating user:', error);
- alert('Error creating user: ' + error.message);
- });
-}
-
-function deleteUser(username, host) {
- if (username === 'root') {
- alert('Cannot delete root user');
- return;
- }
-
- if (!confirm(`Are you sure you want to delete the user "${username}"@"${host}"? This action cannot be undone.`)) {
- return;
- }
-
- fetch('/api/mysql/delete-user', {
- method: 'DELETE',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- username: username,
- host: host
- })
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to delete user';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'User deleted successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'User deleted successfully', 'success');
- loadUsers();
- } else {
- throw new Error(data?.error || 'Failed to delete user');
- }
- })
- .catch(error => {
- console.error('Error deleting user:', error);
- alert('Error deleting user: ' + error.message);
- });
-}
-
-function editUser(username, host) {
- const newPassword = prompt(`Enter new password for user "${username}"@"${host}" (leave empty to keep current password):`);
-
- if (newPassword === null) {
- return; // User cancelled
- }
-
- const privileges = prompt(`Enter new privileges for user "${username}"@"${host}" (leave empty to keep current privileges):`, 'ALL PRIVILEGES');
-
- if (privileges === null) {
- return; // User cancelled
- }
-
- const updateData = {
- username: username,
- host: host
- };
-
- if (newPassword) {
- updateData.password = newPassword;
- }
-
- if (privileges) {
- updateData.privileges = privileges;
- }
-
- fetch('/api/mysql/update-user', {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(updateData)
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to update user';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'User updated successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'User updated successfully', 'success');
- loadUsers();
- } else {
- throw new Error(data?.error || 'Failed to update user');
- }
- })
- .catch(error => {
- console.error('Error updating user:', error);
- alert('Error updating user: ' + error.message);
- });
-}
-
-// Query functions
-function executeQuery() {
- const query = document.getElementById('sqlQuery').value.trim();
- const database = document.getElementById('queryDatabase').value;
-
- if (!query) {
- alert('Please enter a SQL query');
- return;
- }
-
- const resultsDiv = document.getElementById('queryResults');
- const statusSpan = document.getElementById('queryStatus');
-
- resultsDiv.classList.remove('hidden');
- statusSpan.innerHTML = ' Executing...';
- statusSpan.className = 'text-xs text-blue-600 dark:text-blue-400';
-
- fetch('/api/mysql/execute-query', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- query: query,
- database: database
- })
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Query execution failed';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- if (!Array.isArray(data)) {
- throw new Error('Invalid query response');
- }
- // Display results using existing renderer (expects {results: [...]})
- displayQueryResults({ results: data });
-
- // Determine status message
- if (data.length === 1 && typeof data[0] === 'object' && data[0] && 'affected_rows' in data[0]) {
- const affected = Number(data[0].affected_rows) || 0;
- statusSpan.innerHTML = ` ${affected} rows affected`;
- } else {
- statusSpan.innerHTML = ` ${data.length} rows returned`;
- }
- statusSpan.className = 'text-xs text-green-600 dark:text-green-400';
- })
- .catch(error => {
- console.error('Error executing query:', error);
- document.getElementById('queryResultsTable').innerHTML = `
-
-
-
- Error: ${error.message}
-
-
- `;
- statusSpan.innerHTML = ` Error`;
- statusSpan.className = 'text-xs text-red-600 dark:text-red-400';
- });
-}
-
-function displayQueryResults(data) {
- const tbody = document.getElementById('queryResultsTable');
-
- if (!data.results || data.results.length === 0) {
- tbody.innerHTML = `
-
-
- Query executed successfully. No results to display.
-
-
- `;
- return;
- }
-
- const headers = Object.keys(data.results[0]);
-
- tbody.innerHTML = `
-
-
- ${headers.map(header => `
-
- ${header}
-
- `).join('')}
-
-
-
- ${data.results.map(row => `
-
- ${headers.map(header => `
-
- ${row[header] !== null && row[header] !== undefined ? row[header] : 'NULL'}
-
- `).join('')}
-
- `).join('')}
-
- `;
-}
-
-function clearQuery() {
- document.getElementById('sqlQuery').value = '';
- document.getElementById('queryResults').classList.add('hidden');
-}
-
-// Replication functions
-function loadReplicationStatus() {
- fetch('/api/mysql/replication/status')
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to load replication status';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- if (data && (data.success || data.status)) {
- // backend returns {success: True, status: {...}}
- displayReplicationStatus(data.status || data);
- } else {
- throw new Error(data?.error || 'Failed to load replication status');
- }
- })
- .catch(error => {
- console.error('Error loading replication status:', error);
- document.getElementById('masterStatus').innerHTML = `
-
-
- Error: ${error.message}
-
- `;
- document.getElementById('slaveStatus').innerHTML = `
-
-
- Error: ${error.message}
-
- `;
- });
-}
-
-function displayReplicationStatus(data) {
- const masterStatus = document.getElementById('masterStatus');
- const slaveStatus = document.getElementById('slaveStatus');
-
- // Normalize master data: support {status: {master}}, {master}, or legacy {master_status}
- const master = (data && (data.master ?? data.master_status)) ?? null;
-
- if (master) {
- const file = master.file || master.File || 'N/A';
- const position = master.position || master.Position || 'N/A';
- masterStatus.innerHTML = `
-
-
-
- Master is configured
-
-
-
File: ${file}
-
Position: ${position}
-
-
- `;
- } else {
- masterStatus.innerHTML = `
-
-
- Master is not configured
-
- `;
- }
-
- // Normalize slave data: backend returns array in status.slave; legacy had single object slave_status
- const slaveList = data && (Array.isArray(data.slave) ? data.slave : null);
- const slaveObj = slaveList ? slaveList[0] : (data && data.slave_status ? data.slave_status : null);
-
- if (slaveObj) {
- const ioRunning = (slaveObj.slave_io_running || slaveObj.Slave_IO_Running || '').toString();
- const sqlRunning = (slaveObj.slave_sql_running || slaveObj.Slave_SQL_Running || '').toString();
- const isRunning = (ioRunning === 'Yes' && sqlRunning === 'Yes');
- const masterHost = slaveObj.master_host || slaveObj.Master_Host || 'N/A';
- const behind = slaveObj.seconds_behind_master ?? slaveObj.Seconds_Behind_Master;
- const lastErrno = slaveObj.last_errno ?? slaveObj.Last_Errno;
- const lastError = slaveObj.last_error ?? slaveObj.Last_Error;
-
- // Build actions
- let actionsHtml = '';
- if (!isRunning && lastErrno) {
- actionsHtml = `
-
-
-
- Skip Error
-
- ${lastError ? `
Error: ${lastError}
` : ''}
-
- `;
- }
-
- slaveStatus.innerHTML = `
-
-
-
- Slave is ${isRunning ? 'running' : 'stopped'}
-
-
-
Master: ${masterHost}
-
Behind: ${behind != null ? behind : 'N/A'} seconds
-
- ${actionsHtml}
-
- `;
- } else {
- slaveStatus.innerHTML = `
-
-
- Slave is not configured
-
- `;
- }
-}
-
-// Add handler to call backend skip-error
-function skipReplicationError() {
- const btn = event?.currentTarget;
- if (btn) {
- btn.disabled = true;
- btn.innerHTML = ' Skipping...';
- }
- showNotification('Skipping one replication error transaction...', 'info');
- fetch('/api/mysql/replication/skip-error', { method: 'POST' })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to skip replication error';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Replication error skipped';
- showNotification(typeof msg === 'string' ? msg : 'Replication error skipped', 'success');
- loadReplicationStatus();
- })
- .catch(error => {
- console.error('Error skipping replication error:', error);
- showNotification('Error skipping replication error: ' + error.message, 'error');
- })
- .finally(() => {
- if (btn) {
- btn.disabled = false;
- btn.innerHTML = ' Skip Error';
- }
- });
-}
-
-function setupMaster() {
- const serverId = prompt('Enter server ID (unique number, e.g., 1):', '1');
- const logBin = prompt('Enter binary log name (e.g., mysql-bin):', 'mysql-bin');
-
- if (!serverId || !logBin) {
- return;
- }
-
- fetch('/api/mysql/replication/setup-master', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- server_id: parseInt(serverId),
- log_bin: logBin
- })
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to setup master';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Master setup completed successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Master setup completed successfully', 'success');
- loadReplicationStatus();
- } else {
- throw new Error(data?.error || 'Failed to setup master');
- }
- })
- .catch(error => {
- console.error('Error setting up master:', error);
- alert('Error setting up master: ' + error.message);
- });
-}
-
-function disableMaster() {
- if (!confirm('Are you sure you want to disable master replication? This will remove master configuration.')) {
- return;
- }
-
- fetch('/api/mysql/replication/disable-master', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- }
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to disable master';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Master disabled successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Master disabled successfully', 'success');
- loadReplicationStatus();
- } else {
- throw new Error(data?.error || 'Failed to disable master');
- }
- })
- .catch(error => {
- console.error('Error disabling master:', error);
- alert('Error disabling master: ' + error.message);
- });
-}
-
-function setupSlave() {
- const masterHost = prompt('Enter master host:');
- const masterUser = prompt('Enter master username (replication user):');
- const masterPassword = prompt('Enter master password:');
- const serverId = prompt('Enter slave server ID (unique number, different from master):', '2');
-
- if (!masterHost || !masterUser || !masterPassword || !serverId) {
- return;
- }
-
- // Show loading state
- showNotification('Setting up MySQL slave...', 'info');
- const setupBtn = document.querySelector('button[onclick="setupSlave()"]');
- if (setupBtn) {
- setupBtn.disabled = true;
- setupBtn.dataset.originalText = setupBtn.innerHTML;
- setupBtn.innerHTML = ' Setting up...';
- }
- const slaveStatusEl = document.getElementById('slaveStatus');
- if (slaveStatusEl) {
- slaveStatusEl.innerHTML = ' Setting up slave...
';
- }
-
- fetch('/api/mysql/replication/setup-slave', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- master_host: masterHost,
- master_user: masterUser,
- master_password: masterPassword,
- master_log_file: '',
- master_log_pos: 0,
- server_id: parseInt(serverId)
- })
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to setup slave';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Slave setup completed successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Slave setup completed successfully', 'success');
- loadReplicationStatus();
- } else {
- throw new Error(data?.error || 'Failed to setup slave');
- }
- })
- .catch(error => {
- console.error('Error setting up slave:', error);
- showNotification('Error setting up slave: ' + error.message, 'error');
- })
- .finally(() => {
- if (setupBtn) {
- setupBtn.disabled = false;
- setupBtn.innerHTML = setupBtn.dataset.originalText || 'Setup Slave';
- delete setupBtn.dataset.originalText;
- }
- });
-}
-
-function startSlave() {
- fetch('/api/mysql/replication/start-slave', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- }
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to start slave';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Slave started successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Slave started successfully', 'success');
- loadReplicationStatus();
- } else {
- throw new Error(data?.error || 'Failed to start slave');
- }
- })
- .catch(error => {
- console.error('Error starting slave:', error);
- alert('Error starting slave: ' + error.message);
- });
-}
-
-function stopSlave() {
- fetch('/api/mysql/replication/stop-slave', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- }
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to stop slave';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Slave stopped successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Slave stopped successfully', 'success');
- loadReplicationStatus();
- } else {
- throw new Error(data?.error || 'Failed to stop slave');
- }
- })
- .catch(error => {
- console.error('Error stopping slave:', error);
- alert('Error stopping slave: ' + error.message);
- });
-}
-
-function resetSlave() {
- if (!confirm('Are you sure you want to reset slave? This will remove all slave configuration.')) {
- return;
- }
-
- fetch('/api/mysql/replication/reset-slave', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- }
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && data.error) ? data.error : 'Failed to reset slave';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Slave reset successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Slave reset successfully', 'success');
- loadReplicationStatus();
- } else {
- throw new Error(data?.error || 'Failed to reset slave');
- }
- })
- .catch(error => {
- console.error('Error resetting slave:', error);
- alert('Error resetting slave: ' + error.message);
- });
-}
-
-// Log functions
-function setCodeBlockText(id, text) {
- const el = document.getElementById(id);
- el.textContent = text || '';
- if (window.Prism && el.closest('pre')) {
- // Language none; still run Prism to format line numbers
- Prism.highlightElement(el);
- }
-}
-
-function filterLogs(codeId, filterInputId) {
- const filter = document.getElementById(filterInputId)?.value || '';
- const original = (filterLogs._cache?.[codeId]) ?? document.getElementById(codeId)?.textContent ?? '';
- if (!filterLogs._cache) filterLogs._cache = {};
- if (!(codeId in filterLogs._cache)) filterLogs._cache[codeId] = original;
- if (!filter) {
- setCodeBlockText(codeId, filterLogs._cache[codeId]);
- return;
- }
- const lines = filterLogs._cache[codeId].split('\n');
- const filtered = lines.filter(l => l.toLowerCase().includes(filter.toLowerCase())).join('\n');
- setCodeBlockText(codeId, filtered);
-}
-
-function copyLog(codeId) {
- const text = document.getElementById(codeId)?.textContent || '';
- navigator.clipboard.writeText(text).then(() => {
- showNotification('Logs copied to clipboard', 'success');
- }).catch(() => {
- alert('Failed to copy logs');
- });
-}
-
-function toggleLineNumbers(codeId) {
- const code = document.getElementById(codeId);
- if (!code) return;
- const pre = code.closest('pre');
- if (!pre) return;
- // Ensure Prism CSS selector applies
- if (!pre.classList.contains('language-none')) pre.classList.add('language-none');
- const enabled = pre.classList.toggle('line-numbers');
- if (enabled && window.Prism) {
- Prism.highlightElement(code);
- } else {
- // Remove generated line number rows so they don't linger
- const rows = pre.querySelector('.line-numbers-rows');
- if (rows) rows.remove();
- }
-}
-
-function toggleExpand(codeId) {
- const code = document.getElementById(codeId);
- if (!code) return;
- const pre = code.closest('pre');
- if (!pre) return;
- const computed = window.getComputedStyle(pre);
- if (computed.maxHeight !== 'none') {
- pre.style.maxHeight = 'none';
- } else {
- pre.style.maxHeight = '16rem'; // ~max-h-64
- }
-}
-
-function loadErrorLogs() {
- fetch('/api/mysql/error-logs')
- .then(async (response) => {
- const text = await response.text();
- let data;
- try { data = text ? JSON.parse(text) : null; } catch { throw new Error('Invalid JSON response'); }
- if (!response.ok) { throw new Error((data && (data.error || data.message)) || 'Failed to load error logs'); }
- return data;
- })
- .then(data => {
- if (data && (data.success || data.logs)) {
- setCodeBlockText('errorLogs', data.logs || 'No error logs available');
- filterLogs._cache && delete filterLogs._cache['errorLogs'];
- } else {
- throw new Error(data?.error || 'Failed to load error logs');
- }
- })
- .catch(error => {
- console.error('Error loading error logs:', error);
- setCodeBlockText('errorLogs', `Error: ${error.message}`);
- });
-}
-
-function loadReplicationLogs() {
- fetch('/api/mysql/replication-logs')
- .then(async (response) => {
- const text = await response.text();
- let data;
- try { data = text ? JSON.parse(text) : null; } catch { throw new Error('Invalid JSON response'); }
- if (!response.ok) { throw new Error((data && (data.error || data.message)) || 'Failed to load replication logs'); }
- return data;
- })
- .then(data => {
- if (data && (data.success || data.logs)) {
- setCodeBlockText('replicationLogs', data.logs || 'No replication logs available');
- filterLogs._cache && delete filterLogs._cache['replicationLogs'];
- } else {
- throw new Error(data?.error || 'Failed to load replication logs');
- }
- })
- .catch(error => {
- console.error('Error loading replication logs:', error);
- setCodeBlockText('replicationLogs', `Error: ${error.message}`);
- });
-}
-
-function loadSlowLogs() {
- fetch('/api/mysql/slow-logs')
- .then(async (response) => {
- const text = await response.text();
- let data;
- try { data = text ? JSON.parse(text) : null; } catch { throw new Error('Invalid JSON response'); }
- if (!response.ok) { throw new Error((data && (data.error || data.message)) || 'Failed to load slow logs'); }
- return data;
- })
- .then(data => {
- if (data && (data.success || data.logs)) {
- setCodeBlockText('slowLogs', data.logs || 'No slow query logs available');
- filterLogs._cache && delete filterLogs._cache['slowLogs'];
- } else {
- throw new Error(data?.error || 'Failed to load slow logs');
- }
- })
- .catch(error => {
- console.error('Error loading slow logs:', error);
- setCodeBlockText('slowLogs', `Error: ${error.message}`);
- });
-}
-
-function analyzeSlowLogs() {
- fetch('/api/mysql/analyze-slow-logs')
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && (data.error || data.message)) ? (data.error || data.message) : 'Failed to analyze slow logs';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- if (data && (data.success || data.analysis)) {
- document.getElementById('slowLogs').textContent = data.analysis || 'Analysis completed';
- showNotification('Slow query analysis completed', 'success');
- } else {
- throw new Error(data?.error || 'Failed to analyze slow logs');
- }
- })
- .catch(error => {
- console.error('Error analyzing slow logs:', error);
- alert('Error analyzing slow logs: ' + error.message);
- });
-}
-
-// Configuration functions
-function loadMySQLConfig() {
- fetch('/api/mysql/get-config')
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && (data.error || data.message)) ? (data.error || data.message) : 'Failed to load MySQL configuration';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- if (data && (data.success || data.config)) {
- document.getElementById('mysqlConfigContent').value = data.config || '';
- } else {
- throw new Error(data?.error || 'Failed to load MySQL configuration');
- }
- })
- .catch(error => {
- console.error('Error loading MySQL configuration:', error);
- document.getElementById('mysqlConfigContent').value = `# Error loading configuration: ${error.message}`;
- });
-}
-
-function saveMySQLConfig() {
- const config = document.getElementById('mysqlConfigContent').value;
-
- if (!confirm('Are you sure you want to save the MySQL configuration? This will restart the MySQL service.')) {
- return;
- }
-
- fetch('/api/mysql/save-config', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ config: config })
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && (data.error || data.message)) ? (data.error || data.message) : 'Failed to save configuration';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Configuration saved successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Configuration saved successfully', 'success');
- closeEditConfigModal();
- // Refresh MySQL info after config change
- setTimeout(getMySQLInfo, 5000);
- } else {
- throw new Error(data?.error || 'Failed to save configuration');
- }
- })
- .catch(error => {
- console.error('Error saving MySQL configuration:', error);
- alert('Error saving configuration: ' + error.message);
- });
-}
-
-function setRootPassword() {
- const password = document.getElementById('rootPassword').value;
-
- if (!password) {
- alert('Please enter a password');
- return;
- }
-
- if (password.length < 8) {
- alert('Password must be at least 8 characters long');
- return;
- }
-
- fetch('/api/mysql/set-root-password', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ password: password })
- })
- .then(async (response) => {
- const text = await response.text();
- let data;
- try {
- data = text ? JSON.parse(text) : null;
- } catch (e) {
- throw new Error('Invalid JSON response');
- }
- if (!response.ok) {
- const errMsg = (data && (data.error || data.message)) ? (data.error || data.message) : 'Failed to set root password';
- throw new Error(errMsg);
- }
- return data;
- })
- .then(data => {
- const msg = data?.message || data?.success || 'Root password updated successfully';
- if (msg) {
- showNotification(typeof msg === 'string' ? msg : 'Root password updated successfully', 'success');
- closeSetRootPasswordModal();
- } else {
- throw new Error(data?.error || 'Failed to set root password');
- }
- })
- .catch(error => {
- console.error('Error setting root password:', error);
- alert('Error setting root password: ' + error.message);
- });
-}
-
-// Utility functions
-function updateDatabaseSelectors() {
- const selectors = [
- document.getElementById('databaseSelector'),
- document.getElementById('queryDatabase')
- ];
-
- selectors.forEach(selector => {
- if (selector) {
- selector.innerHTML = 'Select Database ' +
- databases.map(db => `
- ${db.name}
- `).join('');
- }
- });
-}
-
-function formatBytes(bytes) {
- if (bytes === 0) return '0 Bytes';
- const k = 1024;
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
- const i = Math.floor(Math.log(bytes) / Math.log(k));
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
-}
-
-function showNotification(message, type = 'info') {
- const notification = document.createElement('div');
- notification.className = `fixed top-4 right-4 px-6 py-3 rounded shadow-lg z-50 ${
- type === 'success' ? 'bg-green-500 text-white' :
- type === 'error' ? 'bg-red-500 text-white' :
- 'bg-blue-500 text-white'
- }`;
- notification.innerHTML = `
-
-
- ${message}
-
- `;
-
- document.body.appendChild(notification);
-
- setTimeout(() => {
- notification.remove();
- }, 3000);
-}
-
-function refreshAllData() {
- showNotification('Refreshing data...', 'info');
-
- // Refresh based on current tab
- const activeTab = document.querySelector('.tab-button.active').id.replace('tab-', '');
-
- switch(activeTab) {
- case 'databases':
- loadDatabases();
- break;
- case 'tables':
- loadTables();
- break;
- case 'users':
- loadUsers();
- break;
- case 'replication':
- loadReplicationStatus();
- break;
- case 'logs':
- loadErrorLogs();
- loadReplicationLogs();
- loadSlowLogs();
- break;
- }
-
- // Always refresh MySQL info
- getMySQLInfo();
-}
-
-// Keyboard shortcuts
-document.addEventListener('keydown', function(e) {
- // Ctrl+Enter to execute query
- if (e.ctrlKey && e.key === 'Enter' && document.getElementById('sqlQuery') === document.activeElement) {
- e.preventDefault();
- executeQuery();
- }
-
- // Escape to close modals
- if (e.key === 'Escape') {
- closeCreateDatabaseModal();
- closeCreateUserModal();
- closeSetRootPasswordModal();
- closeEditConfigModal();
- }
-});
\ No newline at end of file
diff --git a/docker/panel/templates/add_site.html b/docker/panel/templates/add_site.html
deleted file mode 100644
index d6f7ce7f6..000000000
--- a/docker/panel/templates/add_site.html
+++ /dev/null
@@ -1,226 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Add Site - Nginx Manager{% endblock %}
-
-{% block content %}
-
-
-
-
Add New Site
-
Configure a new site (Reverse Proxy, PHP-FPM, or Static)
-
-
-
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/docker/panel/templates/base.html b/docker/panel/templates/base.html
deleted file mode 100644
index 928a04991..000000000
--- a/docker/panel/templates/base.html
+++ /dev/null
@@ -1,559 +0,0 @@
-
-
-
-
-
- {% block title %}SLEMP Panel{% endblock %}
-
-
-
-
-
-
-
-
-
-
-
-
- {% block content %}{% endblock %}
-
-
-
-
-
-
-{% block script %}{% endblock %}
-
-
diff --git a/docker/panel/templates/containers.html b/docker/panel/templates/containers.html
deleted file mode 100644
index b6cc7b204..000000000
--- a/docker/panel/templates/containers.html
+++ /dev/null
@@ -1,400 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Container Management - SLEMP Panel{% endblock %}
-
-{% block content %}
-
-
-
-
Build Logs
-
-
-
-
- Close
-
-
-
-
-
-
-
-
Start Logs
-
-
-
-
- Close
-
-
-
-
-
-
-
-
-
-
-
Container Management
-
Manage Docker containers for your SLEMP stack
-
-
-
-
- Refresh
-
-
-
-
-
-
-
-
-
-
Total Containers
-
{{ containers|length }}
-
-
-
-
-
-
-
-
-
-
-
Running
-
- {{ containers|selectattr('running')|list|length }}
-
-
-
-
-
-
-
-
-
-
-
-
Stopped
-
- {{ containers|rejectattr('running')|list|length }}
-
-
-
-
-
-
-
-
-
-
- {% for category_name, category_containers in categories.items() %}
-
-
-
- {% if category_name == 'PHP Containers' %}
-
- {% elif category_name == 'Web Services' %}
-
- {% else %}
-
- {% endif %}
- {{ category_name }} ({{ category_containers|length }})
-
-
-
-
-
-
-
-
- Service
-
-
- Status
-
-
- Image
-
-
- Ports
-
-
- Info
-
-
- Actions
-
-
-
-
- {% for container in category_containers %}
-
-
-
-
- {% if container.running %}
-
-
-
- {% else %}
-
-
-
- {% endif %}
-
-
-
{{ container.service }}
-
{{ container.name }}
-
-
-
-
- {% if container.running %}
-
-
- Running
-
- {% else %}
-
-
- Stopped
-
- {% endif %}
-
-
- {{ container.image.split('/')[-1] if container.image else 'custom-build' }}
-
-
- {% if container.ports %}
- {% for port in container.ports %}
- {% if port.get('PublishedPort') %}
-
- {{ port.get('PublishedPort') }}:{{ port.get('TargetPort', '') }}
-
- {% endif %}
- {% endfor %}
- {% else %}
- -
- {% endif %}
-
-
-
-
{{ container.volumes }} volumes
-
{{ container.environment }} env
- {% if container.depends_on %}
-
{{ container.depends_on|length }} deps
- {% endif %}
-
-
-
-
- {% if container.service != 'panel' %}
- {% if container.running %}
-
-
- {% elif not container.build or container.has_image %}
-
-
-
- {% endif %}
- {% if container.build and not container.running and not container.has_container %}
-
-
-
- {% endif %}
-
- {% if not container.running and container.has_container %}
-
- {% endif %}
-
- {% if container.build and not container.running and container.has_image and not container.has_container %}
-
- {% endif %}
- {% else %}
- Panel
- {% endif %}
-
-
-
- {% else %}
-
-
-
- No containers in this category
-
-
- {% endfor %}
-
-
-
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
Container Management Notes
-
-
- mlite_panel container cannot be managed from here for safety reasons
- Build operation may take several minutes depending on container size
- Container changes will affect your running services
- Use restart to apply configuration changes without full rebuild
-
-
-
-
-
-
-
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/docker/panel/templates/cron.html b/docker/panel/templates/cron.html
deleted file mode 100644
index 7153d9190..000000000
--- a/docker/panel/templates/cron.html
+++ /dev/null
@@ -1,389 +0,0 @@
-{% extends 'base.html' %}
-{% block title %}Cron Scheduler{% endblock %}
-{% block content %}
-
-
-
Cron Scheduler
-
- Simpan Pengaturan Dasar
- Add Task
-
-
-
-
-
-
Autorenew Certbot
-
-
- Aktif
-
-
- Waktu (HH:MM)
-
-
-
-
-
Backup Website
-
-
- Aktif
-
-
- Waktu (HH:MM)
-
-
-
-
-
Backup MySQL
-
-
- Aktif
-
-
-
-
-
-
-
-
-
-
-
- Name
- Type
- Status
- Schedule
- Last execute
- Operate
-
-
-
-
-
-
-
-
-
-
Add Task
-
-
- Task name
-
-
-
- Execute user
-
-
-
- Task type
-
- Shell Script
- Backup Site
- Backup Database
-
-
-
- Execute cycle
-
- Daily
- Every N minutes
- Every N hours
-
-
-
- Time / Every
-
-
-
-
-
- Scope
-
- All Sites
- Single Site
-
-
-
- Site
-
-
-
-
-
-
-
- Scope
-
- All Databases
- Single Database
-
-
-
- Database
-
-
-
-
-
- Script content
-
-
-
-
- Cancel
- Confirm
-
-
-
-
-
-
-{% endblock %}
diff --git a/docker/panel/templates/dashboard.html b/docker/panel/templates/dashboard.html
deleted file mode 100644
index 6e795c9e2..000000000
--- a/docker/panel/templates/dashboard.html
+++ /dev/null
@@ -1,313 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Dashboard - SLEMP Panel{% endblock %}
-
-{% block content %}
-
-
-
-
Site Management
-
Manage your Nginx reverse proxy configurations
-
-
-
- Add New Site
-
-
-
-
-
-
-
-
-
-
-
Nginx Status
-
- {{ 'Running' if nginx_status else 'Stopped' }}
-
-
-
-
-
-
-
-
-
Total Sites
-
{{ sites|length }} configured
-
-
-
-
-
-
-
-
MySQL Status
-
- {{ 'Running' if mysql_running else 'Stopped' }}
-
-
-
-
-
-
-
-
-
Total Databases
-
{{ total_databases }} databases
-
-
-
-
-
-
-
-
-
-
Network (mlite_nginx)
- Refresh
-
-
-
-
-
-
-
- {% for site in sites %}
-
-
-
-
{{ site.domain }}
-
- {% if site.type == 'php' %}
-
- Type: PHP-FPM
- {% elif site.type == 'static' %}
-
- Type: Static Files
- {% else %}
-
- Type: Nginx Proxy
- {% endif %}
-
-
- {% if site.type == 'php' or site.type == 'static' %}
-
- Root: {{ site.root or '/var/www/public' }}
- {% else %}
-
- Port: {{ site.port }}
- {% endif %}
-
- {% if site.type == 'php' %}
-
-
- PHP: {{ site.php_version or '8.3' }}
-
- {% endif %}
-
-
- {{ site.created_at }}
-
-
-
-
-
-
-
- {{ site.config_file }}
-
-
-
- {% else %}
-
- {% endfor %}
-
-
-
-
-
-
-
-
Delete Site Configuration
-
-
Are you sure you want to delete the site configuration for ? This action cannot be undone.
-
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/docker/panel/templates/edit_vhost.html b/docker/panel/templates/edit_vhost.html
deleted file mode 100644
index 2b492cbac..000000000
--- a/docker/panel/templates/edit_vhost.html
+++ /dev/null
@@ -1,84 +0,0 @@
-{% extends 'base.html' %}
-{% block title %}Edit Vhost - {{ site_name }}{% endblock %}
-{% block content %}
-
-
-
-
Edit Vhost: {{ site_name }}
-
File: /etc/nginx/conf.d/{{ site_name }}.conf
-
-
-
-
- Nginx {% if nginx_status %}Running{% else %}Stopped{% endif %}
-
-
-
-
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/docker/panel/templates/files.html b/docker/panel/templates/files.html
deleted file mode 100644
index 144efd5f8..000000000
--- a/docker/panel/templates/files.html
+++ /dev/null
@@ -1,1794 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}File Manager{% endblock %}
-
-{% block content %}
-
-
-
-
-
-
-
-
File Manager
-
-
-
-
-
- New Directory
-
-
- New File
-
-
-
-
-
- Upload Files
-
-
-
-
-
/
-
-
-
-
-
-
- Name
- Size
- Modified
- Permissions
- Owner
- Actions
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Edit File
-
-
-
-
- Fullscreen
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Save
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Find
-
-
-
-
-
- Replace
-
-
-
-
-
-
- Format
-
-
-
- Dark Theme
- Light Theme
- High Contrast
-
-
- 12px
- 14px
- 16px
- 18px
- 20px
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Save
-
-
- Cancel
-
-
-
-
-
-
-
-
-
-
-
-
Upload Files
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Click to upload
- or drag and drop files here
-
-
- Multiple files supported
-
-
-
-
-
-
-
-
Selected Files:
-
-
-
-
-
-
-
-
- Uploading files...
- 0%
-
-
-
-
-
-
-
-
-
-
- Cancel
-
-
- Upload Files
-
-
-
-
-
-
-
-{% endblock %}
diff --git a/docker/panel/templates/login.html b/docker/panel/templates/login.html
deleted file mode 100644
index 2ba1180aa..000000000
--- a/docker/panel/templates/login.html
+++ /dev/null
@@ -1,280 +0,0 @@
-
-
-
-
-
-
-
- SLEMP Login - Server Management
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
SLEMP Login
-
Secure access to your server dashboard
-
-
- {% with messages = get_flashed_messages() %}
- {% if messages %}
- {% for message in messages %}
-
- {% endfor %}
- {% endif %}
- {% endwith %}
-
-
-
-
-
-
-
-
-
Security Verification
-
-
-
-
-
-
-
Loading...
-
=
-
?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sign in to Dashboard
-
-
-
-
-
-
-
- Protected by advanced security protocols
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docker/panel/templates/modsecurity_dashboard.html b/docker/panel/templates/modsecurity_dashboard.html
deleted file mode 100644
index 931295785..000000000
--- a/docker/panel/templates/modsecurity_dashboard.html
+++ /dev/null
@@ -1,407 +0,0 @@
-{% extends "base.html" %}
-{% block title %}ModSecurity Dashboard{% endblock %}
-{% block content %}
-
-
-
ModSecurity Dashboard
- Auto-refresh setiap 30 detik
-
-
-
-
-
-
-
-
-
-
Grafik Serangan per Waktu
-
-
- 2 jam
- 24 jam
- 7 hari
-
-
-
-
-
-
-
Top Rule IDs
-
-
Top Source IPs
-
-
-
-
-
-
-
-
-
-
-
Detail Serangan
-
0 events
-
-
-
-
-
- Waktu
- IP
- Domain
- Method
- URI
- Rule IDs
- Status
- Severity
-
-
-
-
-
-
-
-
-
-
Manage Rule Exclusions
-
-
- Rule ID
-
-
-
- Scope
-
- Global
- Per Domain
-
-
-
- Domain
-
-
-
- Add
- Remove
-
-
-
-
-
-{% endblock %}
-{% block script %}
-
-
-{% endblock %}
diff --git a/docker/panel/templates/mysql.html b/docker/panel/templates/mysql.html
deleted file mode 100644
index 73823fa8e..000000000
--- a/docker/panel/templates/mysql.html
+++ /dev/null
@@ -1,664 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}MySQL Database Management - SLEMP Panel{% endblock %}
-
-{% block content %}
-
-
-
-
-
Create Database
-
-
-
-
- Cancel
- Create
-
-
-
-
-
-
-
-
Create User
-
-
-
-
- Username
-
-
-
- Password
-
-
-
- Host
-
-
-
- Privileges
-
- ALL PRIVILEGES
- SELECT,INSERT,UPDATE,DELETE
- SELECT
- REPLICATION SLAVE
- REPLICATION CLIENT
- CUSTOM
-
-
-
-
- Cancel
- Create
-
-
-
-
-
-
-
-
Set Root Password
-
-
-
-
-
- Current Password:
- (loading...)
-
-
New Password
-
-
-
-
-
-
-
-
-
- Cancel
- Set Password
-
-
-
-
-
-
-
-
Edit MySQL Configuration
-
-
-
-
-
-
- Cancel
- Save
-
-
-
-
-
-
-
-
-
-
-
- MySQL Database Management
-
-
Manage MySQL databases, tables, users, and queries
-
-
-
-
-
-
-
-
-
MySQL Server Information
-
-
-
- Loading MySQL information...
-
-
-
-
-
- Checking...
-
-
-
-
-
-
-
-
-
- MySQL Containers ({{ mysql_containers|length }})
-
-
-
-
-
-
-
-
- Service
-
-
- Status
-
-
- Image
-
-
- Ports
-
-
- Created
-
-
- Actions
-
-
-
-
- {% for container in mysql_containers %}
-
-
-
-
-
-
-
-
- {{ container.name }}
-
-
- MySQL Database
-
-
-
-
-
- {% if container.running %}
-
-
- Running
-
- {% else %}
-
-
- Stopped
-
- {% endif %}
-
-
- {{ container.image }}
-
-
- {% if container.ports %}
- {% for port in container.ports %}
-
- {{ port }}
-
- {% endfor %}
- {% else %}
- -
- {% endif %}
-
-
- {{ container.created }}
-
-
-
- {% if container.running %}
-
-
- Stop
-
-
-
- Restart
-
- {% else %}
-
-
- Start
-
- {% endif %}
-
-
-
- {% else %}
-
-
-
- No MySQL containers found
- MySQL containers will appear here when they are created
-
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
- Databases
-
-
- Tables
-
-
- Users
-
-
- Queries
-
-
- Replication
-
-
- Logs
-
-
-
-
-
-
-
-
-
-
-
-
- Databases
-
-
-
- Create Database
-
-
-
-
-
-
-
- Database
- Tables
- Size
- Actions
-
-
-
-
-
-
- Loading databases...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Tables
-
-
- Select Database
-
-
-
-
-
-
-
- Table
- Rows
- Size
- Actions
-
-
-
-
-
- Please select a database first
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- MySQL Users
-
-
-
- Create User
-
-
-
-
-
-
-
- User
- Host
- Privileges
- Actions
-
-
-
-
-
-
- Loading users...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- SQL Query Editor
-
-
-
-
-
- Database
-
- Select Database
-
-
-
-
- SQL Query
-
-
-
-
-
-
- Execute
-
-
- Clear
-
-
-
-
-
-
Query Results
-
-
-
-
-
-
-
- Query results will appear here
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Master Configuration
-
-
-
-
-
-
- Loading master status...
-
-
-
-
- Setup Master
-
-
- Disable Master
-
-
-
-
-
-
-
-
-
- Slave Configuration
-
-
-
-
-
-
- Loading slave status...
-
-
-
-
- Setup Slave
-
-
- Start Slave
-
-
- Stop Slave
-
-
- Reset Slave
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Error Logs
-
-
-
- Copy
- Lines
- Expand
-
- Refresh
-
-
-
-
-
-
-
-
-
-
- Replication Logs
-
-
-
- Copy
- Lines
- Expand
-
- Refresh
-
-
-
-
-
-
-
-
-
-
- Slow Query Logs
-
-
-
- Copy
- Lines
- Expand
-
- Analyze
-
-
- Refresh
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- MySQL Configuration
-
-
-
-
-
-
- Set Root Password
-
-
-
- Edit Configuration
-
-
-
- Server Info
-
-
-
-
-
-
-
-
-
-
-
-
-
MySQL Containers vs. Database Management
-
-
- Containers : Start/stop/restart layanan MySQL, lihat image, ports, dan waktu pembuatan.
- Database Management : Kelola database, tabel, user, eksekusi query, logs, replikasi, dan konfigurasi.
- Gunakan tombol MySQL Management di atas untuk masuk ke halaman manajemen database.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{% endblock %}
diff --git a/docker/panel/templates/nginx.html b/docker/panel/templates/nginx.html
deleted file mode 100644
index d51b333ed..000000000
--- a/docker/panel/templates/nginx.html
+++ /dev/null
@@ -1,1276 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}Nginx Management - MLite Panel{% endblock %}
-
-{% block content %}
-
-
-
-
-
Nginx Management
-
Manage your Nginx container and configurations
-
-
-
-
-
-
-
-
-
Start Logs
-
-
-
-
- Close
-
-
-
-
-
-
-
-
-
Let's Encrypt Logs
-
-
-
-
- Close
-
-
-
-
-
-
-
-
-
Add New Site
-
-
-
-
-
-
Domain Name
-
-
Please enter a valid domain name
-
-
- Site Type
-
- Nginx Proxy
- PHP-FPM
- Static Site
-
-
-
-
Backend Port
-
-
Port must be between 1 and 65535
-
-
- Root Directory
-
-
-
- PHP Version
-
- 8.3
- 8.2
- 8.1
- 8.0
- 7.4
- 7.3
- 7.2
- 7.1
- 7.0
- 5.6
-
-
-
-
Configuration Preview
-
-
-
- Cancel
- Add Site
-
-
-
-
-
-
-
-
-
-
-
Edit Site
-
-
-
-
-
-
-
Edit Nginx Configuration
-
-
-
- Cancel
- Save Changes
-
-
-
-
-
-
-
-
-
-
-
Detail Serangan ModSecurity -
-
-
-
-
-
-
- Periode:
-
- Hari Ini
- 7 Hari Terakhir
- 30 Hari Terakhir
- Custom
-
-
-
-
- sampai
-
-
-
- Filter
-
-
- CSV
-
-
- JSON
-
-
- Refresh
-
-
-
-
-
-
-
-
Serangan dari IP Terblokir
-
0
-
-
-
-
-
-
-
-
-
-
-
- Waktu
- IP Address
- Method
- URI
- Rule ID
- Severity
- Action
- Message
- Actions
-
-
-
-
- Memuat data...
-
-
-
-
-
-
-
-
-
-
-
-
-
Konfigurasi SSL / Let's Encrypt
-
-
-
-
-
-
-
-
- Enable Let's Encrypt SSL
-
-
-
- Force HTTPS redirect
-
-
-
-
Email Let's Encrypt
-
-
Digunakan untuk registrasi dan notifikasi Let's Encrypt.
-
-
- Cancel
- Simpan
-
-
-
-
-
-
-
-
-
-
Container Status
-
- {% if nginx_info.running %}
-
-
-
-
- Running
-
- {% else %}
-
-
-
-
- Stopped
-
- {% endif %}
-
-
-
-
-
-
Container Name
-
{{ nginx_container_name }}
-
-
-
Image
-
{{ nginx_info.image }}
-
-
-
Status
-
{{ nginx_info.status }}
-
-
-
Created
-
{{ nginx_info.created }}
-
-
-
-
-
-
Ports
-
- {% if nginx_ports_display %}
- {{ nginx_ports_display }}
- {% else %}
- No ports exposed
- {% endif %}
-
-
-
-
Actions
-
- {% if nginx_info.running %}
-
-
-
-
- Stop
-
-
-
-
-
- Restart
-
- {% else %}
-
-
-
-
- Start
-
- {% endif %}
-
-
-
-
-
-
-
-
-
Website List
-
-
- Add New Site
-
-
- {% if config_files %}
-
-
-
-
- Domain
- Tipe
- Port
- Created
- Status
- ModSecurity
- SSL
- Actions
-
-
-
- {% for config in config_files %}
-
-
-
-
-
- {{ config.type }}
-
-
- {{ config.port or '-' }}
-
-
- {{ config.created }}
-
-
- {% if config.enabled %}
- Enabled
- {% else %}
- Disabled
- {% endif %}
-
-
-
-
Loading...
-
- Toggle
-
-
-
-
-
- Logs
-
-
-
-
-
- {% if config.ssl_enabled %}
-
SSL Enabled
- {% else %}
-
No SSL
- {% endif %}
- {% if config.https_redirect %}
-
HTTPS Redirect
- {% endif %}
-
-
-
-
- SSL/Let's Encrypt
-
-
-
-
-
-
-
-
-
- Edit
-
-
- {% if config.enabled %}
-
-
-
-
- Disable
-
- {% else %}
-
-
-
-
- Enable
-
- {% endif %}
-
-
-
-
-
-
- Delete
-
-
-
-
-
- {% endfor %}
-
-
-
- {% else %}
-
No configuration files found.
- {% endif %}
-
-
-
-
-
Logs
-
- {% for line in logs %}
-
{{ line }}
- {% endfor %}
-
-
Auto-refresh setiap 5 detik.
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/docker/panel/templates/php_fpm.html b/docker/panel/templates/php_fpm.html
deleted file mode 100644
index 42b2e9089..000000000
--- a/docker/panel/templates/php_fpm.html
+++ /dev/null
@@ -1,345 +0,0 @@
-{% extends "base.html" %}
-
-{% block title %}PHP-FPM Management - SLEMP Panel{% endblock %}
-
-{% block content %}
-
-
-
-
Start Logs
-
-
-
-
- Close
-
-
-
-
-
-
-
-
-
-
-
-
- PHP-FPM Management
-
-
Manage PHP-FPM containers and versions
-
-
-
-
- Refresh
-
-
-
-
-
-
-
-
-
-
Total PHP Containers
-
{{ php_containers|length }}
-
-
-
-
-
-
-
-
-
-
-
Running
-
- {{ php_containers|selectattr('running')|list|length }}
-
-
-
-
-
-
-
-
-
-
-
-
Stopped
-
- {{ php_containers|rejectattr('running')|list|length }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- PHP-FPM Containers ({{ php_containers|length }})
-
-
-
-
-
-
-
-
- Service
-
-
- Status
-
-
- PHP Version
-
-
- Ports
-
-
- Created
-
-
- Actions
-
-
-
-
- {% for container in php_containers %}
-
-
-
-
- {% if container.running %}
-
-
-
- {% else %}
-
-
-
- {% endif %}
-
-
-
{{ container.name }}
-
{{ container.image }}
-
-
-
-
- {% if container.running %}
-
-
- Running
-
- {% else %}
-
-
- Stopped
-
- {% endif %}
-
-
-
-
- PHP {{ container.php_version }}
-
-
-
- {% if container.ports %}
- {% for port in container.ports %}
-
- {{ port }}
-
- {% endfor %}
- {% else %}
- -
- {% endif %}
-
-
- {{ container.created[:19] if container.created else '-' }}
-
-
-
- {% if container.running %}
-
-
-
-
-
-
- {% else %}
-
-
-
- {% endif %}
-
-
-
- {% else %}
-
-
-
- No PHP containers found
-
-
- {% endfor %}
-
-
-
-
-
-
-
-
-
-
-
-
-
PHP-FPM Management Notes
-
-
- Each PHP version runs in its own container for isolation
- PHP containers are automatically configured with FPM
- Restart containers to apply configuration changes
- Container logs are available when starting services
-
-
-
-
-
-
-
-
-
-
-{% endblock %}
\ No newline at end of file
diff --git a/docker/php.Dockerfile b/docker/php.Dockerfile
new file mode 100644
index 000000000..fbcd38f29
--- /dev/null
+++ b/docker/php.Dockerfile
@@ -0,0 +1,37 @@
+FROM php:8.1-fpm-alpine
+
+RUN apk update
+RUN apk upgrade
+RUN apk add ca-certificates wget
+RUN update-ca-certificates
+RUN apk add libpng-dev
+
+RUN apk add --no-cache mysql-client msmtp perl wget procps shadow libzip libpng libjpeg-turbo libwebp freetype icu
+
+# Install dcmtk from edge testing repository for mini_pacs support
+RUN apk add --no-cache dcmtk --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/
+
+
+RUN apk add --no-cache --virtual build-essentials \
+ icu-dev icu-libs zlib-dev g++ make automake autoconf libzip-dev \
+ libpng-dev libwebp-dev libjpeg-turbo-dev freetype-dev oniguruma-dev linux-headers && \
+ docker-php-ext-configure gd --enable-gd --with-freetype --with-jpeg --with-webp && \
+ docker-php-ext-install gd && \
+ docker-php-ext-install mysqli && \
+ docker-php-ext-install pdo_mysql && \
+ apk add --no-cache sqlite-dev && \
+ docker-php-ext-install pdo_sqlite && \
+ docker-php-ext-install intl && \
+ docker-php-ext-install opcache && \
+ docker-php-ext-install exif && \
+ docker-php-ext-install zip && \
+ docker-php-ext-install bcmath && \
+ docker-php-ext-install mbstring && \
+ docker-php-ext-install pcntl && \
+ apk del build-essentials && rm -rf /usr/src/php*
+
+# Install Composer
+COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
+
+WORKDIR /var/www/html
+
diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile
deleted file mode 100644
index e593f9677..000000000
--- a/docker/php/Dockerfile
+++ /dev/null
@@ -1,62 +0,0 @@
-# mLITE Docker PHP 8.3 Environment
-# Compatible with mLITE PHP 7.4 - 8.3+ requirements
-FROM php:8.3-fpm
-
-# Install system dependencies required for mLITE
-RUN apt-get update && apt-get install -y \
- git \
- curl \
- libpng-dev \
- libjpeg-dev \
- libonig-dev \
- libxml2-dev \
- libzip-dev \
- libicu-dev \
- libfreetype6-dev \
- zip \
- unzip
-
-RUN apt-get clean && rm -rf /var/lib/apt/lists/*
-
-# Install PHP extensions required by mLITE (compatible with PHP 8.3)
-RUN docker-php-ext-install \
- pdo_mysql \
- mysqli \
- mbstring \
- exif \
- pcntl \
- bcmath \
- gd \
- intl \
- zip \
- opcache
-
-RUN docker-php-ext-enable mysqli
-
-# Configure GD extension with FreeType and JPEG support for mLITE image processing
-RUN docker-php-ext-configure gd --with-freetype --with-jpeg
-RUN docker-php-ext-install -j$(nproc) gd
-
-# Install Redis for caching (optional but recommended for performance)
-RUN pecl install redis && docker-php-ext-enable redis
-
-# Install Xdebug for development debugging (PHP 8.3 compatible)
-RUN pecl install xdebug && docker-php-ext-enable xdebug
-
-# Install Composer for dependency management
-COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
-
-# Copy custom PHP configuration optimized for mLITE
-COPY ./php/php.ini /usr/local/etc/php/conf.d/custom.ini
-
-# Set working directory to mLITE application root
-WORKDIR /var/www/public
-
-# Copy and setup entrypoint script for mLITE initialization
-COPY ./php/entrypoint.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
-
-ENTRYPOINT ["/entrypoint.sh"]
-
-# Expose PHP-FPM port
-EXPOSE 9000
diff --git a/docker/php/Dockerfile-5.6 b/docker/php/Dockerfile-5.6
deleted file mode 100644
index f47275f83..000000000
--- a/docker/php/Dockerfile-5.6
+++ /dev/null
@@ -1,55 +0,0 @@
-# mLITE Docker PHP 5.6 Environment
-FROM php:5.6-fpm
-
-# Install system dependencies (use snapshot.debian.org for EOL stretch)
-RUN echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99snapshot && \
- echo 'Acquire::AllowInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99snapshot && \
- printf "deb [trusted=yes] http://snapshot.debian.org/archive/debian/20190302T000000Z stretch main\n" > /etc/apt/sources.list && \
- printf "deb [trusted=yes] http://snapshot.debian.org/archive/debian-security/20190302T000000Z stretch/updates main\n" >> /etc/apt/sources.list && \
- apt-get -o Acquire::Check-Valid-Until=false -o Acquire::AllowInsecureRepositories=true update && apt-get install -y --allow-unauthenticated \
- git \
- curl \
- libpng-dev \
- libjpeg-dev \
- libmcrypt-dev \
- libxml2-dev \
- libicu-dev \
- libfreetype6-dev \
- zlib1g-dev \
- libxslt-dev \
- libzip-dev \
- libssl-dev \
- zip \
- unzip && \
- apt-get clean && rm -rf /var/lib/apt/lists/*
-
-# PHP extensions
-RUN docker-php-ext-install \
- pdo_mysql \
- mysqli \
- mbstring \
- exif \
- pcntl \
- bcmath \
- intl \
- zip \
- opcache
-
-# GD with legacy flags (PHP < 7.4)
-RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
- docker-php-ext-install -j$(nproc) gd
-
-# Redis and Xdebug via PECL (compatible versions for PHP 5.6)
-RUN pecl channel-update pecl.php.net || true && pecl install redis-3.1.6 && docker-php-ext-enable redis
-RUN pecl install xdebug-2.5.5 && docker-php-ext-enable xdebug
-
-# Custom PHP config
-COPY ./php/php.ini /usr/local/etc/php/conf.d/custom.ini
-
-WORKDIR /var/www/public
-
-COPY ./php/entrypoint.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
-ENTRYPOINT ["/entrypoint.sh"]
-
-EXPOSE 9000
\ No newline at end of file
diff --git a/docker/php/Dockerfile-7.0 b/docker/php/Dockerfile-7.0
deleted file mode 100644
index 962f1f9b2..000000000
--- a/docker/php/Dockerfile-7.0
+++ /dev/null
@@ -1,58 +0,0 @@
-# mLITE Docker PHP 7.0 Environment
-FROM php:7.0-fpm
-
-# Install system dependencies (use snapshot.debian.org for EOL stretch)
-RUN echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99snapshot && \
- echo 'Acquire::AllowInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99snapshot && \
- printf "deb [trusted=yes] http://snapshot.debian.org/archive/debian/20190302T000000Z stretch main\n" > /etc/apt/sources.list && \
- printf "deb [trusted=yes] http://snapshot.debian.org/archive/debian-security/20190302T000000Z stretch/updates main\n" >> /etc/apt/sources.list && \
- apt-get -o Acquire::Check-Valid-Until=false -o Acquire::AllowInsecureRepositories=true update && apt-get install -y --allow-unauthenticated \
- git \
- curl \
- libpng-dev \
- libjpeg-dev \
- libonig-dev \
- libxml2-dev \
- libzip-dev \
- libicu-dev \
- libfreetype6-dev \
- zlib1g-dev \
- zip \
- unzip && \
- apt-get clean && rm -rf /var/lib/apt/lists/*
-
-# PHP extensions
-RUN docker-php-ext-install \
- pdo_mysql \
- mysqli \
- mbstring \
- exif \
- pcntl \
- bcmath \
- intl \
- zip \
- opcache
-
-# GD with legacy flags
-RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
- docker-php-ext-install -j$(nproc) gd
-
-# Redis and Xdebug via PECL (compatible versions)
-RUN pecl install redis-5.3.7 && docker-php-ext-enable redis
-RUN pecl install xdebug-2.6.1 && docker-php-ext-enable xdebug
-
-# Composer v1 (compatible with PHP 7.0)
-COPY --from=composer:1 /usr/bin/composer /usr/bin/composer
-
-# Custom PHP config
-COPY ./php/php.ini /usr/local/etc/php/conf.d/custom.ini
-
-WORKDIR /var/www/public
-
-# Entrypoint
-COPY ./php/entrypoint.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
-ENTRYPOINT ["/entrypoint.sh"]
-
-# Expose PHP-FPM port
-EXPOSE 9000
\ No newline at end of file
diff --git a/docker/php/Dockerfile-7.1 b/docker/php/Dockerfile-7.1
deleted file mode 100644
index 51297aaf3..000000000
--- a/docker/php/Dockerfile-7.1
+++ /dev/null
@@ -1,55 +0,0 @@
-# mLITE Docker PHP 7.1 Environment
-FROM php:7.1-fpm
-
-# Install system dependencies (use snapshot.debian.org for EOL buster)
-RUN echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99snapshot && \
- echo 'Acquire::AllowInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99snapshot && \
- printf "deb [trusted=yes] http://snapshot.debian.org/archive/debian/20210701T000000Z buster main\n" > /etc/apt/sources.list && \
- printf "deb [trusted=yes] http://snapshot.debian.org/archive/debian-security/20210701T000000Z buster/updates main\n" >> /etc/apt/sources.list && \
- apt-get -o Acquire::Check-Valid-Until=false -o Acquire::AllowInsecureRepositories=true update && apt-get install -y \
- git \
- curl \
- libpng-dev \
- libjpeg-dev \
- libonig-dev \
- libxml2-dev \
- libzip-dev \
- libicu-dev \
- libfreetype6-dev \
- zlib1g-dev \
- zip \
- unzip && \
- apt-get clean && rm -rf /var/lib/apt/lists/*
-
-# PHP extensions
-RUN docker-php-ext-install \
- pdo_mysql \
- mysqli \
- mbstring \
- exif \
- pcntl \
- bcmath \
- intl \
- zip \
- opcache
-
-# GD legacy flags
-RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
- docker-php-ext-install -j$(nproc) gd
-
-# Redis and Xdebug via PECL
-RUN pecl install redis-5.3.7 && docker-php-ext-enable redis
-RUN pecl install xdebug-2.9.8 && docker-php-ext-enable xdebug
-
-# Composer v1
-COPY --from=composer:1 /usr/bin/composer /usr/bin/composer
-
-COPY ./php/php.ini /usr/local/etc/php/conf.d/custom.ini
-
-WORKDIR /var/www/public
-
-COPY ./php/entrypoint.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
-ENTRYPOINT ["/entrypoint.sh"]
-
-EXPOSE 9000
\ No newline at end of file
diff --git a/docker/php/Dockerfile-7.2 b/docker/php/Dockerfile-7.2
deleted file mode 100644
index 9f81c7271..000000000
--- a/docker/php/Dockerfile-7.2
+++ /dev/null
@@ -1,57 +0,0 @@
-# mLITE Docker PHP 7.2 Environment
-FROM php:7.2-fpm
-
-# Install system dependencies (use snapshot.debian.org for EOL buster)
-RUN echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99snapshot && \
- echo 'Acquire::AllowInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99snapshot && \
- printf "deb [trusted=yes] http://snapshot.debian.org/archive/debian/20210701T000000Z buster main\n" > /etc/apt/sources.list && \
- printf "deb [trusted=yes] http://snapshot.debian.org/archive/debian-security/20210701T000000Z buster/updates main\n" >> /etc/apt/sources.list && \
- apt-get -o Acquire::Check-Valid-Until=false -o Acquire::AllowInsecureRepositories=true update && apt-get install -y \
- git \
- curl \
- libpng-dev \
- libjpeg-dev \
- libonig-dev \
- libxml2-dev \
- libzip-dev \
- libicu-dev \
- libfreetype6-dev \
- zlib1g-dev \
- zip \
- unzip && \
- apt-get clean && rm -rf /var/lib/apt/lists/*
-
-# PHP extensions
-RUN docker-php-ext-install \
- pdo_mysql \
- mysqli \
- mbstring \
- exif \
- pcntl \
- bcmath \
- intl \
- zip \
- opcache
-
-# GD legacy flags
-RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
- docker-php-ext-install -j$(nproc) gd
-
-# Redis and Xdebug via PECL
-RUN pecl install redis-5.3.7 && docker-php-ext-enable redis
-RUN pecl install xdebug-2.9.8 && docker-php-ext-enable xdebug
-
-# Composer v1 (works for PHP 7.2)
-COPY --from=composer:1 /usr/bin/composer /usr/bin/composer
-
-COPY ./php/php.ini /usr/local/etc/php/conf.d/custom.ini
-
-WORKDIR /var/www/public
-
-# Entrypoint
-COPY ./php/entrypoint.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
-ENTRYPOINT ["/entrypoint.sh"]
-
-# Expose PHP-FPM port
-EXPOSE 9000
\ No newline at end of file
diff --git a/docker/php/Dockerfile-7.3 b/docker/php/Dockerfile-7.3
deleted file mode 100644
index 1d2aecfd9..000000000
--- a/docker/php/Dockerfile-7.3
+++ /dev/null
@@ -1,55 +0,0 @@
-# mLITE Docker PHP 7.3 Environment
-FROM php:7.3-fpm
-
-# Install system dependencies (use snapshot.debian.org for bullseye)
-RUN echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99snapshot && \
- echo 'Acquire::AllowInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99snapshot && \
- printf "deb [trusted=yes] http://snapshot.debian.org/archive/debian/20220101T000000Z bullseye main\n" > /etc/apt/sources.list && \
- printf "deb [trusted=yes] http://snapshot.debian.org/archive/debian-security/20220101T000000Z bullseye-security main\n" >> /etc/apt/sources.list && \
- apt-get -o Acquire::Check-Valid-Until=false -o Acquire::AllowInsecureRepositories=true update && apt-get install -y \
- git \
- curl \
- libpng-dev \
- libjpeg-dev \
- libonig-dev \
- libxml2-dev \
- libzip-dev \
- libicu-dev \
- libfreetype6-dev \
- zlib1g-dev \
- zip \
- unzip && \
- apt-get clean && rm -rf /var/lib/apt/lists/*
-
-# PHP extensions
-RUN docker-php-ext-install \
- pdo_mysql \
- mysqli \
- mbstring \
- exif \
- pcntl \
- bcmath \
- intl \
- zip \
- opcache
-
-# GD legacy flags
-RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
- docker-php-ext-install -j$(nproc) gd
-
-# Redis and Xdebug via PECL
-RUN pecl install redis-5.3.7 && docker-php-ext-enable redis
-RUN pecl install xdebug-3.1.5 && docker-php-ext-enable xdebug
-
-# Composer v1 (works for PHP 7.3)
-COPY --from=composer:1 /usr/bin/composer /usr/bin/composer
-
-COPY ./php/php.ini /usr/local/etc/php/conf.d/custom.ini
-
-WORKDIR /var/www/public
-
-COPY ./php/entrypoint.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
-ENTRYPOINT ["/entrypoint.sh"]
-
-EXPOSE 9000
\ No newline at end of file
diff --git a/docker/php/Dockerfile-7.4 b/docker/php/Dockerfile-7.4
deleted file mode 100644
index ef6bcce7f..000000000
--- a/docker/php/Dockerfile-7.4
+++ /dev/null
@@ -1,59 +0,0 @@
-# mLITE Docker PHP 7.4 Environment
-# Compatible with mLITE PHP 7.4 requirements
-FROM php:7.4-fpm
-
-# Install system dependencies required for mLITE
-RUN apt-get update && apt-get install -y \
- git \
- curl \
- libpng-dev \
- libjpeg-dev \
- libonig-dev \
- libxml2-dev \
- libzip-dev \
- libicu-dev \
- libfreetype6-dev \
- zip \
- unzip && \
- apt-get clean && rm -rf /var/lib/apt/lists/*
-
-# Install PHP extensions required by mLITE
-RUN docker-php-ext-install \
- pdo_mysql \
- mysqli \
- mbstring \
- exif \
- pcntl \
- bcmath \
- gd \
- intl \
- zip \
- opcache
-
-RUN docker-php-ext-enable mysqli
-
-# Configure GD extension with FreeType and JPEG support
-RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \
- docker-php-ext-install -j$(nproc) gd
-
-# Install Redis and Xdebug via PECL
-RUN pecl install redis && docker-php-ext-enable redis
-# Xdebug 3.1.5 is the last compatible with PHP 7.4
-RUN pecl install xdebug-3.1.5 && docker-php-ext-enable xdebug
-
-# Install Composer
-COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
-
-# Copy custom PHP configuration
-COPY ./php/php.ini /usr/local/etc/php/conf.d/custom.ini
-
-# Working directory
-WORKDIR /var/www/public
-
-# Entrypoint
-COPY ./php/entrypoint.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
-ENTRYPOINT ["/entrypoint.sh"]
-
-# Expose PHP-FPM port
-EXPOSE 9000
\ No newline at end of file
diff --git a/docker/php/Dockerfile-8.0 b/docker/php/Dockerfile-8.0
deleted file mode 100644
index 3dcc17c73..000000000
--- a/docker/php/Dockerfile-8.0
+++ /dev/null
@@ -1,58 +0,0 @@
-# mLITE Docker PHP 8.0 Environment
-# Compatible with mLITE PHP requirements
-FROM php:8.0-fpm
-
-# Install system dependencies required for mLITE
-RUN apt-get update && apt-get install -y \
- git \
- curl \
- libpng-dev \
- libjpeg-dev \
- libonig-dev \
- libxml2-dev \
- libzip-dev \
- libicu-dev \
- libfreetype6-dev \
- zip \
- unzip && \
- apt-get clean && rm -rf /var/lib/apt/lists/*
-
-# Install PHP extensions required by mLITE
-RUN docker-php-ext-install \
- pdo_mysql \
- mysqli \
- mbstring \
- exif \
- pcntl \
- bcmath \
- gd \
- intl \
- zip \
- opcache
-
-RUN docker-php-ext-enable mysqli
-
-# Configure GD extension with FreeType and JPEG support
-RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \
- docker-php-ext-install -j$(nproc) gd
-
-# Install Redis and Xdebug via PECL
-RUN pecl install redis && docker-php-ext-enable redis
-RUN pecl install xdebug && docker-php-ext-enable xdebug
-
-# Install Composer
-COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
-
-# Copy custom PHP configuration
-COPY ./php/php.ini /usr/local/etc/php/conf.d/custom.ini
-
-# Working directory
-WORKDIR /var/www/public
-
-# Entrypoint
-COPY ./php/entrypoint.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
-ENTRYPOINT ["/entrypoint.sh"]
-
-# Expose PHP-FPM port
-EXPOSE 9000
\ No newline at end of file
diff --git a/docker/php/Dockerfile-8.1 b/docker/php/Dockerfile-8.1
deleted file mode 100644
index b97b6119b..000000000
--- a/docker/php/Dockerfile-8.1
+++ /dev/null
@@ -1,41 +0,0 @@
-# mLITE Docker PHP 8.1 Environment
-FROM php:8.1-fpm
-
-RUN apt-get update && apt-get install -y \
- git \
- curl \
- libpng-dev \
- libjpeg-dev \
- libonig-dev \
- libxml2-dev \
- libzip-dev \
- libicu-dev \
- libfreetype6-dev \
- zip \
- unzip && \
- apt-get clean && rm -rf /var/lib/apt/lists/*
-
-RUN docker-php-ext-install \
- pdo_mysql \
- mysqli \
- mbstring \
- exif \
- pcntl \
- bcmath \
- gd \
- intl \
- zip \
- opcache && \
- docker-php-ext-enable mysqli
-
-RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \
- docker-php-ext-install -j$(nproc) gd
-
-RUN pecl install redis && docker-php-ext-enable redis
-RUN pecl install xdebug && docker-php-ext-enable xdebug
-
-COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
-COPY ./php/php.ini /usr/local/etc/php/conf.d/custom.ini
-
-WORKDIR /var/www/public
-COPY ./php/entrypoint.sh /entry
\ No newline at end of file
diff --git a/docker/php/Dockerfile-8.2 b/docker/php/Dockerfile-8.2
deleted file mode 100644
index af1a69ca8..000000000
--- a/docker/php/Dockerfile-8.2
+++ /dev/null
@@ -1,42 +0,0 @@
-# mLITE Docker PHP 8.2 Environment
-FROM php:8.2-fpm
-
-RUN apt-get update && apt-get install -y \
- git \
- curl \
- libpng-dev \
- libjpeg-dev \
- libonig-dev \
- libxml2-dev \
- libzip-dev \
- libicu-dev \
- libfreetype6-dev \
- zip \
- unzip && \
- apt-get clean && rm -rf /var/lib/apt/lists/*
-
-RUN docker-php-ext-install \
- pdo_mysql \
- mysqli \
- mbstring \
- exif \
- pcntl \
- bcmath \
- gd \
- intl \
- zip \
- opcache && \
- docker-php-ext-enable mysqli
-
-RUN docker-php-ext-configure gd --with-freetype --with-jpeg && \
- docker-php-ext-install -j$(nproc) gd
-
-RUN pecl install redis && docker-php-ext-enable redis
-RUN pecl install xdebug && docker-php-ext-enable xdebug
-
-COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
-COPY ./php/php.ini /usr/local/etc/php/conf.d/custom.ini
-
-WORKDIR /var/www/public
-COPY ./php/entrypoint.sh /entrypoint.sh
-RUN
\ No newline at end of file
diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh
deleted file mode 100644
index 5afbee994..000000000
--- a/docker/php/entrypoint.sh
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/bash
-# mLITE Docker Entrypoint Script
-# Initializes mLITE application environment for PHP-FPM across versions
-
-set -e
-
-echo "🔧 Initializing mLITE environment..."
-
-# Ensure correct working directory
-cd /var/www/public || exit 1
-
-# --- Folder setup ---
-for dir in uploads tmp backups admin/tmp; do
- mkdir -p "/var/www/public/${dir}"
- chmod -R 777 "/var/www/public/${dir}"
-done
-
-# --- Update database config ---
-if [ -f /var/www/public/config.php ]; then
- echo "⚙️ Updating config.php database host to 'mysql'..."
- sed -i 's/localhost/mysql/g' /var/www/public/config.php
-fi
-
-# --- Composer install (only if vendor folder missing and composer exists) ---
-if [ ! -d /var/www/public/vendor ]; then
- if command -v composer >/dev/null 2>&1; then
- echo "📦 Installing Composer dependencies..."
- composer install --no-dev --optimize-autoloader || true
- else
- echo "ℹ️ Composer not available in this image, skipping install."
- fi
-else
- echo "✅ Composer dependencies already installed, skipping."
-fi
-
-# --- Fix mPDF temp directory permissions (if exists) ---
-if [ -d /var/www/public/vendor/mpdf/mpdf/tmp ]; then
- chmod -R 777 /var/www/public/vendor/mpdf/mpdf/tmp
-fi
-
-echo "🚀 Starting PHP-FPM..."
-exec php-fpm
diff --git a/docker/php/php.ini b/docker/php/php.ini
deleted file mode 100644
index 3ec882dca..000000000
--- a/docker/php/php.ini
+++ /dev/null
@@ -1,30 +0,0 @@
-; mLITE PHP 8.3 Configuration
-; Optimized for mLITE application performance and compatibility
-
-[PHP]
-memory_limit = 256M ; Sufficient memory for mLITE operations
-max_execution_time = 60 ; Adequate time for report generation
-upload_max_filesize = 64M ; Support for large file uploads (medical documents)
-post_max_size = 64M ; Match upload file size limit
-
-display_errors = On ; Enable for development (disable in production)
-display_startup_errors = On ; Show startup errors for debugging
-log_errors = On ; Always log errors for troubleshooting
-error_reporting = E_ALL ; Report all errors for PHP 8.3 compatibility
-
-[Date]
-date.timezone = UTC ; Default timezone (adjust as needed for mLITE)
-
-[opcache]
-; OPcache configuration optimized for mLITE PHP 8.3 performance
-opcache.enable = 1 ; Enable OPcache for better performance
-opcache.memory_consumption = 256 ; Memory allocated for OPcache
-opcache.interned_strings_buffer = 16 ; String interning buffer
-opcache.max_accelerated_files = 16229 ; Maximum files to cache
-
-[xdebug]
-; Xdebug 3.x configuration for PHP 8.3 development debugging
-xdebug.mode = develop,debug ; Enable development and debugging modes
-xdebug.client_host = host.docker.internal ; Connect to host machine for IDE debugging
-xdebug.start_with_request = yes ; Auto-start debugging sessions
-xdebug.log = /tmp/xdebug.log ; Xdebug log file for troubleshooting
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index 91f9c2030..4e5df0a1b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -290,7 +290,8 @@ File paling penting untuk setiap modul. Ini berisi informasi dasar dan instruksi
'description' => 'Lorem ipsum....',
'author' => 'Basoro',
'version' => '1.0',
- 'compatibility' => '2022*', // Kesesuaian dengan versi mLITE
+ 'category' => 'main',
+ 'compatibility' => '6.0.0*', // Kesesuaian dengan versi mLITE
'icon' => 'bolt',
'pages' => ['Contoh' => 'contoh'], // Berfungsi sebagai halaman (opsional)
'install' => function() use($core) // Perintah install
diff --git a/index.php b/index.php
index ed3712092..07f1553e6 100644
--- a/index.php
+++ b/index.php
@@ -7,9 +7,16 @@
$_SERVER['HTTPS'] = 'on';
}
-define('BASE_DIR', __DIR__);
+if (!defined('BASE_DIR')) {
+ define('BASE_DIR', __DIR__);
+}
require_once('config.php');
+if (!file_exists(BASE_DIR . '/.env')) {
+ header('Location: /install.php');
+ exit;
+}
+
// Error reporting configuration
if (DEV_MODE) {
error_reporting(E_ALL);
@@ -28,14 +35,31 @@
try {
$core = new Systems\Site();
} catch (Throwable $e) {
- if (DEV_MODE) {
- throw $e;
- } else {
- error_log('mLITE Error: ' . $e->getMessage());
- http_response_code(500);
- echo 'Internal Server Error';
+ error_log('mLITE Error: ' . $e->getMessage());
+ http_response_code(500);
+ $message = 'System Error. Please contact administrator.';
+
+ // Auto-rescue untuk lingkungan PaaS (Papuyu/Docker) jika .env terbuat tapi database belum di-import
+ $errMsg = $e->getMessage();
+ if (strpos($errMsg, 'Base table or view not found') !== false || strpos($errMsg, 'no such table') !== false || strpos($errMsg, 'Unknown database') !== false) {
+ header('Location: /install.php');
exit;
}
+
+ $response = ['status' => 'error', 'message' => $message];
+
+ if (DEV_MODE) {
+ $devMessage = preg_replace('/`[^`]+`\./', '', $e->getMessage());
+ $response['message'] .= ' Detail: ' . $devMessage;
+ $response['debug'] = [
+ 'file' => $e->getFile(),
+ 'line' => $e->getLine(),
+ 'trace' => explode("\n", $e->getTraceAsString())
+ ];
+ }
+
+ echo json_encode($response);
+ exit;
}
ob_end_flush();
diff --git a/install.php b/install.php
new file mode 100644
index 000000000..e14593820
--- /dev/null
+++ b/install.php
@@ -0,0 +1,584 @@
+setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ @chmod($outputFile, 0666);
+
+ $pdo->exec("PRAGMA synchronous = OFF");
+ $pdo->exec("PRAGMA journal_mode = MEMORY");
+ $pdo->exec("PRAGMA strict = OFF");
+
+ $sqlContent = file_get_contents($inputFile);
+
+ $sqlContent = preg_replace('/^--.*$/m', '', $sqlContent);
+ $sqlContent = preg_replace('/^\/\*.*\*\/$/m', '', $sqlContent);
+ $sqlContent = preg_replace('/^SET .*$/m', '', $sqlContent);
+
+ $statements = preg_split('/;\s*\n/', $sqlContent);
+
+ foreach ($statements as $statement) {
+ $statement = trim($statement);
+ if (empty($statement))
+ continue;
+
+ if (stripos($statement, 'CREATE TABLE') === 0) {
+ processCreateTable($pdo, $statement);
+ } elseif (stripos($statement, 'INSERT INTO') === 0) {
+ processInsert($pdo, $statement);
+ }
+ }
+
+ // Create .env
+ $envData = "DBDRIVER=sqlite\n";
+ file_put_contents($envFile, $envData);
+
+ $success = "SQLite Database successfully generated!";
+ // header('Location: /');
+ // exit;
+
+
+ } catch (Exception $e) {
+ $error = $e->getMessage();
+ }
+ } elseif ($driver === 'mysql') {
+ $host = $_POST['db_host'] ?? 'localhost';
+ $port = $_POST['db_port'] ?? '3306';
+ $dbName = $_POST['db_name'] ?? 'mlite';
+ $user = $_POST['db_user'] ?? 'root';
+ $pass = $_POST['db_pass'] ?? '';
+
+ try {
+ ini_set('memory_limit', '1G');
+
+ // Connect to MySQL server without DB first
+ $dsnServer = "mysql:host=$host;port=$port;charset=utf8mb4";
+ $pdoServer = new PDO($dsnServer, $user, $pass);
+ $pdoServer->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+ // Create database if not exists
+ $pdoServer->exec("CREATE DATABASE IF NOT EXISTS `$dbName` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
+
+ // Connect to the specific database
+ $dsn = "mysql:host=$host;port=$port;dbname=$dbName;charset=utf8mb4";
+ $pdo = new PDO($dsn, $user, $pass);
+ $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+ // Check if tables exist
+ $stmt = $pdo->query("SHOW TABLES");
+ if ($stmt->rowCount() > 0) {
+ throw new Exception("Database sudah ada datanya. Silakan gunakan database yang kosong atau hapus tabel terlebih dahulu.");
+ }
+
+ // Execute SQL Dump
+ $inputFile = $baseDir . '/mlite_db.sql';
+ $sqlContent = file_get_contents($inputFile);
+
+ // Turn off foreign key checks for import
+ $pdo->exec("SET foreign_key_checks = 0;");
+
+ // Clean up to basic statements
+ $sqlContent = preg_replace('/^--.*$/m', '', $sqlContent);
+ $sqlContent = preg_replace('/^\/\*.*\*\/$/m', '', $sqlContent);
+
+ // Multi statement queries can be heavy, splitting by ;
+ $statements = preg_split('/;\s*\n/', $sqlContent);
+ foreach ($statements as $statement) {
+ $statement = trim($statement);
+ if (empty($statement))
+ continue;
+ $pdo->exec($statement);
+ }
+
+ $pdo->exec("SET foreign_key_checks = 1;");
+
+ // Create .env
+ $envData = "DBDRIVER=mysql\n";
+ $envData .= "MYSQLHOST=$host\n";
+ $envData .= "MYSQLPORT=$port\n";
+ $envData .= "MYSQLDATABASE=$dbName\n";
+ $envData .= "MYSQLUSER=$user\n";
+ $envData .= "MYSQLPASSWORD=$pass\n";
+
+ file_put_contents($envFile, $envData);
+
+ $success = "MySQL Database successfully installed!";
+ // header('Location: /');
+ // exit;
+ } catch (Exception $e) {
+ $error = $e->getMessage();
+ }
+ }
+}
+
+// Helper Functions for SQLite parsing
+function processCreateTable($pdo, $sql)
+{
+ if (!preg_match('/CREATE TABLE `?(\w+)`?/i', $sql, $matches))
+ return;
+ $tableName = $matches[1];
+
+ if (!preg_match('/\((.*)\)[^\)]*$/s', $sql, $matches))
+ return;
+ $body = $matches[1];
+
+ $pkColumn = null;
+ if (preg_match('/PRIMARY KEY\s*\(`?([^`\)]+)`?\)/', $body, $matches)) {
+ $pkColumn = $matches[1];
+ if (strpos($pkColumn, ',') !== false) {
+ $pkColumn = null;
+ }
+ }
+
+ $lines = explode("\n", $body);
+ $columnDefs = [];
+ $constraints = [];
+ $indexes = [];
+
+ foreach ($lines as $line) {
+ $line = trim($line);
+ if ($line === '' || $line === ')')
+ continue;
+ if (substr($line, -1) === ',')
+ $line = substr($line, 0, -1);
+
+ if (preg_match('/^(?:UNIQUE )?KEY `?(\w+)`?\s*\((.*)\)/i', $line, $m)) {
+ $isUnique = (stripos($line, 'UNIQUE') === 0);
+ $indexName = $m[1];
+ $cols = str_replace('`', '', $m[2]);
+ $cols = preg_replace('/\(\d+\)/', '', $cols);
+ $indexes[] = "CREATE " . ($isUnique ? "UNIQUE " : "") . "INDEX IF NOT EXISTS `idx_{$tableName}_{$indexName}` ON `$tableName` ($cols)";
+ continue;
+ }
+
+ if (stripos($line, 'PRIMARY KEY') === 0) {
+ if (!$pkColumn) {
+ $line = preg_replace('/\s+USING\s+\w+/i', '', $line);
+ $constraints[] = $line;
+ }
+ continue;
+ }
+
+ if (stripos($line, 'CONSTRAINT') === 0) {
+ $line = preg_replace('/CONSTRAINT `\w+`/', '', $line);
+ $line = preg_replace('/ON UPDATE CURRENT_TIMESTAMP/i', '', $line);
+ $constraints[] = $line;
+ continue;
+ }
+
+ if (preg_match('/^`?(\w+)`?\s+(.*)$/', $line, $m)) {
+ $colName = $m[1];
+ $def = $m[2];
+
+ $def = preg_replace('/int\(\d+\)/i', 'INTEGER', $def);
+ $def = preg_replace('/tinyint\(\d+\)/i', 'INTEGER', $def);
+ $def = preg_replace('/smallint\(\d+\)/i', 'INTEGER', $def);
+ $def = preg_replace('/bigint\(\d+\)/i', 'INTEGER', $def);
+ $def = preg_replace('/double(\(.*\))?/i', 'REAL', $def);
+ $def = preg_replace('/float(\(.*\))?/i', 'REAL', $def);
+ $def = preg_replace('/decimal(\(.*\))?/i', 'REAL', $def);
+ $def = preg_replace('/varchar\(\d+\)/i', 'TEXT', $def);
+ $def = preg_replace('/char\(\d+\)/i', 'TEXT', $def);
+ $def = preg_replace('/text/i', 'TEXT', $def);
+ $def = preg_replace('/longtext/i', 'TEXT', $def);
+ $def = preg_replace('/mediumtext/i', 'TEXT', $def);
+ $def = preg_replace('/enum\(.*\)/i', 'TEXT', $def);
+ $def = preg_replace('/set\(.*\)/i', 'TEXT', $def);
+ $def = preg_replace('/date/i', 'TEXT', $def);
+ $def = preg_replace('/datetime/i', 'TEXT', $def);
+ $def = preg_replace('/time/i', 'TEXT', $def);
+ $def = preg_replace('/timestamp/i', 'TEXT', $def);
+ $def = preg_replace('/blob/i', 'BLOB', $def);
+ $def = preg_replace('/longblob/i', 'BLOB', $def);
+
+ if (stripos($def, 'AUTO_INCREMENT') !== false) {
+ $def = str_ireplace('AUTO_INCREMENT', '', $def);
+ if ($colName === $pkColumn) {
+ $def = preg_replace('/(int|integer)\s+(not\s+null)?/i', 'INTEGER PRIMARY KEY AUTOINCREMENT', $def);
+ }
+ }
+
+ $def = str_ireplace('UNSIGNED', '', $def);
+ $def = str_ireplace('ZEROFILL', '', $def);
+ $def = preg_replace('/ON\s+UPDATE\s+CURRENT_TIMESTAMP/i', '', $def);
+ $def = str_ireplace('CURRENT_TIMESTAMP', 'CURRENT_TIMESTAMP', $def);
+
+ if ($colName === $pkColumn && stripos($def, 'PRIMARY KEY') === false) {
+ $def .= ' PRIMARY KEY';
+ }
+
+ $columnDefs[] = "`$colName` $def";
+ }
+ }
+
+ $createSql = "CREATE TABLE `$tableName` (\n" . implode(",\n", array_merge($columnDefs, $constraints)) . "\n)";
+
+ try {
+ $pdo->exec($createSql);
+ } catch (PDOException $e) {
+ }
+
+ foreach ($indexes as $idxSql) {
+ try {
+ $pdo->exec($idxSql);
+ } catch (PDOException $e) {
+ }
+ }
+}
+
+function processInsert($pdo, $sql)
+{
+ $sql = str_replace('\\"', '##DQ##', $sql);
+ $sql = str_replace("'", "''", $sql);
+ $sql = str_replace('"', "'", $sql);
+ $sql = str_replace('##DQ##', '"', $sql);
+ $sql = str_replace('\\\\', '\\', $sql);
+
+ try {
+ $pdo->exec($sql);
+ } catch (PDOException $e) {
+ }
+}
+
+?>
+
+
+
+
+
+
+
+ mLITE Installer
+
+
+
+
+
+
+
+
Instalasi database sedang berjalan...
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Error: = htmlspecialchars($error) ?>
+
+
+
+
+
+
Berhasil! = htmlspecialchars($success) ?>
+
+ ⚠️ PERINGATAN KEAMANAN
+ Harap segera HAPUS atau UBAH NAMA (rename) file install.php ini. Membiarkan file ini
+ ada di server dapat menimbulkan celah keamanan bagi aplikasi Anda.
+
+
+
Selesai & Buka Aplikasi
+
+
+
+ Database Driver
+
+ SQLite (Portable / Ringan)
+ MySQL / MariaDB
+
+
+
+
+
+ Mulai Instalasi
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/mlite_db.sql b/mlite_db.sql
index 32563ff22..6e01f7fe3 100644
--- a/mlite_db.sql
+++ b/mlite_db.sql
@@ -1,9 +1,12 @@
--- CREATE DATABASE IF NOT EXISTS `mlite_520`;
+-- CREATE DATABASE IF NOT EXISTS `mlite`;
+
+
+-- USE `mlite`;
--- USE `mlite_520`;
SET sql_mode = '';
+
SET foreign_key_checks = 0;
@@ -18,7 +21,7 @@ CREATE TABLE `aturan_pakai` (
KEY `kode_brng` (`kode_brng`),
CONSTRAINT `aturan_pakai_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `aturan_pakai_ibfk_2` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `bahasa_pasien` (
@@ -26,7 +29,9 @@ CREATE TABLE `bahasa_pasien` (
`nama_bahasa` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `nama_bahasa` (`nama_bahasa`) USING BTREE
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `bahasa_pasien` VALUES ("1","-");
@@ -37,7 +42,9 @@ CREATE TABLE `bangsal` (
PRIMARY KEY (`kd_bangsal`),
KEY `nm_bangsal` (`nm_bangsal`),
KEY `status` (`status`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `bangsal` VALUES ("-","-","1"),
("ANG","Anggrek","1"),
("APT","Apotek","1"),
@@ -47,7 +54,9 @@ INSERT INTO `bangsal` VALUES ("-","-","1"),
CREATE TABLE `bank` (
`namabank` varchar(50) NOT NULL,
PRIMARY KEY (`namabank`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `bank` VALUES ("-"),
("T");
@@ -58,7 +67,7 @@ CREATE TABLE `barcode` (
PRIMARY KEY (`id`),
UNIQUE KEY `barcode` (`barcode`),
CONSTRAINT `barcode_ibfk_1` FOREIGN KEY (`id`) REFERENCES `pegawai` (`id`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `beri_obat_operasi` (
@@ -74,7 +83,7 @@ CREATE TABLE `beri_obat_operasi` (
KEY `jumlah` (`jumlah`),
CONSTRAINT `beri_obat_operasi_ibfk_2` FOREIGN KEY (`kd_obat`) REFERENCES `obatbhp_ok` (`kd_obat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `beri_obat_operasi_ibfk_3` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `berkas_digital_perawatan` (
@@ -85,13 +94,15 @@ CREATE TABLE `berkas_digital_perawatan` (
KEY `kode` (`kode`),
CONSTRAINT `berkas_digital_perawatan_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `berkas_digital_perawatan_ibfk_2` FOREIGN KEY (`kode`) REFERENCES `master_berkas_digital` (`kode`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `bidang` (
`nama` varchar(15) NOT NULL,
PRIMARY KEY (`nama`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `bidang` VALUES ("-");
@@ -112,7 +123,7 @@ CREATE TABLE `booking_operasi` (
CONSTRAINT `booking_operasi_ibfk_2` FOREIGN KEY (`kode_paket`) REFERENCES `paket_operasi` (`kode_paket`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `booking_operasi_ibfk_3` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `booking_operasi_ibfk_4` FOREIGN KEY (`kd_ruang_ok`) REFERENCES `ruang_ok` (`kd_ruang_ok`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `booking_periksa` (
@@ -130,7 +141,7 @@ CREATE TABLE `booking_periksa` (
UNIQUE KEY `tanggal` (`tanggal`,`no_telp`),
KEY `kd_poli` (`kd_poli`),
CONSTRAINT `booking_periksa_ibfk_1` FOREIGN KEY (`kd_poli`) REFERENCES `poliklinik` (`kd_poli`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `booking_periksa_balasan` (
@@ -138,7 +149,7 @@ CREATE TABLE `booking_periksa_balasan` (
`balasan` varchar(200) DEFAULT NULL,
PRIMARY KEY (`no_booking`),
CONSTRAINT `booking_periksa_balasan_ibfk_1` FOREIGN KEY (`no_booking`) REFERENCES `booking_periksa` (`no_booking`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `booking_periksa_diterima` (
@@ -148,7 +159,7 @@ CREATE TABLE `booking_periksa_diterima` (
KEY `no_rkm_medis` (`no_rkm_medis`),
CONSTRAINT `booking_periksa_diterima_ibfk_1` FOREIGN KEY (`no_booking`) REFERENCES `booking_periksa` (`no_booking`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `booking_periksa_diterima_ibfk_2` FOREIGN KEY (`no_rkm_medis`) REFERENCES `pasien` (`no_rkm_medis`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `booking_registrasi` (
@@ -172,7 +183,7 @@ CREATE TABLE `booking_registrasi` (
CONSTRAINT `booking_registrasi_ibfk_2` FOREIGN KEY (`kd_poli`) REFERENCES `poliklinik` (`kd_poli`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `booking_registrasi_ibfk_3` FOREIGN KEY (`kd_pj`) REFERENCES `penjab` (`kd_pj`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `booking_registrasi_ibfk_4` FOREIGN KEY (`no_rkm_medis`) REFERENCES `pasien` (`no_rkm_medis`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `bpjs_prb` (
@@ -180,7 +191,7 @@ CREATE TABLE `bpjs_prb` (
`prb` varchar(50) DEFAULT NULL,
PRIMARY KEY (`no_sep`),
CONSTRAINT `bpjs_prb_ibfk_1` FOREIGN KEY (`no_sep`) REFERENCES `bridging_sep` (`no_sep`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `bridging_rujukan_bpjs` (
@@ -201,7 +212,7 @@ CREATE TABLE `bridging_rujukan_bpjs` (
PRIMARY KEY (`no_rujukan`),
KEY `no_sep` (`no_sep`),
CONSTRAINT `bridging_rujukan_bpjs_ibfk_1` FOREIGN KEY (`no_sep`) REFERENCES `bridging_sep` (`no_sep`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `bridging_sep` (
@@ -260,7 +271,7 @@ CREATE TABLE `bridging_sep` (
PRIMARY KEY (`no_sep`),
KEY `no_rawat` (`no_rawat`),
CONSTRAINT `bridging_sep_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `bridging_sep_internal` (
@@ -320,7 +331,7 @@ CREATE TABLE `bridging_sep_internal` (
KEY `no_sep` (`no_sep`),
CONSTRAINT `bridging_sep_internal_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `bridging_sep_internal_ibfk_2` FOREIGN KEY (`no_sep`) REFERENCES `bridging_sep` (`no_sep`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `bridging_srb_bpjs` (
@@ -338,7 +349,7 @@ CREATE TABLE `bridging_srb_bpjs` (
`saran` varchar(100) DEFAULT NULL,
PRIMARY KEY (`no_sep`,`no_srb`),
CONSTRAINT `bridging_srb_bpjs_ibfk_1` FOREIGN KEY (`no_sep`) REFERENCES `bridging_sep` (`no_sep`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `bridging_surat_kontrol_bpjs` (
@@ -353,7 +364,7 @@ CREATE TABLE `bridging_surat_kontrol_bpjs` (
PRIMARY KEY (`no_surat`),
KEY `bridging_surat_kontrol_bpjs_ibfk_1` (`no_sep`),
CONSTRAINT `bridging_surat_kontrol_bpjs_ibfk_1` FOREIGN KEY (`no_sep`) REFERENCES `bridging_sep` (`no_sep`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `bridging_surat_pri_bpjs` (
@@ -371,7 +382,7 @@ CREATE TABLE `bridging_surat_pri_bpjs` (
PRIMARY KEY (`no_surat`),
KEY `no_rawat` (`no_rawat`),
CONSTRAINT `bridging_surat_pri_bpjs_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `cacat_fisik` (
@@ -379,25 +390,27 @@ CREATE TABLE `cacat_fisik` (
`nama_cacat` varchar(30) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `nama_cacat` (`nama_cacat`) USING BTREE
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `cacat_fisik` VALUES ("1","-");
CREATE TABLE `catatan_adime_gizi` (
`no_rawat` varchar(17) NOT NULL,
`tanggal` datetime NOT NULL,
- `asesmen` varchar(1000) DEFAULT NULL,
- `diagnosis` varchar(1000) DEFAULT NULL,
- `intervensi` varchar(1000) DEFAULT NULL,
- `monitoring` varchar(1000) DEFAULT NULL,
- `evaluasi` varchar(1000) DEFAULT NULL,
- `instruksi` varchar(1000) DEFAULT NULL,
+ `asesmen` TEXT DEFAULT NULL,
+ `diagnosis` TEXT DEFAULT NULL,
+ `intervensi` TEXT DEFAULT NULL,
+ `monitoring` TEXT DEFAULT NULL,
+ `evaluasi` TEXT DEFAULT NULL,
+ `instruksi` TEXT DEFAULT NULL,
`nip` varchar(20) DEFAULT NULL,
PRIMARY KEY (`no_rawat`,`tanggal`) USING BTREE,
KEY `nip` (`nip`) USING BTREE,
CONSTRAINT `catatan_adime_gizi_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `catatan_adime_gizi_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `catatan_perawatan` (
@@ -410,7 +423,65 @@ CREATE TABLE `catatan_perawatan` (
KEY `kd_dokter` (`kd_dokter`),
CONSTRAINT `catatan_perawatan_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `catatan_perawatan_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `data_tb` (
+ `no_rawat` varchar(17) NOT NULL,
+ `id_tb_03` varchar(30) DEFAULT NULL,
+ `id_periode_laporan` enum('1=Januari - Maret','2=April - Juni','3=Juli - September','4=Oktober - Desember') DEFAULT NULL,
+ `tanggal_buat_laporan` datetime DEFAULT NULL,
+ `tahun_buat_laporan` year DEFAULT NULL,
+ `kd_wasor` int DEFAULT NULL,
+ `noregkab` int DEFAULT NULL,
+ `id_propinsi` varchar(15) DEFAULT NULL,
+ `kd_kabupaten` varchar(15) DEFAULT NULL,
+ `id_kecamatan` varchar(15) DEFAULT NULL,
+ `id_kelurahan` varchar(15) DEFAULT NULL,
+ `nama_rujukan` enum('Inisiatif pasien/Keluarga','Anggota Masyarakat/Kader','Faskes','Dokter Praktek Mandiri','Poli lain','Lain-lain') DEFAULT NULL,
+ `sebutkan1` varchar(100) DEFAULT NULL,
+ `tipe_diagnosis` enum('Terkonfirmasi bakteriologis','Terdiagnosis klinis') DEFAULT NULL,
+ `klasifikasi_lokasi_anatomi` enum('Paru','Ekstraparu') DEFAULT NULL,
+ `klasifikasi_riwayat_pengobatan` enum('Baru','Kambuh','Diobati setelah gagal','Diobati Setelah Putus Berobat','Lain-lain','Riwayat Pengobatan Sebelumnya Tidak Diketahui','Pindahan') DEFAULT NULL,
+ `klasifikasi_status_hiv` enum('Positif','Negatif','Tidak diketahui') DEFAULT NULL,
+ `total_skoring_anak` enum('1','2','3','4','5','6','7','8','9','10','11','12','13','Tidak dilakukan') DEFAULT NULL,
+ `konfirmasiSkoring5` enum('Uji Tuberkulin Positif','Ada Kontak TB Paru','Uji Tuberkulin Negatif','Tidak Ada Kontak TB Paru') DEFAULT NULL,
+ `konfirmasiSkoring6` enum('Ada Kontak TB Paru','Tidak Ada','Tidak Jelas Kontak TB Paru') DEFAULT NULL,
+ `tanggal_mulai_pengobatan` date DEFAULT NULL,
+ `paduan_oat` TEXT DEFAULT NULL,
+ `sumber_obat` enum('Program TB','Bayar Sendiri','Asuransi','Lain-lain') DEFAULT NULL,
+ `sebutkan` TEXT DEFAULT NULL,
+ `sebelum_pengobatan_hasil_mikroskopis` enum('Negatif','1-19','1+','2+','3+','Tidak dilakukan') DEFAULT NULL,
+ `sebelum_pengobatan_hasil_tes_cepat` enum('Rif sensitif','Rif resisten','Negatif','Rif Indeterminated','Invalid','Error','No Result','Tidak dilakukan') DEFAULT NULL,
+ `sebelum_pengobatan_hasil_biakan` enum('Negatif','1-19 BTA','1+','2+','3+','4+','NTM','Kontaminasi','Tidak dilakukan') DEFAULT NULL,
+ `noreglab_bulan_2` varchar(15) DEFAULT NULL,
+ `hasil_mikroskopis_bulan_2` enum('Negatif','1-19','1+','2+','3+','Tidak dilakukan') DEFAULT NULL,
+ `noreglab_bulan_3` varchar(15) DEFAULT NULL,
+ `hasil_mikroskopis_bulan_3` enum('Negatif','1-19','1+','2+','3+','Tidak dilakukan') DEFAULT NULL,
+ `noreglab_bulan_5` varchar(15) DEFAULT NULL,
+ `hasil_mikroskopis_bulan_5` enum('Negatif','1-19','1+','2+','3+','Tidak dilakukan') DEFAULT NULL,
+ `akhir_pengobatan_noreglab` varchar(15) DEFAULT NULL,
+ `akhir_pengobatan_hasil_mikroskopis` enum('Negatif','1-19','1+','2+','3+','Tidak dilakukan') DEFAULT NULL,
+ `tanggal_hasil_akhir_pengobatan` date DEFAULT NULL,
+ `hasil_akhir_pengobatan` enum('Belum','Sembuh','Pengobatan Lengkap','Lost To Follow Up','Meninggal','Gagal','Pindah','') DEFAULT NULL,
+ `tanggal_dianjurkan_tes` date DEFAULT NULL,
+ `tanggal_tes_hiv` date DEFAULT NULL,
+ `hasil_tes_hiv` enum('Reaktif','Non Reaktif','Indeterminated') DEFAULT NULL,
+ `ppk` enum('Ya','Tidak') DEFAULT NULL,
+ `art` enum('Ya','Tidak') DEFAULT NULL,
+ `tb_dm` enum('Ya','Tidak') DEFAULT NULL,
+ `terapi_dm` enum('OHO','Inj. Insulin','') DEFAULT NULL,
+ `pindah_ro` enum('Ya','Tidak') DEFAULT NULL,
+ `status_pengobatan` enum('Sesuai Standar','Tidak Sesuai Standar') DEFAULT NULL,
+ `foto_toraks` enum('Positif','Negatif','Tidak Dilakukan') DEFAULT NULL,
+ `toraks_tdk_dilakukan` enum('Tidak dilakukan','Setelah terapi antibioka non OAT: tidak ada perbaikan Klinis, ada faktor resiko TB, dan atas pertimbangan dokter','Setelah terapi antibioka non OAT: ada Perbaikan Klinis') DEFAULT NULL,
+ `keterangan` varchar(100) DEFAULT NULL,
+ `kode_icd_x` varchar(15) DEFAULT NULL,
+ PRIMARY KEY (`no_rawat`),
+ KEY `kode_icd_x` (`kode_icd_x`),
+ CONSTRAINT `data_tb_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `data_tb_ibfk_2` FOREIGN KEY (`kode_icd_x`) REFERENCES `penyakit` (`kd_penyakit`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `databarang` (
@@ -470,74 +541,23 @@ CREATE TABLE `databarang` (
CONSTRAINT `databarang_ibfk_5` FOREIGN KEY (`kode_kategori`) REFERENCES `kategori_barang` (`kode`) ON UPDATE CASCADE,
CONSTRAINT `databarang_ibfk_6` FOREIGN KEY (`kode_golongan`) REFERENCES `golongan_barang` (`kode`) ON UPDATE CASCADE,
CONSTRAINT `databarang_ibfk_7` FOREIGN KEY (`kode_satbesar`) REFERENCES `kodesatuan` (`kode_sat`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `databarang` VALUES ("B00001","Paracetamol 500mg","-","-","-","5000","5000","5000","5000","5000","5000","5000","5000","5000","5000","5000","5000","100","-","10","500","2024-06-10","1","-","-","-");
-CREATE TABLE `data_tb` (
- `no_rawat` varchar(17) NOT NULL,
- `id_tb_03` varchar(30) DEFAULT NULL,
- `id_periode_laporan` enum('1=Januari - Maret','2=April - Juni','3=Juli - September','4=Oktober - Desember') DEFAULT NULL,
- `tanggal_buat_laporan` datetime DEFAULT NULL,
- `tahun_buat_laporan` year(4) DEFAULT NULL,
- `kd_wasor` int(11) DEFAULT NULL,
- `noregkab` int(11) DEFAULT NULL,
- `id_propinsi` varchar(15) DEFAULT NULL,
- `kd_kabupaten` varchar(15) DEFAULT NULL,
- `id_kecamatan` varchar(15) DEFAULT NULL,
- `id_kelurahan` varchar(15) DEFAULT NULL,
- `nama_rujukan` enum('Inisiatif pasien/Keluarga','Anggota Masyarakat/Kader','Faskes','Dokter Praktek Mandiri','Poli lain','Lain-lain') DEFAULT NULL,
- `sebutkan1` varchar(100) DEFAULT NULL,
- `tipe_diagnosis` enum('Terkonfirmasi bakteriologis','Terdiagnosis klinis') DEFAULT NULL,
- `klasifikasi_lokasi_anatomi` enum('Paru','Ekstraparu') DEFAULT NULL,
- `klasifikasi_riwayat_pengobatan` enum('Baru','Kambuh','Diobati setelah gagal','Diobati Setelah Putus Berobat','Lain-lain','Riwayat Pengobatan Sebelumnya Tidak Diketahui','Pindahan') DEFAULT NULL,
- `klasifikasi_status_hiv` enum('Positif','Negatif','Tidak diketahui') DEFAULT NULL,
- `total_skoring_anak` enum('1','2','3','4','5','6','7','8','9','10','11','12','13','Tidak dilakukan') DEFAULT NULL,
- `konfirmasiSkoring5` enum('Uji Tuberkulin Positif','Ada Kontak TB Paru','Uji Tuberkulin Negatif','Tidak Ada Kontak TB Paru') DEFAULT NULL,
- `konfirmasiSkoring6` enum('Ada Kontak TB Paru','Tidak Ada','Tidak Jelas Kontak TB Paru') DEFAULT NULL,
- `tanggal_mulai_pengobatan` date DEFAULT NULL,
- `paduan_oat` varchar(500) DEFAULT NULL,
- `sumber_obat` enum('Program TB','Bayar Sendiri','Asuransi','Lain-lain') DEFAULT NULL,
- `sebutkan` varchar(500) DEFAULT NULL,
- `sebelum_pengobatan_hasil_mikroskopis` enum('Negatif','1-19','1+','2+','3+','Tidak dilakukan') DEFAULT NULL,
- `sebelum_pengobatan_hasil_tes_cepat` enum('Rif sensitif','Rif resisten','Negatif','Rif Indeterminated','Invalid','Error','No Result','Tidak dilakukan') DEFAULT NULL,
- `sebelum_pengobatan_hasil_biakan` enum('Negatif','1-19 BTA','1+','2+','3+','4+','NTM','Kontaminasi','Tidak dilakukan') DEFAULT NULL,
- `noreglab_bulan_2` varchar(15) DEFAULT NULL,
- `hasil_mikroskopis_bulan_2` enum('Negatif','1-19','1+','2+','3+','Tidak dilakukan') DEFAULT NULL,
- `noreglab_bulan_3` varchar(15) DEFAULT NULL,
- `hasil_mikroskopis_bulan_3` enum('Negatif','1-19','1+','2+','3+','Tidak dilakukan') DEFAULT NULL,
- `noreglab_bulan_5` varchar(15) DEFAULT NULL,
- `hasil_mikroskopis_bulan_5` enum('Negatif','1-19','1+','2+','3+','Tidak dilakukan') DEFAULT NULL,
- `akhir_pengobatan_noreglab` varchar(15) DEFAULT NULL,
- `akhir_pengobatan_hasil_mikroskopis` enum('Negatif','1-19','1+','2+','3+','Tidak dilakukan') DEFAULT NULL,
- `tanggal_hasil_akhir_pengobatan` date DEFAULT NULL,
- `hasil_akhir_pengobatan` enum('Belum','Sembuh','Pengobatan Lengkap','Lost To Follow Up','Meninggal','Gagal','Pindah','') DEFAULT NULL,
- `tanggal_dianjurkan_tes` date DEFAULT NULL,
- `tanggal_tes_hiv` date DEFAULT NULL,
- `hasil_tes_hiv` enum('Reaktif','Non Reaktif','Indeterminated') DEFAULT NULL,
- `ppk` enum('Ya','Tidak') DEFAULT NULL,
- `art` enum('Ya','Tidak') DEFAULT NULL,
- `tb_dm` enum('Ya','Tidak') DEFAULT NULL,
- `terapi_dm` enum('OHO','Inj. Insulin','') DEFAULT NULL,
- `pindah_ro` enum('Ya','Tidak') DEFAULT NULL,
- `status_pengobatan` enum('Sesuai Standar','Tidak Sesuai Standar') DEFAULT NULL,
- `foto_toraks` enum('Positif','Negatif','Tidak Dilakukan') DEFAULT NULL,
- `toraks_tdk_dilakukan` enum('Tidak dilakukan','Setelah terapi antibioka non OAT: tidak ada perbaikan Klinis, ada faktor resiko TB, dan atas pertimbangan dokter','Setelah terapi antibioka non OAT: ada Perbaikan Klinis') DEFAULT NULL,
- `keterangan` varchar(100) DEFAULT NULL,
- `kode_icd_x` varchar(15) DEFAULT NULL,
- PRIMARY KEY (`no_rawat`),
- KEY `kode_icd_x` (`kode_icd_x`),
- CONSTRAINT `data_tb_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `data_tb_ibfk_2` FOREIGN KEY (`kode_icd_x`) REFERENCES `penyakit` (`kd_penyakit`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `departemen` (
`dep_id` char(4) NOT NULL,
`nama` varchar(25) NOT NULL,
PRIMARY KEY (`dep_id`),
KEY `nama` (`nama`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `departemen` VALUES ("-","-");
+
CREATE TABLE `detail_obat_racikan` (
`tgl_perawatan` date NOT NULL,
`jam` time NOT NULL,
@@ -549,7 +569,8 @@ CREATE TABLE `detail_obat_racikan` (
KEY `kode_brng` (`kode_brng`),
CONSTRAINT `detail_obat_racikan_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE,
CONSTRAINT `detail_obat_racikan_ibfk_2` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
CREATE TABLE `detail_pemberian_obat` (
`tgl_perawatan` date NOT NULL DEFAULT '0000-00-00',
@@ -581,7 +602,7 @@ CREATE TABLE `detail_pemberian_obat` (
CONSTRAINT `detail_pemberian_obat_ibfk_3` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `detail_pemberian_obat_ibfk_4` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `detail_pemberian_obat_ibfk_5` FOREIGN KEY (`kd_bangsal`) REFERENCES `bangsal` (`kd_bangsal`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `detail_periksa_lab` (
@@ -620,7 +641,7 @@ CREATE TABLE `detail_periksa_lab` (
CONSTRAINT `detail_periksa_lab_ibfk_10` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE,
CONSTRAINT `detail_periksa_lab_ibfk_11` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan_lab` (`kd_jenis_prw`) ON UPDATE CASCADE,
CONSTRAINT `detail_periksa_lab_ibfk_12` FOREIGN KEY (`id_template`) REFERENCES `template_laboratorium` (`id_template`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `diagnosa_pasien` (
@@ -636,7 +657,7 @@ CREATE TABLE `diagnosa_pasien` (
KEY `no_rawat` (`no_rawat`),
CONSTRAINT `diagnosa_pasien_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE,
CONSTRAINT `diagnosa_pasien_ibfk_2` FOREIGN KEY (`kd_penyakit`) REFERENCES `penyakit` (`kd_penyakit`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `dokter` (
@@ -671,7 +692,9 @@ CREATE TABLE `dokter` (
KEY `status` (`status`),
CONSTRAINT `dokter_ibfk_2` FOREIGN KEY (`kd_sps`) REFERENCES `spesialis` (`kd_sps`) ON UPDATE CASCADE,
CONSTRAINT `dokter_ibfk_3` FOREIGN KEY (`kd_dokter`) REFERENCES `pegawai` (`nik`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `dokter` VALUES ("DR001","dr. Ataaka Muhammad","L","Barabai","2000-09-18","O","Islam","Barabai","-","MENIKAH","UMUM","UI","-","1");
@@ -682,7 +705,7 @@ CREATE TABLE `dpjp_ranap` (
KEY `dpjp_ranap_ibfk_2` (`kd_dokter`),
CONSTRAINT `dpjp_ranap_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE,
CONSTRAINT `dpjp_ranap_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `emergency_index` (
@@ -690,7 +713,9 @@ CREATE TABLE `emergency_index` (
`nama_emergency` varchar(200) DEFAULT NULL,
`indek` tinyint DEFAULT NULL,
PRIMARY KEY (`kode_emergency`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `emergency_index` VALUES ("-","-","1");
@@ -698,17 +723,19 @@ CREATE TABLE `gambar_radiologi` (
`no_rawat` varchar(17) NOT NULL,
`tgl_periksa` date NOT NULL,
`jam` time NOT NULL,
- `lokasi_gambar` varchar(500) NOT NULL,
+ `lokasi_gambar` varchar(255) NOT NULL,
PRIMARY KEY (`no_rawat`,`tgl_periksa`,`jam`,`lokasi_gambar`),
CONSTRAINT `gambar_radiologi_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `golongan_barang` (
`kode` char(4) NOT NULL,
`nama` varchar(30) DEFAULT NULL,
PRIMARY KEY (`kode`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `golongan_barang` VALUES ("-","-");
@@ -724,7 +751,7 @@ CREATE TABLE `gudangbarang` (
KEY `kd_bangsal` (`kd_bangsal`) USING BTREE,
CONSTRAINT `gudangbarang_ibfk_1` FOREIGN KEY (`kd_bangsal`) REFERENCES `bangsal` (`kd_bangsal`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `gudangbarang_ibfk_2` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `hasil_radiologi` (
@@ -735,7 +762,7 @@ CREATE TABLE `hasil_radiologi` (
PRIMARY KEY (`no_rawat`,`tgl_periksa`,`jam`),
KEY `no_rawat` (`no_rawat`),
CONSTRAINT `hasil_radiologi_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `icd9` (
@@ -743,7 +770,7 @@ CREATE TABLE `icd9` (
`deskripsi_panjang` varchar(250) DEFAULT NULL,
`deskripsi_pendek` varchar(40) DEFAULT NULL,
PRIMARY KEY (`kode`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `industrifarmasi` (
@@ -757,7 +784,9 @@ CREATE TABLE `industrifarmasi` (
KEY `alamat` (`alamat`),
KEY `kota` (`kota`),
KEY `no_telp` (`no_telp`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `industrifarmasi` VALUES ("-","-","-","-","0");
@@ -782,7 +811,7 @@ CREATE TABLE `inventaris` (
KEY `no_box` (`no_box`),
CONSTRAINT `inventaris_ibfk_1` FOREIGN KEY (`kode_barang`) REFERENCES `inventaris_barang` (`kode_barang`) ON UPDATE CASCADE,
CONSTRAINT `inventaris_ibfk_2` FOREIGN KEY (`id_ruang`) REFERENCES `inventaris_ruang` (`id_ruang`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `inventaris_barang` (
@@ -808,7 +837,7 @@ CREATE TABLE `inventaris_barang` (
CONSTRAINT `inventaris_barang_ibfk_6` FOREIGN KEY (`id_merk`) REFERENCES `inventaris_merk` (`id_merk`) ON UPDATE CASCADE,
CONSTRAINT `inventaris_barang_ibfk_7` FOREIGN KEY (`id_kategori`) REFERENCES `inventaris_kategori` (`id_kategori`) ON UPDATE CASCADE,
CONSTRAINT `inventaris_barang_ibfk_8` FOREIGN KEY (`id_jenis`) REFERENCES `inventaris_jenis` (`id_jenis`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `inventaris_jenis` (
@@ -816,7 +845,7 @@ CREATE TABLE `inventaris_jenis` (
`nama_jenis` varchar(40) DEFAULT NULL,
PRIMARY KEY (`id_jenis`),
KEY `nama_jenis` (`nama_jenis`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `inventaris_kategori` (
@@ -824,7 +853,7 @@ CREATE TABLE `inventaris_kategori` (
`nama_kategori` varchar(40) DEFAULT NULL,
PRIMARY KEY (`id_kategori`),
KEY `nama_kategori` (`nama_kategori`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `inventaris_merk` (
@@ -832,7 +861,7 @@ CREATE TABLE `inventaris_merk` (
`nama_merk` varchar(40) NOT NULL,
PRIMARY KEY (`id_merk`),
KEY `nama_merk` (`nama_merk`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `inventaris_peminjaman` (
@@ -850,7 +879,7 @@ CREATE TABLE `inventaris_peminjaman` (
KEY `status_pinjam` (`status_pinjam`) USING BTREE,
CONSTRAINT `inventaris_peminjaman_ibfk_1` FOREIGN KEY (`no_inventaris`) REFERENCES `inventaris` (`no_inventaris`) ON UPDATE CASCADE,
CONSTRAINT `inventaris_peminjaman_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `inventaris_produsen` (
@@ -866,7 +895,7 @@ CREATE TABLE `inventaris_produsen` (
KEY `no_telp` (`no_telp`),
KEY `email` (`email`),
KEY `website_produsen` (`website_produsen`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `inventaris_ruang` (
@@ -874,7 +903,7 @@ CREATE TABLE `inventaris_ruang` (
`nama_ruang` varchar(40) NOT NULL,
PRIMARY KEY (`id_ruang`),
KEY `nama_ruang` (`nama_ruang`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `jabatan` (
@@ -882,7 +911,9 @@ CREATE TABLE `jabatan` (
`nm_jbtn` varchar(25) DEFAULT NULL,
PRIMARY KEY (`kd_jbtn`),
KEY `nm_jbtn` (`nm_jbtn`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `jabatan` VALUES ("-","-");
@@ -900,7 +931,7 @@ CREATE TABLE `jadwal` (
KEY `jam_selesai` (`jam_selesai`),
CONSTRAINT `jadwal_ibfk_1` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `jadwal_ibfk_2` FOREIGN KEY (`kd_poli`) REFERENCES `poliklinik` (`kd_poli`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `jadwal_pegawai` (
@@ -950,7 +981,7 @@ CREATE TABLE `jadwal_pegawai` (
KEY `h18` (`h18`),
KEY `h9` (`h9`),
CONSTRAINT `jadwal_pegawai_ibfk_1` FOREIGN KEY (`id`) REFERENCES `pegawai` (`id`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `jadwal_tambahan` (
@@ -990,7 +1021,7 @@ CREATE TABLE `jadwal_tambahan` (
`h31` enum('Pagi','Pagi2','Pagi3','Pagi4','Pagi5','Pagi6','Pagi7','Pagi8','Pagi9','Pagi10','Siang','Siang2','Siang3','Siang4','Siang5','Siang6','Siang7','Siang8','Siang9','Siang10','Malam','Malam2','Malam3','Malam4','Malam5','Malam6','Malam7','Malam8','Malam9','Malam10','Midle Pagi1','Midle Pagi2','Midle Pagi3','Midle Pagi4','Midle Pagi5','Midle Pagi6','Midle Pagi7','Midle Pagi8','Midle Pagi9','Midle Pagi10','Midle Siang1','Midle Siang2','Midle Siang3','Midle Siang4','Midle Siang5','Midle Siang6','Midle Siang7','Midle Siang8','Midle Siang9','Midle Siang10','Midle Malam1','Midle Malam2','Midle Malam3','Midle Malam4','Midle Malam5','Midle Malam6','Midle Malam7','Midle Malam8','Midle Malam9','Midle Malam10','') NOT NULL,
PRIMARY KEY (`id`,`tahun`,`bulan`),
CONSTRAINT `jadwal_tambahan_ibfk_1` FOREIGN KEY (`id`) REFERENCES `pegawai` (`id`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `jam_jaga` (
@@ -1006,7 +1037,7 @@ CREATE TABLE `jam_jaga` (
KEY `jam_masuk` (`jam_masuk`),
KEY `jam_pulang` (`jam_pulang`),
CONSTRAINT `jam_jaga_ibfk_1` FOREIGN KEY (`dep_id`) REFERENCES `departemen` (`dep_id`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `jam_masuk` (
@@ -1014,7 +1045,9 @@ CREATE TABLE `jam_masuk` (
`jam_masuk` time NOT NULL,
`jam_pulang` time NOT NULL,
PRIMARY KEY (`shift`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `jam_masuk` VALUES ("Pagi","06:00:00","16:00:00"),
("Pagi2","08:00:00","14:00:00"),
("Pagi3","10:00:00","17:00:00"),
@@ -1035,7 +1068,9 @@ CREATE TABLE `jenis` (
PRIMARY KEY (`kdjns`),
KEY `nama` (`nama`),
KEY `keterangan` (`keterangan`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `jenis` VALUES ("-","-","-");
@@ -1047,7 +1082,9 @@ CREATE TABLE `jnj_jabatan` (
PRIMARY KEY (`kode`),
KEY `nama` (`nama`),
KEY `tnj` (`tnj`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `jnj_jabatan` VALUES ("-","-","0","1");
@@ -1085,7 +1122,9 @@ CREATE TABLE `jns_perawatan` (
CONSTRAINT `jns_perawatan_ibfk_1` FOREIGN KEY (`kd_kategori`) REFERENCES `kategori_perawatan` (`kd_kategori`) ON UPDATE CASCADE,
CONSTRAINT `jns_perawatan_ibfk_2` FOREIGN KEY (`kd_pj`) REFERENCES `penjab` (`kd_pj`) ON UPDATE CASCADE,
CONSTRAINT `jns_perawatan_ibfk_3` FOREIGN KEY (`kd_poli`) REFERENCES `poliklinik` (`kd_poli`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `jns_perawatan` VALUES ("RJ001","Pemeriksaan rutin","-","0","0","50000","0","0","0","50000","0","50000","-","-","1");
@@ -1124,7 +1163,9 @@ CREATE TABLE `jns_perawatan_inap` (
CONSTRAINT `jns_perawatan_inap_ibfk_7` FOREIGN KEY (`kd_kategori`) REFERENCES `kategori_perawatan` (`kd_kategori`) ON UPDATE CASCADE,
CONSTRAINT `jns_perawatan_inap_ibfk_8` FOREIGN KEY (`kd_pj`) REFERENCES `penjab` (`kd_pj`) ON UPDATE CASCADE,
CONSTRAINT `jns_perawatan_inap_ibfk_9` FOREIGN KEY (`kd_bangsal`) REFERENCES `bangsal` (`kd_bangsal`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `jns_perawatan_inap` VALUES ("RI001","Pasang Infus","-","0","0","0","25000","0","0","0","25000","25000","-","-","1","Kelas 1");
@@ -1156,7 +1197,9 @@ CREATE TABLE `jns_perawatan_lab` (
KEY `menejemen` (`menejemen`),
KEY `status` (`status`),
CONSTRAINT `jns_perawatan_lab_ibfk_1` FOREIGN KEY (`kd_pj`) REFERENCES `penjab` (`kd_pj`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `jns_perawatan_lab` VALUES ("LAB001","Pemeriksaan Darah","0","0","0","100000","0","0","0","100000","-","1","Kelas 1","PK");
@@ -1187,16 +1230,20 @@ CREATE TABLE `jns_perawatan_radiologi` (
KEY `menejemen` (`menejemen`),
KEY `status` (`status`),
CONSTRAINT `jns_perawatan_radiologi_ibfk_1` FOREIGN KEY (`kd_pj`) REFERENCES `penjab` (`kd_pj`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `jns_perawatan_radiologi` VALUES ("RAD001","Thorax","0","0","0","150000","0","0","0","150000","-","1","Kelas 1");
CREATE TABLE `kabupaten` (
- `kd_kab` int NOT NULL AUTO_INCREMENT,
+ `kd_kab` int NOT NULL,
`nm_kab` varchar(60) NOT NULL,
PRIMARY KEY (`kd_kab`),
UNIQUE KEY `nm_kab` (`nm_kab`)
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `kabupaten` VALUES ("1","-");
@@ -1214,7 +1261,9 @@ CREATE TABLE `kamar` (
KEY `kelas` (`kelas`),
KEY `statusdata` (`statusdata`),
CONSTRAINT `kamar_ibfk_1` FOREIGN KEY (`kd_bangsal`) REFERENCES `bangsal` (`kd_bangsal`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `kamar` VALUES ("ANG01","ANG","100000","KOSONG","Kelas 1","1"),
("ANG02","ANG","100000","KOSONG","Kelas 1","1");
@@ -1244,14 +1293,16 @@ CREATE TABLE `kamar_inap` (
KEY `trf_kamar` (`trf_kamar`),
CONSTRAINT `kamar_inap_ibfk_2` FOREIGN KEY (`kd_kamar`) REFERENCES `kamar` (`kd_kamar`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `kamar_inap_ibfk_3` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `kategori_barang` (
`kode` char(4) NOT NULL,
`nama` varchar(30) DEFAULT NULL,
PRIMARY KEY (`kode`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `kategori_barang` VALUES ("-","-");
@@ -1262,7 +1313,9 @@ CREATE TABLE `kategori_penyakit` (
PRIMARY KEY (`kd_ktg`),
KEY `nm_kategori` (`nm_kategori`),
KEY `ciri_umum` (`ciri_umum`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `kategori_penyakit` VALUES ("-","-","-");
@@ -1271,15 +1324,19 @@ CREATE TABLE `kategori_perawatan` (
`nm_kategori` varchar(30) DEFAULT NULL,
PRIMARY KEY (`kd_kategori`),
KEY `nm_kategori` (`nm_kategori`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `kategori_perawatan` VALUES ("-","-");
CREATE TABLE `kecamatan` (
- `kd_kec` int NOT NULL AUTO_INCREMENT,
+ `kd_kec` int NOT NULL,
`nm_kec` varchar(60) NOT NULL,
PRIMARY KEY (`kd_kec`)
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `kecamatan` VALUES ("1","-");
@@ -1288,7 +1345,9 @@ CREATE TABLE `kelompok_jabatan` (
`nama_kelompok` varchar(100) DEFAULT NULL,
`indek` tinyint DEFAULT NULL,
PRIMARY KEY (`kode_kelompok`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `kelompok_jabatan` VALUES ("-","-","1");
@@ -1296,7 +1355,9 @@ CREATE TABLE `kelurahan` (
`kd_kel` varchar(11) NOT NULL,
`nm_kel` varchar(60) NOT NULL,
PRIMARY KEY (`kd_kel`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `kelurahan` VALUES ("1","-");
@@ -1305,7 +1366,9 @@ CREATE TABLE `kodesatuan` (
`satuan` varchar(30) DEFAULT NULL,
PRIMARY KEY (`kode_sat`),
KEY `satuan` (`satuan`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `kodesatuan` VALUES ("-","-");
@@ -1320,7 +1383,7 @@ CREATE TABLE `laporan_operasi` (
`laporan_operasi` text NOT NULL,
PRIMARY KEY (`no_rawat`,`tanggal`),
CONSTRAINT `laporan_operasi_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `maping_dokter_dpjpvclaim` (
@@ -1329,7 +1392,7 @@ CREATE TABLE `maping_dokter_dpjpvclaim` (
`nm_dokter_bpjs` varchar(50) DEFAULT NULL,
PRIMARY KEY (`kd_dokter`) USING BTREE,
CONSTRAINT `maping_dokter_dpjpvclaim_ibfk_1` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `maping_dokter_pcare` (
@@ -1338,7 +1401,7 @@ CREATE TABLE `maping_dokter_pcare` (
`nm_dokter_pcare` varchar(50) DEFAULT NULL,
PRIMARY KEY (`kd_dokter`) USING BTREE,
CONSTRAINT `maping_dokter_pcare_ibfk_1` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `maping_poli_bpjs` (
@@ -1348,7 +1411,7 @@ CREATE TABLE `maping_poli_bpjs` (
PRIMARY KEY (`kd_poli_rs`),
UNIQUE KEY `kd_poli_bpjs` (`kd_poli_bpjs`) USING BTREE,
CONSTRAINT `maping_poli_bpjs_ibfk_1` FOREIGN KEY (`kd_poli_rs`) REFERENCES `poliklinik` (`kd_poli`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `maping_poliklinik_pcare` (
@@ -1357,13 +1420,15 @@ CREATE TABLE `maping_poliklinik_pcare` (
`nm_poli_pcare` varchar(50) DEFAULT NULL,
PRIMARY KEY (`kd_poli_rs`) USING BTREE,
CONSTRAINT `maping_poliklinik_pcare_ibfk_1` FOREIGN KEY (`kd_poli_rs`) REFERENCES `poliklinik` (`kd_poli`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `master_aturan_pakai` (
`aturan` varchar(150) NOT NULL,
PRIMARY KEY (`aturan`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `master_aturan_pakai` VALUES ("3 x 1 Sehari");
@@ -1371,7 +1436,9 @@ CREATE TABLE `master_berkas_digital` (
`kode` varchar(10) NOT NULL,
`nama` varchar(100) DEFAULT NULL,
PRIMARY KEY (`kode`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `master_berkas_digital` VALUES ("DIG001","Berkas Digital");
@@ -1379,14 +1446,16 @@ CREATE TABLE `master_masalah_keperawatan` (
`kode_masalah` varchar(3) NOT NULL,
`nama_masalah` varchar(100) DEFAULT NULL,
PRIMARY KEY (`kode_masalah`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `metode_racik` (
`kd_racik` varchar(3) NOT NULL,
`nm_racik` varchar(30) NOT NULL,
PRIMARY KEY (`kd_racik`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `metode_racik` VALUES ("1","Puyer");
@@ -1395,7 +1464,17 @@ CREATE TABLE `mlite_akun_kegiatan` (
`kegiatan` varchar(200) DEFAULT NULL,
`kd_rek` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+INSERT INTO `mlite_akun_kegiatan` VALUES
+(1,'Penerimaan Pasien Rawat Jalan','4101'),
+(2,'Penerimaan Pasien Rawat Inap','4102'),
+(3,'Penerimaan Penjualan Obat & BHP','4103'),
+(4,'Penerimaan Laboratorium','4104'),
+(5,'Penerimaan Radiologi','4105'),
+(6,'Pembayaran Gaji Karyawan','5101'),
+(7,'Pembelian Obat & BHP','5201'),
+(8,'Pembayaran Biaya Operasional','5301');
CREATE TABLE `mlite_antrian_loket` (
@@ -1409,7 +1488,7 @@ CREATE TABLE `mlite_antrian_loket` (
`status` varchar(10) NOT NULL DEFAULT '0',
`loket` varchar(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`kd`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_antrian_referensi` (
@@ -1421,7 +1500,7 @@ CREATE TABLE `mlite_antrian_referensi` (
`jenis_kunjungan` varchar(10) NOT NULL,
`status_kirim` varchar(20) DEFAULT NULL,
`keterangan` varchar(250) DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_antrian_referensi_batal` (
@@ -1429,7 +1508,7 @@ CREATE TABLE `mlite_antrian_referensi_batal` (
`nomor_referensi` varchar(50) NOT NULL,
`kodebooking` varchar(100) NOT NULL,
`keterangan` varchar(250) NOT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_antrian_referensi_taskid` (
@@ -1439,7 +1518,7 @@ CREATE TABLE `mlite_antrian_referensi_taskid` (
`waktu` varchar(50) NOT NULL,
`status` varchar(20) DEFAULT NULL,
`keterangan` varchar(250) NOT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_apamregister` (
@@ -1447,7 +1526,7 @@ CREATE TABLE `mlite_apamregister` (
`email` varchar(225) NOT NULL,
`nomor_ktp` varchar(225) NOT NULL,
`nomor_telepon` varchar(225) DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_api_key` (
@@ -1459,7 +1538,7 @@ CREATE TABLE `mlite_api_key` (
`exp_time` datetime NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
KEY `mlite_api_key_ibfk_1` (`username`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_apotek_online_log` (
@@ -1475,7 +1554,7 @@ CREATE TABLE `mlite_apotek_online_log` (
PRIMARY KEY (`id`) USING BTREE,
KEY `no_rawat` (`no_rawat`) USING BTREE,
KEY `tanggal_kirim` (`tanggal_kirim`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_apotek_online_maping_obat` (
@@ -1484,7 +1563,7 @@ CREATE TABLE `mlite_apotek_online_maping_obat` (
`nama_obat_bpjs` varchar(200) NOT NULL,
PRIMARY KEY (`kode_brng`) USING BTREE,
KEY `kd_obat_bpjs` (`kd_obat_bpjs`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_apotek_online_resep_response_log` (
@@ -1511,7 +1590,7 @@ CREATE TABLE `mlite_apotek_online_resep_response_log` (
KEY `idx_no_sep_kunjungan` (`no_sep_kunjungan`) USING BTREE,
KEY `idx_no_resep` (`no_resep`) USING BTREE,
KEY `idx_tanggal_simpan` (`tanggal_simpan`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_apotek_online_sep_data` (
@@ -1546,7 +1625,7 @@ CREATE TABLE `mlite_apotek_online_sep_data` (
KEY `no_kartu` (`no_kartu`) USING BTREE,
KEY `nama_peserta` (`nama_peserta`) USING BTREE,
KEY `tanggal_simpan` (`tanggal_simpan`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_billing` (
@@ -1562,7 +1641,144 @@ CREATE TABLE `mlite_billing` (
`id_user` int NOT NULL,
`keterangan` varchar(100) NOT NULL,
PRIMARY KEY (`id_billing`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE `mlite_billing_pembayaran` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `no_rawat` varchar(17) NOT NULL,
+ `tgl_bayar` date NOT NULL,
+ `jam_bayar` time NOT NULL,
+ `metode` varchar(30) NOT NULL DEFAULT 'Tunai',
+ `jumlah_bayar` double NOT NULL DEFAULT 0,
+ `id_user` int(11) DEFAULT NULL,
+ `keterangan` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_billing_pembayaran_rawat` (`no_rawat`),
+ KEY `idx_billing_pembayaran_tgl` (`tgl_bayar`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+CREATE TABLE `mlite_billing_pembayaran_detail` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `pembayaran_id` int(11) NOT NULL,
+ `kelompok` varchar(30) NOT NULL,
+ `jumlah_alokasi` double NOT NULL DEFAULT 0,
+ `ref_modul` varchar(30) DEFAULT NULL,
+ `kd_jenis_prw` varchar(15) DEFAULT NULL,
+ `tgl_periksa` date DEFAULT NULL,
+ `jam` time DEFAULT NULL,
+ `status_periksa` varchar(10) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_billing_pembayaran_detail_pembayaran` (`pembayaran_id`),
+ KEY `idx_billing_pembayaran_detail_kelompok` (`kelompok`),
+ CONSTRAINT `fk_billing_pembayaran_detail_header` FOREIGN KEY (`pembayaran_id`) REFERENCES `mlite_billing_pembayaran` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+CREATE TABLE `mlite_bpjs_emr_device` (
+ `id` INT NOT NULL AUTO_INCREMENT,
+ `device_id` VARCHAR(50) NOT NULL,
+ `nama_alkes` VARCHAR(150) NOT NULL,
+ `kategori` varchar(50) DEFAULT 'tindakan',
+ `kode_produk` VARCHAR(100) DEFAULT NULL,
+ `keterangan` TEXT DEFAULT NULL,
+ `manufacturer` VARCHAR(255) DEFAULT NULL,
+ `manufacture_date` DATE DEFAULT NULL,
+ `expiration_date` DATE DEFAULT NULL,
+ `model` VARCHAR(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uq_device_id` (`device_id`),
+ KEY `idx_nama_alkes` (`nama_alkes`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE `mlite_bpjs_emr_logs` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `no_sep` varchar(50) DEFAULT NULL,
+ `no_rawat` varchar(50) DEFAULT NULL,
+ `payload_json` longtext,
+ `payload_encrypted` longtext,
+ `response` longtext,
+ `status` varchar(20) DEFAULT NULL,
+ `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_bpjs_emr_mapping_lab` (
+ `id_template` varchar(20) NOT NULL,
+ `loinc_code` varchar(20) NOT NULL,
+ `loinc_display` varchar(255) DEFAULT NULL,
+ `master_device_id` int DEFAULT NULL,
+ `focal_device_code` varchar(255) DEFAULT NULL,
+ `focal_device_display` varchar(255) DEFAULT NULL,
+ `focal_device_action` varchar(20) DEFAULT NULL,
+ KEY `idx_mapping_lab_master_device` (`master_device_id`),
+ PRIMARY KEY (`id_template`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_bpjs_emr_mapping_radiologi` (
+ `kd_jenis_prw` varchar(20) NOT NULL,
+ `standard_code` varchar(20) NOT NULL,
+ `standard_display` varchar(255) DEFAULT NULL,
+ `system` varchar(100) DEFAULT NULL,
+ `master_device_id` int DEFAULT NULL,
+ `focal_device_code` varchar(255) DEFAULT NULL,
+ `focal_device_display` varchar(255) DEFAULT NULL,
+ `focal_device_action` varchar(20) DEFAULT NULL,
+ KEY `idx_mapping_rad_master_device` (`master_device_id`),
+ PRIMARY KEY (`kd_jenis_prw`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_bpjs_emr_mapping_prosedur` (
+ `kd_jenis_prw` varchar(20) NOT NULL,
+ `snomed_code` varchar(20) NOT NULL,
+ `snomed_display` varchar(255) DEFAULT NULL,
+ `master_device_id` int DEFAULT NULL,
+ `focal_device_code` varchar(255) DEFAULT NULL,
+ `focal_device_display` varchar(255) DEFAULT NULL,
+ `focal_device_action` varchar(20) DEFAULT NULL,
+ KEY `idx_mapping_proc_master_device` (`master_device_id`),
+ PRIMARY KEY (`kd_jenis_prw`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_bpjs_emr_mapping_prosedur_ranap` (
+ `kd_jenis_prw` varchar(20) NOT NULL,
+ `snomed_code` varchar(20) NOT NULL,
+ `snomed_display` varchar(255) DEFAULT NULL,
+ `master_device_id` int DEFAULT NULL,
+ `focal_device_code` varchar(255) DEFAULT NULL,
+ `focal_device_display` varchar(255) DEFAULT NULL,
+ `focal_device_action` varchar(20) DEFAULT NULL,
+ KEY `idx_mapping_proc_ranap_master_device` (`master_device_id`),
+ PRIMARY KEY (`kd_jenis_prw`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_bpjs_emr_mapping_operasi` (
+ `kode_paket` varchar(20) NOT NULL,
+ `snomed_code` varchar(20) NOT NULL,
+ `snomed_display` varchar(255) DEFAULT NULL,
+ `master_device_id` int DEFAULT NULL,
+ `focal_device_code` varchar(255) DEFAULT NULL,
+ `focal_device_display` varchar(255) DEFAULT NULL,
+ `focal_device_action` varchar(20) DEFAULT NULL,
+ KEY `idx_mapping_operasi_master_device` (`master_device_id`),
+ PRIMARY KEY (`kode_paket`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_bpjs_emr_mapping_obat` (
+ `kode_brng` varchar(20) NOT NULL,
+ `code` varchar(20) NOT NULL,
+ PRIMARY KEY (`kode_brng`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_bpjs_emr_uuid_condition` (
+ `kd_penyakit` varchar(15) NOT NULL,
+ `uuid` varchar(200) NULL DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_bridging_pcare` (
@@ -1633,7 +1849,75 @@ CREATE TABLE `mlite_bridging_pcare` (
`terapi_obat` text,
`terapi_non_obat` text,
PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_crud_permissions` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `user` varchar(100) NOT NULL,
+ `module` varchar(100) NOT NULL,
+ `can_create` varchar(10) NOT NULL DEFAULT 'true',
+ `can_read` varchar(10) NOT NULL DEFAULT 'true',
+ `can_update` varchar(10) NOT NULL DEFAULT 'true',
+ `can_delete` varchar(10) NOT NULL DEFAULT 'true',
+ PRIMARY KEY (`id`) USING BTREE,
+ UNIQUE KEY `user` (`user`,`module`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_farmasi_pengajuan_obat` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `no_pengajuan` varchar(30) NOT NULL,
+ `tanggal_pengajuan` date NOT NULL,
+ `kode_brng` varchar(15) NOT NULL,
+ `jumlah` int(11) NOT NULL DEFAULT '0',
+ `status` varchar(20) NOT NULL DEFAULT 'Menunggu',
+ `catatan` text,
+ `dibuat_oleh` varchar(100) DEFAULT '-',
+ `disetujui_oleh` varchar(100) DEFAULT NULL,
+ `disetujui_at` datetime DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_no_pengajuan` (`no_pengajuan`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_farmasi_pemesanan_obat` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `no_pemesanan` varchar(30) NOT NULL,
+ `no_pengajuan` varchar(30) NOT NULL,
+ `pengajuan_id` int(11) NOT NULL,
+ `kode_brng` varchar(15) NOT NULL,
+ `tanggal_pemesanan` date NOT NULL,
+ `supplier_kode` text,
+ `supplier` varchar(255) NOT NULL,
+ `jumlah_pengajuan` int(11) NOT NULL DEFAULT '0',
+ `jumlah_pesan` int(11) NOT NULL DEFAULT '0',
+ `status_pemesanan` varchar(20) NOT NULL DEFAULT 'Draft',
+ `catatan` text,
+ `dibuat_oleh` varchar(100) DEFAULT '-',
+ `created_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_no_pemesanan` (`no_pemesanan`),
+ KEY `idx_no_pengajuan_pemesanan` (`no_pengajuan`),
+ KEY `idx_pengajuan_id` (`pengajuan_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_farmasi_penerimaan_obat` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `pemesanan_id` int(11) NOT NULL,
+ `tanggal_penerimaan` date NOT NULL,
+ `jumlah_terima` int(11) NOT NULL DEFAULT '0',
+ `jenis_pembayaran` varchar(10) NOT NULL DEFAULT 'Cash',
+ `tanggal_jatuh_tempo` date DEFAULT NULL,
+ `nomor_faktur` varchar(100) DEFAULT NULL,
+ `catatan` text,
+ `dibuat_oleh` varchar(100) DEFAULT '-',
+ `created_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_pemesanan_id` (`pemesanan_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_detailjurnal` (
@@ -1648,20 +1932,113 @@ CREATE TABLE `mlite_detailjurnal` (
KEY `kredit` (`kredit`),
CONSTRAINT `mlite_detailjurnal_ibfk_1` FOREIGN KEY (`no_jurnal`) REFERENCES `mlite_jurnal` (`no_jurnal`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `mlite_detailjurnal_ibfk_2` FOREIGN KEY (`kd_rek`) REFERENCES `mlite_rekening` (`kd_rek`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-
-
-CREATE TABLE `mlite_crud_permissions` (
- `id` int NOT NULL AUTO_INCREMENT,
- `user` varchar(100) NOT NULL,
- `module` varchar(100) NOT NULL,
- `can_create` varchar(10) NOT NULL DEFAULT 'true',
- `can_read` varchar(10) NOT NULL DEFAULT 'true',
- `can_update` varchar(10) NOT NULL DEFAULT 'true',
- `can_delete` varchar(10) NOT NULL DEFAULT 'true',
- PRIMARY KEY (`id`) USING BTREE,
- UNIQUE KEY `user` (`user`,`module`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+-- Detail jurnal 2025
+-- JU-2025-001: Penerimaan Q1 Jan (D=75jt, K=75jt)
+INSERT INTO `mlite_detailjurnal` VALUES
+('JU-2025-001','1101',0,75000000,0),
+('JU-2025-001','4101',0,0,30000000),
+('JU-2025-001','4102',0,0,20000000),
+('JU-2025-001','4103',0,0,15000000),
+('JU-2025-001','4104',0,0,5000000),
+('JU-2025-001','4105',0,0,5000000),
+-- JU-2025-002: Pembayaran Gaji Jan (D=23jt, K=23jt)
+('JU-2025-002','5101',0,10000000,0),
+('JU-2025-002','5102',0,8000000,0),
+('JU-2025-002','5103',0,5000000,0),
+('JU-2025-002','1101',0,0,23000000),
+-- JU-2025-003: Penerimaan Q2 Apr (D=90jt, K=90jt)
+('JU-2025-003','1101',0,90000000,0),
+('JU-2025-003','4101',0,0,35000000),
+('JU-2025-003','4102',0,0,25000000),
+('JU-2025-003','4103',0,0,20000000),
+('JU-2025-003','4104',0,0,5000000),
+('JU-2025-003','4105',0,0,5000000),
+-- JU-2025-004: Pembayaran Biaya Q2 (D=50jt, K=50jt)
+('JU-2025-004','5101',0,10000000,0),
+('JU-2025-004','5102',0,8000000,0),
+('JU-2025-004','5103',0,5000000,0),
+('JU-2025-004','5201',0,25000000,0),
+('JU-2025-004','5301',0,2000000,0),
+('JU-2025-004','1101',0,0,50000000),
+-- JU-2025-005: Penerimaan Q3 Jul (D=85jt, K=85jt)
+('JU-2025-005','1101',0,85000000,0),
+('JU-2025-005','4101',0,0,30000000),
+('JU-2025-005','4102',0,0,25000000),
+('JU-2025-005','4103',0,0,18000000),
+('JU-2025-005','4104',0,0,7000000),
+('JU-2025-005','4105',0,0,5000000),
+-- JU-2025-006: Pembayaran Biaya Q3 (D=45jt, K=45jt)
+('JU-2025-006','5101',0,10000000,0),
+('JU-2025-006','5102',0,8000000,0),
+('JU-2025-006','5103',0,5000000,0),
+('JU-2025-006','5201',0,20000000,0),
+('JU-2025-006','5301',0,2000000,0),
+('JU-2025-006','1101',0,0,45000000),
+-- JU-2025-007: Penerimaan Q4 Okt (D=95jt, K=95jt)
+('JU-2025-007','1101',0,95000000,0),
+('JU-2025-007','4101',0,0,35000000),
+('JU-2025-007','4102',0,0,30000000),
+('JU-2025-007','4103',0,0,18000000),
+('JU-2025-007','4104',0,0,7000000),
+('JU-2025-007','4105',0,0,5000000),
+-- JU-2025-008: Pembayaran Biaya Q4 (D=47jt, K=47jt)
+('JU-2025-008','5101',0,10000000,0),
+('JU-2025-008','5102',0,8000000,0),
+('JU-2025-008','5103',0,5000000,0),
+('JU-2025-008','5201',0,22000000,0),
+('JU-2025-008','5301',0,2000000,0),
+('JU-2025-008','1101',0,0,47000000),
+-- JU-2025-009: Penyesuaian Penyusutan Akhir Tahun (D=15jt, K=15jt)
+('JU-2025-009','5401',0,15000000,0),
+('JU-2025-009','1601',0,0,5000000),
+('JU-2025-009','1701',0,0,7000000),
+('JU-2025-009','1801',0,0,3000000),
+-- Detail jurnal 2026
+-- JU-2026-001: Penerimaan Jan 2026 (D=80jt, K=80jt)
+('JU-2026-001','1101',0,80000000,0),
+('JU-2026-001','4101',0,0,30000000),
+('JU-2026-001','4102',0,0,25000000),
+('JU-2026-001','4103',0,0,15000000),
+('JU-2026-001','4104',0,0,5000000),
+('JU-2026-001','4105',0,0,5000000),
+-- JU-2026-002: Pembayaran Biaya Jan 2026 (D=45.5jt, K=45.5jt)
+('JU-2026-002','5101',0,10000000,0),
+('JU-2026-002','5102',0,8000000,0),
+('JU-2026-002','5103',0,5000000,0),
+('JU-2026-002','5201',0,20000000,0),
+('JU-2026-002','5301',0,2500000,0),
+('JU-2026-002','1101',0,0,45500000),
+-- JU-2026-003: Penerimaan Feb 2026 (D=75jt, K=75jt)
+('JU-2026-003','1101',0,75000000,0),
+('JU-2026-003','4101',0,0,28000000),
+('JU-2026-003','4102',0,0,22000000),
+('JU-2026-003','4103',0,0,14000000),
+('JU-2026-003','4104',0,0,6000000),
+('JU-2026-003','4105',0,0,5000000),
+-- JU-2026-004: Pembayaran Biaya Feb 2026 (D=41.6jt, K=41.6jt)
+('JU-2026-004','5101',0,10000000,0),
+('JU-2026-004','5102',0,8000000,0),
+('JU-2026-004','5103',0,5000000,0),
+('JU-2026-004','5201',0,18000000,0),
+('JU-2026-004','5302',0,600000,0),
+('JU-2026-004','1101',0,0,41600000),
+-- JU-2026-005: Penerimaan Mar 2026 (D=85jt, K=85jt)
+('JU-2026-005','1101',0,85000000,0),
+('JU-2026-005','4101',0,0,32000000),
+('JU-2026-005','4102',0,0,27000000),
+('JU-2026-005','4103',0,0,16000000),
+('JU-2026-005','4104',0,0,5000000),
+('JU-2026-005','4105',0,0,5000000),
+-- JU-2026-006: Pembayaran Biaya Mar 2026 (D=48.1jt, K=48.1jt)
+('JU-2026-006','5101',0,10000000,0),
+('JU-2026-006','5102',0,8000000,0),
+('JU-2026-006','5103',0,5000000,0),
+('JU-2026-006','5201',0,22000000,0),
+('JU-2026-006','5301',0,2500000,0),
+('JU-2026-006','5302',0,600000,0),
+('JU-2026-006','1101',0,0,48100000);
CREATE TABLE `mlite_duitku` (
@@ -1679,7 +2056,7 @@ CREATE TABLE `mlite_duitku` (
KEY `reference` (`reference`),
KEY `mlite_duitku_ibfk_1` (`no_rkm_medis`),
CONSTRAINT `mlite_duitku_ibfk_1` FOREIGN KEY (`no_rkm_medis`) REFERENCES `pasien` (`no_rkm_medis`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_eklaim_logs` (
@@ -1692,23 +2069,28 @@ CREATE TABLE `mlite_eklaim_logs` (
`status` int DEFAULT '1',
`username` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-CREATE TABLE `mlite_fenton` (
+CREATE TABLE `mlite_esignatures` (
`id` int NOT NULL AUTO_INCREMENT,
- `no_rawat` varchar(17) NOT NULL,
- `tanggal` datetime NOT NULL,
- `usia_kehamilan` int NOT NULL,
- `tgl_lahir` date NOT NULL,
- `berat_badan` float NOT NULL,
- `lingkar_kepala` int NOT NULL,
- `panjang_badan` int NOT NULL,
- `petugas` varchar(60) NOT NULL,
- `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
- `deleted_at` timestamp NULL DEFAULT NULL,
- PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+ `ref_type` varchar(50) NOT NULL,
+ `ref_id` varchar(50) NOT NULL,
+ `signer_role` varchar(50) NOT NULL,
+ `signer_id` varchar(50) NOT NULL,
+ `signer_name` varchar(255) NOT NULL,
+ `signature_path` varchar(255) NOT NULL,
+ `signature_hash` varchar(255) NOT NULL,
+ `chain_hash` varchar(255) DEFAULT NULL,
+ `signed_at` datetime NOT NULL,
+ `ip_address` varchar(45) NOT NULL,
+ `user_agent` varchar(255) NOT NULL,
+ `legal_basis` text,
+ `audit_json` text,
+ PRIMARY KEY (`id`),
+ KEY `ref_idx` (`ref_type`,`ref_id`),
+ KEY `hash_idx` (`signature_hash`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_geolocation_presensi` (
@@ -1718,7 +2100,7 @@ CREATE TABLE `mlite_geolocation_presensi` (
`longitude` varchar(200) NOT NULL,
KEY `mlite_geolocation_presensi_ibfk_1` (`id`),
CONSTRAINT `mlite_geolocation_presensi_ibfk_1` FOREIGN KEY (`id`) REFERENCES `pegawai` (`id`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_idr_codes` (
@@ -1732,7 +2114,7 @@ CREATE TABLE `mlite_idr_codes` (
`asterisk` tinyint(1) DEFAULT NULL,
`im` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_inacbg_codes` (
@@ -1743,7 +2125,7 @@ CREATE TABLE `mlite_inacbg_codes` (
`system` varchar(100) DEFAULT NULL,
`validcode` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_jurnal` (
@@ -1758,28 +2140,71 @@ CREATE TABLE `mlite_jurnal` (
KEY `tgl_jurnal` (`tgl_jurnal`),
KEY `jenis` (`jenis`),
KEY `keterangan` (`keterangan`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+INSERT INTO `mlite_jurnal` VALUES
+('JU-2025-001','BKT-2025-001','2025-01-15','U','Penerimaan Kasir Rawat Jalan','Penerimaan pendapatan layanan Q1 Januari 2025. Diposting oleh Administrator.'),
+('JU-2025-002','BKT-2025-002','2025-01-31','U','Pembayaran Gaji Karyawan','Pembayaran gaji seluruh karyawan bulan Januari 2025. Diposting oleh Administrator.'),
+('JU-2025-003','BKT-2025-003','2025-04-15','U','Penerimaan Kasir Q2','Penerimaan pendapatan layanan Q2 April 2025. Diposting oleh Administrator.'),
+('JU-2025-004','BKT-2025-004','2025-04-30','U','Pembayaran Biaya Operasional Q2','Pembayaran biaya operasional bulan April 2025. Diposting oleh Administrator.'),
+('JU-2025-005','BKT-2025-005','2025-07-15','U','Penerimaan Kasir Q3','Penerimaan pendapatan layanan Q3 Juli 2025. Diposting oleh Administrator.'),
+('JU-2025-006','BKT-2025-006','2025-07-31','U','Pembayaran Biaya Operasional Q3','Pembayaran biaya operasional bulan Juli 2025. Diposting oleh Administrator.'),
+('JU-2025-007','BKT-2025-007','2025-10-15','U','Penerimaan Kasir Q4','Penerimaan pendapatan layanan Q4 Oktober 2025. Diposting oleh Administrator.'),
+('JU-2025-008','BKT-2025-008','2025-10-31','U','Pembayaran Biaya Operasional Q4','Pembayaran biaya operasional bulan Oktober 2025. Diposting oleh Administrator.'),
+('JU-2025-009','BKT-2025-009','2025-12-31','P','Penyesuaian Akhir Tahun 2025','Jurnal penyesuaian beban penyusutan aset tetap tahun 2025. Diposting oleh Administrator.'),
+('JU-2026-001','BKT-2026-001','2026-01-15','U','Penerimaan Kasir Januari 2026','Penerimaan pendapatan layanan bulan Januari 2026. Diposting oleh Administrator.'),
+('JU-2026-002','BKT-2026-002','2026-01-31','U','Pembayaran Biaya Januari 2026','Pembayaran biaya operasional bulan Januari 2026. Diposting oleh Administrator.'),
+('JU-2026-003','BKT-2026-003','2026-02-15','U','Penerimaan Kasir Februari 2026','Penerimaan pendapatan layanan bulan Februari 2026. Diposting oleh Administrator.'),
+('JU-2026-004','BKT-2026-004','2026-02-28','U','Pembayaran Biaya Februari 2026','Pembayaran biaya operasional bulan Februari 2026. Diposting oleh Administrator.'),
+('JU-2026-005','BKT-2026-005','2026-03-15','U','Penerimaan Kasir Maret 2026','Penerimaan pendapatan layanan bulan Maret 2026. Diposting oleh Administrator.'),
+('JU-2026-006','BKT-2026-006','2026-03-31','U','Pembayaran Biaya Maret 2026','Pembayaran biaya operasional bulan Maret 2026. Diposting oleh Administrator.');
CREATE TABLE `mlite_kasir_shift` (
- `id_shift` INT AUTO_INCREMENT PRIMARY KEY,
- `user_id` VARCHAR(64) NOT NULL,
- `waktu_buka` DATETIME NOT NULL,
- `waktu_tutup` DATETIME NULL,
- `kas_awal` DECIMAL(14,2) DEFAULT 0,
- `kas_akhir` DECIMAL(14,2) DEFAULT 0,
- `total_transaksi` DECIMAL(14,2) DEFAULT 0,
- `selisih` DECIMAL(14,2) DEFAULT 0,
- `keterangan` VARCHAR(255) DEFAULT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+ `id_shift` int NOT NULL AUTO_INCREMENT,
+ `user_id` varchar(64) NOT NULL,
+ `waktu_buka` datetime NOT NULL,
+ `waktu_tutup` datetime DEFAULT NULL,
+ `kas_awal` decimal(14,2) DEFAULT '0.00',
+ `kas_akhir` decimal(14,2) DEFAULT '0.00',
+ `total_transaksi` decimal(14,2) DEFAULT '0.00',
+ `selisih` decimal(14,2) DEFAULT '0.00',
+ `keterangan` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id_shift`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_login_attempts` (
`ip` text,
`attempts` int NOT NULL,
`expires` int NOT NULL DEFAULT '0'
-) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+
+
+INSERT INTO `mlite_login_attempts` VALUES ("127.0.0.1","0","0");
+
+CREATE TABLE `mlite_loinc_lab` (
+ `No` int(11) DEFAULT NULL,
+ `Kategori` text,
+ `NamaPemeriksaan` text,
+ `PermintaanHasil` text,
+ `Spesimen` text,
+ `TipeHasilPemeriksaan` text,
+ `Satuan` text,
+ `MetodeAnalisis` text,
+ `Code` varchar(20) NOT NULL,
+ `Display` text,
+ `Component` text,
+ `Property` text,
+ `Timing` text,
+ `System` text,
+ `Scale` text,
+ `Method` text,
+ `UnitOfMeasure` text,
+ `CodeSystem` text,
+ PRIMARY KEY (`Code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_loinc_radiologi` (
`No` text,
@@ -1800,15 +2225,282 @@ CREATE TABLE `mlite_loinc_radiologi` (
`BodySiteDisplay` text,
`BodySiteCodeSystem` text,
PRIMARY KEY (`Code`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+CREATE TABLE `mlite_kfa` (
+ `kode_kfa` varchar(50) NOT NULL,
+ `nama_kfa` text,
+ `kode_bahan` varchar(50) DEFAULT NULL,
+ `nama_bahan` text,
+ `numerator` varchar(10) DEFAULT NULL,
+ `satuan_num` varchar(10) DEFAULT NULL,
+ `denominator` varchar(10) DEFAULT NULL,
+ `satuan_den` varchar(10) DEFAULT NULL,
+ `nama_satuan_den` varchar(10) DEFAULT NULL,
+ `kode_sediaan` varchar(50) DEFAULT NULL,
+ `nama_sediaan` varchar(100) DEFAULT NULL,
+ `type` enum('obat','alkes') NOT NULL DEFAULT 'obat',
+ PRIMARY KEY (`kode_kfa`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+CREATE TABLE `mlite_ktpl` (
+ `kode_ktpl` varchar(50) NOT NULL,
+ `nama_ktpl` text,
+ `has_modifier` int(11) NOT NULL DEFAULT 0,
+ `modifier_count` int(11) NOT NULL DEFAULT 0,
+ `status` enum('Aktif','Tidak Aktif') NOT NULL DEFAULT 'Aktif',
+ PRIMARY KEY (`kode_ktpl`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_clinical_pathway` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `kode_cp` varchar(30) NOT NULL,
+ `nama_cp` varchar(150) NOT NULL,
+ `jenis_layanan` enum('Ralan','Ranap') NOT NULL DEFAULT 'Ranap',
+ `target_los` int(11) NOT NULL DEFAULT 0,
+ `target_tarif` double NOT NULL DEFAULT 0,
+ `confidence_score` decimal(5,2) NOT NULL DEFAULT 0.00,
+ `evidence_note` text,
+ `guideline_note` text,
+ `aktif` enum('Ya','Tidak') NOT NULL DEFAULT 'Ya',
+ `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `kode_cp` (`kode_cp`),
+ KEY `nama_cp` (`nama_cp`),
+ KEY `jenis_layanan` (`jenis_layanan`),
+ KEY `aktif` (`aktif`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-CREATE TABLE `mlite_modules` (
- `id` int NOT NULL AUTO_INCREMENT,
+
+CREATE TABLE `mlite_clinical_pathway_diagnosis` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `clinical_pathway_id` int(11) NOT NULL,
+ `kd_penyakit` varchar(10) NOT NULL,
+ `prioritas` tinyint(4) NOT NULL DEFAULT 1,
+ `tipe` enum('Utama','Sekunder') NOT NULL DEFAULT 'Utama',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `cp_diagnosis_unique` (`clinical_pathway_id`,`kd_penyakit`,`tipe`),
+ KEY `kd_penyakit` (`kd_penyakit`),
+ CONSTRAINT `fk_cp_diagnosis_cp` FOREIGN KEY (`clinical_pathway_id`) REFERENCES `mlite_clinical_pathway` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `fk_cp_diagnosis_penyakit` FOREIGN KEY (`kd_penyakit`) REFERENCES `penyakit` (`kd_penyakit`) ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_clinical_pathway_day` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `clinical_pathway_id` int(11) NOT NULL,
+ `hari_ke` int(11) NOT NULL,
+ `label_hari` varchar(100) DEFAULT NULL,
+ `tujuan_harian` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `cp_day_unique` (`clinical_pathway_id`,`hari_ke`),
+ CONSTRAINT `fk_cp_day_cp` FOREIGN KEY (`clinical_pathway_id`) REFERENCES `mlite_clinical_pathway` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_clinical_pathway_activity` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `clinical_pathway_day_id` int(11) NOT NULL,
+ `kategori` enum('Assessment','Laboratorium','Radiologi','Obat','Tindakan','Nutrisi','Edukasi','Monitoring','Outcome') NOT NULL,
+ `uraian_kegiatan` varchar(255) DEFAULT NULL,
+ `sumber_tabel` varchar(50) DEFAULT NULL,
+ `item_kode` varchar(50) DEFAULT NULL,
+ `item_nama` varchar(255) NOT NULL,
+ `keterangan` text,
+ `evidence_frequency` int(11) NOT NULL DEFAULT 0,
+ `evidence_percentage` decimal(5,2) NOT NULL DEFAULT 0.00,
+ `evidence_status` enum('Wajib','Direkomendasikan','Opsional') NOT NULL DEFAULT 'Opsional',
+ `wajib` enum('Ya','Tidak') NOT NULL DEFAULT 'Ya',
+ `urutan` int(11) NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`),
+ KEY `clinical_pathway_day_id` (`clinical_pathway_day_id`),
+ KEY `kategori` (`kategori`),
+ KEY `item_kode` (`item_kode`),
+ CONSTRAINT `fk_cp_activity_day` FOREIGN KEY (`clinical_pathway_day_id`) REFERENCES `mlite_clinical_pathway_day` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_clinical_pathway_patient` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `no_rawat` varchar(17) NOT NULL,
+ `clinical_pathway_id` int(11) NOT NULL,
+ `kd_penyakit` varchar(10) DEFAULT NULL,
+ `tanggal_mulai` datetime NOT NULL,
+ `tanggal_selesai` datetime DEFAULT NULL,
+ `status` enum('Draft','Aktif','Selesai','Drop') NOT NULL DEFAULT 'Aktif',
+ `auto_generated` enum('Ya','Tidak') NOT NULL DEFAULT 'Ya',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `no_rawat` (`no_rawat`),
+ KEY `clinical_pathway_id` (`clinical_pathway_id`),
+ KEY `kd_penyakit` (`kd_penyakit`),
+ KEY `status` (`status`),
+ CONSTRAINT `fk_cp_patient_reg` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `fk_cp_patient_cp` FOREIGN KEY (`clinical_pathway_id`) REFERENCES `mlite_clinical_pathway` (`id`) ON UPDATE CASCADE,
+ CONSTRAINT `fk_cp_patient_penyakit` FOREIGN KEY (`kd_penyakit`) REFERENCES `penyakit` (`kd_penyakit`) ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_clinical_pathway_execution` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `clinical_pathway_patient_id` int(11) NOT NULL,
+ `clinical_pathway_activity_id` int(11) NOT NULL,
+ `hari_ke` int(11) NOT NULL,
+ `tanggal_rencana` date DEFAULT NULL,
+ `tanggal_realisasi` datetime DEFAULT NULL,
+ `status` enum('Planned','Completed','Missed','Variance') NOT NULL DEFAULT 'Planned',
+ `sumber_data` varchar(50) DEFAULT NULL,
+ `sumber_referensi` varchar(100) DEFAULT NULL,
+ `petugas` varchar(20) DEFAULT NULL,
+ `catatan` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `cp_exec_unique` (`clinical_pathway_patient_id`,`clinical_pathway_activity_id`,`hari_ke`),
+ KEY `clinical_pathway_activity_id` (`clinical_pathway_activity_id`),
+ KEY `status` (`status`),
+ KEY `tanggal_rencana` (`tanggal_rencana`),
+ CONSTRAINT `fk_cp_execution_patient` FOREIGN KEY (`clinical_pathway_patient_id`) REFERENCES `mlite_clinical_pathway_patient` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `fk_cp_execution_activity` FOREIGN KEY (`clinical_pathway_activity_id`) REFERENCES `mlite_clinical_pathway_activity` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_clinical_pathway_variance` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `clinical_pathway_patient_id` int(11) NOT NULL,
+ `clinical_pathway_execution_id` int(11) DEFAULT NULL,
+ `kategori_variance` enum('Diagnosis','LOS','Obat','Tindakan','Lab','Radiologi','Nutrisi','Edukasi','Outcome','Administrasi') NOT NULL,
+ `penyebab` varchar(255) DEFAULT NULL,
+ `deskripsi` text NOT NULL,
+ `severity` enum('Rendah','Sedang','Tinggi') NOT NULL DEFAULT 'Sedang',
+ `tanggal_variance` datetime NOT NULL,
+ `status_tindak_lanjut` enum('Open','Closed') NOT NULL DEFAULT 'Open',
+ PRIMARY KEY (`id`),
+ KEY `clinical_pathway_patient_id` (`clinical_pathway_patient_id`),
+ KEY `clinical_pathway_execution_id` (`clinical_pathway_execution_id`),
+ KEY `kategori_variance` (`kategori_variance`),
+ CONSTRAINT `fk_cp_variance_patient` FOREIGN KEY (`clinical_pathway_patient_id`) REFERENCES `mlite_clinical_pathway_patient` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `fk_cp_variance_execution` FOREIGN KEY (`clinical_pathway_execution_id`) REFERENCES `mlite_clinical_pathway_execution` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_clinical_pathway_compliance` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `clinical_pathway_patient_id` int(11) NOT NULL,
+ `planned_activity` int(11) NOT NULL DEFAULT 0,
+ `completed_activity` int(11) NOT NULL DEFAULT 0,
+ `missed_activity` int(11) NOT NULL DEFAULT 0,
+ `compliance_percentage` decimal(5,2) NOT NULL DEFAULT 0.00,
+ `kategori_kepatuhan` enum('Sangat Patuh','Patuh','Kurang Patuh','Tidak Patuh') NOT NULL DEFAULT 'Tidak Patuh',
+ `last_calculated_at` datetime DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `clinical_pathway_patient_id` (`clinical_pathway_patient_id`),
+ CONSTRAINT `fk_cp_compliance_patient` FOREIGN KEY (`clinical_pathway_patient_id`) REFERENCES `mlite_clinical_pathway_patient` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_clinical_pathway_audit` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `clinical_pathway_patient_id` int(11) DEFAULT NULL,
+ `clinical_pathway_id` int(11) DEFAULT NULL,
+ `aksi` varchar(100) NOT NULL,
+ `referensi` varchar(100) DEFAULT NULL,
+ `deskripsi` text,
+ `user_aksi` varchar(50) DEFAULT NULL,
+ `created_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `clinical_pathway_patient_id` (`clinical_pathway_patient_id`),
+ KEY `clinical_pathway_id` (`clinical_pathway_id`),
+ KEY `aksi` (`aksi`),
+ CONSTRAINT `fk_cp_audit_patient` FOREIGN KEY (`clinical_pathway_patient_id`) REFERENCES `mlite_clinical_pathway_patient` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
+ CONSTRAINT `fk_cp_audit_cp` FOREIGN KEY (`clinical_pathway_id`) REFERENCES `mlite_clinical_pathway` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_clinical_pathway_cppt_template` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `kd_penyakit` varchar(10) NOT NULL,
+ `ppra` varchar(100) NOT NULL,
+ `subjective` text NOT NULL,
+ `objective` text NOT NULL,
+ `assessment` text NOT NULL,
+ `plan` text NOT NULL,
+ `aktif` enum('Ya','Tidak') NOT NULL DEFAULT 'Ya',
+ `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
+ `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `cppt_template_kd_penyakit_ppra` (`kd_penyakit`,`ppra`),
+ KEY `cppt_template_aktif` (`aktif`),
+ CONSTRAINT `fk_cppt_template_penyakit` FOREIGN KEY (`kd_penyakit`) REFERENCES `penyakit` (`kd_penyakit`) ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_mini_pacs_instance` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `series_id` int NOT NULL,
+ `sop_instance_uid` varchar(100) NOT NULL,
+ `file_path` TEXT NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_sop_instance` (`sop_instance_uid`),
+ KEY `fk_series_id` (`series_id`),
+ CONSTRAINT `fk_mlite_mini_pacs_instance_series` FOREIGN KEY (`series_id`) REFERENCES `mlite_mini_pacs_series` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_mini_pacs_instance_metadata` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `instance_id` int(11) NOT NULL,
+ `tag` varchar(20) NOT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `value` text,
+ PRIMARY KEY (`id`),
+ KEY `idx_instance_id` (`instance_id`),
+ KEY `idx_tag` (`tag`),
+ CONSTRAINT `fk_pacs_instance_metadata` FOREIGN KEY (`instance_id`) REFERENCES `mlite_mini_pacs_instance` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_mini_pacs_series` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `study_id` int NOT NULL,
+ `series_instance_uid` varchar(100) NOT NULL,
+ `series_description` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_series_instance` (`series_instance_uid`),
+ KEY `fk_study_id` (`study_id`),
+ CONSTRAINT `fk_mlite_mini_pacs_series_study` FOREIGN KEY (`study_id`) REFERENCES `mlite_mini_pacs_study` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_mini_pacs_study` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `no_rawat` varchar(17) NOT NULL,
+ `study_instance_uid` varchar(100) NOT NULL,
+ `study_date` datetime DEFAULT NULL,
+ `modality` varchar(10) DEFAULT NULL,
+ `description` text,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uk_study_instance` (`study_instance_uid`),
+ KEY `fk_no_rawat` (`no_rawat`),
+ CONSTRAINT `fk_mlite_mini_pacs_study_no_rawat` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_mini_pacs_worklist_status` (
+ `noorder` varchar(20) NOT NULL,
+ `pulled_at` datetime DEFAULT NULL,
+ `notified` tinyint(1) DEFAULT 0,
+ PRIMARY KEY (`noorder`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_modules` (
+ `id` int NOT NULL AUTO_INCREMENT,
`dir` text,
`sequence` text,
PRIMARY KEY (`id`)
-) ENGINE=MyISAM AUTO_INCREMENT=36 DEFAULT CHARSET=utf8mb3;
+) ENGINE=MyISAM AUTO_INCREMENT=55 DEFAULT CHARSET=utf8mb4;
+
+
INSERT INTO `mlite_modules` VALUES ("1","settings","9"),
("2","dashboard","0"),
("3","master","1"),
@@ -1821,7 +2513,7 @@ INSERT INTO `mlite_modules` VALUES ("1","settings","9"),
("10","modules","7"),
("11","wagateway","10"),
("12","apotek_ralan","11"),
-("13","dokter_ralan","12"),
+("36","afm","31"),
("14","igd","13"),
("15","dokter_igd","14"),
("16","laboratorium","15"),
@@ -1841,7 +2533,22 @@ INSERT INTO `mlite_modules` VALUES ("1","settings","9"),
("30","vedika","29"),
("31","profil","30"),
("32","orthanc","31"),
-("33","veronisa","32");
+("37","bpjs_emr","32"),
+("38","bridging_hfis","33"),
+("39","dokter_ralan","34"),
+("54","esignature","48"),
+("41","icare","36"),
+("42","inventaris","37"),
+("43","jasa_medis","38"),
+("44","laporan","39"),
+("45","veronisa","40"),
+("46","mlite_api_key","41"),
+("47","mlite_logs","42"),
+("48","penjualan","43"),
+("49","satu_sehat","44"),
+("50","sertisign","45"),
+("51","surat","46"),
+("52","utd","47");
CREATE TABLE `mlite_news` (
@@ -1859,7 +2566,7 @@ CREATE TABLE `mlite_news` (
`updated_at` int NOT NULL,
`created_at` int NOT NULL,
PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_news_tags` (
@@ -1867,7 +2574,7 @@ CREATE TABLE `mlite_news_tags` (
`name` varchar(225) DEFAULT NULL,
`slug` varchar(225) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_news_tags_relationship` (
@@ -1877,18 +2584,18 @@ CREATE TABLE `mlite_news_tags_relationship` (
KEY `tag_id` (`tag_id`) USING BTREE,
CONSTRAINT `mlite_news_tags_relationship_ibfk_1` FOREIGN KEY (`news_id`) REFERENCES `mlite_news` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT,
CONSTRAINT `mlite_news_tags_relationship_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `mlite_news_tags` (`id`) ON DELETE CASCADE ON UPDATE RESTRICT
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_notifications` (
`id` int NOT NULL AUTO_INCREMENT,
`judul` varchar(250) NOT NULL,
`pesan` text NOT NULL,
- `tanggal` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `tanggal` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`no_rkm_medis` varchar(255) NOT NULL,
`status` varchar(250) NOT NULL DEFAULT 'unread',
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_odontogram` (
@@ -1896,558 +2603,93 @@ CREATE TABLE `mlite_odontogram` (
`no_rkm_medis` text NOT NULL,
`pemeriksaan` text,
`kondisi` text,
- `catatan` text,
- `id_user` text NOT NULL,
- `tgl_input` text NOT NULL,
- PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-
-
-CREATE TABLE `mlite_ohis` (
- `id` int NOT NULL AUTO_INCREMENT,
- `no_rkm_medis` text NOT NULL,
- `d_16` text,
- `d_11` text,
- `d_26` text,
- `d_36` text,
- `d_31` text,
- `d_46` text,
- `c_16` text,
- `c_11` text,
- `c_26` text,
- `c_36` text,
- `c_31` text,
- `c_46` text,
- `debris` text,
- `calculus` text,
- `nilai` text,
- `kriteria` text,
- `id_user` text NOT NULL,
- `tgl_input` text NOT NULL,
- PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-
-
-CREATE TABLE `mlite_pendaftaran_oral_diagnostic` (
- `no_reg` varchar(8) DEFAULT NULL,
- `no_rawat` varchar(17) NOT NULL,
- `tgl_registrasi` date DEFAULT NULL,
- `jam_reg` time DEFAULT NULL,
- `kd_dokter` varchar(20) DEFAULT NULL,
- `no_rkm_medis` varchar(15) DEFAULT NULL,
- `kd_poli` char(5) DEFAULT NULL,
- `p_jawab` varchar(100) DEFAULT NULL,
- `almt_pj` varchar(200) DEFAULT NULL,
- `hubunganpj` varchar(20) DEFAULT NULL,
- `biaya_reg` double DEFAULT NULL,
- `stts` enum('Belum','Sudah','Batal','Berkas Diterima','Dirujuk','Meninggal','Dirawat','Pulang Paksa') DEFAULT NULL,
- `stts_daftar` enum('-','Lama','Baru') NOT NULL,
- `status_lanjut` enum('Ralan','Ranap') NOT NULL,
- `kd_pj` char(3) NOT NULL,
- `umurdaftar` int DEFAULT NULL,
- `sttsumur` enum('Th','Bl','Hr') DEFAULT NULL,
- `status_bayar` enum('Sudah Bayar','Belum Bayar') NOT NULL,
- `status_poli` enum('Lama','Baru') NOT NULL,
- PRIMARY KEY (`no_rawat`),
- KEY `no_rkm_medis` (`no_rkm_medis`),
- KEY `kd_poli` (`kd_poli`),
- KEY `kd_pj` (`kd_pj`),
- KEY `status_lanjut` (`status_lanjut`),
- KEY `kd_dokter` (`kd_dokter`),
- KEY `status_bayar` (`status_bayar`) USING BTREE,
- CONSTRAINT `mlite_pendaftaran_oral_diagnostic_ibfk_3` FOREIGN KEY (`kd_poli`) REFERENCES `poliklinik` (`kd_poli`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_pendaftaran_oral_diagnostic_ibfk_4` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_pendaftaran_oral_diagnostic_ibfk_6` FOREIGN KEY (`kd_pj`) REFERENCES `penjab` (`kd_pj`) ON UPDATE CASCADE,
- CONSTRAINT `mlite_pendaftaran_oral_diagnostic_ibfk_7` FOREIGN KEY (`no_rkm_medis`) REFERENCES `pasien` (`no_rkm_medis`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-
-
-CREATE TABLE `mlite_pengaduan` (
- `id` varchar(15) NOT NULL,
- `tanggal` datetime NOT NULL,
- `no_rkm_medis` varchar(15) NOT NULL,
- `pesan` varchar(255) NOT NULL,
- PRIMARY KEY (`id`),
- KEY `no_rkm_medis` (`no_rkm_medis`),
- CONSTRAINT `mlite_pengaduan_ibfk_1` FOREIGN KEY (`no_rkm_medis`) REFERENCES `pasien` (`no_rkm_medis`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-
-
-CREATE TABLE `mlite_pengaduan_detail` (
- `id` int NOT NULL AUTO_INCREMENT,
- `pengaduan_id` varchar(15) NOT NULL,
- `tanggal` datetime NOT NULL,
- `no_rkm_medis` varchar(15) NOT NULL,
- `pesan` varchar(225) DEFAULT NULL,
- PRIMARY KEY (`id`) USING BTREE,
- KEY `pengaduan_detail_ibfk_1` (`pengaduan_id`),
- CONSTRAINT `mlite_pengaduan_detail_ibfk_1` FOREIGN KEY (`pengaduan_id`) REFERENCES `mlite_pengaduan` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
-
-
-CREATE TABLE `mlite_penilaian_awal_keperawatan_gigi` (
- `no_rawat` varchar(17) NOT NULL,
- `tanggal` datetime NOT NULL,
- `informasi` enum('Autoanamnesis','Alloanamnesis') NOT NULL,
- `td` varchar(8) NOT NULL DEFAULT '',
- `nadi` varchar(5) NOT NULL DEFAULT '',
- `rr` varchar(5) NOT NULL,
- `suhu` varchar(5) NOT NULL DEFAULT '',
- `bb` varchar(5) NOT NULL DEFAULT '',
- `tb` varchar(5) NOT NULL DEFAULT '',
- `bmi` varchar(10) NOT NULL,
- `keluhan_utama` varchar(150) NOT NULL DEFAULT '',
- `riwayat_penyakit` enum('Tidak Ada','Diabetes Melitus','Hipertensi','Penyakit Jantung','HIV','Hepatitis','Haemophilia','Lain-lain') DEFAULT NULL,
- `ket_riwayat_penyakit` varchar(30) NOT NULL,
- `alergi` varchar(25) NOT NULL DEFAULT '',
- `riwayat_perawatan_gigi` enum('Tidak','Ya, Kapan') NOT NULL,
- `ket_riwayat_perawatan_gigi` varchar(50) NOT NULL DEFAULT '',
- `kebiasaan_sikat_gigi` enum('1x','2x','3x','Mandi','Setelah Makan','Sebelum Tidur') NOT NULL,
- `kebiasaan_lain` enum('Tidak ada','Minum kopi/teh','Minum alkohol','Bruxism','Menggigit pensil','Mengunyah 1 sisi rahang','Merokok','Lain-lain') DEFAULT NULL,
- `ket_kebiasaan_lain` varchar(30) NOT NULL,
- `obat_yang_diminum_saatini` varchar(100) DEFAULT NULL,
- `alat_bantu` enum('Tidak','Ya') NOT NULL,
- `ket_alat_bantu` varchar(30) NOT NULL,
- `prothesa` enum('Tidak','Ya') NOT NULL,
- `ket_pro` varchar(50) NOT NULL,
- `status_psiko` enum('Tenang','Takut','Cemas','Depresi','Lain-lain') NOT NULL,
- `ket_psiko` varchar(70) NOT NULL,
- `hub_keluarga` enum('Baik','Tidak Baik') NOT NULL,
- `tinggal_dengan` enum('Sendiri','Orang Tua','Suami / Istri','Lainnya') NOT NULL,
- `ket_tinggal` varchar(40) NOT NULL,
- `ekonomi` enum('Baik','Cukup','Kurang') NOT NULL,
- `budaya` enum('Tidak Ada','Ada') NOT NULL,
- `ket_budaya` varchar(50) NOT NULL,
- `edukasi` enum('Pasien','Keluarga') NOT NULL,
- `ket_edukasi` varchar(50) NOT NULL,
- `berjalan_a` enum('Ya','Tidak') NOT NULL,
- `berjalan_b` enum('Ya','Tidak') NOT NULL,
- `berjalan_c` enum('Ya','Tidak') NOT NULL,
- `hasil` enum('Tidak beresiko (tidak ditemukan a dan b)','Resiko rendah (ditemukan a/b)','Resiko tinggi (ditemukan a dan b)') NOT NULL,
- `lapor` enum('Ya','Tidak') NOT NULL,
- `ket_lapor` varchar(15) NOT NULL,
- `nyeri` enum('Tidak Ada Nyeri','Nyeri Akut','Nyeri Kronis') NOT NULL,
- `lokasi` varchar(50) NOT NULL,
- `skala_nyeri` enum('0','1','2','3','4','5','6','7','8','9','10') NOT NULL,
- `durasi` varchar(25) NOT NULL,
- `frekuensi` varchar(25) NOT NULL,
- `nyeri_hilang` enum('Istirahat','Medengar Musik','Minum Obat','Tidak ada nyeri','Lain-lain') NOT NULL,
- `ket_nyeri` varchar(40) NOT NULL,
- `pada_dokter` enum('Tidak','Ya') NOT NULL,
- `ket_dokter` varchar(15) NOT NULL,
- `kebersihan_mulut` enum('Baik','Cukup','Kurang') NOT NULL,
- `mukosa_mulut` enum('Normal','Pigmentasi','Radang') NOT NULL,
- `karies` enum('Ada','Tidak') NOT NULL,
- `karang_gigi` enum('Ada','Tidak') NOT NULL,
- `gingiva` enum('Normal','Radang') NOT NULL,
- `palatum` enum('Normal','Radang') NOT NULL,
- `rencana` varchar(200) NOT NULL,
- `nip` varchar(20) NOT NULL,
- PRIMARY KEY (`no_rawat`) USING BTREE,
- KEY `nip` (`nip`) USING BTREE,
- CONSTRAINT `mlite_penilaian_awal_keperawatan_gigi_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_penilaian_awal_keperawatan_gigi_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
-
-
-CREATE TABLE `mlite_penilaian_awal_keperawatan_ralan` (
- `no_rawat` varchar(17) NOT NULL,
- `tanggal` datetime NOT NULL,
- `informasi` enum('Autoanamnesis','Alloanamnesis') NOT NULL,
- `td` varchar(8) NOT NULL DEFAULT '',
- `nadi` varchar(5) NOT NULL DEFAULT '',
- `rr` varchar(5) NOT NULL,
- `suhu` varchar(5) NOT NULL DEFAULT '',
- `gcs` varchar(5) NOT NULL,
- `bb` varchar(5) NOT NULL DEFAULT '',
- `tb` varchar(5) NOT NULL DEFAULT '',
- `bmi` varchar(10) NOT NULL,
- `keluhan_utama` varchar(150) NOT NULL DEFAULT '',
- `rpd` varchar(100) NOT NULL DEFAULT '',
- `rpk` varchar(100) NOT NULL,
- `rpo` varchar(100) NOT NULL,
- `alergi` varchar(25) NOT NULL DEFAULT '',
- `alat_bantu` enum('Tidak','Ya') NOT NULL,
- `ket_bantu` varchar(50) NOT NULL DEFAULT '',
- `prothesa` enum('Tidak','Ya') NOT NULL,
- `ket_pro` varchar(50) NOT NULL,
- `adl` enum('Mandiri','Dibantu') NOT NULL,
- `status_psiko` enum('Tenang','Takut','Cemas','Depresi','Lain-lain') NOT NULL,
- `ket_psiko` varchar(70) NOT NULL,
- `hub_keluarga` enum('Baik','Tidak Baik') NOT NULL,
- `tinggal_dengan` enum('Sendiri','Orang Tua','Suami / Istri','Lainnya') NOT NULL,
- `ket_tinggal` varchar(40) NOT NULL,
- `ekonomi` enum('Baik','Cukup','Kurang') NOT NULL,
- `budaya` enum('Tidak Ada','Ada') NOT NULL,
- `ket_budaya` varchar(50) NOT NULL,
- `edukasi` enum('Pasien','Keluarga') NOT NULL,
- `ket_edukasi` varchar(50) NOT NULL,
- `berjalan_a` enum('Ya','Tidak') NOT NULL,
- `berjalan_b` enum('Ya','Tidak') NOT NULL,
- `berjalan_c` enum('Ya','Tidak') NOT NULL,
- `hasil` enum('Tidak beresiko (tidak ditemukan a dan b)','Resiko rendah (ditemukan a/b)','Resiko tinggi (ditemukan a dan b)') NOT NULL,
- `lapor` enum('Ya','Tidak') NOT NULL,
- `ket_lapor` varchar(15) NOT NULL,
- `sg1` enum('Tidak','Tidak Yakin','Ya, 1-5 Kg','Ya, 6-10 Kg','Ya, 11-15 Kg','Ya, >15 Kg') NOT NULL,
- `nilai1` enum('0','1','2','3','4') NOT NULL,
- `sg2` enum('Ya','Tidak') NOT NULL,
- `nilai2` enum('0','1') NOT NULL,
- `total_hasil` tinyint NOT NULL,
- `nyeri` enum('Tidak Ada Nyeri','Nyeri Akut','Nyeri Kronis') NOT NULL,
- `provokes` enum('Proses Penyakit','Benturan','Lain-lain') NOT NULL,
- `ket_provokes` varchar(40) NOT NULL,
- `quality` enum('Seperti Tertusuk','Berdenyut','Teriris','Tertindih','Tertiban','Lain-lain') NOT NULL,
- `ket_quality` varchar(50) NOT NULL,
- `lokasi` varchar(50) NOT NULL,
- `menyebar` enum('Tidak','Ya') NOT NULL,
- `skala_nyeri` enum('0','1','2','3','4','5','6','7','8','9','10') NOT NULL,
- `durasi` varchar(25) NOT NULL,
- `nyeri_hilang` enum('Istirahat','Medengar Musik','Minum Obat') NOT NULL,
- `ket_nyeri` varchar(40) NOT NULL,
- `pada_dokter` enum('Tidak','Ya') NOT NULL,
- `ket_dokter` varchar(15) NOT NULL,
- `rencana` varchar(200) NOT NULL,
- `nip` varchar(20) NOT NULL,
- PRIMARY KEY (`no_rawat`) USING BTREE,
- KEY `nip` (`nip`) USING BTREE,
- CONSTRAINT `mlite_penilaian_awal_keperawatan_ralan_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_penilaian_awal_keperawatan_ralan_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
-
-
-CREATE TABLE `mlite_penilaian_awal_keperawatan_ranap` (
- `no_rawat` varchar(17) NOT NULL,
- `tanggal` datetime NOT NULL,
- `informasi` enum('Autoanamnesis','Alloanamnesis') NOT NULL,
- `ket_informasi` varchar(30) NOT NULL,
- `tiba_diruang_rawat` enum('Jalan Tanpa Bantuan','Kursi Roda','Brankar') NOT NULL,
- `kasus_trauma` enum('Trauma','Non Trauma') DEFAULT NULL,
- `cara_masuk` enum('Poli','IGD','Lain-lain') NOT NULL,
- `rps` varchar(300) NOT NULL,
- `rpd` varchar(100) NOT NULL,
- `rpk` varchar(100) NOT NULL,
- `rpo` varchar(100) NOT NULL,
- `riwayat_pembedahan` varchar(40) NOT NULL,
- `riwayat_dirawat_dirs` varchar(40) NOT NULL,
- `alat_bantu_dipakai` enum('Kacamata','Prothesa','Alat Bantu Dengar','Lain-lain') NOT NULL,
- `riwayat_kehamilan` enum('Tidak','Ya') NOT NULL,
- `riwayat_kehamilan_perkiraan` varchar(30) NOT NULL,
- `riwayat_tranfusi` varchar(40) NOT NULL,
- `riwayat_alergi` varchar(40) NOT NULL,
- `riwayat_merokok` enum('Tidak','Ya') NOT NULL,
- `riwayat_merokok_jumlah` varchar(5) NOT NULL,
- `riwayat_alkohol` enum('Tidak','Ya') NOT NULL,
- `riwayat_alkohol_jumlah` varchar(5) NOT NULL,
- `riwayat_narkoba` enum('Tidak','Ya') NOT NULL,
- `riwayat_olahraga` enum('Tidak','Ya') NOT NULL,
- `pemeriksaan_mental` varchar(40) NOT NULL,
- `pemeriksaan_keadaan_umum` enum('Baik','Sedang','Buruk') NOT NULL,
- `pemeriksaan_gcs` varchar(10) NOT NULL,
- `pemeriksaan_td` varchar(8) NOT NULL,
- `pemeriksaan_nadi` varchar(5) NOT NULL,
- `pemeriksaan_rr` varchar(5) NOT NULL,
- `pemeriksaan_suhu` varchar(5) NOT NULL,
- `pemeriksaan_spo2` varchar(5) NOT NULL,
- `pemeriksaan_bb` varchar(5) NOT NULL,
- `pemeriksaan_tb` varchar(5) NOT NULL,
- `pemeriksaan_susunan_kepala` enum('TAK','Hydrocephalus','Hematoma','Lain-lain') NOT NULL,
- `pemeriksaan_susunan_wajah` enum('TAK','Asimetris','Kelainan Kongenital') NOT NULL,
- `pemeriksaan_susunan_leher` enum('TAK','Kaku Kuduk','Pembesaran Thyroid','Pembesaran KGB') NOT NULL,
- `pemeriksaan_susunan_kejang` enum('TAK','Kuat','Ada') NOT NULL,
- `pemeriksaan_susunan_sensorik` enum('TAK','Sakit Nyeri','Rasa kebas') NOT NULL,
- `pemeriksaan_kardiovaskuler_denyut_nadi` enum('Teratur','Tidak Teratur') NOT NULL,
- `pemeriksaan_kardiovaskuler_sirkulasi` enum('Akral Hangat','Akral Dingin','Edema') NOT NULL,
- `pemeriksaan_kardiovaskuler_pulsasi` enum('Kuat','Lemah','Lain-lain') NOT NULL,
- `pemeriksaan_respirasi_pola_nafas` enum('Normal','Bradipnea','Tachipnea') NOT NULL,
- `pemeriksaan_respirasi_retraksi` enum('Tidak Ada','Ringan','Berat') NOT NULL,
- `pemeriksaan_respirasi_suara_nafas` enum('Vesikuler','Wheezing','Rhonki') NOT NULL,
- `pemeriksaan_respirasi_volume_pernafasan` enum('Normal','Hiperventilasi','Hipoventilasi') NOT NULL,
- `pemeriksaan_respirasi_jenis_pernafasan` enum('Pernafasan Dada','Alat Bantu Pernafasaan') NOT NULL,
- `pemeriksaan_respirasi_irama_nafas` enum('Teratur','Tidak Teratur') NOT NULL,
- `pemeriksaan_respirasi_batuk` enum('Tidak','Ya : Produktif','Ya : Non Produktif') NOT NULL,
- `pemeriksaan_gastrointestinal_mulut` enum('TAK','Stomatitis','Mukosa Kering','Bibir Pucat','Lain-lain') NOT NULL,
- `pemeriksaan_gastrointestinal_gigi` enum('TAK','Karies','Goyang','Lain-lain') NOT NULL,
- `pemeriksaan_gastrointestinal_lidah` enum('TAK','Kotor','Gerak Asimetris','Lain-lain') NOT NULL,
- `pemeriksaan_gastrointestinal_tenggorokan` enum('TAK','Gangguan Menelan','Sakit Menelan','Lain-lain') NOT NULL,
- `pemeriksaan_gastrointestinal_abdomen` enum('Supel','Asictes',' Tegang','Nyeri Tekan/Lepas','Lain-lain') NOT NULL,
- `pemeriksaan_gastrointestinal_peistatik_usus` enum('TAK','Tidak Ada Bising Usus','Hiperistaltik') NOT NULL,
- `pemeriksaan_gastrointestinal_anus` enum('TAK','Atresia Ani') NOT NULL,
- `pemeriksaan_neurologi_pengelihatan` enum('TAK','Ada Kelainan') NOT NULL,
- `pemeriksaan_neurologi_alat_bantu_penglihatan` enum('Tidak','Kacamata','Lensa Kontak') NOT NULL,
- `pemeriksaan_neurologi_pendengaran` enum('TAK','Berdengung','Nyeri','Tuli','Keluar Cairan','Lain-lain') NOT NULL,
- `pemeriksaan_neurologi_bicara` enum('Jelas','Tidak Jelas') NOT NULL,
- `pemeriksaan_neurologi_sensorik` enum('TAK','Sakit Nyeri','Rasa Kebas','Lain-lain') NOT NULL,
- `pemeriksaan_neurologi_motorik` enum('TAK','Hemiparese','Tetraparese','Tremor','Lain-lain') NOT NULL,
- `pemeriksaan_neurologi_kekuatan_otot` enum('Kuat','Lemah') NOT NULL,
- `pemeriksaan_integument_warnakulit` enum('Pucat','Sianosis','Normal','Lain-lain') NOT NULL,
- `pemeriksaan_integument_turgor` enum('Baik','Sedang','Buruk') NOT NULL,
- `pemeriksaan_integument_kulit` enum('Normal','Rash/Kemerahan','Luka','Memar','Ptekie','Bula') NOT NULL,
- `pemeriksaan_integument_dekubitas` enum('Tidak Ada','Usia > 65 tahun','Obesitas','Imobilisasi','Paraplegi/Vegetative State','Dirawat Di HCU','Penyakit Kronis (DM, CHF, CKD)','Inkontinentia Uri/Alvi') NOT NULL,
- `pemeriksaan_muskuloskletal_pergerakan_sendi` enum('Bebas','Terbatas') NOT NULL,
- `pemeriksaan_muskuloskletal_kekauatan_otot` enum('Baik','Lemah','Tremor') NOT NULL,
- `pemeriksaan_muskuloskletal_nyeri_sendi` enum('Tidak Ada','Ada') NOT NULL,
- `pemeriksaan_muskuloskletal_oedema` enum('Tidak Ada','Ada') NOT NULL,
- `pemeriksaan_muskuloskletal_fraktur` enum('Tidak Ada','Ada') NOT NULL,
- `pemeriksaan_eliminasi_bab_frekuensi_jumlah` varchar(5) NOT NULL,
- `pemeriksaan_eliminasi_bab_frekuensi_durasi` varchar(10) NOT NULL,
- `pemeriksaan_eliminasi_bab_konsistensi` varchar(30) NOT NULL,
- `pemeriksaan_eliminasi_bab_warna` varchar(30) NOT NULL,
- `pemeriksaan_eliminasi_bak_frekuensi_jumlah` varchar(5) NOT NULL,
- `pemeriksaan_eliminasi_bak_frekuensi_durasi` varchar(10) NOT NULL,
- `pemeriksaan_eliminasi_bak_warna` varchar(30) NOT NULL,
- `pemeriksaan_eliminasi_bak_lainlain` varchar(30) NOT NULL,
- `pola_aktifitas_makanminum` enum('Mandiri','Bantuan Orang Lain') NOT NULL,
- `pola_aktifitas_mandi` enum('Mandiri','Bantuan Orang Lain') NOT NULL,
- `pola_aktifitas_eliminasi` enum('Mandiri','Bantuan Orang Lain') NOT NULL,
- `pola_aktifitas_berpakaian` enum('Mandiri','Bantuan Orang Lain') NOT NULL,
- `pola_aktifitas_berpindah` enum('Mandiri','Bantuan Orang Lain') NOT NULL,
- `pola_nutrisi_frekuesi_makan` varchar(3) NOT NULL,
- `pola_nutrisi_jenis_makanan` varchar(20) NOT NULL,
- `pola_nutrisi_porsi_makan` varchar(3) NOT NULL,
- `pola_tidur_lama_tidur` varchar(3) NOT NULL,
- `pola_tidur_gangguan` enum('Tidak Ada Gangguan','Insomnia') NOT NULL,
- `pengkajian_fungsi_kemampuan_sehari` enum('Mandiri','Bantuan Minimal','Bantuan Sebagian','Ketergantungan Total') NOT NULL,
- `pengkajian_fungsi_aktifitas` enum('Tirah Baring','Duduk','Berjalan') NOT NULL,
- `pengkajian_fungsi_berjalan` enum('TAK','Penurunan Kekuatan/ROM','Paralisis','Sering Jatuh','Deformitas','Hilang Keseimbangan','Riwayat Patah Tulang','Lain-lain') NOT NULL,
- `pengkajian_fungsi_ambulasi` enum('Walker','Tongkat','Kursi Roda','Tidak Menggunakan') NOT NULL,
- `pengkajian_fungsi_ekstrimitas_atas` enum('TAK','Lemah','Oedema','Tidak Simetris','Lain-lain') NOT NULL,
- `pengkajian_fungsi_ekstrimitas_bawah` enum('TAK','Varises','Oedema','Tidak Simetris','Lain-lain') NOT NULL,
- `pengkajian_fungsi_menggenggam` enum('Tidak Ada Kesulitan','Terakhir','Lain-lain') NOT NULL,
- `pengkajian_fungsi_koordinasi` enum('Tidak Ada Kesulitan','Ada Masalah') NOT NULL,
- `pengkajian_fungsi_kesimpulan` enum('Ya (Co DPJP)','Tidak (Tidak Perlu Co DPJP)') NOT NULL,
- `riwayat_psiko_kondisi_psiko` enum('Tidak Ada Masalah','Marah','Takut','Depresi','Cepat Lelah','Cemas','Gelisah','Sulit Tidur','Lain-lain') NOT NULL,
- `riwayat_psiko_gangguan_jiwa` enum('Ya','Tidak') NOT NULL,
- `riwayat_psiko_perilaku` enum('Tidak Ada Masalah','Perilaku Kekerasan','Gangguan Efek','Gangguan Memori','Halusinasi','Kecenderungan Percobaan Bunuh Diri','Lain-lain') NOT NULL,
- `riwayat_psiko_hubungan_keluarga` enum('Harmonis','Kurang Harmonis','Tidak Harmonis','Konflik Besar') NOT NULL,
- `riwayat_psiko_tinggal` enum('Sendiri','Orang Tua','Suami/Istri','Keluarga','Lain-lain') NOT NULL,
- `riwayat_psiko_nilai_kepercayaan` enum('Tidak Ada','Ada') NOT NULL,
- `riwayat_psiko_pendidikan_pj` enum('-','TS','TK','SD','SMP','SMA','SLTA/SEDERAJAT','D1','D2','D3','D4','S1','S2','S3') NOT NULL,
- `riwayat_psiko_edukasi_diberikan` enum('Pasien','Keluarga') NOT NULL,
- `penilaian_nyeri` enum('Tidak Ada Nyeri','Nyeri Akut','Nyeri Kronis') NOT NULL,
- `penilaian_nyeri_penyebab` enum('Proses Penyakit','Benturan','Lain-lain') NOT NULL,
- `penilaian_nyeri_kualitas` enum('Seperti Tertusuk','Berdenyut','Teriris','Tertindih','Tertiban','Lain-lain') NOT NULL,
- `penilaian_nyeri_lokasi` varchar(50) NOT NULL,
- `penilaian_nyeri_menyebar` enum('Tidak','Ya') NOT NULL,
- `penilaian_nyeri_skala` enum('0','1','2','3','4','5','6','7','8','9','10') NOT NULL,
- `penilaian_nyeri_waktu` varchar(5) NOT NULL,
- `penilaian_nyeri_hilang` enum('Istirahat','Medengar Musik','Minum Obat') NOT NULL,
- `penilaian_nyeri_diberitahukan_dokter` enum('Tidak','Ya') NOT NULL,
- `penilaian_nyeri_jam_diberitahukan_dokter` varchar(10) NOT NULL,
- `penilaian_jatuhmorse_skala1` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhmorse_nilai1` tinyint DEFAULT NULL,
- `penilaian_jatuhmorse_skala2` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhmorse_nilai2` tinyint DEFAULT NULL,
- `penilaian_jatuhmorse_skala3` enum('Tidak Ada/Kursi Roda/Perawat/Tirah Baring','Tongkat/Alat Penopang','Berpegangan Pada Perabot') DEFAULT NULL,
- `penilaian_jatuhmorse_nilai3` tinyint DEFAULT NULL,
- `penilaian_jatuhmorse_skala4` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhmorse_nilai4` tinyint DEFAULT NULL,
- `penilaian_jatuhmorse_skala5` enum('Normal/Tirah Baring/Imobilisasi','Lemah','Terganggu') DEFAULT NULL,
- `penilaian_jatuhmorse_nilai5` tinyint DEFAULT NULL,
- `penilaian_jatuhmorse_skala6` enum('Sadar Akan Kemampuan Diri Sendiri','Sering Lupa Akan Keterbatasan Yang Dimiliki') DEFAULT NULL,
- `penilaian_jatuhmorse_nilai6` tinyint DEFAULT NULL,
- `penilaian_jatuhmorse_totalnilai` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala1` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai1` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala2` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai2` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala3` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai3` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala4` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai4` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala5` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai5` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala6` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai6` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala7` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai7` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala8` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai8` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala9` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai9` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala10` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai10` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_skala11` enum('Tidak','Ya') DEFAULT NULL,
- `penilaian_jatuhsydney_nilai11` tinyint DEFAULT NULL,
- `penilaian_jatuhsydney_totalnilai` tinyint DEFAULT NULL,
- `skrining_gizi1` enum('Tidak ada penurunan berat badan','Tidak yakin/ tidak tahu/ terasa baju lebih longgar','Ya 1-5 kg','Ya 6-10 kg','Ya 11-15 kg','Ya > 15 kg') DEFAULT NULL,
- `nilai_gizi1` int DEFAULT NULL,
- `skrining_gizi2` enum('Tidak','Ya') DEFAULT NULL,
- `nilai_gizi2` int DEFAULT NULL,
- `nilai_total_gizi` double DEFAULT NULL,
- `skrining_gizi_diagnosa_khusus` enum('Tidak','Ya') DEFAULT NULL,
- `skrining_gizi_diketahui_dietisen` enum('Tidak','Ya') DEFAULT NULL,
- `skrining_gizi_jam_diketahui_dietisen` varchar(10) DEFAULT NULL,
- `rencana` varchar(200) DEFAULT NULL,
- `nip1` varchar(20) NOT NULL,
- `nip2` varchar(20) NOT NULL,
- `kd_dokter` varchar(20) NOT NULL,
- PRIMARY KEY (`no_rawat`) USING BTREE,
- KEY `nip1` (`nip1`) USING BTREE,
- KEY `nip2` (`nip2`) USING BTREE,
- KEY `kd_dokter` (`kd_dokter`) USING BTREE,
- CONSTRAINT `mlite_penilaian_awal_keperawatan_ranap_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_penilaian_awal_keperawatan_ranap_ibfk_2` FOREIGN KEY (`nip1`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_penilaian_awal_keperawatan_ranap_ibfk_3` FOREIGN KEY (`nip2`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_penilaian_awal_keperawatan_ranap_ibfk_4` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+ `catatan` text,
+ `id_user` text NOT NULL,
+ `tgl_input` text NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-CREATE TABLE `mlite_penilaian_medis_igd` (
- `no_rawat` varchar(17) NOT NULL,
- `tanggal` datetime NOT NULL,
- `kd_dokter` varchar(20) NOT NULL,
- `anamnesis` enum('Autoanamnesis','Alloanamnesis') NOT NULL,
- `hubungan` varchar(100) NOT NULL,
- `keluhan_utama` varchar(2000) NOT NULL DEFAULT '',
- `rps` varchar(2000) NOT NULL,
- `rpd` varchar(1000) NOT NULL DEFAULT '',
- `rpk` varchar(1000) NOT NULL,
- `rpo` varchar(1000) NOT NULL,
- `alergi` varchar(100) NOT NULL DEFAULT '',
- `keadaan` enum('Sehat','Sakit Ringan','Sakit Sedang','Sakit Berat') NOT NULL,
- `gcs` varchar(10) NOT NULL,
- `kesadaran` enum('Compos Mentis','Apatis','Somnolen','Sopor','Koma') NOT NULL,
- `td` varchar(8) NOT NULL DEFAULT '',
- `nadi` varchar(5) NOT NULL DEFAULT '',
- `rr` varchar(5) NOT NULL,
- `suhu` varchar(5) NOT NULL DEFAULT '',
- `spo` varchar(5) NOT NULL,
- `bb` varchar(5) NOT NULL DEFAULT '',
- `tb` varchar(5) NOT NULL DEFAULT '',
- `kepala` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `mata` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `gigi` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `leher` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `thoraks` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `abdomen` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `genital` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `ekstremitas` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `ket_fisik` text NOT NULL,
- `ket_lokalis` text NOT NULL,
- `ekg` text NOT NULL,
- `rad` text NOT NULL,
- `lab` text NOT NULL,
- `diagnosis` varchar(500) NOT NULL,
- `tata` text NOT NULL,
- PRIMARY KEY (`no_rawat`) USING BTREE,
- KEY `kd_dokter` (`kd_dokter`) USING BTREE,
- CONSTRAINT `mlite_penilaian_medis_igd_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_penilaian_medis_igd_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+CREATE TABLE `mlite_ohis` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `no_rkm_medis` text NOT NULL,
+ `d_16` text,
+ `d_11` text,
+ `d_26` text,
+ `d_36` text,
+ `d_31` text,
+ `d_46` text,
+ `c_16` text,
+ `c_11` text,
+ `c_26` text,
+ `c_36` text,
+ `c_31` text,
+ `c_46` text,
+ `debris` text,
+ `calculus` text,
+ `nilai` text,
+ `kriteria` text,
+ `id_user` text NOT NULL,
+ `tgl_input` text NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-CREATE TABLE `mlite_penilaian_medis_ralan` (
+CREATE TABLE `mlite_pendaftaran_oral_diagnostic` (
+ `no_reg` varchar(8) DEFAULT NULL,
`no_rawat` varchar(17) NOT NULL,
- `tanggal` datetime NOT NULL,
- `kd_dokter` varchar(20) NOT NULL,
- `anamnesis` enum('Autoanamnesis','Alloanamnesis') NOT NULL,
- `hubungan` varchar(30) NOT NULL,
- `keluhan_utama` varchar(2000) NOT NULL DEFAULT '',
- `rps` varchar(2000) NOT NULL,
- `rpd` varchar(1000) NOT NULL DEFAULT '',
- `rpk` varchar(1000) NOT NULL,
- `rpo` varchar(1000) NOT NULL,
- `alergi` varchar(50) NOT NULL DEFAULT '',
- `keadaan` enum('Sehat','Sakit Ringan','Sakit Sedang','Sakit Berat') NOT NULL,
- `gcs` varchar(10) NOT NULL,
- `kesadaran` enum('Compos Mentis','Apatis','Somnolen','Sopor','Koma') NOT NULL,
- `td` varchar(8) NOT NULL DEFAULT '',
- `nadi` varchar(5) NOT NULL DEFAULT '',
- `rr` varchar(5) NOT NULL,
- `suhu` varchar(5) NOT NULL DEFAULT '',
- `spo` varchar(5) NOT NULL,
- `bb` varchar(5) NOT NULL DEFAULT '',
- `tb` varchar(5) NOT NULL DEFAULT '',
- `kepala` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `gigi` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `tht` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `thoraks` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `abdomen` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `genital` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `ekstremitas` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `kulit` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `ket_fisik` text NOT NULL,
- `ket_lokalis` text NOT NULL,
- `penunjang` text NOT NULL,
- `diagnosis` varchar(500) NOT NULL,
- `tata` text NOT NULL,
- `konsulrujuk` varchar(1000) NOT NULL,
- PRIMARY KEY (`no_rawat`) USING BTREE,
- KEY `kd_dokter` (`kd_dokter`) USING BTREE,
- CONSTRAINT `mlite_penilaian_medis_ralan_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_penilaian_medis_ralan_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+ `tgl_registrasi` date DEFAULT NULL,
+ `jam_reg` time DEFAULT NULL,
+ `kd_dokter` varchar(20) DEFAULT NULL,
+ `no_rkm_medis` varchar(15) DEFAULT NULL,
+ `kd_poli` char(5) DEFAULT NULL,
+ `p_jawab` varchar(100) DEFAULT NULL,
+ `almt_pj` varchar(200) DEFAULT NULL,
+ `hubunganpj` varchar(20) DEFAULT NULL,
+ `biaya_reg` double DEFAULT NULL,
+ `stts` enum('Belum','Sudah','Batal','Berkas Diterima','Dirujuk','Meninggal','Dirawat','Pulang Paksa') DEFAULT NULL,
+ `stts_daftar` enum('-','Lama','Baru') NOT NULL,
+ `status_lanjut` enum('Ralan','Ranap') NOT NULL,
+ `kd_pj` char(3) NOT NULL,
+ `umurdaftar` int DEFAULT NULL,
+ `sttsumur` enum('Th','Bl','Hr') DEFAULT NULL,
+ `status_bayar` enum('Sudah Bayar','Belum Bayar') NOT NULL,
+ `status_poli` enum('Lama','Baru') NOT NULL,
+ PRIMARY KEY (`no_rawat`),
+ KEY `no_rkm_medis` (`no_rkm_medis`),
+ KEY `kd_poli` (`kd_poli`),
+ KEY `kd_pj` (`kd_pj`),
+ KEY `status_lanjut` (`status_lanjut`),
+ KEY `kd_dokter` (`kd_dokter`),
+ KEY `status_bayar` (`status_bayar`) USING BTREE,
+ CONSTRAINT `mlite_pendaftaran_oral_diagnostic_ibfk_3` FOREIGN KEY (`kd_poli`) REFERENCES `poliklinik` (`kd_poli`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `mlite_pendaftaran_oral_diagnostic_ibfk_4` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE,
+ CONSTRAINT `mlite_pendaftaran_oral_diagnostic_ibfk_6` FOREIGN KEY (`kd_pj`) REFERENCES `penjab` (`kd_pj`) ON UPDATE CASCADE,
+ CONSTRAINT `mlite_pendaftaran_oral_diagnostic_ibfk_7` FOREIGN KEY (`no_rkm_medis`) REFERENCES `pasien` (`no_rkm_medis`) ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-CREATE TABLE `mlite_penilaian_medis_ranap` (
- `no_rawat` varchar(17) NOT NULL,
+CREATE TABLE `mlite_pengaduan` (
+ `id` varchar(15) NOT NULL,
`tanggal` datetime NOT NULL,
- `kd_dokter` varchar(20) NOT NULL,
- `anamnesis` enum('Autoanamnesis','Alloanamnesis') NOT NULL,
- `hubungan` varchar(100) NOT NULL,
- `keluhan_utama` varchar(2000) NOT NULL DEFAULT '',
- `rps` varchar(2000) NOT NULL,
- `rpd` varchar(1000) NOT NULL DEFAULT '',
- `rpk` varchar(1000) NOT NULL,
- `rpo` varchar(1000) NOT NULL,
- `alergi` varchar(100) NOT NULL DEFAULT '',
- `keadaan` enum('Sehat','Sakit Ringan','Sakit Sedang','Sakit Berat') NOT NULL,
- `gcs` varchar(10) NOT NULL,
- `kesadaran` enum('Compos Mentis','Apatis','Somnolen','Sopor','Koma') NOT NULL,
- `td` varchar(8) NOT NULL DEFAULT '',
- `nadi` varchar(5) NOT NULL DEFAULT '',
- `rr` varchar(5) NOT NULL,
- `suhu` varchar(5) NOT NULL DEFAULT '',
- `spo` varchar(5) NOT NULL,
- `bb` varchar(5) NOT NULL DEFAULT '',
- `tb` varchar(5) NOT NULL DEFAULT '',
- `kepala` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `mata` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `gigi` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `tht` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `thoraks` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `jantung` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `paru` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `abdomen` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `genital` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `ekstremitas` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `kulit` enum('Normal','Abnormal','Tidak Diperiksa') NOT NULL,
- `ket_fisik` text NOT NULL,
- `ket_lokalis` text NOT NULL,
- `lab` text NOT NULL,
- `rad` text NOT NULL,
- `penunjang` text NOT NULL,
- `diagnosis` varchar(500) NOT NULL,
- `tata` text NOT NULL,
- `edukasi` varchar(1000) NOT NULL,
- PRIMARY KEY (`no_rawat`) USING BTREE,
- KEY `kd_dokter` (`kd_dokter`) USING BTREE,
- CONSTRAINT `mlite_penilaian_medis_ranap_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_penilaian_medis_ranap_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+ `no_rkm_medis` varchar(15) NOT NULL,
+ `pesan` varchar(255) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `no_rkm_medis` (`no_rkm_medis`),
+ CONSTRAINT `mlite_pengaduan_ibfk_1` FOREIGN KEY (`no_rkm_medis`) REFERENCES `pasien` (`no_rkm_medis`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
-CREATE TABLE `mlite_penilaian_ulang_nyeri` (
- `no_rawat` varchar(17) NOT NULL,
+CREATE TABLE `mlite_pengaduan_detail` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `pengaduan_id` varchar(15) NOT NULL,
`tanggal` datetime NOT NULL,
- `nyeri` enum('Tidak Ada Nyeri','Nyeri Akut','Nyeri Kronis') NOT NULL,
- `provokes` enum('Proses Penyakit','Benturan','Lain-lain','-') NOT NULL,
- `ket_provokes` varchar(40) NOT NULL,
- `quality` enum('Seperti Tertusuk','Berdenyut','Teriris','Tertindih','Tertiban','Lain-lain','-') NOT NULL,
- `ket_quality` varchar(50) NOT NULL,
- `lokasi` varchar(50) NOT NULL,
- `menyebar` enum('Tidak','Ya') NOT NULL,
- `skala_nyeri` enum('0','1','2','3','4','5','6','7','8','9','10') NOT NULL,
- `durasi` varchar(25) NOT NULL,
- `nyeri_hilang` enum('Istirahat','Medengar Musik','Minum Obat','-') NOT NULL,
- `ket_nyeri` varchar(40) NOT NULL,
- `manajemen_nyeri` varchar(1000) DEFAULT NULL,
- `nip` varchar(20) NOT NULL,
- PRIMARY KEY (`no_rawat`,`tanggal`) USING BTREE,
- KEY `nip` (`nip`) USING BTREE,
- CONSTRAINT `mlite_penilaian_ulang_nyeri_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_penilaian_ulang_nyeri_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+ `no_rkm_medis` varchar(15) NOT NULL,
+ `pesan` varchar(225) DEFAULT NULL,
+ PRIMARY KEY (`id`) USING BTREE,
+ KEY `pengaduan_detail_ibfk_1` (`pengaduan_id`),
+ CONSTRAINT `mlite_pengaduan_detail_ibfk_1` FOREIGN KEY (`pengaduan_id`) REFERENCES `mlite_pengaduan` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_penjualan` (
@@ -2461,7 +2703,7 @@ CREATE TABLE `mlite_penjualan` (
`id_user` varchar(50) NOT NULL,
`keterangan` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
CREATE TABLE `mlite_penjualan_barang` (
@@ -2471,7 +2713,7 @@ CREATE TABLE `mlite_penjualan_barang` (
`harga` varchar(100) DEFAULT NULL,
`keterangan` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_penjualan_billing` (
@@ -2485,7 +2727,7 @@ CREATE TABLE `mlite_penjualan_billing` (
`jam` time NOT NULL,
`id_user` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
CREATE TABLE `mlite_penjualan_detail` (
@@ -2500,20 +2742,7 @@ CREATE TABLE `mlite_penjualan_detail` (
`jam` time NOT NULL,
`id_user` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
-
-
-CREATE TABLE `mlite_peta_mukosa_rongga_mulut` (
- `no_rawat` varchar(17) NOT NULL,
- `tanggal` datetime NOT NULL,
- `kelainan` text,
- `gambar` text,
- `nip` varchar(20) NOT NULL,
- PRIMARY KEY (`no_rawat`) USING BTREE,
- KEY `nip` (`nip`) USING BTREE,
- CONSTRAINT `mlite_peta_mukosa_rongga_mulut_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
- CONSTRAINT `mlite_peta_mukosa_rongga_mulut_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
CREATE TABLE `mlite_query_logs` (
@@ -2524,7 +2753,7 @@ CREATE TABLE `mlite_query_logs` (
`error_message` text,
`username` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_rekening` (
@@ -2537,7 +2766,48 @@ CREATE TABLE `mlite_rekening` (
KEY `nm_rek` (`nm_rek`),
KEY `tipe` (`tipe`),
KEY `balance` (`balance`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+INSERT INTO `mlite_rekening` VALUES
+-- Aktiva Lancar (tipe N, balance D)
+('1101','Kas Umum','N','D','1'),
+('1102','Kas Kasir Rawat Jalan','N','D','1'),
+('1103','Kas Kasir Rawat Inap','N','D','1'),
+('1104','Kas Farmasi','N','D','1'),
+('1105','Kas Kecil','N','D','1'),
+('1201','Bank BRI','N','D','1'),
+('1301','Piutang BPJS','N','D','1'),
+('1302','Piutang Pasien Umum','N','D','1'),
+('1401','Persediaan Obat & BHP','N','D','1'),
+-- Aktiva Tetap (tipe N, balance D)
+('1601','Gedung & Bangunan','N','D','1'),
+('1701','Peralatan Medis','N','D','1'),
+('1801','Kendaraan','N','D','1'),
+('1901','Inventaris Kantor','N','D','1'),
+-- Hutang Lancar (tipe N, balance K)
+('2101','Hutang Usaha','N','K','1'),
+('2102','Hutang Gaji','N','K','1'),
+-- Hutang Jangka Panjang (tipe N, balance K)
+('2201','Hutang Bank','N','K','1'),
+-- Modal (tipe M, balance K)
+('3101','Modal Disetor','M','K','1'),
+('3201','Laba Ditahan','M','K','1'),
+-- Pendapatan (tipe R, balance K)
+('4101','Pendapatan Rawat Jalan','R','K','1'),
+('4102','Pendapatan Rawat Inap','R','K','1'),
+('4103','Pendapatan Obat & BHP','R','K','1'),
+('4104','Pendapatan Laboratorium','R','K','1'),
+('4105','Pendapatan Radiologi','R','K','1'),
+('4201','Pendapatan Lain-lain','R','K','1'),
+-- Beban (tipe R, balance D)
+('5101','Beban Gaji Dokter','R','D','1'),
+('5102','Beban Gaji Paramedis','R','D','1'),
+('5103','Beban Gaji Karyawan','R','D','1'),
+('5201','Beban Obat & BHP','R','D','1'),
+('5301','Beban Listrik','R','D','1'),
+('5302','Beban Air & Kebersihan','R','D','1'),
+('5401','Beban Penyusutan','R','D','1'),
+('5501','Beban Administrasi Umum','R','D','1');
CREATE TABLE `mlite_rekeningtahun` (
@@ -2548,7 +2818,40 @@ CREATE TABLE `mlite_rekeningtahun` (
KEY `kd_rek` (`kd_rek`),
KEY `saldo_awal` (`saldo_awal`),
CONSTRAINT `mlite_rekeningtahun_ibfk_1` FOREIGN KEY (`kd_rek`) REFERENCES `mlite_rekening` (`kd_rek`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+-- Saldo awal akun per tahun (N = Neraca, M = Modal)
+-- Verifikasi: Total Aktiva 2025 = 1.420.000.000, Total Pasiva 2025 = 1.420.000.000
+INSERT INTO `mlite_rekeningtahun` VALUES
+(2025,'1101',50000000),
+(2025,'1201',200000000),
+(2025,'1301',75000000),
+(2025,'1302',15000000),
+(2025,'1401',80000000),
+(2025,'1601',500000000),
+(2025,'1701',300000000),
+(2025,'1801',150000000),
+(2025,'1901',50000000),
+(2025,'2101',25000000),
+(2025,'2102',20000000),
+(2025,'2201',200000000),
+(2025,'3101',800000000),
+(2025,'3201',375000000),
+-- Verifikasi: Total Aktiva 2026 = 1.585.000.000, Total Pasiva 2026 = 1.585.000.000
+(2026,'1101',230000000),
+(2026,'1201',200000000),
+(2026,'1301',75000000),
+(2026,'1302',15000000),
+(2026,'1401',80000000),
+(2026,'1601',495000000),
+(2026,'1701',293000000),
+(2026,'1801',147000000),
+(2026,'1901',50000000),
+(2026,'2101',25000000),
+(2026,'2102',20000000),
+(2026,'2201',200000000),
+(2026,'3101',800000000),
+(2026,'3201',540000000);
CREATE TABLE `mlite_remember_me` (
@@ -2558,7 +2861,7 @@ CREATE TABLE `mlite_remember_me` (
`expiry` int NOT NULL,
PRIMARY KEY (`id`),
KEY `mlite_remember_me_ibfk_1` (`user_id`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
CREATE TABLE `mlite_rujukan_internal_poli` (
@@ -2573,7 +2876,7 @@ CREATE TABLE `mlite_rujukan_internal_poli` (
CONSTRAINT `mlite_rujukan_internal_poli_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `mlite_rujukan_internal_poli_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `mlite_rujukan_internal_poli_ibfk_3` FOREIGN KEY (`kd_poli`) REFERENCES `poliklinik` (`kd_poli`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_satu_sehat_departemen` (
@@ -2582,7 +2885,7 @@ CREATE TABLE `mlite_satu_sehat_departemen` (
PRIMARY KEY (`dep_id`),
UNIQUE KEY `id_organisasi_satusehat` (`id_organisasi_satusehat`),
CONSTRAINT `mlite_satu_sehat_departemen_ibfk_1` FOREIGN KEY (`dep_id`) REFERENCES `departemen` (`dep_id`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_satu_sehat_lokasi` (
@@ -2597,7 +2900,7 @@ CREATE TABLE `mlite_satu_sehat_lokasi` (
UNIQUE KEY `id_lokasi_satusehat` (`id_lokasi_satusehat`),
KEY `id_organisasi_satusehat` (`id_organisasi_satusehat`),
CONSTRAINT `mlite_satu_sehat_lokasi_ibfk_2` FOREIGN KEY (`id_organisasi_satusehat`) REFERENCES `mlite_satu_sehat_departemen` (`id_organisasi_satusehat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_satu_sehat_mapping_lab` (
@@ -2611,7 +2914,7 @@ CREATE TABLE `mlite_satu_sehat_mapping_lab` (
`sampel_display` varchar(80) NOT NULL,
PRIMARY KEY (`id_template`) USING BTREE,
CONSTRAINT `mlite_satu_sehat_mapping_lab_ibfk_1` FOREIGN KEY (`id_template`) REFERENCES `template_laboratorium` (`id_template`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_satu_sehat_mapping_obat` (
@@ -2633,7 +2936,7 @@ CREATE TABLE `mlite_satu_sehat_mapping_obat` (
`id_medication` varchar(50) DEFAULT NULL,
PRIMARY KEY (`kode_brng`) USING BTREE,
CONSTRAINT `mlite_satu_sehat_mapping_obat_ibfk_1` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_satu_sehat_mapping_praktisi` (
@@ -2643,7 +2946,7 @@ CREATE TABLE `mlite_satu_sehat_mapping_praktisi` (
PRIMARY KEY (`practitioner_id`),
KEY `kd_dokter` (`kd_dokter`),
CONSTRAINT `mlite_satu_sehat_mapping_praktisi_ibfk_1` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_satu_sehat_mapping_rad` (
@@ -2656,7 +2959,7 @@ CREATE TABLE `mlite_satu_sehat_mapping_rad` (
`sampel_display` varchar(80) NOT NULL,
PRIMARY KEY (`kd_jenis_prw`) USING BTREE,
CONSTRAINT `mlite_satu_sehat_mapping_rad_ibfk_1` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan_radiologi` (`kd_jenis_prw`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_satu_sehat_response` (
@@ -2684,6 +2987,7 @@ CREATE TABLE `mlite_satu_sehat_response` (
`id_rad_specimen` varchar(50) DEFAULT NULL,
`id_rad_observation` varchar(50) DEFAULT NULL,
`id_rad_diagnostic` varchar(50) DEFAULT NULL,
+ `id_imaging_study` varchar(50) DEFAULT NULL,
`id_lab_pk_request` varchar(50) DEFAULT NULL,
`id_lab_pk_specimen` varchar(50) DEFAULT NULL,
`id_lab_pk_observation` varchar(50) DEFAULT NULL,
@@ -2697,18 +3001,63 @@ CREATE TABLE `mlite_satu_sehat_response` (
`id_lab_mb_observation` varchar(50) DEFAULT NULL,
`id_lab_mb_diagnostic` varchar(50) DEFAULT NULL,
`id_careplan` varchar(50) DEFAULT NULL,
+ `id_allergy` varchar(50) DEFAULT NULL,
+ `id_questionnaire` varchar(50) DEFAULT NULL,
PRIMARY KEY (`no_rawat`),
CONSTRAINT `mlite_satu_sehat_response_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_sertisign_webhook` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `transaction_id` varchar(100) NOT NULL,
+ `status` varchar(50) NOT NULL,
+ `document_url` varchar(255) NOT NULL,
+ `payload` text NOT NULL,
+ `received_at` datetime NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `transaction_idx` (`transaction_id`),
+ KEY `status_idx` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
+CREATE TABLE `mlite_mapping_snomed_icd` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `no_rawat` varchar(20) NOT NULL,
+ `kd_penyakit` varchar(10) NOT NULL,
+ `snomed_concept_id` bigint NOT NULL,
+ `snomed_term` varchar(255) NOT NULL,
+ `status_penyakit` enum('Baru','Lama') DEFAULT 'Baru',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uniq_mapping` (`no_rawat`,`kd_penyakit`,`snomed_concept_id`),
+ KEY `no_rawat` (`no_rawat`),
+ KEY `kd_penyakit` (`kd_penyakit`),
+ KEY `snomed_concept_id` (`snomed_concept_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+CREATE TABLE `mlite_mapping_snomed_icd9` (
+ `id` int NOT NULL AUTO_INCREMENT,
+ `no_rawat` varchar(17) NOT NULL,
+ `kd_tindakan` varchar(10) NOT NULL,
+ `snomed_concept_id` varchar(50) NOT NULL,
+ `snomed_term` text DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `uniq_mapping` (`no_rawat`,`kd_tindakan`,`snomed_concept_id`),
+ KEY `no_rawat` (`no_rawat`),
+ KEY `kd_tindakan` (`kd_tindakan`),
+ KEY `snomed_concept_id` (`snomed_concept_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_settings` (
`id` int NOT NULL AUTO_INCREMENT,
`module` varchar(100) NOT NULL,
`field` varchar(100) NOT NULL,
- `value` varchar(1000) NOT NULL,
+ `value` TEXT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `module` (`module`,`field`)
-) ENGINE=MyISAM AUTO_INCREMENT=161 DEFAULT CHARSET=utf8mb3;
+) ENGINE=MyISAM AUTO_INCREMENT=221 DEFAULT CHARSET=utf8mb4;
+
+
INSERT INTO `mlite_settings` VALUES ("1","settings","logo","uploads/settings/logo.png"),
("2","settings","nama_instansi","mLITE Indonesia"),
("3","settings","alamat","Jl. Perintis Kemerdekaan 45"),
@@ -2727,11 +3076,11 @@ INSERT INTO `mlite_settings` VALUES ("1","settings","logo","uploads/settings/log
("16","settings","pj_laboratorium","DR001"),
("17","settings","radiologi","-"),
("18","settings","pj_radiologi","DR001"),
-("19","settings","dokter_ralan_per_dokter",'false'),
-("20","settings","cekstatusbayar",'false'),
-("21","settings","ceklimit",'false'),
-("22","settings","responsivevoice",'false'),
-("23","settings","notif_presensi",'true'),
+("19","settings","dokter_ralan_per_dokter","false"),
+("20","settings","cekstatusbayar","false"),
+("21","settings","ceklimit","false"),
+("22","settings","responsivevoice","false"),
+("23","settings","notif_presensi","true"),
("24","settings","BpjsApiUrl","https://apijkn-dev.bpjs-kesehatan.go.id/vclaim-rest-dev/"),
("25","settings","BpjsConsID","-"),
("26","settings","BpjsSecretKey","-"),
@@ -2742,7 +3091,7 @@ INSERT INTO `mlite_settings` VALUES ("1","settings","logo","uploads/settings/log
("31","settings","admin_mode","complex"),
("32","settings","input_kasir","tidak"),
("33","settings","editor","wysiwyg"),
-("34","settings","version","5.4.0"),
+("34","settings","version","6.3.4"),
("35","settings","update_check",""),
("36","settings","update_changelog",""),
("37","settings","update_version","0"),
@@ -2771,7 +3120,7 @@ INSERT INTO `mlite_settings` VALUES ("1","settings","logo","uploads/settings/log
("60","anjungan","text_laboratorium","Running text display antrian laboratorium....."),
("61","anjungan","text_apotek","Running text display antrian apotek....."),
("62","anjungan","text_farmasi","Running text display antrian farmasi....."),
-("63","anjungan","vidio","G4im8_n0OoI"),
+("63","anjungan","vidio","PLFtgYUkJmfyx7rESNFuRDUmhtkzSeFPQo"),
("64","api","apam_key","qtbexUAxzqO3M8dCOo2vDMFvgYjdUEdMLVo341"),
("65","api","apam_status_daftar","Terdaftar"),
("66","api","apam_status_dilayani","Anda siap dilayani"),
@@ -2815,12 +3164,12 @@ INSERT INTO `mlite_settings` VALUES ("1","settings","logo","uploads/settings/log
("104","jkn_mobile","bahasa_pasien",""),
("105","jkn_mobile","cacat_fisik",""),
("106","keuangan","jurnal_kasir","0"),
-("107","keuangan","akun_kredit_pendaftaran",""),
-("108","keuangan","akun_kredit_tindakan",""),
-("109","keuangan","akun_kredit_obat_bhp",""),
-("110","keuangan","akun_kredit_laboratorium",""),
-("111","keuangan","akun_kredit_radiologi",""),
-("112","keuangan","akun_kredit_tambahan_biaya",""),
+("107","keuangan","akun_kredit_pendaftaran","4101"),
+("108","keuangan","akun_kredit_tindakan","4102"),
+("109","keuangan","akun_kredit_obat_bhp","4103"),
+("110","keuangan","akun_kredit_laboratorium","4104"),
+("111","keuangan","akun_kredit_radiologi","4105"),
+("112","keuangan","akun_kredit_tambahan_biaya","4201"),
("113","manajemen","penjab_umum","UMU"),
("114","manajemen","penjab_bpjs","BPJ"),
("115","presensi","lat","-2.58"),
@@ -2853,12 +3202,10 @@ INSERT INTO `mlite_settings` VALUES ("1","settings","logo","uploads/settings/log
("142","orthanc","server","http://localhost:8042"),
("143","orthanc","username","orthanc"),
("144","orthanc","password","orthanc"),
-("145","veronisa","username",""),
-("146","veronisa","password",""),
-("147","veronisa","obat_kronis",""),
+("166","afm","username_finger",""),
("148","jkn_mobile","kirimantrian","tidak"),
-("149","settings","keamanan","ya"),
-("150","dokter_ralan","set_sudah","tidak"),
+("149","settings","keamanan","tidak"),
+("165","afm","afm_token","fc4eba4aa3ea79a7bba3070cba848696"),
("151","settings","websocket","tidak"),
("152","settings","websocket_proxy",""),
("153","settings","username_fp",""),
@@ -2868,12 +3215,64 @@ INSERT INTO `mlite_settings` VALUES ("1","settings","logo","uploads/settings/log
("157","settings","billing_obat","false"),
("158","settings","prefix_surat","RS"),
("159","farmasi","keterangan_etiket",""),
-("160","pcare","consumerUserKeyAntrol",""),
-("161","settings","set_nomor_surat","000"),
-("162","settings","password_expire","tidak"),
-("163","farmasi","embalase","0"),
-("164","farmasi","tuslah","0"),
-("165","settings","log_query","tidak");
+("160","settings","set_nomor_surat","000"),
+("161","settings","password_expire","tidak"),
+("162","farmasi","embalase","0"),
+("163","farmasi","tuslah","0"),
+("164","settings","log_query","tidak"),
+("167","afm","password_finger",""),
+("168","afm","x_header_token","X-Header-Token"),
+("169","bpjs_emr","consid",""),
+("170","bpjs_emr","secretkey",""),
+("171","bpjs_emr","userkey",""),
+("172","bpjs_emr","koders",""),
+("173","bpjs_emr","kode_kemkes",""),
+("174","bpjs_emr","kodepos",""),
+("175","bpjs_emr","baseurl","https://apijkn-dev.bpjs-kesehatan.go.id/erekammedis_dev/"),
+("176","dokter_ralan","set_sudah","tidak"),
+("177","esignature","kode_berkasdigital",""),
+("178","icare","url","https://apijkn.bpjs-kesehatan.go.id/wsihs/api/rs/validate"),
+("179","icare","consid",""),
+("180","icare","secretkey",""),
+("181","icare","userkey",""),
+("182","icare","urlPCare","https://apijkn.bpjs-kesehatan.go.id/wsihs/api/pcare/validate"),
+("183","icare","usernameICare",""),
+("184","icare","passwordICare",""),
+("185","satu_sehat","organizationid",""),
+("186","satu_sehat","clientid",""),
+("187","satu_sehat","secretkey",""),
+("188","satu_sehat","authurl","https://api-satusehat-dev.dto.kemkes.go.id/oauth2/v1"),
+("189","satu_sehat","fhirurl","https://api-satusehat-dev.dto.kemkes.go.id/fhir-r4/v1"),
+("190","satu_sehat","kelurahan",""),
+("191","satu_sehat","kecamatan",""),
+("192","satu_sehat","kabupaten",""),
+("193","satu_sehat","propinsi",""),
+("194","satu_sehat","kodepos",""),
+("195","satu_sehat","longitude",""),
+("196","satu_sehat","latitude",""),
+("197","satu_sehat","zonawaktu","WIB"),
+("198","satu_sehat","farmasi",""),
+("199","satu_sehat","laboratorium",""),
+("200","satu_sehat","radiologi",""),
+("201","satu_sehat","praktisiapotek",""),
+("202","satu_sehat","praktisilab",""),
+("203","satu_sehat","praktisirad",""),
+("205","sertisign","api_host","https://api-stag.sertisign.id/"),
+("206","sertisign","api_key",""),
+("207","veronisa","username",""),
+("208","veronisa","password",""),
+("209","veronisa","obat_kronis",""),
+("210","veronisa","cons_id",""),
+("211","veronisa","kode_ppk",""),
+("212","veronisa","user_key",""),
+("213","veronisa","secret_key",""),
+("214","veronisa","bpjs_api_url",""),
+("215","settings","login_otp", "tidak"),
+("216","keuangan","akun_debet_kas","1101"),
+("217","bpjs_emr","kecamatan",""),
+("218","satu_sehat","imaging",""),
+("219","farmasi","pajak_obat_persen","0"),
+("220","settings","billing_parsial","false");
CREATE TABLE `mlite_subrekening` (
@@ -2883,9 +3282,17 @@ CREATE TABLE `mlite_subrekening` (
KEY `kd_rek` (`kd_rek`),
CONSTRAINT `mlite_subrekening_ibfk_1` FOREIGN KEY (`kd_rek`) REFERENCES `mlite_rekening` (`kd_rek`) ON UPDATE CASCADE,
CONSTRAINT `mlite_subrekening_ibfk_2` FOREIGN KEY (`kd_rek2`) REFERENCES `mlite_rekening` (`kd_rek`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+CREATE TABLE `mlite_snomed` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `kode` varchar(20) NOT NULL,
+ `istilah` text NOT NULL,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `kode` (`kode`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
CREATE TABLE `mlite_surat_rujukan` (
`id` int NOT NULL AUTO_INCREMENT,
`nomor_surat` varchar(100) DEFAULT NULL,
@@ -2895,7 +3302,7 @@ CREATE TABLE `mlite_surat_rujukan` (
`tgl_lahir` varchar(100) DEFAULT NULL,
`umur` varchar(100) DEFAULT NULL,
`jk` varchar(100) DEFAULT NULL,
- `alamat` varchar(1000) DEFAULT NULL,
+ `alamat` TEXT DEFAULT NULL,
`kepada` varchar(250) DEFAULT NULL,
`di` varchar(250) DEFAULT NULL,
`anamnesa` varchar(100) DEFAULT NULL,
@@ -2907,7 +3314,7 @@ CREATE TABLE `mlite_surat_rujukan` (
`dokter` varchar(100) DEFAULT NULL,
`petugas` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_surat_sakit` (
@@ -2919,7 +3326,7 @@ CREATE TABLE `mlite_surat_sakit` (
`tgl_lahir` varchar(100) DEFAULT NULL,
`umur` varchar(100) DEFAULT NULL,
`jk` varchar(100) DEFAULT NULL,
- `alamat` varchar(1000) DEFAULT NULL,
+ `alamat` TEXT DEFAULT NULL,
`keadaan` varchar(100) DEFAULT NULL,
`diagnosa` varchar(100) DEFAULT NULL,
`lama_angka` varchar(100) DEFAULT NULL,
@@ -2929,7 +3336,7 @@ CREATE TABLE `mlite_surat_sakit` (
`dokter` varchar(100) DEFAULT NULL,
`petugas` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_surat_sehat` (
@@ -2941,7 +3348,7 @@ CREATE TABLE `mlite_surat_sehat` (
`tgl_lahir` varchar(100) DEFAULT NULL,
`umur` varchar(100) DEFAULT NULL,
`jk` varchar(100) DEFAULT NULL,
- `alamat` varchar(1000) DEFAULT NULL,
+ `alamat` TEXT DEFAULT NULL,
`tanggal` varchar(100) DEFAULT NULL,
`berat_badan` varchar(100) DEFAULT NULL,
`tinggi_badan` varchar(100) DEFAULT NULL,
@@ -2952,7 +3359,7 @@ CREATE TABLE `mlite_surat_sehat` (
`dokter` varchar(100) DEFAULT NULL,
`petugas` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_temporary` (
@@ -3056,45 +3463,11 @@ CREATE TABLE `mlite_temporary` (
`temp98` text,
`temp99` text,
`temp100` text
-) ENGINE=MyISAM DEFAULT CHARSET=latin1;
-
-
-CREATE TABLE `mlite_triase` (
- `no_rawat` varchar(17) NOT NULL,
- `tgl_kunjungan` datetime NOT NULL,
- `cara_masuk` enum('Jalan','Brankar','Kursi Roda','Digendong') NOT NULL,
- `alat_transportasi` enum('-','AGD','Sendiri','Swasta') NOT NULL,
- `alasan_kedatangan` enum('Datang Sendiri','Polisi','Rujukan','-') NOT NULL,
- `keterangan_kedatangan` varchar(100) NOT NULL,
- `macam_kasus` enum('Trauma Kecelakaan Lalu Lintas','Trauma Kecelakaan Kerja','Trauma Kasus Unit Pelayanan Anak & Perempuan','Trauma Lainnya','Non Trauma') NOT NULL,
- `tekanan_darah` varchar(8) NOT NULL,
- `nadi` varchar(3) NOT NULL,
- `pernapasan` varchar(3) NOT NULL,
- `suhu` varchar(5) NOT NULL,
- `saturasi_o2` varchar(3) NOT NULL,
- `nyeri` varchar(5) NOT NULL,
- `jenis_triase` enum('Primer','Sekunder') NOT NULL,
- `keluhan_utama` varchar(500) NOT NULL,
- `kebutuhan_khusus` enum('-','UPPA','Airborne','Dekontaminan') NOT NULL,
- `catatan` varchar(100) NOT NULL,
- `plan` enum('Ruang Resusitasi','Ruang Kritis','Zona Kuning','Zona Hijau') NOT NULL,
- `nik` varchar(20) NOT NULL,
- PRIMARY KEY (`no_rawat`) USING BTREE,
- CONSTRAINT `mlite_triase_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
-
-
-CREATE TABLE `mlite_triase_detail` (
- `no_rawat` varchar(17) NOT NULL,
- `skala` varchar(3) NOT NULL,
- `kode_skala` varchar(3) NOT NULL,
- PRIMARY KEY (`no_rawat`,`skala`,`kode_skala`) USING BTREE,
- CONSTRAINT `mlite_triase_detail_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
CREATE TABLE `mlite_triase_igd` (
- `id_triase` bigint NOT NULL AUTO_INCREMENT,
+ `id_triase` int NOT NULL AUTO_INCREMENT,
`no_rawat` varchar(17) NOT NULL,
`no_rkm_medis` varchar(15) NOT NULL,
`tgl_triase` datetime NOT NULL,
@@ -3116,30 +3489,11 @@ CREATE TABLE `mlite_triase_igd` (
`keluhan_utama` text,
`diagnosa_awal` text,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
- `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id_triase`) USING BTREE,
KEY `no_rawat` (`no_rawat`) USING BTREE,
CONSTRAINT `fk_triase_reg_periksa` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-
-
-CREATE TABLE `mlite_triase_pemeriksaan` (
- `kode_pemeriksaan` varchar(3) NOT NULL,
- `nama_pemeriksaan` varchar(150) DEFAULT NULL,
- PRIMARY KEY (`kode_pemeriksaan`) USING BTREE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
-
-
-CREATE TABLE `mlite_triase_skala` (
- `id` int NOT NULL AUTO_INCREMENT,
- `kode_pemeriksaan` varchar(3) NOT NULL,
- `skala` int NOT NULL,
- `kode_skala` varchar(3) NOT NULL,
- `pengkajian_skala` varchar(150) NOT NULL,
- PRIMARY KEY (`id`) USING BTREE,
- KEY `kode_pemeriksaan` (`kode_pemeriksaan`) USING BTREE,
- CONSTRAINT `mlite_triase_skala_ibfk_1` FOREIGN KEY (`kode_pemeriksaan`) REFERENCES `mlite_triase_pemeriksaan` (`kode_pemeriksaan`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_users` (
@@ -3155,9 +3509,11 @@ CREATE TABLE `mlite_users` (
`email` text,
`role` varchar(100) NOT NULL DEFAULT 'user',
`cap` varchar(100) DEFAULT '',
- `access` varchar(500) NOT NULL DEFAULT 'dashboard',
+ `access` TEXT,
PRIMARY KEY (`id`)
-) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3;
+) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
+
+
INSERT INTO `mlite_users` VALUES ("1","admin","Administrator","Admin ganteng baik hati, suka menabung dan tidak sombong.","$2y$10$pgRnDiukCbiYVqsamMM3ROWViSRqbyCCL33N8.ykBKZx0dlplXe9i",NULL,NULL,NULL,"avatar6422cb573b50c.png","info@mlite.id","admin","","all"),
("2","DR001","dr. Ataaka Muhammad","-","$2y$10$kuf2BxvViduBpUTn.6Nxsug3AskH/PGvXTSlfCfJqK8Ayb9a0.vqC",NULL,NULL,NULL,"avatar643a104444515.png","info@mlite.id","admin","","all");
@@ -3168,7 +3524,7 @@ CREATE TABLE `mlite_users_vedika` (
`password` text,
`fullname` text,
PRIMARY KEY (`id`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
CREATE TABLE `mlite_vedika` (
@@ -3185,7 +3541,7 @@ CREATE TABLE `mlite_vedika` (
KEY `idx_vedika_status_jenis_tgl` (`status`,`jenis`,`tgl_registrasi`),
KEY `idx_vedika_nosep` (`nosep`),
KEY `idx_vedika_no_rkm_medis` (`no_rkm_medis`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_vedika_feedback` (
@@ -3195,7 +3551,7 @@ CREATE TABLE `mlite_vedika_feedback` (
`catatan` text,
`username` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_veronisa` (
@@ -3211,7 +3567,7 @@ CREATE TABLE `mlite_veronisa` (
KEY `idx_no_rawat` (`no_rawat`),
KEY `idx_status` (`status`),
KEY `idx_nosep` (`nosep`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mlite_veronisa_feedback` (
@@ -3221,7 +3577,7 @@ CREATE TABLE `mlite_veronisa_feedback` (
`catatan` text,
`username` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mutasi_berkas` (
@@ -3234,7 +3590,7 @@ CREATE TABLE `mutasi_berkas` (
`ranap` datetime NOT NULL,
PRIMARY KEY (`no_rawat`),
CONSTRAINT `mutasi_berkas_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `mutasibarang` (
@@ -3256,7 +3612,7 @@ CREATE TABLE `mutasibarang` (
CONSTRAINT `mutasibarang_ibfk_1` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `mutasibarang_ibfk_2` FOREIGN KEY (`kd_bangsaldari`) REFERENCES `bangsal` (`kd_bangsal`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `mutasibarang_ibfk_3` FOREIGN KEY (`kd_bangsalke`) REFERENCES `bangsal` (`kd_bangsal`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `obat_racikan` (
@@ -3275,7 +3631,7 @@ CREATE TABLE `obat_racikan` (
KEY `no_racik` (`no_racik`),
CONSTRAINT `obat_racikan_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE,
CONSTRAINT `obat_racikan_ibfk_2` FOREIGN KEY (`kd_racik`) REFERENCES `metode_racik` (`kd_racik`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `obatbhp_ok` (
@@ -3288,7 +3644,7 @@ CREATE TABLE `obatbhp_ok` (
KEY `nm_obat` (`nm_obat`),
KEY `hargasatuan` (`hargasatuan`),
CONSTRAINT `obatbhp_ok_ibfk_1` FOREIGN KEY (`kode_sat`) REFERENCES `kodesatuan` (`kode_sat`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `operasi` (
@@ -3376,7 +3732,7 @@ CREATE TABLE `operasi` (
CONSTRAINT `operasi_ibfk_42` FOREIGN KEY (`bidan`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `operasi_ibfk_43` FOREIGN KEY (`perawat_luar`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `operasi_ibfk_44` FOREIGN KEY (`kode_paket`) REFERENCES `paket_operasi` (`kode_paket`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `opname` (
@@ -3403,7 +3759,7 @@ CREATE TABLE `opname` (
KEY `kode_brng` (`kode_brng`) USING BTREE,
CONSTRAINT `opname_ibfk_1` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `opname_ibfk_2` FOREIGN KEY (`kd_bangsal`) REFERENCES `bangsal` (`kd_bangsal`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `paket_operasi` (
@@ -3472,7 +3828,7 @@ CREATE TABLE `paket_operasi` (
KEY `bidan3` (`bidan3`),
KEY `asisten_operator3_2` (`asisten_operator3`),
CONSTRAINT `paket_operasi_ibfk_1` FOREIGN KEY (`kd_pj`) REFERENCES `penjab` (`kd_pj`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `pasien` (
@@ -3534,7 +3890,7 @@ CREATE TABLE `pasien` (
CONSTRAINT `pasien_ibfk_7` FOREIGN KEY (`bahasa_pasien`) REFERENCES `bahasa_pasien` (`id`) ON UPDATE CASCADE,
CONSTRAINT `pasien_ibfk_8` FOREIGN KEY (`cacat_fisik`) REFERENCES `cacat_fisik` (`id`) ON UPDATE CASCADE,
CONSTRAINT `pasien_ibfk_9` FOREIGN KEY (`kd_prop`) REFERENCES `propinsi` (`kd_prop`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `pegawai` (
@@ -3570,7 +3926,7 @@ CREATE TABLE `pegawai` (
`mulai_kontrak` date DEFAULT NULL,
`cuti_diambil` int NOT NULL,
`dankes` double NOT NULL,
- `photo` varchar(500) DEFAULT NULL,
+ `photo` TEXT DEFAULT NULL,
`no_ktp` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `nik_2` (`nik`),
@@ -3615,7 +3971,9 @@ CREATE TABLE `pegawai` (
CONSTRAINT `pegawai_ibfk_7` FOREIGN KEY (`indexins`) REFERENCES `departemen` (`dep_id`) ON UPDATE CASCADE,
CONSTRAINT `pegawai_ibfk_8` FOREIGN KEY (`bpd`) REFERENCES `bank` (`namabank`) ON UPDATE CASCADE,
CONSTRAINT `pegawai_ibfk_9` FOREIGN KEY (`kode_emergency`) REFERENCES `emergency_index` (`kode_emergency`) ON UPDATE CASCADE
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `pegawai` VALUES ("1","DR001","dr. Ataaka Muhammad","Pria","-","-","-","-","-","-","-","-","-","-","-","0","Barabai","2016-06-10","-","Barabai","2019-09-18","<1","-","-","-","AKTIF","0","0","0","2019-09-18","1","0","-","0");
@@ -3631,7 +3989,7 @@ CREATE TABLE `pemeliharaan_inventaris` (
KEY `nip` (`nip`),
CONSTRAINT `pemeliharaan_inventaris_ibfk_1` FOREIGN KEY (`no_inventaris`) REFERENCES `inventaris` (`no_inventaris`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `pemeliharaan_inventaris_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `pemeriksaan_ralan` (
@@ -3647,21 +4005,21 @@ CREATE TABLE `pemeriksaan_ralan` (
`spo2` varchar(3) NOT NULL,
`gcs` varchar(10) DEFAULT NULL,
`kesadaran` enum('Compos Mentis','Somnolence','Sopor','Coma') NOT NULL,
- `keluhan` varchar(2000) DEFAULT NULL,
- `pemeriksaan` varchar(2000) DEFAULT NULL,
+ `keluhan` TEXT DEFAULT NULL,
+ `pemeriksaan` TEXT DEFAULT NULL,
`alergi` varchar(50) DEFAULT NULL,
`lingkar_perut` varchar(5) DEFAULT NULL,
- `rtl` varchar(2000) NOT NULL,
- `penilaian` varchar(2000) NOT NULL,
- `instruksi` varchar(2000) NOT NULL,
- `evaluasi` varchar(2000) NOT NULL,
+ `rtl` TEXT NOT NULL,
+ `penilaian` TEXT NOT NULL,
+ `instruksi` TEXT NOT NULL,
+ `evaluasi` TEXT NOT NULL,
`nip` varchar(20) NOT NULL,
PRIMARY KEY (`no_rawat`,`tgl_perawatan`,`jam_rawat`) USING BTREE,
KEY `no_rawat` (`no_rawat`),
KEY `nip` (`nip`) USING BTREE,
CONSTRAINT `pemeriksaan_ralan_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `pemeriksaan_ralan_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `pegawai` (`nik`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `pemeriksaan_ranap` (
@@ -3677,20 +4035,20 @@ CREATE TABLE `pemeriksaan_ranap` (
`spo2` varchar(3) NOT NULL,
`gcs` varchar(10) DEFAULT NULL,
`kesadaran` enum('Compos Mentis','Somnolence','Sopor','Coma') NOT NULL,
- `keluhan` varchar(2000) DEFAULT NULL,
- `pemeriksaan` varchar(2000) DEFAULT NULL,
+ `keluhan` TEXT DEFAULT NULL,
+ `pemeriksaan` TEXT DEFAULT NULL,
`alergi` varchar(50) DEFAULT NULL,
- `penilaian` varchar(2000) NOT NULL,
- `rtl` varchar(2000) NOT NULL,
- `instruksi` varchar(2000) NOT NULL,
- `evaluasi` varchar(2000) NOT NULL,
+ `penilaian` TEXT NOT NULL,
+ `rtl` TEXT NOT NULL,
+ `instruksi` TEXT NOT NULL,
+ `evaluasi` TEXT NOT NULL,
`nip` varchar(20) NOT NULL,
PRIMARY KEY (`no_rawat`,`tgl_perawatan`,`jam_rawat`),
KEY `no_rawat` (`no_rawat`),
KEY `nip` (`nip`),
CONSTRAINT `pemeriksaan_ranap_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `pemeriksaan_ranap_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `pegawai` (`nik`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `pendidikan` (
@@ -3700,7 +4058,9 @@ CREATE TABLE `pendidikan` (
`kenaikan` double NOT NULL,
`maksimal` int NOT NULL,
PRIMARY KEY (`tingkat`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `pendidikan` VALUES ("-","1","0","0","1");
@@ -3778,7 +4138,7 @@ CREATE TABLE `penilaian_awal_keperawatan_igd` (
KEY `nip` (`nip`) USING BTREE,
CONSTRAINT `penilaian_awal_keperawatan_igd_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `penilaian_awal_keperawatan_igd_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `penilaian_awal_keperawatan_ralan` (
@@ -3843,7 +4203,7 @@ CREATE TABLE `penilaian_awal_keperawatan_ralan` (
KEY `nip` (`nip`) USING BTREE,
CONSTRAINT `penilaian_awal_keperawatan_ralan_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `penilaian_awal_keperawatan_ralan_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `penilaian_awal_keperawatan_ranap` (
@@ -3854,24 +4214,24 @@ CREATE TABLE `penilaian_awal_keperawatan_ranap` (
`tiba_diruang_rawat` enum('Jalan Tanpa Bantuan','Kursi Roda','Brankar') NOT NULL,
`kasus_trauma` enum('Trauma','Non Trauma') DEFAULT NULL,
`cara_masuk` enum('Poli','IGD','Lain-lain') NOT NULL,
- `rps` varchar(300) NOT NULL,
- `rpd` varchar(100) NOT NULL,
- `rpk` varchar(100) NOT NULL,
- `rpo` varchar(100) NOT NULL,
- `riwayat_pembedahan` varchar(40) NOT NULL,
- `riwayat_dirawat_dirs` varchar(40) NOT NULL,
+ `rps` TEXT NOT NULL,
+ `rpd` TEXT NOT NULL,
+ `rpk` TEXT NOT NULL,
+ `rpo` TEXT NOT NULL,
+ `riwayat_pembedahan` TEXT NOT NULL,
+ `riwayat_dirawat_dirs` TEXT NOT NULL,
`alat_bantu_dipakai` enum('Kacamata','Prothesa','Alat Bantu Dengar','Lain-lain') NOT NULL,
`riwayat_kehamilan` enum('Tidak','Ya') NOT NULL,
- `riwayat_kehamilan_perkiraan` varchar(30) NOT NULL,
- `riwayat_tranfusi` varchar(40) NOT NULL,
- `riwayat_alergi` varchar(40) NOT NULL,
+ `riwayat_kehamilan_perkiraan` TEXT NOT NULL,
+ `riwayat_tranfusi` TEXT NOT NULL,
+ `riwayat_alergi` TEXT NOT NULL,
`riwayat_merokok` enum('Tidak','Ya') NOT NULL,
`riwayat_merokok_jumlah` varchar(5) NOT NULL,
`riwayat_alkohol` enum('Tidak','Ya') NOT NULL,
`riwayat_alkohol_jumlah` varchar(5) NOT NULL,
`riwayat_narkoba` enum('Tidak','Ya') NOT NULL,
`riwayat_olahraga` enum('Tidak','Ya') NOT NULL,
- `pemeriksaan_mental` varchar(40) NOT NULL,
+ `pemeriksaan_mental` TEXT NOT NULL,
`pemeriksaan_keadaan_umum` enum('Baik','Sedang','Buruk') NOT NULL,
`pemeriksaan_gcs` varchar(10) NOT NULL,
`pemeriksaan_td` varchar(8) NOT NULL,
@@ -3882,43 +4242,43 @@ CREATE TABLE `penilaian_awal_keperawatan_ranap` (
`pemeriksaan_bb` varchar(5) NOT NULL,
`pemeriksaan_tb` varchar(5) NOT NULL,
`pemeriksaan_susunan_kepala` enum('TAK','Hydrocephalus','Hematoma','Lain-lain') NOT NULL,
- `pemeriksaan_susunan_kepala_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_susunan_kepala_keterangan` TEXT NOT NULL,
`pemeriksaan_susunan_wajah` enum('TAK','Asimetris','Kelainan Kongenital') NOT NULL,
- `pemeriksaan_susunan_wajah_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_susunan_wajah_keterangan` TEXT NOT NULL,
`pemeriksaan_susunan_leher` enum('TAK','Kaku Kuduk','Pembesaran Thyroid','Pembesaran KGB') NOT NULL,
`pemeriksaan_susunan_kejang` enum('TAK','Kuat','Ada') NOT NULL,
- `pemeriksaan_susunan_kejang_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_susunan_kejang_keterangan` TEXT NOT NULL,
`pemeriksaan_susunan_sensorik` enum('TAK','Sakit Nyeri','Rasa kebas') NOT NULL,
`pemeriksaan_kardiovaskuler_denyut_nadi` enum('Teratur','Tidak Teratur') NOT NULL,
`pemeriksaan_kardiovaskuler_sirkulasi` enum('Akral Hangat','Akral Dingin','Edema') NOT NULL,
- `pemeriksaan_kardiovaskuler_sirkulasi_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_kardiovaskuler_sirkulasi_keterangan` TEXT NOT NULL,
`pemeriksaan_kardiovaskuler_pulsasi` enum('Kuat','Lemah','Lain-lain') NOT NULL,
`pemeriksaan_respirasi_pola_nafas` enum('Normal','Bradipnea','Tachipnea') NOT NULL,
`pemeriksaan_respirasi_retraksi` enum('Tidak Ada','Ringan','Berat') NOT NULL,
`pemeriksaan_respirasi_suara_nafas` enum('Vesikuler','Wheezing','Rhonki') NOT NULL,
`pemeriksaan_respirasi_volume_pernafasan` enum('Normal','Hiperventilasi','Hipoventilasi') NOT NULL,
`pemeriksaan_respirasi_jenis_pernafasan` enum('Pernafasan Dada','Alat Bantu Pernafasaan') NOT NULL,
- `pemeriksaan_respirasi_jenis_pernafasan_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_respirasi_jenis_pernafasan_keterangan` TEXT NOT NULL,
`pemeriksaan_respirasi_irama_nafas` enum('Teratur','Tidak Teratur') NOT NULL,
`pemeriksaan_respirasi_batuk` enum('Tidak','Ya : Produktif','Ya : Non Produktif') NOT NULL,
`pemeriksaan_gastrointestinal_mulut` enum('TAK','Stomatitis','Mukosa Kering','Bibir Pucat','Lain-lain') NOT NULL,
- `pemeriksaan_gastrointestinal_mulut_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_gastrointestinal_mulut_keterangan` TEXT NOT NULL,
`pemeriksaan_gastrointestinal_gigi` enum('TAK','Karies','Goyang','Lain-lain') NOT NULL,
- `pemeriksaan_gastrointestinal_gigi_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_gastrointestinal_gigi_keterangan` TEXT NOT NULL,
`pemeriksaan_gastrointestinal_lidah` enum('TAK','Kotor','Gerak Asimetris','Lain-lain') NOT NULL,
- `pemeriksaan_gastrointestinal_lidah_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_gastrointestinal_lidah_keterangan` TEXT NOT NULL,
`pemeriksaan_gastrointestinal_tenggorokan` enum('TAK','Gangguan Menelan','Sakit Menelan','Lain-lain') NOT NULL,
- `pemeriksaan_gastrointestinal_tenggorokan_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_gastrointestinal_tenggorokan_keterangan` TEXT NOT NULL,
`pemeriksaan_gastrointestinal_abdomen` enum('Supel','Asictes',' Tegang','Nyeri Tekan/Lepas','Lain-lain') NOT NULL,
- `pemeriksaan_gastrointestinal_abdomen_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_gastrointestinal_abdomen_keterangan` TEXT NOT NULL,
`pemeriksaan_gastrointestinal_peistatik_usus` enum('TAK','Tidak Ada Bising Usus','Hiperistaltik') NOT NULL,
`pemeriksaan_gastrointestinal_anus` enum('TAK','Atresia Ani') NOT NULL,
`pemeriksaan_neurologi_pengelihatan` enum('TAK','Ada Kelainan') NOT NULL,
- `pemeriksaan_neurologi_pengelihatan_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_neurologi_pengelihatan_keterangan` TEXT NOT NULL,
`pemeriksaan_neurologi_alat_bantu_penglihatan` enum('Tidak','Kacamata','Lensa Kontak') NOT NULL,
`pemeriksaan_neurologi_pendengaran` enum('TAK','Berdengung','Nyeri','Tuli','Keluar Cairan','Lain-lain') NOT NULL,
`pemeriksaan_neurologi_bicara` enum('Jelas','Tidak Jelas') NOT NULL,
- `pemeriksaan_neurologi_bicara_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_neurologi_bicara_keterangan` TEXT NOT NULL,
`pemeriksaan_neurologi_sensorik` enum('TAK','Sakit Nyeri','Rasa Kebas','Lain-lain') NOT NULL,
`pemeriksaan_neurologi_motorik` enum('TAK','Hemiparese','Tetraparese','Tremor','Lain-lain') NOT NULL,
`pemeriksaan_neurologi_kekuatan_otot` enum('Kuat','Lemah') NOT NULL,
@@ -3929,11 +4289,11 @@ CREATE TABLE `penilaian_awal_keperawatan_ranap` (
`pemeriksaan_muskuloskletal_pergerakan_sendi` enum('Bebas','Terbatas') NOT NULL,
`pemeriksaan_muskuloskletal_kekauatan_otot` enum('Baik','Lemah','Tremor') NOT NULL,
`pemeriksaan_muskuloskletal_nyeri_sendi` enum('Tidak Ada','Ada') NOT NULL,
- `pemeriksaan_muskuloskletal_nyeri_sendi_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_muskuloskletal_nyeri_sendi_keterangan` TEXT NOT NULL,
`pemeriksaan_muskuloskletal_oedema` enum('Tidak Ada','Ada') NOT NULL,
- `pemeriksaan_muskuloskletal_oedema_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_muskuloskletal_oedema_keterangan` TEXT NOT NULL,
`pemeriksaan_muskuloskletal_fraktur` enum('Tidak Ada','Ada') NOT NULL,
- `pemeriksaan_muskuloskletal_fraktur_keterangan` varchar(50) NOT NULL,
+ `pemeriksaan_muskuloskletal_fraktur_keterangan` TEXT NOT NULL,
`pemeriksaan_eliminasi_bab_frekuensi_jumlah` varchar(5) NOT NULL,
`pemeriksaan_eliminasi_bab_frekuensi_durasi` varchar(10) NOT NULL,
`pemeriksaan_eliminasi_bab_konsistensi` varchar(30) NOT NULL,
@@ -3955,40 +4315,40 @@ CREATE TABLE `penilaian_awal_keperawatan_ranap` (
`pengkajian_fungsi_kemampuan_sehari` enum('Mandiri','Bantuan Minimal','Bantuan Sebagian','Ketergantungan Total') NOT NULL,
`pengkajian_fungsi_aktifitas` enum('Tirah Baring','Duduk','Berjalan') NOT NULL,
`pengkajian_fungsi_berjalan` enum('TAK','Penurunan Kekuatan/ROM','Paralisis','Sering Jatuh','Deformitas','Hilang Keseimbangan','Riwayat Patah Tulang','Lain-lain') NOT NULL,
- `pengkajian_fungsi_berjalan_keterangan` varchar(40) NOT NULL,
+ `pengkajian_fungsi_berjalan_keterangan` TEXT NOT NULL,
`pengkajian_fungsi_ambulasi` enum('Walker','Tongkat','Kursi Roda','Tidak Menggunakan') NOT NULL,
`pengkajian_fungsi_ekstrimitas_atas` enum('TAK','Lemah','Oedema','Tidak Simetris','Lain-lain') NOT NULL,
- `pengkajian_fungsi_ekstrimitas_atas_keterangan` varchar(40) NOT NULL,
+ `pengkajian_fungsi_ekstrimitas_atas_keterangan` TEXT NOT NULL,
`pengkajian_fungsi_ekstrimitas_bawah` enum('TAK','Varises','Oedema','Tidak Simetris','Lain-lain') NOT NULL,
- `pengkajian_fungsi_ekstrimitas_bawah_keterangan` varchar(40) NOT NULL,
+ `pengkajian_fungsi_ekstrimitas_bawah_keterangan` TEXT NOT NULL,
`pengkajian_fungsi_menggenggam` enum('Tidak Ada Kesulitan','Terakhir','Lain-lain') NOT NULL,
- `pengkajian_fungsi_menggenggam_keterangan` varchar(40) NOT NULL,
+ `pengkajian_fungsi_menggenggam_keterangan` TEXT NOT NULL,
`pengkajian_fungsi_koordinasi` enum('Tidak Ada Kesulitan','Ada Masalah') NOT NULL,
- `pengkajian_fungsi_koordinasi_keterangan` varchar(40) NOT NULL,
+ `pengkajian_fungsi_koordinasi_keterangan` TEXT NOT NULL,
`pengkajian_fungsi_kesimpulan` enum('Ya (Co DPJP)','Tidak (Tidak Perlu Co DPJP)') NOT NULL,
`riwayat_psiko_kondisi_psiko` enum('Tidak Ada Masalah','Marah','Takut','Depresi','Cepat Lelah','Cemas','Gelisah','Sulit Tidur','Lain-lain') NOT NULL,
`riwayat_psiko_gangguan_jiwa` enum('Ya','Tidak') NOT NULL,
`riwayat_psiko_perilaku` enum('Tidak Ada Masalah','Perilaku Kekerasan','Gangguan Efek','Gangguan Memori','Halusinasi','Kecenderungan Percobaan Bunuh Diri','Lain-lain') NOT NULL,
- `riwayat_psiko_perilaku_keterangan` varchar(40) NOT NULL,
+ `riwayat_psiko_perilaku_keterangan` TEXT NOT NULL,
`riwayat_psiko_hubungan_keluarga` enum('Harmonis','Kurang Harmonis','Tidak Harmonis','Konflik Besar') NOT NULL,
`riwayat_psiko_tinggal` enum('Sendiri','Orang Tua','Suami/Istri','Keluarga','Lain-lain') NOT NULL,
- `riwayat_psiko_tinggal_keterangan` varchar(40) NOT NULL,
+ `riwayat_psiko_tinggal_keterangan` TEXT NOT NULL,
`riwayat_psiko_nilai_kepercayaan` enum('Tidak Ada','Ada') NOT NULL,
- `riwayat_psiko_nilai_kepercayaan_keterangan` varchar(40) NOT NULL,
+ `riwayat_psiko_nilai_kepercayaan_keterangan` TEXT NOT NULL,
`riwayat_psiko_pendidikan_pj` enum('-','TS','TK','SD','SMP','SMA','SLTA/SEDERAJAT','D1','D2','D3','D4','S1','S2','S3') NOT NULL,
`riwayat_psiko_edukasi_diberikan` enum('Pasien','Keluarga') NOT NULL,
- `riwayat_psiko_edukasi_diberikan_keterangan` varchar(40) NOT NULL,
+ `riwayat_psiko_edukasi_diberikan_keterangan` TEXT NOT NULL,
`penilaian_nyeri` enum('Tidak Ada Nyeri','Nyeri Akut','Nyeri Kronis') NOT NULL,
`penilaian_nyeri_penyebab` enum('Proses Penyakit','Benturan','Lain-lain') NOT NULL,
- `penilaian_nyeri_ket_penyebab` varchar(50) NOT NULL,
+ `penilaian_nyeri_ket_penyebab` TEXT NOT NULL,
`penilaian_nyeri_kualitas` enum('Seperti Tertusuk','Berdenyut','Teriris','Tertindih','Tertiban','Lain-lain') NOT NULL,
- `penilaian_nyeri_ket_kualitas` varchar(50) NOT NULL,
- `penilaian_nyeri_lokasi` varchar(50) NOT NULL,
+ `penilaian_nyeri_ket_kualitas` TEXT NOT NULL,
+ `penilaian_nyeri_lokasi` TEXT NOT NULL,
`penilaian_nyeri_menyebar` enum('Tidak','Ya') NOT NULL,
`penilaian_nyeri_skala` enum('0','1','2','3','4','5','6','7','8','9','10') NOT NULL,
`penilaian_nyeri_waktu` varchar(5) NOT NULL,
`penilaian_nyeri_hilang` enum('Istirahat','Medengar Musik','Minum Obat') NOT NULL,
- `penilaian_nyeri_ket_hilang` varchar(50) NOT NULL,
+ `penilaian_nyeri_ket_hilang` TEXT NOT NULL,
`penilaian_nyeri_diberitahukan_dokter` enum('Tidak','Ya') NOT NULL,
`penilaian_nyeri_jam_diberitahukan_dokter` varchar(10) NOT NULL,
`penilaian_jatuhmorse_skala1` enum('Tidak','Ya') DEFAULT NULL,
@@ -4033,10 +4393,10 @@ CREATE TABLE `penilaian_awal_keperawatan_ranap` (
`nilai_gizi2` int DEFAULT NULL,
`nilai_total_gizi` double DEFAULT NULL,
`skrining_gizi_diagnosa_khusus` enum('Tidak','Ya') DEFAULT NULL,
- `skrining_gizi_ket_diagnosa_khusus` varchar(50) DEFAULT NULL,
+ `skrining_gizi_ket_diagnosa_khusus` TEXT,
`skrining_gizi_diketahui_dietisen` enum('Tidak','Ya') DEFAULT NULL,
`skrining_gizi_jam_diketahui_dietisen` varchar(10) DEFAULT NULL,
- `rencana` varchar(200) DEFAULT NULL,
+ `rencana` TEXT,
`nip1` varchar(20) NOT NULL,
`nip2` varchar(20) NOT NULL,
`kd_dokter` varchar(20) NOT NULL,
@@ -4048,7 +4408,7 @@ CREATE TABLE `penilaian_awal_keperawatan_ranap` (
CONSTRAINT `penilaian_awal_keperawatan_ranap_ibfk_2` FOREIGN KEY (`nip1`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `penilaian_awal_keperawatan_ranap_ibfk_3` FOREIGN KEY (`nip2`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `penilaian_awal_keperawatan_ranap_ibfk_4` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `penilaian_medis_igd` (
@@ -4057,11 +4417,11 @@ CREATE TABLE `penilaian_medis_igd` (
`kd_dokter` varchar(20) NOT NULL,
`anamnesis` enum('Autoanamnesis','Alloanamnesis') NOT NULL,
`hubungan` varchar(100) NOT NULL,
- `keluhan_utama` varchar(2000) NOT NULL DEFAULT '',
- `rps` varchar(2000) NOT NULL,
- `rpd` varchar(1000) NOT NULL DEFAULT '',
- `rpk` varchar(1000) NOT NULL,
- `rpo` varchar(1000) NOT NULL,
+ `keluhan_utama` TEXT NOT NULL DEFAULT '',
+ `rps` TEXT NOT NULL,
+ `rpd` TEXT NOT NULL DEFAULT '',
+ `rpk` TEXT NOT NULL,
+ `rpo` TEXT NOT NULL,
`alergi` varchar(100) NOT NULL DEFAULT '',
`keadaan` enum('Sehat','Sakit Ringan','Sakit Sedang','Sakit Berat') NOT NULL,
`gcs` varchar(10) NOT NULL,
@@ -4086,13 +4446,13 @@ CREATE TABLE `penilaian_medis_igd` (
`ekg` text NOT NULL,
`rad` text NOT NULL,
`lab` text NOT NULL,
- `diagnosis` varchar(500) NOT NULL,
+ `diagnosis` TEXT NOT NULL,
`tata` text NOT NULL,
PRIMARY KEY (`no_rawat`) USING BTREE,
KEY `kd_dokter` (`kd_dokter`) USING BTREE,
CONSTRAINT `penilaian_medis_igd_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `penilaian_medis_igd_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `penilaian_medis_ralan` (
@@ -4101,11 +4461,11 @@ CREATE TABLE `penilaian_medis_ralan` (
`kd_dokter` varchar(20) NOT NULL,
`anamnesis` enum('Autoanamnesis','Alloanamnesis') NOT NULL,
`hubungan` varchar(30) NOT NULL,
- `keluhan_utama` varchar(2000) NOT NULL DEFAULT '',
- `rps` varchar(2000) NOT NULL,
- `rpd` varchar(1000) NOT NULL DEFAULT '',
- `rpk` varchar(1000) NOT NULL,
- `rpo` varchar(1000) NOT NULL,
+ `keluhan_utama` TEXT NOT NULL DEFAULT '',
+ `rps` TEXT NOT NULL,
+ `rpd` TEXT NOT NULL DEFAULT '',
+ `rpk` TEXT NOT NULL,
+ `rpo` TEXT NOT NULL,
`alergi` varchar(50) NOT NULL DEFAULT '',
`keadaan` enum('Sehat','Sakit Ringan','Sakit Sedang','Sakit Berat') NOT NULL,
`gcs` varchar(10) NOT NULL,
@@ -4128,14 +4488,14 @@ CREATE TABLE `penilaian_medis_ralan` (
`ket_fisik` text NOT NULL,
`ket_lokalis` text NOT NULL,
`penunjang` text NOT NULL,
- `diagnosis` varchar(500) NOT NULL,
+ `diagnosis` TEXT NOT NULL,
`tata` text NOT NULL,
- `konsulrujuk` varchar(1000) NOT NULL,
+ `konsulrujuk` TEXT NOT NULL,
PRIMARY KEY (`no_rawat`) USING BTREE,
KEY `kd_dokter` (`kd_dokter`) USING BTREE,
CONSTRAINT `penilaian_medis_ralan_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `penilaian_medis_ralan_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `penilaian_medis_ranap` (
@@ -4144,11 +4504,11 @@ CREATE TABLE `penilaian_medis_ranap` (
`kd_dokter` varchar(20) NOT NULL,
`anamnesis` enum('Autoanamnesis','Alloanamnesis') NOT NULL,
`hubungan` varchar(100) NOT NULL,
- `keluhan_utama` varchar(2000) NOT NULL DEFAULT '',
- `rps` varchar(2000) NOT NULL,
- `rpd` varchar(1000) NOT NULL DEFAULT '',
- `rpk` varchar(1000) NOT NULL,
- `rpo` varchar(1000) NOT NULL,
+ `keluhan_utama` TEXT NOT NULL DEFAULT '',
+ `rps` TEXT NOT NULL,
+ `rpd` TEXT NOT NULL DEFAULT '',
+ `rpk` TEXT NOT NULL,
+ `rpo` TEXT NOT NULL,
`alergi` varchar(100) NOT NULL DEFAULT '',
`keadaan` enum('Sehat','Sakit Ringan','Sakit Sedang','Sakit Berat') NOT NULL,
`gcs` varchar(10) NOT NULL,
@@ -4176,14 +4536,14 @@ CREATE TABLE `penilaian_medis_ranap` (
`lab` text NOT NULL,
`rad` text NOT NULL,
`penunjang` text NOT NULL,
- `diagnosis` varchar(500) NOT NULL,
+ `diagnosis` TEXT NOT NULL,
`tata` text NOT NULL,
- `edukasi` varchar(1000) NOT NULL,
+ `edukasi` TEXT NOT NULL,
PRIMARY KEY (`no_rawat`) USING BTREE,
KEY `kd_dokter` (`kd_dokter`) USING BTREE,
CONSTRAINT `penilaian_medis_ranap_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `penilaian_medis_ranap_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `penilaian_ulang_nyeri` (
@@ -4205,7 +4565,7 @@ CREATE TABLE `penilaian_ulang_nyeri` (
KEY `nip` (`nip`) USING BTREE,
CONSTRAINT `penilaian_ulang_nyeri_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `penilaian_ulang_nyeri_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `penjab` (
@@ -4217,7 +4577,9 @@ CREATE TABLE `penjab` (
`attn` varchar(60) NOT NULL,
`status` enum('0','1') NOT NULL,
PRIMARY KEY (`kd_pj`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `penjab` VALUES ("-","-","-","-","0","0","1"),
("BPJ","BPJS Kesehatan","-","-","0","0","1"),
("UMU","Umum","-","-","0","0","1");
@@ -4235,7 +4597,7 @@ CREATE TABLE `penyakit` (
KEY `nm_penyakit` (`nm_penyakit`),
KEY `status` (`status`),
CONSTRAINT `penyakit_ibfk_1` FOREIGN KEY (`kd_ktg`) REFERENCES `kategori_penyakit` (`kd_ktg`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `perbaikan_inventaris` (
@@ -4251,7 +4613,7 @@ CREATE TABLE `perbaikan_inventaris` (
KEY `nip` (`nip`),
CONSTRAINT `perbaikan_inventaris_ibfk_1` FOREIGN KEY (`no_permintaan`) REFERENCES `permintaan_perbaikan_inventaris` (`no_permintaan`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `perbaikan_inventaris_ibfk_2` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `periksa_lab` (
@@ -4282,7 +4644,7 @@ CREATE TABLE `periksa_lab` (
CONSTRAINT `periksa_lab_ibfk_12` FOREIGN KEY (`dokter_perujuk`) REFERENCES `dokter` (`kd_dokter`) ON UPDATE CASCADE,
CONSTRAINT `periksa_lab_ibfk_13` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON UPDATE CASCADE,
CONSTRAINT `periksa_lab_ibfk_9` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `periksa_radiologi` (
@@ -4320,7 +4682,7 @@ CREATE TABLE `periksa_radiologi` (
CONSTRAINT `periksa_radiologi_ibfk_6` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan_radiologi` (`kd_jenis_prw`) ON UPDATE CASCADE,
CONSTRAINT `periksa_radiologi_ibfk_7` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON UPDATE CASCADE,
CONSTRAINT `periksa_radiologi_ibfk_8` FOREIGN KEY (`dokter_perujuk`) REFERENCES `dokter` (`kd_dokter`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `permintaan_detail_permintaan_lab` (
@@ -4334,7 +4696,7 @@ CREATE TABLE `permintaan_detail_permintaan_lab` (
CONSTRAINT `permintaan_detail_permintaan_lab_ibfk_2` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan_lab` (`kd_jenis_prw`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `permintaan_detail_permintaan_lab_ibfk_3` FOREIGN KEY (`id_template`) REFERENCES `template_laboratorium` (`id_template`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `permintaan_detail_permintaan_lab_ibfk_4` FOREIGN KEY (`noorder`) REFERENCES `permintaan_lab` (`noorder`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `permintaan_lab` (
@@ -4355,7 +4717,7 @@ CREATE TABLE `permintaan_lab` (
KEY `no_rawat` (`no_rawat`),
CONSTRAINT `permintaan_lab_ibfk_2` FOREIGN KEY (`dokter_perujuk`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `permintaan_lab_ibfk_3` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `permintaan_pemeriksaan_lab` (
@@ -4366,7 +4728,7 @@ CREATE TABLE `permintaan_pemeriksaan_lab` (
KEY `kd_jenis_prw` (`kd_jenis_prw`),
CONSTRAINT `permintaan_pemeriksaan_lab_ibfk_1` FOREIGN KEY (`noorder`) REFERENCES `permintaan_lab` (`noorder`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `permintaan_pemeriksaan_lab_ibfk_2` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan_lab` (`kd_jenis_prw`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `permintaan_pemeriksaan_radiologi` (
@@ -4377,7 +4739,7 @@ CREATE TABLE `permintaan_pemeriksaan_radiologi` (
KEY `kd_jenis_prw` (`kd_jenis_prw`),
CONSTRAINT `permintaan_pemeriksaan_radiologi_ibfk_1` FOREIGN KEY (`noorder`) REFERENCES `permintaan_radiologi` (`noorder`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `permintaan_pemeriksaan_radiologi_ibfk_2` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan_radiologi` (`kd_jenis_prw`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `permintaan_perbaikan_inventaris` (
@@ -4391,7 +4753,7 @@ CREATE TABLE `permintaan_perbaikan_inventaris` (
KEY `nik` (`nik`),
CONSTRAINT `permintaan_perbaikan_inventaris_ibfk_1` FOREIGN KEY (`no_inventaris`) REFERENCES `inventaris` (`no_inventaris`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `permintaan_perbaikan_inventaris_ibfk_2` FOREIGN KEY (`nik`) REFERENCES `pegawai` (`nik`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `permintaan_radiologi` (
@@ -4412,16 +4774,16 @@ CREATE TABLE `permintaan_radiologi` (
KEY `no_rawat` (`no_rawat`),
CONSTRAINT `permintaan_radiologi_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `permintaan_radiologi_ibfk_3` FOREIGN KEY (`dokter_perujuk`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `personal_pasien` (
`no_rkm_medis` varchar(15) NOT NULL,
- `gambar` varchar(1000) DEFAULT NULL,
- `password` varchar(1000) DEFAULT NULL,
+ `gambar` TEXT DEFAULT NULL,
+ `password` TEXT DEFAULT NULL,
PRIMARY KEY (`no_rkm_medis`),
CONSTRAINT `personal_pasien_ibfk_1` FOREIGN KEY (`no_rkm_medis`) REFERENCES `pasien` (`no_rkm_medis`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `perusahaan_pasien` (
@@ -4431,7 +4793,9 @@ CREATE TABLE `perusahaan_pasien` (
`kota` varchar(40) DEFAULT NULL,
`no_telp` varchar(27) DEFAULT NULL,
PRIMARY KEY (`kode_perusahaan`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `perusahaan_pasien` VALUES ("-","-","-","-","0");
@@ -4459,7 +4823,9 @@ CREATE TABLE `petugas` (
KEY `alamat` (`alamat`),
CONSTRAINT `petugas_ibfk_4` FOREIGN KEY (`nip`) REFERENCES `pegawai` (`nik`) ON UPDATE CASCADE,
CONSTRAINT `petugas_ibfk_5` FOREIGN KEY (`kd_jbtn`) REFERENCES `jabatan` (`kd_jbtn`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `petugas` VALUES ("DR001","dr. Ataaka Muhammad","L","Barabai","2020-12-01","A","Islam","MENIKAH","-","-","0","1");
@@ -4473,18 +4839,22 @@ CREATE TABLE `poliklinik` (
KEY `nm_poli` (`nm_poli`),
KEY `registrasi` (`registrasi`),
KEY `registrasilama` (`registrasilama`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `poliklinik` VALUES ("-","-","0","0","1"),
("IGDK","IGD","0","0","1"),
("UMU","Umum","0","0","1");
CREATE TABLE `propinsi` (
- `kd_prop` int NOT NULL AUTO_INCREMENT,
+ `kd_prop` int NOT NULL,
`nm_prop` varchar(30) NOT NULL,
PRIMARY KEY (`kd_prop`),
UNIQUE KEY `nm_prop` (`nm_prop`)
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `propinsi` VALUES ("1","-");
@@ -4497,7 +4867,7 @@ CREATE TABLE `prosedur_pasien` (
KEY `kode` (`kode`),
CONSTRAINT `prosedur_pasien_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `prosedur_pasien_ibfk_2` FOREIGN KEY (`kode`) REFERENCES `icd9` (`kode`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `rawat_inap_dr` (
@@ -4522,7 +4892,7 @@ CREATE TABLE `rawat_inap_dr` (
CONSTRAINT `rawat_inap_dr_ibfk_3` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON UPDATE CASCADE,
CONSTRAINT `rawat_inap_dr_ibfk_6` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan_inap` (`kd_jenis_prw`) ON UPDATE CASCADE,
CONSTRAINT `rawat_inap_dr_ibfk_7` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `rawat_inap_drpr` (
@@ -4547,7 +4917,7 @@ CREATE TABLE `rawat_inap_drpr` (
CONSTRAINT `rawat_inap_drpr_ibfk_2` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan_inap` (`kd_jenis_prw`) ON UPDATE CASCADE,
CONSTRAINT `rawat_inap_drpr_ibfk_3` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON UPDATE CASCADE,
CONSTRAINT `rawat_inap_drpr_ibfk_4` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `rawat_inap_pr` (
@@ -4570,7 +4940,7 @@ CREATE TABLE `rawat_inap_pr` (
CONSTRAINT `rawat_inap_pr_ibfk_3` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON UPDATE CASCADE,
CONSTRAINT `rawat_inap_pr_ibfk_6` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan_inap` (`kd_jenis_prw`) ON UPDATE CASCADE,
CONSTRAINT `rawat_inap_pr_ibfk_7` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `rawat_jl_dr` (
@@ -4594,7 +4964,7 @@ CREATE TABLE `rawat_jl_dr` (
CONSTRAINT `rawat_jl_dr_ibfk_2` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan` (`kd_jenis_prw`) ON UPDATE CASCADE,
CONSTRAINT `rawat_jl_dr_ibfk_3` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON UPDATE CASCADE,
CONSTRAINT `rawat_jl_dr_ibfk_5` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `rawat_jl_drpr` (
@@ -4621,7 +4991,7 @@ CREATE TABLE `rawat_jl_drpr` (
CONSTRAINT `rawat_jl_drpr_ibfk_2` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan` (`kd_jenis_prw`) ON UPDATE CASCADE,
CONSTRAINT `rawat_jl_drpr_ibfk_3` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON UPDATE CASCADE,
CONSTRAINT `rawat_jl_drpr_ibfk_4` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `rawat_jl_pr` (
@@ -4645,7 +5015,7 @@ CREATE TABLE `rawat_jl_pr` (
CONSTRAINT `rawat_jl_pr_ibfk_10` FOREIGN KEY (`nip`) REFERENCES `petugas` (`nip`) ON UPDATE CASCADE,
CONSTRAINT `rawat_jl_pr_ibfk_8` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE,
CONSTRAINT `rawat_jl_pr_ibfk_9` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan` (`kd_jenis_prw`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `reg_periksa` (
@@ -4679,7 +5049,7 @@ CREATE TABLE `reg_periksa` (
CONSTRAINT `reg_periksa_ibfk_4` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `reg_periksa_ibfk_6` FOREIGN KEY (`kd_pj`) REFERENCES `penjab` (`kd_pj`) ON UPDATE CASCADE,
CONSTRAINT `reg_periksa_ibfk_7` FOREIGN KEY (`no_rkm_medis`) REFERENCES `pasien` (`no_rkm_medis`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `rekap_presensi` (
@@ -4691,11 +5061,11 @@ CREATE TABLE `rekap_presensi` (
`keterlambatan` varchar(20) NOT NULL,
`durasi` varchar(20) DEFAULT NULL,
`keterangan` varchar(100) NOT NULL,
- `photo` varchar(500) NOT NULL,
+ `photo` TEXT NOT NULL,
PRIMARY KEY (`id`,`jam_datang`),
KEY `id` (`id`),
CONSTRAINT `rekap_presensi_ibfk_1` FOREIGN KEY (`id`) REFERENCES `pegawai` (`id`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `resep_dokter` (
@@ -4707,7 +5077,7 @@ CREATE TABLE `resep_dokter` (
KEY `kode_brng` (`kode_brng`),
CONSTRAINT `resep_dokter_ibfk_1` FOREIGN KEY (`no_resep`) REFERENCES `resep_obat` (`no_resep`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `resep_dokter_ibfk_2` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `resep_dokter_racikan` (
@@ -4722,7 +5092,7 @@ CREATE TABLE `resep_dokter_racikan` (
KEY `kd_racik` (`kd_racik`),
CONSTRAINT `resep_dokter_racikan_ibfk_1` FOREIGN KEY (`no_resep`) REFERENCES `resep_obat` (`no_resep`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `resep_dokter_racikan_ibfk_2` FOREIGN KEY (`kd_racik`) REFERENCES `metode_racik` (`kd_racik`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `resep_dokter_racikan_detail` (
@@ -4737,7 +5107,7 @@ CREATE TABLE `resep_dokter_racikan_detail` (
KEY `kode_brng` (`kode_brng`),
CONSTRAINT `resep_dokter_racikan_detail_ibfk_1` FOREIGN KEY (`no_resep`) REFERENCES `resep_obat` (`no_resep`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `resep_dokter_racikan_detail_ibfk_2` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `resep_obat` (
@@ -4756,7 +5126,7 @@ CREATE TABLE `resep_obat` (
KEY `kd_dokter` (`kd_dokter`),
CONSTRAINT `resep_obat_ibfk_3` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE,
CONSTRAINT `resep_obat_ibfk_4` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `resep_pulang` (
@@ -4778,7 +5148,7 @@ CREATE TABLE `resep_pulang` (
CONSTRAINT `resep_pulang_ibfk_2` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `resep_pulang_ibfk_3` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE,
CONSTRAINT `resep_pulang_ibfk_4` FOREIGN KEY (`kd_bangsal`) REFERENCES `bangsal` (`kd_bangsal`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `resiko_kerja` (
@@ -4786,7 +5156,9 @@ CREATE TABLE `resiko_kerja` (
`nama_resiko` varchar(200) DEFAULT NULL,
`indek` tinyint DEFAULT NULL,
PRIMARY KEY (`kode_resiko`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `resiko_kerja` VALUES ("-","-","1");
@@ -4821,7 +5193,7 @@ CREATE TABLE `resume_pasien` (
KEY `kd_dokter` (`kd_dokter`),
CONSTRAINT `resume_pasien_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `resume_pasien_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `resume_pasien_ranap` (
@@ -4870,7 +5242,7 @@ CREATE TABLE `resume_pasien_ranap` (
KEY `kd_dokter` (`kd_dokter`),
CONSTRAINT `resume_pasien_ranap_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `resume_pasien_ranap_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `riwayat_barang_medis` (
@@ -4892,26 +5264,28 @@ CREATE TABLE `riwayat_barang_medis` (
KEY `kd_bangsal` (`kd_bangsal`) USING BTREE,
CONSTRAINT `riwayat_barang_medis_ibfk_1` FOREIGN KEY (`kode_brng`) REFERENCES `databarang` (`kode_brng`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `riwayat_barang_medis_ibfk_2` FOREIGN KEY (`kd_bangsal`) REFERENCES `bangsal` (`kd_bangsal`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `ruang_ok` (
`kd_ruang_ok` varchar(3) NOT NULL,
`nm_ruang_ok` varchar(50) DEFAULT NULL,
PRIMARY KEY (`kd_ruang_ok`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `set_keterlambatan` (
`toleransi` int DEFAULT NULL,
`terlambat1` int DEFAULT NULL,
`terlambat2` int DEFAULT NULL
-) ENGINE=MyISAM DEFAULT CHARSET=latin1;
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
CREATE TABLE `set_no_rkm_medis` (
`no_rkm_medis` varchar(15) NOT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `set_no_rkm_medis` VALUES ("000000");
@@ -4934,14 +5308,16 @@ CREATE TABLE `skdp_bpjs` (
KEY `kd_dokter` (`kd_dokter`) USING BTREE,
CONSTRAINT `skdp_bpjs_ibfk_1` FOREIGN KEY (`no_rkm_medis`) REFERENCES `pasien` (`no_rkm_medis`) ON UPDATE CASCADE,
CONSTRAINT `skdp_bpjs_ibfk_2` FOREIGN KEY (`kd_dokter`) REFERENCES `dokter` (`kd_dokter`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `spesialis` (
`kd_sps` char(5) NOT NULL DEFAULT '',
`nm_sps` varchar(30) DEFAULT NULL,
PRIMARY KEY (`kd_sps`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `spesialis` VALUES ("UMUM","Dokter Umum");
@@ -4950,7 +5326,9 @@ CREATE TABLE `stts_kerja` (
`ktg` varchar(20) NOT NULL,
`indek` tinyint NOT NULL,
PRIMARY KEY (`stts`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `stts_kerja` VALUES ("-","-","1");
@@ -4958,7 +5336,9 @@ CREATE TABLE `stts_wp` (
`stts` char(5) NOT NULL,
`ktg` varchar(50) NOT NULL,
PRIMARY KEY (`stts`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `stts_wp` VALUES ("-","-");
@@ -4967,7 +5347,9 @@ CREATE TABLE `suku_bangsa` (
`nama_suku_bangsa` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `nama_suku_bangsa` (`nama_suku_bangsa`) USING BTREE
-) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT;
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `suku_bangsa` VALUES ("1","-");
@@ -4977,7 +5359,7 @@ CREATE TABLE `tambahan_biaya` (
`besar_biaya` double NOT NULL,
PRIMARY KEY (`no_rawat`,`nama_biaya`),
CONSTRAINT `tambahan_biaya_ibfk_1` FOREIGN KEY (`no_rawat`) REFERENCES `reg_periksa` (`no_rawat`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `template_laboratorium` (
@@ -5016,7 +5398,9 @@ CREATE TABLE `template_laboratorium` (
KEY `biaya_item` (`biaya_item`),
KEY `urut` (`urut`),
CONSTRAINT `template_laboratorium_ibfk_1` FOREIGN KEY (`kd_jenis_prw`) REFERENCES `jns_perawatan_lab` (`kd_jenis_prw`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
+
INSERT INTO `template_laboratorium` VALUES ("LAB001","1","Leukosit","LK","10","5","10","5","0","0","0","0","0","0","0","0","1"),
("LAB001","2","Hemoglobin","HB","20","10","20","10","0","0","0","0","0","0","0","0","2");
@@ -5029,10 +5413,10 @@ CREATE TABLE `temporary_presensi` (
`status` enum('Tepat Waktu','Terlambat Toleransi','Terlambat I','Terlambat II','Tepat Waktu & PSW','Terlambat Toleransi & PSW','Terlambat I & PSW','Terlambat II & PSW') NOT NULL,
`keterlambatan` varchar(20) NOT NULL,
`durasi` varchar(20) DEFAULT NULL,
- `photo` varchar(500) NOT NULL,
+ `photo` TEXT NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `temporary_presensi_ibfk_1` FOREIGN KEY (`id`) REFERENCES `pegawai` (`id`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `utd_donor` (
@@ -5060,7 +5444,7 @@ CREATE TABLE `utd_donor` (
CONSTRAINT `utd_donor_ibfk_1` FOREIGN KEY (`petugas_aftap`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `utd_donor_ibfk_2` FOREIGN KEY (`petugas_u_saring`) REFERENCES `petugas` (`nip`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `utd_donor_ibfk_3` FOREIGN KEY (`no_pendonor`) REFERENCES `utd_pendonor` (`no_pendonor`) ON DELETE CASCADE ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `utd_komponen_darah` (
@@ -5074,7 +5458,7 @@ CREATE TABLE `utd_komponen_darah` (
`total` double DEFAULT NULL,
`pembatalan` double DEFAULT NULL,
PRIMARY KEY (`kode`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `utd_pendonor` (
@@ -5100,7 +5484,7 @@ CREATE TABLE `utd_pendonor` (
CONSTRAINT `utd_pendonor_ibfk_1` FOREIGN KEY (`kd_kec`) REFERENCES `kecamatan` (`kd_kec`) ON UPDATE CASCADE,
CONSTRAINT `utd_pendonor_ibfk_2` FOREIGN KEY (`kd_kab`) REFERENCES `kabupaten` (`kd_kab`) ON UPDATE CASCADE,
CONSTRAINT `utd_pendonor_ibfk_3` FOREIGN KEY (`kd_prop`) REFERENCES `propinsi` (`kd_prop`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE `utd_stok_darah` (
@@ -5115,6 +5499,7 @@ CREATE TABLE `utd_stok_darah` (
PRIMARY KEY (`no_kantong`),
KEY `kode_komponen` (`kode_komponen`),
CONSTRAINT `utd_stok_darah_ibfk_1` FOREIGN KEY (`kode_komponen`) REFERENCES `utd_komponen_darah` (`kode`) ON UPDATE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
+
SET foreign_key_checks = 1;
diff --git a/plugins/afm/Admin.php b/plugins/afm/Admin.php
index 3498bf2dc..533c0bd75 100644
--- a/plugins/afm/Admin.php
+++ b/plugins/afm/Admin.php
@@ -19,11 +19,15 @@ public function getManage()
{
$this->assign['title'] = 'Pengaturan Modul API AFM';
$this->assign['afm'] = htmlspecialchars_array($this->settings('afm'));
- return $this->draw('settings.html', ['settings' => $this->assign]);
+ return $this->draw('settings.html', ['settings' => htmlspecialchars_array($this->assign)]);
}
public function postSaveSettings()
{
+ if ($this->core->getUserInfo('role') != 'admin') {
+ $this->notify('failure', 'Anda tidak memiliki hak akses untuk halaman ini.');
+ redirect(url([ADMIN, 'afm', 'manage']));
+ }
foreach ($_POST['afm'] as $key => $val) {
$this->settings('afm', $key, $val);
}
diff --git a/plugins/afm/Help.md b/plugins/afm/Help.md
deleted file mode 100644
index 1bc4f1632..000000000
--- a/plugins/afm/Help.md
+++ /dev/null
@@ -1,8 +0,0 @@
-Untuk menggunakan fitur ini, silahkan ikuti langkah-langkah berikut!
-1. Aktifkan modul AFM (Anjungan finger mandiri). Jika anda bisa membaca tutorial ini berarti anda sudah mengaktifkan modulnya (LOL) ...!!
-2. Isikan kredensial di pengaturan AFM, yag terdiri dari token AFM, username finger dan password finger BPJS.
-3. Download file AFM di mLITE.id dengan klik mlite.id/page/afm
-4. Extract dan jalankan file hasil download point 3 di Anjungan Finger mandiri
-5. Silahkan ujicoba
-
-Jika ada kesulitan silahkan tanyakan di grup
diff --git a/plugins/afm/Info.php b/plugins/afm/Info.php
index 9a0d5e562..6d5ad33ea 100644
--- a/plugins/afm/Info.php
+++ b/plugins/afm/Info.php
@@ -5,7 +5,7 @@
'author' => 'Basoro',
'category' => 'bridging',
'version' => '1.0',
- 'compatibility' => '5.*.*',
+ 'compatibility' => '6.*.*',
'icon' => 'laptop',
'pages' => ['API AFM mLITE' => 'afm'],
'install' => function () use ($core) {
diff --git a/plugins/afm/README.md b/plugins/afm/README.md
new file mode 100644
index 000000000..0159411f3
--- /dev/null
+++ b/plugins/afm/README.md
@@ -0,0 +1,29 @@
+# Plugin AFM
+
+Dokumentasi singkat penggunaan modul **AFM** di mLITE.
+
+## Akses Modul
+
+- Masuk ke panel admin mLITE.
+- Buka menu **API AFM mLITE**.
+- Pilih submenu sesuai kebutuhan:
+ - Kelola
+
+## Panduan Pengguna (Petugas)
+
+Plugin AFM bersifat konfigurasi teknis dan tidak memiliki alur operasional harian untuk petugas. Akses diberikan kepada admin teknis yang mengelola integrasi API.
+
+## Panduan Admin
+
+1. **Kelola (Pengaturan Token API)**
+ - Buka menu **API AFM mLITE → Kelola**.
+ - Isi field **AFM Token** dengan token autentikasi yang diberikan oleh penyedia layanan AFM.
+ - Isi **Username Finger** dan **Password Finger** untuk integrasi perangkat fingerprint.
+ - Isi **X-Header-Token** sesuai nama header yang disepakati dengan sistem eksternal (default: `X-Header-Token`).
+ - Klik **Simpan** untuk menyimpan konfigurasi.
+
+## Catatan
+
+- Token AFM bersifat rahasia; jangan bagikan ke pihak yang tidak berwenang.
+- Perubahan token memerlukan sinkronisasi ulang dengan sistem eksternal yang menggunakan API ini.
+- Plugin ini termasuk kategori **bridging** dan berfungsi sebagai katalog titik akses API AFM mLITE untuk integrasi sistem lain.
diff --git a/plugins/afm/Site.php b/plugins/afm/Site.php
index 25e6cd313..e0ad76c20 100644
--- a/plugins/afm/Site.php
+++ b/plugins/afm/Site.php
@@ -59,9 +59,13 @@ public function _getCariPasien()
} else if ($header[$this->settings->get('afm.x_header_token')] == $this->settings->get('afm.afm_token')){
$cari = isset_or($decode['nomor_kartu'],'');
$field = isset_or($decode['kolom'], 'no_rkm_medis');
- $sql = "SELECT no_peserta , no_rkm_medis , no_ktp , no_tlp FROM pasien WHERE " . $field . " = '" . $cari . "'";
+ $allowed_fields = ['no_rkm_medis', 'no_peserta', 'no_ktp', 'no_tlp'];
+ if (!in_array($field, $allowed_fields)) {
+ $field = 'no_rkm_medis';
+ }
+ $sql = "SELECT no_peserta , no_rkm_medis , no_ktp , no_tlp FROM pasien WHERE `" . $field . "` = :cari";
$query = $this->db()->pdo()->prepare($sql);
- $query->execute();
+ $query->execute([':cari' => $cari]);
$cek_bookings = $query->fetch();
if (!is_array($cek_bookings)) {
$cek_bookings = [];
@@ -81,7 +85,7 @@ public function _getCariPasien()
);
http_response_code(200);
}
- echo json_encode($response,true);
+ echo json_encode(htmlspecialchars_array($response),true);
exit();
}
@@ -134,7 +138,7 @@ public function _getBio()
);
http_response_code(200);
}
- echo json_encode($response,true);
+ echo json_encode(htmlspecialchars_array($response),true);
exit();
}
@@ -201,7 +205,7 @@ public function _getRegPasien()
http_response_code(200);
}
}
- echo json_encode($response,true);
+ echo json_encode(htmlspecialchars_array($response),true);
exit();
}
@@ -253,7 +257,7 @@ public function _getUserPassFinger()
);
http_response_code(200);
}
- echo json_encode($response,true);
+ echo json_encode(htmlspecialchars_array($response),true);
exit();
}
@@ -320,7 +324,7 @@ public function _getSepPatient()
http_response_code(200);
}
}
- echo json_encode($response,true);
+ echo json_encode(htmlspecialchars_array($response),true);
exit();
}
}
diff --git a/plugins/afm/view/admin/settings.html b/plugins/afm/view/admin/settings.html
index 106f308c7..bdff733e0 100644
--- a/plugins/afm/view/admin/settings.html
+++ b/plugins/afm/view/admin/settings.html
@@ -3,20 +3,20 @@
-
{$settings.title}
+ {?=isset_or($settings.title)?}
Token
-
+
Username Finger BPJS
-
+
Password Finger BPJS
-
+
Web Server
diff --git a/plugins/anjungan/Admin.php b/plugins/anjungan/Admin.php
index 477bbc320..5abfc87c2 100644
--- a/plugins/anjungan/Admin.php
+++ b/plugins/anjungan/Admin.php
@@ -24,7 +24,7 @@ public function getManage()
['name' => 'Pemanggil', 'url' => url([ADMIN, 'anjungan', 'pemanggil']), 'icon' => 'bullhorn', 'desc' => 'Pemanggil Antrian'],
['name' => 'Pengaturan', 'url' => url([ADMIN, 'anjungan', 'settings']), 'icon' => 'gear', 'desc' => 'Pengaturan Anjungan'],
];
- return $this->draw('manage.html', ['sub_modules' => $sub_modules]);
+ return $this->draw('manage.html', ['sub_modules' => htmlspecialchars_array($sub_modules)]);
}
public function getIndex()
@@ -210,16 +210,24 @@ public function getPemanggil()
public function getSettings()
{
+ if ($this->core->getUserInfo('role') != 'admin') {
+ $this->notify('failure', 'Anda tidak memiliki hak akses untuk halaman ini.');
+ redirect(url([ADMIN, 'anjungan', 'index']));
+ }
$this->assign['title'] = 'Pengaturan Modul Anjungan';
$this->assign['poliklinik'] = $this->_getPoliklinik($this->settings->get('anjungan.display_poli'));
$this->assign['penjab'] = $this->_getPenjab($this->settings->get('anjungan.carabayar'));
$this->assign['anjungan'] = htmlspecialchars_array($this->settings('anjungan'));
- return $this->draw('settings.html', ['settings' => $this->assign]);
+ return $this->draw('settings.html', ['settings' => htmlspecialchars_array($this->assign)]);
}
public function postSaveSettings()
{
+ if ($this->core->getUserInfo('role') != 'admin') {
+ $this->notify('failure', 'Anda tidak memiliki hak akses untuk halaman ini.');
+ redirect(url([ADMIN, 'anjungan', 'index']));
+ }
$_POST['anjungan']['display_poli'] = implode(',', $_POST['anjungan']['display_poli']);
$_POST['anjungan']['carabayar'] = implode(',', $_POST['anjungan']['carabayar']);
foreach ($_POST['anjungan'] as $key => $val) {
diff --git a/plugins/anjungan/Help.md b/plugins/anjungan/Help.md
deleted file mode 100644
index ee707d08c..000000000
--- a/plugins/anjungan/Help.md
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/plugins/anjungan/Info.php b/plugins/anjungan/Info.php
index 91bad78c6..02a5e53a0 100644
--- a/plugins/anjungan/Info.php
+++ b/plugins/anjungan/Info.php
@@ -5,7 +5,7 @@
'author' => 'Basoro',
'category' => 'bridging',
'version' => '1.0',
- 'compatibility' => '5.*.*',
+ 'compatibility' => '6.*.*',
'icon' => 'desktop',
'pages' => ['Anjungan Pasien Mandiri' => 'anjungan'],
'install' => function () use ($core) {
diff --git a/plugins/anjungan/README.md b/plugins/anjungan/README.md
new file mode 100644
index 000000000..ba694d169
--- /dev/null
+++ b/plugins/anjungan/README.md
@@ -0,0 +1,55 @@
+# Plugin Anjungan
+
+Dokumentasi singkat penggunaan modul **Anjungan Pasien Mandiri** di mLITE.
+
+## Akses Modul
+
+- Masuk ke panel admin mLITE.
+- Buka menu **Anjungan Pasien Mandiri**.
+- Pilih submenu sesuai kebutuhan:
+ - Kelola
+ - Display
+ - Pemanggil
+ - Pengaturan
+
+## Panduan Pengguna (Petugas)
+
+Petugas loket, CS, dan apotek menggunakan fitur **Pemanggil** untuk memanggil nomor antrian pasien:
+
+1. **Panggil Antrian Loket**
+ - Buka **Pemanggil → Panggil Loket**.
+ - Klik tombol loket yang dituju untuk memanggil nomor antrian berikutnya.
+ - Sistem akan memperbarui nomor antrian dan memutar suara pemanggilan.
+ - Gunakan tombol **Reset** untuk mereset nomor antrian ke awal hari.
+
+2. **Panggil Antrian CS**
+ - Buka **Pemanggil → Panggil CS**.
+ - Proses sama seperti pemanggil loket, menggunakan antrian tipe CS.
+
+3. **Panggil Antrian Apotek**
+ - Buka **Pemanggil → Panggil Apotek**.
+ - Proses sama seperti pemanggil loket, menggunakan antrian tipe Apotek.
+
+4. **Display Informasi**
+ - Buka submenu **Display** untuk menampilkan layar informasi antrian poliklinik, loket, laboratorium, dan apotek secara publik (dapat dipasang di monitor ruang tunggu).
+
+## Panduan Admin
+
+1. **Pengaturan Anjungan**
+ - Buka **Anjungan → Pengaturan**.
+ - Pilih **Poliklinik** yang ditampilkan di anjungan (multiselect).
+ - Pilih **Cara Bayar** yang diperbolehkan mendaftar mandiri.
+ - Atur nomor antrian awal untuk Loket, CS, dan Apotek.
+ - Isi **Running Text** untuk setiap layar display (anjungan, loket, poli, lab, apotek, farmasi).
+ - Isi **ID Video YouTube** yang akan ditayangkan di layar anjungan.
+ - Klik **Simpan** untuk menyimpan pengaturan.
+
+2. **Reset Antrian Harian**
+ - Gunakan fungsi reset antrian (Loket/CS/Apotek) di awal hari atau saat antrian perlu dimulai ulang.
+ - Reset dapat dipicu melalui endpoint yang tersedia di Admin.php (`resetAnjunganLoket`, `resetAnjunganCS`, `resetAnjunganApotek`).
+
+## Catatan
+
+- Suara pemanggilan menggunakan file audio digit (0–9) di folder `plugins/anjungan/suara/`.
+- Layar Display dapat dibuka di browser terpisah atau monitor publik tanpa login admin.
+- Pastikan nomor antrian awal di pengaturan sudah benar sebelum operasional dimulai setiap hari.
diff --git a/plugins/anjungan/Site.php b/plugins/anjungan/Site.php
index 52430e5cf..510061be2 100644
--- a/plugins/anjungan/Site.php
+++ b/plugins/anjungan/Site.php
@@ -27,6 +27,11 @@ public function init()
public function routes()
{
+ // Auth or IP Restriction Check
+ if (strpos($_SERVER['REQUEST_URI'], '/anjungan') !== false) {
+ $this->_checkAccess();
+ }
+
$this->route('anjungan', 'getIndex');
$this->route('anjungan/pasien', 'getDisplayAPM');
$this->route('anjungan/loket', 'getDisplayAntrianLoket');
@@ -48,6 +53,7 @@ public function routes()
$this->route('anjungan/display/poli/(:str)', 'getDisplayAntrianPoliDisplay');
$this->route('anjungan/display/poli/(:str)/(:str)', 'getDisplayAntrianPoliDisplay');
$this->route('anjungan/laboratorium', 'getDisplayAntrianLaboratorium');
+ $this->route('anjungan/radiologi', 'getDisplayAntrianRadiologi');
$this->route('anjungan/apotek', 'getDisplayAntrianApotek');
$this->route('anjungan/apotek/ambilantrian', 'getDisplayAntrianApotekAmbil');
$this->route('anjungan/ajax', 'getAjax');
@@ -70,6 +76,151 @@ public function routes()
//$this->route('anjungan/daftar/baru/(:str)', 'getDaftarBPJS');
}
+ private function _checkAccess()
+ {
+ // 1. Check if user is logged in
+ if (isset($_SESSION['mlite_user'])) {
+ return true;
+ }
+
+ // 2. Check if IP is local/intranet
+ $clientIP = $_SERVER['REMOTE_ADDR'];
+
+ // Define local/intranet ranges
+ // localhost, 127.0.0.1, ::1
+ // 10.0.0.0 - 10.255.255.255
+ // 172.16.0.0 - 172.31.255.255
+ // 192.168.0.0 - 192.168.255.255
+
+ $isLocal = false;
+
+ // Check for IPv6 localhost
+ if ($clientIP === '::1') {
+ $isLocal = true;
+ }
+
+ // Check for IPv4
+ $ipLong = ip2long($clientIP);
+
+ if ($ipLong !== false) {
+ // 127.0.0.0/8
+ if (($ipLong & 0xFF000000) === 0x7F000000) {
+ $isLocal = true;
+ }
+ // 10.0.0.0/8
+ elseif (($ipLong & 0xFF000000) === 0x0A000000) {
+ $isLocal = true;
+ }
+ // 172.16.0.0/12
+ elseif (($ipLong & 0xFFF00000) === 0xAC100000) {
+ $isLocal = true;
+ }
+ // 192.168.0.0/16
+ elseif (($ipLong & 0xFFFF0000) === 0xC0A80000) {
+ $isLocal = true;
+ }
+ }
+
+ if (!$isLocal) {
+ http_response_code(403);
+ $logo = $this->settings->get('settings.logo');
+ $instansi = $this->settings->get('settings.nama_instansi');
+ $alamat = $this->settings->get('settings.alamat');
+ $ip = $_SERVER['REMOTE_ADDR'];
+
+ $html = '
+
+
+
Access Denied - ' . htmlspecialchars($instansi) . '
+
+
+
+
+
+ ' . ($logo ? '
' : '') . '
+
Akses Ditolak
+
IP Anda: ' . htmlspecialchars($ip) . '
+
+ Maaf, halaman Anjungan hanya dapat diakses melalui jaringan lokal (Intranet) Rumah Sakit atau oleh pengguna yang telah terautentikasi.
+
+
Login Disini
+
+
+
+ ';
+
+ die($html);
+ }
+
+ return true;
+ }
+
public function getIndex()
{
echo $this->draw('index.html', ['test' => 'Opo iki']);
@@ -438,7 +589,6 @@ public function _resultDisplayAntrianPoli()
}
$row['selanjutnya'] = $this->db('reg_periksa')
->select('reg_periksa.no_reg')
- //->select(['no_urut_reg' => 'ifnull(MAX(CONVERT(RIGHT(reg_periksa.no_reg,3),signed)),0)'])
->select('pasien.nm_pasien')
->join('pasien', 'pasien.no_rkm_medis = reg_periksa.no_rkm_medis')
->where('reg_periksa.tgl_registrasi', $date)
@@ -448,11 +598,11 @@ public function _resultDisplayAntrianPoli()
->asc('reg_periksa.no_reg')
->toArray();
$row['get_no_reg'] = $this->db('reg_periksa')
- ->select(['max' => 'ifnull(MAX(CONVERT(RIGHT(no_reg,3),signed)),0)'])
- ->where('tgl_registrasi', $date)
- ->where('kd_poli', $row['kd_poli'])
- ->where('kd_dokter', $row['kd_dokter'])
- ->oneArray();
+ ->where('tgl_registrasi', $date)
+ ->where('kd_poli', $row['kd_poli'])
+ ->where('kd_dokter', $row['kd_dokter'])
+ ->nextRightNumber('no_reg', 3);
+
$row['diff'] = (strtotime($row['jam_selesai'])-strtotime($row['jam_mulai']))/60;
$row['interval'] = 0;
if($row['diff'] == 0) {
@@ -690,7 +840,6 @@ public function _resultDisplayAntrianPoliKodeSatu()
}
$row['selanjutnya'] = $this->db('reg_periksa')
->select('reg_periksa.no_reg')
- //->select(['no_urut_reg' => 'ifnull(MAX(CONVERT(RIGHT(reg_periksa.no_reg,3),signed)),0)'])
->select('pasien.nm_pasien')
->join('pasien', 'pasien.no_rkm_medis = reg_periksa.no_rkm_medis')
->where('reg_periksa.tgl_registrasi', $date)
@@ -699,12 +848,13 @@ public function _resultDisplayAntrianPoliKodeSatu()
->where('reg_periksa.kd_dokter', $row['kd_dokter'])
->asc('reg_periksa.no_reg')
->toArray();
+
$row['get_no_reg'] = $this->db('reg_periksa')
- ->select(['max' => 'ifnull(MAX(CONVERT(RIGHT(no_reg,3),signed)),0)'])
->where('tgl_registrasi', $date)
->where('kd_poli', $row['kd_poli'])
->where('kd_dokter', $row['kd_dokter'])
- ->oneArray();
+ ->nextRightNumber('no_reg', 3);
+
$row['diff'] = (strtotime($row['jam_selesai'])-strtotime($row['jam_mulai']))/60;
$row['interval'] = 0;
if($row['diff'] == 0) {
@@ -823,7 +973,6 @@ public function _resultDisplayAntrianPoliKodeDua()
}
$row['selanjutnya'] = $this->db('reg_periksa')
->select('reg_periksa.no_reg')
- //->select(['no_urut_reg' => 'ifnull(MAX(CONVERT(RIGHT(reg_periksa.no_reg,3),signed)),0)'])
->select('pasien.nm_pasien')
->join('pasien', 'pasien.no_rkm_medis = reg_periksa.no_rkm_medis')
->where('reg_periksa.tgl_registrasi', $date)
@@ -833,11 +982,10 @@ public function _resultDisplayAntrianPoliKodeDua()
->asc('reg_periksa.no_reg')
->toArray();
$row['get_no_reg'] = $this->db('reg_periksa')
- ->select(['max' => 'ifnull(MAX(CONVERT(RIGHT(no_reg,3),signed)),0)'])
->where('tgl_registrasi', $date)
->where('kd_poli', $row['kd_poli'])
->where('kd_dokter', $row['kd_dokter'])
- ->oneArray();
+ ->nextRightNumber('no_reg', 3);
$row['diff'] = (strtotime($row['jam_selesai'])-strtotime($row['jam_mulai']))/60;
$row['interval'] = 0;
if($row['diff'] == 0) {
@@ -956,7 +1104,6 @@ public function _resultDisplayAntrianPoliKodeTiga()
}
$row['selanjutnya'] = $this->db('reg_periksa')
->select('reg_periksa.no_reg')
- //->select(['no_urut_reg' => 'ifnull(MAX(CONVERT(RIGHT(reg_periksa.no_reg,3),signed)),0)'])
->select('pasien.nm_pasien')
->join('pasien', 'pasien.no_rkm_medis = reg_periksa.no_rkm_medis')
->where('reg_periksa.tgl_registrasi', $date)
@@ -966,11 +1113,10 @@ public function _resultDisplayAntrianPoliKodeTiga()
->asc('reg_periksa.no_reg')
->toArray();
$row['get_no_reg'] = $this->db('reg_periksa')
- ->select(['max' => 'ifnull(MAX(CONVERT(RIGHT(no_reg,3),signed)),0)'])
->where('tgl_registrasi', $date)
->where('kd_poli', $row['kd_poli'])
->where('kd_dokter', $row['kd_dokter'])
- ->oneArray();
+ ->nextRightNumber('no_reg', 3);
$row['diff'] = (strtotime($row['jam_selesai'])-strtotime($row['jam_mulai']))/60;
$row['interval'] = 0;
if($row['diff'] == 0) {
@@ -1097,7 +1243,6 @@ public function _resultDisplayAntrianPoliKodeDisplay()
}
$row['selanjutnya'] = $this->db('reg_periksa')
->select('reg_periksa.no_reg')
- //->select(['no_urut_reg' => 'ifnull(MAX(CONVERT(RIGHT(reg_periksa.no_reg,3),signed)),0)'])
->select('pasien.nm_pasien')
->join('pasien', 'pasien.no_rkm_medis = reg_periksa.no_rkm_medis')
->where('reg_periksa.tgl_registrasi', $date)
@@ -1107,11 +1252,10 @@ public function _resultDisplayAntrianPoliKodeDisplay()
->asc('reg_periksa.no_reg')
->toArray();
$row['get_no_reg'] = $this->db('reg_periksa')
- ->select(['max' => 'ifnull(MAX(CONVERT(RIGHT(no_reg,3),signed)),0)'])
->where('tgl_registrasi', $date)
->where('kd_poli', $row['kd_poli'])
->where('kd_dokter', $row['kd_dokter'])
- ->oneArray();
+ ->nextRightNumber('no_reg', 3);
$row['diff'] = (strtotime($row['jam_selesai'])-strtotime($row['jam_mulai']))/60;
$row['interval'] = 0;
if($row['diff'] == 0) {
@@ -1252,32 +1396,125 @@ public function getDisplayAntrianLaboratorium()
//exit();
}
- public function _resultDisplayAntrianLaboratorium()
+ public function getDisplayAntrianRadiologi()
{
- $date = date('Y-m-d');
- $tentukan_hari=date('D',strtotime(date('Y-m-d')));
- $day = array(
- 'Sun' => 'AKHAD',
- 'Mon' => 'SENIN',
- 'Tue' => 'SELASA',
- 'Wed' => 'RABU',
- 'Thu' => 'KAMIS',
- 'Fri' => 'JUMAT',
- 'Sat' => 'SABTU'
- );
- $hari=$day[$tentukan_hari];
+ $logo = $this->settings->get('settings.logo');
+ $title = 'Display Antrian Radiologi';
+ $display = $this->_resultDisplayAntrianRadiologi();
- $poliklinik = $this->settings('settings', 'laboratorium');
- $rows = $this->db('reg_periksa')
- ->join('pasien', 'pasien.no_rkm_medis=reg_periksa.no_rkm_medis')
- ->where('tgl_registrasi', date('Y-m-d'))
- ->where('kd_poli', $poliklinik)
- ->asc('no_reg')
- ->toArray();
+ $_username = '';
+ $__username = 'Tamu';
+ if(isset($_SESSION['mlite_user'])) {
+ $_username = $this->core->getUserInfo('fullname', null, true);
+ $__username = $this->core->getUserInfo('username');
+ }
+ $tanggal = getDayIndonesia(date('Y-m-d')).', '.dateIndonesia(date('Y-m-d'));
+ $username = !empty($_username) ? $_username : $__username;
+ $running_text = $this->settings->get('anjungan.text_radiologi');
+ if(empty($running_text)) {
+ $running_text = $this->settings->get('anjungan.text_laboratorium');
+ }
- return $rows;
+ $content = $this->draw('display.antrian.radiologi.html', [
+ 'logo' => $logo,
+ 'title' => $title,
+ 'powered' => 'Powered by
mLITE ',
+ 'username' => $username,
+ 'tanggal' => $tanggal,
+ 'running_text' => $running_text,
+ 'display' => $display
+ ]);
+
+ $assign = [
+ 'title' => $this->settings->get('settings.nama_instansi'),
+ 'desc' => $this->settings->get('settings.alamat'),
+ 'content' => $content
+ ];
+
+ $this->setTemplate("canvas.html");
+ $this->tpl->set('page', ['title' => $assign['title'], 'desc' => $assign['desc'], 'content' => $assign['content']]);
}
+ public function _resultDisplayAntrianLaboratorium()
+ {
+ $date_start = date('Y-m-d', strtotime('-1 month'));
+ $date = date('Y-m-d');
+
+ $rows = $this->db('permintaan_lab')
+ ->join('dokter', 'dokter.kd_dokter=permintaan_lab.dokter_perujuk')
+ ->join('reg_periksa', 'reg_periksa.no_rawat=permintaan_lab.no_rawat')
+ ->join('poliklinik', 'poliklinik.kd_poli=reg_periksa.kd_poli')
+ ->join('pasien', 'pasien.no_rkm_medis=reg_periksa.no_rkm_medis')
+ ->join('permintaan_pemeriksaan_lab', 'permintaan_pemeriksaan_lab.noorder=permintaan_lab.noorder')
+ ->join('jns_perawatan_lab', 'jns_perawatan_lab.kd_jenis_prw=permintaan_pemeriksaan_lab.kd_jenis_prw')
+ ->where('permintaan_lab.tgl_hasil', '0000-00-00')
+ ->where('permintaan_lab.tgl_permintaan', '>=', $date_start)
+ ->where('permintaan_lab.tgl_permintaan', '<=', $date)
+ ->group('permintaan_lab.noorder')
+ ->desc('permintaan_lab.tgl_permintaan')
+ ->toArray();
+
+ $filteredRows = [];
+ foreach ($rows as $row) {
+
+ // Tentukan status untuk data yang akan ditampilkan
+ if ($row['tgl_sampel'] == '0000-00-00' && $row['jam_sampel'] == '00:00:00') {
+ $row['status_antrian'] = 'Menunggu';
+ $row['status_class'] = 'label-warning';
+ } elseif ($row['tgl_sampel'] != '0000-00-00' && $row['jam_sampel'] != '00:00:00') {
+ $row['status_antrian'] = 'Diproses';
+ $row['status_class'] = 'label-success';
+ } else {
+ $row['status_antrian'] = 'Diproses';
+ $row['status_class'] = 'label-warning';
+ }
+
+ $filteredRows[] = $row;
+ }
+
+ $rows = $filteredRows;
+
+ return $rows;
+ }
+
+ public function _resultDisplayAntrianRadiologi()
+ {
+ $date_start = date('Y-m-d', strtotime('-1 month'));
+ $date = date('Y-m-d');
+
+ $rows = $this->db('permintaan_radiologi')
+ ->join('dokter', 'dokter.kd_dokter=permintaan_radiologi.dokter_perujuk')
+ ->join('reg_periksa', 'reg_periksa.no_rawat=permintaan_radiologi.no_rawat')
+ ->join('poliklinik', 'poliklinik.kd_poli=reg_periksa.kd_poli')
+ ->join('pasien', 'pasien.no_rkm_medis=reg_periksa.no_rkm_medis')
+ ->join('permintaan_pemeriksaan_radiologi', 'permintaan_pemeriksaan_radiologi.noorder=permintaan_radiologi.noorder')
+ ->join('jns_perawatan_radiologi', 'jns_perawatan_radiologi.kd_jenis_prw=permintaan_pemeriksaan_radiologi.kd_jenis_prw')
+ ->where('permintaan_radiologi.tgl_hasil', '0000-00-00')
+ ->where('permintaan_radiologi.tgl_permintaan', '>=', $date_start)
+ ->where('permintaan_radiologi.tgl_permintaan', '<=', $date)
+ ->group('permintaan_radiologi.noorder')
+ ->desc('permintaan_radiologi.tgl_permintaan')
+ ->toArray();
+
+ $filteredRows = [];
+ foreach ($rows as $row) {
+ if ($row['tgl_sampel'] == '0000-00-00' && $row['jam_sampel'] == '00:00:00') {
+ $row['status_antrian'] = 'Menunggu';
+ $row['status_class'] = 'label-warning';
+ } elseif ($row['tgl_sampel'] != '0000-00-00' && $row['jam_sampel'] != '00:00:00') {
+ $row['status_antrian'] = 'Diproses';
+ $row['status_class'] = 'label-success';
+ } else {
+ $row['status_antrian'] = 'Diproses';
+ $row['status_class'] = 'label-warning';
+ }
+
+ $filteredRows[] = $row;
+ }
+
+ return $filteredRows;
+ }
+
public function getDisplayAntrianApotek()
{
$logo = $this->settings->get('settings.logo');
@@ -1729,7 +1966,6 @@ public function getAjax()
case "simpanloket":
$this->db('mlite_antrian_loket')
->save([
- 'kd' => NULL,
'type' => 'Loket',
'noantrian' => $_GET['noantrian'],
'postdate' => date('Y-m-d'),
@@ -1801,7 +2037,6 @@ public function getAjax()
case "simpancs":
$this->db('mlite_antrian_loket')
->save([
- 'kd' => NULL,
'type' => 'CS',
'noantrian' => $_GET['noantrian'],
'postdate' => date('Y-m-d'),
@@ -1876,7 +2111,6 @@ public function getAjax()
case "simpanapotek":
$this->db('mlite_antrian_loket')
->save([
- 'kd' => NULL,
'type' => 'Apotek',
'noantrian' => $_GET['noantrian'],
'no_rkm_medis' => $_POST['no_rkm_medis'],
@@ -1965,7 +2199,7 @@ public function getAjax()
$data['status'] = 'err';
$data['result'] = '';
}
- echo json_encode($data);
+ echo json_encode(htmlspecialchars_array($data));
}
break;
@@ -1982,7 +2216,7 @@ public function getAjax()
$data['status'] = 'err';
$data['result'] = '';
}
- echo json_encode($data);
+ echo json_encode(htmlspecialchars_array($data));
}
break;
@@ -1992,7 +2226,7 @@ public function getAjax()
if($this->db('reg_periksa')->where('no_rkm_medis', $_POST['no_rkm_medis'])->where('tgl_registrasi', $_POST['tgl_registrasi'])->oneArray()) {
$data['status'] = 'exist';
$data['result'] = '';
- echo json_encode($data);
+ echo json_encode(htmlspecialchars_array($data));
} else {
$tanggal = $_POST['tgl_registrasi'];
$tentukan_hari = date('D',strtotime($tanggal));
@@ -2014,7 +2248,7 @@ public function getAjax()
$data['status'] = 'err';
$data['result'] = '';
}
- echo json_encode($data);
+ echo json_encode(htmlspecialchars_array($data));
}
}
break;
@@ -2060,7 +2294,7 @@ public function getAjax()
$data['status'] = 'err';
$data['result'] = '';
}
- echo json_encode($data);
+ echo json_encode(htmlspecialchars_array($data));
}
}
break;
@@ -2076,7 +2310,7 @@ public function getAjax()
$data['status'] = 'err';
$data['result'] = '';
}
- echo json_encode($data);
+ echo json_encode(htmlspecialchars_array($data));
}
break;
case "get-namadokter":
@@ -2091,7 +2325,7 @@ public function getAjax()
$data['status'] = 'err';
$data['result'] = '';
}
- echo json_encode($data);
+ echo json_encode(htmlspecialchars_array($data));
}
break;
case "post-registrasi":
@@ -2180,7 +2414,80 @@ public function getAjax()
$data['status'] = 'err';
$data['result'] = '';
}
- echo json_encode($data);
+ echo json_encode(htmlspecialchars_array($data));
+ }
+ break;
+ case "loket-display":
+ $apotek = $this->db('mlite_antrian_loket')
+ ->select('noantrian')
+ ->select('loket')
+ ->where('type', 'Apotek')
+ ->where('postdate', date('Y-m-d'))
+ ->where('status', '>=', 1)
+ ->desc('kd')
+ ->oneArray();
+
+ echo json_encode([
+ 'loket' => [
+ 'antrian' => (string) $this->settings->get('anjungan.no_antrian_loket'),
+ 'counter' => (string) $this->settings->get('anjungan.konter_antrian_loket')
+ ],
+ 'cs' => [
+ 'antrian' => (string) $this->settings->get('anjungan.no_antrian_cs'),
+ 'counter' => (string) $this->settings->get('anjungan.konter_antrian_cs')
+ ],
+ 'apotek' => [
+ 'antrian' => isset($apotek['noantrian']) ? (string) $apotek['noantrian'] : '',
+ 'counter' => isset($apotek['loket']) ? (string) $apotek['loket'] : ''
+ ]
+ ]);
+ break;
+ // FOR DISPLAY
+ case "apotek-display":
+ $rows = $this->_resultDisplayAntrianApotek();
+ foreach ($rows as $row) {
+ echo '
'
+ . ''. $row['noantrian'] . ' '. $row['nm_pasien'] . ' - ' . $row['no_rkm_medis'] . ' Resep: ' . $row['jenis_resep'] . ' Status: ' . $row['status_resep'] . ' '
+ . ' ';
+ }
+ break;
+ case "laboratorium-display":
+ $rows = $this->_resultDisplayAntrianLaboratorium();
+ foreach ($rows as $row) {
+
+ echo '
'
+ . '' . $row['no_reg'] . ' ' . ' Nama: ' . $row['nm_pasien'] . ', No. RM: ' . $row['no_rkm_medis'] . ' Status: ' . $row['status_antrian'] . ' '
+ . ' ';
+ }
+ break;
+ case "radiologi-display":
+ $rows = $this->_resultDisplayAntrianRadiologi();
+ foreach ($rows as $row) {
+ echo '
'
+ . '' . $row['no_reg'] . ' ' . ' Nama: ' . $row['nm_pasien'] . ', No. RM: ' . $row['no_rkm_medis'] . ' Status: ' . $row['status_antrian'] . ' '
+ . ' ';
+ }
+ break;
+ case "bed-display":
+ $rows = $this->_resultDisplayBed();
+ foreach ($rows as $row) {
+ echo '
'
+ . '' . $row['nm_bangsal'] . ' '
+ . '' . $row['kelas'] . ' '
+ . ''
+ . 'Jumlah Kosong : ' . $row['kosong']['jumlah'] . ' | Jumlah Terisi : ' . $row['isi']['jumlah']
+ . ' '
+ . ' ';
+ }
+ break;
+ case "operasi-display":
+ $rows = $this->_resultDisplayJadwalOperasi();
+ foreach ($rows as $row) {
+ echo '
'
+ . 'Nama: ' . $row['nm_pasien'] . ', No. RM: ' . $row['no_rkm_medis'] . 'Status: ' . $row['status'] . ' '
+ . '[ Operator : ' . $row['namadok'] . ' ] [ Rujukan Dari : '
+ . '{if: $row.kd_poli == "IGDK"}' . $row['kamar.namabangsal'] . '{elseif: $row.kd_poli == "U0027"}' . $row['kamar.namabangsal'] . '{else}' . $row['namapoli'] . '{/if} ] '
+ . ' ';
}
break;
}
@@ -2206,7 +2513,7 @@ public function getPresensi()
'powered' => 'Powered by
mLITE ',
'tanggal' => $tanggal,
'running_text' => $this->settings->get('anjungan.text_poli'),
- 'jam_jaga' => $this->db('jam_jaga')->select('jam_masuk')->group('jam_masuk')->toArray()
+ 'jam_jaga' => $this->db('jam_jaga')->select('jam_masuk')->select('jam_pulang')->group('jam_masuk')->toArray()
]);
$assign = [
@@ -2266,40 +2573,43 @@ public function getUpload()
$idpeg = $this->db('barcode')->where('barcode', $barcode)->oneArray();
$jam_jaga = $this->db('jam_jaga')->join('pegawai', 'pegawai.departemen = jam_jaga.dep_id')->where('pegawai.id', $idpeg['id'])->where('jam_jaga.shift', $_GET['shift'])->oneArray();
- $jadwal_pegawai = $this->db('jadwal_pegawai')->where('id', $idpeg['id'])->where('h'.date('j'), $_GET['shift'])->oneArray();
+ $jadwal_pegawai = $this->db('jadwal_pegawai')->where('id', $idpeg['id'])->where('h'.date('j'), $_GET['shift'])->where('bulan', date('m'))->where('tahun', date('Y'))->oneArray();
- $set_keterlambatan = $this->db('set_keterlambatan')->toArray();
- $toleransi = $set_keterlambatan['toleransi'];
- $terlambat1 = $set_keterlambatan['terlambat1'];
- $terlambat2 = $set_keterlambatan['terlambat2'];
+ $set_keterlambatan = $this->db('set_keterlambatan')->oneArray();
+ $toleransi = $set_keterlambatan['toleransi'] ?? 0;
+ $terlambat1 = $set_keterlambatan['terlambat1'] ?? 0;
+ $terlambat2 = $set_keterlambatan['terlambat2'] ?? 0;
- $valid = $this->db('rekap_presensi')->where('id', $idpeg['id'])->where('shift', $jam_jaga['shift'])->like('jam_datang', '%'.date('Y-m-d').'%')->oneArray();
+ $valid = $this->db('rekap_presensi')->where('id', $idpeg['id'])->where('shift', $jam_jaga['shift'] ?? '')->like('jam_datang', '%'.date('Y-m-d').'%')->oneArray();
if($valid){
- $this->notify('failure', 'Anda sudah presensi untuk tanggal '.date('Y-m-d'));
+ echo "Anda sudah presensi untuk tanggal ".date('Y-m-d');
//}elseif((!empty($idpeg['id']))&&(!empty($jam_jaga['shift']))&&($jadwal_pegawai)&&(!$valid)) {
}elseif((!empty($idpeg['id']))) {
$cek = $this->db('temporary_presensi')->where('id', $idpeg['id'])->oneArray();
if(!$cek){
if(empty($urlnya)){
- $this->notify('failure', 'Pilih shift dulu...!!!!');
+ echo "Pilih shift dulu...!!!!";
}else{
-
+
+ $keterlambatan = '00:00:00';
$status = 'Tepat Waktu';
+ $jam_masuk = $jam_jaga['jam_masuk'] ?? '00:00:00';
+ $shift_jaga = $jam_jaga['shift'] ?? '';
- if((strtotime(date('Y-m-d H:i:s'))-strtotime(date('Y-m-d').' '.$jam_jaga['jam_masuk']))>($toleransi*60)) {
+ if((strtotime(date('Y-m-d H:i:s'))-strtotime(date('Y-m-d').' '.$jam_masuk))>($toleransi*60)) {
$status = 'Terlambat Toleransi';
}
- if((strtotime(date('Y-m-d H:i:s'))-strtotime(date('Y-m-d').' '.$jam_jaga['jam_masuk']))>($terlambat1*60)) {
+ if((strtotime(date('Y-m-d H:i:s'))-strtotime(date('Y-m-d').' '.$jam_masuk))>($terlambat1*60)) {
$status = 'Terlambat I';
}
- if((strtotime(date('Y-m-d H:i:s'))-strtotime(date('Y-m-d').' '.$jam_jaga['jam_masuk']))>($terlambat2*60)) {
+ if((strtotime(date('Y-m-d H:i:s'))-strtotime(date('Y-m-d').' '.$jam_masuk))>($terlambat2*60)) {
$status = 'Terlambat II';
}
- if(strtotime(date('Y-m-d H:i:s'))-(date('Y-m-d').' '.$jam_jaga['jam_masuk'])>($toleransi*60)) {
- $awal = new \DateTime(date('Y-m-d').' '.$jam_jaga['jam_masuk']);
+ if((strtotime(date('Y-m-d H:i:s')) - strtotime(date('Y-m-d').' '.$jam_masuk)) > ($toleransi*60)) {
+ $awal = new \DateTime(date('Y-m-d').' '.$jam_masuk);
$akhir = new \DateTime();
$diff = $akhir->diff($awal,true); // to make the difference to be always positive.
$keterlambatan = $diff->format('%H:%I:%S');
@@ -2309,7 +2619,7 @@ public function getUpload()
$insert = $this->db('temporary_presensi')
->save([
'id' => $idpeg['id'],
- 'shift' => $jam_jaga['shift'],
+ 'shift' => $shift_jaga,
'jam_datang' => date('Y-m-d H:i:s'),
'jam_pulang' => NULL,
'status' => $status,
@@ -2319,13 +2629,15 @@ public function getUpload()
]);
if($insert) {
- $this->notify('success', 'Presensi Masuk jam '.$jam_jaga['jam_masuk'].' '.$status.' '.$keterlambatan);
+ echo htmlspecialchars("Presensi Masuk jam ".$jam_masuk." ".$status." ".$keterlambatan." telah disimpan", ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}
}elseif($cek){
$status = $cek['status'];
- if((strtotime(date('Y-m-d H:i:s'))-strtotime(date('Y-m-d').' '.$jam_jaga['jam_pulang']))<0) {
+ $jam_pulang = $jam_jaga['jam_pulang'] ?? '00:00:00';
+
+ if((strtotime(date('Y-m-d H:i:s'))-strtotime(date('Y-m-d').' '.$jam_pulang))<0) {
$status = $cek['status'].' & PSW';
}
@@ -2357,13 +2669,13 @@ public function getUpload()
'photo' => $presensi['photo']
]);
if($insert) {
- $this->notify('success', 'Presensi pulang telah disimpan');
$this->db('temporary_presensi')->where('id', $cek['id'])->delete();
+ echo htmlspecialchars("Presensi Masuk jam ".$presensi['jam_datang']." ".$presensi['status']." ".$presensi['keterlambatan']." telah disimpan", ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}
}
}else{
- $this->notify('failure', 'ID Pegawai atau jadwal shift tidak sesuai. Silahkan pilih berdasarkan shift departemen anda!');
+ echo "ID Pegawai atau jadwal shift tidak sesuai. Silahkan pilih berdasarkan shift departemen anda!";
}
}
}
@@ -2537,7 +2849,7 @@ public function getSepMandiriNokaNorm()
$url = $this->api_url . '/SEP/FingerPrint/Peserta/' . $slug[2] . '/TglPelayanan/'.$dateNow.'';
$output = BpjsService::get($url, NULL, $this->consid, $this->secretkey, $this->user_key, $tStamp);
$json = json_decode($output, true);
- //echo json_encode($json);
+ //echo json_encode(htmlspecialchars_array($json));
$code = $json['metaData']['code'];
$message = $json['metaData']['message'];
$stringDecrypt = stringDecrypt($key, $json['response']);
@@ -2794,6 +3106,7 @@ public function postSaveSEP()
$cek_kuota['jam_mulai'] = date('H:i:s',strtotime('+'.$minutes.' minutes',strtotime($jadwaldokter['jam_mulai'])));
$kodebooking = $this->settings->get('settings.ppk_bpjs').''.convertNorawat($reg_periksa['no_rawat']).''.$maping_poli_bpjs['kd_poli_bpjs'].''.$reg_periksa['no_reg'];
+ $tlp = substr($pasien['no_tlp'], 0, 13);
$jenisKunjungan = '1';
$nomorreferensi = $_POST['norujukan'];
if(isset($_POST['tujuanKunj']) == '2') {
@@ -2805,7 +3118,7 @@ public function postSaveSEP()
'jenispasien' => 'JKN',
'nomorkartu' => $pasien['no_peserta'],
'nik' => $pasien['no_ktp'],
- 'nohp' => $pasien['no_tlp'],
+ 'nohp' => $tlp,
'kodepoli' => $maping_poli_bpjs['kd_poli_bpjs'],
'namapoli' => $maping_poli_bpjs['nm_poli_bpjs'],
'pasienbaru' => '0',
@@ -3015,7 +3328,7 @@ public function postSaveSEP()
'prb' => $_POST['prolanis_prb']
]);
}
- echo $_POST['sep_no_sep'];
+ echo htmlspecialchars($_POST['sep_no_sep']);
} else {
$simpan_sep = $this->db('bridging_sep_internal')->save([
'no_sep' => $_POST['sep_no_sep'],
@@ -3075,7 +3388,7 @@ public function postSaveSEP()
} else {
- echo $data['metaData']['message'];
+ echo htmlspecialchars($data['metaData']['message'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
exit();
@@ -3339,7 +3652,7 @@ public function getDaftarBPJS($nik)
$url = $this->api_url . 'Peserta/nik/' . $nik . '/tglSEP/' . $tglPelayananSEP;
$output = BpjsService::get($url, NULL, $this->consid, $this->secretkey, $this->user_key, $tStamp);
$json = json_decode($output, true);
- //echo json_encode($json);
+ //echo json_encode(htmlspecialchars_array($json));
$code = $json['metaData']['code'];
$message = $json['metaData']['message'];
$stringDecrypt = stringDecrypt($key, $json['response']);
diff --git a/plugins/anjungan/suara/lab.mp3 b/plugins/anjungan/suara/lab.mp3
new file mode 100644
index 000000000..d34f78257
Binary files /dev/null and b/plugins/anjungan/suara/lab.mp3 differ
diff --git a/plugins/anjungan/view/admin/index.html b/plugins/anjungan/view/admin/index.html
index 09f3dab24..8750942e0 100644
--- a/plugins/anjungan/view/admin/index.html
+++ b/plugins/anjungan/view/admin/index.html
@@ -59,6 +59,36 @@
Antrian Laboratorium
+
+
+
+
+
Antrian Radiologi
+
+
+
+
diff --git a/plugins/anjungan/view/admin/manage.html b/plugins/anjungan/view/admin/manage.html
index a2faeb1eb..bdf53d77c 100644
--- a/plugins/anjungan/view/admin/manage.html
+++ b/plugins/anjungan/view/admin/manage.html
@@ -4,14 +4,14 @@
{loop: $sub_modules}
diff --git a/plugins/anjungan/view/admin/pemanggil.html b/plugins/anjungan/view/admin/pemanggil.html
index aa8815a80..4c96c5835 100644
--- a/plugins/anjungan/view/admin/pemanggil.html
+++ b/plugins/anjungan/view/admin/pemanggil.html
@@ -118,23 +118,23 @@
-
Loket {$value}
+
Loket {?=isset_or($value)?}
-
{?=strtoupper($namaloket)?}{$antrian}
+ {?=strtoupper($namaloket)?}{?=isset_or($antrian)?}
-
+
Anjungan Cetak Surat Eligibilitas Peserta BPJS
-
{$settings.nama_instansi}
+
{?=isset_or($settings.nama_instansi)?}
{if: $notify}
-
- {$notify.text}
+
+ {?=isset_or($notify.text)?}
×
@@ -160,9 +160,9 @@
{$settings.nama_instansi}
Made with by drg. F. Basoro .
- {$powered}.
+ {?=isset_or($powered)?}.
-
{$tanggal} {?= sprintf('Login sebagai %s ', $username) ?}
+
{?=isset_or($tanggal)?} {?= sprintf('Login sebagai %s ', $username) ?}
@@ -208,7 +208,7 @@
{$settings.nama_instansi}
-
Antrian Apotek {$settings.nama_instansi}
+
Antrian Apotek {?=isset_or($settings.nama_instansi)?}
{if: isset($_GET['status']) && $_GET['status'] == '1'}
@@ -76,38 +104,56 @@
-
+
-
{$running_text} :: {loop: $jadwal} {$value.nm_dokter} [{$value.nm_poli}] - {$value.jam_mulai} s/d {$value.jam_selesai} WITA {/loop}
+ {?=isset_or($running_text)?} :: {loop: $jadwal} {?=isset_or($value.nm_dokter)?} [{?=isset_or($value.nm_poli)?}] - {?=isset_or($value.jam_mulai)?} s/d {?=isset_or($value.jam_selesai)?} WITA {/loop}
+
{if: isset($_GET['status']) && $_GET['status'] == '1'}
-
-
+ }
+
+
-
-
Antrian Laboratorium {$settings.nama_instansi}
-
-
-
-
-
-
-
- {loop: $display}
-
- {$value.no_reg} Nama: {$value.nm_pasien}, No. RM: {$value.no_rkm_medis} Status: {$value.stts}
-
- {/loop}
-
+
+
+ Antrian Laboratorium {$settings.nama_instansi}
+
-
-
{$running_text}
-
-
-
+
+
+
+
+
{$running_text}
+
-
+
+
+
+
-
diff --git a/plugins/anjungan/view/daftar.html b/plugins/anjungan/view/daftar.html
index 71b218bef..9124a8b16 100644
--- a/plugins/anjungan/view/daftar.html
+++ b/plugins/anjungan/view/daftar.html
@@ -5,7 +5,7 @@
-
diff --git a/plugins/anjungan/view/display.antrian.apotek.html b/plugins/anjungan/view/display.antrian.apotek.html
index f16112f55..e867312b6 100644
--- a/plugins/anjungan/view/display.antrian.apotek.html
+++ b/plugins/anjungan/view/display.antrian.apotek.html
@@ -3,11 +3,11 @@
@@ -55,11 +55,39 @@
padding-top: 0;
padding-bottom: 0;
}
+ .scroll-container {
+ height: 700px;
+ overflow: hidden;
+ position: relative;
+ }
+ .scroll-container ul {
+ position: absolute;
+ width: 100%;
+ animation: scrollUp 20s linear infinite;
+ }
+ @keyframes scrollUp {
+ 0% { top: 100%; }
+ 100% { top: -100%; }
+ }
+ .running-text-wrapper {
+ overflow: hidden;
+ white-space: nowrap;
+ width: 100%;
+ }
+ .running-text-track {
+ display: inline-block;
+ padding-left: 100%;
+ animation: ticker 25s linear infinite;
+ }
+ @keyframes ticker {
+ 0% { transform: translateX(0); }
+ 100% { transform: translateX(-100%); }
+ }