~/blog
~/blog$ render faiz.blog.playing-with-css-(and-more):-re-re-re-re-revamping-the-notebook

Playing with CSS (and more): re-re-re-re-revamping the notebook

Saturday, 18 January 2025 23:09:52 WIB | tags: web, css, cicd, gdrive | 76 hits | 0 comment(s)

Holiday was just right at the door, I had a pile of unread books and tons of unplayed games on Steam. What did I do instead? Correct, revamping the notebook [re: nickname of this blog] for the I-lost-my-count times 😎. So the reason for another revamping is because I felt the design was too mundane (which I intended to make, since I felt the even previous version was too decorated haha) and didn’t show my true character. So I spent days searching for a true self unique personal website design, but online references mostly only have the classic portfolio layout where it has a big picture of the site owner as the landing page (like how this notebook used to be a few years back) and the rest of the page are standard website layout. 

But then, I remembered I saw a fully functioning Windows XP-themed website (too bad I can’t remember the website) and thought, hmm, my old friends know me as the computer expert (even I'm not that expert, but they ask for my help when their Instagram accounts are hacked!), I have a Computer Science degree, and I work in the IT field with Macbook as the daily driver. Probably a Macbook and terminal-themed design suits me well! Thus, I added exploring the CSS to mimic the terminal UI as one of the action items of the ongoing notebook refactor project. The project itself already started around July last year when I decided to be actively writing again, and apart of revamping the design, I also configured the CI pipeline and created a bash script to do a daily backup to Google Drive.

Warning: I'll be babbling nonsense story about my notebook in the first section of this note. If you’re here only to see the technical story, you can jump here.

Contents

Blogging Story

Macbook OSX + Terminal UI

CI Pipeline

Daily DB Backup to GDrive

Closing

Blogging Story

So my blogging story started when I was still in elementary school almost two decades ago. I wrote a brief history about it on this note (in Indonesian). On the note, I called this first notebook version as v0 notebook. The platform I used: wordpress.com. Later, the v1 notebook was released around 2008 utilising a free hosting from 000webhost, with a free domain from co.cc, and built using a free web-builder feature. The address was faizrachiemy.co.cc and if you dig back to the older notes, you may find the references to this v1 notebook. I posted 2009 Jakarta Bombings news in my notebook, gathering the info and pics from Twitter and Kaskus. Apparently this article brings significant visitors to the site, probably because online news sites were still difficult to find back then?

I found wordpress.org installation from the 000webhost cPanel and due to the familiarity from the v0 experience, I fell in love with it. Named this iteration as the v2 notebook, this was the golden era of my blogging life. Wrote actively, got tens of thousands of visitors, and fought spams tirelessly because I didn’t know how to configure the Akismet back then. I even customised the layout and content, used free custom theme and added Sundanese Unicode converter module. But, an era always came to end, and Joomla was the successor of the mighty wordpress.org.

The v3 notebook existed because I found Joomla gave more room for customisation. AFAIR, it was because I could freely create a new section for either plain text or HTML content, whereas in Wordpress, this requires editing the theme files directly on the codes. This happened around (or just before) my first year of college, which then led us to the v4 notebook. Based on the note's created date, the v4 notebook was created around mid-2015 and was completely different from the previous versions because it was built from scratch and not using any CMS and used plain PHP instead. 

Just like I said in the first paragraph, I used to have a website with a big owner’s picture on the landing page. I used this version as an online portfolio, with the blocks layout where it shows the thumbnail, title, and category. Then, I revamped the site to a blueish color, similar to the layout of this Virtual Password website (I used my notebook’s CSS styles for my college’s final project) and has a custom “Faiz” icon for the Faiz page, just like this latest version of notebook’s Faiz page, but an older one (and with no sunglasses). I then played with CSS to create a book-like website with a fully working page-opening animation, which to this date is still my favorite design, GIF attached above is the preserved site. I might use this design for some project in the future. Later, around mid-2020 I re-revamped the site to be more professional since I intended to use this as an online CV (but it still has the big owner’s picture, see the picture below for reference. The image was not a screenshot of the actual site, it was my old concept on a PowerPoint file).

Reaching the third quarter of 2021, I got a task to continue the development of a Flask-based website at work. To learn about the Flask, I rewrote my notebook’s code in Python and most importantly, I didn’t forget to revamp the layout (haha). This Flask-based notebook is called v5 notebook, and the first iteration has a dark UI (picture below is the concept). I also had a financial management module on this version where I store all of my income & outcome, where it was previously stored on a Google spreadsheet but then moved this module back into the spreadsheet for a more flexible management.

Somehow the old me wanted a less decorated notebook, and then v5.1.0 was released just like the image above, but without any new note post created for 4 straight years. Until mid last year, when I suddenly had the urge to write again. Around that time, I also bought a Fluent Python 2nd Edition book, which became the reason I rewrote my 3 year-old Python codes to be more Pythonic while also following Flask’s best practices for both project structure and implementation. I later added the theme toggler, and finally on the 22 December (last month) I revamped the site once again to be a Macbook & terminal look-alike, call it the v5.2.0.

Macbook OSX + Terminal UI

To follow the Macbook OS X desktop UI as the base of the notebook, I separated the HTML layout into 4 different main sections: main navbar, desktop icons, terminal’s titlebar, and the terminal itself. The reason why terminal’s titlebar being separated with the terminal body is because I want to maintain the main scrollbar position, thus, if the page is refreshed the scrollbar won’t be back to top. This cannot be achieved by simple CSS if the terminal titlebar & body being in one single HTML block. Either when the user scrolls the terminal will be scrolled as well, or the problem I stated earlier where the page will be back to top if the page is refreshed, because the terminal body will have its own scrollbar. The layout skeleton looks like this:

Three of the main sections have “fixed position”, or in CSS it has position: fixed attribute, while the terminal don’t, so it is bound to the main scrollbar. The actual CSS codes that made this happen looks like CSS blocks on each sub-sections below (non-related attributes are hidden for simplicity, you can inspect my notebook’s element if you want to see).

Main Navbar

In the main navbar, I put my logo and links to the two main pages. I also put the active user on the center of the navbar ([email protected]), where if I logged in, the username will change so it shows my username. This gimmick helps me identify whether I already logged in or not, because, if I visit any notes as a guest, it will add the visit count by one and I want to keep that as an organic counter as possible. Furthermore, I added the clock on the rightern most of the navbar to give an OS feel.

HTML


  <div class="container-fluid row">
  <div id="window-main-titlebar">
      <div class="w-100 justify-content-between align-items-center d-none d-md-flex">
          <nav class="row">
              <div class="col">
                  <a href="{{ url_for('router_blog.index') }}" class="nav-link nav-item"></a><img id="window-main-logo" src="{{ url_for('static', filename='images/Breaking_Ring_Transparent.png') }}" alt="Rahiemy Logo"></a>
              </div>
              <div class="col">
                  <a href="{{ url_for('router_blog.index') }}" class="nav-link nav-item">Notes</a>
              </div>
              <div class="col">
                  <a href="{{ url_for('router_page.page_faiz') }}" class="nav-link nav-item">Faiz?</a>
              </div>
              {% if current_user is not none %}
                  [ priviledge user only contents :p ]
              {% endif %}
          </nav>
          <span class="text-center">{% if current_user is not none %}{{ current_user.username }}{% else %}guest{% endif %}@faiz.rahiemy.id</span>
          <span id="window-main-time">Date/Time</span>
      </div>
      <div class="w-100 justify-content-between align-items-center d-flex d-md-none">
          <span class="text-center">{% if current_user is not none %}{{ current_user.username }}{% else %}guest{% endif %}@faiz.rahiemy.id</span>
          <a class="nav-link nav-item" data-bs-toggle="offcanvas" data-bs-target="#mobile-navbar" aria-controls="mobile-navbar"><img id="window-main-logo" src="{{ url_for('static', filename='images/Breaking_Ring_Transparent.png') }}" alt="Rahiemy Logo"></a>
      </div>
  </div>
</div>
...

CSS

#window-main-titlebar {
    position: fixed;
    top: 0em;
    ....
    z-index: 1;
}

