forked from mirrors/amnezia-wg-easy
		
	Feat expiration date (#1296)
Closes #1287 Co-authored-by: Vadim Babadzhanyan <vadim.babadzhanyan@my.games>
This commit is contained in:
		
							parent
							
								
									40af030266
								
							
						
					
					
						commit
						8145809e22
					
				
					 14 changed files with 241 additions and 21 deletions
				
			
		| 
						 | 
				
			
			@ -40,3 +40,4 @@ module.exports.UI_TRAFFIC_STATS = process.env.UI_TRAFFIC_STATS || 'false';
 | 
			
		|||
module.exports.UI_CHART_TYPE = process.env.UI_CHART_TYPE || 0;
 | 
			
		||||
module.exports.UI_SHOW_LINKS = process.env.UI_SHOW_LINKS || 'false';
 | 
			
		||||
module.exports.UI_ENABLE_SORT_CLIENTS = process.env.UI_ENABLE_SORT_CLIENTS || 'false';
 | 
			
		||||
module.exports.WG_ENABLE_EXPIRES_TIME = process.env.WG_ENABLE_EXPIRES_TIME || 'false';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,6 +35,7 @@ const {
 | 
			
		|||
  UI_CHART_TYPE,
 | 
			
		||||
  UI_SHOW_LINKS,
 | 
			
		||||
  UI_ENABLE_SORT_CLIENTS,
 | 
			
		||||
  WG_ENABLE_EXPIRES_TIME,
 | 
			
		||||
} = require('../config');
 | 
			
		||||
 | 
			
		||||
const requiresPassword = !!PASSWORD_HASH;
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +60,11 @@ const isPasswordValid = (password) => {
 | 
			
		|||
  return false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cronJobEveryMinute = async () => {
 | 
			
		||||
  await WireGuard.cronJobEveryMinute();
 | 
			
		||||
  setTimeout(cronJobEveryMinute, 60 * 1000);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = class Server {
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
| 
						 | 
				
			
			@ -110,6 +116,11 @@ module.exports = class Server {
 | 
			
		|||
        return `${UI_ENABLE_SORT_CLIENTS}`;
 | 
			
		||||
      }))
 | 
			
		||||
 | 
			
		||||
      .get('/api/wg-enable-expire-time', defineEventHandler((event) => {
 | 
			
		||||
        setHeader(event, 'Content-Type', 'application/json');
 | 
			
		||||
        return `${WG_ENABLE_EXPIRES_TIME}`;
 | 
			
		||||
      }))
 | 
			
		||||
 | 
			
		||||
      // Authentication
 | 
			
		||||
      .get('/api/session', defineEventHandler((event) => {
 | 
			
		||||
        const authenticated = requiresPassword
 | 
			
		||||
| 
						 | 
				
			
			@ -224,7 +235,8 @@ module.exports = class Server {
 | 
			
		|||
      }))
 | 
			
		||||
      .post('/api/wireguard/client', defineEventHandler(async (event) => {
 | 
			
		||||
        const { name } = await readBody(event);
 | 
			
		||||
        await WireGuard.createClient({ name });
 | 
			
		||||
        const { expiredDate } = await readBody(event);
 | 
			
		||||
        await WireGuard.createClient({ name, expiredDate });
 | 
			
		||||
        return { success: true };
 | 
			
		||||
      }))
 | 
			
		||||
      .delete('/api/wireguard/client/:clientId', defineEventHandler(async (event) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -265,6 +277,15 @@ module.exports = class Server {
 | 
			
		|||
        const { address } = await readBody(event);
 | 
			
		||||
        await WireGuard.updateClientAddress({ clientId, address });
 | 
			
		||||
        return { success: true };
 | 
			
		||||
      }))
 | 
			
		||||
      .put('/api/wireguard/client/:clientId/expireDate', defineEventHandler(async (event) => {
 | 
			
		||||
        const clientId = getRouterParam(event, 'clientId');
 | 
			
		||||
        if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') {
 | 
			
		||||
          throw createError({ status: 403 });
 | 
			
		||||
        }
 | 
			
		||||
        const { expireDate } = await readBody(event);
 | 
			
		||||
        await WireGuard.updateClientExpireDate({ clientId, expireDate });
 | 
			
		||||
        return { success: true };
 | 
			
		||||
      }));
 | 
			
		||||
 | 
			
		||||
    const safePathJoin = (base, target) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -340,6 +361,8 @@ module.exports = class Server {
 | 
			
		|||
 | 
			
		||||
    createServer(toNodeListener(app)).listen(PORT, WEBUI_HOST);
 | 
			
		||||
    debug(`Listening on http://${WEBUI_HOST}:${PORT}`);
 | 
			
		||||
 | 
			
		||||
    cronJobEveryMinute();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,6 +24,7 @@ const {
 | 
			
		|||
  WG_POST_UP,
 | 
			
		||||
  WG_PRE_DOWN,
 | 
			
		||||
  WG_POST_DOWN,
 | 
			
		||||
  WG_ENABLE_EXPIRES_TIME,
 | 
			
		||||
} = require('../config');
 | 
			
		||||
 | 
			
		||||
module.exports = class WireGuard {
 | 
			
		||||
| 
						 | 
				
			
			@ -147,6 +148,9 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : ''
 | 
			
		|||
      publicKey: client.publicKey,
 | 
			
		||||
      createdAt: new Date(client.createdAt),
 | 
			
		||||
      updatedAt: new Date(client.updatedAt),
 | 
			
		||||
      expiredAt: client.expiredAt !== null
 | 
			
		||||
        ? new Date(client.expiredAt)
 | 
			
		||||
        : null,
 | 
			
		||||
      allowedIPs: client.allowedIPs,
 | 
			
		||||
      hash: Math.abs(CRC32.str(clientId)).toString(16),
 | 
			
		||||
      downloadableConfig: 'privateKey' in client,
 | 
			
		||||
| 
						 | 
				
			
			@ -227,7 +231,7 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
 | 
			
		|||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async createClient({ name }) {
 | 
			
		||||
  async createClient({ name, expiredDate }) {
 | 
			
		||||
    if (!name) {
 | 
			
		||||
      throw new Error('Missing: Name');
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -256,7 +260,6 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
 | 
			
		|||
    if (!address) {
 | 
			
		||||
      throw new Error('Maximum number of clients reached.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Create Client
 | 
			
		||||
    const id = crypto.randomUUID();
 | 
			
		||||
    const client = {
 | 
			
		||||
| 
						 | 
				
			
			@ -269,10 +272,15 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
 | 
			
		|||
 | 
			
		||||
      createdAt: new Date(),
 | 
			
		||||
      updatedAt: new Date(),
 | 
			
		||||
 | 
			
		||||
      expiredAt: null,
 | 
			
		||||
      enabled: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (expiredDate) {
 | 
			
		||||
      client.expiredAt = new Date(expiredDate);
 | 
			
		||||
      client.expiredAt.setHours(23);
 | 
			
		||||
      client.expiredAt.setMinutes(59);
 | 
			
		||||
      client.expiredAt.setSeconds(59);
 | 
			
		||||
    }
 | 
			
		||||
    config.clients[id] = client;
 | 
			
		||||
 | 
			
		||||
    await this.saveConfig();
 | 
			
		||||
| 
						 | 
				
			
			@ -329,6 +337,22 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
 | 
			
		|||
    await this.saveConfig();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async updateClientExpireDate({ clientId, expireDate }) {
 | 
			
		||||
    const client = await this.getClient({ clientId });
 | 
			
		||||
 | 
			
		||||
    if (expireDate) {
 | 
			
		||||
      client.expiredAt = new Date(expireDate);
 | 
			
		||||
      client.expiredAt.setHours(23);
 | 
			
		||||
      client.expiredAt.setMinutes(59);
 | 
			
		||||
      client.expiredAt.setSeconds(59);
 | 
			
		||||
    } else {
 | 
			
		||||
      client.expiredAt = null;
 | 
			
		||||
    }
 | 
			
		||||
    client.updatedAt = new Date();
 | 
			
		||||
 | 
			
		||||
    await this.saveConfig();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async __reloadConfig() {
 | 
			
		||||
    await this.__buildConfig();
 | 
			
		||||
    await this.__syncConfig();
 | 
			
		||||
| 
						 | 
				
			
			@ -355,4 +379,23 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`;
 | 
			
		|||
    await Util.exec('wg-quick down wg0').catch(() => {});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async cronJobEveryMinute() {
 | 
			
		||||
    const config = await this.getConfig();
 | 
			
		||||
    if (WG_ENABLE_EXPIRES_TIME === 'true') {
 | 
			
		||||
      let needSaveConfig = false;
 | 
			
		||||
      for (const client of Object.values(config.clients)) {
 | 
			
		||||
        if (client.enabled !== true) continue;
 | 
			
		||||
        if (client.expiredAt !== null && new Date() > new Date(client.expiredAt)) {
 | 
			
		||||
          debug(`Client ${client.id} expired.`);
 | 
			
		||||
          needSaveConfig = true;
 | 
			
		||||
          client.enabled = false;
 | 
			
		||||
          client.updatedAt = new Date();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (needSaveConfig) {
 | 
			
		||||
        await this.saveConfig();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										24
									
								
								src/package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										24
									
								
								src/package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -17,6 +17,7 @@
 | 
			
		|||
        "qrcode": "^1.5.4"
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
        "@tailwindcss/forms": "^0.5.7",
 | 
			
		||||
        "eslint-config-athom": "^3.1.3",
 | 
			
		||||
        "nodemon": "^3.1.4",
 | 
			
		||||
        "tailwindcss": "^3.4.10"
 | 
			
		||||
| 
						 | 
				
			
			@ -452,6 +453,19 @@
 | 
			
		|||
        "node": ">=14"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@tailwindcss/forms": {
 | 
			
		||||
      "version": "0.5.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
 | 
			
		||||
      "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "mini-svg-data-uri": "^1.2.3"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/json-schema": {
 | 
			
		||||
      "version": "7.0.15",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -3261,6 +3275,16 @@
 | 
			
		|||
        "node": ">=10.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/mini-svg-data-uri": {
 | 
			
		||||
      "version": "1.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "bin": {
 | 
			
		||||
        "mini-svg-data-uri": "cli.js"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/minimatch": {
 | 
			
		||||
      "version": "3.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,13 +16,14 @@
 | 
			
		|||
  "license": "CC BY-NC-SA 4.0",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "bcryptjs": "^2.4.3",
 | 
			
		||||
    "crc-32": "^1.2.2",
 | 
			
		||||
    "debug": "^4.3.6",
 | 
			
		||||
    "express-session": "^1.18.0",
 | 
			
		||||
    "h3": "^1.12.0",
 | 
			
		||||
    "qrcode": "^1.5.4",
 | 
			
		||||
    "crc-32": "^1.2.2"
 | 
			
		||||
    "qrcode": "^1.5.4"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@tailwindcss/forms": "^0.5.7",
 | 
			
		||||
    "eslint-config-athom": "^3.1.3",
 | 
			
		||||
    "nodemon": "^3.1.4",
 | 
			
		||||
    "tailwindcss": "^3.4.10"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -714,6 +714,10 @@ video {
 | 
			
		|||
  margin-bottom: 2.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mb-2 {
 | 
			
		||||
  margin-bottom: 0.5rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mb-4 {
 | 
			
		||||
  margin-bottom: 1rem;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1160,6 +1164,10 @@ video {
 | 
			
		|||
  fill: #4b5563;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-0 {
 | 
			
		||||
  padding: 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-1 {
 | 
			
		||||
  padding: 0.25rem;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1465,6 +1473,10 @@ video {
 | 
			
		|||
  cursor: default;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-0 {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.last\:border-b-0:last-child {
 | 
			
		||||
  border-bottom-width: 0px;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -127,7 +127,7 @@
 | 
			
		|||
              </button>
 | 
			
		||||
 | 
			
		||||
              <!-- New client -->
 | 
			
		||||
              <button @click="clientCreate = true; clientCreateName = '';"
 | 
			
		||||
              <button @click="clientCreate = true; clientCreateName = ''; clientExpiredDate = '';"
 | 
			
		||||
                class="hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 max-md:border-l-0 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded-r-full md:rounded inline-flex items-center transition">
 | 
			
		||||
                <svg class="w-4 md:mr-2" inline xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
 | 
			
		||||
                  stroke="currentColor">
 | 
			
		||||
| 
						 | 
				
			
			@ -259,6 +259,32 @@
 | 
			
		|||
                      <div v-if="uiShowLinks" :ref="'client-' + client.id + '-hash'" class="text-gray-400 text-xs">
 | 
			
		||||
                        <a :href="'./' + client.hash + ''">{{document.location.protocol}}//{{document.location.host}}/{{client.hash}}</a>
 | 
			
		||||
                      </div>
 | 
			
		||||
                      <!-- Expire Date -->
 | 
			
		||||
                      <div v-show="enableExpireTime" class=" block md:inline-block pb-1 md:pb-0 text-gray-500 dark:text-neutral-400 text-xs">
 | 
			
		||||
                        <span class="group">
 | 
			
		||||
                          <!-- Show -->
 | 
			
		||||
                          <input v-show="clientEditExpireDateId === client.id" v-model="clientEditExpireDate"
 | 
			
		||||
                            v-on:keyup.enter="updateClientExpireDate(client, clientEditExpireDate); clientEditExpireDate = null; clientEditExpireDateId = null;"
 | 
			
		||||
                            v-on:keyup.escape="clientEditExpireDate = null; clientEditExpireDateId = null;"
 | 
			
		||||
                            :ref="'client-' + client.id + '-expire'"
 | 
			
		||||
                            type="text"
 | 
			
		||||
                            class="rounded border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 outline-none w-70 text-black dark:text-neutral-300 dark:placeholder:text-neutral-500 text-xs p-0" />
 | 
			
		||||
                          <span v-show="clientEditExpireDateId !== client.id"
 | 
			
		||||
                            class="inline-block ">{{client.expiredAt  | expiredDateFormat}}</span>
 | 
			
		||||
 | 
			
		||||
                          <!-- Edit -->
 | 
			
		||||
                          <span v-show="clientEditExpireDateId !== client.id"
 | 
			
		||||
                            @click="clientEditExpireDate = client.expiredAt ? client.expiredAt.toISOString().slice(0, 10) : 'yyyy-mm-dd'; clientEditExpireDateId = client.id; setTimeout(() => $refs['client-' + client.id + '-expire'][0].select(), 1);"
 | 
			
		||||
                            class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity">
 | 
			
		||||
                            <svg xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                              class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100" fill="none"
 | 
			
		||||
                              viewBox="0 0 24 24" stroke="currentColor">
 | 
			
		||||
                              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
 | 
			
		||||
                                d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
 | 
			
		||||
                            </svg>
 | 
			
		||||
                          </span>
 | 
			
		||||
                        </span>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
                    <!-- Info -->
 | 
			
		||||
| 
						 | 
				
			
			@ -378,7 +404,7 @@
 | 
			
		|||
            <div v-if="clients && clients.length === 0">
 | 
			
		||||
              <p class="text-center m-10 text-gray-400 dark:text-neutral-400 text-sm">
 | 
			
		||||
                {{$t("noClients")}}<br /><br />
 | 
			
		||||
                <button @click="clientCreate = true; clientCreateName = '';"
 | 
			
		||||
                <button @click="clientCreate = true; clientCreateName = ''; clientExpiredDate = '';"
 | 
			
		||||
                  class="bg-red-800 hover:bg-red-700 text-white border-2 border-none py-2 px-4 rounded inline-flex items-center transition">
 | 
			
		||||
                  <svg class="w-4 mr-2" inline xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
 | 
			
		||||
                    stroke="currentColor">
 | 
			
		||||
| 
						 | 
				
			
			@ -470,6 +496,16 @@
 | 
			
		|||
                          type="text" v-model.trim="clientCreateName" :placeholder="$t('name')" />
 | 
			
		||||
                      </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="mt-2" v-show="enableExpireTime">
 | 
			
		||||
                      <p class="text-sm text-gray-500">
 | 
			
		||||
                        <label class="block text-gray-900 dark:text-neutral-200 text-sm font-bold mb-2" for="expireDate">
 | 
			
		||||
                            {{$t("ExpireDate")}}
 | 
			
		||||
                        </label>
 | 
			
		||||
                        <input
 | 
			
		||||
                          class="rounded p-2 border-2 dark:bg-neutral-700 dark:text-neutral-200 border-gray-100 dark:border-neutral-600 focus:border-gray-200 focus:dark:border-neutral-500 dark:placeholder:text-neutral-400 outline-none w-full"
 | 
			
		||||
                          type="date" v-model.trim="clientExpiredDate" :placeholder="$t('expireDate')" name="expireDate"/>
 | 
			
		||||
                      </p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,6 +71,13 @@ class API {
 | 
			
		|||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getWGEnableExpireTime() {
 | 
			
		||||
    return this.call({
 | 
			
		||||
      method: 'get',
 | 
			
		||||
      path: '/wg-enable-expire-time',
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getSession() {
 | 
			
		||||
    return this.call({
 | 
			
		||||
      method: 'get',
 | 
			
		||||
| 
						 | 
				
			
			@ -101,17 +108,20 @@ class API {
 | 
			
		|||
      ...client,
 | 
			
		||||
      createdAt: new Date(client.createdAt),
 | 
			
		||||
      updatedAt: new Date(client.updatedAt),
 | 
			
		||||
      expiredAt: client.expiredAt !== null
 | 
			
		||||
        ? new Date(client.expiredAt)
 | 
			
		||||
        : null,
 | 
			
		||||
      latestHandshakeAt: client.latestHandshakeAt !== null
 | 
			
		||||
        ? new Date(client.latestHandshakeAt)
 | 
			
		||||
        : null,
 | 
			
		||||
    })));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async createClient({ name }) {
 | 
			
		||||
  async createClient({ name, expiredDate }) {
 | 
			
		||||
    return this.call({
 | 
			
		||||
      method: 'post',
 | 
			
		||||
      path: '/wireguard/client',
 | 
			
		||||
      body: { name },
 | 
			
		||||
      body: { name, expiredDate },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -152,6 +162,14 @@ class API {
 | 
			
		|||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async updateClientExpireDate({ clientId, expireDate }) {
 | 
			
		||||
    return this.call({
 | 
			
		||||
      method: 'put',
 | 
			
		||||
      path: `/wireguard/client/${clientId}/expireDate/`,
 | 
			
		||||
      body: { expireDate },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async restoreConfiguration(file) {
 | 
			
		||||
    return this.call({
 | 
			
		||||
      method: 'put',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -77,10 +77,13 @@ new Vue({
 | 
			
		|||
    clientDelete: null,
 | 
			
		||||
    clientCreate: null,
 | 
			
		||||
    clientCreateName: '',
 | 
			
		||||
    clientExpiredDate: '',
 | 
			
		||||
    clientEditName: null,
 | 
			
		||||
    clientEditNameId: null,
 | 
			
		||||
    clientEditAddress: null,
 | 
			
		||||
    clientEditAddressId: null,
 | 
			
		||||
    clientEditExpireDate: null,
 | 
			
		||||
    clientEditExpireDateId: null,
 | 
			
		||||
    qrcode: null,
 | 
			
		||||
 | 
			
		||||
    currentRelease: null,
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +95,7 @@ new Vue({
 | 
			
		|||
    uiShowLinks: false,
 | 
			
		||||
    enableSortClient: false,
 | 
			
		||||
    sortClient: true, // Sort clients by name, true = asc, false = desc
 | 
			
		||||
    enableExpireTime: false,
 | 
			
		||||
 | 
			
		||||
    uiShowCharts: localStorage.getItem('uiShowCharts') === '1',
 | 
			
		||||
    uiTheme: localStorage.theme || 'auto',
 | 
			
		||||
| 
						 | 
				
			
			@ -296,9 +300,10 @@ new Vue({
 | 
			
		|||
    },
 | 
			
		||||
    createClient() {
 | 
			
		||||
      const name = this.clientCreateName;
 | 
			
		||||
      const expiredDate = this.clientExpiredDate;
 | 
			
		||||
      if (!name) return;
 | 
			
		||||
 | 
			
		||||
      this.api.createClient({ name })
 | 
			
		||||
      this.api.createClient({ name, expiredDate })
 | 
			
		||||
        .catch((err) => alert(err.message || err.toString()))
 | 
			
		||||
        .finally(() => this.refresh().catch(console.error));
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -327,6 +332,11 @@ new Vue({
 | 
			
		|||
        .catch((err) => alert(err.message || err.toString()))
 | 
			
		||||
        .finally(() => this.refresh().catch(console.error));
 | 
			
		||||
    },
 | 
			
		||||
    updateClientExpireDate(client, expireDate) {
 | 
			
		||||
      this.api.updateClientExpireDate({ clientId: client.id, expireDate })
 | 
			
		||||
        .catch((err) => alert(err.message || err.toString()))
 | 
			
		||||
        .finally(() => this.refresh().catch(console.error));
 | 
			
		||||
    },
 | 
			
		||||
    restoreConfig(e) {
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      const file = e.currentTarget.files.item(0);
 | 
			
		||||
| 
						 | 
				
			
			@ -370,6 +380,15 @@ new Vue({
 | 
			
		|||
    timeago: (value) => {
 | 
			
		||||
      return timeago.format(value, i18n.locale);
 | 
			
		||||
    },
 | 
			
		||||
    expiredDateFormat: (value) => {
 | 
			
		||||
      if (value === null) return i18n.t('Permanent');
 | 
			
		||||
      const dateTime = new Date(value);
 | 
			
		||||
      const options = { year: 'numeric', month: 'long', day: 'numeric' };
 | 
			
		||||
      return dateTime.toLocaleDateString(i18n.locale, options);
 | 
			
		||||
    },
 | 
			
		||||
    expiredDateEditFormat: (value) => {
 | 
			
		||||
      if (value === null) return 'yyyy-MM-dd';
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.prefersDarkScheme.addListener(this.handlePrefersChange);
 | 
			
		||||
| 
						 | 
				
			
			@ -433,6 +452,14 @@ new Vue({
 | 
			
		|||
        this.enableSortClient = false;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    this.api.getWGEnableExpireTime()
 | 
			
		||||
      .then((res) => {
 | 
			
		||||
        this.enableExpireTime = res;
 | 
			
		||||
      })
 | 
			
		||||
      .catch(() => {
 | 
			
		||||
        this.enableExpireTime = false;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    Promise.resolve().then(async () => {
 | 
			
		||||
      const lang = await this.api.getLang();
 | 
			
		||||
      if (lang !== localStorage.getItem('lang') && i18n.availableLocales.includes(lang)) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,6 +37,8 @@ const messages = { // eslint-disable-line no-unused-vars
 | 
			
		|||
    rememberMe: 'Remember me',
 | 
			
		||||
    titleRememberMe: 'Stay logged after closing the browser',
 | 
			
		||||
    sort: 'Sort',
 | 
			
		||||
    ExpireDate: 'Expire Date',
 | 
			
		||||
    Permanent: 'Permanent',
 | 
			
		||||
  },
 | 
			
		||||
  ua: {
 | 
			
		||||
    name: 'Ім`я',
 | 
			
		||||
| 
						 | 
				
			
			@ -108,6 +110,8 @@ const messages = { // eslint-disable-line no-unused-vars
 | 
			
		|||
    rememberMe: 'Запомнить меня',
 | 
			
		||||
    titleRememberMe: 'Оставаться в системе после закрытия браузера',
 | 
			
		||||
    sort: 'Сортировка',
 | 
			
		||||
    ExpireDate: 'Дата истечения срока',
 | 
			
		||||
    Permanent: 'Бессрочно',
 | 
			
		||||
  },
 | 
			
		||||
  tr: { // Müslüm Barış Korkmazer @babico
 | 
			
		||||
    name: 'İsim',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,7 @@
 | 
			
		|||
@tailwind base;
 | 
			
		||||
@tailwind components;
 | 
			
		||||
@tailwind utilities;
 | 
			
		||||
 | 
			
		||||
.p-0 {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue