Project structure:

extension_SSSSS/
├── css/
│   └── styles.css
├── icons/
│   ├── icon128.png
│   ├── icon16.png
│   ├── icon32.png
│   └── icon48.png
├── images/
│   ├── icons/
│   │   ├── DarkVPN_icon_bars2.png
│   │   └── DarkVPN_icon_house3.png
│   └── DarkVPN_LOGO.png
├── js/
│   ├── background.js
│   ├── jquery-3.7.1.min.js
│   ├── menu.js
│   └── popup.js
├── GenerateProjectShort.py
├── ProjectOneFile.txt
├── manifest.json
└── popup.html


Project files:

manifest.json
```json
{
  "manifest_version": 2,
  "name": "DarkVPN",
  "version": "1.0",
  "description": "Fastest DarkVPN",
  "permissions": [
    "proxy",
    "storage",
    "webRequest",
    "webRequestBlocking"
  ],
  "browser_action": {
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "32": "icons/icon32.png",
      "48": "icons/icon48.png",
      "128": "icons/icon128.png"
    }
  },
  "icons": {
    "16": "icons/icon16.png",
    "32": "icons/icon32.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "background": {
    "service_worker": "js/background.js"
  }
}
```


popup.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>DarkVPN</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="css/styles.css">
</head>
<body>
  <div class="container">
    <div class="dark-vpn-title">DarkVPN</div>
    <div class="version-text">V-0.6.1</div>
    <div class="toggle-container">
      <button id="toggleButton">
        <img src="images/DarkVPN_LOGO.png" class="button-logo">
        <div class="b-title">Connect</div>
      </button>
    </div>
    <div class="menu-container">
      <div class="setting-bar" id="autoConnectSetting">
        <div class="setting-tile">Автоподключение</div>
        <div class="setting-switcher">
          <div class="switcher-ball"></div>
        </div>
      </div>
      <div class="setting-bar" id="adBlockSetting">
        <div class="setting-tile">Блокировка рекламы</div>
        <div class="setting-switcher">
          <div class="switcher-ball"></div>
        </div>
      </div>
    </div>
    <div class="info-container">
      <div class="buttons-row">
        <button class="control-btn" id="home-btn">
          <img src="images/icons/DarkVPN_icon_house3.png" class="control-btn-icon">
          <img src="images/icons/DarkVPN_icon_house3.png" class="control-btn-icon control-btn-glow">
        </button>
        <button class="control-btn" id="menu-btn">
          <img src="images/icons/DarkVPN_icon_bars2.png" class="control-btn-icon">
          <img src="images/icons/DarkVPN_icon_bars2.png" class="control-btn-icon control-btn-glow">
        </button>
      </div>
    </div>
  </div>
  <div class="gradient-point"></div>
  <script src="js/jquery-3.7.1.min.js"></script>
  <script src="js/popup.js"></script>
  <script src="js/menu.js"></script>
</body>
</html>
```


css.styles.css
```css
:root {
  --violet-color: #6e41e1
}

body {
  width: 320px;
  height: 500px;
  margin: 0;
  font-family: 'Roboto', sans-serif;
  background-color: #1e1e1f;
  border: none;
  color: #ffffff;
}

.container {
  display: flex;
  flex-direction: column;
  height: 100%;
  justify-content: space-between;
}

.dark-vpn-title {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 32px;
  font-family: "Ubuntu", sans-serif;
  font-weight: 500;
  font-size: 40px;
  color: #6a6a6a;
  padding: 1px 10px;
  /* background-color: #ffffff1f; */
  /* border-radius: 10px; */
  transition: opacity 0.2s;
}
.version-text {
  position: absolute;
  top: 10px;
  right: 10px;
  font-family: 'Roboto', sans-serif;
  color: rgb(255 255 255 / 15%);
  font-size: 10px;
}

.toggle-container {
  display: flex;
  justify-content: center;
  z-index: 2;
  height: 100%;
  align-items: center;
  transition: height 0.3s ease;
}

#toggleButton {
  display: flex;
  height: 150px;
  width: 150px;
  border-radius: 50%;
  font-size: 18px;
  font-weight: 500;
  color: #ffffff;
  background-color: #333333;
  border: none;
  cursor: pointer;
  position: relative;
  align-items: center;
  justify-content: center;
  transition: all 0.3s ease;
}

#toggleButton::before {
  content: '';
  position: absolute;
  width: 150%;
  height: 150%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.07) 0%, rgba(255,255,255,0) 70%);
  transition: all 0.3s ease;
  transform: scale(1);
  opacity: 1;
}

#toggleButton.connected::before {
  background: radial-gradient(circle, rgba(138, 43, 226, 0.2) 0%, rgb(77 46 205 / 20%) 50%, rgba(138, 43, 226, 0) 70%);
  animation: pulse 2s infinite;
}

#toggleButton:hover {
  transform: scale(1.05);
}

#toggleButton:active {
  transition: all 0.1s;
  transform: scale(0.95);
}

@keyframes pulse {
  0% {
    transform: scale(1);
    opacity: 1;
  }
  50% {
    transform: scale(1.1);
    opacity: 0.5;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.button-logo {
  width: 50%;
  height: 50%;
  transition: opacity 0.3s ease;
}

.menu-container {
  display: flex;
  padding: 0  20px 0 20px;
  position: absolute;
  z-index: 5;
  height: 350px;
  /* width: calc(100% - 40px); */
  background-color: #212121;
  border-radius: 10px;
  transform: translate(-50%, -50%) scale(0.9);
  left: 50%;
  top: 50%;
  opacity: 0;
  pointer-events: none;
  transition: transform 0.3s ease, opacity 0.1s;
  flex-direction: column;
  align-items: center;
}

.info-container {
  display: flex;
  padding: 10px;
  z-index: 2;
  flex-direction: column;
  margin-bottom: 25px;
}

.info-row {
  display: flex;
  justify-content: space-between;
  margin-bottom: 10px;
}

.info-label {
  font-weight: 300;
}

.info-value {
  font-weight: 500;
}

.buttons-row {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
}

.control-btn {
  display: flex;
  height: 32px;
  width: 32px;
  background: none;
  border: none;
  outline: none;
  align-items: center;
  justify-content: center;
  padding: 0;
  position: relative;
  cursor: pointer;
  transition: all 0.1s;
}

.control-btn .control-btn-icon {
  position: absolute;
  width: 100%;
  height: 100%;
}

.control-btn .control-btn-glow {
  opacity: 0;
  transition: opacity 0.2s;
  filter: blur(5px);
}

.control-btn:hover .control-btn-glow {
  opacity: 0.2;
}

.control-btn:hover {
  transform: scale(1.05);
}

.control-btn:active {
  transform: scale(0.98);
}


.hide-logo .button-logo {
  transition: opacity 0.1s ease;
  opacity: 0;
}


.shrink-button .toggle-container {
  height: 100px;
}

.shrink-button {
  #toggleButton {
    height: 35px;
    width: 130px;
    border-radius: 50px;
  }
  #toggleButton::before {
    opacity: 0;
    animation: none;
  }
  #toggleButton:hover {
    transform: scale(1);
    opacity: 0.95;
  }
  #toggleButton:active {
    transform: scale(1);
    opacity: 0.9;
  }
}
.display-tb-title {
  .b-title {
    transition: opacity 0.3s;
    opacity: 1;
  }
}

.shrink-button #toggleButton::before {
  opacity: 0;
}


.show-menu .menu-container {
  transition: transform 0.2s, opacity 0.2s ease;
  pointer-events: all;
  transform: translate(-50%, -50%) scale(1);
  opacity: 1;
}


#toggleButton.connected {
  background-color: var(--violet-color);
}

.b-title {
  transition: opacity 0.1s;
  position: absolute;
  opacity: 0;
  font-family: 'Roboto', sans-serif;
  color: rgba(255, 255, 255, 0.93);
}