#window-main-logo {
    height: 1.25em;
    width: auto;

    z-index: 2;
}

Time Clock (using local time)

        const dateOptions = { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric' };
        var updateDate = function() {
            var currentDate = new Date();
            $('#window-main-time').html(currentDate.toLocaleDateString('en-GB', dateOptions) + ' ' + currentDate.toLocaleTimeString('en-GB'));
            setTimeout(updateDate, 1000);
        }

Desktop

I admit, the desktop doesn’t really like a desktop (you may not realise it is mimicking a desktop if I don’t explain it here won’t you!?). The large logo + searchbar is meant as a desktop widget, where the remaining icons represent desktop icons.

CSS

#desktop {
    position: fixed;
    top: 1em;

    padding: 1.5em;
}

.desktop-logo {
    width: 100%;
}

.desktop-widget {
    padding: 1em;
    
    background-color: var(--fr-brown-even-darker);
    border-radius: 2em;
}

.desktop-widget a:hover {
    color: var(--fr-brown-darker)
}

Terminal TitleBar

As I said before, this titlebar being separated is only meant to prevent scrolling issue. I added some gimmick on the buttons, but no further interesting details in this part.

HTML


  <div id="window-blog-titlebar-container" class="container-fluid row">
    <div id="window-blog-titlebar-container-background" class="col col-md-9"></div>
    <div id="window-blog-titlebar" class="text-center col col-md-9">
        <div id="window-blog-titlebar-buttons-container">
            <div class="window-titlebar-buttons" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="Of course these buttons won't close the window :)">
                <div></div>
                <div></div>
                <div></div>
            </div>
        </div>
        ~/{{ base_data.controller }}
    </div>
  </div>

CSS

.window-titlebar-buttons {
    position: absolute;
    top: .25em;
    left: 0em;

    display: flex;
    flex-direction: row;
    gap: .5em;
    z-index: 3;
}

...

#window-blog-titlebar-container {
    position: relative;
}

#window-blog-titlebar-container-background {
    position: fixed;
    top: 1.5em;
    right: 0;
    height: 1em;

    background-color: var(--bs-body-bg);
    z-index: 1;
}

#window-blog-titlebar {
    position: fixed;
    top: 1.5em;
    right: 0;
    
    background-image: linear-gradient(var(--fr-window-titlebar-bg-main), var(--fr-window-titlebar-bg-secondary));
    border-top: .15em solid var(--fr-brown-dark);
    border-left: .15em solid var(--fr-brown-dark);
    border-right: .15em solid var(--fr-brown-dark);
    border-bottom: .15em solid var(--fr-window-light-accent);
    border-top-left-radius: .5em;
    border-top-right-radius: .5em;
    z-index: 2;
}

#window-blog-titlebar-buttons-container {
    position: relative;
}

Terminal Body

The main section of the layout, apart from the monospace font use, I also added several gimmicks to make it more terminal-alike (I tried as hard as I could!). Firstly, I put the working directory and its “command to render” for each item on the terminal. This includes every note posts, most visited note, tags, shortcuts, and footer. Secondly, I put user status (a gimmick for myself), similar to the username on the center of the navbar, the $ will change to # if a sudoers [re: me] is logged in. Lastly, I added a blinking cursor at the very end of the terminal body, mimicking the actual terminal. Thanks to Alan Dunning for the CSS reference of blinking cursor.

HTML - Terminal


  <div class="container-fluid row">
    <div id="window-blog" class="col col-md-9">
        <div class="row pb-3">
            <div class="col-sm-12 col-md-6">
                <span class="text-darker">~/blog{% if current_user is not none %}#{% else %}${% endif %}</span> render <span class="text-dark">faiz.blog.x</span>
                [ blog component here ]
            </div>
        </div>
    </div>
  </div>

CSS - Terminal

#window-blog {
    position: absolute;
    top: 3em;
    right: 0;

    color: var(--fr-brown-dark);
    background-color: var(--bs-body-bg-secondary);
    border-left: .15em solid var(--fr-brown-dark);
    border-right: .15em solid var(--fr-brown-dark);
    border-bottom: .15em solid var(--fr-brown-dark);
    border-bottom-left-radius: .5em;
    border-bottom-right-radius: .5em;
}

