Keep learning forward
To be updated ...#TIL : Getting database from data range to array of enum objects
function getData(range, columnMap) { const dataValues = range.getValues(); const mapEntries = Object.entries(columnMap) return dataValues.map((row) => Object.fromEntries(mapEntries.map((colMap) => [colMap[0], row[colMap[1]]]))) }
Example:
You have a sheet with data from A1:D*** (*** is last row), with the field mappings :
- first col is product_code
- second col is total
- fourth col is rank1
So the code of main function is
function main(){ // Get the sheet you want var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0]; var lastRow = sheet.getLastRow(); var dataRange = sheet.getRange("A1:D" + (lastRow)); var data = getData(dataRange, { "product_code": 0, "total": 1, // skip third column "rank1": 3, }) console.log(data) }
#TIL : JS DOM event deepdive using SvelteJS demo
The DOM event handling go through 2 phrases : Capture and Bubble (with support of preventDefault, stopPropagation, stopImmediatePropagation methods to modify behavior)
Try the demo here : https://svelte.dev/repl/99d1edbf7bd9426384be2c7763d2e872?version=4.2.0
<script> const onClickEle = (e) => { console.log((new Date()).toISOString() + " : Clicked " + e.currentTarget.tagName) } </script> <h5>Default (bubble)</h5> <div on:click={onClickEle}> <button on:click={onClickEle}>Click here!</button> </div> <h5>Default (bubble with stopPropagation)</h5> <div on:click={onClickEle}> <button on:click|stopPropagation={onClickEle}>Click here!</button> </div> <h5>Parent capture</h5> <div on:click|capture={onClickEle}> <button on:click={onClickEle}>Click here!</button> </div> <h5>Parent capture stopPropagation</h5> <div on:click|capture|stopPropagation={onClickEle}> <button on:click={onClickEle}>Click here!</button> </div> <h5>Self (ele = event target)</h5> <div on:click|self={onClickEle}> <button on:click={onClickEle}>Click here!</button> </div> <style> div { padding: 10px 30px; background: gray; cursor: pointer; display: inline } </style>
#TIL : ES6 Module import in client browsers
moduleA.js
export const message = "Hello from moduleA!";
moduleB.js
import { message } from './moduleA.js'; console.log(message);
app.html
<script type="module" src="moduleB.js"></script> // Print console log "Hello from moduleA!"
So moduleB.js acts like a module entry-point, it loads all dependencies deeply to run it-self, so you only need add script tag point to it.
#TIL : SvelteJS : Reactivity via variable assignment
Reactivity in Svelte works via assignment of variable, so we have this as example
let todos = []; // Don't work todos.push({id: 1, name: 'cleaning'}) todos.push({id: 2, name: 'coding'}) // Work todos = [...todos, {id: 1, name: 'cleaning'}] // or this even work as tricky way todos.push({id: 1, name: 'cleaning'}) todos = todos
Why ? I don't know in internal system how it implement, but I think because Svelte works as a compiler, so it triggers the update when it see the assignment variable line.
Different with another frameworks, they work based on proxy object
#TIL : Array group by function in JS
Object.prototype.groupBy = function(cb) { const groupByCategory = this.reduce((group, item) => { const category = cb(item); group[category] = group[category] ?? []; group[category].push(item); return group; }, {}); return groupByCategory; } // Example const people = [{"name":"John","age":28},{"name":"Alice","age":24},{"name":"Michael","age":32},{"name":"Emily","age":29},{"name":"David","age":27},{"name":"Sophia","age":38}]; const by_first_age_period = people.groupBy((person) => Math.floor(person.age / 10) * 10 + '+'); console.log(by_first_age_period);
#TIL : CSS Selector element has an attribute which contains some string
[abc*="something"]
means select element has attributeabc
, and its value contains "something" stringExample :
/* Warning all admin link is red text color */ a[href*="/admin/"] { color: red; }
#TIL : HTTP Status Codes : 301+302 vs 307+308
- HTTP 307 : Temporary redirect, keep METHOD and BODY
- HTTP 308 : Permanent redirect, keep METHOD and BODY
vs
- HTTP 301 : Permanent redirect, fallback to GET method
- HTTP 302 : Temporary redirect, fallback to GET method
#TIL : Becareful on using port mapping of Docker in Development
Many examples, articles and repository use insecure config in port mapping Docker.
This is example in official document of Docker
https://docs.docker.com/engine/reference/run/#detached--d
$ docker run -d -p 80:80 my_image nginx -g 'daemon off;'
or
from 12.2k stars repo in Github
https://github.com/chatwoot/chatwoot/blob/develop/docker-compose.yaml#L89
ports: - '5432:5432'
This mapping config means you will proxy all traffic from the container port to the docker host (your dev machine) with binding
0.0.0.0
. It means if you don't have a firewall or accidently open all ports, your contaier data will be public in the Local Network (so danger if you use in public places like coffee shops)So remember to add the IP, which you wanted to bind in docker host (or have a network firewall ON)
ports: - '127.0.0.1:5432:5432' - '172.17.0.1:5432:5432'
#TIL : Using extra hosts to add custom ip of hostnames to Docker container
By default, all docker container using DNS server from docker host, so if you want to overwrite the specific hostnames ip address, try the flag
--add-host [hostname]:[ip]
$ docker run -it --add-host db1:1.2.3.4 --add-host db2:2.3.4.5 alpine cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters 1.2.3.4 db1 2.3.4.5 db2 172.17.0.2 0bdcf2fb2216
If you use docker-compose, then add the extra-hosts param below the service you want
extra_hosts: - "crm.dev:host-gateway" - "db1:1.2.3.4"
Bonus :
host-gateway
mean the ip address of the gateway of network container in, often be the docker host!#TIL : Laravel run scheduled command within parent environment
Last day I updated my Laravel app from version 8 to 9, all my scheduled command in Console Kernel has run in another environment like "production". Which causes my system didn't work as expected.
Example, I have 2-3 enviroment which I run cronjob like
* * * * * php artisan schedule:run --env=hello * * * * * php artisan schedule:run --env=world
But all sub scheduled commands has been run with "production" instead of "hello" or "world"
So this is my fix, I created a lambda function which pass current environment to the child command.
class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule) { // Added 2 lines to get lamda function $env = app()->environment(); $schedule_command = fn ($cmd) => $schedule->command($cmd, ['--env' => $env]); // Then schedule using new lambda function $schedule_command('check_product_stock')->everyTenMinutes(); $schedule_command('run_job_queue')->everyMinute(); $schedule_command('clean_old_data')->dailyAt('00:25'); } protected function commands() { $this->load(__DIR__ . '/Commands'); require base_path('routes/console.php'); } }
Enjoy ! :) Hope it works for you !
#TIL : Minimize Google PHP SDK before deploying on production
In case you use
google/apiclient
package to connect to Google API services, remember to cleaning unused services after deploying to production.So let me show you why
First, lets counting number of classes of Google API services inside composer autoload classmap file (Composer use this file as a mapping to lazyload your needed class)
$ cat vendor/composer/autoload_classmap.php | grep 'Google' | wc -l 15436 $ du -h vendor/composer/autoload_classmap.php 3.5M vendor/composer/autoload_classmap.php
As you can see, it has almost 15k classes having
Google
in class namespace. And yourautoload_classmap.php
has 3.5Mb in size (so large)So, let cleaning unused services (by keeping only services you need)
Editing your
composer.json
file by adding"pre-autoload-dump"
inscripts
and"google/apiclient-services"
inextra
, like below (replace services you need like Drive, Youtube, Sheets, etc..){ "scripts": { "pre-autoload-dump": "Google\\Task\\Composer::cleanup" }, "extra": { "google/apiclient-services": [ "Drive", "YouTube", "Sheets" ] } }
Finally, run
composer dump-autoload
again and see the result$ cat vendor/composer/autoload_classmap.php | grep 'Google' | wc -l 383 $ du -m vendor/composer/autoload_classmap.php 1.3M vendor/composer/autoload_classmap.php
Class map reduce from 15k to 400 lines, and size from 3.5M to 1.3M ! Such an optimize !
Beware : If you add services after cleaning, try to run
rm -r vendor/google/apiclient-services && composer update
to redownloading all services classes.#TIL : Getting back Whoops error pages in Laravel 9
Laravel 9 uses "Spatie/Ignition" as default error handling pages , which is beatiful but bloated within add-on service like Flare.
So if you want to get back the good-old "Whoops" error pages, this tutorial is for you :D
First, we remove "spatie/ignition" from composer packages
$ composer remove "spatie/laravel-ignition"
Then, add 3 lines in the
boot
function ofAppServiceProvider
use Illuminate\Contracts\Foundation\ExceptionRenderer; use Illuminate\Foundation\Exceptions\Whoops\WhoopsExceptionRenderer; class AppServiceProvider extends ServiceProvider { public function boot() { // Add 3 lines below between boot function if (config('app.debug')) { $this->app->bind(ExceptionRenderer::class, WhoopsExceptionRenderer::class); } } }
And last but not least, open the
config/app.php
then add to this line to array config (try to replace the name of Editor you use in this list )... 'editor' => 'vscode' ...
Now try to add this line to second line in
web.php
route file to test$x = 1 / 0;
#TIL : DirectAdmin change document root to public directory
By default, the document root of a domain in DirectAdmin (DA) has the format
/home/[your_username]/domains/[your_domain]/public_html
But in case you have to run modern PHP app like Laravel or Symfony framework (they use child public directory as their docroot). In some case, using .htaccess file to rewrite is a hacky way to solve problem (or this is the option when you don't have Admin account of DA)
So, if you have an Admin account, try this
Open menu Server Manager > Custom HTTPD Configurations, then click on your domain httpd config file, then click CUSTOMIZE
In the large textbox, add this line (remind to replace username and domain) to
|?DOCROOT=/home/[your_username]/domains/[your_domain]/public_html/public|
Click SAVE to save modification. Then go to Service Monitor and reload your webservers (Nginx, Apache or OpenLiteSpeed)
#TIL : Insert fastly current date time to cell in Google Spreadsheets
Yo! Back to my #TIL
Today, I find out a way to insert fastly current date or current time to a cell. It makes you faster 3-5 seconds and reduce confusing about what the time is?
- To insert current date : press
Ctrl + :
- To insert current time : press
Ctrl + Shift + :
Excel (Spreadsheets) never dies !
- To insert current date : press
#TIL : JSON Parse is faster than Javascript Object declaration
TLDR;
Use
JSON.parse("[your data in json string]")
if your data is big (>10KB for instance)Short Explaination :
JSON.parse
parses a string to object, so it has many strict requirements than Javascript parses the source code (more syntax, more context)Long Explaination :
#TIL : Use NGINX as a TCP,UDP load balancer
NGINX is well known as a simple and good web server right now, but not everyone knows that NGINX can act like a TCP-UDP loadbalancer. So you won't need to install HAProxy when you need a LoadBalancer.
This feature is released on NGINX 1.9+. So you can setup it by this rule
stream { upstream backend1 { server s1.backend1.com:12345; server s2.backend1.com:12345; } server { listen 54321; proxy_pass backend1; } upstream backend2 { server s1.backend2.com:7777; server s2.backend2.com:7777; server s3.backend2.com:7777; } server { listen 8888 udp; # add udp keyword if you want UDP server proxy_pass backend2; } }
To learn more, click here : https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-udp-load-balancer/
#TIL : Improve apt package manager speed by changing source list mirrors
Sometimes, you ran into issues that apt update package list so slow or even can not connect to the destination server.
You can change the default list into new one near your country. Get example : if you live in ASIA, choose the Singapore or Japan mirror instead of main mirror. you just change the "archive.ubuntu.com" and "security.ubuntu.com" to "[country_code].archive.ubuntu.com" in file list
/etc/apt/sources.list
Then run
sudo apt update
to test your new mirror :)TIP : you can generate the new source list file based on this online tool : https://repogen.simplylinux.ch/generate.php
#TIL : Run container processes as non-root user
As default, docker runs your container as root user (uid=0). Although docker isolates your filesystem to protect docker host, but running processes as root is redundant and increasing attacking surface. Even it can make its written files having root owner, which can mess your docker-host filesystem permission.
This is example that show docker runs as root
$ docker run --rm alpine sleep 30
and open another terminal to check this
$ ps xau | grep 'sleep' khanhic+ 15552 0.5 0.4 1264452 49280 pts/1 Sl+ 17:37 0:00 docker run --rm alpine:3.9 sleep 30 root 15610 0.6 0.0 1520 4 ? Ss 17:37 0:00 sleep 30 khanhic+ 15876 0.0 0.0 23076 1024 pts/2 S+ 17:37 0:00 grep --color=auto sleep
You can see that the process sleep 30 is running as root with pid = 15610
To control which user docker container runs as, you can use the
--user [userid]:[groupid]
argumentExample
$ docker run --rm --user 1000:1000 alpine sleep 30
Then you will get this result
$ ps xau | grep 'sleep' khanhic+ 16275 2.0 0.4 1411916 50124 pts/1 Sl+ 17:41 0:00 docker run --rm --user 1000:1000 alpine:3.9 sleep 30 khanhic+ 16336 1.5 0.0 1520 4 ? Ss 17:41 0:00 sleep 30 khanhic+ 16403 0.0 0.0 23076 984 pts/2 S+ 17:41 0:00 grep --color=auto sleep
TIP : you can set a environment variable by add this line to ~/.bash_profile or ~/.bashrc
export DOCKER_UID="$(id -u ${USER}):$(id -g ${USER})"`
then use docker command like
docker run --user $DOCKER_UID ....
#TIL : Can not get real IP address from Load Balancer SSL Passthrough
When you use a load balancer stay in front of your app, and use SSL Passthrough mode. You will never get real IP of client, because Load balancer works like a TCP load balancer, which means it can not add extra HTTP headers into encrypted traffic from client when it doesn't handle SSL termination.
So if you use 1 domain or wildcard subdomains, it's better if you use SSL Termination mode.
#TIL : Export MySQL data by specified query to CSV file
To export data from MySQL by specified query to CSV file, you can use this command
$ mysql -B -u username -p database_name -h dbhost -e "SELECT * FROM table_name;" | sed "s/'/\'/;s/\t/\",\"/g;s/^/\"/;s/$/\"/;s/\n//g"
Tip from : https://stackoverflow.com/a/25427665
#TIL : Run shell command in all hosts
To run a shell command in all hosts, you can use the module name
raw
and provide shell command to module args.Example:
- To list all CPU model name of hosts
$ ansible all -m raw -a "cat /proc/cpuinfo | grep 'model name'"
#TIL : Can not run downloaded binary inside alpine linux because of missing shared libs
Alpine linux becomes the most base image for docker images because it's lightweight and handful package manager apk. Sometimes, you create an image that downloads the binary file but can not execute it. It shows something like this:
/entrypoint.sh: line ***: [your binary]: not found
The problem is your binary built within shared libraries, so it can't run without shared libraries dependencies. To findout which libraries is missing, use this
$ ldd [your binary path]
This is sample result
/usr/local/bin # ldd hugo /lib64/ld-linux-x86-64.so.2 (0x7fa852f2a000) libpthread.so.0 => /lib64/ld-linux-x86-64.so.2 (0x7fa852f2a000) Error loading shared library libstdc++.so.6: No such file or directory (needed by hugo) libdl.so.2 => /lib64/ld-linux-x86-64.so.2 (0x7fa852f2a000) libm.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7fa852f2a000) Error loading shared library libgcc_s.so.1: No such file or directory (needed by hugo) libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x7fa852f2a000)
So we need to install
libstdc++
andlibc6-compat
before run the binaryRUN apk add --no-cache libstdc++ libc6-compat
#TIL : Critical notice of string DOM manipulation using jQuery
Sometimes you want to manipulate the HTML DOM elements inside as a string, then you found a lot of HTML parser or DOM library from the Internet (but it supports only NodeJS). How to do it in a browser ?
The answer is "jQuery is your best friend in browser environment" :)
Then you try this :
const content = jQuery('<p><strong>Hello</strong></p><p>from</p><p><strong>KhanhIceTea</strong></p>'); content.find('p > strong').each(function(i, ele) { $(ele).css('color', 'red'); }); console.log(content.html());
What you expected
<p><strong style="color: red;">Hello</strong></p><p>from</p><p><strong style="color: red;">KhanhIceTea</strong></p>
But the console print
<strong>Hello</strong>
SURPRISE !?? JQUERY SUCKS ?
Nope ! The reason is simple, DOM data structure is a tree. And, any tree has a root, right ??? Now you understand the problem ? Then we fix it below
const html_content = '<p><strong>Hello</strong></p><p>from</p><p><strong>KhanhIceTea</strong></p>'; // Wrap all elements into one root element (div) const content = jQuery('<div />').append(html_content); // or const content = jQuery('<div>' + html_content + '</div>'); content.find('p > strong').each(function(i, ele) { $(ele).css('color', 'red'); }); console.log(content.html());
#TIL : SSH to docker host in Docker for Mac
When you need to debug the docker host of your docker server inside macOS. You can connect to its tty screen by
$ screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty
Then type
ENTER
to enter the screen and start debugging docker host.To disconnect the screen, press "Ctrl + A > Ctrl + " and confirm "yes".
#TIL : View function source in developer tool console
If you are in console of developer tool and you want to know what the function does, you can view its source by:
- call
.toSource()
of variable or function name in Firefox
Example :
>> function hello(name) { return "Hello " + name; } >> hello.toSource(); <- "function hello(name) { return \"Hello \" + name; }"
- click 'show function definition' in Chrome
- call
#TIL : Make cron job runs every seconds
Cron is a UNIX service that helps user run command periodically. And crontab is simple tool to setup cron job as user, just type in the command
crontab -e
then setup your cron schedule.Btw, sometimes you want to run the cronjob every seconds (5, 10 or 20 seconds), but crontab only support every minute. How to achieve your goal without using another tool ?
I got an idea that we can use the
sleep
command to make it done. So this is solution.This below is crontab rule that run command every 10 seconds.
* * * * * [command] * * * * * sleep 10 && [command] * * * * * sleep 20 && [command] * * * * * sleep 30 && [command] * * * * * sleep 40 && [command] * * * * * sleep 50 && [command]
It's simple, right ? ;)
#TIL : Telnet server through SSL or TLS
I often use
telnet
andnetcat
to debug my TCP server and client. But these tool only support plain connection, mean every data transfer between server and client is unencrypted and unsafe.So if you want to achieve the same result through secure connection (SSL or TLS), use this command
$ openssl s_client -host example.com -port 443 $ # or short syntax $ openssl s_client -connect example.com:443
I made a function named
telnets
in.bash_profile
to make it easier to usefunction telnets() { openssl s_client -host "$1" -port "$2" }
then I just type this on bash shell within same syntax of
telnet
$ telnets github.com 443
TIP: To hide detail of certificates, add
-quiet
flag into commandMore info, check
openssl s_client -h
#TIL : [Bug] Input lost focus after typing 1 character in Safari Browser
Today my team met a weird bug on Safari browsers (on all Apple devices), that input losts its focus after you type first character.
First, we thought it's javascript bug so we wasted a lot of time for debugging the behaviour of input. But nothing works !
So we continued searching on Google, then we found this wonderful answer
The root issues caused by one CSS overrided
-webkit-user-select
tonone
, so we have to prevent it by add this line to end of CSS fileinput, input:before, input:after { -webkit-user-select: initial !important; -khtml-user-select: initial !important; -moz-user-select: initial !important; -ms-user-select: initial !important; user-select: initial !important; }
Hope it helpful for you next time ! :D
#TIL : View all parameters passed to callback function without reading docs
Time before, I often meet the situation that I forgot the parameters pass to a function (so I have to searching the API docs to read via Google). This progress can take you huge time if it repeats many times.
So I think one way to debug the parameters without reading API docs, that is pass
console.log
as a callback function parameter.Examples :
[3,9,27].forEach(console.log); // So you will get // 3 0 // 9 1 // 27 2 // Then you know , first parameter is item, second parameter is indexed key
jQuery.ajax('/data.json').done(console.log).fail(console.error);
#TIL : Detect HTTP Response 304 Status Code in AJAX
Sometimes, you have a interval timer to retrieve new data updates from AJAX requests. But even the response status code of response is 304 (no body), the browser will treat it as 200 response and copy the cached body from browser cache to response body. So it will trigger the re-rederning UI components.
The way we can detect it is via its response headers list.
This example is using
fetch
API (which is supported in major browsers nowaday)fetch('https://some.thing/has/data') .then(function (res) { if (res.headers.get('status') == '304 Not Modified') { return null; } return res.json(); }) .then(function (data) { if (data == null) return; // Render your data below renderUI(data); });