.setting-bar {
  display: flex;
  width: 100%;
  gap: 20px;
  /* height: 50px; */
  /* background-color: rgb(255 255 255 / 0%); */
  align-items: center;
  justify-content: space-between;
  padding: 10px;
  cursor: pointer;
}
.setting-tile {
  font-family: 'Roboto', sans-serif;
  font-weight: 500;
  font-size: 15px;
}
.setting-switcher {
  display: flex;
  height: 30px;
  width: 54px;
  background-color: #ffffff26;
  align-items: center;
  justify-content: center;
  border-radius: 30px;
}
.switcher-ball {
  height: 24px;
  width: 24px;
  background-color: white;
  border-radius: 100%;
  transform: translateX(-50%);
  pointer-events: none;
  transition: all 0.1s;
}

.setting-bar.active .setting-switcher {
  background-color: var(--violet-color);
}

.switcher-ball-active {
  transform: translateX(50%);
}

```


js.background.js
```js
// Existing proxy configuration
const proxyConfig = {
  mode: "fixed_servers",
  rules: {
    singleProxy: {
      scheme: "socks5",
      host: "185.106.94.81",
      port: 44590
    },
    bypassList: ["localhost"]
  }
};

// Function to connect to VPN
function connectVPN() {
  chrome.proxy.settings.set({value: proxyConfig, scope: 'regular'}, function() {
    chrome.storage.local.set({isConnected: true});
  });
}

// Function to disconnect from VPN
function disconnectVPN() {
  chrome.proxy.settings.clear({scope: 'regular'}, function() {
    chrome.storage.local.set({isConnected: false});
  });
}