HTML - Blinking Cursor

The blinking cursor will be put on the end of every active-terminal span.


  <footer class="row">
      <div class="col">
          <span class="active-terminal">
              <span class="text-darker">~/blog{% if current_user is not none %}#{% else %}${% endif %}</span> Faiz Rahiemy's Notebook since 2009 until now
          </span>
      </div>
  </footer>

CSS - Blinking Cursor

.active-terminal {
    display: inline;
    position: relative;
}

.active-terminal::after {
    display: inline;
    position: absolute;
    right: -1em;
    width: .5em;
    height: 1.5em;

    content: "";
    background-color: #fff;
    vertical-align: top;
    -webkit-animation: blink 1s step-end infinite;
    animation: blink 1s step-end infinite;
  }
  
@-webkit-keyframes blink {
    0% { opacity: 1.0; }
    50% { opacity: 0.0; }
    100% { opacity: 1.0; }
}

@keyframes blink {
    0% { opacity: 1.0; }
    50% { opacity: 0.0; }
    100% { opacity: 1.0; }
}

Mobile UI

For now, I’m keeping the same terminal look-alike UI on mobile, and classic mobile-sidebar menu if the top-right logo is clicked. Initially, I planned to use Android UI as this mobile-sidebar design instead of the classic one. But I’m still exploring the best options. I may update this section if I finally made up my mind.

CI Pipeline

On this refactor project, apart from just rewriting the code, I also migrated from old shared-hosting in Domainesia to a dedicated compute engine in Vultr. To make my life easier, I added a CI on my private GitLab repository, where it will pull the changes from the main branch and directly update the code on my server. Preventing the need for me to re-allowing my current IP for SSH port to the server, SSH-ing to the server, manually pasting the changes and restarting the service. Hassle-free. However, this is not without a drawback. This means I added a new attack surface to my server, where, if I don’t careful, an attacker can execute a remote command through the GitLab runner if somehow they can obtain access to it. Regardless, I am also planning to use Kubernetes for my services and separating runner, service, and DB, but this is still on the low-priority backlog. In the CI file, the only: main part is configuring that only changes on the main branch will trigger the pipeline. While, the when: manual is configuring the pipeline to be only manually triggered by me. If there is no "when: manual" line, the pipeline will start automatically and I don't want that.

stages:
  - pull_changes

pull_latest_changes:
  stage: pull_changes
  before_script:
    - cd [private path]
    - git remote set-url origin https://[git user]:[email protected]/[git group]/[git repo].git
  script:
    - sudo git pull origin main
    - sudo systemctl restart [service name]
  only:
    - main
  tags:
    - [gitlab runner tag]
  when: manual

In my case, since the runner is running on the same host as the webserver, I used the shell executor type instead of docker, and put the commands on the before_script and script block of the CI file. See this documentation for more detailed executor runner type.

...
[[runners]]
  name = "shell executor runner"
  executor = "shell"
...

You may want to shout at me "Faiz! Are you crazy!? Giving a sudoers privilege to the runner user!?". Calm down, I am a least privilege believer. For now, I only allow specific commands to this gitlab-runner user. By running the sudo visudo I added an additional line to the /etc/sudoers file:

...
gitlab-runner ALL=(ALL) NOPASSWD: /usr/bin/git pull origin main, /bin/systemctl restart [service name].service
...

By adding this line, I only grant the gitlab-runner user a sudo access for git pull origin main and systemctl restart [service name] command. Executing other sudo command will resulting to password will be asked to the user, and since I don't set any password for gitlab-runner user (for security reason), it will returning permission denied.

Daily DB Backup to GDrive

While previously on the cPanel I can configure a periodic backup for my DB, this dedicated compute engine gave me an opportunity to learn how to write the code myself. Since it is a simple task, I used a simple bash script for this task. The script will dump the database to a single SQL file, then using Google’s oauth2l, the script will fetch the access token of a pre-granted service account, and then it will upload the SQL file using GDrive API.

Initially, I used files.create API, which works as expected on the first try. However, on the second try, it will create a new file, instead of updating the existing file even if the name and directory of the file is identical. Then, I use the files.update API, supplying the existing file’s ID, then voila. My DB backup is uploaded to the GDrive, in my pre-defined directory. The steps:

  1. Directory on the Google Drive

    1. Create a specific directory on the Google Drive

    2. Access the directory

    3. Obtain the directory ID: https://drive.google.com/drive/u/0/folders/[this is the directory ID]

  2. Google Service Account

    1. Has a Google Cloud project

    2. Enable Google Drive API

    3. Create a service account from Credentials menu, no permission are required

    4. Create and download the .json key

    5. Grant Editor access of the Google Drive Directory to the service account email, this can be done by “Sharing” access of the directory on the Google Drive website

  3. oauth2l

    1. Download and get the oauth2l binary ready

  4. Prepare the script

  5. Configure the cron

The script for the files.create API is:

#!/bin/bash

# Variables
FILE_PATH="[private path]/[sql file name].sql"
FILE_NAME="[sql file name].sql"
DRIVE_FOLDER_ID="[Google Drive Folder ID]"
SERVICE_ACCOUNT_KEY="[private path]/service_account.json"
SCOPE="https://www.googleapis.com/auth/drive"

# Run mysqldump command
mysqldump -u [mysql user name] [DB name] > [private path]/[sql file name].sql

# Get an access token
ACCESS_TOKEN=$([private path]/oauth2l fetch --credentials=$SERVICE_ACCOUNT_KEY --scope=$SCOPE)

# Check if token retrieval was successful
if [ -z "$ACCESS_TOKEN" ]; then
  echo "Failed to retrieve access token"
  exit 1
fi

# Upload file to Google Drive
curl -X POST -L \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -F "metadata={name :'$FILE_NAME', parents:['$DRIVE_FOLDER_ID']};type=application/json;charset=UTF-8" \
    -F "file=@$FILE_PATH;type=application/octet-stream" \
    "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"

echo "File uploaded successfully."

The files.create will returning the file ID, which will be mandatory on files.update API. The files.update API, the code is as follow:

#!/bin/bash

# Variables
FILE_PATH="[private path]/[sql file name].sql"
FILE_NAME="[sql file name].sql"
DRIVE_FILE_ID="[Google Drive File ID]"
SERVICE_ACCOUNT_KEY="[private path]/service_account.json"
SCOPE="https://www.googleapis.com/auth/drive"

# Run mysqldump command
mysqldump -u [mysql user name] [DB name] > [private path]/[sql file name].sql

# Get an access token
ACCESS_TOKEN=$([private path]/oauth2l fetch --credentials=$SERVICE_ACCOUNT_KEY --scope=$SCOPE)

# Check if token retrieval was successful
if [ -z "$ACCESS_TOKEN" ]; then
  echo "Failed to retrieve access token"
  exit 1
fi

# Upload file to Google Drive
curl -X POST -L \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -F "metadata={name :'$FILE_NAME']};type=application/json;charset=UTF-8" \
    -F "file=@$FILE_PATH;type=application/octet-stream" \
    "https://www.googleapis.com/upload/drive/v3/files/$DRIVE_FILE_ID?uploadType=multipart"

echo "File uploaded successfully."

The files.create API won't be necessary if you know your file's ID https://drive.google.com/file/d/[File ID]/view

I configure the cron to run at 5 PM (12 AM GMT+7) daily, so it is:

# SQL Backuper
0 17    * * *   [cron user]    sh [private path]/fr_dailysql_backuper.sh

Closing

So that’s how I revamped the notebook for I-lost-the-count times. Deep in my heart, I really want to create an open source CMS project based on these codes, this idea has been living freely in my head for the past years now. But at least, before that happens, you can create your own terminal website, auto-update pipeline, and DB backup by following the steps above. Cheers!

Comments

Be the first to comment!

Give Comments









* required fields

Sending comment...

~/blog$ shortcuts: > Notes and > Faiz?