// Check auto-connect setting on startup
chrome.runtime.onStartup.addListener(function() {
  chrome.storage.local.get(['autoConnect', 'isConnected'], function(data) {
    if (data.autoConnect && !data.isConnected) {
      connectVPN();
    }
  });
});

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
  if (request.action === 'toggle') {
    chrome.storage.local.get('isConnected', function(data) {
      const isConnected = !data.isConnected;
      if (isConnected) {
        connectVPN();
      } else {
        disconnectVPN();
      }
      sendResponse({isConnected: isConnected});
    });
    return true;
  } else if (request.action === 'getIP') {
    fetch('https://api.ipify.org?format=json')
      .then(response => response.json())
      .then(data => sendResponse({ip: data.ip}))
      .catch(() => sendResponse({ip: 'Unable to fetch IP'}));
    return true;
  }
});
```


js.menu.js
```js
$(document).ready(function() {
  let isMenuMode = false;

  function enterMenuMode() {
    if (!isMenuMode) {
      isMenuMode = true;
      $('.dark-vpn-title').css("opacity", "0")
      $('body').addClass('hide-logo');
      $('body').addClass('shrink-button');
      setTimeout(() => {
        $('body').addClass('display-tb-title');
      }, 100);
      setTimeout(() => {
        $('body').addClass('show-menu');
      }, 200);
    }
  }

  function exitMenuMode() {
    if (isMenuMode) {
      isMenuMode = false;
      $('.dark-vpn-title').css("opacity", "1")
      $('body').removeClass('show-menu');
      $('body').removeClass('shrink-button');
      $('body').removeClass('hide-logo');
      $('body').removeClass('display-tb-title');
    }
  }

  function UpdateConnectButtonTitle() {
    if ($('#toggleButton').hasClass('connected')) {
      $('.b-title').text("Disconnect");
    } else {
      $('.b-title').text("Connect");
    }
  }

  function toggleConnection() {
    $('#toggleButton').toggleClass('connected');
    UpdateConnectButtonTitle();
  }

  $('#home-btn').click(exitMenuMode);
  $('#menu-btn').click(enterMenuMode);
  $('#toggleButton').click(toggleConnection);

  // Load saved settings
  chrome.storage.local.get(['autoConnect', 'adBlock'], function(data) {
    if (data.autoConnect) {
      $('#autoConnectSetting').addClass('active');
      $('#autoConnectSetting .switcher-ball').addClass('switcher-ball-active');
    }
    if (data.adBlock) {
      $('#adBlockSetting').addClass('active');
      $('#adBlockSetting .switcher-ball').addClass('switcher-ball-active');
    }
  });

  // Save settings when toggled
  $('.setting-bar').click(function() {
    $(this).toggleClass('active');
    $(this).find('.switcher-ball').toggleClass('switcher-ball-active');

    const settingId = $(this).attr('id');
    const isActive = $(this).hasClass('active');

    if (settingId === 'autoConnectSetting') {
      chrome.storage.local.set({autoConnect: isActive});
    } else if (settingId === 'adBlockSetting') {
      chrome.storage.local.set({adBlock: isActive});
    }
  });
});
```


js.popup.js
```js
document.addEventListener('DOMContentLoaded', function() {
  const toggleButton = document.getElementById('toggleButton');
  const ipElement = document.getElementById('ip');

  function updateButtonState(isConnected) {
    // Проверяем, что isConnected определена и имеет значение true
    if (isConnected) {
      toggleButton.classList.add('connected');
      $('.b-title').text("Disconnect");
      chrome.runtime.sendMessage({action: 'getIP'}, function(response) {
        ipElement.textContent = response.ip;
      });
    } else {
      // В противном случае, удаляем класс 'connected'
      toggleButton.classList.remove('connected');
      ipElement.textContent = '-.-.-.-';
      $('.b-title').text("Connect");
    }
  }

  chrome.storage.local.get('isConnected', function(data) {
    updateButtonState(data.isConnected);
  });

  toggleButton.addEventListener('click', function() {
    chrome.runtime.sendMessage({action: 'toggle'}, function(response) {
      updateButtonState(response.isConnected);
    });
  });
});
```



Это код моегро расширения для VPN.
Можешь пожалуйста сделать в нем следующее:
- Аутентификацию

1 - Код прокси сервера я изменить не могу. Нужно что-то придумать, чтобы если человек, скажем, не оплатил, то он не мог бы пользоваться.
Напиши для этого сервер Flask, который бы имел API эндпоинты, на которые расширение бы каждые 10 секунд отправляло запрос, и если получало добро - то продолжало работу, если ошибку - продолжало, если запрет - то отключение.
Нужно реализовать это максимально грамотно, чтобы нельзя было зареверсить. Давай, чтобы было больше безопасности, непосредственно сам ip прокси сервера на js лежать не будет, мы его будем запрашивать у API эндпоинт на Flask, если все успешно.

Сделай пожалуйста еще админ-панель на этом же сервере Flask `/admin`.
Сделай так, чтобы там сначала было текстовое поле и кнопка Login. В это текстовое поле нужно будет ввести секретный ключ для входа(определяется в переменной на сервере).
Основной интерфейс должен включать hotbar слева, основной contant справа.
Должен быть контейнер grid с квадратными tile пользователей.
У каждого пользователя будет имя, его ip, подключен ли сейчас, когда истекает подписка.
Если навестись на квадрат tile, то появятся кнопки: "Ban", "Stop"/"UnStop", "Renew".
Последняя кнопка должна показывать контейнер по центру поверх всего остального, в котором будет title - "Renew".
Там будет информация о пользователе, а также когда заканчивается подписка(сколько осталось/закончилась).
Там будет текстовое поле с количеством дней, сколько продлить и кнопка "Renew". По ее нажатию у этого пользователя в бд сместится подписка(если уже была, то сложится, если не было/закончилась, то станет равна значению).
Сделай так, чтобы там использовался socketio в интерфейсе admin-панели.
`на которые расширение бы каждые 10 секунд отправляло запрос` - вместе с запросом должна отправляться информация о том, на каком вебсайте сейчас конкретно находится пользователь - его домен вместе со /.
В tile у пользователя в админ-панели должно в реальном времени это обновляться и показываться.


Может быть большая проблема - иногда браузер очищает память расширений, видимо, это ошибка. По этому, простой ввод индивидуального ключа для каждого пользователя моет быть затруднен, т.к. он в какой-то момент может просто очиститься и нужно будет вводиться нова.

А, сделай так, чтобы на API эндпоинта отправлялась также обязательное поле - версия расширения, и если она отличается от последней, то API вернет ошибку.

Спасибо!
Реализуй это пожалуйста полностью.